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
-
Explique, com um exemplo, por que ajustar um
StandardScalerno dataset completo antes dotrain_test_splitcausa 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 umPipelineusado na CV). -
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 NumPyOperações elemento a elemento e broadcasting eliminam o loop, rodando em C e sendo muito mais rápidas e legíveis.
-
Você tem colunas numéricas e categóricas com valores faltantes. Descreva a estrutura de um
Pipeline/ColumnTransformerque imputa, escala as numéricas, faz one-hot nas categóricas e treina um classificador — tudo sem vazamento.✓ Resposta:Um
ColumnTransformercom dois ramos: no ramo numérico, umPipeline([SimpleImputer(strategy="median"), StandardScaler()]); no ramo categórico,SimpleImputer(strategy="most_frequent")seguido deOneHotEncoder(handle_unknown="ignore"). EsseColumnTransformervira o primeiro passo de umPipelinecujo segundo passo é o classificador. Ao usarcross_val_score/GridSearchCVsobre esse pipeline, cada fold reajusta imputação, escala e categorias apenas no treino do fold — sem vazamento. -
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 dosDataLoaders 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.