Il Neurone Artificiale Adaline, Esempi di Classificazione
Parte (2/2)
Potrebbe interessarti:
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.
Classificazione con il Dataset Iris
Proviamo ora ad usare il nostro classificatore binario Adaline con il famoso dataset Iris, e verifichiamo che il modello sia in grado di distinguere due classi specifiche per mezzo delle loro caratteristiche.
Il dataset Iris è uno dei dataset più famosi e utilizzati nel Machine Learning, soprattutto per scopi didattici e di sperimentazione. È stato introdotto dallo statistico e biologo Ronald Fisher nel 1936, ed è spesso considerato l’“Hello World” dell’apprendimento automatico. Il dataset contiene 150 osservazioni di fiori appartenenti a 3 specie di Iris:
- Iris setosa
- Iris versicolor
- Iris virginica
Per ciascun fiore sono misurate 4 caratteristiche (feature), tutte espresse in centimetri:
- Lunghezza del sepalo (sepal length)
- Larghezza del sepalo (sepal width)
- Lunghezza del petalo (petal length)
- Larghezza del petalo (petal width)
Sebbene il dataset contenga tre classi, possiamo adattarlo a un problema di classificazione binaria scegliendo solo due specie - ad esempio, “Iris setosa” e “Iris versicolor” - tra le tre disponibili. In questo modo, il classificatore dovrà imparare a distinguere tra due tipi di fiori. Inoltre, per poter mostrare i campioni con uno scatterplot1 2D, scegliamo solo due feature, “Sepal length” e “Petal width” (ogni feature dovrà essere associata ad un asse dimensionale):
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
from sklearn import datasets
def get_iris_data():
"""Get 100 examples from the Iris dataset, considering just 2 classes:
'Iris Setosa' (first 50 examples) and 'Iris Versicolour' (last 50 examples).
Then consider only 2 features: 'sepal length' and 'petal width'."""
# this data sets consists of 3 different types of irises:
# 'Setosa', 'Versicolour' and 'Virginica'.
# There are 150 examples, 50 examples for each class.
# The features are: 'sepal length', 'sepal width', 'petal length' and 'petal width'.
iris = datasets.load_iris()
X = iris.data[:, [0, 3]] # consider features: 'sepal length' and 'petal width'
y = iris.target
X = X[0:100] # consider classes: 'Iris Setosa' and 'Iris Versicolour'
y = y[0:100]
return X, y
def plot_dataset_2D(X, y, x_label="$x_1$", y_label="$x_2$", class_labels=("0", "1")):
"""Draw a scatter-plot of the 2D input data."""
markers = ("s", "o")
colors = ("red", "blue")
plt.figure(figsize=(8, 5))
# plot class samples
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
marker=markers[idx], label=f"Class {class_labels[cl]}",
c=colors[idx], edgecolor="black", zorder=2)
plt.xlabel(x_label)
plt.ylabel(y_label)
plt.legend()
plt.grid(color="gray", alpha=0.5, linestyle="--")
plt.show()
X, y = get_iris_data()
plot_dataset_2D(X, y, x_label="Sepal Length", y_label="Petal Width")

Quello che vogliamo fare è utilizzare l’intero dataset per eseguire il training del modello, e mostrare a video un plot delle regioni decisionali, ed il confine decisionale che le separa.
from matplotlib.colors import ListedColormap
def plot_decision_regions(X, y, classifier, resolution=0.02, test_range=None,
x_label="$x_1$", y_label="$x_2$", class_labels=("0", "1")):
# setup marker generator and color map
markers = ("o", "s", "^", "v", "<")
colors = ("red", "blue", "lightgreen", "gray", "cyan")
cmap = ListedColormap(colors[:len(np.unique(y))])
x1_min, x1_max = X[:, 0].min() - 0.1, X[:, 0].max() + 0.1
x2_min, x2_max = X[:, 1].min() - 0.1, X[:, 1].max() + 0.1
x1_range = np.arange(x1_min, x1_max, resolution)
x2_range = np.arange(x2_min, x2_max, resolution)
xx1, xx2 = np.meshgrid(x1_range, x2_range)
# generate a matrix where each row / example is defined by the features" row vector [x1, x2].
# Then use this matrix to compute the predicted value for each example.
# Each prediction value can be 0 (for class "Iris-setosa") or 1 for the other types of Iris
xx = np.array([xx1.ravel(), xx2.ravel()]).T
preds = classifier.predict(xx)
# reshape the prediction matrix and use it as colormap to draw the filled contours
preds = preds.reshape(xx1.shape)
plt.figure(figsize=(8, 5))
plt.contourf(xx1, xx2, preds, alpha=0.3, cmap=cmap)
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
# plot class examples
for idx, y_class in enumerate(np.unique(y)):
plt.scatter(x=X[y == y_class, 0], # "Petal Length"
y=X[y == y_class, 1], # "Petal Width"
alpha=0.8,
c=colors[idx],
marker=markers[idx],
label=f"Class {class_labels[idx]}",
edgecolor="black")
# highlight test examples with a circle marker
if test_range:
X_test, y_test = X[test_range, :], y[test_range]
plt.scatter(X_test[:, 0],
X_test[:, 1],
c="none",
edgecolor="black",
alpha=1.0,
linewidth=1,
marker="o",
s=100,
label="Test set")
plt.xlabel(x_label)
plt.ylabel(y_label)
plt.legend()
plt.tight_layout()
plt.show()
# fit the Adaline model
adaline = AdalineGD(epochs=10000, eta=0.01)
adaline.fit(X, y)
Un plot delle regioni decisionali è un grafico che mostra come un classificatore “vede” lo spazio delle feature, e come esso decida la classe di appartenenza di ciascun punto, in base alle feature di input. È uno strumento molto utile per capire il comportamento interno di un modello di classificazione. In tale grafico, ogni punto del piano è colorato in base alla classe predetta dal modello per quel particolare punto. Le osservazioni del dataset (i “data point”) vengano visualizzati sopra queste regioni, con un marcatore o un colore diverso:
# plot the decision regions
plot_decision_regions(X, y, classifier=adaline, resolution=0.005,
x_label="Sepal Length", y_label="Petal Width",
class_labels=["0 (Iris Setosa)", "1 (Iris Versicolour)"])

In questo caso, vediamo che lo spazio delle feature (il piano cartesiano 2D, con le coordinate “Sepal Length” e “Petal Width”) viene diviso in due sezioni:
- quella arancione, che identifica i punti appartenenti alla classe $0$ (Iris Setosa);
- quella violetta, che mostra le osservazioni appartenenti alla classe $1$ (Iris Versicolour).
Fin qui tutto bene, considerando il fatto che abbiamo usato un dataset linearente separabile. Purtroppo, determinati casi di classificazione binaria mostrano i limiti del modello Adaline, che si basa sull’algoritmo di Regressione Lineare. Vediamo quali.
I Problemi di Adaline
Adaline è sostanzialmente un regressore lineare con un nome “bizzarro”, che possiede capacità di classificazione binaria, con alcune limitazioni importanti. I problemi più evidenti sono due:
il valore previsto, ritornato dalla funzione di attivazione, è continuo, ma non probabilistico. Il modello non fornisce una probabilità delimitata (ad esempio, nell’intervallo $[0, 1]$), ma solo numeri grezzi. Se il valore di soglia è $0.5$, un valore di output “basso” come $0.6$ oppure “alto” come $100$ viene sempre classificato come appartenente alla classe “$1$”. Non essendoci vincoli ai valori ritornati, il modello non può esprimere la “sicurezza” sulla classificazione effettuata.
la funzione di costo MSE, basata sulla Regressione Lineare, è sensibile ai dati sbilanciati, e agli outlier. Per dato sbilanciato si intende un set di dati in cui una o alcune classi hanno un numero di esempi molto maggiore rispetto alle altre. Un outlier è un’osservazione che differisce in modo significativo da altre osservazioni del dataset.
Il Valore Previsto è Continuo, non Probabilistico
Adaline prevede valori numerici continui, in un intervallo $\left(-\infty, +\infty\right)$. Interpretare tali risultati come valori di probabilità può essere fuorviante. Le previsioni di regressione non sono delimitate tra $0$ e $1$, quindi non possono essere interpretate direttamente come probabilità, e - in teoria - non esiste un valore di soglia univoco che possa essere usato per distinguere una classe dall’altra. Di contro, i modelli di classificazione binaria correttamente funzionanti producono naturalmente risultati compresi tra $0$ e $1$, che possono essere interpretati come la probabilità di una data osservazione di appartenere a una particolare classe, solitamente quella positiva.
Per verificare quanto detto, creiamo un dataset bilanciato che contenga un elenco di $20$ clienti, e una label “Purchased” con valori $1$ (True) e $0$ (False) per determinare se il cliente ha acquistato un determinato prodotto. Nel dataset definiamo $10$ clienti di età compresa tra $10$ e $19$ anni che hanno acquistato, e $10$ clienti di età compresa tra $20$ e $29$ anni che non hanno acquistato. Eseguiamo il training del nostro modello Adaline, e mostriamo a video lo scatter-plot del dataset, dove sull’asse $x$ associamo il valore di “Age”, e sull’asse $y$ i relativi valori label. Oltre a questo, disegnamo la retta definita dalla funzione di ipotesi, che corrisponde ai valori continui di output del modello, in funzione dei dati di input:

Ora usiamo tale retta per fare delle previsioni di acquisto per dei clienti con una determinata età. La colonna “Adaline Output” della tabella sottostante indica la “possibilità” che una data osservazione appartenga alla classe “$1$”:
| Age | Adaline Output | |
|---|---|---|
| 0 | 10 | 1.172 |
| 1 | 15 | 0.815 |
| 2 | 19 | 0.529 |
| 3 | 20 | 0.458 |
| 4 | 25 | 0.101 |
| 5 | 30 | -0.257 |
Possiamo notare che il valore di regressione per la riga con indice $0$ è $1.172$, e per la riga $5$ è $-0.257$. Entrambi i valori non possono essere interpretati come probabilità, in quanto non sono nell’intervallo $\left[0, 1\right]$. Esistono altri modelli che forniscono risultati decisamente migliori, quali ad esempio la Regressione Logistica:

| Age | Adaline (Regressione) | Regressione Logistica (Probabilità) | |
|---|---|---|---|
| 0 | 10 | 1.172 | 1.000 |
| 1 | 15 | 0.815 | 0.995 |
| 2 | 19 | 0.529 | 0.645 |
| 3 | 20 | 0.458 | 0.355 |
| 4 | 25 | 0.101 | 0.001 |
| 5 | 30 | -0.257 | 0.000 |
Nella tabella precedente, possiamo notare come la colonna relativa alla Regressione Logistica indichi dei valori di output nel range $\left[0, 1\right]$, che possono quindi essere interpretati come valori di probabilità che l’osservazione appartenga alla classe positiva.
La Regressione Lineare è sensibile ai Dati Sbilanciati, e agli Outlier
Ora aggiungiamo altri $10$ clienti al nostro dataset, di età compresa tra i $60$ e i $70$ anni, che non hanno comprato:

Addestriamo nuovamente il nostro modello Adaline, e confrontiamolo con la Regressione Logistica. Il grafico sottostante, per entrambi i modelli, mostra:
- i confini decisionali,
- l’output dell funzione di attivazione,
- l’output dell funzione di soglia, ovvero il valore di output del modello.
Se analizziamo la funzione di soglia, notiamo come Adaline sbagli a classificare le osservazioni aventi valore $\text{Age}$ di $20$, $21$ e $22$, che vengono erroneamente poste nella regione decisionale della classe “$1$”. Questo accade in quanto la Regressione Lineare è sensibile sia ai dati sbilanciati, che agli outlier, i quali possono influenzare significativamente i parametri del modello, portando a prestazioni non ottimali. Al contrario, la Regressione Logistica, riesce in questo caso a classificare correttamente tutti gli esempi:

Perchè accade questo? Nel caso di Adaline, come mostrato nella Figura 6, uno o più outlier possono modificare fortemente la linea di regressione, perché l’errore quadratico calcolato dalla funzione di costo $\text{MSE}$ sul singolo valore anomalo $\mathbf{x}$ cresce molto rapidamente con la distanza verticale (errore) tra il punto osservato e la retta di regressione, ossia tra valore reale $y$ e valore predetto $\hat{y}$.
Quei punti “estremi”, nella fase di addestramento del modello, costringerebbero la retta definita dalla funzione di ipotesi (che è uguale alla funzione di attivazione) a piegarsi per avvicinarsi a loro, peggiorando la predizione per tutti gli altri punti.
Senza entrare troppo nei dettagli matematici (per ora), la Regressione Logistica non calcola direttamente un valore continuo come Adaline, ma applica una funzione di attivazione sigmoide $\sigma$ alla somma pesata $z$, che “comprime” qualsiasi valore reale da passare alla funzione di costo in un intervallo ristretto $[0, 1]$:

Il sigmoide “agisce come un freno” che limita i valori estremi, e non dà loro molto peso nel processo di ottimizzazione del modello. Per questo motivo, la Regressione Logistica non cambia drasticamente la propria decisione per assecondare tali osservazioni.
Ricapitolando
In questa sezione abbiamo visto l’architettura di Adaline (Adaptive Linear Neuron), un neurone artificiale che definisce una rete neurale a singolo layer, utilizzabile - entro certi limiti - come classificatore binario. Abbiamo notato come Adaline estenda il precedente modello di Regressione Lineare multipla, aggiungendo ad esso una funzione di attivazione $\sigma$, e una funzione di soglia $\hat{y}$, che serve a trasformare un valore di regressione continuo in un valore di classificazione categorico. Abbiamo poi visto le limitazioni più evidenti di tale modello, ovvero:
- il valore di regressione previsto è continuo, non probabilistico
- Adaline è sensibile ai dati sbilanciati, e agli outlier
e abbiamo messo in relazione i risultati di Adaline con la Regressione Logistica, un modello di rete neurale a singolo layer che risolve in larga parte tali problemi, che andremo ad analizzare nel prossimo capitolo.
uno scatterplot è un diagramma di dispersione che utilizza gli esempi del dataset per rappresentare i valori di due diverse feature numeriche. ↩︎