O nome k-NN vem de “k-Nearest Neighbor” (k-ésimo vizinho mais próximo), ou seja, esse método de machine learning tem como objetivo classificar x_t atribuindo a ele o rótulo representado mais frequentemente dentre as k amostras mais próximas e utilizando um esquema de votação. O que determina essa “proximidade entre vizinhos” é uma distância calculada entre pontos em um espaço euclidiano (instâncias), cujas fórmulas mais comuns são a distância Euclidiana e a distância Manhattan.
O “Center for Machine Learning and Intelligent Systems” da Universidade da Califórnia (CML-UCI) possui centenas de conjuntos de dados, disponíveis gratuitamente nesse link: UCI Machine Learning Repository. Um dos conjuntos de dados mais utilizados é o iris data set, que contém as dimensões de pétalas (protegem partes reprodutoras da planta e atraem polinizadores) e sépalas (partes semelhantes a folhas que envolvem o botão da flor) de exemplares de três espécies de flores iris. Ele contém 3 classes (Iris Setosa, Iris Versicolour e Iris Virginica) de 50 instâncias cada. Uma classe é linearmente separável das outras duas; estas últimas NÃO são separáveis linearmente entre si. Aqui, o objetivo é classificar a planta conforme quatro características físicas (em cm): comprimento da sépala, largura da sépala, comprimento da pétala e largura da pétala.

A figura acima ilustra a classificação usando k-NN (mapa de cores) para as instâncias com classes já informadas (pontos coloridos) considerando apenas o comprimento e largura da sépala (vermelho é virginica, verde é versicolor e azul é setosa).
Implementação em Python
Além de definir o conjunto de dados de treinamento e a métrica para calcular a distância entre os exemplos de treinamento, também deve-se definir o número de vizinhos mais próximos que serão considerados pelo algoritmo (o valor de K).
Não existe um valor único para o K (varia de acordo com a base de dados). Se K for muito pequeno, a classificação fica sensível a pontos de ruído; Se K for muito grande, a vizinhança pode incluir elementos de outras classes. Pode-se definir um valor inicial como a raiz quadrada do número de amostras e então ir chutando valores para ver qual K acerta mais, desde que K seja um número ímpar/primo.
O algoritmo k-NN consiste em, para cada nova amostra:
- Calcular a distância entre o exemplo desconhecido e o outros exemplos do conjunto de treinamento
- Identificar os K vizinhos mais próximos
- O rótulo da classe com mais representantes no conjunto definido no passo anterior será o escolhido (voto majoritário)
Esse algoritmo foi aplicado no script a seguir. São utilizados o “iris data set” (com 150 amostras), a distância Euclidiana como métrica e K = 13. O objetivo é classificar a classe da planta íris conforme as quatro dimensões fornecidas de suas flores. Uma parte das amostras (60%) é utilizada para fazer o treinamento e o restante serve para fazer o teste, que funciona da seguinte forma: o algoritmo faz a classificação e o resultado é comparado com seu valor de classe conhecido; se a classe prevista for a mesma que a observada, é registrado um acerto.
Como é retirada uma porcentagem dos dados para treinamento conforme a ordem das linhas, deve-se reordenar randomicamente as linhas, já que o arquivo original possui as linhas ordenadas por classe. Para isso, use o comando “cat iris.data | sort -R > iris.data2” no terminal Linux ou “random.shuffle(amostras)” logo após fechar o “for” de leitura do arquivo (nesse caso, cada vez que rodar o script os dados são rearranjado, atrapalhando outras comparações).
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Implementação do k-NN para "iris data set"
import sys
import math
import random
######################### FUNÇÕES #########################
# Imprime informações sobre os dados de iris.data
def info_dataset(amostras, verbose=True):
if verbose:
print('Total de amostras: %d' % len(amostras))
rotulo1, rotulo2, rotulo3 = 0, 0, 0
for amostra in amostras:
if amostra[-1] == 'Iris-setosa':
rotulo1 += 1
elif amostra[-1] == 'Iris-versicolor':
rotulo2 += 1
elif amostra[-1] == 'Iris-virginica':
rotulo3 += 1
else:
print("nenhuma classificação!")
if verbose:
print('Total rotulo 1: %d' % rotulo1)
print('Total rotulo 2: %d' % rotulo2)
print('Total rotulo 3: %d' % rotulo3)
return [len(amostras), rotulo1, rotulo2, rotulo3]
# Distância Euclidiana
def dist_euclidiana(v1, v2):
dim, soma = len(v1), 0
# loop sem pegar a última coluna pq é a saída (dim - 1)
for i in range(dim - 1):
soma += math.pow(v1[i] - v2[i], 2)
return math.sqrt(soma)
# KNN propriamente dito
def knn(treinamento, nova_amostra, K):
dists, tam_treino = {}, len(treinamento)
# calcula a distância euclidiana das amostras de treinamento
for i in range(tam_treino):
d = dist_euclidiana(treinamento[i], nova_amostra)
dists[i] = d
# obtém as chaves (índices) dos k-vizinhos mais próximos
k_vizinhos = sorted(dists, key=dists.get)[:K]
# votação majoritária
qtd_rotulo1, qtd_rotulo2, qtd_rotulo3 = 0, 0, 0
for indice in k_vizinhos:
if treinamento[indice][-1] == 'Iris-setosa':
qtd_rotulo1 += 1
elif treinamento[indice][-1] == 'Iris-versicolor':
qtd_rotulo2 += 1
elif treinamento[indice][-1] == 'Iris-virginica':
qtd_rotulo3 += 1
# Imprimir/retornar classe com mais votos
if (qtd_rotulo1 > qtd_rotulo2 and qtd_rotulo1 > qtd_rotulo3):
#print('Classe 1')
return 'Iris-setosa'
elif (qtd_rotulo2 > qtd_rotulo1 and qtd_rotulo2 > qtd_rotulo3):
#print('Classe 2')
return 'Iris-versicolor'
elif (qtd_rotulo3 > qtd_rotulo1 and qtd_rotulo3 > qtd_rotulo2):
#print('Classe 3')
return 'Iris-virginica'
######################### PROGRAMA #########################
# Inicialização da lista de amostras
amostras = []
# Carregar dados de arquivo CSV com amostras
with open('iris.data2', 'r') as f:
#ler arquivo linha por linha
for linha in f.readlines():
# obter os atributos da amostra
atrib = linha.replace('\n','').split(',')
amostras.append([float(atrib[0]), float(atrib[1]), float(atrib[2]), float(atrib[3]), atrib[4]])
# Guardar totais de cada rótulo/classe
_, rotulo1, rotulo2, rotulo3 = info_dataset(amostras, verbose=False)
# Separar 60% dos dados para treinamento e o restante fica para testar
p = 0.6
treinamento, teste = [], []
cont = 1
max_rotulo1, max_rotulo2, max_rotulo3 = int(p*rotulo1), int(p*rotulo2), int(p*rotulo3)
total_rotulo1, total_rotulo2 , total_rotulo3 = 0, 0, 0
for amostra in amostras:
if cont <= (max_rotulo1 + max_rotulo2 + max_rotulo3):
treinamento.append(amostra)
if amostra[-1] == 'Iris-setosa' and total_rotulo1 < max_rotulo1:
total_rotulo1 += 1
elif amostra[-1] == 'Iris-versicolor' and total_rotulo2 < max_rotulo2:
total_rotulo2 += 1
elif amostra[-1] == 'Iris-virginica' and total_rotulo3 < max_rotulo3:
total_rotulo3 += 1
else:
teste.append(amostra)
cont += 1
# Testar com as amostras do conjunto de teste
acertos = 0
K = 13
for amostra in teste:
classe = knn(treinamento, amostra, K)
#print('%s X %s' % (amostra[-1], classe))
#sys.exit("fim de teste")
if amostra[-1] == classe:
acertos += 1
print('Total de treinamento: %d' % len(treinamento))
print('Total de testes: %d' % len(teste))
print('Total de acertos: %d' % acertos)
print('Porcentagem de acertos: %.2f%%' % (100 * acertos / len(teste)))
A saída revela o número de amostras utilizadas no treinamento, o total de testes realizados e o número de classificações acertadas (com exibição em porcentagem também):
$ python3 knn_iris.py Total de treinamento: 90 Total de testes: 60 Total de acertos: 57 Porcentagem de acertos: 95.00%
Nota-se que o modelo do tipo k-NN, implementado para classificar o tipo de flor conforme as dimensões da sépala e da pétala, consegue estimar bem a classe de novos dados (não utilizados no treinamento).
Implementação em Python scikit-learn
A scikit-learn (de SciPy Toolkit) é uma biblioteca de aprendizado de máquina de código aberto para a linguagem de programação Python, uma extensão do SciPy desenvolvida por terceiros e distribuída separadamente. É projetada para interagir com as bibliotecas Python numéricas e científicas, como a NumPy, que suporta arrays e matrizes multidimensionais e também possui maior desempenho para realizar operações matemáticas que demandam mais processamento. Para sua instalação no Python 3 (foi utilizado Python 3.5.2), execute os seguintes comandos no terminal Linux:
sudo apt-get install python3-pip # caso não tenha o pip instalado para python 3 sudo pip3 install scikit-learn
Para versões mais antigas de Linux, usar “sudo apt-get install python3-setuptools && sudo easy_install3 pip” no lugar da primeira linha.
Para instalar via conda, use:
conda install -c anaconda scikit-learn
O código apresentado anteriormente pode ficar mais sucinto e eficiente usando a biblioteca sklearn.neighbors.KNeighborsClassifier da scikit-learn e seus métodos. Veja como fica o código para as mesmas condições utilizadas no script anterior:
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Implementação do k-NN com skLearn
import sys
from sklearn.neighbors import KNeighborsClassifier
# Inicialização da lista de amostras separada em entrada e saída (classes)
entradas, saidas = [], []
# Carregar dados de arquivo CSV com amostras
with open('iris.data2', 'r') as f:
#ler arquivo linha por linha
for linha in f.readlines():
# obter os atributos da amostra
atrib = linha.replace('\n','').split(',')
entradas.append([float(atrib[0]), float(atrib[1]), float(atrib[2]), float(atrib[3])])
saidas.append(atrib[4])
# Separar 60% dos dados para treinamento e o restante fica para testar
p = 0.6
limite = int(p * len(entradas))
# Chamar a função do skLearn para aplicar o k-NN com K = 13
knn = KNeighborsClassifier(n_neighbors=13)
# Ajustar o modelo usando "entradas" como dados de treinamento e "saidas" como valores-alvo
knn.fit(entradas[:limite], saidas[:limite])
# Previsão dos rótulos das classes para o restante da base de dados
rotulos_previstos = knn.predict(entradas[limite:])
# Contabilizar acertos para os dados de teste
acertos, indice_rotulo = 0, 0
for i in range(limite, len(entradas)):
if rotulos_previstos[indice_rotulo] == saidas[i]:
acertos += 1
indice_rotulo += 1
print('Total de treinamento: %d' % limite)
print('Total de testes: %d' % (len(entradas) - limite))
print('Total de acertos: %d' % acertos)
print('Porcentagem de acertos: %.2f%%' % (100 * acertos / (len(entradas) - limite)))
A saída deve ser a mesma do caso anterior.
Para estudo e previsão de séries temporais, existe o kNN-TSP (k-Nearest Neighbor – Time Series Prediction). Sua ideia consiste em, considerando os últimos n registros ocorridos, encontrar as subsequências de tamanho n que apresentaram comportamentos similares no passado. Com base nas informações dessas subsequências, é realizado o cálculo do valor futuro.
Fonte: curso Machine Learning e Data Science com Python da udemy.





One comment