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 (x², x³, …) 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 = β₀ + β₁xLa regressione polinomiale di grado n adatta:
y = β₀ + β₁x + β₂x² + β₃x³ + … + βₙxⁿL'intuizione chiave è che x², x³ 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
| Grado | Nome | Forma |
|---|---|---|
| 1 | Lineare | Retta |
| 2 | Quadratico | Curva singola (parabola) |
| 3 | Cubico | Un punto di flesso |
| 4+ | Ordine superiore | Curve 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
ntermini 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_scoreIl 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)}") # 16Passo 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.9514Un 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.5La 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.9654I 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