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
try:
import weka.core.jvm as jvm
jvm.start()
from .weka_classifiers import WekaClassifiers
except:
WekaClassifiers = None
......@@ -17,7 +14,7 @@ from util.config import Config
_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)]
] )
......
......@@ -16,6 +16,21 @@ class Classifier(object):
def get_name(self):
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
def get_config(self):
......@@ -32,9 +47,16 @@ class Classifier(object):
def must_train(self):
return False
def train(self, dataset=None, training_data=None):
def train(self, dataset, training_data, force = False):
pass
@abstractmethod
def classify(self, dataset, test_data):
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 @@
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.classifiers import Classifier as WClassifier
from weka.classifiers import Evaluation as WEvaluation
......@@ -17,28 +19,36 @@ from collections import OrderedDict
from util.config import Config
from util.file_utils import File
from util.utils import TimeUtils
from weka_alias import WekaAlias
from classifier import Classifier
class WekaClassifiers(Classifier):
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)
def get_name(self):
return "Weka ML Algorithms"
self.reset()
def get_config(self):
weka_config = OrderedDict()
weka_config["classname"] = self.classname
weka_config["classname"].value = weka_config["classname"].value.split('.')[-1]
weka_config["options"] = self.options
return weka_config
def set_config(self, configs):
configs["classname"].value = WekaAlias.get_classifier(configs["classname"].value)
self.classname = Config.nvl_config(configs["classname"], self.classname)
self.options = Config.nvl_config(configs["options"], self.options)
......@@ -46,7 +56,7 @@ class WekaClassifiers(Classifier):
weka_config = OrderedDict()
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 = ''
for config in weka_config:
......@@ -58,7 +68,13 @@ class WekaClassifiers(Classifier):
def must_train(self):
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")
training_file = File.make_path(dataset, training_data + ".arff")
......@@ -93,3 +109,39 @@ class WekaClassifiers(Classifier):
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
from .tk_logger import Log
from .tk_menu import Menu
from .tk_itemmenu import ItemMenu, Command, Separator, CheckButton
from .tk_popup import Popup
from .tk_utils import Utils
__all__ = ["tk_canvas",
......@@ -13,5 +14,6 @@ __all__ = ["tk_canvas",
"tk_customframe",
"tk_logger",
"tk_menu",
"tk_itemmenu",
"tk_itemmenu",
"tk_popup",
"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):
self._grid.destroy();
def popup(self):
pass
def log_and_popup(self, message):
pass
def popup(self, message):
tk_local.Popup(self._root).show(message)
def dialogue_config(self, title, configs, callback):
......@@ -210,9 +207,13 @@ class TkInterface(Interface):
def quit(self):
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.destroy()
def debug(self, event):
print("DEBUG")
......
......@@ -66,6 +66,8 @@ if __name__ == "__main__":
tk.add_command("Choose classifier", act.select_classifier)
tk.add_command("Configure", act.configure_classifier)
tk.add_separator()
tk.add_command("Cross Validation", act.cross_validation, 'X')
tk.add_separator()
tk.add_command("Execute", act.run_classifier, 'C')
tk.render_menu()
......
......@@ -105,14 +105,15 @@ class Act(object):
self.tk.add_image(self._image, self._image_name, onclick)
self._const_image = self._image
self.segmenter.clean()
self.segmenter.reset()
def restore_image(self):
if self._const_image is not None:
self.tk.write_log("Restoring image...")
self.tk.refresh_image(self._const_image)
self.segmenter.clean()
self.segmenter.reset()
def close_image(self):
......@@ -198,6 +199,8 @@ class Act(object):
self._init_classes()
self.tk.refresh_panel_classes(self.classes)
if self.classifier: self.classifier.reset()
def toggle_dataset_generator(self):
self._dataset_generator = not self._dataset_generator
......@@ -232,7 +235,7 @@ class Act(object):
self.segmenter.set_config(new_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)
......@@ -284,6 +287,8 @@ class Act(object):
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("Time elapsed: %0.3f seconds", run_time)
if self.classifier: self.classifier.reset()
def select_classifier(self):
......@@ -320,6 +325,8 @@ class Act(object):
self.classifier.set_config(new_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)
......@@ -336,6 +343,7 @@ class Act(object):
start_time = TimeUtils.get_time()
list_segments = self.segmenter.get_list_segments()
if len(list_segments) == 0:
self.tk.append_log("Running %s... (%0.3f seconds)", self.segmenter.get_name(), (TimeUtils.get_time() - start_time))
......@@ -356,57 +364,75 @@ class Act(object):
self._image = self._const_image
# Original classification from python-superpixel, deprecated, use only for debug purposes
if hasattr(self, 'python_superpixel'):
for idx_segment in list_segments:
segment, size_segment = self.segmenter.get_segment(self, idx_segment=idx_segment)[0:2]
filepath = f.save_image(segment, self.dataset, "test")
if self.classifier.must_train():
output_file, _ = fextractor.extract_one_file(self.dataset, filepath, "test")
labels = self.classifier.classify(self.dataset, "test")
cl = self.get_class_by_name(labels[0])
self._image, _ = self.segmenter.paint_segment(self._image, cl["color"].value, idx_segment=[idx_segment], border=False)
self.tk.refresh_image(self._image)
# New and optimized classification
else:
tmp = ".tmp"
f.remove_dir(f.make_path(self.dataset, tmp))
tmp = ".tmp"
f.remove_dir(f.make_path(self.dataset, tmp))
self.tk.append_log("Generating test images... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
for idx_segment in list_segments:
segment, size_segment, idx_segment = self.segmenter.get_segment(self, idx_segment=idx_segment)[:-1]
filepath = f.save_class_image(segment, self.dataset, tmp, self._image_name, idx_segment)
if self.classifier.must_train():
self.tk.append_log("Running extractors on test images... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
output_file, _ = fextractor.extract_all(self.dataset, "test", dirs=[tmp])
self.tk.append_log("Running classifier on test data... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
self.tk.append_log("Generating test images... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
len_segments = {}
for idx_segment in list_segments:
segment, size_segment, idx_segment = self.segmenter.get_segment(self, idx_segment=idx_segment)[:-1]
labels = self.classifier.classify(self.dataset, "test")
f.remove_dir(f.make_path(self.dataset, tmp))
filepath = f.save_class_image(segment, self.dataset, tmp, self._image_name, idx_segment)
len_segments[idx_segment] = size_segment
self.tk.append_log("Painting segments... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
if self.classifier.must_train():
self.tk.append_log("Running extractors on test images... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
for cl in self.classes:
idx_segment = [ list_segments[idx] for idx in range(0, len(labels)) if cl["name"].value == labels[idx]]
if len(idx_segment) > 0:
self._image, _ = self.segmenter.paint_segment(self._image, cl["color"].value, idx_segment=idx_segment, border=False)
output_file, _ = fextractor.extract_all(self.dataset, "test", dirs=[tmp])
self.tk.append_log("Running classifier on test data... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
labels = self.classifier.classify(self.dataset, "test")
f.remove_dir(f.make_path(self.dataset, tmp))
self.tk.append_log("Painting segments... (%0.3f seconds)", (TimeUtils.get_time() - start_time))
self.tk.refresh_image(self._image)
popup_info = "%s\n" % str(self.classifier.get_summary_config())
len_total = sum([len_segments[idx] for idx in len_segments])
popup_info += "%-16s%-16s%0.2f%%\n" % ("Total", str(len_total), (len_total*100.0)/len_total)
for cl in self.classes:
idx_segment = [ list_segments[idx] for idx in range(0, len(labels)) if cl["name"].value == labels[idx]]
if len(idx_segment) > 0:
self._image, _ = self.segmenter.paint_segment(self._image, cl["color"].value, idx_segment=idx_segment, border=False)
len_classes = sum([len_segments[idx] for idx in idx_segment])
popup_info += "%-16s%-16s%0.2f%%\n" % (cl["name"].value, str(len_classes), (len_classes*100.0)/len_total)
self.tk.refresh_image(self._image)
self.tk.popup(popup_info)
end_time = TimeUtils.get_time()
self.tk.append_log("\nClassification finished")
self.tk.append_log("Time elapsed: %0.3f seconds", (end_time - start_time))
def cross_validation(self):
if self.classifier is None:
raise IException("You must install python-weka-wrapper")