Sistemas baseados em regras têm um problema bastante sério: eles dependem da construção de regras. Pode ser interessante, para diversos problemas, que os critérios de classificação sejam aprendidos automaticamente pelo sistema. Neste caderno, utilizaremos
Ao final desta iteração, o aluno será capaz de:
In [1]:
# Inicializacao
%matplotlib inline
import numpy as np
from matplotlib import pyplot as plt
KNN é um algoritmo não-paramétrico que funciona da seguinte forma. Na fase de treinamento (fit), o sistema classificador recebe como entrada um conjunto de elementos rotulados da base de dados. Na fase de teste (predict), o sistema encontra o rótulo dos K vizinhos (dentre os elementos recebidos no treinamento) mais próximos de cada elemento recebido como entrada, e então retorna o rótulo mais frequente nesses vizinhos.
Trata-se de um algoritmo bastante conhecido, implementado no módulo scikit-learn do Python. Por favor, altere o código abaixo até se sentir confortável com os métodos fit() e predict() do classificador. Como exercício, modifique o código para que as entradas tenham mais de uma dimensão, e tente predizer o que acontece ao variar o número K de vizinhos avaliados.
In [4]:
from sklearn.neighbors import KNeighborsClassifier
X = [[0], [1], [2], [3], [4]];
y = ['V', 'V', 'F', 'F', 'F'];
neigh = KNeighborsClassifier(n_neighbors=3); # n_neighbors = K
neigh.fit(X, y);
print neigh.predict([ [1.3], [1.7], [1.5]])
Veja que o sklearn.neighbors recebe como entrada um conjunto de dados num formato que não é o mesmo que temos usado até o momento. Então, precisaremos representar nossos dados de uma forma compatível com o que o módulo requer - uma lista de N listas, sendo que cada uma dessas N listas é um vetor de dados. O código para abrir a base de dados, então, fica:
In [5]:
# Abrindo conjunto de dados
import csv
with open("biometria.csv", 'rb') as f:
dados = list(csv.reader(f))
rotulos_volei = [d[0] for d in dados[1:-1] if d[0] is 'V']
rotulos_futebol = [d[0] for d in dados[1:-1] if d[0] is 'F']
altura_volei = [[float(d[1])] for d in dados[1:-1] if d[0] is 'V']
altura_futebol = [[float(d[1])] for d in dados[1:-1] if d[0] is 'F']
peso_volei = [[float(d[2])] for d in dados[1:-1] if d[0] is 'V']
peso_futebol = [[float(d[2])] for d in dados[1:-1] if d[0] is 'F']
O próprio KNeighborsClassifier já implementa um método que verifica o índice de acertos de um processo de classificação. O método recebe como entrada vetores que serão classificados e um conjunto de rótulos-gabarito correspondentes. Assim, o usuário fica isento de implementar essas funções e podemos escrever todo nosso processo de classificação na forma:
In [6]:
classificador = KNeighborsClassifier(n_neighbors=3); # n_neighbors = K
classificador.fit(altura_volei + altura_futebol, rotulos_volei + rotulos_futebol);
score = classificador.score(altura_volei + altura_futebol, rotulos_volei + rotulos_futebol)
print "Acertos:", int(score * len(altura_volei + altura_futebol))
Mas, perceba que há algo errado. Na etapa de treinamento do nosso sistema, fornecemos a ele todos os nossos dados rotulados. Depois, na etapa de teste, verificamos quais pontos estão próximos deles mesmos - ou seja, estamos avaliando se o sistema que fizemos é capaz de classificar os próprios dados que foram usados para treiná-lo.
Os resultados que tivemos, portanto, não permitem inferir se nosso sistema é capaz de generalizar o resultado. Em outras palavras, não mostramos que o sistema faz muito mais que reproduzir seus dados de treino. Podemos dizer que nosso sistema pode ter sido sobre-ajustado (ou, que sofreu over-fitting), e, portanto, só demonstramos funciona sobre os dados que foram usados em seu treinamento.
Um sistema sobre-ajustado tem pouca capacidade de generalização, ou seja, os resultados obtidos para o conjunto de teste não extrapolam para dados adicionais. Para evitar o sobre-ajuste, é preciso construir bases de dados diferentes para as quais executaremos procedimentos de treinamento e de teste. Esse procedimento é chamado de validação.
Existem muitas maneiras de dividir a base de dados. Podemos, por exemplo, escolher aleatoriamente uma fração dos dados para compor a base de treinamento e usar os dados restantes como base de teste. Esse procedimento está implementado no scikit-learn, de forma que não precisamos nos preocupar (muito) com seu funcionamento interno.
In [7]:
# Validacao usando divisao aleatoria do conjunto de dados
from sklearn.cross_validation import train_test_split
dados_treino, dados_teste, rotulos_treino, rotulos_teste =\
train_test_split(altura_volei + altura_futebol, rotulos_volei + rotulos_futebol, train_size=.3)
classificador = KNeighborsClassifier(n_neighbors=5); # n_neighbors = K
classificador.fit(dados_treino, rotulos_treino);
score = classificador.score(dados_teste, rotulos_teste);
print "% de Acertos:", 100*score
Ao variar o parâmetro train_size, alteramos a fração dos dados que será utilizada para treinamento. Uma quantidade muito grande de dados de treino implica num teste menos significativo, mas pode aumentar a quantidade de acertos do sistema. Além disso, como os conjuntos são divididos aleatoriamente, duas execuções seguidas do mesmo código podem levar a resultados diferentes.
É interessante que verifiquemos a robustez do nosso sistema de classificação, ou seja, a variância que podemos esperar em seus resultados. Faremos isso através de um procedimento chamado Monte Carlo, que consiste em executar diversas vezes processo em questão e então analisar numericamente os resultados. Para a avaliação do nosso sistema, estamos interessados em evidenciar a média e a variância do número de acertos de nosso sistema para diferentes tamanhos do conjunto de treinamento.
In [8]:
# Parametros para executar busca exaustiva
train_size_min = 0.2
train_size_max = 0.95
train_size_step = 0.05
# Numero de iteracoes para cada tamanho de conjunto de treino
n_iter = 100
# Listas que armazenarao os resultados
steps = []
medias = []
variancias = []
train_size_atual = train_size_min
while train_size_atual <= train_size_max: # para cada tamanho do conjunto de treino
acertos = []
for k in xrange(n_iter): # para cada iteracao do processo Monte Carlo
dados_treino, dados_teste, rotulos_treino, rotulos_teste =\
train_test_split(altura_volei + altura_futebol, rotulos_volei + rotulos_futebol, train_size=train_size_atual)
classificador = KNeighborsClassifier(n_neighbors=4); # n_neighbors = K
classificador.fit(dados_treino, rotulos_treino);
score = classificador.score(dados_teste, rotulos_teste);
acertos.append(score)
steps.append(train_size_atual)
medias.append(np.mean(np.array(acertos)))
variancias.append(np.std(np.array(acertos)))
train_size_atual += train_size_step
plt.figure();
plt.errorbar(steps, medias, yerr=variancias);
plt.ylabel('Indice de acertos');
plt.xlabel('Tamanho do conjunto de treino');
A grande variância do índice de acertos mostra que o modelo que escolhemos é muito sensível aos dados de treinamento, ou seja, dados de treinamento diferentes levam a resultados muito diferentes. Assim, dizemos que o modelo KNN, para esta aplicação, não foi eficaz no sentido de generalizar seus resultados. Nas próximas lições, verificaremos como podemos construir modelos mais robustos para este mesmo problema.
In [ ]:
In [ ]:
In [ ]:
In [ ]: