La Regolarizzazione del Modello
Il progetto Python, che comprende gli script mostrati in questo post, e quelli relativi alla creazione dei grafici Matplotlib e Plotly, è disponibile assieme al corso Introduzione al Machine Learning.
Nella fase di training di un modello, l’uso di valori “estremi” dei parametri può portare a problemi di overfitting. Come abbiamo già visto, l’overfitting si verifica quando un modello apprende non solo i pattern sottostanti ai dati di addestramento, ma anche il rumore di fondo e dettagli specifici che non generalizzano su dati sconosciuti (sia che essi provengano dal test set, che dal “mondo reale”). Supponiamo di avere un regressore lineare, con una funzione di ipotesi di questo tipo:
$$ \begin{align} \hat{y} & = w_1x_1 + w_2x_2 + b\\[6pt] \end{align} $$Se i pesi $w_1$ e $w_2$ sono elevati, una piccola variazione dei valori di $x_1$ o $x_2$ si tradurrà in un’ampia variazione della previsione di output, $\hat{y}$. Di contro, se i pesi sono piccoli, la predizione $\hat{y}$ sarà meno sensibile alle variazioni delle feature di input. Il che significa che piccole variazioni in $x_1$ o $x_2$ porteranno a variazioni minime in $\hat{y}$, ed il modello fornirà quindi previsioni più uniformi e stabili, ed avrà maggiori capacità di generalizzazione.
La regolarizzazione del modello è una tecnica usata per porre rimedio all’overfitting, aggiungendo una penalità, o vincolo, che limiti la capacità del modello di regolare i propri parametri. Questo viene fatto andando ad estendere la funzione di costo, con un ulteriore termine di regolarizzazione. Penalizzando la propria complessità, il modello viene incoraggiato a trovare nei dati pattern più semplici.
Un basso valore di regolarizzazione indica al modello di dare molta importanza ai dati di addestramento, e meno importanza alla penalizzazione della complessità del modello. Al contrario, un elevato valore di regolarizzazione indica al modello di dare maggiore importanza alla sua penalizzazione, a scapito dell’adattamento al dataset di training. In sostanza, un valore di regolarizzazione basso pone molta fiducia nei dati di training, mentre un valore elevato indica che tale set potrebbe non essere pienamente rappresentativo della base dati del mondo reale.
Come Funziona la Regolarizzazione
Il processo di regolarizzazione aggiunge alla funzione di costo (MSE, Cross-Entropy, ecc.) un nuovo termine, $R$, che rappresenta una penalità, o vincolo, che serve a ridurre i valori dei pesi del modello, e rendere il predittore meno sensibile alle singole feature del dataset. Un classificatore con pesi di minor valore porta al riconoscimento di pattern più semplici, che generalizzano meglio sui nuovi dati. Ad esempio, considerando la funzione di costo $\text{MSE}$, abbiamo:
$$ \begin{align} L_{\text{total}} & = \text{MSE}(\textbf{w}, b) + R(\textbf{w})\\[6pt] & = \frac{1}{m} \sum_{i=1}^{m}\left[\left(y^{(i)} - \hat{y}^{(i)}\right)^2\right] + R(\textbf{w})\\[6pt] & = \frac{1}{m} \sum_{i=1}^{m}\left\{\left[y^{(i)} - \left(w_1x^{(i)}_1 + \cdots + w_nx^{(i)}_n + b\right)\right]^2\right\} + R(\textbf{w})\\[6pt] \end{align} $$dove la formula per calcolare $R$ dipende dal tipo di regolarizzazione che vogliamo implementare, come vedremo di seguito. L’algoritmo di ottimizzazione è quindi costretto a bilanciare l’adattamento ai dati di training (minimizzazando la funzione di costo), con la semplicità del modello (minimizzando il termine di regolarizzazione, $R$, e quindi i pesi del modello).
$R$ è una funzione in $\textbf{w}$, ma non in $b$. La regolarizzazione tipicamente penalizza solo i parametri di peso del modello, ma non non l’intercetta, ovvero il termine di bias, $b$. Pesi di valore maggiore corrispondono a previsioni più sensibili, che reagiscono più fortemente a piccole variazioni delle feature di input, portando spesso a previsioni instabili o erratiche, soprattutto su dati di test. Il termine di bias, $b$, è usato dalla funzione di ipotesi per spostare la previsione del modello “verso l’alto o verso il basso”, ma non influisce sulla complessità del modello allo stesso modo dei pesi $\textbf{w}$. Per questo, non viene considerato.
Nei modelli ML, vengono spesso usate la regolarizzazione $\text{L}^{2}$ (Ridge) e la regolarizzazione $\text{L}^{1}$ (Lasso). La differenza fondamentale tra le due sta nel modo in cui vengono penalizzati i parametri di peso $\textbf{w}$, e negli effetti che questo processo porta alla fase di addestramento del modello.
La Regolarizzazione $\text{L}^{2}$ (Ridge)
La regolarizzazione $\text{L}^{2}$, nota anche come regolarizzazione di Ridge, aggiunge un termine di penalità alla funzione di costo che è proporzionale alla somma del quadrato dei pesi del modello. Per un modello di regressione lineare, la funzione di costo con regolarizzazione $\text{L}^{2}$ è definita come:
$$ \begin{align} L_{\text{total}} & = \text{MSE} + \lambda \sum_{j=1}^{n}w^2_j\\[6pt] & = \frac{1}{m} \sum_{i=1}^{m}\left(y^{(i)} - \hat{y}^{(i)}\right)^2 + \lambda \sum_{j=1}^{n}w^2_j\\[6pt] \end{align} $$dove:
$\lambda \sum_{j=1}^{n}w^2_j$ $\hspace{0.1cm}$ è il termine di regolarizzazione $\text{L}^{2}$, utilizzato per ridurre la complessità del modello.
$w_j$ sono i parametri di peso del modello, e $n$ è il numero di feature.
$\lambda$ (lambda) è il parametro che controlla la forza della regolarizzazione. Un $\lambda$ più alto aumenta l’effetto di regolarizzazione, mentre un $\lambda$ più basso lo riduce.
$\sum_{j=1}^{n}w^2_j$ $\hspace{0.1cm}$ è il quadrato della distanza euclidea che va dall’origine dello spazio dei parametri, al punto espresso dal vettore $\textbf{w}$:
$$ \begin{align} \sum_{j=1}^{n}w^2_j = \left(\sqrt{w^2_1 + w^2_2 + \cdots + w^2_n}\right)^{2} = w^2_1 + w^2_2 + \cdots + w^2_n\\[6pt] \end{align} $$
Il termine di penalizzazione $\lambda \sum_{j=1}^{n}w^2_j$ $\hspace{0.1cm}$ è quadratico, e cresce rapidamente quando i pesi diventano di valore elevato. Ovvero: $\text{L}^{2}$ penalizza maggiormente i pesi maggiori, rispetto ai pesi minori. Durante il processo di ottimizzazione, il modello cercherà sia di ridurre la perdita adattandosi bene ai dati, sia di mantenere i pesi piccoli, per evitare un’elevata penalizzazione.
La regolarizzazione $\text{L}^{2}$ riduce efficacemente i pesi verso il valore zero, in modo proporzionale alla loro grandezza, e al valore di $\lambda$, ma non setta a zero nessuno di essi. Man mano che i pesi delle feature meno rilevanti per il modello si avvicinano a zero, l’impatto della loro riduzione sulla funzione di costo diventa sempre minore. La derivata:
$$ \begin{align} \frac{\partial \sum_{j=1}^{n}w^2_j }{\partial w_i} & = \frac{\partial w^2_i }{\partial w_i} = 2w_i\\[6pt] \end{align} $$è continua, il che significa che se un peso è piccolo, anche la spinta verso lo zero è piccola. Più il peso diventa piccolo, più la “trazione” diventa debole, quindi esso potrebbe non raggiungere completamente lo zero. Non annullando nessun peso, $\text{L}^{2}$ mantiene tutte le feature (anche se con coefficienti inferiori), e questo è utile quando riteniamo che ognuna di esse dia un contributo alla predizione del modello.
Intuizione Geometrica
A livello geometrico, per visualizzare il processo di regolarizzazione $\text{L}^{2}$, consideriamo un dataset definito dalla funzione target $w_1x_1 + w_2x_2$, dove $w_1 = 100$ e $w_2 = 100$. Usando un regressore lineare opportunamente addestrato, nello spazio vettoriale dei pesi, la superficie generata dalla funzione di costo MSE, che è un termine quadratico, prende la forma di un paraboloide, e definisce sul piano $w_1w_2$ dei contorni ellittici o circolari intorno alla soluzione ottimale. Minimizzare tale funzione significa muoversi nel contorno più interno, verso le coordinate $(w_1 = 100, w_2 = 100)$, dove $\text{MSE}$ avrà il suo valore più basso - in quanto, in tale punto, $\hat{y}^{(i)}$ tenderà ad essere uguale a $y^{(i)}$:

La regolarizzazione $\text{L}^{2}$ aggiunge una penalità basata sulla somma dei quadrati dei pesi, in questo caso: $\lambda(w_1^{2} + w_2^{2})$. Questo termine può essere visualizzato come un paraboloide ellittico, centrato nell’origine $(0, 0)$, dove la lunghezza dei semiassi dipende dal valore di $\lambda$, e dall’entità dei pesi. I contorni disegnati sul piano $w_1w_2$ sono delle circonferenze:

Senza la regolarizzazione, l’algoritmo di ottimizzazione troverebbe il punto $(w_1, w_2)$ all’interno del contorno ellittico di $\text{MSE}$ che dà il valore di costo più basso (Figura 1). Tuttavia, aggiungendo la regolarizzazione $\text{L}^{2}$ (Figura 2), a seconda della grandezza di $\lambda$, l’algoritmo vincola i parametri del modello a rimanere all’interno del paraboloide, diminuendo la sua dipendenza dal dataset di addestramento. Questo risultato può essere inteso come l’aggiunta di un bias che semplifica il modello, aumentandone il valore minimo di perdita, ma riducendone la varianza. Il risultato è visualizzato dall’immagine sottostante. Il primo grafico mostra le superfici $\text{MSE}$ e $\text{L}^2$, mentre il secondo visualizza l’applicazione del termine $\text{L}^2$ sulla funzione di costo originale:

Il modello è quindi forzato ad usare pesi più piccoli, che portano a una generalizzazione migliore. Di contro, spostandosi dal punto ottimale di minima originale, la varianza diminuisce, ed il bias aumenta, e con esso, anche il nuovo valore minimo di costo:

Senza regolarizzazione, una funzione polinomiale di grado elevato può adattarsi eccessivamente ai dati, dando luogo a una curva molto ondeggiante e complessa, che cerca di passare attraverso o vicino ad ogni data-point. Questo overfitting cattura il rumore di fondo, piuttosto che la vera tendenza sottostante al dataset.
Nell’esempio seguente, possiamo vedere il grafico di una curva polinomiale di ventesimo grado usata come funzione di ipotesi, alla quale viene applicato il termine di regolarizzazione $\text{L}^2$, con diversi valori di $\lambda$. La regolarizzazione $\text{L}^{2}$ scoraggia i pesi di valore elevato e riduce gradualmente la flessibilità del polinomio, ma utilizza comunque tutti i termini polinomiali dell’equazione, in quanto nessun coefficiente viene settato a $0$. Pertanto, anche i termini di ordine superiore (come, in quest’esempio, $x^{20}$, $x^{19}$, ecc.) avranno pesi non nulli, anche se potrebbero avere valori molto bassi. Il modello produrrà quindi una curva più dolce e meno complessa, che tenderà a catturare la tendenza generale della base dati, piuttosto che il suo rumore di fondo:

Un Esempio con Scikit Learn
Il regressore Ridge
di Scikit Learn è un modello di regressione lineare che incorpora la regolarizzazione $\text{L}^{2}$
per ridurre la tendenza del predittore ad adattarsi al rumore presente nei dati di addestramento.
La forza della regolarizzazione è controllata dal parametro alpha del costruttore.
Quando alpha = 0, Ridge diventa equivalente alla Regressione Lineare standard.
All’aumentare di alpha, l’effetto di regolarizzazione diventa più forte,
portando a coefficienti del modello più vicini allo zero.
Nel prossimo esempio andiamo a confrontare il modello
LinearRegression con Ridge.
Generiamo un dataset sintetico con una relazione polinomiale di quarto grado, e un po’ di rumore aggiunto, poi utilizziamo la classe
PolynomialFeatures
per trasformare i dati di input, per permettere ai due modelli lineari di catturare relazioni non lineari esistenti nel dataset.
Successivamente, addestriamo i due regressori, e confrontiamo l’errore quadratico medio (MSE) delle previsioni di entrambi:
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.preprocessing import PolynomialFeatures
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
def get_regression_data(n_samples=100):
"""Create a synthetic non-linear dataset using a quadratic relationship with noise."""
X = 6 * np.random.rand(n_samples, 1) - 3
y = 0.5 * X ** 4 + 12 * X ** 3 - 7 * X ** 2 + X + 2 + np.random.randn(n_samples, 1) * 100.2
return X, y
# get data
np.random.seed(random_state)
X, y = get_regression_data(100)
# split the data into training and testing sets
# X_train shape: (n_train_examples, 1)
# X_test shape: (n_test_examples, 1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# Polynomial Feature transformation
poly = PolynomialFeatures(degree=4)
X_train_poly = poly.fit_transform(X_train) # shape: (n_train_examples, degree + 1)
X_test_poly = poly.transform(X_test) # shape: (n_test_examples, degree + 1)
# Linear Regression model
lin_reg = LinearRegression()
lin_reg.fit(X_train_poly, y_train)
lin_y_pred = lin_reg.predict(X_test_poly)
lin_mse = mean_squared_error(y_test, lin_y_pred)
# Ridge Regression model (alpha controls regularization strength)
ridge_reg = Ridge(alpha=100.0)
ridge_reg.fit(X_train_poly, y_train)
ridge_y_pred = ridge_reg.predict(X_test_poly)
ridge_mse = mean_squared_error(y_test, ridge_y_pred)
# display the MSE metrics
print(f'MSE for Linear Regression: ... {lin_mse:.3f}')
print(f'MSE for Ridge Regression: .... {ridge_mse:.3f}')
MSE for Linear Regression: ... 7042.583
MSE for Ridge Regression: .... 6246.319
Vediamo ora il fit dei dati:
def plot_data(X, y, lin_reg, ridge_reg, poly, lin_mse, ridge_mse):
plt.figure(figsize=(8, 5))
plt.scatter(X, y, s=30, edgecolor="k", label="Data")
X_plot = np.linspace(X.min(), X.max(), 100).reshape(-1, 1)
X_plot_poly = poly.transform(X_plot)
plt.plot(X_plot, lin_reg.predict(X_plot_poly), color='red',
label=f"Linear Regression (MSE: {lin_mse:.3f})", linewidth=2)
plt.plot(X_plot, ridge_reg.predict(X_plot_poly), color='green',
label=f"Ridge Regression (MSE: {ridge_mse:.3f})", linewidth=2)
plt.xlabel('X')
plt.ylabel('y')
plt.legend()
#plt.grid(color='gray', alpha=0.5, linestyle='--')
plt.tight_layout()
plt.show()
# plot the results
plot_data(X_test, y_test, lin_reg, ridge_reg, poly, lin_mse, ridge_mse)

Possiamo notare come la regolarizzazione usata da Ridge permetta a tale modello di avere un punteggio MSE più basso
di quello di LinearRegression, in quanto essa previene in parte la cattura del rumore di fondo presente nel dataset,
diminuendo quindi il problema dell’overfitting.
La Regolarizzazione $\text{L}^{1}$ (Lasso)
La regolarizzazione $\text{L}^{1}$, nota anche come Lasso (Least Absolute Shrinkage and Selection Operator), aumenta il termine di penalizzazione della funzione di costo di un fattore pari alla somma dei valori assoluti dei pesi, moltiplicato per il parametro di regolarizzazione $\lambda$ (lambda). Per un modello di regressione lineare, la funzione di costo con regolarizzazione $\text{L}^{1}$ è definita come:
$$ \begin{align} L_{\text{total}} & = \text{MSE} + \lambda \sum_{j=1}^{n}\lvert w_j\rvert\\[6pt] & = \frac{1}{m} \sum_{i=1}^{m}\left(y^{(i)} - \hat{y}^{(i)}\right)^2 + \lambda \sum_{j=1}^{n}\lvert w_j\rvert\\[6pt] \end{align} $$dove:
$\lambda \sum_{j=1}^{n}\lvert w_j\rvert$ $\hspace{0.1cm}$ è il termine di regolarizzazione, o penalità, $\text{L}^{1}$, utilizzato per ridurre la complessità del modello.
$w_j$ sono i parametri di peso del modello, e $n$ è il numero di feature.
$\lambda$ è il parametro che controlla la forza della regolarizzazione. Un $\lambda$ più alto aumenta l’effetto di regolarizzazione, mentre un $\lambda$ più basso lo riduce.
$\sum_{j=1}^{n}\lvert w_j\rvert$ $\hspace{0.1cm}$ è la somma dei valori assoluti dei pesi, conosciuta anche come distanza di Manhattan. Nello spazio dei parametri, essa rappresenta una regione a forma di diamante, definita come:
$$ \begin{align} \sum_{j=1}^{n}\lvert w_j\rvert = \lvert w_1\rvert + \lvert w_2\rvert + \cdots + \lvert w_n\rvert\\[6pt] \end{align} $$
L’algoritmo di ottimizzazione cerca di minimizzare il costo totale $L_{\text{total}}$, che è una combinazione dell’errore ritornato dalla funzione di costo per la previsione del valore target, e del termine di regolarizzazione $\text{L}^1$, che aumenta con ogni peso diverso da zero. Per ridurre la perdita totale, l’ottimizzatore è incentivato a impostare alcuni pesi esattamente a zero, soprattutto se questo non compromette significativamente la capacità del modello di effettuare previsioni accurate. Immaginiamo due feature, $x_1$ e $x_2$:
- $x_1$ ha una forte relazione con il valore target $y$, e la sua inclusione riduce pesantemente la funzione di costo.
- $x_2$ ha una relazione con $y$ debole o nulla, e la sua aggiunta non migliora le previsioni del modello.
Se vengono considerate entrambe le feature, la penalità $\text{L}^1$ aumenta, perché più pesi sono diversi da zero. Ora l’ottimizzatore deve fare una scelta:
- mantenere la feature $x_2$ aumenta la penalità $\text{L}^1$, senza riduzione significativa della funzione di costo.
- eliminare $x_2$ - impostandone il peso a zero - riduce $\text{L}^1$, e aumenta solo leggermente la funzione di costo.
Quindi, la mossa ottimale è impostare il peso di $x_2$ a zero. Come per $\text{L}^{2}$, l’intensità della penalizzazione è determinata dal parametro di regolarizzazione $\lambda$. Aumentando il suo valore si ottengono valori più piccoli dei pesi del modello, riducendone di fatto la complessità.
Tramite la regolarizzazione $\text{L}^{1}$, i pesi molto piccoli diminuiscono di valore quanto quelli grandi, e possono arrivare esattamente a zero. Questo accade poiché:
$$ \begin{align} \frac{\partial \sum_{j=1}^{n} \lvert w_j\rvert}{\partial w_i} & = \frac{\partial \lvert w_i\rvert}{\partial w_i} = \text{sign}(w_i) = \pm 1\\[6pt] \end{align} $$quando $w_i \ne 0$. Quindi la “spinta” verso lo zero è costante, finché il coefficiente $w_i$ non è zero. Questa forza costante rende più facile per i pesi essere spinti fino a zero e rimanervi. Questo comportamento è utile in quanto può selezionare automaticamente le feature più importanti, portando a zero i pesi di quelle irrilevanti, che quindi possono essere rimosse dal dataset. Tale riduzione della dimensionalità porta a modelli più semplici e interpretabili, e aiuta a controllare i problemi di overfitting.
Intuizione Geometrica
A livello geometrico, per visualizzare il processo di regolarizzazione $\text{L}^{1}$, consideriamo nuovamente il regressore lineare, e la funzione target, con i due parametri, $w_1 = 100$ e $w_2 = 100$. La regolarizzazione $\text{L}^{1}$ aggiunge una penalità basata sulla somma dei valori assoluti dei pesi, come ad esempio $\lambda\left(\lvert w_1\rvert + \lvert w_2\rvert\right)$. Questo termine può essere visualizzato come “una piramide rovesciata”, centrata nell’origine $(0, 0)$. I contorni disegnati sul piano $w_1w_2$ sono dei rombi, definiti dall’equazione $\lvert w_1\rvert + \lvert w_2\rvert = c$, con i vertici posizionati nei punti in cui $w_1$ o $w_2$ sono uguali a zero. Nel nostro caso, il termine $c$ è implicito, e dipende dal valore di $\lambda$, e dall’entità dei pesi:

Aggiungendo la regolarizzazione $\text{L}^{1}$ (Figura 6), a seconda della grandezza di $\lambda$, l’algoritmo vincola i parametri del modello a rimanere all’interno della “piramide rovesciata”. Sul piano $w_1w_2$, il punto ottimale di perdita si sposta nel punto in cui il contorno ellittico di $\text{MSE}$ tocca il contorno a forma di rombo di $\text{L}^{1}$, e questo accade spesso nei punti dove $w_1$ o $w_2$ sono uguali a $0$. In tal caso, il modello genera una soluzione in cui le feature divenute irrilevanti, vengono rimosse. Nell’immagine seguente, abbiamo applicato un elevato valore di $\lambda$ per “estremizzare”, e rendere maggiormente visibile la trasformazione geometrica applicata alla superficie di costo (che setta entrambi i pesi $w_1$ e $w_2$ a $0$):

Di seguito, possiamo vedere cosa accade usando valori $\lambda$ sempre più elevati. Il comportamento è molto simile a quello della regolarizzazione $\text{L}^{2}$, con l’aggiunta del fatto che i pesi $w_1$ e $w_2$ possono essere settati a $0$:

La regolarizzazione $\text{L}^{1}$ diminuisce il valore dei pesi piccoli come di quelli grandi, e questi valori possono arrivare esattamente a zero. Valutando una funzione di ipotesi polinomiale di grado elevato, la regolarizzazione $\text{L}^{1}$ può eliminare i termini polinomiali di ordine superiore, portando i loro pesi esattamente a zero. Ad esempio, la regolarizzazione potrebbe eliminare il termine $x^3$ dal modello, impostando $w_3$ a zero, se lo ritenesse necessario. $\text{L}^{1}$ può ritornare un modello polinomiale “sparso”1, con meno termini rispetto al modello iniziale (ad esempio, solo $x$ e $x^2$), e questo può fornire una soluzione più semplice e interpretabile:

plot [1] - coefficients: [+0.0000 +5.3723 -10.6509 +3.5058 -0.3329]
plot [2] - coefficients: [+0.0000 -0.0000 -1.4120 -0.0000 +0.0497]
plot [3] - coefficients: [+0.0000 -0.0000 -0.0000 -0.1381 +0.0192]
plot [4] - coefficients: [+0.0000 -0.0000 -0.0000 -0.0000 -0.0076]
Un Esempio con Scikit Learn
In Scikit Learn, la regressione Lasso
è un tipo di regressione lineare che include la regolarizzazione $\text{L}^{1}$,
che penalizza i valori assoluti dei coefficienti del modello.
Lasso può essere uno strumento potente per la selezione delle feature, soprattutto quando abbiamo
feature collineari, che causano ridondanza nei dati.
Il termine di regolarizzazione $\text{L}^{1}$ ha l’effetto di ridurre i coefficienti del modello esattamente a zero,
selezionando automaticamente le feature più importanti, eliminando quelle irrilevanti o ridondanti,
che possono causare problemi di overfitting.
Feature Collineari
Quando due feature $x_1$ e $x_2$ sono collineari, esiste una forte relazione lineare tra di esse, e $x_2$ può essere prevista quasi esattamente utilizzando $x_1$, o viceversa. Ad esempio, se $x_2$ è collineare con $x_1$, esiste una relazione del tipo:
$$ \begin{align} x_2 = w_1x_1 + b\\[6pt] \end{align} $$Nel caso di collinearità perfetta, una feature è una combinazione lineare esatta di altre feature. Ad esempio:
$$ \begin{align} x_2 = 2x_1\\[6pt] \end{align} $$Quando più di due feature sono coinvolte in una relazione lineare, si parla di multicollinearità. Nell’esempio seguente, $x_3$ dipende linearmente sia da $x_1$, che da $x_2$:
$$ \begin{align} x_3 = 3x_1 + 4x_2\\[6pt] \end{align} $$Geometricamente, le feature collineari rappresentano vettori che si trovano lungo la stessa linea, o che sono linearmente dipendenti nello spazio delle feature, causando ridondanza nei dati. Le feature ridondanti possono generare overfitting, soprattutto in dataset ad alta dimensionalità. Esse creano anche problemi di interpretabilità: poiché gli effetti di due feature collineari $x_1$ e $x_2$ si confondono l’uno con l’altro, è difficile comprendere quanto $x_1$ e $x_2$ contribuiscano individualmente al modello.
Il grafico seguente mostra uno spazio 2D dove $x_2$ è collineare a $x_1$, in quanto $x_2 = 2x1 + \text{noise}$:
def get_collinear_data(n_samples, w1, w2, b, noise_scale=0.02):
"""Get a 2D dataset with collinear features, where x2 = 2x1 + noise."""
noise = np.random.normal(0, noise_scale, n_samples)
x1 = np.random.rand(n_samples)
x2 = 2 * x1 + noise
# vstack stacks x1 and x2 arrays vertically (row-wise),
# where each array has been reshaped to (1, N)
X = np.vstack([x1, x2]).T
y = w1*X[:, 0] + w2*X[:, 1] + b
return X, y
def plot_data(X, title, show_y_ticks=True):
"""Plot the input 2D dataset."""
plt.figure(figsize=(8, 5))
plt.scatter(X[:, 0], X[:, 1], s=30, edgecolor="k", label="Data Points")
plt.xlabel("x1")
if show_y_ticks:
plt.ylabel("x2")
else:
plt.yticks([])
plt.title(title)
plt.legend()
#plt.grid(color='gray', alpha=0.5, linestyle='--')
plt.tight_layout()
plt.show()
np.random.seed(42)
# get a dataset with collinear features, and plot it
X, y = get_collinear_data(100, 100, 100, 0, noise_scale=0.05)
plot_data(X, "2D Collinear Dataset")

Selezione delle Feature
Nell’esempio seguente, usiamo un regressore Lasso
per fare selezione delle feature. Quando due o più feature sono altamente correlate, Lasso tende a selezionarne solo una,
riducendo le altre a zero. Questo aiuta a semplificare il modello.
Istanziamo la classe Lasso, usando un valore alpha = 0,1, ed eseguiamo il training sul dataset 2D collineare.
Il parametro alpha controlla la forza della penalizzazione $\text{L}^{1}$.
Un valore più alto di alpha porta a ridurre a zero un maggior numero di caratteristiche.
Dopo la fase di addestramento del modello, usiamo l’attributo lasso.coef_
per verificare se e quali pesi del modello siano stati settati a $0$.
Se abbiamo almeno un peso con valore $0$, costruiamo un nuovo “pseudo” dataset 2D,
dove la feature $x_1$ è quella con peso diverso da $0$, e $x_2$ è un vettore colonna $0$.
Poi usiamo questo nuovo dataset per mostrare a video $x_1$, l’unica feature rimanente avente potere esplicativo.
Dovremmo ottenere solo una linea di data point, alla coordinata $x_2 = 0$:
# split data into Training and Test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# apply Lasso regression (L1 regularization) to transform the 2D dataset into 1D dataset
lasso = Lasso(alpha=0.12).fit(X_train, y_train)
# select only the feature that correspond to non-zero weights
selected_features_idx = lasso.coef_ != 0
# display stuff
with np.printoptions(formatter={'float': lambda x: "{:>.2f}".format(x)}):
print(f"Coefficients: ........ {lasso.coef_}")
print(f"Intercept: ........... {lasso.intercept_:.2f}")
print(f"Selected Features: ... {selected_features_idx}")
# did feature selection work?
# If so, we should have just one non-zero coefficient, that is related to the unique,
# meaningful feature. The weight of the other feature should have been set to zero
if np.sum(selected_features_idx) == 1:
# ok, now just build a new "pseudo" 2D dataset, where x2 is a 0 column vector.
# Then we use this new dataset to plot the unique feature that has explanatory power.
# We should obtain just a line of data points, where x2 = 0...
x1 = X[:, selected_features_idx].ravel()
x2 = np.zeros(len(x1))
X_1d = np.vstack((x1, x2)).T
plot_data(X_1d, "1D Dataset", show_y_ticks=False)
else:
print("Lasso was not able to do Feature Selection. Try again with another 'alpha' value")

un polinomio è un’espressione algebrica composta da termini, dove ogni termine è il prodotto di un coefficiente e di una variabile elevata a una potenza intera non negativa. Ad esempio: $0.1x^{3} - 1.5x^{2} - 3x^{1} + 0.5x^{0}$. Un polinomio “sparso” (sparse polynomial), è un polinomio che ha molti termini mancanti. Rispetto all’esempio precedente: $-1.5x^{2} + 0.5x^{0}$. ↩︎