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

Pynovisao - Documentando classifiers

parent d4b928d1
...@@ -13,14 +13,33 @@ from interface.interface import InterfaceException as IException ...@@ -13,14 +13,33 @@ from interface.interface import InterfaceException as IException
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
class Classifier(object): class Classifier(object):
"""Abstract class for classifiers algorithms."""
__metaclass__ = ABCMeta __metaclass__ = ABCMeta
def get_name(self): def get_name(self):
"""Return the name of class.
Returns
-------
name : string
Returns the name of instantiated class.
"""
return self.__class__.__name__ return self.__class__.__name__
@staticmethod @staticmethod
def confusion_matrix(labels, matrix, title = None): def confusion_matrix(labels, matrix, title = None):
"""Return a formatted confusion matrix.
Returns
-------
labels : list of string
List of name of classes of confusion matrix.
matrix : list of list of int
Matrix of confusion.
title : string, optional, default = None
Title of confusion matrix.
"""
title = "Confusion Matrix" if title is None else "Confusion Matrix " + title title = "Confusion Matrix" if title is None else "Confusion Matrix " + title
info = "=== " + title + " ===\n" info = "=== " + title + " ===\n"
...@@ -36,31 +55,54 @@ class Classifier(object): ...@@ -36,31 +55,54 @@ class Classifier(object):
@abstractmethod @abstractmethod
def get_config(self): def get_config(self):
"""Return configuration of classifier.
Implement this method to extend this class with a new classifier algorithm.
"""
pass pass
@abstractmethod @abstractmethod
def set_config(self, configs): def set_config(self, configs):
"""Update configuration of classifier.
Implement this method to extend this class with a new classifier algorithm.
"""
pass pass
@abstractmethod @abstractmethod
def get_summary_config(self): def get_summary_config(self):
"""Return fomatted summary of configuration.
Implement this method to extend this class with a new classifier algorithm.
"""
pass pass
def must_train(self): def must_train(self):
"""Return if classifier must be trained.
"""
return False return False
def train(self, dataset, training_data, force = False): def train(self, dataset, training_data, force = False):
"""Perform the training of classifier.
"""
pass pass
@abstractmethod @abstractmethod
def classify(self, dataset, test_dir = None, test_data = None): def classify(self, dataset, test_dir = None, test_data = None):
"""Perform the classification.
Implement this method to extend this class with a new classifier algorithm.
"""
pass pass
def cross_validate(self, detail = True): def cross_validate(self, detail = True):
"""Perform cross validation using trained data.
"""
raise IException("Method not available for this classifier") raise IException("Method not available for this classifier")
def experimenter(self): def experimenter(self):
"""Perform a test using all classifiers available.
"""
raise IException("Method not available for this classifier") raise IException("Method not available for this classifier")
def reset(self): def reset(self):
"""Clean all data of classification.
Implement this method to extend this class with a new classifier algorithm.
"""
pass pass
...@@ -32,11 +32,18 @@ from classifier import Classifier ...@@ -32,11 +32,18 @@ from classifier import Classifier
class CNNCaffe(Classifier): class CNNCaffe(Classifier):
CREATE_LMDB = False # I tried to use the default python interface to perform the classification as explained at here:
# http://nbviewer.jupyter.org/github/BVLC/caffe/blob/master/examples/00-classification.ipynb
# But for some unknown reason it didn't work as expected, it generated poor results.
# I kept the implementation anyway, to use must be set CREATE_LMDB = False.
# Otherwise it will used another approach that generates optimal results.
CREATE_LMDB = True
def __init__(self): def __init__(self):
"""Constructor.
"""
self.model_def = Config("ModelDef", '../examples/deploy.prototxt', str) self.model_def = Config("ModelDef", '../examples/deploy.prototxt', str)
self.model_weights = Config("ModelWeights", '../examples/caffenet_train_iter_7500.caffemodel', str) self.model_weights = Config("ModelWeights", '../examples/caffenet_train_iter_15000.caffemodel', str)
self.mean_image = Config("MeanImage", '../examples/imagenet_mean.binaryproto', str) self.mean_image = Config("MeanImage", '../examples/imagenet_mean.binaryproto', str)
self.labels_file = Config("LabelsFile", '../examples/labels.txt', str) self.labels_file = Config("LabelsFile", '../examples/labels.txt', str)
...@@ -56,6 +63,13 @@ class CNNCaffe(Classifier): ...@@ -56,6 +63,13 @@ class CNNCaffe(Classifier):
def get_config(self): def get_config(self):
"""Return configuration of classifier.
Returns
-------
config : OrderedDict
Current configs of classifier.
"""
caffe_config = OrderedDict() caffe_config = OrderedDict()
caffe_config["model_def"] = self.model_def caffe_config["model_def"] = self.model_def
...@@ -66,6 +80,13 @@ class CNNCaffe(Classifier): ...@@ -66,6 +80,13 @@ class CNNCaffe(Classifier):
return caffe_config return caffe_config
def set_config(self, configs): def set_config(self, configs):
"""Update configuration of classifier.
Parameters
----------
configs : OrderedDict
New configs of classifier.
"""
self.model_def = Config.nvl_config(configs["model_def"], self.model_def) self.model_def = Config.nvl_config(configs["model_def"], self.model_def)
self.model_weights = Config.nvl_config(configs["model_weights"], self.model_weights) self.model_weights = Config.nvl_config(configs["model_weights"], self.model_weights)
self.mean_image = Config.nvl_config(configs["mean_image"], self.mean_image) self.mean_image = Config.nvl_config(configs["mean_image"], self.mean_image)
...@@ -74,6 +95,13 @@ class CNNCaffe(Classifier): ...@@ -74,6 +95,13 @@ class CNNCaffe(Classifier):
self._create_net() self._create_net()
def get_summary_config(self): def get_summary_config(self):
"""Return fomatted summary of configuration.
Returns
-------
summary : string
Formatted string with summary of configuration.
"""
caffe_config = OrderedDict() caffe_config = OrderedDict()
caffe_config[self.model_def.label] = self.model_def.value caffe_config[self.model_def.label] = self.model_def.value
...@@ -89,6 +117,23 @@ class CNNCaffe(Classifier): ...@@ -89,6 +117,23 @@ class CNNCaffe(Classifier):
def classify(self, dataset, test_dir, test_data): def classify(self, dataset, test_dir, test_data):
"""Perform the classification.
Parameters
----------
dataset : string
Path to image dataset.
test_dir : string
Name of test data directory.
test_data : string
Not used.
Returns
-------
summary : list of string
List of predicted classes for each instance in test data in ordered way.
"""
# if CNNCaffe.CREATE_LMDB = True use the alternative approach.
if CNNCaffe.CREATE_LMDB: if CNNCaffe.CREATE_LMDB:
return self._classify_lmdb(dataset, test_dir, test_data) return self._classify_lmdb(dataset, test_dir, test_data)
...@@ -135,16 +180,33 @@ class CNNCaffe(Classifier): ...@@ -135,16 +180,33 @@ class CNNCaffe(Classifier):
# perform classification # perform classification
output = self.net.forward() output = self.net.forward()
# the output probability vector for the first image in the batch # the output probability vector for the each image in the batch
prediction = output['prob'][0] prediction = output['prob'][0]
print(["%0.4f" % pr for pr in prediction ]) print(["%0.4f" % pr for pr in prediction ])
# append the class with max probability.
classes.append(labels[prediction.argmax()]) classes.append(labels[prediction.argmax()])
return classes return classes
def _classify_lmdb(self, dataset, test_dir, test_data): def _classify_lmdb(self, dataset, test_dir, test_data):
"""Perform the alternative classification creating LMDB backend.
Parameters
----------
dataset : string
Path to image dataset.
test_dir : string
Name of test data directory.
test_data : string
Not used.
Returns
-------
summary : list of string
List of predicted classes for each instance in test data in ordered way.
"""
test_dir = File.make_path(dataset, test_dir) test_dir = File.make_path(dataset, test_dir)
classes = [] classes = []
......
...@@ -165,9 +165,27 @@ _weka_alias = { ...@@ -165,9 +165,27 @@ _weka_alias = {
} }
class WekaAlias(object): class WekaAlias(object):
"""Class of alias for classifiers available on python-weka-wrapper."""
@staticmethod @staticmethod
def get_classifier(name): def get_classifier(name):
"""Return full name of classifier.
Parameters
----------
name : string
Alias of classifier, not case sensitive.
Returns
-------
classifier : string
Full name of classifier.
Raises
------
IException 'Invalid classifier'
The user must install the required dependencies to classifiers.
"""
classifiers = [_weka_alias[alias] for alias in _weka_alias] classifiers = [_weka_alias[alias] for alias in _weka_alias]
if name in classifiers: if name in classifiers:
...@@ -184,4 +202,11 @@ class WekaAlias(object): ...@@ -184,4 +202,11 @@ class WekaAlias(object):
@staticmethod @staticmethod
def get_aliases(): def get_aliases():
"""Return all aliases.
Returns
-------
classifier : Dictionary
Dictionary with all aliases.
"""
return _weka_alias return _weka_alias
...@@ -27,8 +27,18 @@ from weka_alias import WekaAlias ...@@ -27,8 +27,18 @@ from weka_alias import WekaAlias
from classifier import Classifier from classifier import Classifier
class WekaClassifiers(Classifier): class WekaClassifiers(Classifier):
"""Class for all classifiers available in python-weka-wrapper"""
def __init__(self, classname="weka.classifiers.functions.SMO", options='default'): def __init__(self, classname="weka.classifiers.functions.SMO", options='default'):
"""Constructor.
Parameters
----------
classname : string, optional, default = 'weka.classifiers.functions.SMO'
Classifier initialized as default.
options : string, optional, default = 'default'
Classifier options initialized as default. Use the string 'default' to default options.
"""
if not jvm.started: if not jvm.started:
jvm.start() jvm.start()
...@@ -39,6 +49,13 @@ class WekaClassifiers(Classifier): ...@@ -39,6 +49,13 @@ class WekaClassifiers(Classifier):
def get_config(self): def get_config(self):
"""Return configuration of classifier.
Returns
-------
config : OrderedDict
Current configs of classifier.
"""
weka_config = OrderedDict() weka_config = OrderedDict()
weka_config["classname"] = self.classname weka_config["classname"] = self.classname
...@@ -49,12 +66,26 @@ class WekaClassifiers(Classifier): ...@@ -49,12 +66,26 @@ class WekaClassifiers(Classifier):
return weka_config return weka_config
def set_config(self, configs): def set_config(self, configs):
"""Update configuration of classifier.
Parameters
----------
configs : OrderedDict
New configs of classifier.
"""
configs["classname"].value = WekaAlias.get_classifier(configs["classname"].value) 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)
def get_summary_config(self): def get_summary_config(self):
"""Return fomatted summary of configuration.
Returns
-------
summary : string
Formatted string with summary of configuration.
"""
weka_config = OrderedDict() weka_config = OrderedDict()
weka_config[self.classname.label] = self.classname.value weka_config[self.classname.label] = self.classname.value
...@@ -68,9 +99,26 @@ class WekaClassifiers(Classifier): ...@@ -68,9 +99,26 @@ class WekaClassifiers(Classifier):
def must_train(self): def must_train(self):
"""Return if classifier must be trained.
Returns
-------
True
"""
return True return True
def train(self, dataset, training_data, force = False): def train(self, dataset, training_data, force = False):
"""Perform the training of classifier.
Parameters
----------
dataset : string
Path to image dataset.
training_data : string
Name of ARFF training file.
force : boolean, optional, default = False
If False don't perform new training if there is trained data.
"""
if self.data is not None and not force: if self.data is not None and not force:
return return
...@@ -89,6 +137,22 @@ class WekaClassifiers(Classifier): ...@@ -89,6 +137,22 @@ class WekaClassifiers(Classifier):
def classify(self, dataset, test_dir, test_data): def classify(self, dataset, test_dir, test_data):
"""Perform the classification.
Parameters
----------
dataset : string
Path to image dataset.
test_dir : string
Not used.
test_data : string
Name of test data file.
Returns
-------
summary : list of string
List of predicted classes for each instance in test data in ordered way.
"""
loader = WLoader(classname="weka.core.converters.ArffLoader") loader = WLoader(classname="weka.core.converters.ArffLoader")
test_file = File.make_path(dataset, test_data) test_file = File.make_path(dataset, test_data)
...@@ -113,6 +177,18 @@ class WekaClassifiers(Classifier): ...@@ -113,6 +177,18 @@ class WekaClassifiers(Classifier):
def cross_validate(self, detail = True): def cross_validate(self, detail = True):
"""Perform cross validation using trained data.
Parameters
----------
detail : boolean, optional, default = True
If true return a detailed information of cross validation.
Returns
-------
info : string
Info with results of cross validation.
"""
start_time = TimeUtils.get_time() start_time = TimeUtils.get_time()
info = "Scheme:\t%s %s\n" % (str(self.classifier.classname) , " ".join([str(option) for option in self.classifier.options])) info = "Scheme:\t%s %s\n" % (str(self.classifier.classname) , " ".join([str(option) for option in self.classifier.options]))
...@@ -145,11 +221,19 @@ class WekaClassifiers(Classifier): ...@@ -145,11 +221,19 @@ class WekaClassifiers(Classifier):
def experimenter(self): def experimenter(self):
"""Perform a test using all classifiers available.
Returns
-------
info : string
Info with results of experimenter.
"""
info = "" info = ""
aliases = sorted(WekaAlias.get_aliases()) aliases = sorted(WekaAlias.get_aliases())
for alias in aliases: for alias in aliases:
try: try:
# Ignore very slow classifiers.
if alias == 'KStar' or alias == 'LWL' or alias == 'MultilayerPerceptron': if alias == 'KStar' or alias == 'LWL' or alias == 'MultilayerPerceptron':
continue continue
...@@ -173,5 +257,7 @@ class WekaClassifiers(Classifier): ...@@ -173,5 +257,7 @@ class WekaClassifiers(Classifier):
def reset(self): def reset(self):
"""Clean all data of classification.
"""
self.data = None self.data = None
self.classifier = None self.classifier = None
...@@ -259,12 +259,12 @@ class Act(object): ...@@ -259,12 +259,12 @@ class Act(object):
Parameters Parameters
---------- ----------
name : string. name : string
Name of class. Name of class.
Returns Returns
------- -------
index : integer. index : integer
Index of class in list self.classes. Index of class in list self.classes.
Raises Raises
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment