Artigo 36 — Machine Learning com scikit-learn
Prof. Ricardo Matos Módulo 7 · Data Science e Machine Learning · Artigo 36 de 52
Introdução
Machine Learning é a capacidade de um sistema aprender padrões a partir de dados sem ser explicitamente programado para cada caso. scikit-learn é a biblioteca de ML mais usada em Python — oferece uma API consistente, bem documentada e que cobre a grande maioria dos algoritmos clássicos. Neste artigo percorremos o pipeline completo: preparação de dados, treinamento, avaliação e seleção de modelos.
O Pipeline de Machine Learning
Dados Brutos
│
▼
Limpeza e EDA ← pandas, NumPy
│
▼
Feature Engineering ← transformações, encoding
│
▼
Divisão Train/Test ← train_test_split
│
▼
Pré-processamento ← StandardScaler, OneHotEncoder
│
▼
Treinamento ← fit()
│
▼
Avaliação ← score(), métricas
│
▼
Ajuste de Hiperparâmetros ← GridSearchCV
│
▼
Modelo Final ← predict(), deploy
Instalação
pip install scikit-learn
Tipos de Aprendizado
Supervisionado Não supervisionado
────────────────── ──────────────────
Classificação Clustering
- Logistic Regression - K-Means
- Random Forest - DBSCAN
- SVM - Hierarchical
- KNN
Redução de dimensionalidade
Regressão - PCA
- Linear Regression - t-SNE
- Ridge / Lasso
- Gradient Boosting
Preparando os Dados
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import (
StandardScaler, MinMaxScaler,
LabelEncoder, OneHotEncoder
)
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
# Dataset simulado — previsão de aprovação de alunos
np.random.seed(42)
n = 500
df = pd.DataFrame({
"horas_estudo": np.random.uniform(1, 10, n),
"faltas": np.random.randint(0, 20, n),
"nota_anterior": np.random.uniform(3, 10, n),
"participacao": np.random.choice(["alta", "media", "baixa"], n),
"turno": np.random.choice(["manha", "tarde", "noite"], n),
"nota_final": None
})
# Variável alvo — criada com uma regra realista
df["nota_final"] = (
0.4 * df["horas_estudo"] +
0.3 * df["nota_anterior"] +
-0.1 * df["faltas"] +
np.random.normal(0, 0.5, n)
).clip(0, 10)
df["aprovado"] = (df["nota_final"] >= 6.0).astype(int)
print(df.head())
print(f"\nDistribuição: {df['aprovado'].value_counts().to_dict()}")
# Separando features e alvo
X = df.drop(columns=["nota_final", "aprovado"])
y = df["aprovado"]
# Divisão treino/teste
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
stratify=y # mantém proporção das classes
)
print(f"\nTreino: {X_train.shape[0]} amostras")
print(f"Teste: {X_test.shape[0]} amostras")
Pré-processamento com Pipeline
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
# Colunas por tipo
numericas = ["horas_estudo", "faltas", "nota_anterior"]
categoricas = ["participacao", "turno"]
# Pipeline para numéricas: imputa nulos + normaliza
pipe_num = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler())
])
# Pipeline para categóricas: imputa nulos + one-hot encoding
pipe_cat = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("encoder", OneHotEncoder(drop="first", sparse_output=False))
])
# Combina os dois pipelines
preprocessador = ColumnTransformer([
("num", pipe_num, numericas),
("cat", pipe_cat, categoricas)
])
# Transformando os dados
X_train_proc = preprocessador.fit_transform(X_train)
X_test_proc = preprocessador.transform(X_test)
print(f"Shape após pré-processamento: {X_train_proc.shape}")
Classificação: Múltiplos Algoritmos
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import (
accuracy_score, precision_score, recall_score,
f1_score, roc_auc_score, classification_report,
confusion_matrix
)
def avaliar_classificador(nome, modelo, X_tr, X_te, y_tr, y_te):
modelo.fit(X_tr, y_tr)
y_pred = modelo.predict(X_te)
y_prob = (
modelo.predict_proba(X_te)[:, 1]
if hasattr(modelo, "predict_proba") else None
)
return {
"modelo": nome,
"acuracia": accuracy_score(y_te, y_pred),
"precisao": precision_score(y_te, y_pred, zero_division=0),
"recall": recall_score(y_te, y_pred, zero_division=0),
"f1": f1_score(y_te, y_pred, zero_division=0),
"roc_auc": roc_auc_score(y_te, y_prob) if y_prob is not None else None,
}
modelos = [
("Regressão Logística", LogisticRegression(max_iter=1000, random_state=42)),
("Árvore de Decisão", DecisionTreeClassifier(random_state=42)),
("Random Forest", RandomForestClassifier(n_estimators=100, random_state=42)),
("Gradient Boosting", GradientBoostingClassifier(random_state=42)),
("SVM", SVC(probability=True, random_state=42)),
("KNN", KNeighborsClassifier(n_neighbors=5)),
]
resultados = []
for nome, modelo in modelos:
resultado = avaliar_classificador(
nome, modelo,
X_train_proc, X_test_proc,
y_train, y_test
)
resultados.append(resultado)
df_resultados = pd.DataFrame(resultados).set_index("modelo")
print(df_resultados.round(4).to_string())
Métricas de Avaliação em Detalhe
from sklearn.metrics import (
confusion_matrix, ConfusionMatrixDisplay,
roc_curve, RocCurveDisplay,
precision_recall_curve
)
import matplotlib.pyplot as plt
def avaliar_detalhado(modelo, X_test, y_test, nome_modelo="Modelo"):
y_pred = modelo.predict(X_test)
y_prob = modelo.predict_proba(X_test)[:, 1]
print(f"\n{'='*50}")
print(f"Relatório: {nome_modelo}")
print('='*50)
print(classification_report(y_test, y_pred,
target_names=["Reprovado", "Aprovado"]))
# Matriz de confusão
cm = confusion_matrix(y_test, y_pred)
tn, fp, fn, tp = cm.ravel()
print(f"Verdadeiros Positivos (TP): {tp}")
print(f"Verdadeiros Negativos (TN): {tn}")
print(f"Falsos Positivos (FP): {fp} ← Reprovado previsto como Aprovado")
print(f"Falsos Negativos (FN): {fn} ← Aprovado previsto como Reprovado")
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# Matriz de confusão visual
ConfusionMatrixDisplay(cm, display_labels=["Reprovado", "Aprovado"]).plot(
ax=axes[0], colorbar=False
)
axes[0].set_title(f"Matriz de Confusão — {nome_modelo}")
# Curva ROC
fpr, tpr, _ = roc_curve(y_test, y_prob)
auc = roc_auc_score(y_test, y_prob)
axes[1].plot(fpr, tpr, label=f"AUC = {auc:.3f}", color="#2E75B6")
axes[1].plot([0, 1], [0, 1], "k--", alpha=0.4)
axes[1].set_xlabel("Taxa de Falso Positivo")
axes[1].set_ylabel("Taxa de Verdadeiro Positivo")
axes[1].set_title("Curva ROC")
axes[1].legend()
plt.tight_layout()
plt.savefig(f"avaliacao_{nome_modelo.lower().replace(' ', '_')}.png", dpi=120)
plt.show()
# Avaliando o melhor modelo
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train_proc, y_train)
avaliar_detalhado(rf, X_test_proc, y_test, "Random Forest")
Importância de Features
def plotar_importancia_features(modelo, feature_names: list, top_n: int = 10):
importancias = pd.Series(
modelo.feature_importances_,
index=feature_names
).sort_values(ascending=True).tail(top_n)
fig, ax = plt.subplots(figsize=(8, 5))
importancias.plot.barh(ax=ax, color="#2E75B6", alpha=0.8)
ax.set_title(f"Top {top_n} Features mais Importantes")
ax.set_xlabel("Importância")
plt.tight_layout()
plt.savefig("importancia_features.png", dpi=120)
plt.show()
return importancias
# Obtendo nomes das features após preprocessamento
nomes_num = numericas
nomes_cat = preprocessador.named_transformers_["cat"]["encoder"]\
.get_feature_names_out(categoricas).tolist()
todos_nomes = nomes_num + nomes_cat
plotar_importancia_features(rf, todos_nomes)
Regressão
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
# Agora prevemos a nota_final (regressão)
y_reg_train = df.loc[X_train.index, "nota_final"]
y_reg_test = df.loc[X_test.index, "nota_final"]
def avaliar_regressor(nome, modelo, X_tr, X_te, y_tr, y_te):
modelo.fit(X_tr, y_tr)
y_pred = modelo.predict(X_te)
rmse = np.sqrt(mean_squared_error(y_te, y_pred))
return {
"modelo": nome,
"R²": r2_score(y_te, y_pred),
"RMSE": rmse,
"MAE": mean_absolute_error(y_te, y_pred),
}
regressores = [
("Regressão Linear", LinearRegression()),
("Ridge", Ridge(alpha=1.0)),
("Lasso", Lasso(alpha=0.1)),
("Random Forest", RandomForestRegressor(n_estimators=100, random_state=42)),
("Gradient Boosting", GradientBoostingRegressor(random_state=42)),
]
resultados_reg = []
for nome, modelo in regressores:
r = avaliar_regressor(
nome, modelo,
X_train_proc, X_test_proc,
y_reg_train, y_reg_test
)
resultados_reg.append(r)
print(pd.DataFrame(resultados_reg).set_index("modelo").round(4))
Validação Cruzada e GridSearchCV
from sklearn.model_selection import (
cross_val_score, KFold,
GridSearchCV, RandomizedSearchCV
)
# Validação cruzada — avaliação mais robusta
kf = KFold(n_splits=5, shuffle=True, random_state=42)
modelo = RandomForestClassifier(random_state=42)
scores = cross_val_score(
modelo, X_train_proc, y_train,
cv=kf, scoring="f1"
)
print(f"F1 cross-val: {scores.mean():.4f} ± {scores.std():.4f}")
# Grid Search — busca exaustiva de hiperparâmetros
param_grid = {
"n_estimators": [50, 100, 200],
"max_depth": [None, 5, 10],
"min_samples_split": [2, 5],
"min_samples_leaf": [1, 2],
}
grid_search = GridSearchCV(
RandomForestClassifier(random_state=42),
param_grid,
cv=5,
scoring="f1",
n_jobs=-1, # usa todos os núcleos
verbose=1
)
grid_search.fit(X_train_proc, y_train)
print(f"\nMelhores parâmetros: {grid_search.best_params_}")
print(f"Melhor F1: {grid_search.best_score_:.4f}")
# Randomized Search — mais eficiente para espaços grandes
from scipy.stats import randint
param_dist = {
"n_estimators": randint(50, 300),
"max_depth": [None, 5, 10, 15, 20],
"max_features": ["sqrt", "log2", None],
}
rand_search = RandomizedSearchCV(
RandomForestClassifier(random_state=42),
param_dist,
n_iter=20,
cv=5,
scoring="f1",
random_state=42,
n_jobs=-1
)
rand_search.fit(X_train_proc, y_train)
print(f"\nRandomized — Melhor F1: {rand_search.best_score_:.4f}")
Pipeline Completo: Da Entrada ao Modelo
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
# Pipeline que une pré-processamento + modelo
pipeline_completo = Pipeline([
("preprocessador", preprocessador),
("classificador", RandomForestClassifier(
n_estimators=100,
max_depth=10,
random_state=42
))
])
# Treinamento em uma linha
pipeline_completo.fit(X_train, y_train)
# Predição diretamente nos dados brutos
y_pred = pipeline_completo.predict(X_test)
print(f"F1: {f1_score(y_test, y_pred):.4f}")
print(f"AUC: {roc_auc_score(y_test, pipeline_completo.predict_proba(X_test)[:, 1]):.4f}")
# Salvando o modelo
import joblib
joblib.dump(pipeline_completo, "modelo_aprovacao.pkl")
print("\nModelo salvo: modelo_aprovacao.pkl")
# Carregando e usando
modelo_carregado = joblib.load("modelo_aprovacao.pkl")
novo_aluno = pd.DataFrame([{
"horas_estudo": 7.5,
"faltas": 2,
"nota_anterior": 8.0,
"participacao": "alta",
"turno": "manha"
}])
prob = modelo_carregado.predict_proba(novo_aluno)[0, 1]
pred = modelo_carregado.predict(novo_aluno)[0]
print(f"\nNovo aluno:")
print(f" Probabilidade de aprovação: {prob:.1%}")
print(f" Previsão: {'Aprovado' if pred == 1 else 'Reprovado'}")
Clustering: K-Means
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
# Agrupando alunos por perfil de desempenho
features_cluster = df[["horas_estudo", "faltas", "nota_anterior", "nota_final"]]
scaler = StandardScaler()
X_cluster = scaler.fit_transform(features_cluster)
# Método do cotovelo — encontrar k ideal
inercias = []
silhouettes = []
k_range = range(2, 8)
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X_cluster)
inercias.append(kmeans.inertia_)
silhouettes.append(silhouette_score(X_cluster, kmeans.labels_))
# Melhor k pelo silhouette
melhor_k = k_range[np.argmax(silhouettes)]
print(f"Melhor k: {melhor_k} (silhouette={max(silhouettes):.3f})")
# Modelo final
kmeans_final = KMeans(n_clusters=melhor_k, random_state=42, n_init=10)
df["cluster"] = kmeans_final.fit_predict(X_cluster)
# Perfil de cada cluster
perfil = df.groupby("cluster")[
["horas_estudo", "faltas", "nota_anterior", "nota_final"]
].mean().round(2)
print("\nPerfil dos clusters:")
print(perfil)
Resumo
- scikit-learn oferece uma API consistente:
fit(),predict(),score()para todos os modelos train_test_splitcomstratifygarante proporção das classes nos conjuntos de treino e testeColumnTransformerePipelineencadeiam pré-processamento e modelagem de forma limpa e reproduzível- Avalie classificadores com F1, AUC-ROC e matriz de confusão — nunca apenas acurácia
- Avalie regressores com R², RMSE e MAE — cada métrica captura um aspecto diferente do erro
GridSearchCVencontra os melhores hiperparâmetros;RandomizedSearchCVé mais eficiente para espaços grandesjoblib.dump/loadserializa pipelines completos — pronto para deploy- K-Means agrupa dados não rotulados; o método do cotovelo e silhouette escolhem o k ideal
Referências e Leituras Complementares
- scikit-learn — documentação oficial — https://scikit-learn.org/stable/
- scikit-learn — guia do usuário — https://scikit-learn.org/stable/user_guide.html
- Escolhendo o estimador certo — https://scikit-learn.org/stable/tutorial/machine_learning_map/
- joblib — serialização — https://joblib.readthedocs.io/en/stable/
- GÉRON, Aurélien. Hands-On Machine Learning with Scikit-Learn, Keras and TensorFlow. 3. ed. O'Reilly Media, 2022. — a referência mais completa de ML prático com Python.
- MÜLLER, Andreas C.; GUIDO, Sarah. Introduction to Machine Learning with Python. O'Reilly Media, 2016. — introdução acessível focada em scikit-learn.
- RASCHKA, Sebastian; MIRJALILI, Vahid. Python Machine Learning. 3. ed. Packt, 2019. — cobertura ampla de algoritmos e técnicas de avaliação.
Prof. Ricardo Matos — Dominando o Python em 1 Ano · Artigo 36 de 52 Próximo: Artigo 37 — Redes Neurais com TensorFlow e Keras
you asked
Sim