9 min de leitura
#14 - Dados Categóricos e Bagging
Como transformar colunas de texto em números com one hot encoding, e como combinar vários modelos com bootstrap aggregation (bagging) pra reduzir overfitting.
Faltam duas peças práticas antes de fechar a série: o que fazer quando uma coluna é feita de texto, não de números — e como reduzir o overfitting combinando vários modelos em vez de confiar num só.
Dados categóricos
Modelos de Machine Learning, no geral, só aceitam dados numéricos — mas datasets do mundo real vêm cheios de colunas de texto. Em vez de simplesmente descartar essa informação, dá pra transformá-la em algo que o modelo consegue usar.
Lembra do dataset de carros usado nas regressões? Ele tem as colunas Car e Model, que ficaram de fora das previsões até agora — não dá pra encontrar uma relação linear entre uma variável categórica (a marca do carro) e uma numérica (o CO2) do jeito que ela está.
One Hot Encoding
A solução é criar uma coluna numérica pra cada categoria diferente, com valores 1 ou 0 — 1 significa que aquela linha pertence a esse grupo, 0 significa que não. Essa técnica se chama one hot encoding, e o Pandas já tem um método pronto pra isso: get_dummies().
import pandas
carros = pandas.read_csv("data.csv")
carros_codificados = pandas.get_dummies(carros[['Car']])
print(carros_codificados.to_string())
O resultado é uma coluna pra cada marca de carro presente no dataset (Car_Audi, Car_BMW, Car_Fiat, Car_Ford, e assim por diante), com um único 1 na coluna correspondente à marca daquele carro, e 0 em todas as outras:
Car_Audi Car_BMW Car_Fiat Car_Ford ... Car_Toyota Car_VW Car_Volvo
0 0 0 0 0 ... 1 0 0
1 0 0 0 0 ... 0 0 0
9 1 0 0 0 ... 0 0 0
16 0 1 0 0 ... 0 0 0
24 0 0 0 0 ... 0 0 1
Com as colunas codificadas, dá pra combiná-las com as colunas numéricas (Volume, Weight) e treinar a regressão múltipla incluindo a marca do carro na previsão:
import pandas
from sklearn import linear_model
carros = pandas.read_csv("data.csv")
carros_codificados = pandas.get_dummies(carros[['Car']])
X = pandas.concat([carros[['Volume', 'Weight']], carros_codificados], axis=1)
y = carros['CO2']
regr = linear_model.LinearRegression()
regr.fit(X, y)
co2_previsto = regr.predict([[2300, 1300, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0]])
print(co2_previsto)
A saída é:
[122.45153299]
A lista de previsão combina Volume=2300, Weight=1300, e um vetor de 17 posições com um único 1 marcando a marca escolhida — nesse caso, dá uma previsão bem diferente da regressão sem a marca (107g), mostrando que a marca do carro carrega informação relevante pra prever o CO2.
Reduzindo o número de colunas
Não é preciso criar uma coluna pra cada categoria — dá pra manter a mesma informação usando uma coluna a menos, com o parâmetro drop_first=True.
import pandas
cores = pandas.DataFrame({'color': ['blue', 'red']})
dummies = pandas.get_dummies(cores, drop_first=True)
print(dummies)
A saída é:
color_red
0 0
1 1
Quando color_red é 0, já se sabe que a cor é “blue” — não é preciso uma coluna própria pra ela. O mesmo vale com mais categorias:
import pandas
cores = pandas.DataFrame({'color': ['blue', 'red', 'green']})
dummies = pandas.get_dummies(cores, drop_first=True)
dummies['color'] = cores['color']
print(dummies)
A saída é:
color_green color_red color
0 0 0 blue
1 0 1 red
2 1 0 green
Quando ambas as colunas (color_green e color_red) são 0, a cor só pode ser “blue” — a informação continua completa, só com uma coluna a menos.
Bagging
Modelos como a árvore de decisão do post #9 podem sofrer overfitting no conjunto de treino, decorando os dados em vez de aprender um padrão que generalize. Bootstrap Aggregation, ou bagging, é uma técnica de ensembling (combinar vários modelos) que ataca esse problema: em vez de treinar um único modelo, treina-se vários, cada um numa amostra aleatória do dataset original (tirada com reposição), e depois combina-se as previsões de todos — por voto majoritário, em classificação, ou por média, em regressão.
O modelo base, sem bagging
Usando o dataset de vinhos do scikit-learn, pra classificar o tipo de vinho:
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.tree import DecisionTreeClassifier
dados = datasets.load_wine(as_frame=True)
X = dados.data
y = dados.target
X_treino, X_teste, y_treino, y_teste = train_test_split(X, y, test_size=0.25, random_state=22)
dtree = DecisionTreeClassifier(random_state=22)
dtree.fit(X_treino, y_treino)
y_previsto = dtree.predict(X_teste)
print("Acurácia no treino:", accuracy_score(y_true=y_treino, y_pred=dtree.predict(X_treino)))
print("Acurácia no teste:", accuracy_score(y_true=y_teste, y_pred=y_previsto))
A saída é:
Acurácia no treino: 1.0
Acurácia no teste: 0.8222222222222222
Uma acurácia de 100% no treino contra 82% no teste é um sinal clássico de overfitting: a árvore memorizou o conjunto de treino quase perfeitamente, mas perde precisão em dados que nunca viu.
Combinando várias árvores com BaggingClassifier
import matplotlib.pyplot as plt
from sklearn.ensemble import BaggingClassifier
faixa_estimadores = [2, 4, 6, 8, 10, 12, 14, 16]
modelos = []
scores = []
for n_estimadores in faixa_estimadores:
clf = BaggingClassifier(n_estimators=n_estimadores, random_state=22)
clf.fit(X_treino, y_treino)
modelos.append(clf)
scores.append(accuracy_score(y_true=y_teste, y_pred=clf.predict(X_teste)))
plt.figure(figsize=(9, 6))
plt.plot(faixa_estimadores, scores)
plt.xlabel("n_estimators", fontsize=18)
plt.ylabel("score", fontsize=18)
plt.tick_params(labelsize=16)
plt.show()
BaggingClassifier já usa DecisionTreeClassifier como modelo base por padrão, então não é preciso especificar isso. Variando o número de árvores agregadas (n_estimators) de 2 a 16, a acurácia no teste sobe de 82.2% para 95.5% — um ganho de mais de 13 pontos percentuais sobre a árvore única, só por combinar vários modelos treinados em amostras diferentes. Depois de 14 estimadores, a acurácia para de melhorar.
Nota: os números exatos variam se você rodar sem fixar
random_state— por isso, na prática, vale combinar bagging com validação cruzada (post anterior) pra garantir que o ganho de desempenho é real, e não só sorte da divisão treino/teste.
Avaliação Out-of-Bag
Como o bootstrapping seleciona amostras aleatórias com reposição, sempre sobram algumas observações que nenhum dos modelos usou pra treinar — as chamadas observações out-of-bag (OOB). Elas podem servir como uma espécie de conjunto de validação “de graça”, sem precisar reservar dados à parte.
from sklearn.ensemble import BaggingClassifier
modelo_oob = BaggingClassifier(n_estimators=12, oob_score=True, random_state=22)
modelo_oob.fit(X_treino, y_treino)
print(modelo_oob.oob_score_)
Nota: a estimativa out-of-bag pode superestimar o erro em problemas de classificação binária, e costuma divergir um pouco do score medido no conjunto de teste, especialmente em datasets pequenos — trate-a como um complemento rápido, não como substituta de uma avaliação mais completa.
Com dados categóricos codificados e bagging reduzindo o overfitting de árvores de decisão, fecha-se o conjunto de técnicas práticas de pré-processamento e ensembling da série. Falta só um classificador clássico pra encerrar: o K-Nearest Neighbors, no próximo (e último) post.
Fonte adaptada: Categorical Data, Bootstrap Aggregation (Bagging)
Comentários