Python

Artigo 36 — Machine Learning com scikit-learn Já leu

11 min de leitura

Artigo 36 — Machine Learning com scikit-learn
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

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_split com stratify garante proporção das classes nos conjuntos de treino e teste
  • ColumnTransformer e Pipeline encadeiam 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
  • GridSearchCV encontra os melhores hiperparâmetros; RandomizedSearchCV é mais eficiente para espaços grandes
  • joblib.dump/load serializa 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


claude response

Dominando o Python em 1 Ano

Comentários

Mais em Python

Artigo 38 — Processamento de Linguagem Natural (NLP)
Artigo 38 — Processamento de Linguagem Natural (NLP)

Processamento de Linguagem Natural é o campo da IA que lida com texto e fala...

Decoradores e Metaprogramação
Decoradores e Metaprogramação

Decoradores são um dos recursos mais elegantes do Python. Eles permite...

Interfaces, Protocolos e Composição
Interfaces, Protocolos e Composição

No artigo anterior vimos que herança é uma ferramenta poderosa...