Esta não é uma introdução à linguagem — é uma revisão cirúrgica do subconjunto de Python que aparece o tempo todo em modelagem avançada: vetorização com NumPy, manipulação com Pandas, o padrão de API do scikit-learn (fit/transform/predict) e a construção de pipelines que evitam vazamento de dados. Se você domina isso, o resto do curso flui.

Consolidar as ferramentas de Python que sustentam todo projeto de ML: arrays, DataFrames, a API estimator do sklearn, pipelines, e as armadilhas de reprodutibilidade e vazamento.

O que a vetorização economiza

Em ML você processa matrizes com milhões de entradas. Loops Python puros são lentos; operações vetorizadas em NumPy rodam em C compilado.

import numpy as np
import time

x = np.random.randn(1_000_000)

# Loop puro (lento)
t0 = time.perf_counter()
soma = 0.0
for v in x:
    soma += v * v
t_loop = time.perf_counter() - t0

# Vetorizado (rápido)
t0 = time.perf_counter()
soma_vec = np.sum(x ** 2)
t_vec = time.perf_counter() - t0

print(f"Loop:       {t_loop*1000:.1f} ms")
print(f"Vetorizado: {t_vec*1000:.1f} ms")
print(f"Speedup:    {t_loop/t_vec:.0f}x")
# Tipicamente 50x a 200x mais rápido

Regra de ouro: se você está escrevendo um for sobre linhas de um array em ML, quase sempre há uma forma vetorizada melhor.

NumPy — o essencial para modelagem

import numpy as np

# Um dataset: 5 amostras, 3 features
X = np.array([
    [1.0, 200.0, 0.5],
    [2.0, 150.0, 0.8],
    [3.0, 300.0, 0.2],
    [1.5, 250.0, 0.6],
    [2.5, 180.0, 0.4],
])
y = np.array([0, 1, 0, 1, 1])

# Padronização (z-score) por coluna — broadcasting
media = X.mean(axis=0)
desvio = X.std(axis=0)
X_norm = (X - media) / desvio

# Selecionar amostras de uma classe (indexação booleana)
X_classe1 = X[y == 1]

# Produto matricial: base de toda camada linear
W = np.random.randn(3, 2)   # 3 features -> 2 saídas
logits = X_norm @ W         # (5,3) @ (3,2) = (5,2)

print("Shape dos logits:", logits.shape)

Conceitos que você usará sem parar: axis, broadcasting, @ (produto matricial), indexação booleana, reshape(-1, n). (O NumPy do curso-base cobre isso em profundidade; aqui reforçamos o que reaparece em avaliação e deep learning.)

Pandas — o cavalo de batalha da engenharia de dados

import pandas as pd

df = pd.DataFrame({
    "idade":      [25, 42, 31, 55, 28, 40],
    "renda":      [3200, 8500, 4100, 12000, 3800, 7200],
    "regiao":     ["sul", "sul", "norte", "leste", "norte", "leste"],
    "inadimplente": [0, 0, 1, 0, 1, 0],
})

# Seleção e filtro
adultos_jovens = df[df["idade"] < 35]

# Agrupamento e agregação (fundamental em feature engineering)
por_regiao = df.groupby("regiao").agg(
    renda_media=("renda", "mean"),
    taxa_inad=("inadimplente", "mean"),
    n=("idade", "size"),
)
print(por_regiao)

# Criar features derivadas
df["renda_log"] = np.log1p(df["renda"])
df["renda_por_idade"] = df["renda"] / df["idade"]

# One-hot encoding rápido (cuidado com vazamento — ver Módulo 3)
df_encoded = pd.get_dummies(df, columns=["regiao"], drop_first=True)
print(df_encoded.columns.tolist())

A API do scikit-learn — o padrão que unifica tudo

Todo transformador e modelo do sklearn segue o mesmo contrato. Entender esse padrão é entender 80% da biblioteca.

from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

# Transformadores: fit -> transform
scaler = StandardScaler()
scaler.fit(X)                 # aprende média e desvio DO TREINO
X_scaled = scaler.transform(X)  # aplica

# Estimadores: fit -> predict / predict_proba
modelo = LogisticRegression()
modelo.fit(X_scaled, y)
pred = modelo.predict(X_scaled)
proba = modelo.predict_proba(X_scaled)[:, 1]

A regra crítica: fit só pode ver dados de treino. Se você ajustar o StandardScaler no dataset inteiro antes do split, a média/desvio do teste "vazam" para o treino. Isso nos leva ao conceito mais importante desta aula.

Pipelines — a defesa nº 1 contra vazamento de dados

Um Pipeline encapsula pré-processamento + modelo como um único estimador. Dentro de validação cruzada, cada fold re-ajusta o pré-processamento apenas no treino daquele fold, eliminando vazamento automaticamente.

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

num_features = ["idade", "renda"]
cat_features = ["regiao"]

# Pré-processamento por tipo de coluna
pre = ColumnTransformer([
    ("num", Pipeline([
        ("imputer", SimpleImputer(strategy="median")),
        ("scaler", StandardScaler()),
    ]), num_features),
    ("cat", OneHotEncoder(handle_unknown="ignore"), cat_features),
])

# Pipeline completo: pré + modelo
clf = Pipeline([
    ("pre", pre),
    ("model", RandomForestClassifier(n_estimators=200, random_state=42)),
])

X_df = df[num_features + cat_features]
y_df = df["inadimplente"]

# Validação cruzada correta: o pipeline inteiro é reajustado por fold
scores = cross_val_score(clf, X_df, y_df, cv=3, scoring="roc_auc")
print(f"AUC médio: {scores.mean():.3f} ± {scores.std():.3f}")

Guarde esta frase: todo pré-processamento que aprende parâmetros dos dados (média, mínimo, categorias, alvo) deve viver dentro do pipeline. Fora dele, é fonte de vazamento.

Reprodutibilidade

import numpy as np, random, os

SEED = 42
random.seed(SEED)
np.random.seed(SEED)
os.environ["PYTHONHASHSEED"] = str(SEED)
# Em torch: torch.manual_seed(SEED)

Sempre fixe a semente e reporte a versão das bibliotecas. Resultados de ML que não são reprodutíveis não são resultados — são anedotas.

Type hints e código de modelagem legível

from dataclasses import dataclass
import numpy as np

@dataclass
class ResultadoAvaliacao:
    auc: float
    f1: float
    n_amostras: int

def avaliar(y_true: np.ndarray, y_score: np.ndarray) -> ResultadoAvaliacao:
    from sklearn.metrics import roc_auc_score, f1_score
    y_pred = (y_score >= 0.5).astype(int)
    return ResultadoAvaliacao(
        auc=roc_auc_score(y_true, y_score),
        f1=f1_score(y_true, y_pred),
        n_amostras=len(y_true),
    )

dataclass e type hints tornam pipelines de experimentação legíveis e menos propensos a bugs silenciosos.

Vetorização com NumPy é ordens de magnitude mais rápida que laços, e a regra prática é preferi-la sempre. O Pandas é a ferramenta central de manipulação e de engenharia de features. Toda a API do scikit-learn obedece ao mesmo trio — fit, transform, predict — com uma disciplina que não se negocia: o fit só enxerga o treino. Pipeline e ColumnTransformer embalam o pré-processamento e são a principal defesa contra vazamento dentro da validação cruzada. Por fim, fixe sementes e versões, ou o resultado de hoje não se reproduz amanhã.

Exercícios

  1. Explique, com um exemplo, por que ajustar um StandardScaler no dataset completo antes do train_test_split causa vazamento de dados.

    ✓ Resposta:

    Ao chamar scaler.fit(X) no dataset completo, a média e o desvio-padrão calculados incorporam informação das amostras que depois virarão o conjunto de teste. O modelo, portanto, é avaliado em dados cuja estatística de normalização ele "já viu". Em produção, você não teria acesso à distribuição futura para calcular essas estatísticas. O resultado é uma estimativa de desempenho otimista. Correto: ajustar o scaler só no treino (ou colocá-lo dentro de um Pipeline usado na CV).

  2. Reescreva o loop abaixo de forma vetorizada em NumPy: > ``python > resultado = [] > for i in range(len(a)): > resultado.append(a[i] * 2 + b[i]) > ``

    ✓ Resposta:
    resultado = a * 2 + b   # a e b são arrays NumPy

    Operações elemento a elemento e broadcasting eliminam o loop, rodando em C e sendo muito mais rápidas e legíveis.

  3. Você tem colunas numéricas e categóricas com valores faltantes. Descreva a estrutura de um Pipeline/ColumnTransformer que imputa, escala as numéricas, faz one-hot nas categóricas e treina um classificador — tudo sem vazamento.

    ✓ Resposta:

    Um ColumnTransformer com dois ramos: no ramo numérico, um Pipeline([SimpleImputer(strategy="median"), StandardScaler()]); no ramo categórico, SimpleImputer(strategy="most_frequent") seguido de OneHotEncoder(handle_unknown="ignore"). Esse ColumnTransformer vira o primeiro passo de um Pipeline cujo segundo passo é o classificador. Ao usar cross_val_score/GridSearchCV sobre esse pipeline, cada fold reajusta imputação, escala e categorias apenas no treino do fold — sem vazamento.

  4. Por que fixar random_state/seed é insuficiente para reprodutibilidade total em projetos de deep learning?

    ✓ Resposta:

    Além da seed do Python/NumPy, há fontes de não-determinismo em GPUs (operações paralelas em ordem não-determinística, algoritmos de cuDNN), na semente do framework (torch.manual_seed), no embaralhamento dos DataLoaders e no paralelismo de CPU. Reprodutibilidade total exige fixar todas as sementes, configurar flags determinísticas (torch.use_deterministic_algorithms(True)), controlar o número de workers e, ainda assim, aceitar pequenas variações entre hardwares diferentes.

Fontes e leituras recomendadas