Introdução
A K-vizinhos mais próximos (KNN) O algoritmo é um tipo de algoritmo de aprendizado de máquina supervisionado usado para classificação, regressão e detecção de valores discrepantes. É extremamente fácil de implementar em sua forma mais básica, mas pode executar tarefas bastante complexas. É um algoritmo de aprendizado preguiçoso, pois não possui uma fase de treinamento especializada. Em vez disso, ele usa todos os dados para treinamento enquanto classifica (ou regride) um novo ponto de dados ou instância.
KN é um algoritmo de aprendizado não paramétrico, o que significa que não assume nada sobre os dados subjacentes. Este é um recurso extremamente útil, pois a maioria dos dados do mundo real não segue nenhuma suposição teórica, por exemplo, separabilidade linear, distribuição uniforme, etc.
Neste guia, veremos como o KNN pode ser implementado com a biblioteca Scikit-Learn do Python. Antes disso, vamos primeiro explorar como podemos usar o KNN e explicar a teoria por trás dele. Depois disso, vamos dar uma olhada no Conjunto de dados de habitação da Califórnia usaremos para ilustrar o algoritmo KNN e várias de suas variações. Em primeiro lugar, veremos como implementar o algoritmo KNN para a regressão, seguido de implementações da classificação KNN e da detecção de outliers. No final, vamos concluir com alguns dos prós e contras do algoritmo.
Quando você deve usar o KNN?
Suponha que você queira alugar um apartamento e descobriu recentemente que o vizinho do seu amigo pode colocar o apartamento dela para alugar em 2 semanas. Como o apartamento ainda não está em um site de aluguel, como você poderia tentar estimar o valor do aluguel?
Digamos que seu amigo pague US$ 1,200 de aluguel. O valor do seu aluguel pode estar em torno desse número, mas os apartamentos não são exatamente os mesmos (orientação, área, qualidade dos móveis etc.), então seria bom ter mais dados sobre outros apartamentos.
Ao perguntar a outros vizinhos e observar os apartamentos do mesmo prédio que foram listados em um site de aluguel, os três aluguéis de apartamentos vizinhos mais próximos são de US$ 1,200, US$ 1,210, US$ 1,210 e US$ 1,215. Esses apartamentos ficam no mesmo quarteirão e andar do apartamento do seu amigo.
Outros apartamentos, que ficam mais distantes, no mesmo andar, mas em um bloco diferente, têm aluguéis de US$ 1,400, US$ 1,430, US$ 1,500 e US$ 1,470. Parece que eles são mais caros devido a ter mais luz do sol à noite.
Considerando a proximidade do apartamento, parece que seu aluguel estimado seria de cerca de US$ 1,210. Essa é a ideia geral do que K-vizinhos mais próximos (KNN) algoritmo faz! Ele classifica ou regride novos dados com base em sua proximidade com os dados já existentes.
Traduza o Exemplo em Teoria
Quando o valor estimado é um número contínuo, como o valor do aluguel, KNN é usado para regressão. Mas também poderíamos dividir os apartamentos em categorias com base no aluguel mínimo e máximo, por exemplo. Quando o valor é discreto, tornando-se uma categoria, KNN é usado para classificação.
Há também a possibilidade de estimar quais vizinhos são tão diferentes dos outros que provavelmente deixarão de pagar aluguel. Isso é o mesmo que detectar quais pontos de dados estão tão distantes que não se encaixam em nenhum valor ou categoria, quando isso acontece, KNN é usado para detecção de valores discrepantes.
No nosso exemplo, também já sabíamos os aluguéis de cada apartamento, o que significa que nossos dados estavam rotulados. KNN usa dados previamente rotulados, o que o torna um algoritmo de aprendizagem supervisionada.
O KNN é extremamente fácil de implementar em sua forma mais básica e ainda executa tarefas bastante complexas de classificação, regressão ou detecção de valores discrepantes.
Cada vez que um novo ponto é adicionado aos dados, o KNN usa apenas uma parte dos dados para decidir o valor (regressão) ou classe (classificação) desse ponto adicionado. Como não é necessário olhar para todos os pontos novamente, isso o torna um algoritmo de aprendizado preguiçoso.
O KNN também não assume nada sobre as características dos dados subjacentes, não espera que os dados se encaixem em algum tipo de distribuição, como uniforme, ou que sejam linearmente separáveis. Isso significa que é um algoritmo de aprendizado não paramétrico. Esse é um recurso extremamente útil, pois a maioria dos dados do mundo real não segue nenhuma suposição teórica.
Visualizando diferentes usos do KNN
Como foi mostrado, a intuição por trás do algoritmo KNN é um dos mais diretos de todos os algoritmos de aprendizado de máquina supervisionados. O algoritmo calcula primeiro o distância de um novo ponto de dados para todos os outros pontos de dados de treinamento.
Observação: A distância pode ser medida de diferentes maneiras. Você pode usar um Minkowski, Euclidiano, Manhattan, Mahalanobis ou fórmula de Hamming, para citar algumas métricas. Com dados de alta dimensão, a distância euclidiana muitas vezes começa a falhar (alta dimensionalidade é… estranha), e a distância de Manhattan é usada.
Depois de calcular a distância, o KNN seleciona um número de pontos de dados mais próximos – 2, 3, 10 ou, na verdade, qualquer número inteiro. Este número de pontos (2, 3, 10, etc.) K em K-Nearest Neighbors!
Na etapa final, se for uma tarefa de regressão, o KNN calculará a soma ponderada média dos K-pontos mais próximos para a previsão. Se for uma tarefa de classificação, o novo ponto de dados será atribuído à classe à qual pertence a maioria dos pontos K-mais próximos selecionados.
Vamos visualizar o algoritmo em ação com a ajuda de um exemplo simples. Considere um conjunto de dados com duas variáveis e um K de 3.
Ao realizar a regressão, a tarefa é encontrar o valor de um novo ponto de dados, com base na soma ponderada média dos 3 pontos mais próximos.
KNN com K = 3
, Quando usado para regressão:
O algoritmo KNN começará calculando a distância do novo ponto de todos os pontos. Em seguida, ele encontra os 3 pontos com a menor distância até o novo ponto. Isso é mostrado na segunda figura acima, na qual os três pontos mais próximos, 47
, 58
e 79
foram cercados. Depois disso, ele calcula a soma ponderada de 47
, 58
e 79
– neste caso os pesos são iguais a 1 – estamos considerando todos os pontos como iguais, mas também poderíamos atribuir pesos diferentes com base na distância. Depois de calcular a soma ponderada, o novo valor do ponto é 61,33
.
E ao realizar uma classificação, a tarefa KNN para classificar um novo ponto de dados, no "Purple"
or "Red"
classe.
KNN com K = 3
, Quando usado para classificação:
O algoritmo KNN começará da mesma forma que antes, calculando a distância do novo ponto de todos os pontos, encontrando os 3 pontos mais próximos com a menor distância até o novo ponto e, em vez de calcular um número, ele atribui o novo ponto para a classe à qual pertence a maioria dos três pontos mais próximos, a classe vermelha. Portanto, o novo ponto de dados será classificado como "Red"
.
O processo de detecção de outliers é diferente dos dois acima, falaremos mais sobre ele ao implementá-lo após as implementações de regressão e classificação.
Note: O código fornecido neste tutorial foi executado e testado com o seguinte Caderno Jupyter.
O conjunto de dados de habitação Scikit-Learn Califórnia
Vamos usar o Conjunto de dados de habitação da Califórnia para ilustrar como o algoritmo KNN funciona. O conjunto de dados foi derivado do censo americano de 1990. Uma linha do conjunto de dados representa o censo de um grupo de quarteirões.
Nesta seção, veremos os detalhes do conjunto de dados de habitação da Califórnia, para que você possa obter uma compreensão intuitiva dos dados com os quais trabalharemos. É muito importante conhecer seus dados antes de começar a trabalhar neles.
A quadra group é a menor unidade geográfica para a qual o US Census Bureau publica dados de amostra. Além do grupo de quarteirões, outro termo utilizado é agregado familiar, um agregado familiar é um grupo de pessoas que residem dentro de uma casa.
O conjunto de dados consiste em nove atributos:
MedInc
– renda mediana no grupo de blocosHouseAge
– idade média da casa em um grupo de quarteirõesAveRooms
– o número médio de quartos (fornecidos por família)AveBedrms
– o número médio de quartos (fornecidos por família)Population
– população do grupo de blocosAveOccup
– o número médio de membros do agregado familiarLatitude
– latitude do grupo de blocosLongitude
– longitude do grupo de blocosMedHouseVal
– valor médio da casa para distritos da Califórnia (centenas de milhares de dólares)
O conjunto de dados é já faz parte da biblioteca Scikit-Learn, precisamos apenas importá-lo e carregá-lo como um dataframe:
from sklearn.datasets import fetch_california_housing
california_housing = fetch_california_housing(as_frame=True)
df = california_housing.frame
Importar os dados diretamente do Scikit-Learn, importa mais do que apenas as colunas e números e inclui a descrição dos dados como um Bunch
object - então acabamos de extrair o frame
. Mais detalhes do conjunto de dados estão disponíveis SUA PARTICIPAÇÃO FAZ A DIFERENÇA.
Vamos importar o Pandas e dar uma olhada nas primeiras linhas de dados:
import pandas as pd
df.head()
A execução do código exibirá as cinco primeiras linhas do nosso conjunto de dados:
MedInc HouseAge AveRooms AveBedrms Population AveOccup Latitude Longitude MedHouseVal
0 8.3252 41.0 6.984127 1.023810 322.0 2.555556 37.88 -122.23 4.526
1 8.3014 21.0 6.238137 0.971880 2401.0 2.109842 37.86 -122.22 3.585
2 7.2574 52.0 8.288136 1.073446 496.0 2.802260 37.85 -122.24 3.521
3 5.6431 52.0 5.817352 1.073059 558.0 2.547945 37.85 -122.25 3.413
4 3.8462 52.0 6.281853 1.081081 565.0 2.181467 37.85 -122.25 3.422
Neste guia, usaremos MedInc
, HouseAge
, AveRooms
, AveBedrms
, Population
, AveOccup
, Latitude
, Longitude
prever MedHouseVal
. Algo semelhante à nossa narrativa de motivação.
Vamos agora pular direto para a implementação do algoritmo KNN para a regressão.
Regressão com K-Nearest Neighbors com Scikit-Learn
Até agora, conhecemos nosso conjunto de dados e agora podemos prosseguir para outras etapas do algoritmo KNN.
Dados de pré-processamento para regressão KNN
O pré-processamento é onde aparecem as primeiras diferenças entre as tarefas de regressão e classificação. Como esta seção trata de regressão, prepararemos nosso conjunto de dados de acordo.
Para a regressão, precisamos prever outro valor médio da casa. Para isso, atribuiremos MedHouseVal
para y
e todas as outras colunas para X
apenas soltando MedHouseVal
:
y = df['MedHouseVal']
X = df.drop(['MedHouseVal'], axis = 1)
Observando nossas descrições de variáveis, podemos ver que temos diferenças nas medidas. Para evitar adivinhações, vamos usar o describe()
método para verificar:
X.describe().T
Isto resulta em:
count mean std min 25% 50% 75% max
MedInc 20640.0 3.870671 1.899822 0.499900 2.563400 3.534800 4.743250 15.000100
HouseAge 20640.0 28.639486 12.585558 1.000000 18.000000 29.000000 37.000000 52.000000
AveRooms 20640.0 5.429000 2.474173 0.846154 4.440716 5.229129 6.052381 141.909091
AveBedrms 20640.0 1.096675 0.473911 0.333333 1.006079 1.048780 1.099526 34.066667
Population 20640.0 1425.476744 1132.462122 3.000000 787.000000 1166.000000 1725.000000 35682.000000
AveOccup 20640.0 3.070655 10.386050 0.692308 2.429741 2.818116 3.282261 1243.333333
Latitude 20640.0 35.631861 2.135952 32.540000 33.930000 34.260000 37.710000 41.950000
Longitude 20640.0 -119.569704 2.003532 -124.350000 -121.800000 -118.490000 -118.010000 -114.310000
Aqui, podemos ver que a mean
valor de MedInc
é de aproximadamente 3.87
e os votos de mean
valor de HouseAge
é de cerca de 28.64
, tornando-o 7.4 vezes maior do que MedInc
. Outras características também apresentam diferenças na média e no desvio padrão – para ver isso, observe o mean
e std
valores e observar como eles estão distantes uns dos outros. Por MedInc
std
é de aproximadamente 1.9
, Por HouseAge
, std
is 12.59
e o mesmo se aplica aos outros recursos.
Estamos usando um algoritmo baseado em distância e algoritmos baseados em distância sofrem muito com dados que não estão na mesma escala, como esses dados. A escala dos pontos pode (e na prática quase sempre distorce) distorcer a distância real entre os valores.
Para executar o Feature Scaling, usaremos o Scikit-Learn's StandardScaler
aula mais tarde. Se aplicarmos o dimensionamento agora (antes de uma divisão de teste de treinamento), o cálculo incluirá dados de teste, efetivamente vazamento informações de dados de teste no restante do pipeline. Esse tipo de Vazamento de informações infelizmente é comumente ignorado, resultando em achados irreproduzíveis ou ilusórios.
Dividindo dados em conjuntos de treinamento e teste
Para poder dimensionar nossos dados sem vazamento, mas também para avaliar nossos resultados e evitar ajustes excessivos, dividiremos nosso conjunto de dados em divisões de treinamento e teste.
Uma maneira direta de criar divisões de treinamento e teste é a train_test_split
método do Scikit-Learn. A divisão não se divide linearmente em algum ponto, mas amostra X% e Y% aleatoriamente. Para tornar esse processo reproduzível (para fazer com que o método sempre mostre os mesmos pontos de dados), definiremos o random_state
argumento para um certo SEED
:
from sklearn.model_selection import train_test_split
SEED = 42
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=SEED)
Este trecho de código mostra 75% dos dados para treinamento e 25% dos dados para teste. Ao alterar o test_size
para 0.3, por exemplo, você poderia treinar com 70% dos dados e testar com 30%.
Usando 75% dos dados para treinamento e 25% para teste, de 20640 registros, o conjunto de treinamento contém 15480 e o conjunto de teste contém 5160. Podemos inspecionar esses números rapidamente imprimindo os comprimentos do conjunto de dados completo e dos dados divididos :
len(X)
len(X_train)
len(X_test)
Excelente! Agora podemos ajustar o escalador de dados no X_train
definir e dimensionar ambos X_train
e X_test
sem vazar nenhum dado de X_test
para dentro X_train
.
Dimensionamento de recursos para regressão KNN
Ao importar StandardScaler
, instanciando-o, ajustando-o de acordo com nossos dados de trem (evitando vazamento) e transformando conjuntos de dados de trem e teste, podemos executar o dimensionamento de recursos:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
Observação: Já que muitas vezes você vai ligar scaler.fit(X_train)
seguido scaler.transform(X_train)
- você pode chamar um único scaler.fit_transform(X_train)
seguido scaler.transform(X_test)
para tornar a chamada mais curta!
Agora nossos dados estão dimensionados! O scaler mantém apenas os pontos de dados, e não os nomes das colunas, quando aplicado em um DataFrame
. Vamos organizar os dados em um DataFrame novamente com nomes de colunas e usar describe()
observar as mudanças mean
e std
:
col_names=['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude']
scaled_df = pd.DataFrame(X_train, columns=col_names)
scaled_df.describe().T
Isso nos dará:
count mean std min 25% 50% 75% max
MedInc 15480.0 2.074711e-16 1.000032 -1.774632 -0.688854 -0.175663 0.464450 5.842113
HouseAge 15480.0 -1.232434e-16 1.000032 -2.188261 -0.840224 0.032036 0.666407 1.855852
AveRooms 15480.0 -1.620294e-16 1.000032 -1.877586 -0.407008 -0.083940 0.257082 56.357392
AveBedrms 15480.0 7.435912e-17 1.000032 -1.740123 -0.205765 -0.108332 0.007435 55.925392
Population 15480.0 -8.996536e-17 1.000032 -1.246395 -0.558886 -0.227928 0.262056 29.971725
AveOccup 15480.0 1.055716e-17 1.000032 -0.201946 -0.056581 -0.024172 0.014501 103.737365
Latitude 15480.0 7.890329e-16 1.000032 -1.451215 -0.799820 -0.645172 0.971601 2.953905
Longitude 15480.0 2.206676e-15 1.000032 -2.380303 -1.106817 0.536231 0.785934 2.633738
Observe como todos os desvios padrão estão agora 1
e os meios tornaram-se menores. Isso é o que torna nossos dados mais uniforme! Vamos treinar e avaliar um regressor baseado em KNN.
Treinando e Prevendo a Regressão KNN
A API intuitiva e estável do Scikit-Learn torna os regressores e classificadores de treinamento muito simples. Vamos importar o KNeighborsRegressor
classe da sklearn.neighbors
module, instancie-o e ajuste-o aos nossos dados de trem:
from sklearn.neighbors import KNeighborsRegressor
regressor = KNeighborsRegressor(n_neighbors=5)
regressor.fit(X_train, y_train)
No código acima, o n_neighbors
é o valor para K, ou o número de vizinhos que o algoritmo levará em consideração para escolher um novo valor médio da casa. 5
é o valor padrão para KNeighborsRegressor()
. Não existe um valor ideal para K e ele é selecionado após testes e avaliação, porém, para começar, 5
é um valor comumente usado para KNN e, portanto, foi definido como o valor padrão.
A etapa final é fazer previsões sobre nossos dados de teste. Para isso, execute o seguinte script:
y_pred = regressor.predict(X_test)
Agora podemos avaliar o quão bem nosso modelo generaliza para novos dados para os quais temos rótulos (verdade básica) – o conjunto de teste!
Avaliando o Algoritmo para Regressão KNN
As métricas de regressão mais comumente usadas para avaliar o algoritmo são erro médio absoluto (MAE), erro quadrático médio (MSE), erro quadrático médio (RMSE) e coeficiente de determinação (R2):
- Erro Médio Absoluto (MAE): Quando subtraímos os valores previstos dos valores reais, obtemos os erros, somamos os valores absolutos desses erros e obtemos sua média. Essa métrica dá uma noção do erro geral para cada previsão do modelo, quanto menor (mais próximo de 0) melhor:
$$
mae = (frac{1}{n})sum_{i=1}^{n}esquerda | Real – Previsto certo |
$$
Observação: Você também pode encontrar o y
e ŷ
(leia como y-hat) nas equações. o y
refere-se aos valores reais e a ŷ
aos valores previstos.
- Erro médio quadrático (MSE): É semelhante à métrica MAE, mas eleva ao quadrado os valores absolutos dos erros. Além disso, como no MAE, quanto menor ou mais próximo de 0, melhor. O valor do MSE é elevado ao quadrado para tornar os erros ainda maiores. Uma coisa a se atentar é que geralmente é uma métrica difícil de interpretar devido ao tamanho de seus valores e ao fato de não estarem na mesma escala dos dados.
$$
mse = soma_{i=1}^{D}(Real – Previsto)^2
$$
- Erro quadrático médio (RMSE): tenta resolver o problema de interpretação levantado com o MSE obtendo a raiz quadrada de seu valor final, de modo a dimensioná-lo de volta para as mesmas unidades dos dados. É mais fácil de interpretar e bom quando precisamos exibir ou mostrar o valor real dos dados com o erro. Ele mostra o quanto os dados podem variar, portanto, se tivermos um RMSE de 4.35, nosso modelo pode cometer um erro porque adicionou 4.35 ao valor real ou precisou de 4.35 para chegar ao valor real. Quanto mais próximo de 0, melhor também.
$$
rmse = sqrt{ soma_{i=1}^{D}(Real – Previsto)^2}
$$
A mean_absolute_error()
e mean_squared_error()
métodos de sklearn.metrics
pode ser usado para calcular essas métricas, como pode ser visto no snippet a seguir:
from sklearn.metrics import mean_absolute_error, mean_squared_error
mae = mean_absolute_error(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
rmse = mean_squared_error(y_test, y_pred, squared=False)
print(f'mae: {mae}')
print(f'mse: {mse}')
print(f'rmse: {rmse}')
A saída do script acima é assim:
mae: 0.4460739527131783
mse: 0.4316907430948294
rmse: 0.6570317671884894
O R2 pode ser calculado diretamente com o score()
método:
regressor.score(X_test, y_test)
Quais saídas:
0.6737569252627673
Os resultados mostram que o erro geral e o erro médio do nosso algoritmo KNN estão em torno de 0.44
e 0.43
. Além disso, o RMSE mostra que podemos ir acima ou abaixo do valor real dos dados adicionando 0.65
ou subtraindo 0.65
. Quão bom é isso?
Vamos verificar como estão os preços:
y.describe()
count 20640.000000
mean 2.068558
std 1.153956
min 0.149990
25% 1.196000
50% 1.797000
75% 2.647250
max 5.000010
Name: MedHouseVal, dtype: float64
A média é 2.06
e o desvio padrão da média é 1.15
então nossa pontuação de ~0.44
não é realmente estelar, mas não é tão ruim.
Com o R2, quanto mais próximo de 1 chegarmos (ou 100), melhor. O R2 informa o quanto das mudanças nos dados, ou dados variação estão sendo compreendidos ou explicado por KN.
$$
R^2 = 1 – frac{soma(Real – Previsto)^2}{soma(Real – Média Real)^2}
$$
Com um valor de 0.67
, podemos ver que nosso modelo explica 67% da variância dos dados. Já é mais de 50%, o que é bom, mas não muito bom. Existe alguma maneira de fazermos melhor?
Usamos um K predeterminado com um valor de 5
, portanto, estamos usando 5 vizinhos para prever nossos alvos, o que não é necessariamente o melhor número. Para entender qual seria um número ideal de Ks, podemos analisar os erros do nosso algoritmo e escolher o K que minimiza a perda.
Encontrando o melhor K para regressão KNN
Idealmente, você veria qual métrica se encaixa mais no seu contexto – mas geralmente é interessante testar todas as métricas. Sempre que puder testar todos eles, faça-o. Aqui, mostraremos como escolher o melhor K usando apenas o erro médio absoluto, mas você pode alterá-lo para qualquer outra métrica e comparar os resultados.
Para fazer isso, criaremos um loop for e executaremos modelos que tenham de 1 a X vizinhos. Em cada interação, vamos calcular o MAE e plotar o número de Ks junto com o resultado do MAE:
error = []
for i in range(1, 40):
knn = KNeighborsRegressor(n_neighbors=i)
knn.fit(X_train, y_train)
pred_i = knn.predict(X_test)
mae = mean_absolute_error(y_test, pred_i)
error.append(mae)
Agora, vamos traçar o error
s:
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(range(1, 40), error, color='red',
linestyle='dashed', marker='o',
markerfacecolor='blue', markersize=10)
plt.title('K Value MAE')
plt.xlabel('K Value')
plt.ylabel('Mean Absolute Error')
Olhando para o gráfico, parece que o valor MAE mais baixo é quando K é 12
. Vamos dar uma olhada mais de perto no gráfico para ter certeza, plotando menos dados:
plt.figure(figsize=(12, 6))
plt.plot(range(1, 15), error[:14], color='red',
linestyle='dashed', marker='o',
markerfacecolor='blue', markersize=10)
plt.title('K Value MAE')
plt.xlabel('K Value')
plt.ylabel('Mean Absolute Error')
Confira nosso guia prático e prático para aprender Git, com práticas recomendadas, padrões aceitos pelo setor e folha de dicas incluída. Pare de pesquisar comandos Git no Google e realmente aprender -lo!
Você também pode obter o menor erro e o índice desse ponto usando o built-in min()
função (funciona em listas) ou converter a lista em um array NumPy e obter o argmin()
(índice do elemento com o menor valor):
import numpy as np
print(min(error))
print(np.array(error).argmin())
Começamos a contar os vizinhos em 1, enquanto os arrays são baseados em 0, então o 11º índice é 12 vizinhos!
Isso significa que precisamos de 12 vizinhos para poder prever um ponto com o menor erro MAE. Podemos executar o modelo e as métricas novamente com 12 vizinhos para comparar os resultados:
knn_reg12 = KNeighborsRegressor(n_neighbors=12)
knn_reg12.fit(X_train, y_train)
y_pred12 = knn_reg12.predict(X_test)
r2 = knn_reg12.score(X_test, y_test)
mae12 = mean_absolute_error(y_test, y_pred12)
mse12 = mean_squared_error(y_test, y_pred12)
rmse12 = mean_squared_error(y_test, y_pred12, squared=False)
print(f'r2: {r2}, nmae: {mae12} nmse: {mse12} nrmse: {rmse12}')
O código a seguir sai:
r2: 0.6887495617137436,
mae: 0.43631325936692505
mse: 0.4118522151025172
rmse: 0.6417571309323467
Com 12 vizinhos, nosso modelo KNN agora explica 69% da variação nos dados e perdeu um pouco menos, passando de 0.44
para 0.43
, 0.43
para 0.41
e 0.65
para 0.64
com as respectivas métricas. Não é uma melhoria muito grande, mas é uma melhoria, no entanto.
Observação: Indo mais longe nesta análise, fazer uma Análise Exploratória de Dados (EDA) juntamente com a análise de resíduos pode ajudar a selecionar recursos e obter melhores resultados.
Já vimos como usar KNN para regressão – mas e se quiséssemos classificar um ponto ao invés de prever seu valor? Agora, podemos ver como usar o KNN para classificação.
Classificação usando K-Nearest Neighbors com Scikit-Learn
Nesta tarefa, em vez de prever um valor contínuo, queremos prever a classe à qual esses grupos de blocos pertencem. Para fazer isso, podemos dividir o valor médio da casa para os distritos em grupos com diferentes faixas de valor da casa ou caixas.
Quando você deseja usar um valor contínuo para classificação, geralmente pode agrupar os dados. Dessa forma, você pode prever grupos, em vez de valores.
Dados de pré-processamento para classificação
Vamos criar os compartimentos de dados para transformar nossos valores contínuos em categorias:
df["MedHouseValCat"] = pd.qcut(df["MedHouseVal"], 4, retbins=False, labels=[1, 2, 3, 4])
Então, podemos dividir nosso conjunto de dados em seus atributos e rótulos:
y = df['MedHouseValCat']
X = df.drop(['MedHouseVal', 'MedHouseValCat'], axis = 1)
Já que usamos o MedHouseVal
coluna para criar bins, precisamos descartar a MedHouseVal
coluna e MedHouseValCat
colunas de X
. Dessa forma, o DataFrame
conterá as primeiras 8 colunas do conjunto de dados (ou seja, atributos, recursos) enquanto nosso y
conterá apenas o MedHouseValCat
rótulo atribuído.
Observação: Você também pode selecionar colunas usando .iloc()
em vez de derrubá-los. Ao soltar, esteja ciente de que você precisa atribuir y
valores antes de atribuir X
valores, porque você não pode atribuir uma coluna descartada de um DataFrame
para outro objeto na memória.
Dividindo dados em conjuntos de treinamento e teste
Como foi feito com a regressão, também dividiremos o conjunto de dados em divisões de treinamento e teste. Como temos dados diferentes, precisamos repetir esse processo:
from sklearn.model_selection import train_test_split
SEED = 42
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=SEED)
Usaremos o valor padrão do Scikit-Learn de 75% de dados de trem e 25% de dados de teste novamente. Isso significa que teremos o mesmo número de registros de treinamento e teste que na regressão anterior.
Dimensionamento de recursos para classificação
Como estamos lidando com o mesmo conjunto de dados não processado e suas unidades de medida variáveis, realizaremos o dimensionamento de recursos novamente, da mesma forma que fizemos para nossos dados de regressão:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
Treinamento e Previsão para Classificação
Depois de categorizar, dividir e dimensionar os dados, podemos finalmente encaixar um classificador neles. Para a previsão, usaremos 5 vizinhos novamente como linha de base. Você também pode instanciar o KNeighbors_
class sem argumentos e usará automaticamente 5 vizinhos. Aqui, em vez de importar o KNeighborsRegressor
, vamos importar o KNeighborsClassifier
, classe:
from sklearn.neighbors import KNeighborsClassifier
classifier = KNeighborsClassifier()
classifier.fit(X_train, y_train)
Depois de encaixar o KNeighborsClassifier
, podemos prever as classes dos dados de teste:
y_pred = classifier.predict(X_test)
Hora de avaliar as previsões! A previsão de classes seria uma abordagem melhor do que a previsão de valores neste caso? Vamos avaliar o algoritmo para ver o que acontece.
Avaliando KNN para Classificação
Para avaliar o classificador KNN, também podemos usar o score
mas executa uma métrica diferente, pois estamos pontuando um classificador e não um regressor. A métrica básica para classificação é accuracy
– descreve quantas previsões nosso classificador acertou. O valor de precisão mais baixo é 0 e o mais alto é 1. Geralmente, multiplicamos esse valor por 100 para obter uma porcentagem.
$$
precisão = frac{text{número de previsões corretas}}{text{número total de previsões}}
$$
Observação: É extremamente difícil obter 100% de precisão em qualquer dado real, se isso acontecer, esteja ciente de que algum vazamento ou algo errado pode estar acontecendo - não há consenso sobre um valor de precisão ideal e também depende do contexto. Dependendo do custo do erro (como é ruim se confiarmos no classificador e ele estiver errado), uma taxa de erro aceitável pode ser de 5%, 10% ou até 30%.
Vamos pontuar nosso classificador:
acc = classifier.score(X_test, y_test)
print(acc)
Observando a pontuação resultante, podemos deduzir que nosso classificador acertou ~62% de nossas classes. Isso já ajuda na análise, embora sabendo apenas o que o classificador acertou fica difícil melhorá-lo.
Existem 4 classes em nosso conjunto de dados - e se nosso classificador obtivesse 90% das classes 1, 2 e 3 certas, se apenas 30% da classe 4 certo?
Uma falha sistêmica de alguma classe, em oposição a uma falha equilibrada compartilhada entre classes, pode resultar em uma pontuação de precisão de 62%. A precisão não é uma métrica muito boa para avaliação real – mas serve como um bom proxy. Na maioria das vezes, com conjuntos de dados balanceados, uma precisão de 62% é distribuída de forma relativamente uniforme. Além disso, na maioria das vezes, os conjuntos de dados não são balanceados, então voltamos à estaca zero com a precisão sendo uma métrica insuficiente.
Podemos analisar mais profundamente os resultados usando outras métricas para determinar isso. Este passo também é diferente da regressão, aqui usaremos:
- Matriz de Confusão: Para saber o quanto acertamos ou erramos cada aula. Os valores que foram corretos e corretamente previstos são chamados verdadeiros positivos aqueles que foram previstos como positivos, mas não foram positivos, são chamados falso-positivo. A mesma nomenclatura de verdadeiros negativos e falsos negativos é usado para valores negativos;
- Precisão: Para entender quais valores de previsão corretos foram considerados corretos pelo nosso classificador. A precisão dividirá esses valores de verdadeiros positivos por qualquer coisa que tenha sido prevista como positiva;
$$
precisão = frac{texto{verdadeiro positivo}}{texto{verdadeiro positivo} + texto{falso positivo}}
$$
- Recordar: para entender quantos dos verdadeiros positivos foram identificados pelo nosso classificador. O recall é calculado dividindo os verdadeiros positivos por qualquer coisa que deveria ter sido prevista como positiva.
$$
recall = frac{texto{verdadeiro positivo}}{texto{verdadeiro positivo} + texto{falso negativo}}
$$
- Pontuação F1: O balanceamento ou média harmônica de precisão e revocação. O valor mais baixo é 0 e o mais alto é 1. Quando
f1-score
é igual a 1, significa que todas as classes foram previstas corretamente – esta é uma pontuação muito difícil de obter com dados reais (exceções quase sempre existem).
$$
text{f1-score} = 2* frac{text{precisão} * text{recall}}{text{precisão} + text{recall}}
$$
Observação: Uma pontuação F1 ponderada também existe, e é apenas uma F1 que não aplica o mesmo peso a todas as classes. O peso é tipicamente ditado pelas classes ajuda – quantas instâncias “suportam” a pontuação F1 (a proporção de rótulos pertencentes a uma determinada classe). Quanto menor o suporte (quanto menos instâncias de uma classe), menor a ponderação de F1 para essa classe, porque é menos confiável.
A confusion_matrix()
e classification_report()
métodos do sklearn.metrics
módulo pode ser usado para calcular e exibir todas essas métricas. o confusion_matrix
é melhor visualizado usando um mapa de calor. O relatório de classificação já nos dá accuracy
, precision
, recall
e f1-score
, mas você também pode importar cada uma dessas métricas de sklearn.metrics
.
Para obter métricas, execute o seguinte snippet:
from sklearn.metrics import classification_report, confusion_matrix
import seaborn as sns
classes_names = ['class 1','class 2','class 3', 'class 4']
cm = pd.DataFrame(confusion_matrix(yc_test, yc_pred),
columns=classes_names, index = classes_names)
sns.heatmap(cm, annot=True, fmt='d');
print(classification_report(y_test, y_pred))
A saída do script acima é assim:
precision recall f1-score support
1 0.75 0.78 0.76 1292
2 0.49 0.56 0.53 1283
3 0.51 0.51 0.51 1292
4 0.76 0.62 0.69 1293
accuracy 0.62 5160
macro avg 0.63 0.62 0.62 5160
weighted avg 0.63 0.62 0.62 5160
Os resultados mostram que a KNN conseguiu classificar todos os 5160 registros do conjunto de teste com 62% de precisão, acima da média. Os suportes são bastante iguais (distribuição uniforme de classes no conjunto de dados), portanto, o F1 ponderado e o F1 não ponderado serão aproximadamente os mesmos.
Também podemos ver o resultado das métricas para cada uma das 4 classes. A partir disso, podemos perceber que class 2
teve a menor precisão, menor recall
, e mais baixo f1-score
. Class 3
está logo atrás class 2
por ter as pontuações mais baixas, e então, temos class 1
com as melhores pontuações seguidas de class 4
.
Observando a matriz de confusão, podemos ver que:
class 1
foi principalmente confundido comclass 2
em 238 casosclass 2
paraclass 1
em 256 entradas, e paraclass 3
em 260 casosclass 3
foi principalmente enganado porclass 2
, 374 entradas eclass 4
, em 193 casosclass 4
foi classificado erroneamente comoclass 3
para 339 entradas, e comoclass 2
em 130 casos.
Além disso, observe que a diagonal exibe os valores positivos verdadeiros, ao olhar para ela, é fácil ver que class 2
e class 3
têm os valores menos previstos corretamente.
Com esses resultados, poderíamos aprofundar a análise inspecionando-os ainda mais para descobrir por que isso aconteceu e também entendendo se 4 classes são a melhor maneira de agrupar os dados. Talvez valores de class 2
e class 3
estavam muito próximos um do outro, então ficou difícil distingui-los.
Sempre tente testar os dados com um número diferente de compartimentos para ver o que acontece.
Além do número arbitrário de compartimentos de dados, há também outro número arbitrário que escolhemos, o número de K vizinhos. A mesma técnica que aplicamos à tarefa de regressão pode ser aplicada à classificação ao determinar o número de Ks que maximizam ou minimizam um valor de métrica.
Encontrando a melhor classificação K para KNN
Vamos repetir o que foi feito para a regressão e plotar o gráfico dos valores K e a métrica correspondente para o conjunto de teste. Você também pode escolher qual métrica melhor se adapta ao seu contexto, aqui, vamos escolher f1-score
.
Desta forma, vamos traçar o f1-score
para os valores previstos do conjunto de teste para todos os valores de K entre 1 e 40.
Primeiro, importamos o f1_score
da sklearn.metrics
e então calcular seu valor para todas as previsões de um classificador K-Nearest Neighbors, onde K varia de 1 a 40:
from sklearn.metrics import f1_score
f1s = []
for i in range(1, 40):
knn = KNeighborsClassifier(n_neighbors=i)
knn.fit(X_train, y_train)
pred_i = knn.predict(X_test)
f1s.append(f1_score(y_test, pred_i, average='weighted'))
O próximo passo é traçar o f1_score
valores contra valores K. A diferença da regressão é que ao invés de escolher o valor K que minimiza o erro, desta vez vamos escolher o valor que maximiza a f1-score
.
Execute o seguinte script para criar o gráfico:
plt.figure(figsize=(12, 6))
plt.plot(range(1, 40), f1s, color='red', linestyle='dashed', marker='o',
markerfacecolor='blue', markersize=10)
plt.title('F1 Score K Value')
plt.xlabel('K Value')
plt.ylabel('F1 Score')
O gráfico de saída fica assim:
A partir da saída, podemos ver que o f1-score
é o mais alto quando o valor de K é 15
. Vamos treinar novamente nosso classificador com 15 vizinhos e ver o que ele faz com os resultados do nosso relatório de classificação:
classifier15 = KNeighborsClassifier(n_neighbors=15)
classifier15.fit(X_train, y_train)
y_pred15 = classifier15.predict(X_test)
print(classification_report(y_test, y_pred15))
Isso resulta em:
precision recall f1-score support
1 0.77 0.79 0.78 1292
2 0.52 0.58 0.55 1283
3 0.51 0.53 0.52 1292
4 0.77 0.64 0.70 1293
accuracy 0.63 5160
macro avg 0.64 0.63 0.64 5160
weighted avg 0.64 0.63 0.64 5160
Observe que nossas métricas melhoraram com 15 vizinhos, temos 63% de precisão e superior precision
, recall
e f1-scores
, mas ainda precisamos analisar melhor os bins para tentar entender por que o f1-score
para aulas 2
e 3
ainda está baixo.
Além de usar KNN para regressão e determinação de valores de blocos e para classificação, para determinar classes de blocos – também podemos usar KNN para detectar quais valores médios de blocos são diferentes da maioria – aqueles que não seguem o que a maioria dos dados está fazendo. Em outras palavras, podemos usar KNN para detectando valores discrepantes.
Implementando KNN para detecção de outliers com Scikit-Learn
Detecção de valores discrepantes usa outro método que difere do que havíamos feito anteriormente para regressão e classificação.
Aqui, veremos a que distância cada um dos vizinhos está de um ponto de dados. Vamos usar os 5 vizinhos padrão. Para um ponto de dados, calcularemos a distância para cada um dos K vizinhos mais próximos. Para fazer isso, importaremos outro algoritmo KNN do Scikit-learn que não é específico para regressão ou classificação chamado simplesmente NearestNeighbors
.
Após a importação, vamos instanciar um NearestNeighbors
class com 5 vizinhos – você também pode instanciar com 12 vizinhos para identificar outliers em nosso exemplo de regressão ou com 15, para fazer o mesmo para o exemplo de classificação. Em seguida, ajustaremos nossos dados de trem e usaremos o kneighbors()
método para encontrar nossas distâncias calculadas para cada ponto de dados e índices de vizinhos:
from sklearn.neighbors import NearestNeighbors
nbrs = NearestNeighbors(n_neighbors = 5)
nbrs.fit(X_train)
distances, indexes = nbrs.kneighbors(X_train)
Agora temos 5 distâncias para cada ponto de dados – a distância entre ele e seus 5 vizinhos, e um índice que os identifica. Vamos dar uma olhada nos três primeiros resultados e no formato do array para visualizar melhor.
Para ver a forma das três primeiras distâncias, execute:
distances[:3], distances.shape
(array([[0. , 0.12998939, 0.15157687, 0.16543705, 0.17750354],
[0. , 0.25535314, 0.37100754, 0.39090243, 0.40619693],
[0. , 0.27149697, 0.28024623, 0.28112326, 0.30420656]]),
(3, 5))
Observe que existem 3 linhas com 5 distâncias cada. Também podemos olhar e os índices dos vizinhos:
indexes[:3], indexes[:3].shape
Isto resulta em:
(array([[ 0, 8608, 12831, 8298, 2482],
[ 1, 4966, 5786, 8568, 6759],
[ 2, 13326, 13936, 3618, 9756]]),
(3, 5))
Na saída acima, podemos ver os índices de cada um dos 5 vizinhos. Agora, podemos continuar a calcular a média das 5 distâncias e traçar um gráfico que conte cada linha no eixo X e exiba cada distância média no eixo Y:
dist_means = distances.mean(axis=1)
plt.plot(dist_means)
plt.title('Mean of the 5 neighbors distances for each data point')
plt.xlabel('Count')
plt.ylabel('Mean Distances')
Observe que há uma parte do gráfico em que as distâncias médias têm valores uniformes. Esse ponto do eixo Y em que as médias não são muito altas ou muito baixas é exatamente o ponto que precisamos identificar para cortar os valores discrepantes.
Neste caso, é onde a distância média é 3. Vamos traçar o gráfico novamente com uma linha pontilhada horizontal para poder identificá-lo:
dist_means = distances.mean(axis=1)
plt.plot(dist_means)
plt.title('Mean of the 5 neighbors distances for each data point with cut-off line')
plt.xlabel('Count')
plt.ylabel('Mean Distances')
plt.axhline(y = 3, color = 'r', linestyle = '--')
Esta linha marca a distância média para a qual todos os valores acima dela variam. Isso significa que todos os pontos com mean
distância acima 3
são nossos outliers. Podemos descobrir os índices desses pontos usando np.where()
. Este método irá produzir tanto True
or False
para cada índice em relação ao mean
acima 3 condição:
import numpy as np
outlier_index = np.where(dist_means > 3)
outlier_index
O código acima resulta em:
(array([ 564, 2167, 2415, 2902, 6607, 8047, 8243, 9029, 11892,
12127, 12226, 12353, 13534, 13795, 14292, 14707]),)
Agora temos nossos índices de pontos discrepantes. Vamos localizá-los no dataframe:
outlier_values = df.iloc[outlier_index]
outlier_values
Isto resulta em:
MedInc HouseAge AveRooms AveBedrms Population AveOccup Latitude Longitude MedHouseVal
564 4.8711 27.0 5.082811 0.944793 1499.0 1.880803 37.75 -122.24 2.86600
2167 2.8359 30.0 4.948357 1.001565 1660.0 2.597809 36.78 -119.83 0.80300
2415 2.8250 32.0 4.784232 0.979253 761.0 3.157676 36.59 -119.44 0.67600
2902 1.1875 48.0 5.492063 1.460317 129.0 2.047619 35.38 -119.02 0.63800
6607 3.5164 47.0 5.970639 1.074266 1700.0 2.936097 34.18 -118.14 2.26500
8047 2.7260 29.0 3.707547 1.078616 2515.0 1.977201 33.84 -118.17 2.08700
8243 2.0769 17.0 3.941667 1.211111 1300.0 3.611111 33.78 -118.18 1.00000
9029 6.8300 28.0 6.748744 1.080402 487.0 2.447236 34.05 -118.78 5.00001
11892 2.6071 45.0 4.225806 0.903226 89.0 2.870968 33.99 -117.35 1.12500
12127 4.1482 7.0 5.674957 1.106998 5595.0 3.235975 33.92 -117.25 1.24600
12226 2.8125 18.0 4.962500 1.112500 239.0 2.987500 33.63 -116.92 1.43800
12353 3.1493 24.0 7.307323 1.460984 1721.0 2.066026 33.81 -116.54 1.99400
13534 3.7949 13.0 5.832258 1.072581 2189.0 3.530645 34.17 -117.33 1.06300
13795 1.7567 8.0 4.485173 1.120264 3220.0 2.652389 34.59 -117.42 0.69500
14292 2.6250 50.0 4.742236 1.049689 728.0 2.260870 32.74 -117.13 2.03200
14707 3.7167 17.0 5.034130 1.051195 549.0 1.873720 32.80 -117.05 1.80400
Nossa detecção de valores discrepantes está concluída. É assim que identificamos cada ponto de dados que se desvia da tendência geral dos dados. Podemos ver que existem 16 pontos em nossos dados de trem que devem ser mais examinados, investigados, talvez tratados ou até mesmo removidos de nossos dados (caso tenham sido inseridos erroneamente) para melhorar os resultados. Esses pontos podem ter resultado de erros de digitação, inconsistências nos valores médios dos blocos, ou mesmo ambos.
Prós e contras de KNN
Nesta seção, apresentaremos alguns dos prós e contras de usar o algoritmo KNN.
Prós
- É fácil de implementar
- É um algoritmo de aprendizado lento e, portanto, não requer treinamento em todos os pontos de dados (usando apenas os vizinhos K-Nearest para prever). Isso torna o algoritmo KNN muito mais rápido do que outros algoritmos que exigem treinamento com todo o conjunto de dados, como Máquinas de vetor de suporte, regressão linear, etc.
- Como o KNN não requer treinamento antes de fazer previsões, novos dados podem ser adicionados sem problemas
- Existem apenas dois parâmetros necessários para trabalhar com KNN, ou seja, o valor de K e a função de distância
Desvantagens
- O algoritmo KNN não funciona bem com dados de alta dimensão porque com um grande número de dimensões, a distância entre os pontos fica “estranha”, e as métricas de distância que usamos não se sustentam
- Finalmente, o algoritmo KNN não funciona bem com recursos categóricos, pois é difícil encontrar a distância entre dimensões com recursos categóricos
Indo além - Projeto de ponta a ponta manual
Neste projeto guiado, você aprenderá a criar modelos de aprendizado de máquina tradicionais poderosos, bem como modelos de aprendizado profundo, utilizar o Ensemble Learning e treinar meta-aprendizes para prever preços de imóveis a partir de uma série de modelos Scikit-Learn e Keras.
Usando Keras, a API de aprendizado profundo criada com base no Tensorflow, vamos experimentar arquiteturas, construir um conjunto de modelos empilhados e treinar um meta-aprendiz rede neural (modelo de nível 1) para descobrir o preço de uma casa.
O aprendizado profundo é incrível – mas antes de recorrer a ele, é aconselhável tentar também resolver o problema com técnicas mais simples, como aprendizagem superficial algoritmos. Nosso desempenho de linha de base será baseado em um Regressão da Floresta Aleatória algoritmo. Além disso, exploraremos a criação de conjuntos de modelos por meio do Scikit-Learn por meio de técnicas como ensacamento e votação.
Este é um projeto de ponta a ponta e, como todos os projetos de Machine Learning, começaremos com Análise exploratória de dados, Seguido por Pré-processamento de dados e finalmente Edifício Raso e Modelos de aprendizado profundo para ajustar os dados que exploramos e limpamos anteriormente.
Conclusão
KNN é um algoritmo simples, mas poderoso. Ele pode ser usado para muitas tarefas, como regressão, classificação ou detecção de valores discrepantes.
O KNN tem sido amplamente utilizado para encontrar similaridade de documentos e reconhecimento de padrões. Também tem sido empregado para o desenvolvimento de sistemas de recomendação e para redução de dimensionalidade e etapas de pré-processamento para visão computacional – particularmente tarefas de reconhecimento facial.
Neste guia, passamos pela regressão, classificação e detecção de valores discrepantes usando a implementação do algoritmo K-Nearest Neighbor do Scikit-Learn.