Commit 22601ddd authored by Gilberto Astolfi's avatar Gilberto Astolfi

commit new feature syntactic

parent c711d7f5
File mode changed from 100644 to 100755
......@@ -244,3 +244,95 @@ Por fim será necessário configurar sua CNNCaffe.
- Para os campos ModelDef, ModelWeights e MeanImage, você deverá fornecer os caminhos relativos ao seu treinamento realizado no passo anterior.
- Para o campo LabelsFile você deve fornecer o caminho de um arquivo que descrava nominalmente as classes na ordem 0, 1, ..., n-1, onde n é o número de classes que você treinou.
- Um arquivo de exemplo pode ser encontrado em examples/labels.txt.
# Implementando um novo classificador no pynovisao
Nesta seção será usado como exemplo o classificador **Syntactic**, do tipo **KTESTABLE** e, como opção de hiperparâmetros, o tamanho do vocabulário.
Inicialmente, você deve criar uma classe onde estão, em um dicionário (chave, valor), todos os tipos do seu classificador. A classe deve ser criada no diretório src/classification/. Veja como exemplo a classe SyntacticAlias no arquivo src/classification/syntactic_alias.py.
O próximo passo é criar o arquivo (.py) do seu classificador no diretório src/classification/, por exemplo, syntactic.py. No arquivo recém-criado você deve implementar a classe de seu classificador estendendo a classe **Classifier**, que está implementada no arquivo src/classification/classifier.py. Veja exemplo abaixo.
```python
#syntactic.py
#importações mínimas necessárias
from collections import OrderedDict
from util.config import Config
from util.utils import TimeUtils
from classifier import Classifier
class Syntactic(Classifier):
"""Class for syntactic classifier"""
```
No construtor da classe você deve informar valores padrão para os parâmetros. No caso do exemplo abaixo, **classename** é o tipo do classificador e **options** é o tamanho do alfabeto. Além disso, alguns atributos devem ser inicializados: **self.classname** e **self.options**. O atributo **self.dataset** (opicional) é o path do conjunto de treinamento e teste que o usuário informa na interface gráfica. Ter esse atributo na classe é importante para ter acesso ao conjunto de imagens em qualquer um dos métodos, ele é inicializado no método **train** discutido posteriormente.
```python
def __init__(self, classname="KTESTABLE", options='32'):
self.classname = Config("ClassName", classname, str)
self.options = Config("Options", options, str)
self.dataset = None
self.reset()
```
O métodos **get_name**, **get_config**, **set_config**, **get_summary_config** e **must_train** possuem implementações padrão. Veja exemplo de implementação em src/classification/classifier.py.
O método train deve ser implementado para treinar o seu classificador. No parâmetro dataset é passado o diretório onde estão as imagens para treinamento. No corpo do método o valor do atributo self.dataset, declarado como opcional no construtor, é alterado com o atual diretório de treinamento.
```python
def train(self, dataset, training_data, force = False):
dataset += '/'
# atributo que mantém o diretório do dataset.
self.dataset = dataset
# os dois testes abaixo são padrão
if self.data is not None and not force:
return
if self.data is not None:
self.reset()
# implemente aqui seu treinamento.
```
O método **classify** deve ser implementado para que seu classificador faça a predição. No parâmetro **dataset** é passado o diretório onde estão as imagens para treinamento, no **test_dir** é passado a pasta temporária criada pelo pynovisao onde estão as imagens que serão usadas para o teste. A pasta temporária é criada dentro do diretório dataset. Então, para acessar o diretório de testes basta concatenar **dataset** e **test_dir**, como no exemplo no corpo do método abaixo. O parâmetro test_data é um arquivo arff que contém os dados para os testes.
O método **classify** deve retornar uma lista contendo as classes preditas pelo seu classificador. Exemplo: [‘daninha’,’daninha’,’mancha_alvo’, ‘daninha’]
```python
def classify(self, dataset, test_dir, test_data):
# diretório onde estão as imagens para testes.
path_test = dataset + '/' + test_dir + '/'
# implemente aqui o preditor de seu classificador
return # uma lista com as classes preditas
```
O método **cross_validate** deve ser implementado e o objetivo é implementar a validação cruzada. O método retorna uma string (info) com as métricas. Obs: o atributo **self.dataset** atualizado no método **train** pode ser utilizado no método cross-validate para acessar o diretório das imagens de treinamento.
```python
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]))
# implemente aqui a validação cruzada.
return info
```
O método **reset** deve ser implementado de forma padrão, como exemplo abaixo.
```python
def reset(self):
self.data = None
self.classifier = None
```
Após a implementação de seu classificador, você deve configurá-lo no pynovisao. A configuração deve ser realizada no arquivo src/classification/__init__.py.
Caso você necessite de classes utilitárias, os arquivos delas devem ser criados no diretório src/util/. Além disso, as classes utilitárias devem ser registradas como módulos no arquivo src/util/__init__.py
......@@ -9,10 +9,16 @@ try:
from .cnn_caffe import CNNCaffe
except:
CNNCaffe = None
try:
from .syntactic import Syntactic
except:
Syntactic = None
__all__ = ["cnn_caffe",
"classifier",
"weka_classifiers"]
"weka_classifiers",
"syntactic"]
from collections import OrderedDict
......@@ -23,7 +29,9 @@ _classifier_list = OrderedDict( [
["cnn_caffe", Config("Invalid" if CNNCaffe is None else CNNCaffe.__name__,
WekaClassifiers is None and CNNCaffe is not None, bool, meta=CNNCaffe, hidden=CNNCaffe is None)],
["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)],
["syntactic", Config("Invalid" if Syntactic is None else Syntactic.__name__,
Syntactic is not None, bool, meta=Syntactic, hidden=Syntactic is None)]
] )
def get_classifier_config():
......@@ -32,3 +40,4 @@ def get_classifier_config():
def set_classifier_config(configs):
_classifier_list["cnn_caffe"] = Config.nvl_config(configs["cnn_caffe"], _classifier_list["cnn_caffe"])
_classifier_list["weka_classifiers"] = Config.nvl_config(configs["weka_classifiers"], _classifier_list["weka_classifiers"])
_classifier_list["syntactic"] = Config.nvl_config(configs["syntactic"], _classifier_list["syntactic"])
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
"""
Runs collection of machine learning algorithms for data mining tasks available in Weka.
Hall, Mark, et al, The WEKA data mining software: an update, ACM SIGKDD explorations newsletter, 2009.
Name: weka_classifiers.py
Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com )
"""
from collections import OrderedDict
from util.config import Config
from util.utils import TimeUtils
from classifier import Classifier
from syntactic_alias import SyntacticAlias
import statistics
from pandas_ml import ConfusionMatrix
from sklearn.model_selection import ShuffleSplit
from sklearn import metrics
from util.syntactic_utils import BoVW_SYNTACTIC
from util.syntactic_utils import BoVW
'''
pip install -U scikit-learn
pip install tensorflow-gpu
pip install statistics
pip install pandas_ml
'''
#syntactic.py
class Syntactic(Classifier):
"""Class for syntactic classifier which to use K-Testable inference"""
def __init__(self, classname="KTESTABLE", options='32'):
self.classname = Config("ClassName", classname, str)
self.options = Config("Options", options, str)
self.bov_syn = None
self.name_classes = None
self.dataset = None
self.reset()
def get_name(self):
"""Return the name of class.
Returns
-------
name : string
Returns the name of instantiated class.
"""
return self.__class__.__name__
def get_config(self):
"""Return configuration of classifier.
Returns
-------
config : OrderedDict
Current configs of classifier.
"""
syntactic_config = OrderedDict()
syntactic_config["classname"] = self.classname
syntactic_config["classname"].value = syntactic_config["classname"].value.split('.')[-1]
syntactic_config["options"] = self.options
return syntactic_config
def set_config(self, configs):
"""Update configuration of classifier.
Parameters
----------
configs : OrderedDict
New configs of classifier.
"""
configs["classname"].value = SyntacticAlias.get_classifier(configs["classname"].value)
self.classname = Config.nvl_config(configs["classname"], self.classname)
self.options = Config.nvl_config(configs["options"], self.options)
def get_summary_config(self):
"""Return fomatted summary of configuration.
Returns
-------
summary : string
Formatted string with summary of configuration.
"""
syntactic_config = OrderedDict()
syntactic_config[self.classname.label] = self.classname.value
syntactic_config[self.options.label] = self.options.value
summary = ''
for config in syntactic_config:
summary += "%s: %s\n" % (config, str(syntactic_config[config]))
return summary
def must_train(self):
"""Return if classifier must be trained.
Returns
-------
True
"""
return True
def train(self, dataset, training_data, force = False):
dataset += '/'
# This is necessary to cross-validation
self.dataset = dataset
if self.data is not None and not force:
return
if self.data is not None:
self.reset()
((X_train, y_train), name_classes) = self.__load_data(dataset, True)
self.name_classes = name_classes
self.bov_syn = BoVW_SYNTACTIC(X_train, y_train)
self.bov_syn.trainModel()
def classify(self, dataset, test_dir, test_data):
path_test = dataset + '/' + test_dir + '/'
((X_test, y_test), name_classes) = self.__load_data(path_test, False)
self.bov_syn.set_test(X_test, y_test)
pred, cl = self.bov_syn.testModel()
return self.class_to_text(pred)
def class_to_text(self, classes):
cl = {}
for key, value in self.name_classes.iteritems():
cl[value] = key
class_text = []
for key in classes:
class_text.append(cl[str(key)])
return class_text
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()
info = "Scheme:\t%s\n" % self.classname.value
info += "\t%s\n" % self.options.value
dataset = self.dataset
((X_train, y_train), name_classes) = self.__load_data(dataset, True)
self.name_classes = name_classes
kf = ShuffleSplit(10, 0.10, random_state=0)
predictions = []
classes = []
accuracy_list = []
for train_index, test_index in kf.split(X_train):
xx_train, xx_test = X_train[train_index], X_train[test_index]
yy_train, yy_test = y_train[train_index], y_train[test_index]
self.bov_syn = BoVW_SYNTACTIC(xx_train, yy_train)
self.bov_syn.set_test(xx_test, yy_test)
self.bov_syn.trainModel()
pred, cl = self.bov_syn.testModel()
predictions.extend(self.class_to_text(pred))
classes.extend(self.class_to_text(cl))
accuracy_list.append(metrics.accuracy_score(cl, pred))
info += "Time taken to build model: %0.5f seconds\n\n" % (TimeUtils.get_time() - start_time)
info += "Average: %0.5f \n\n" % statistics.mean(accuracy_list)
info += "Median: %0.5f \n\n" % statistics.median(accuracy_list)
info += metrics.classification_report(classes, predictions, digits=2)
confusion_matrix = ConfusionMatrix(classes, predictions)
info += "\nConfusion matrix:\n%s" % confusion_matrix
return info
def experimenter(self):
start_time = TimeUtils.get_time()
info += "Info: %0.5f seconds\n\n" % (TimeUtils.get_time() - start_time)
return info
def reset(self):
"""Clean all data of classification.
"""
self.data = None
self.classifier = None
self.bov_syn = None
def __load_data(self, dataset, train):
bov = BoVW(int(self.options.value))
return bov.load_data(dataset, False, train)
#!/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
_syntactic_alias = {
"KTESTABLE": "KTESTABLE"
}
class SyntacticAlias(object):
"""Class of alias for classifiers available on python-weka-wrapper."""
@staticmethod
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 = [_syntactic_alias[alias] for alias in _syntactic_alias]
if name in classifiers:
return name
alias = name.upper().strip()
aliases = [a.upper() for a in _syntactic_alias]
if alias in aliases:
return _syntactic_alias.values()[ aliases.index(alias) ]
raise IException('Invalid classifier')
@staticmethod
def get_aliases():
"""Return all aliases.
Returns
-------
classifier : Dictionary
Dictionary with all aliases.
"""
return _syntactic_alias
......@@ -26,6 +26,7 @@ from util.utils import TimeUtils
from weka_alias import WekaAlias
from classifier import Classifier
class WekaClassifiers(Classifier):
"""Class for all classifiers available in python-weka-wrapper"""
......@@ -44,7 +45,6 @@ class WekaClassifiers(Classifier):
self.classname = Config("ClassName", classname, str)
self.options = Config("Options", options, str)
self.reset()
......@@ -62,7 +62,7 @@ class WekaClassifiers(Classifier):
weka_config["classname"].value = weka_config["classname"].value.split('.')[-1]
weka_config["options"] = self.options
return weka_config
def set_config(self, configs):
......@@ -77,7 +77,8 @@ class WekaClassifiers(Classifier):
self.classname = Config.nvl_config(configs["classname"], self.classname)
self.options = Config.nvl_config(configs["options"], self.options)
def get_summary_config(self):
"""Return fomatted summary of configuration.
......@@ -90,11 +91,13 @@ class WekaClassifiers(Classifier):
weka_config[self.classname.label] = self.classname.value
weka_config[self.options.label] = self.options.value
#print 'self.options.value',
#print self.options.value
summary = ''
for config in weka_config:
summary += "%s: %s\n" % (config, str(weka_config[config]))
return summary
......@@ -119,6 +122,8 @@ class WekaClassifiers(Classifier):
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:
return
......@@ -153,6 +158,8 @@ class WekaClassifiers(Classifier):
summary : list of string
List of predicted classes for each instance in test data in ordered way.
"""
loader = WLoader(classname="weka.core.converters.ArffLoader")
test_file = File.make_path(dataset, test_data)
......@@ -169,10 +176,8 @@ class WekaClassifiers(Classifier):
prediction = self.classifier.distribution_for_instance(inst)
#cl = int(values[prediction.argmax()][7:])
cl = values[prediction.argmax()]
#print 'Classe:', cl
classes.append(cl)
return classes
......@@ -189,6 +194,9 @@ class WekaClassifiers(Classifier):
info : string
Info with results of cross validation.
"""
#print 'cross_validation'
start_time = TimeUtils.get_time()
info = "Scheme:\t%s %s\n" % (str(self.classifier.classname) , " ".join([str(option) for option in self.classifier.options]))
......@@ -229,7 +237,7 @@ class WekaClassifiers(Classifier):
Info with results of experimenter.
"""
info = ""
#print 'experimenter'
aliases = sorted(WekaAlias.get_aliases())
for alias in aliases:
try:
......
......@@ -14,8 +14,10 @@ if sys.version_info[0] < 3:
else:
import tkinter as Tk
import tkMessageBox
#import tkinter.messagebox
import tk as tk_local
#import tkinter as tk_local
from interface import Interface, InterfaceException as IException
......
......@@ -55,6 +55,7 @@ class Act(object):
self._image = None
self._const_image = None
self._image_name = None
self._image_path = None
self._init_dataset(args["dataset"])
self._init_classes(args["classes"], args["colors"])
......@@ -150,8 +151,7 @@ class Act(object):
self.segmenter.reset()
self._gt_segments = None
def restore_image(self):
"""Refresh the image and clean the segmentation.
"""
......@@ -177,6 +177,7 @@ class Act(object):
self.tk.write_log("Closing image...")
self._const_image = None
self._image = None
self._image_path = None
def add_class(self, dialog = True, name = None, color = None):
"""Add a new class.
......@@ -490,6 +491,8 @@ class Act(object):
self.tk.write_log("Running %s...", self.classifier.get_name())
self.tk.append_log("\n%s", str(self.classifier.get_summary_config()))
#self.classifier.set
start_time = TimeUtils.get_time()
# Perform a segmentation, if needed.
......
......@@ -3,7 +3,13 @@ from .file_utils import File
from .utils import ColorUtils, ImageUtils, TimeUtils
from .x11_colors import X11Colors
from .syntactic_utils import BoVW_SYNTACTIC, BoVW
__all__ = ["config",
"file_utils",
"utils",
"x11_colors"]
\ No newline at end of file
"x11_colors",
"syntactic_utils"]
\ No newline at end of file
This diff is collapsed.
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