Regressão Polinomial
Aprenda como funciona a regressão polinomial, quando usá-la e como construir, avaliar e ajustar um modelo polinomial em Python com scikit-learn.
A regressão linear traça uma linha reta pelos dados. Quando a relação entre a entrada e a saída é curva — pense na trajetória de uma bola, no crescimento de uma colônia bacteriana ou em uma curva dose-resposta — uma linha reta é um ajuste ruim, independentemente de quantas variáveis você adicione. A regressão polinomial resolve isso adicionando versões elevadas das variáveis existentes (x², x³, …) para que o modelo possa se curvar para corresponder à curvatura dos dados, enquanto ainda usa o mecanismo de mínimos quadrados ordinários por baixo dos panos.
Esta página aborda:
- Como a regressão polinomial estende a regressão linear matematicamente
- Quando usar a regressão polinomial e qual grau escolher
- Um pipeline completo com scikit-learn: transformação de variáveis, treinamento, avaliação
- A armadilha do overfitting e como detectá-la com curvas de treino/teste
- Comparando a qualidade do modelo com RMSE e R²
Como Funciona a Regressão Polinomial
A Equação
A regressão linear ajusta:
y = β₀ + β₁xA regressão polinomial de grau n ajusta:
y = β₀ + β₁x + β₂x² + β₃x³ + … + βₙxⁿO ponto-chave é que x², x³ e assim por diante são tratados como variáveis adicionais. O modelo ainda é linear em seus coeficientes (valores β); apenas as variáveis são não-lineares. Isso significa que os mínimos quadrados ordinários ainda funcionam — você simplesmente pré-processa a matriz de entrada primeiro.
Graus e o Que Significam
| Grau | Nome | Forma |
|---|---|---|
| 1 | Linear | Linha reta |
| 2 | Quadrático | Curva simples (parábola) |
| 3 | Cúbico | Um ponto de inflexão |
| 4+ | Ordem superior | Curvas mais complexas |
Escolher o grau certo é a habilidade central. Muito baixo e o modelo faz underfitting (perde curvatura real). Muito alto e o modelo faz overfitting (memoriza o ruído e falha em dados novos).
Quando Usar a Regressão Polinomial
Use a regressão polinomial quando:
- Um gráfico de dispersão mostra uma curva clara nos dados que uma linha reta não consegue capturar
- Você quer uma alternativa rápida e interpretável a modelos baseados em árvore para curvatura moderada
- Você já tem uma linha de base de regressão linear funcionando e ela faz underfitting
Prefira outras abordagens quando:
- A relação é altamente complexa ou possui muitas variáveis → tente árvores de decisão ou gradient boosting
- Você precisa prever bem fora do intervalo de treinamento (extrapolação) → polinômios de alto grau divergem de forma selvagem fora do intervalo dos dados
- Você tem muitas variáveis de entrada → cada variável ganha
ntermos polinomiais, então a matriz de variáveis cresce rapidamente
Construindo um Modelo de Regressão Polinomial
Passo 1: Importar Bibliotecas
import numpy as np
import matplotlib
matplotlib.use('Agg') # non-interactive backend for scripts
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_scoreO transformador PolynomialFeatures do scikit-learn lida automaticamente com a expansão x → [1, x, x², …, xⁿ]. Envolvê-lo com LinearRegression em um Pipeline mantém o código limpo e evita vazamento de dados durante a validação cruzada.
Passo 2: Criar Dados de Exemplo
rng = np.random.default_rng(42)
X = rng.uniform(-3, 3, 80).reshape(-1, 1) # 80 points from -3 to 3
y = 0.5 * X.ravel()**2 - X.ravel() + 2 + rng.normal(0, 0.5, 80)
# True relationship: y ≈ 0.5x² - x + 2 (a parabola with noise)A relação subjacente é quadrática, portanto um polinômio de grau 2 deve recuperá-la bem. Esta é o tipo de situação em que a regressão linear faz underfitting sistematicamente.
Passo 3: Dividir em Conjuntos de Treino e Teste
Sempre divida antes de ajustar para que você tenha dados reservados para avaliar. Veja Train/Test Split para uma explicação completa.
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
print(f"Training samples: {len(X_train)}") # 64
print(f"Test samples: {len(X_test)}") # 16Passo 4: Construir e Ajustar o Pipeline
degree = 2
model = make_pipeline(
PolynomialFeatures(degree=degree, include_bias=False),
LinearRegression()
)
model.fit(X_train, y_train)make_pipeline encadeia as duas etapas: PolynomialFeatures transforma a entrada, depois LinearRegression ajusta nas variáveis expandidas. Você chama .fit(), .predict() e .score() no pipeline exatamente como faria em um estimador simples.
Passo 5: Avaliar o Modelo
y_pred_train = model.predict(X_train)
y_pred_test = model.predict(X_test)
rmse_train = np.sqrt(mean_squared_error(y_train, y_pred_train))
rmse_test = np.sqrt(mean_squared_error(y_test, y_pred_test))
r2_train = r2_score(y_train, y_pred_train)
r2_test = r2_score(y_test, y_pred_test)
print(f"Train RMSE: {rmse_train:.4f} Test RMSE: {rmse_test:.4f}")
print(f"Train R²: {r2_train:.4f} Test R²: {r2_test:.4f}")Saída esperada:
Train RMSE: 0.4605 Test RMSE: 0.4538
Train R²: 0.9480 Test R²: 0.9514Um R² próximo de 0,95 em dados reservados significa que o modelo explica cerca de 95% da variância — um excelente ajuste para dados com este nível de ruído. As pontuações de treino e teste são próximas, o que indica que o modelo generaliza em vez de fazer overfitting.
Interpretando as métricas:
- RMSE (Raiz do Erro Quadrático Médio) — o erro médio nas mesmas unidades de
y. Menor é melhor. - R² (coeficiente de determinação) — fração da variância explicada. Valores mais próximos de 1,0 são melhores; 0 significa que o modelo não é melhor do que prever a média.
Passo 6: Visualizar o Ajuste
X_line = np.linspace(-3.5, 3.5, 200).reshape(-1, 1)
y_line = model.predict(X_line)
plt.figure(figsize=(7, 5))
plt.scatter(X_train, y_train, alpha=0.6, label='Train', color='steelblue', s=25)
plt.scatter(X_test, y_test, alpha=0.8, label='Test', color='orange', s=40, zorder=5)
plt.plot(X_line, y_line, color='red', linewidth=2, label=f'Degree-{degree} fit')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Polynomial Regression (degree 2)')
plt.legend()
plt.tight_layout()
plt.savefig('poly_reg_fit.png', dpi=120)
print("Plot saved.")A curva vermelha deve passar suavemente pelos pontos dispersos — próxima o suficiente para capturar a forma parabólica sem serpentear pelos pontos de ruído individuais.
A Armadilha do Overfitting
O maior risco da regressão polinomial é escolher um grau muito alto. Um polinômio de grau 15 pode memorizar todos os 80 pontos de treinamento perfeitamente (RMSE ≈ 0), mas vai oscilar de forma selvagem entre eles e falhar em dados novos.
O diagnóstico padrão é uma curva de validação: plote o erro de treino e teste em função do grau.
degrees = range(1, 12)
train_rmses, test_rmses = [], []
for d in degrees:
m = make_pipeline(PolynomialFeatures(d, include_bias=False), LinearRegression())
m.fit(X_train, y_train)
train_rmses.append(np.sqrt(mean_squared_error(y_train, m.predict(X_train))))
test_rmses.append(np.sqrt(mean_squared_error(y_test, m.predict(X_test))))
plt.figure(figsize=(7, 4))
plt.plot(degrees, train_rmses, 'o-', label='Train RMSE', color='steelblue')
plt.plot(degrees, test_rmses, 's-', label='Test RMSE', color='orange')
plt.xlabel('Polynomial Degree')
plt.ylabel('RMSE')
plt.title('Validation Curve: Choosing the Best Degree')
plt.legend()
plt.tight_layout()
plt.savefig('poly_reg_validation_curve.png', dpi=120)
print("Plot saved.")O que você verá:
- Grau 1 (linear): tanto o RMSE de treino quanto o de teste são altos — underfitting.
- Grau 2: ambos caem acentuadamente — o modelo captura a forma real.
- Grau 5+: o RMSE de treino continua caindo, mas o RMSE de teste sobe — a diferença entre treino e teste sinaliza overfitting.
O melhor grau é onde o RMSE de teste é o mais baixo antes de começar a subir novamente. Neste exemplo, é o grau 2, que corresponde ao verdadeiro processo gerador dos dados.
Para uma seleção de grau mais robusta, use validação cruzada em vez de uma única divisão treino/teste.
Usando numpy.polyfit (Alternativa Rápida)
Para problemas univariados simples, a função polyfit do NumPy oferece um ajuste em uma linha sem scikit-learn. É útil para análise exploratória, mas não se integra com pipelines ou validação cruzada.
import numpy as np
x = np.array([1, 2, 3, 4, 5], dtype=float)
y = np.array([2.1, 4.0, 9.2, 16.1, 25.0])
# Fit a degree-2 polynomial
# Returns coefficients from highest to lowest degree: [a2, a1, a0]
coefficients = np.polyfit(x, y, 2)
poly = np.poly1d(coefficients)
print("Coefficients (highest to lowest degree):", np.round(coefficients, 3))
print("Prediction at x=6:", round(poly(6), 2))Saída esperada:
Coefficients (highest to lowest degree): [ 1.121 -0.939 1.76 ]
Prediction at x=6: 36.5A curva ajustada é aproximadamente y ≈ 1,12x² - 0,94x + 1,76, que é próxima à relação real y = x² nesses dados. Os coeficientes não são exatamente [1, 0, 0] porque há apenas cinco pontos de dados ruidosos para ajustar.
np.poly1d envolve os coeficientes para que você possa chamar o polinômio como uma função: poly(6) avalia 1,121(36) - 0,939(6) + 1,76 ≈ 36,5.
Pipeline Completo (Todos os Passos Juntos)
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
# 1. Generate data (true relationship: y = 0.5x² - x + 2 + noise)
rng = np.random.default_rng(42)
X = rng.uniform(-3, 3, 80).reshape(-1, 1)
y = 0.5 * X.ravel()**2 - X.ravel() + 2 + rng.normal(0, 0.5, 80)
# 2. Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 3. Build and fit
model = make_pipeline(PolynomialFeatures(degree=2, include_bias=False), LinearRegression())
model.fit(X_train, y_train)
# 4. Evaluate
rmse = np.sqrt(mean_squared_error(y_test, model.predict(X_test)))
r2 = r2_score(y_test, model.predict(X_test))
print(f"Test RMSE: {rmse:.4f}")
print(f"Test R²: {r2:.4f}")
# 5. Inspect learned coefficients
lr = model.named_steps['linearregression']
pf = model.named_steps['polynomialfeatures']
feature_names = pf.get_feature_names_out(['x'])
for name, coef in zip(feature_names, lr.coef_):
print(f" {name}: {coef:.4f}")
print(f" intercept: {lr.intercept_:.4f}")Saída esperada:
Test RMSE: 0.4538
Test R²: 0.9514
x: -0.9625
x^2: 0.4909
intercept: 1.9654Os coeficientes recuperados (x^2 ≈ 0,49, x ≈ -0,96, intercepto ≈ 1,97) correspondem de perto aos valores verdadeiros (0,5, -1, 2), o que confirma que o modelo aprendeu a forma correta.
Escalonamento de Variáveis e Regressão Polinomial
Ao trabalhar com polinômios de grau mais alto, os valores das variáveis crescem rapidamente — x = 100 produz x² = 10.000 e x³ = 1.000.000. Isso pode tornar o problema de mínimos quadrados numericamente instável.
A correção padrão é escalonar as variáveis com StandardScaler dentro do pipeline, antes da expansão polinomial:
from sklearn.preprocessing import StandardScaler
model = make_pipeline(
StandardScaler(),
PolynomialFeatures(degree=3, include_bias=False),
LinearRegression()
)Colocar StandardScaler primeiro significa que ele é ajustado apenas nos dados de treinamento e aplicado de forma consistente aos dados de teste — sem vazamento. Veja Feature Scaling para detalhes sobre por que o escalonamento é importante.
Armadilhas Comuns
Pular a divisão treino/teste. Se você ajustar e avaliar nos mesmos dados, polinômios de alto grau parecerão ter desempenho perfeito enquanto falham completamente com novas entradas. Sempre avalie em dados reservados.
Extrapolar fora do intervalo de treinamento. Curvas polinomiais oscilam e divergem fora do intervalo dos dados de treinamento. Um modelo treinado em x ∈ [0, 5] pode dar previsões absurdas em x = 10. A regressão linear extrapola de forma mais conservadora.
Não escalonar antes dos termos de alto grau. Valores grandes de variáveis combinados com expansão de alto grau podem produzir overflow numérico ou matrizes mal condicionadas. Use StandardScaler no pipeline.
Escolher o grau pelo erro de treinamento isoladamente. O RMSE de treino sempre diminui à medida que o grau aumenta. Use o RMSE de teste ou validação cruzada para encontrar o grau que generaliza.
Esquecer include_bias=False. PolynomialFeatures adiciona uma coluna constante (termo de intercepto) por padrão. LinearRegression também adiciona seu próprio intercepto. Passar include_bias=False para PolynomialFeatures evita a coluna redundante.
Próximos Passos
- Regressão Linear — a base de linha reta sobre a qual a regressão polinomial é construída
- Regressão Múltipla — combine muitas variáveis (a regressão polinomial aplicada a múltiplas entradas também produz termos de interação)
- Train/Test Split — a forma correta de avaliar qualquer modelo de regressão
- Validação Cruzada — mais robusta do que uma única divisão para ajustar o grau polinomial
- Feature Scaling — por que e como padronizar as entradas antes do ajuste