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.

Dimmi di Più

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)}$:

Grafico della funzione di costo MSE senza Regolarizzazione L2
Figura 1: Funzione di Costo MSE senza Regolarizzazione L2

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:

Grafico della funzione di costo MSE con Regolarizzazione L2
Figura 2: Regolarizzazione L2

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:

Grafico della funzione di costo MSE non regolarizzata, e aggiunta del Termine di Regolarizzazione L2
Figura 3: Funzione di Costo MSE non regolarizzata, e aggiunta del Termine di Regolarizzazione L2

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:

Grafico della funzione di Costo MSE con Regolarizzazione L2, per diversi valori di lambda
Figura 4: Funzione di Costo MSE con Regolarizzazione L2, per diversi valori di lambda

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:

Grafico dei valori di regressione, con Regolarizzazione L2
Figura 5: Regressione con Regolarizzazione L2

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)
Grafico dei valori di output di Linear Regression contro Ridge Regression
Figura 6: Linear Regression contro Ridge Regression

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:

Intuizione Geometrica per la Regolarizzazione L1
Figura 7: Regolarizzazione L1

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

Grafico della funzione di costo MSE non regolarizzata, e aggiunta del Termine di Regolarizzazione L1
Figura 8: Funzione di Costo MSE non regolarizzata, e aggiunta del Termine di Regolarizzazione L1

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$:

Grafico della funzione di Costo MSE con Regolarizzazione L1, per diversi valori di lambda
Figura 9: Funzione di Costo MSE con Regolarizzazione L1, per diversi valori di lambda

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:

Grafico dei valori di output della Regressione con Regolarizzazione L1
Figura 10: Regressione con Regolarizzazione L1
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")
Scatterplot di due feature Collineari x1 e x2, dove x2 = 2x1 + noise
Figura 11: Feature Collineari: x2 = 2x1 + noise

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")
Grafico della selezione delle feature su dataset monodimensionale
Figura 12: Selezione delle Feature su un Dataset 1D

  1. 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}$. ↩︎

Caricamento
  • Rendering delle formule LaTeX...