Capítulo 40, Ecossistema
Do NumPy ao aprendizado de máquina
O aprendizado de máquina clássico é álgebra linear sobre matrizes de números. Aqui você vê a mesma regressão feita à mão com o NumPy e pelo scikit-learn, e confere que dão a mesma resposta.
Código deste capítulo: ecossistema/cap40_numpy_sklearn.py
A convenção que une tudo
O scikit-learn espera os dados como uma matriz X de forma (n_amostras, n_características): uma linha por exemplo, uma coluna por atributo. E os alvos em y, um vetor com um valor por exemplo. É a convenção do capítulo 41 (linhas são estudantes, colunas são atributos), e vale para praticamente todo o ecossistema.
uv sync --group ecossistema
A mesma regressão, de dois jeitos
Primeiro, dados com resposta conhecida: três características, coeficientes verdadeiros [2, -1, 0,5], um intercepto de 3 e um pouco de ruído. A solução com NumPy usa mínimos quadrados (capítulo 23), com uma coluna de uns para o intercepto. A do scikit-learn usa o LinearRegression:
import numpy as np
from sklearn.linear_model import LinearRegression
rng = np.random.default_rng(0)
X = rng.normal(size=(200, 3))
verdadeiro = np.array([2.0, -1.0, 0.5])
y = X @ verdadeiro + 3.0 + rng.normal(0, 0.1, 200)
Xb = np.hstack([X, np.ones((200, 1))])
theta, *_ = np.linalg.lstsq(Xb, y, rcond=None)
modelo = LinearRegression().fit(X, y)
print(np.allclose(theta[:3], modelo.coef_), np.isclose(theta[3], modelo.intercept_))
print(np.allclose(theta[:3], verdadeiro, atol=0.05))
True True
True
Os dois métodos dão a mesma resposta, e ambos recuperam os coeficientes verdadeiros. Não há mágica no LinearRegression: é essa conta, empacotada com uma interface padronizada (fit, predict, score).
Medir o erro à mão
O coeficiente de determinação (R²) compara o erro do modelo com o erro de simplesmente prever a média. Escrevê-lo em NumPy leva uma linha, e o resultado confere com o score do scikit-learn:
def r2(real, previsto):
return 1 - ((real - previsto) ** 2).sum() / ((real - real.mean()) ** 2).sum()
print(round(r2(y, modelo.predict(X)), 3) == round(modelo.score(X, y), 3))
True
Avaliar em dados que o modelo não viu
Medir o erro nos mesmos dados do ajuste é otimista demais. A prática correta é separar uma parte para teste (com a ideia do capítulo 19: embaralhar e cortar):
ordem = rng.permutation(len(y))
treino, teste = np.split(ordem, [150])
Xb_treino = np.hstack([X[treino], np.ones((150, 1))])
theta_t, *_ = np.linalg.lstsq(Xb_treino, y[treino], rcond=None)
previsto = np.hstack([X[teste], np.ones((50, 1))]) @ theta_t
print(r2(y[teste], previsto) > 0.99)
True
Descida do gradiente: aprender sem a fórmula fechada
Para modelos sem solução fechada (como as redes neurais), o caminho é iterar: calcular o gradiente do erro e dar um passo pequeno na direção contrária. Para a regressão, o gradiente do erro quadrático médio é 2/n · Xᵀ(Xw − y), e a iteração converge para a mesma solução dos mínimos quadrados:
w = np.zeros(4)
for _ in range(500):
w -= 0.1 * (2 / len(y)) * Xb.T @ (Xb @ w - y)
print(np.allclose(w, theta, atol=1e-3))
True
O 0.1 é a taxa de aprendizado: grande demais e a iteração diverge, pequena demais e ela demora. O capítulo 43 constrói uma rede neural inteira com essa mesma ideia.
Padronização: o que o `StandardScaler` faz
Muitos modelos funcionam melhor com características na mesma escala. O StandardScaler subtrai a média e divide pelo desvio de cada coluna, exatamente o z-score do capítulo 13:
from sklearn.preprocessing import StandardScaler
print(np.allclose(StandardScaler().fit_transform(X), (X - X.mean(axis=0)) / X.std(axis=0)))
True
Vazamento de dados
Calcule a média e o desvio só nos dados de treino e aplique os mesmos números ao teste. Padronizar tudo junto antes de separar deixa informação do teste vazar para o treino, e a avaliação fica otimista. O
fitaprende com o treino, e otransformsó aplica.
O caminho até o aprendizado profundo
O PyTorch e o JAX têm tensores com a mesma forma, a mesma indexação e as mesmas regras de broadcasting do NumPy. A conversão entre eles é barata, porque, na CPU, podem compartilhar a memória. O que eles acrescentam é GPU e derivação automática. Quem domina o capítulo 17 lê um tutorial deles sem estranhar o vocabulário.
Exercício 1
Um classificador do centroide mais próximo
Escreva classificador_centroide(X_treino, y_treino, X_teste): calcule o centro (a média) de cada classe no treino e classifique cada ponto de teste pela classe do centro mais próximo. Confira que concorda com o NearestCentroid do scikit-learn.