Machine Learning: Regressão Linear
Última atualização: 2026-08-26
A regressão linear é o "Hello World" do ML — simples, interpretável e surpreendentemente eficaz. É o ponto de partida para toda tarefa de regressão.
1. O que você vai aprender
- A matemática por trás da regressão linear: função hipótese, função de perda (MSE), descida do gradiente
- Implementação com Scikit-learn: LinearRegression vs SGDRegressor, regularização (Ridge/Lasso/ElasticNet)
- Regressão múltipla e interpretação de features: o significado econômico dos coeficientes
- Teste de hipóteses e diagnóstico: análise de resíduos, gráficos Q-Q, heterocedasticidade, multicolinearidade (VIF)
- A previsão de vendas do Bob: prever a receita do próximo mês usando dados históricos de gasto com anúncios + tráfego
2. A história real de um e-commerce
(1) O problema: não saber como dividir o orçamento de anúncios
O Bob tem um orçamento mensal de 200 mil USD para anúncios, que deve ser distribuído entre Google Ads, Facebook e e-mail marketing. Sua intuição diz "gaste mais, venda mais", mas no mês passado, 50 mil USD extras no Google Ads geraram apenas 30 mil USD de receita adicional — há retornos decrescentes, mas ele não faz ideia de onde está o ponto de virada.
(2) A solução com regressão linear
A regressão linear pode quantificar a contribuição de cada canal para a receita e encontrar a alocação ótima de orçamento.
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X_train, y_train)
# Cada coeficiente = contribuição marginal por 1k USD gasto em anúncios
for channel, coef in zip(channels, model.coef_):
print(f"{channel}: +{coef:.2f}k USD de receita por 1k USD gasto")
(3) O resultado: melhoria de 35% no ROI
O Bob descobre que o Google Ads tem coeficiente de 0,8 (cada 1k USD gasto retorna 0,8k USD), enquanto o e-mail tem coeficiente de 2,5 (cada 1k USD gasto retorna 2,5k USD). Depois de realocar o orçamento, o ROI geral melhora 35%.
3. A matemática por trás da regressão linear
(1) Função hipótese e função de perda
A regressão linear assume que a saída é uma combinação linear das entradas: $\hat{y} = w_1x_1 + w_2x_2 + ... + b$
A função de perda usa MSE (Erro Quadrático Médio): $L = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2$
▶ Exemplo: Regressão linear do zero
import numpy as np
# Descida do gradiente para regressão linear
def linear_regression_gd(X, y, lr=0.01, epochs=1000):
n_samples, n_features = X.shape
w = np.zeros(n_features)
b = 0.0
for epoch in range(epochs):
# Forward pass
y_pred = X @ w + b
# Calcular gradientes
dw = (2 / n_samples) * (X.T @ (y_pred - y))
db = (2 / n_samples) * np.sum(y_pred - y)
# Atualizar parâmetros
w -= lr * dw
b -= lr * db
if epoch % 200 == 0:
loss = np.mean((y - y_pred) ** 2)
print(f"Época {epoch}: MSE = {loss:.4f}")
return w, b
# Testar com dados simples
rng = np.random.default_rng(42)
X = rng.uniform(0, 10, (100, 1))
y = 3 * X.squeeze() + 7 + rng.normal(0, 2, 100)
w, b = linear_regression_gd(X, y, lr=0.01, epochs=1000)
print(f"\nAprendido: w={w[0]:.2f}, b={b:.2f}")
print(f"Verdadeiro: w=3.00, b=7.00")
Saída:
# Função definida com sucesso
(2) Visualização da descida do gradiente
sequenceDiagram
participant Init as Inicialização
participant Fwd as Forward Pass
participant Loss as Calcular Perda
participant Grad as Calcular Gradiente
participant Update as Atualizar Parâmetros
Init->>Fwd: w=0, b=0
loop Cada Época
Fwd->>Loss: y_pred = Xw + b
Loss->>Grad: MSE = mean((y - y_pred)²)
Grad->>Update: dw, db = gradientes
Update->>Fwd: w -= lr*dw, b -= lr*db
end
4. Implementação com Scikit-learn
(1) LinearRegression vs SGDRegressor
▶ Exemplo: Comparando dois solvers
from sklearn.linear_model import LinearRegression, SGDRegressor
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
rng = np.random.default_rng(42)
n = 500
X = rng.uniform(0, 100, (n, 3))
y = 50 + 0.8 * X[:, 0] + 1.2 * X[:, 1] - 0.5 * X[:, 2] + rng.normal(0, 5, n)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Método 1: Equação normal (forma fechada, exata)
lr_normal = LinearRegression()
lr_normal.fit(X_train, y_train)
# Método 2: SGD (iterativo, para grandes conjuntos de dados)
lr_sgd = Pipeline([
("scaler", StandardScaler()),
("sgd", SGDRegressor(max_iter=1000, learning_rate="constant", eta0=0.01, random_state=42)),
])
lr_sgd.fit(X_train, y_train)
print(f"R² da Equação Normal: {lr_normal.score(X_test, y_test):.4f}")
print(f"R² do SGD: {lr_sgd.score(X_test, y_test):.4f}")
print(f"\nCoeficientes normais: {lr_normal.coef_.round(2)}")
print(f"Coeficientes verdadeiros: [0.80, 1.20, -0.50]")
Saída:
# Executado com sucesso
| Dimensão | LinearRegression (Equação Normal) | SGDRegressor |
|---|---|---|
| Solver | Solução analítica $(X^TX)^{-1}X^Ty$ | Descida do gradiente iterativa |
| Complexidade | O(n³) no número de features | O(n) por passo |
| Tamanho de dados adequado | Pequeno a médio (n < 100k) | Grande (n > 100k) |
| Precisão | Solução exata | Solução aproximada |
| Requer normalização | Não | Sim |
(2) Regularização: Ridge/Lasso/ElasticNet
▶ Exemplo: Comparação de regularização
from sklearn.linear_model import Ridge, Lasso, ElasticNet
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np
rng = np.random.default_rng(42)
n, p = 100, 20 # p > features relevantes (apenas 5 importam)
X = rng.standard_normal((n, p))
true_coefs = np.zeros(p)
true_coefs[:5] = [3, -2, 1.5, 0.8, -0.5]
y = X @ true_coefs + rng.normal(0, 1, n)
models = {
"LinearRegression": LinearRegression(),
"Ridge (alpha=1)": Ridge(alpha=1),
"Lasso (alpha=0.1)": Lasso(alpha=0.1),
"ElasticNet (alpha=0.1)": ElasticNet(alpha=0.1, l1_ratio=0.5),
}
for name, model in models.items():
pipe = Pipeline([("scaler", StandardScaler()), ("model", model)])
scores = cross_val_score(pipe, X, y, cv=5, scoring="r2")
pipe.fit(X, y)
nonzero = np.sum(np.abs(pipe.named_steps["model"].coef_) > 0.01)
print(f"{name:25s}: R²={scores.mean():.3f}, Coefs não-zero={nonzero}/{p}")
Saída:
# Executado com sucesso
| Regularização | Termo de penalidade | Efeito | Caso de uso |
|---|---|---|---|
| Ridge (L2) | $\alpha \sum w_i^2$ | Encolhe os coeficientes em direção a zero | Muitas features relevantes |
| Lasso (L1) | $\alpha \sum |w_i|$ | Esparsa, leva alguns coeficientes a zero | Seleção de features |
| ElasticNet | L1 + L2 híbrido | Esparsa + estável | Grupos de features correlacionadas |
5. Regressão múltipla e interpretação de features
▶ Exemplo: Análise de ROI de anúncios do Bob
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import pandas as pd
import numpy as np
rng = np.random.default_rng(42)
n = 200
df = pd.DataFrame({
"google_ads_k": rng.uniform(20, 80, n),
"facebook_ads_k": rng.uniform(10, 50, n),
"email_marketing_k": rng.uniform(5, 30, n),
"traffic_k": rng.uniform(50, 300, n),
})
df["revenue_k"] = (
100
+ 0.8 * df["google_ads_k"]
+ 1.2 * df["facebook_ads_k"]
+ 2.5 * df["email_marketing_k"]
+ 0.15 * df["traffic_k"]
+ rng.normal(0, 10, n)
)
X = df.drop(columns=["revenue_k"])
y = df["revenue_k"]
# Treinar com features padronizadas para comparação justa
pipe = Pipeline([("scaler", StandardScaler()), ("model", LinearRegression())])
pipe.fit(X, y)
# Interpretar coeficientes padronizados (ranking de importância)
coefs = pipe.named_steps["model"].coef_
importance = pd.DataFrame({
"feature": X.columns,
"std_coef": coefs,
"abs_importance": np.abs(coefs),
}).sort_values("abs_importance", ascending=False)
print("Importância das Features (coeficientes padronizados):")
print(importance.to_string(index=False))
Saída:
Importância das Features (coeficientes padronizados):
Conclusão-chave: A magnitude dos coeficientes padronizados reflete diretamente a importância das features. O coeficiente de 2,5 do e-mail é muito maior que os 0,8 do Google, o que significa que cada 1k USD investido em e-mail retorna 3x mais do que o mesmo investimento no Google Ads.
6. Diagnóstico de regressão
(1) Análise de resíduos
▶ Exemplo: Diagnóstico de regressão em 4 painéis
import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LinearRegression
from scipy import stats
rng = np.random.default_rng(42)
X = rng.uniform(0, 100, 200).reshape(-1, 1)
y = 50 + 0.8 * X.squeeze() + rng.normal(0, 5, 200)
model = LinearRegression().fit(X, y)
y_pred = model.predict(X)
residuals = y - y_pred
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# (1) Resíduos vs Ajustados (verificar homocedasticidade)
axes[0, 0].scatter(y_pred, residuals, alpha=0.5, s=15)
axes[0, 0].axhline(0, color="red", linestyle="--")
axes[0, 0].set_xlabel("Valores Ajustados")
axes[0, 0].set_ylabel("Resíduos")
axes[0, 0].set_title("Resíduos vs Ajustados")
# (2) Gráfico Q-Q (verificar normalidade)
stats.probplot(residuals, plot=axes[0, 1])
axes[0, 1].set_title("Gráfico Q-Q")
# (3) Scale-Location (verificar variância)
axes[1, 0].scatter(y_pred, np.sqrt(np.abs(residuals / np.std(residuals))), alpha=0.5, s=15)
axes[1, 0].set_xlabel("Valores Ajustados")
axes[1, 0].set_ylabel("Scale-Location")
axes[1, 0].set_title("Scale-Location")
# (4) Real vs Previsto
axes[1, 1].scatter(y, y_pred, alpha=0.5, s=15)
axes[1, 1].plot([y.min(), y.max()], [y.min(), y.max()], "r--")
axes[1, 1].set_xlabel("Real")
axes[1, 1].set_ylabel("Previsto")
axes[1, 1].set_title("Real vs Previsto")
plt.tight_layout()
plt.savefig("regression_diagnostics.png", dpi=150)
Saída:
# Executado com sucesso
(2) Multicolinearidade e VIF
▶ Exemplo: Detecção de VIF
from statsmodels.stats.outliers_influence import variance_inflation_factor
import pandas as pd
import numpy as np
# VIF > 10 indica multicolinearidade severa
rng = np.random.default_rng(42)
n = 100
df = pd.DataFrame({
"ad_spend": rng.uniform(10, 100, n),
"traffic": rng.normal(0, 1, n) * 50 + 500,
"clicks": rng.normal(0, 1, n) * 100 + 1000, # Altamente correlacionado com ad_spend
"revenue": rng.uniform(50, 500, n),
})
features = ["ad_spend", "traffic", "clicks"]
for i, col in enumerate(features):
vif = variance_inflation_factor(df[features].values, i)
print(f"{col:12s}: VIF = {vif:.2f} {'⚠️ ALTO' if vif > 10 else '✅ OK'}")
Saída:
# Executado com sucesso
| Diagnóstico | Método de verificação | Critério normal | Correção para violações |
|---|---|---|---|
| Linearidade | Gráfico de resíduos vs ajustados | Dispersão aleatória | Adicionar termos polinomiais |
| Normalidade | Gráfico Q-Q | Aproximadamente em linha reta | Transformação log |
| Homocedasticidade | Gráfico Scale-Location | Faixa horizontal | Regressão ponderada |
| Multicolinearidade | VIF | VIF < 10 | Remover ou mesclar features |
❓ Perguntas Frequentes
P: A LinearRegression exige normalização das features? R: Pode treinar sem normalização, mas os coeficientes não serão comparáveis (unidades diferentes). Após a padronização, as magnitudes dos coeficientes refletem diretamente a importância das features. Ridge/Lasso/SGD exigem normalização.
P: Devo escolher Ridge ou Lasso? R: Muitas features que podem contribuir → Ridge. Necessidade de seleção automática de features → Lasso. Grupos de features altamente correlacionadas → ElasticNet. Na prática, teste os três e use validação cruzada para escolher o melhor.
P: Como escolho o parâmetro alpha? R: Use GridSearchCV ou RidgeCV/LassoCV para seleção automática. Um alpha maior significa regularização mais forte, empurrando os coeficientes para mais perto de zero. Normalmente, pesquise em escala logarítmica: [0.001, 0.01, 0.1, 1, 10, 100].
P: Como interpreto um coeficiente de regressão negativo? R: Um coeficiente negativo significa que o alvo diminui conforme a feature aumenta. Por exemplo, se "taxa de devolução" tem coeficiente -5, significa que a receita cai 5k USD para cada 1% de aumento na taxa de devolução.
P: E se o VIF > 10? R: Remova a feature com o VIF mais alto e recalcule. Alternativamente, use PCA para eliminar a colinearidade. Ou use regressão Ridge (a regularização L2 é naturalmente resistente à multicolinearidade).
P: E se os resíduos não forem normalmente distribuídos? R: Tente uma transformação log em y (log(y)). Se os resíduos mostrarem formato de funil (heterocedasticidade), use mínimos quadrados ponderados ou aplique uma transformação Box-Cox em y.
📖 Resumo
- A regressão linear assume que y é uma combinação linear de x, resolvida por perda MSE + descida do gradiente ou equação normal
- API unificada do sklearn: LinearRegression (solução exata) vs SGDRegressor (dados em grande escala)
- O trio da regularização: Ridge (L2 encolhe coeficientes) / Lasso (L1 torna esparsa) / ElasticNet (L1+L2 híbrido)
- As magnitudes dos coeficientes padronizados refletem a importância das features e orientam diretamente decisões de negócio
- As quatro verificações de diagnóstico: gráfico de resíduos (linearidade) / gráfico Q-Q (normalidade) / Scale-Location (variância) / VIF (colinearidade)
- Interpretar coeficientes em termos de negócio é o maior trunfo da regressão linear — a explicabilidade supera modelos caixa-preta
📝 Exercícios
- Básico (Dificuldade ⭐): Use a LinearRegression do sklearn para prever o conjunto de dados California Housing. Produza R² e todos os coeficientes das features. Dica:
fetch_california_housing()+model.coef_. - Intermediário (Dificuldade ⭐⭐): Compare Ridge (alpha=0.1/1/10/100) nos mesmos dados usando R² com validação cruzada e magnitudes dos coeficientes. Observe como a força da regularização afeta os coeficientes. Dica: use Pipeline(StandardScaler+Ridge) + GridSearchCV.
- Desafio (Dificuldade ⭐⭐⭐): Implemente um pipeline completo de diagnóstico de regressão — após o treino, plote os 4 painéis de diagnóstico, calcule o VIF, determine se as premissas da regressão linear se sustentam e proponha melhorias caso não se sustentem. Dica: consulte o código de diagnóstico na Seção 6.
← Anterior: Exercício Integrador — Projeto para Iniciantes | Próximo: Regressão Logística →