Laboratório 4 - Classificação com Sklearn
O objetivo desse laboratório é aplicar os conhecimentos sobre análise e pré-processamento de dados para treinar modelos de classificação. Dessa forma, as seguintes atividades serão realizadas:
- Importação dos Dados
- Pré-processamento e Análise
- Treinamento de Diferentes Modelos para Classificação
- Avaliação e Visualização de Desempenho
- Ajuste de Hiperparâmetros
- Visualizando Regiões de Decisão
Importando Dados
Iremos utilizar dados disponívels no O UCI Machine Learning Repository, mais comumente referido como "UCI datasets", a qual é uma coleção amplamente utilizada de conjuntos de dados para pesquisa em aprendizado de máquina e análise de dados. Este repositório é mantido pelo Center for Machine Learning and Intelligent Systems da Universidade da Califórnia, Irvine (UCI).
Podemos utilizado diretamente do nosso código instalando o pacote ucimlrepo
. Dessa forma, podemos instalar o pacote da seguinte forma:
pip install ucimlrepo
Após a instalação, podemos importa-lo no nosso código e fazer o download do dataset alvo utilizando seu id
. para esse laboratório, utilizaremos o dataset de Breast Cancer Wisconsin (Diagnostic), esse dataset possui id=17
, assim utilizamos o seguinte código:
from ucimlrepo import fetch_ucirepo
# faz o fetch do dataset
dataset = fetch_ucirepo(id=17)
# dados (pandas dataframes)
X = dataset.data.features
y = dataset.data.targets
X
Pré-processamento e Análise
Agora precisamos analisar os dados e processar-los caso for necessário. Primeiramente podemos analisar untilizando X.info()
para apresentar as principais informações do dataset.
Valores Nulos
Em seguida, podemos verificar a existencia de valores nulos utilizando:
X.isnull().sum()
moda
ou remove-los caso a quantidade seja muito pequena. A seguir são apresentados exemplos para tratamento de dados nulos:
#removendo atributos com grandes quantidade de valores nulos
X.drop(['atributo_1', 'atributo_2'], axis=1, inplace=True)
#inserindo moda em valores nulos de um atributo
moda_ = X['atributo_3'].mode()[0]
X['atributo_3'].fillna(moda_education, inplace=True)
Atenção
Note que estamos trabalhando com dados em dois dataframes X
e y
. Portanto, caso algum exemplo seja removido de X
ele também deve ser removido de y
para manter a integridade do dataset
Encoding de Dados
Muitas vezes podemos ter dados do tipo object
em nossos datasets. Dados desse tipo são dados textuais, nominais ou categóricos que não podemos fornece-los para os modelos de aprendizado de máquina dessa forma. Assim, precisamos converte-los para uma representação numérica mantendo suas características. Para isso, podemos utilizar o LabelEncoder
presente no sklearn
. Primeiramente, precisamos identificar os atributos que precisam ser alterados da seguinte forma:
atributos_object = X.dtypes == 'object'
atributos_object = X.columns[atributos_object]
atributos_object
Em seguida, para cada atributo encontrado, aplicamos o LabelEnconder
para transformar esse atributo em numérico da seguinte forma:
from sklearn.preprocessing import LabelEncoder
for atributo in atributos_object:
X[atributo] = LabelEncoder().fit_transform(X[atributo])
Tip
É importante aplicar o encoding em cada atributo de forma independente e apenas nos atributos do tipo object
caso contrário até valores do tipo float seriam encodados o que não é correto.
Normalizando Dados
Após aplicar o encoding nos atributos necessários, precisamos normalizar nossos dados para que cada atributo tenha a mesma importância para os modelos (i.e., tenham a mesma escala). Além disso, a normalização melhora a convergência dos e desempenho dos modelos. Assim, podemos realizar a normalização da seguinte forma:
from sklearn.preprocessing import MinMaxScaler
for atributo in X.columns:
X[atributo] = MinMaxScaler().fit_transform(X[[atributo]])
Note
Existem diferentes tipos de normalizadores disponíveis no sklearn que também podem ser usados, como por exemplo: Normalizer, MinMaxScaler, StandardScaler
Reduzindo Dimensionalidade
A redução de dimensionalidade pode ser interessante para datasets com grandes quantidades de atributos, com ela podemos reduzir a quantidade de dimensões para treinar modelos de forma mais eficiente, garantindo a mesma estrutura e representatividade dos dados. Realizaremos a redução de dimensionalidades com o PCA (Principal Component Analysis), assim após aplicar o PCA em nosso dataset, podemos analizar a quantidade de componentes necessárias para atingir uma variância com o explained_variance_ratio_
, que indica a proporção da variância total do conjunto de dados que é explicada por cada um dos componentes principais extraídos pelo PCA. Idealmente desejamos a quantidade de componentes que alcance pelo menos 95% das dimensões totais. O código a seguir aprenseta um exemplo para os dados desse laboratório.
from sklearn.decomposition import PCA
pca = PCA().fit(X)
plt.plot(np.cumsum(pca.explained_variance_ratio_), label='Variância explicada')
plt.axhline(0.95, color='red', linestyle='--', label='95% percentil')
plt.grid(True, linestyle='--')
plt.legend()

Dessa forma, para reduzir a quantidade de dimensões para o valor encontrado pela análise, utilizamos o seguinte código.
pca = PCA(n_components=8)
X_pca = pca.fit_transform(X)
Classificadores
Antes de realizar o treinamento dos modelos devemos separar nossos dados em dois conjuntos distintos, chamados de dados treino e dados de teste. O primeiro conjunto é utilizado para treinar o modelo, enquanto o segudo é utilizado para analisar o modelo em conjutno de dados que ainda não foram vistos durante o treinamento.
Note
Para treinar os modelos, temos os dados de entrada (X) e rótulos (y) para cada dado de entrada. Dessa forma, precisamos gerar os seguintes conjuntos de dados x_treino, y_treino, x_teste, y_teste
Podemos fazer essa divisão dos dados utilizando train_test_split
da seguinte forma:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_pca, y, test_size=0.4, random_state=99)
Definindo Classificadores
Existem diversos classificadores já disponíveis no sklearn
. Porntanto, para utilizarmos no nosso código precisamos apenas importar e instânciar. Nesse exemplo vamos utilizar os seguites classificadores:
- K-Vizinhos mais Próximos (KNN)
- Máquina de Vetores de Suporte (SVM)
- Árvode de Decisão (DT)
- Florestas Aleatórias (RF)
- Regressão Logística (LR)
- Naive Bayes (NB)
O métodos para treinamento é o mesmo para todos os classificadores .fit()
. Assim, para facilitar o gerenciamento vamos instanciar todos os classificadores em um dicionário da seguinte forma:
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
classificadores = {
'KNN': KNeighborsClassifier(),
'SVM': SVC(),
'DT' : DecisionTreeClassifier(),
'RF' : RandomForestClassifier(),
'LR' : LogisticRegression(),
'NB' : GaussianNB()
}
Treinando Classificadores
Após instânciar cada um dos classificadores, podemos treinar cada um deles dentro de um laço for
. Além disso, podemos coletar algumas métricas durante o treinamento. Nesse exemplo vamos utilizar o tempo de treinamento e também a acurácia.
import time
acuracia_treino = {}
tempo_treino = {}
for clf in classificadores:
start_time = time.process_time()
classificadores[clf].fit(X_train, y_train)
acuracia_treino[clf] = classificadores[clf].score(X_train, y_train)
tempo_treino[clf] = time.process_time() - start_time
Visualizando Desempenho - Treino
Com os resultados coletados podemos gerar visualizações para comparar o desempenho de cada um dos classificadores, como apresentado no código a seguir:
fig, ax = plt.subplots(1, 2, figsize=(15, 5))
sns.barplot(x=list(acuracia_treino.keys()), y=list(acuracia_treino.values()), ax=ax[0], ec='k')
sns.barplot(x=list(acuracia_treino.keys()), y=list(acuracia_treino.values()), ax=ax[1], ec='k')
ax[0].set_yscale('log')
ax[0].set_title('Acurácia de Treino', size=15)
ax[1].set_yscale('log')
ax[1].set_title('Tempo Treinamento', size=15)

Atenção
Os resultados apresentados são para o conjunto de dados utilizados para treinamento, os quais os classificadores já "viram", esses resultados não são relevantes para analisar a generalização dos modelos. Portanto, reportar o desempenho do modelo devemos utilizar dados de teste que foram os dados que o modelo ainda não viu.
Visualizando Desempenho - Teste
Para analisar o desempenho dos podemos utilizar diversas métricas, como por exemplo:
- Acurácia: Medir a proporção de previsões corretas em relação ao total de previsões feitas. É útil quando as classes estão balanceadas (isto é, cada classe tem aproximadamente o mesmo número de instâncias)
- Precision: Avaliar a proporção de verdadeiros positivos (TP) entre todas as instâncias que foram preditas como positivas. Importante quando o custo de falsos positivos é alto, ou seja, é crucial evitar falsos alarmes.
- Recal: Medir a proporção de verdadeiros positivos entre todas as instâncias que realmente são positivas. Essencial quando o custo de falsos negativos é alto, como em diagnósticos médicos, onde é importante identificar todas as ocorrências da classe positiva.
- F1-Score: Fornecer uma média harmônica entre precisão (precision) e recall, equilibrando a importância de ambos. Útil em cenários onde há um trade-off entre precisão e recall, especialmente quando as classes estão desequilibradas.
Podemos utilizar o seguinte código para avaliar cada métrica apresentada:
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
metricas = {}
for clf in classificadores:
metricas[clf] = {}
classificadores[clf].fit(X_train, y_train)
predictions = classificadores[clf].predict(X_test)
metricas[clf]['accuracy'] = accuracy_score(y_test, predictions)
metricas[clf]['precision'] = precision_score(y_test, predictions)
metricas[clf]['recall'] = recall_score(y_test, predictions)
metricas[clf]['f1'] = f1_score(y_test, predictions)
resultado = pd.DataFrame(metricas)

Matriz de Confusão
A matriz de confusão é uma ferramenta usada para avaliar o desempenho de um modelo de classificação, comparando as previsões do modelo com os valores reais das classes. Ela apresenta os resultados em uma tabela de dupla entrada que permite visualizar facilmente as taxas de acertos e erros para cada classe. Ela fornece uma visão detalhada de como as previsões do modelo se distribuem em relação às classes reais. Isso ajuda a identificar não apenas a quantidade de acertos e erros, mas também o tipo de erro (falsos positivos ou falsos negativos). Podemos utilizar o seguinte código para apresentar a matriz de confusão para cada classificador
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
fig, ax = plt.subplots(nrows=2, ncols=3, figsize=(15, 9))
ax = ax.flatten()
for idx, clf in enumerate(classificadores):
predictions = classificadores[clf].predict(X_test)
cm = confusion_matrix(y_test, predictions, )
disp = ConfusionMatrixDisplay(confusion_matrix=cm,
display_labels=classificadores[clf].classes_, )
disp.plot(ax=ax[idx], cmap='inferno')
ax[idx].set_title(clf)

Ajustando Hiperparâmetros & Validação Cruzada
Como visto em aula, diferentes valores para os hiperparâmetros do modelo resulta em comportamentos diferentes. Assim, devemos encontrar o melhor resultado para nosso conjunto de dados utilizando valores arbitrários.
Uma técnica que é utilizada durante esse processo é a validação cruzada que é usada para avaliar a robustez e a capacidade de generalização de um modelo. Ao invés de treinar e testar o modelo em uma única divisão dos dados (como usando um simples conjunto de treinamento e teste), a validação cruzada divide os dados em múltiplas partes (ou folds) e realiza múltiplos ciclos de treinamento e teste.
A validação cruzada garante que o modelo seja avaliado em diferentes subconjuntos dos dados, ajudando a evitar problemas como overfitting (quando o modelo se ajusta demais ao conjunto de treino) ou underfitting (quando o modelo é muito simples para capturar os padrões nos dados).
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
ks = [3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 37, 33, 35, 53]
accs = []
for k in ks:
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(X_train, y_train)
scores = cross_val_score(knn, x_treino, y_treino, cv=5)
accs.append(scores.mean())
fig, ax = plt.subplots(figsize=(10, 5))
sns.lineplot(x=ks, y=accs, linewidth=3, ax=ax)
plt.grid(True, linestyle=':')
plt.xlabel('Número de vizinhos')
plt.ylabel('Acurácia média')

Visualizando Regiões de Decisão
Analisar as regiões de decisão de um modelo de classificação é essencial para interpretar o comportamento do modelo, detectar problemas como overfitting ou underfitting, identificar áreas problemáticas nos dados e, em última análise, ajustar e melhorar o modelo para alcançar um desempenho mais robusto e confiável.
Para gerar as regiões de decisão vamos utilizar a biblioteca mlxtend
que pode ser instalada via pip
.
pip install mlxtend
dica
from sklearn.decomposition import PCA
import seaborn as sns
pca = PCA(n_components=2)
dados_pca = pca.fit_transform(X)
x_treino, x_teste, y_treino, y_teste = train_test_split(dados_pca, y, test_size=0.2, random_state=99, shuffle=True)
Após reduzir as dimensões podemos realizar a plotagem dos dados e também plotagem das regiõse de decisão com o método plot_decision_regions
para cada um dos classificadores da seguinte forma:
from mlxtend.plotting import plot_decision_regions
fig, ax = plt.subplots(nrows=2, ncols=3, figsize=(15, 7))
ax = ax.flatten()
for idx, clf in enumerate(classificadores):
classificadores[clf].fit(x_treino, y_treino)
plot_decision_regions(x_teste, y_teste, clf=classificadores[clf],
legend=2, ax=ax[idx], colors='blue,red')
ax[idx].set_title(clf)

Exercício - (Entregável)
Para o exercício você deve procurar outro dataset para classificação disponível no UCI
e realizar as seguintes tarefas:
Dataset
O dataset utilizado para esse exercício deve conter uma quantidade significativa de registros (i.e., milhares de dados), possuir valores nulos para tratamento e também possuir múltiplos rótulos (i.e., > 2)
- Realizar a importação dos dados utilizando o
ucimlrepo
- Analizar o dataset, gerando visualizações
- Realizar o pré-processamento dos dados tratanto atributos to tipo
object
, valores nulos, normalização e redução de dimensionalidade - Fazer o split dos dados para treino e teste
- Treinar quatro classificadores diferentes ajustantando seus hiperparâmetros utilizando validação cruzada
- Gerar visualizações para as seguintes métricas de acurácia, precision e recall
- Gerar a matriz de confusão para cada classificador
- Plotar as regiões de decisão para os classificadores
Entraga
A entrega deve ser feita pelo Classroom enviando o notebook .ipynb
já executado com todas os outputs e plotagens necessárias.