Sabemos que o termo "Inteligência Artificial" se popularizou nos últimos anos,
MAS
É apenas um nome bonitinho para o que Turing e Arthur Samuel construíam já na década de 40 e 50: Machine Learning.
Hoje vamos aprender a como treinar um algoritmo utilizando a biblioteca Scikit-Learn do Python.
Veremos o KNN (K-Nearest Neighbors).
KNN
O KNN é um algoritmo de aprendizado supervisionado usado para classificação e regressão.
A ideia central do KNN é classificar um dado novo com base na proximidade dos dados já conhecidos.
Por exemplo, se queremos classificar a bolinha de cor verde pertencente a classe azul ou vermelha, verificamos quantas bolinhas de cada classe estão próximas dela e com base na quantidade de vizinhos, classificamos a bolinha verde (No exemplo, a bolinha verde irá ser classificada como azul).
Essa quantidade de vizinhos para a classificação é denominada K.
Objetivo
Aprender a estrutura básica de treinamento de um algoritmo utilizando a biblioteca Scikit-Learn.
Como faremos isso?
Treinando o algoritmo KNN.
Base de dados Utilizada
Vamos utilizar uma base de dados que contém amostras de 10 anos de estações meteorológicas da Austrália.
Disponível em: https://www.kaggle.com/datasets/jsphyg/weather-dataset-rattle-package/discussion/485344
Tarefa de predição
Classificar se no dia após a observação irá chover, tendo como variável alvo RainTomorrow.
Uma olhadinha na base de dados
O dataset contém 145.460 registros.
É dividido em 23 colunas (características).
Bizoiando as primeiras linhas do dataset:
import pandas as pd
dataframe = pd.read_csv("weatherAUS.csv")
dataframe.head()
Vamos ver a proporção de cada classe, importante para identificar se o nosso dataset está desbalanceado.
columns = dataframe.columns.tolist()
for col in columns:
#Com o normalize = True, irá retornar em percentual (E para ficar legível, multiplicamos por 100)
proportion = dataframe[col].value_counts(normalize=True) * 100
print(proportion)
print("-----------")
Onde há valores nulos?
Isso é importante para o tratamento de dados posteriormente.
print(dataframe.isnull().sum())
Ou seja, muitas colunas com gráficos nulos!
Vamos tratar!
POXA KELVIN, MAS PORQUE TEM VALORES NULOS???
Porque equipamentos falham, internet cai, em algumas regiões não tem o equipamento adequado etc etc.
Ou seja, é de responsabilidade SUA lidar com esses problemas.
Transformação e tratamento de dados
import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder
# https://www.kaggle.com/datasets/jsphyg/weather-dataset-rattle-package
# dataframe = pd.read_csv("weatherAUS.csv")
# Alterando as variáveis No e Yes para inteiro (0 e 1)
dataframe["RainToday"] = dataframe["RainToday"].map({"No": 0, "Yes": 1})
# Tratando as datas
dataframe["Date"] = pd.to_datetime(dataframe["Date"])
dataframe["Year"] = dataframe["Date"].dt.year
dataframe["Month"] = dataframe["Date"].dt.month
dataframe["Day"] = dataframe["Date"].dt.day
dataframe.drop(columns=["Date"], inplace=True)
#Capturando somente as variaveis numericas, visto que, as categoricas não fazem sentido tirar a média
numeric_columns = dataframe.select_dtypes(include=[np.number]).columns.tolist()
columns_with_nulls_numeric = dataframe[numeric_columns].columns[dataframe[numeric_columns].isnull().any()].to_list()
for column in columns_with_nulls_numeric:
location_month_mean_value = dataframe.groupby(["Location", "Month"])[column].transform("mean")
dataframe[column] = dataframe[column].fillna(location_month_mean_value)
# Quando uma location está completamente sem valores para determinada região, vamos tentar tirar a media do mes na respectiva
numeric_columns = dataframe.select_dtypes(include=[np.number]).columns.tolist()
columns_with_nulls_numeric = dataframe[numeric_columns].columns[dataframe[numeric_columns].isnull().any()].to_list()
for column in columns_with_nulls_numeric:
mean_value_month = dataframe.groupby(["Month"])[column].transform("mean")
dataframe[column] = dataframe[column].fillna(mean_value_month)
# Criando uma label (ID) para os valores em string
label_encoder = LabelEncoder()
dataframe["Location"] = label_encoder.fit_transform(dataframe["Location"])
dataframe["WindGustDir"] = label_encoder.fit_transform(dataframe["WindGustDir"])
dataframe["WindDir9am"] = label_encoder.fit_transform(dataframe["WindDir9am"])
dataframe["WindDir3pm"] = label_encoder.fit_transform(dataframe["WindDir3pm"])
# Dataframe depois dos devidos tratamentos
dataframe.head()
Como podemos ver na etapa de transformação e tratamento, temos alguns pontos importantes dessa etapa:
1 - A variávell RainToday foi transformada para valores binários.
2 - A data estava em formato de String (2008-01-01) e para a mesma foram criadas as colunas de dia, mês e ano. Após essa transformação, o campo original de Date foi excluído.
3 - Para as colunas com valores nulos, vamos utilizar a estratégia de agrupar por Localização e mês e tirar a média para o registro faltante (Lembrando que, isso não é uma regra, é apenas uma forma de fazer).
4 - Caso uma localização inteira não tenha registro, foi utilizado a média global daquela característica.
5 - Para as variáveis categóricas, foi utilizado o LabelEnconder, que dá à elas um ID, visto que, o algorítmo que estamos trabalhando não aceita valores em strings. Por exemplo, direção do vendo NORTE assumiu o valor 1, direção do vento NORTE->SUL, assumiu o valor 2, e assim sucessivamente. Isso se deve ao fato do KNN não trabalharem com valores strings.
As seguintes caracteristicas categórias foram transformadas: Location, WindGustDir, WindDir9am e WindDir3pm.
Ainda há valores nulos?
print(dataframe.isnull().sum())
Sim! E está justamente na variável que vamos prever (E EXCLUIR DO NOSSO DATASET!).
Não foi removida antes para não impactar nas médias globais.
O arroz e feijão do algoritmo
Divisão em conjuntos de treinamento e teste
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
# Cria um novo dataframe dropando a coluna alvo com valores nulos
dataframe_cleaned = dataframe.dropna(subset=["RainTomorrow"]).copy()
# Coluna alvo, ou seja, o que quero identificar, convertida para 0 e 1
y = dataframe_cleaned["RainTomorrow"].map({"No": 0, "Yes": 1})
# Dropando a coluna alvo
dataframe_cleaned.drop(columns=["RainTomorrow"], inplace = True)
print(dataframe.shape)
X = dataframe_cleaned.values
Após remoção dos registro nulos em RainTomorrow, temos um dataset com o tamanho de 142193
print(dataframe_cleaned.shape)
Diferenças de grandeza
Como o KNN utiliza o cálculo de distância para classificar os dados, as características com diferença de grandeza devem ser normalizadas.
Ou seja, imagine que uma característica seja de 0, até 1 e outra sendo de 10, até 1000. Claramente temos uma diferença de grande e ambas devem ser transformadas.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train_knn, X_test_knn, y_train_knn, y_test_knn = train_test_split(X_scaled, y, test_size=0.25)
# Aqui ocorre o treinamento do algoritmo com o valor padrão de K = 5.
knn = KNeighborsClassifier()
knn.fit(X_train_knn, y_train_knn)
# Aqui, vamos prever a variável RainTomorrow
y_pred_knn = knn.predict(X_test_knn)
LEMBRANDO QUE
Para fins de aprendizado, vamos utilizar ao longo do código os valores padrões da biblioteca scikit-learn (Ou seja, não vamos mexer nos hiperparâmetros).
Como avaliamos o nosso algoritmo? Métricas!
Lembrando que 25% do dataset de tamanho 142.193 equivale a 35.549 registros.
from sklearn.metrics import accuracy_score, confusion_matrix, precision_score, recall_score
acc_knn = accuracy_score(y_test_knn, y_pred_knn) * 100
confusion_m_knn = confusion_matrix(y_test_knn, y_pred_knn)
precision_knn = precision_score(y_test_knn, y_pred_knn) * 100
recall_knn = recall_score(y_test_knn, y_pred_knn) * 100
print("-> Resultados do KNN:")
print("Acurácia: %.2f%%\nPrecisão: %.2f%%\nRecall: %.2f%%" %(acc_knn, precision_knn, recall_knn))
print("Matriz de confusão:\n\tVerdadeiro Positivo: \t%i\tFalso Negativo: \t%i\n\tFalso Positivo: \t%i\tVerdadeiro Negativo: \t%i"
%(confusion_m_knn[0][0], confusion_m_knn[0][1], confusion_m_knn[1][0], confusion_m_knn[1][1]))
Poderia escrever um artigo inteiro apenas sobre as métricas, mas no momento vamos nos contentar em apenas entende-las:
Acurácia: A proporção de previsões corretas (verdadeiros positivos + verdadeiros negativos) em relação ao total de registros.
Precisão: A precisão é a proporção de previsões corretas entre todas as previsões positivas feitas pelo modelo.
Exemplo: Se o modelo prevê que irá chover em 100 dias e, desses 100 dias, 70 realmente choveu (VP) e 30 não choveu (FP), então a precisão seria de 70%.
Recall: É proporção de previsões corretas para a classe positiva (RainTomorrow = Yes) entre todas as instâncias realmente positivas no conjunto de dados.
Exemplo: Se realmente choveu em 100 dias e o modelo previu corretamente que chovia em 80 desses dias (VP), mas errou em 20 dias (FN), então o recall seria de 80%.
Conclusão
Eai? Nosso algoritmo está bom? Está ruim? Já podemos nos candidatar para a pessoa da previsão do tempo do jornal local?
A resposta é: DEPENDE!
O quão aceitável é uma acurácia de 83%? Para uma pessoa que vai passar o dia inteiro no PC, pouco importa!
Mas para o agricultor que pode perder um dia inteiro de trabalho, importa muito!
(Isso vale para as demais métricas!)
Faça você mesmo!
Para fins de aprendizado, recomendo que vocês mexam nos valores de K, utilizem outros meios de transformação, faça outro tipo de tratamento para os valores nulos!
Novamente, isso foi apenas um ponta pé inicial para mostrar à vocês que machine learning pode ser divertido e fácil!
Eai, amanhã vai chover?
Top comments (2)
incredible, i love it! nice article.
Que bacana! Achei muito interessante. Obrigado por compartilhar!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.