Commit 94859823 authored by Alessandro dos Santos Ferreira's avatar Alessandro dos Santos Ferreira
Browse files

Pynovisao - Popup de estatistica dos classificadores e validacao cruzada

parent 2056b1bb
from .classifier import Classifier from .classifier import Classifier
try: try:
import weka.core.jvm as jvm
jvm.start()
from .weka_classifiers import WekaClassifiers from .weka_classifiers import WekaClassifiers
except: except:
WekaClassifiers = None WekaClassifiers = None
...@@ -17,7 +14,7 @@ from util.config import Config ...@@ -17,7 +14,7 @@ from util.config import Config
_classifier_list = OrderedDict( [ _classifier_list = OrderedDict( [
["weka_classifiers", Config("Invalid" if WekaClassifiers is None else WekaClassifiers().get_name(), ["weka_classifiers", Config("Invalid" if WekaClassifiers is None else WekaClassifiers.__name__,
WekaClassifiers is not None, bool, meta=WekaClassifiers, hidden=WekaClassifiers is None)] WekaClassifiers is not None, bool, meta=WekaClassifiers, hidden=WekaClassifiers is None)]
] ) ] )
......
...@@ -16,6 +16,21 @@ class Classifier(object): ...@@ -16,6 +16,21 @@ class Classifier(object):
def get_name(self): def get_name(self):
return self.__class__.__name__ return self.__class__.__name__
@staticmethod
def confusion_matrix(labels, matrix, title = None):
title = "Confusion Matrix" if title is None else "Confusion Matrix " + title
info = "=== " + title + " ===\n"
info += "\t".join(labels) + "\t<-- classified as\n"
for i in range(0, len(labels)):
for val in matrix[i]:
info += str(int(val)) + "\t"
info += "| %s\n" % (labels[i])
info += "\n\n"
return info
@abstractmethod @abstractmethod
def get_config(self): def get_config(self):
...@@ -32,9 +47,16 @@ class Classifier(object): ...@@ -32,9 +47,16 @@ class Classifier(object):
def must_train(self): def must_train(self):
return False return False
def train(self, dataset=None, training_data=None): def train(self, dataset, training_data, force = False):
pass pass
@abstractmethod @abstractmethod
def classify(self, dataset, test_data): def classify(self, dataset, test_data):
pass pass
def cross_validate(self, detail = True):
pass
@abstractmethod
def reset(self):
pass
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
"""
List of alias for classifiers available on python-weka-wrapper.
Name: weka_alias.py
Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com )
"""
from interface.interface import InterfaceException as IException
_weka_alias = {
### Bayes
"AODE": "weka.classifiers.bayes.AODE",
"AODEsr": "weka.classifiers.bayes.AODEsr",
"BayesianLogisticRegression": "weka.classifiers.bayes.BayesianLogisticRegression",
"BayesNet": "weka.classifiers.bayes.BayesNet",
"ComplementNaiveBayes": "weka.classifiers.bayes.ComplementNaiveBayes",
"DMNBtext": "weka.classifiers.bayes.DMNBtext",
"HNB": "weka.classifiers.bayes.HNB",
"NaiveBayes": "weka.classifiers.bayes.NaiveBayes",
"NaiveBayesMultinomial": "weka.classifiers.bayes.NaiveBayesMultinomial",
"NaiveBayesMultinomialUpdateable": "weka.classifiers.bayes.NaiveBayesMultinomialUpdateable",
"NaiveBayesMultinomialText": "weka.classifiers.bayes.NaiveBayesMultinomialText",
"NaiveBayesSimple": "weka.classifiers.bayes.NaiveBayesSimple",
"NaiveBayesUpdateable": "weka.classifiers.bayes.NaiveBayesUpdateable",
"WAODE": "weka.classifiers.bayes.WAODE",
### Functions
"GaussianProcesses": "weka.classifiers.functions.GaussianProcesses",
"IsotonicRegression": "weka.classifiers.functions.IsotonicRegression",
"LeastMedSq": "weka.classifiers.functions.LeastMedSq",
"LibLINEAR": "weka.classifiers.functions.LibLINEAR",
"LibSVM": "weka.classifiers.functions.LibSVM",
"LinearRegression": "weka.classifiers.functions.LinearRegression",
"Logistic": "weka.classifiers.functions.Logistic",
"MLPClassifier": "weka.classifiers.functions.MLPClassifier",
"MLPRegressor": "weka.classifiers.functions.MLPRegressor",
"MultilayerPerceptron": "weka.classifiers.functions.MultilayerPerceptron",
"NonNegativeLogisticRegression": "weka.classifiers.functions.NonNegativeLogisticRegression",
"PaceRegression": "weka.classifiers.functions.PaceRegression",
"PLSClassifier": "weka.classifiers.functions.PLSClassifier",
"RBFNetwork": "weka.classifiers.functions.RBFNetwork",
"RBFClassifier": "weka.classifiers.functions.RBFClassifier",
"RBFRegressor": "weka.classifiers.functions.RBFRegressor",
"SimpleLinearRegression": "weka.classifiers.functions.SimpleLinearRegression",
"SimpleLogistic": "weka.classifiers.functions.SimpleLogistic",
"SGD": "weka.classifiers.functions.SGD",
"SGDText": "weka.classifiers.functions.SGDText",
"SMO": "weka.classifiers.functions.SMO",
"SMOreg": "weka.classifiers.functions.SMOreg",
"SPegasos": "weka.classifiers.functions.SPegasos",
"SVMreg": "weka.classifiers.functions.SVMreg",
"VotedPerceptron": "weka.classifiers.functions.VotedPerceptron",
"Winnow": "weka.classifiers.functions.Winnow",
### Lazy
"IB1": "weka.classifiers.lazy.IB1",
"IBk": "weka.classifiers.lazy.IBk",
"KStar": "weka.classifiers.lazy.KStar",
"LBR": "weka.classifiers.lazy.LBR",
"LWL": "weka.classifiers.lazy.LWL",
# Meta
"AdaBoostM1": "weka.classifiers.meta.AdaBoostM1",
"AdditiveRegression": "weka.classifiers.meta.AdditiveRegression",
"AttributeSelectedClassifier": "weka.classifiers.meta.AttributeSelectedClassifier",
"Bagging": "weka.classifiers.meta.Bagging",
"ClassificationViaClustering": "weka.classifiers.meta.ClassificationViaClustering",
"ClassificationViaRegression": "weka.classifiers.meta.ClassificationViaRegression",
"CostSensitiveClassifier": "weka.classifiers.meta.CostSensitiveClassifier",
"CVParameterSelection": "weka.classifiers.meta.CVParameterSelection",
"Dagging": "weka.classifiers.meta.Dagging",
"Decorate": "weka.classifiers.meta.Decorate",
"END": "weka.classifiers.meta.END",
"EnsembleSelection": "weka.classifiers.meta.EnsembleSelection",
"FilteredClassifier": "weka.classifiers.meta.FilteredClassifier",
"Grading": "weka.classifiers.meta.Grading",
"GridSearch": "weka.classifiers.meta.GridSearch",
"LogitBoost": "weka.classifiers.meta.LogitBoost",
"MetaCost": "weka.classifiers.meta.MetaCost",
"MultiBoostAB": "weka.classifiers.meta.MultiBoostAB",
"MultiClassClassifier": "weka.classifiers.meta.MultiClassClassifier",
"MultiClassClassifierUpdateable": "weka.classifiers.meta.MultiClassClassifierUpdateable",
"MultiScheme": "weka.classifiers.meta.MultiScheme",
"OneClassClassifier": "weka.classifiers.meta.OneClassClassifier",
"OrdinalClassClassifier": "weka.classifiers.meta.OrdinalClassClassifier",
"RacedIncrementalLogitBoost": "weka.classifiers.meta.RacedIncrementalLogitBoost",
"RandomCommittee": "weka.classifiers.meta.RandomCommittee",
"RandomSubSpace": "weka.classifiers.meta.RandomSubSpace",
"RealAdaBoost": "weka.classifiers.meta.RealAdaBoost",
"RegressionByDiscretization": "weka.classifiers.meta.RegressionByDiscretization",
"RotationForest": "weka.classifiers.meta.RotationForest",
"Stacking": "weka.classifiers.meta.Stacking",
"StackingC": "weka.classifiers.meta.StackingC",
"ThresholdSelector": "weka.classifiers.meta.ThresholdSelector",
"Vote": "weka.classifiers.meta.Vote",
"ClassBalancedND": "weka.classifiers.meta.ClassBalancedND",
"DataNearBalancedND": "weka.classifiers.meta.DataNearBalancedND",
"ND": "weka.classifiers.meta.ND",
### Miscellaneous
"HyperPipes": "weka.classifiers.misc.sc.HyperPipes",
"InputMappedClassifier": "weka.classifiers.misc.sc.InputMappedClassifier",
"MinMaxExtension": "weka.classifiers.misc.sc.MinMaxExtension",
"OLM": "weka.classifiers.misc.sc.OLM",
"OSDL": "weka.classifiers.misc.sc.OSDL",
"SerializedClassifier": "weka.classifiers.misc.sc.SerializedClassifier",
"VFI": "weka.classifiers.misc.sc.VFI",
### Multi-instance
"CitationKNN": "weka.classifiers.mi.CitationKNN",
"MDD": "weka.classifiers.mi.MDD",
"MIBoost": "weka.classifiers.mi.MIBoost",
"MIDD": "weka.classifiers.mi.MIDD",
"MIEMDD": "weka.classifiers.mi.MIEMDD",
"MILR": "weka.classifiers.mi.MILR",
"MINND": "weka.classifiers.mi.MINND",
"MIOptimalBall": "weka.classifiers.mi.MIOptimalBall",
"MIRI": "weka.classifiers.mi.MIRI",
"MISMO": "weka.classifiers.mi.MISMO",
"MISVM": "weka.classifiers.mi.MISVM",
"MITI": "weka.classifiers.mi.MITI",
"MIWrapper": "weka.classifiers.mi.MIWrapper",
"SimpleMI": "weka.classifiers.mi.SimpleMI",
"TLD": "weka.classifiers.mi.TLD",
"TLDSimple": "weka.classifiers.mi.TLDSimple",
### Rules
"ConjunctiveRule": "weka.classifiers.rules.ConjunctiveRule",
"DecisionTable": "weka.classifiers.rules.DecisionTable",
"DTNB": "weka.classifiers.rules.DTNB",
"FURIA": "weka.classifiers.rules.FURIA",
"JRip": "weka.classifiers.rules.JRip",
"M5Rules": "weka.classifiers.rules.M5Rules",
"NNge": "weka.classifiers.rules.NNge",
"OneR": "weka.classifiers.rules.OneR",
"PART": "weka.classifiers.rules.PART",
"Prism": "weka.classifiers.rules.Prism",
"Ridor": "weka.classifiers.rules.Ridor",
"ZeroR": "weka.classifiers.rules.ZeroR",
### Trees
"ADTree": "weka.classifiers.trees.ADTree",
"BFTree": "weka.classifiers.trees.BFTree",
"DecisionStump": "weka.classifiers.trees.DecisionStump",
"ExtraTree": "weka.classifiers.trees.ExtraTree",
"FT": "weka.classifiers.trees.FT",
"HoeffdingTree": "weka.classifiers.trees.HoeffdingTree",
"Id3": "weka.classifiers.trees.Id3",
"J48": "weka.classifiers.trees.J48",
"J48graft": "weka.classifiers.trees.J48graft",
"LADTree": "weka.classifiers.trees.LADTree",
"LMT": "weka.classifiers.trees.LMT",
"M5P": "weka.classifiers.trees.M5P",
"NBTree": "weka.classifiers.trees.NBTree",
"RandomForest": "weka.classifiers.trees.RandomForest",
"RandomTree": "weka.classifiers.trees.RandomTree",
"REPTree": "weka.classifiers.trees.REPTree",
"SimpleCart": "weka.classifiers.trees.SimpleCart",
"UserClassifier": "weka.classifiers.trees.UserClassifier"
}
class WekaAlias(object):
@staticmethod
def get_classifier(name):
classifiers = [_weka_alias[alias] for alias in _weka_alias]
if name in classifiers:
return name
alias = name.upper().strip()
aliases = [a.upper() for a in _weka_alias]
if alias in aliases:
return _weka_alias.values()[ aliases.index(alias) ]
raise IException('Invalid classifier')
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com ) Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com )
""" """
import weka.core.jvm as jvm
from weka.core.converters import Loader as WLoader from weka.core.converters import Loader as WLoader
from weka.classifiers import Classifier as WClassifier from weka.classifiers import Classifier as WClassifier
from weka.classifiers import Evaluation as WEvaluation from weka.classifiers import Evaluation as WEvaluation
...@@ -17,28 +19,36 @@ from collections import OrderedDict ...@@ -17,28 +19,36 @@ from collections import OrderedDict
from util.config import Config from util.config import Config
from util.file_utils import File from util.file_utils import File
from util.utils import TimeUtils
from weka_alias import WekaAlias
from classifier import Classifier from classifier import Classifier
class WekaClassifiers(Classifier): class WekaClassifiers(Classifier):
def __init__(self, classname="weka.classifiers.functions.SMO", options='default'): def __init__(self, classname="weka.classifiers.functions.SMO", options='default'):
self.classname = Config("ClassName", classname, 'classifier') if not jvm.started:
jvm.start()
self.classname = Config("ClassName", classname, str)
self.options = Config("Options", options, str) self.options = Config("Options", options, str)
self.reset()
def get_name(self):
return "Weka ML Algorithms"
def get_config(self): def get_config(self):
weka_config = OrderedDict() weka_config = OrderedDict()
weka_config["classname"] = self.classname weka_config["classname"] = self.classname
weka_config["classname"].value = weka_config["classname"].value.split('.')[-1]
weka_config["options"] = self.options weka_config["options"] = self.options
return weka_config return weka_config
def set_config(self, configs): def set_config(self, configs):
configs["classname"].value = WekaAlias.get_classifier(configs["classname"].value)
self.classname = Config.nvl_config(configs["classname"], self.classname) self.classname = Config.nvl_config(configs["classname"], self.classname)
self.options = Config.nvl_config(configs["options"], self.options) self.options = Config.nvl_config(configs["options"], self.options)
...@@ -46,7 +56,7 @@ class WekaClassifiers(Classifier): ...@@ -46,7 +56,7 @@ class WekaClassifiers(Classifier):
weka_config = OrderedDict() weka_config = OrderedDict()
weka_config[self.classname.label] = self.classname.value weka_config[self.classname.label] = self.classname.value
weka_config[self.options.label] = self.options.value.strip() weka_config[self.options.label] = self.options.value
summary = '' summary = ''
for config in weka_config: for config in weka_config:
...@@ -58,7 +68,13 @@ class WekaClassifiers(Classifier): ...@@ -58,7 +68,13 @@ class WekaClassifiers(Classifier):
def must_train(self): def must_train(self):
return True return True
def train(self, dataset, training_data): def train(self, dataset, training_data, force = False):
if self.data is not None and not force:
return
if self.data is not None:
self.reset()
loader = WLoader(classname="weka.core.converters.ArffLoader") loader = WLoader(classname="weka.core.converters.ArffLoader")
training_file = File.make_path(dataset, training_data + ".arff") training_file = File.make_path(dataset, training_data + ".arff")
...@@ -93,3 +109,39 @@ class WekaClassifiers(Classifier): ...@@ -93,3 +109,39 @@ class WekaClassifiers(Classifier):
return classes return classes
def cross_validate(self, detail = True):
start_time = TimeUtils.get_time()
info = "Scheme:\t%s %s\n" % (str(self.classifier.classname) , " ".join([str(option) for option in self.classifier.options]))
if detail == True:
info += "Relation:\t%s\n" % (self.data.relationname)
info += "Instances:\t%d\n" % (self.data.num_instances)
info += "Attributes:\t%d\n\n" % (self.data.num_attributes)
evl = WEvaluation(self.data)
evl.crossvalidate_model(self.classifier, self.data, 10, WRandom(1))
if detail == False:
info += "Correctly Classified Instances: %0.4f%%\n" % (evl.percent_correct)
info += "Time taken to build model: %0.5f seconds\n\n" % (TimeUtils.get_time() - start_time)
#info += str(evl.percent_correct) + "\n\n"
if detail == True:
info += "=== Stratified cross-validation ===\n"
info += evl.summary() + "\n\n"
info += str(evl.class_details()) + "\n\n"
classes = [str(self.data.class_attribute.value(i)) for i in range(0, self.data.class_attribute.num_values)]
cm = evl.confusion_matrix
info += Classifier.confusion_matrix(classes, cm)
return info
def reset(self):
self.data = None
self.classifier = None
...@@ -5,6 +5,7 @@ from .tk_customframe import CustomGrid ...@@ -5,6 +5,7 @@ from .tk_customframe import CustomGrid
from .tk_logger import Log from .tk_logger import Log
from .tk_menu import Menu from .tk_menu import Menu
from .tk_itemmenu import ItemMenu, Command, Separator, CheckButton from .tk_itemmenu import ItemMenu, Command, Separator, CheckButton
from .tk_popup import Popup
from .tk_utils import Utils from .tk_utils import Utils
__all__ = ["tk_canvas", __all__ = ["tk_canvas",
...@@ -13,5 +14,6 @@ __all__ = ["tk_canvas", ...@@ -13,5 +14,6 @@ __all__ = ["tk_canvas",
"tk_customframe", "tk_customframe",
"tk_logger", "tk_logger",
"tk_menu", "tk_menu",
"tk_itemmenu", "tk_itemmenu",
"tk_popup",
"tk_utils"] "tk_utils"]
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
"""
Shows a message popup using Tkinter window.
Name: tk_popup.py
Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com )
"""
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
else:
import tkinter as Tk
import thread
class Popup(object):
def __init__(self, parent):
self.parent = parent
def _blocking_popup(self, message, width, height, title = 'Popup'):
if message is None:
return
self.root = Tk.Tk()
self.root.title(title)
self.root.geometry('%dx%d+%d+%d' % (width, height, 0, 0))
text = Tk.Text(self.root, bg="white", fg="black", padx=5, pady=5)
text.insert(Tk.INSERT, message)
#text.configure(state='disabled')
text.pack(side=Tk.TOP, fill=Tk.BOTH, expand=True)
self.root.mainloop()
def show(self, message, width=800, height=600):
thread.start_new_thread(self._blocking_popup, (message, width, height))
...@@ -160,11 +160,8 @@ class TkInterface(Interface): ...@@ -160,11 +160,8 @@ class TkInterface(Interface):
self._grid.destroy(); self._grid.destroy();
def popup(self): def popup(self, message):
pass tk_local.Popup(self._root).show(message)
def log_and_popup(self, message):
pass
def dialogue_config(self, title, configs, callback): def dialogue_config(self, title, configs, callback):
...@@ -210,9 +207,13 @@ class TkInterface(Interface): ...@@ -210,9 +207,13 @@ class TkInterface(Interface):
def quit(self): def quit(self):
if tkMessageBox.askokcancel("Quit", "Do you want to quit?"): if tkMessageBox.askokcancel("Quit", "Do you want to quit?"):
try:
import weka.core.jvm as jvm
jvm.stop()
except:
pass
self._root.quit() self._root.quit()
self._root.destroy() self._root.destroy()
def debug(self, event): def debug(self, event):
print("DEBUG") print("DEBUG")
......
...@@ -66,6 +66,8 @@ if __name__ == "__main__": ...@@ -66,6 +66,8 @@ if __name__ == "__main__":
tk.add_command("Choose classifier", act.select_classifier) tk.add_command("Choose classifier", act.select_classifier)
tk.add_command("Configure", act.configure_classifier) tk.add_command("Configure", act.configure_classifier)
tk.add_separator() tk.add_separator()
tk.add_command("Cross Validation", act.cross_validation, 'X')
tk.add_separator()
tk.add_command("Execute", act.run_classifier, 'C') tk.add_command("Execute", act.run_classifier, 'C')
tk.render_menu() tk.render_menu()
......
...@@ -105,14 +105,15 @@ class Act(object): ...@@ -105,14 +105,15 @@ class Act(object):
self.tk.add_image(self._image, self._image_name, onclick) self.tk.add_image(self._image, self._image_name, onclick)
self._const_image = self._image self._const_image = self._image
self.segmenter.clean() self.segmenter.reset()
def restore_image(self): def restore_image(self):
if self._const_image is not None: if self._const_image is not None:
self.tk.write_log("Restoring image...") self.tk.write_log("Restoring image...")
self.tk.refresh_image(self._const_image) self.tk.refresh_image(self._const_image)
self.segmenter.clean() self.segmenter.reset()
def close_image(self): def close_image(self):
...@@ -198,6 +199,8 @@ class Act(object): ...@@ -198,6 +199,8 @@ class Act(object):
self._init_classes() self._init_classes()
self.tk.refresh_panel_classes(self.classes) self.tk.refresh_panel_classes(self.classes)
if self.classifier: self.classifier.reset()
def toggle_dataset_generator(self): def toggle_dataset_generator(self):
self._dataset_generator = not self._dataset_generator self._dataset_generator = not self._dataset_generator
...@@ -232,7 +235,7 @@ class Act(object): ...@@ -232,7 +235,7 @@ class Act(object):
self.segmenter.set_config(new_config) self.segmenter.set_config(new_config)
self.tk.append_log("\nConfig updated:\n%s", str(self.segmenter.get_summary_config())) self.tk.append_log("\nConfig updated:\n%s", str(self.segmenter.get_summary_config()))
self.segmenter.clean() self.segmenter.reset()
self.tk.dialogue_config(title, current_config, process_config) self.tk.dialogue_config(title, current_config, process_config)
...@@ -284,6 +287,8 @@ class Act(object): ...@@ -284,6 +287,8 @@ class Act(object):
output_file, run_time = fextractor.extract_all(self.dataset, "training") output_file, run_time = fextractor.extract_all(self.dataset, "training")
self.tk.append_log("\nOutput file saved in %s", output_file) self.tk.append_log("\nOutput file saved in %s", output_file)
self.tk.append_log("Time elapsed: %0.3f seconds", run_time) self.tk.append_log("Time elapsed: %0.3f seconds", run_time)
if self.classifier: self.classifier.reset()
def select_classifier(self): def select_classifier(self):
...@@ -320,6 +325,8 @@ class Act(object): ...@@ -320,6 +325,8 @@ class Act(object):
self.classifier.set_config(new_config) self.classifier.set_config(new_config)
self.tk.append_log("\nConfig updated:\n%s", str(self.classifier.get_summary_config())) self.tk.append_log("\nConfig updated:\n%s", str(self.classifier.get_summary_config()))
if self.classifier: self.classifier.reset()
self.tk.dialogue_config(title, current_config, process_config) self.tk.dialogue_config(title, current_config, process_config)
...@@ -336,6 +343,7 @@ class Act(object): ...@@ -336,6 +343,7 @@ class Act(object):