W3docs

Regressione Polinomiale

Scopri come funziona la regressione polinomiale, quando usarla e come costruire, valutare e ottimizzare un modello polinomiale in Python con scikit-learn.

La regressione lineare traccia una retta attraverso i dati. Quando la relazione tra input e output è curva — come la traiettoria di un pallone, la crescita di una colonia batterica o una curva dose-risposta — una retta non è un buon adattamento, indipendentemente dal numero di caratteristiche aggiunto. La regressione polinomiale risolve questo problema aggiungendo versioni potenziate delle caratteristiche esistenti (, , …) così il modello può piegarsi per seguire la curvatura dei dati, utilizzando comunque sotto il cofano la meccanica dei minimi quadrati ordinari.

Questa pagina tratta:

  • Come la regressione polinomiale estende matematicamente la regressione lineare
  • Quando usare la regressione polinomiale e quale grado scegliere
  • Una pipeline completa con scikit-learn: trasformazione delle caratteristiche, addestramento, valutazione
  • La trappola dell'overfitting e come rilevarla con le curve di addestramento/test
  • Confronto della qualità del modello con RMSE e R²

Come Funziona la Regressione Polinomiale

L'Equazione

La regressione lineare adatta:

y = β₀ + β₁x

La regressione polinomiale di grado n adatta:

y = β₀ + β₁x + β₂x² + β₃x³ + … + βₙxⁿ

L'intuizione chiave è che , e così via vengono trattati come caratteristiche aggiuntive. Il modello è ancora lineare nei suoi coefficienti (valori β); solo le caratteristiche sono non lineari. Ciò significa che i minimi quadrati ordinari funzionano ancora — basta pre-elaborare prima la matrice di input.

Gradi e Significato

GradoNomeForma
1LineareRetta
2QuadraticoCurva singola (parabola)
3CubicoUn punto di flesso
4+Ordine superioreCurve più complesse

Scegliere il grado giusto è l'abilità centrale. Troppo basso e il modello va in underfitting (manca la curvatura reale). Troppo alto e il modello va in overfitting (memorizza il rumore e fallisce su nuovi dati).

Quando Usare la Regressione Polinomiale

Usa la regressione polinomiale quando:

  • Un grafico a dispersione mostra una curva evidente nei dati che una retta non riesce a catturare
  • Vuoi un'alternativa rapida e interpretabile ai modelli basati su alberi per curvatura moderata
  • Hai già una baseline di regressione lineare funzionante che va in underfitting

Preferisci altri approcci quando:

  • La relazione è molto complessa o ha molte caratteristiche → prova alberi decisionali o gradient boosting
  • Devi fare previsioni ben oltre il range di addestramento (estrapolazione) → i polinomi di alto grado divergono selvaggiamente fuori dall'intervallo dei dati
  • Hai molte caratteristiche in input → ciascuna riceve n termini polinomiali, quindi la matrice delle caratteristiche cresce rapidamente

Costruire un Modello di Regressione Polinomiale

Passo 1: Importare le Librerie

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_score

Il trasformatore PolynomialFeatures di scikit-learn gestisce automaticamente l'espansione x → [1, x, x², …, xⁿ]. Incapsularlo con LinearRegression in una Pipeline mantiene il codice pulito e previene la fuga di dati durante la cross-validation.

Passo 2: Creare Dati di Esempio

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)

La relazione sottostante è quadratica, quindi un polinomio di grado 2 dovrebbe recuperarla bene. Questa è il tipo di situazione in cui la regressione lineare va sistematicamente in underfitting.

Passo 3: Dividere in Set di Addestramento e Test

Dividi sempre prima di adattare in modo da avere dati separati per la valutazione. Vedi Train/Test Split per una spiegazione 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)}")   # 16

Passo 4: Costruire e Addestrare la Pipeline

degree = 2
model = make_pipeline(
    PolynomialFeatures(degree=degree, include_bias=False),
    LinearRegression()
)
model.fit(X_train, y_train)

make_pipeline concatena i due passaggi: PolynomialFeatures trasforma l'input, poi LinearRegression si adatta sulle caratteristiche espanse. Chiami .fit(), .predict() e .score() sulla pipeline esattamente come faresti su un estimatore semplice.

Passo 5: Valutare il Modello

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}")

Output atteso:

Train RMSE: 0.4605   Test RMSE: 0.4538
Train R²:   0.9480   Test R²:   0.9514

Un R² vicino a 0.95 sui dati separati significa che il modello spiega circa il 95% della varianza — un ottimo adattamento per dati così rumorosi. I punteggi di addestramento e test sono vicini, il che indica che il modello generalizza piuttosto che andare in overfitting.

Interpretazione delle metriche:

  • RMSE (Root Mean Squared Error) — l'errore medio nelle stesse unità di y. Minore è meglio.
  • R² (coefficiente di determinazione) — frazione di varianza spiegata. Valori più vicini a 1.0 sono migliori; 0 significa che il modello non è migliore della previsione della media.

Passo 6: Visualizzare l'Adattamento

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.")

La curva rossa dovrebbe scorrere fluidamente attraverso la dispersione — abbastanza vicina da catturare la forma parabolica senza zigzagare attraverso i singoli punti di rumore.

La Trappola dell'Overfitting

Il rischio maggiore della regressione polinomiale è scegliere un grado troppo alto. Un polinomio di grado 15 può memorizzare perfettamente tutti gli 80 punti di addestramento (RMSE ≈ 0) ma oscillerà selvaggiamente tra di essi e fallirà sui nuovi dati.

La diagnostica standard è una curva di validazione: tracciare l'errore di addestramento e test in funzione del grado.

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.")

Cosa vedrai:

  • Grado 1 (lineare): sia il RMSE di addestramento che quello di test sono alti — underfitting.
  • Grado 2: entrambi scendono bruscamente — il modello cattura la forma reale.
  • Grado 5+: il RMSE di addestramento continua a scendere, ma quello di test sale — il divario tra addestramento e test segnala overfitting.

Il grado migliore è quello in cui il RMSE di test è minimo prima di ricominciare a salire. In questo esempio è il grado 2, che corrisponde al vero processo generatore dei dati.

Per una selezione del grado più robusta, usa la cross-validation invece di una singola suddivisione addestramento/test.

Usare numpy.polyfit (Alternativa Rapida)

Per semplici problemi univariati, la funzione polyfit di NumPy offre un adattamento in una riga senza scikit-learn. È utile per l'analisi esplorativa ma non si integra con pipeline o cross-validation.

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))

Output atteso:

Coefficients (highest to lowest degree): [ 1.121 -0.939  1.76 ]
Prediction at x=6: 36.5

La curva adattata è approssimativamente y ≈ 1.12x² - 0.94x + 1.76, che è vicina alla vera relazione y = x² in questi dati. I coefficienti non sono esattamente [1, 0, 0] perché ci sono solo cinque punti di dati rumorosi da adattare.

np.poly1d racchiude i coefficienti così puoi chiamare il polinomio come una funzione: poly(6) valuta 1.121(36) - 0.939(6) + 1.76 ≈ 36.5.

Pipeline Completa (Tutti i Passi Insieme)

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}")

Output atteso:

Test RMSE: 0.4538
Test R²:   0.9514
  x: -0.9625
  x^2: 0.4909
  intercept: 1.9654

I coefficienti recuperati (x^2 ≈ 0.49, x ≈ -0.96, intercetta ≈ 1.97) corrispondono strettamente ai valori reali (0.5, -1, 2), il che conferma che il modello ha appreso la forma corretta.

Scalatura delle Caratteristiche e Regressione Polinomiale

Quando si lavora con polinomi di grado elevato, i valori delle caratteristiche crescono rapidamente — x = 100 produce x² = 10.000 e x³ = 1.000.000. Questo può rendere il problema dei minimi quadrati numericamente instabile.

La soluzione standard è scalare le caratteristiche con StandardScaler all'interno della pipeline, prima dell'espansione polinomiale:

from sklearn.preprocessing import StandardScaler

model = make_pipeline(
    StandardScaler(),
    PolynomialFeatures(degree=3, include_bias=False),
    LinearRegression()
)

Posizionare StandardScaler per primo significa che viene adattato solo sui dati di addestramento e applicato in modo coerente ai dati di test — nessuna fuga. Vedi Feature Scaling per dettagli su perché la scalatura è importante.

Errori Comuni

Saltare la divisione addestramento/test. Se adatti e valuti sugli stessi dati, i polinomi di alto grado sembreranno funzionare perfettamente mentre falliscono completamente su nuovi input. Valuta sempre su dati separati.

Estrapolazione al di fuori dell'intervallo di addestramento. Le curve polinomiali oscillano e divergono al di fuori dell'intervallo dei dati di addestramento. Un modello addestrato su x ∈ [0, 5] può dare previsioni assurde a x = 10. La regressione lineare estrapolva in modo più conservativo.

Non scalare prima dei termini di alto grado. Valori di caratteristiche elevati combinati con un'espansione di alto grado possono produrre overflow numerico o matrici mal condizionate. Usa StandardScaler nella pipeline.

Scegliere il grado solo in base all'errore di addestramento. Il RMSE di addestramento diminuisce sempre all'aumentare del grado. Usa il RMSE di test o la cross-validation per trovare il grado che generalizza.

Dimenticare include_bias=False. PolynomialFeatures aggiunge per default una colonna costante (termine di intercetta). Anche LinearRegression aggiunge la propria intercetta. Passare include_bias=False a PolynomialFeatures evita la colonna ridondante.

Passi Successivi

  • Regressione Lineare — la base con la retta su cui si fonda la regressione polinomiale
  • Regressione Multipla — combina molte caratteristiche (la regressione polinomiale applicata a più input produce anche termini di interazione)
  • Train/Test Split — il modo corretto per valutare qualsiasi modello di regressione
  • Cross-Validation — più robusto di una singola suddivisione per ottimizzare il grado polinomiale
  • Feature Scaling — perché e come standardizzare gli input prima dell'adattamento
Was this page helpful?