Implementação da classe LVQ


In [234]:
import csv
import random
import numpy as np

class LVQ(): 
    def __init__(self, dataset):
        """
        Construtor da classe
        :param nome_arquivo: nome do arquivo csv que contem os dados
        """
        self.dados = dataset
        self.dataset = dataset
        self.qtd_caracteristicas = 0
        self.amplitudes = []
        self.qtd_caracteristicas = len(self.dados[0])-1
        
    def normalizar(self):
        """
        Normalizada todas as caracteristicas para um intervalo de 0 - 1, para todas tenham o mesmo peso na 
        classificacao
        """
        lista = []*(len(self.dados[0])-1)
        self.amplitudes = []
        for caracteristica in range(len(self.dados[0])-1):
            lista = [elemento[caracteristica] for elemento in self.dados]
            self.amplitudes += [[max(lista), min(lista)]]
            for elemento in self.dados:
                elemento[caracteristica] = (elemento[caracteristica] - min(lista))/(max(lista)+min(lista))
        
    def triagem(self, split: float=0.65):
        """
        Divide aleatoriament os elementos do conjunto de dados em dois subconjuntos: teste e treino
        :param split: de 0 a 1 -> 'porcentagem' dos elementos que serao do conjunto de treino
        """        
        self.treino, self.teste = [], []
        for elemento in self.dados:
            if random.random() < split:
                self.treino += [elemento]
            else:
                self.teste += [elemento]
    
    def resumir(self, n: float=10,  e: float=10, t: float=0.4):
        """
        Retorna o codebook dos dados, ou seja, os elementos que melhor representam o todo
        :param t: taxa de aprendizado inicial
        :param e: numero de epocas
        :param n: numero de elementos do coodbook 
        """
        #Geracacao aleatorio dos elementos iniciais do codebook         
        self.codebook = [[]]*n
        for i in range(n):
            self.codebook[i] = [0] * (self.qtd_caracteristicas + 1)
            for caracteristica in range(self.qtd_caracteristicas + 1):
                self.codebook[i][caracteristica] = random.choice(self.dados)[caracteristica]

        for epoca in range(e):
            taxa = t * (1.0-(epoca/float(e)))
            for elemento in self.treino:
                representante = self.encontrar_mais_proximo(elemento, self.codebook)
                o = -1
                if representante[-1] == elemento[-1]:
                    o = 1
                for caracteristica in range(self.qtd_caracteristicas):
                    erro = (elemento[caracteristica]-representante[caracteristica]) 
                    representante[caracteristica] += (erro * taxa * o)
      
    def testar(self):
        """
        Executa a classificacao para cada elemento do conjunto teste e retorna a precisao do algoritmo
        """
        qtd_teste = len(self.teste)
        precisao = 100.0
        for elemento in self.teste: 
            bmu = self.encontrar_mais_proximo(elemento, self.codebook)
            if bmu[-1] != elemento[-1]:
                precisao -= (1/qtd_teste)*100
 
        return precisao
    
    def encontrar_mais_proximo(self, elemento, lista):
        """
        Executa a classificacao para cada elemento do conjunto teste e retorna a precisao do algoritmo
        :param elemento: vetor para o qual deve-se vetor mais proximo de uma dada lista
        :param lista: lista de vetores
        """
        resposta = [lista[0], spatial.distance.euclidean(elemento[0:-1], lista[0][0:-1])]
        for i in lista:
            distancia = spatial.distance.euclidean(elemento[0:-1], i[0:-1])
            if distancia < resposta[1]: 
                resposta = [i, distancia]
        return resposta[0]
    
    @property
    def representantes(self):
        """
        Retorna o codebook "original", com as caracteristicas em seus intervalos originais. Ou seja, 
        retorna o codebook desnormalizado, caso ele tenha sido normalizado
        """
        representantes_desnormalizados = [[]]*len(self.codebook)
        if self.amplitudes:
            for index, representante in enumerate(self.codebook): 
                representante_desnormalizado = []
                for caracteristica in range(self.qtd_caracteristicas):
                    aux = ((self.amplitudes[caracteristica][0] + self.amplitudes[caracteristica][1])\
                          * representante[caracteristica]) + self.amplitudes[caracteristica][1]
                    representante_desnormalizado += [aux]
                representante_desnormalizado += [representante[-1]]    
                representantes_desnormalizados[index] = representante_desnormalizado
        else: 
            return self.codebook
        
        return representantes_desnormalizados
    
    @property
    def classes(self): 
        """
        Retorna as classes do dataset
        """
        classes = []
        for elemento in self.dados:
            if elemento[-1] not in classes:
                classes.append(elemento[-1])
        
        return classes

Algumas outras funções utilizadas


In [281]:
import random 
def importar_dataset(arquivo_csv: str=None):
        """
        Carrega os dados iniciais da classe através de um arquivo csv. Esperar-se um arquivo possua linhas
        com n colunas, de modo que a n-ézima represente a classe do elemento e as anteriores representem, 
        cada uma, uma caracteristica diferente.
        :param arquivo_csv: nome do arquivo csv
        """
        dados = []
        with open(arquivo_csv, 'r') as arquivo_csv:
            arquivo = csv.reader(arquivo_csv)
            for index, linha in enumerate(arquivo):
                if linha:
                    dados += [list(map(float, linha[0:-1]))]
                    dados[index] += [linha[-1]]
        return dados

def random_cores(qtd: int=3): 
    """
    Retorna aleatoriamente cores no formato hexademal de acordo com a quantidade pedida
    """
    lista = [(210,180,140), (139,69,19), (244,164,96), (85,107,47), (0,255,0), (102,205,170), (127,255,212),
            (72,209,204), (0,255,255), (176,196,222), (30,144,255), (0,0,255), (220,20,60), (255,105,180), 
             (255,0,255), (139,0,139), (255,192,203), (255,0,0), (250,128,114), (255,165,0), (255,255,0)]
    
    random.shuffle(lista)
    cores = lista[0:qtd]
    resposta = []
    for cor in cores:
        resposta += ['#%02x%02x%02x' % cor]
    return resposta

Aplicação da classe LVQ na classificação do conjunto de dados da IRIS


In [294]:
import matplotlib.pyplot as plt
dataset = importar_dataset("datas/IRIS.csv")

# Dados normalizados
print("Algoritmo com os dados normalizados entre 0 - 1")
iris_norm = LVQ(dataset)
iris_norm.triagem(0.75)
iris_norm.normalizar()
iris_norm.resumir(n=8, e=13, t=0.5)
print("Precisão: ", iris_norm.testar(), "% \n")
classes = iris_norm.classes

classes_cor = {}
cores = random_cores(len(classes))
for index, classe in enumerate(classes):
    classes_cor[classe] = cores[index]
    
for elemento in iris_norm.dataset:
    plt.plot(elemento[0], elemento[1], 'o', color=classes_cor[elemento[-1]])

for representante in iris_norm.codebook: 
    plt.plot(representante[0], representante[1], 'D' , ms=10, mfc='none', color=classes_cor[representante[-1]])

plt.show()
    
# Sem normalização 
print("Algoritmo com os dados não normalizados")
iris = LVQ(dataset)
iris.triagem(0.75)
iris.resumir(n=8, e=13, t=0.5)
print("Precisão: ", iris.testar(),"% \n")
# for representante in iris.representantes:
#     print(representante)


Algoritmo com os dados normalizados entre 0 - 1
Precisão:  97.22222222222223 % 

Algoritmo com os dados não normalizados
Precisão:  95.55555555555554 %