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 può essere visto, dal punto di vista matematico, come un modello lineare che utilizza un output continuo e una funzione di soglia per svolgere compiti di classificazione binaria. Questa soluzione, pur essendo semplice e importante dal punto di vista storico e didattico, presenta alcune limitazioni. In particolare:
L’output della funzione di attivazione è continuo, ma non probabilistico:
Il modello non produce valori delimitati nell’intervallo $[0,1]$, ma uno score reale che può assumere qualsiasi valore in $\left(-\infty,+\infty\right)$. Se, ad esempio, utilizziamo una soglia $\theta=0.5$, sia un output pari a $0.6$ sia un output pari a $100$ vengono assegnati alla classe $1$. Questi valori non possono però essere interpretati direttamente come probabilità.Adaline utilizza la MSE per approssimare numericamente le label:
Questo criterio è naturale per un problema di regressione, ma meno appropriato per un problema di classificazione: il modello cerca infatti di rendere gli output continui il più possibile vicini ai valori numerici assegnati alle classi, invece di ottimizzare direttamente la separazione tra le classi.
Il Valore Previsto è Continuo, non Probabilistico
Adaline produce valori numerici continui nell’intervallo $\left(-\infty,+\infty\right)$. Questi valori rappresentano uno score utilizzato successivamente dalla funzione di soglia, ma non possono essere interpretati direttamente come probabilità.
Ad esempio, se utilizziamo label $0$ e $1$, possiamo scegliere una soglia $\theta=0.5$: un output maggiore o uguale a $0.5$ viene assegnato alla classe $1$, mentre un output inferiore viene assegnato alla classe $0$. La soglia permette quindi di effettuare la classificazione, ma non trasforma lo score di Adaline in una probabilità. Alcuni modelli di classificazione, come la Regressione Logistica, producono invece valori compresi tra $0$ e $1$ che, sotto le assunzioni del modello, possono essere interpretati come stime della probabilità che un’osservazione appartenga alla classe positiva.
Per verificare quanto detto, creiamo un dataset bilanciato contenente $20$ clienti e una label “Purchased”, con valore $1$ (True) oppure $0$ (False), che indica se il cliente ha acquistato un determinato prodotto. Nel dataset definiamo $10$ clienti di età compresa tra $10$ e $19$ anni che hanno acquistato il prodotto e $10$ clienti di età compresa tra $20$ e $29$ anni che non lo hanno acquistato. Addestriamo il nostro modello Adaline e mostriamo lo scatter plot del dataset, riportando sull’asse $x$ la feature “Age” e sull’asse $y$ le relative label. Disegniamo inoltre la retta definita dalla funzione lineare del modello, che rappresenta gli output continui prodotti da Adaline:

Utilizziamo ora il modello per calcolare l’output associato ad alcuni clienti di età diversa. La colonna “Adaline Output” della tabella seguente riporta lo score continuo prodotto dal modello:
| 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 l’output relativo alla riga con indice $0$ è $1.172$, mentre quello relativo alla riga $5$ è $-0.257$. Entrambi i valori non possono essere interpretati come probabilità, poiché si trovano al di fuori dell’intervallo $[0,1]$.
Un modello come la Regressione Logistica affronta il problema in maniera differente:

| 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 Regressione Logistica produca valori nell’intervallo $[0,1]$, interpretabili come stime della probabilità di appartenenza alla classe positiva.
La Regressione Lineare Cerca di Approssimare Numericamente le Label
Consideriamo ora un limite più importante di Adaline come classificatore. Aggiungiamo al dataset altri $10$ clienti, di età compresa tra $60$ e $70$ anni, che non hanno acquistato il prodotto:

Addestriamo nuovamente Adaline e confrontiamo il risultato con quello ottenuto mediante la Regressione Logistica. Il grafico seguente mostra, per entrambi i modelli:
- il confine decisionale;
- l’output della funzione di attivazione;
- la classe finale prodotta dalla funzione di soglia.
Osservando le regioni decisionali, possiamo notare come Adaline classifichi erroneamente le osservazioni con valori di $\text{Age}$ pari a $20$, $21$ e $22$, assegnandole alla classe $1$ (i quadratini rossi nell’area celeste). La causa può essere compresa osservando la funzione di costo utilizzata dal modello. Adaline minimizza la MSE tra l’output continuo $\text{out}^{(i)}$ e la label $y^{(i)}$. Di conseguenza, non cerca direttamente il confine che separa meglio le due classi: cerca invece una retta i cui valori siano complessivamente il più possibile vicini ai valori numerici $0$ e $1$ assegnati alle label:

Le nuove osservazioni con valori elevati di $\text{Age}$ hanno una forte influenza sulla retta ottimizzata mediante MSE. Se la retta produce per una di queste osservazioni un valore molto distante dalla relativa label $y=0$, la penalizzazione cresce quadraticamente:
$$ \left(y^{(i)}-\text{out}^{(i)}\right)^2 $$Durante il training, il modello tende quindi a modificare i propri parametri per ridurre anche questi errori. La retta risultante può spostarsi o cambiare inclinazione, modificando di conseguenza anche il punto nel quale attraversa la soglia $\theta=0.5$ e quindi il confine decisionale. È importante osservare che i clienti di età compresa tra $60$ e $70$ anni non sono necessariamente outlier dal punto di vista della classificazione: sono infatti perfettamente coerenti con la classe $0$. Sono però osservazioni molto lontane nello spazio della feature e, proprio per questo, possono esercitare una forte influenza sull’ottimizzazione quadratica di Adaline.
La Regressione Logistica affronta il problema di classificazione in modo differente. Invece di utilizzare direttamente la somma pesata $z$ come output, applica una funzione Sigmoide:
$$ \sigma(z)=\frac{1}{1+e^{-z}}, $$che trasforma $z$ in un valore compreso nell’intervallo $(0,1)$:

Oltre a questo, la Regressione Logistica utilizza una funzione di costo specificamente adatta alla classificazione, la Logistic Loss (un caso particolare della funzione Cross-Entropy), che valuta quanto la probabilità prevista dal modello sia coerente con la classe osservata:
$$ L\left(y,\hat{p}\right) = \begin{cases} -\ln\left(\hat{p}\right) & \text{se $y = 1$}\\[6pt] -\ln\left(1-\hat{p}\right) & \text{se $y = 0$} \end{cases}\label{log_loss_single2}\tag{17} $$La combinazione tra funzione Sigmoide e Binary Cross-Entropy porta quindi a un criterio di ottimizzazione diverso da quello di Adaline: il modello non cerca di far coincidere una retta con i valori numerici $0$ e $1$ delle label, ma apprende direttamente le probabilità associate alle due classi e, attraverso queste, un confine decisionale adatto al problema di classificazione. Nel dataset dell’esempio, questo permette alla Regressione Logistica di mantenere il confine decisionale tra i due gruppi e di classificare correttamente tutte le osservazioni.
Ricapitolando
In questa sezione abbiamo analizzato l’architettura di Adaline (Adaptive Linear Neuron), uno dei primi modelli di neurone artificiale, utilizzabile — entro certi limiti — come classificatore binario. Abbiamo visto come Adaline condivida gran parte della propria struttura matematica con il precedente modello di Regressione Lineare multipla: calcola una somma pesata delle feature,
$$ z = \mathbf{w}\cdot\mathbf{x}+b, $$e utilizza una funzione di attivazione $\sigma$ che, nel caso di Adaline, corrisponde semplicemente alla funzione identità:
$$ \text{out}=\sigma(z)=z. $$L’output del modello è quindi un valore reale e continuo. Per effettuare una classificazione binaria, Adaline applica successivamente una funzione di soglia, che trasforma tale output continuo nella previsione categorica $\hat{y}$.
Durante il training, invece, la funzione di soglia non viene utilizzata: Adaline calcola la MSE direttamente sugli output continui e aggiorna i parametri $\mathbf{w}$ e $b$ tramite la Discesa del Gradiente.
Abbiamo infine evidenziato alcune importanti limitazioni del modello:
- l’output continuo prodotto da Adaline non rappresenta una probabilità e può assumere qualsiasi valore reale;
- la funzione di costo MSE è naturale per i problemi di regressione, ma non è specificamente progettata per la classificazione: Adaline cerca di approssimare numericamente i valori assegnati alle label, anziché ottimizzare direttamente un criterio probabilistico di classificazione.
Abbiamo quindi confrontato Adaline con la Regressione Logistica, un modello lineare specificamente progettato per la classificazione binaria, che affronta in modo più appropriato queste limitazioni attraverso l’utilizzo della funzione Sigmoide e di una funzione di costo adatta alla classificazione.
uno scatterplot è un diagramma di dispersione che utilizza gli esempi del dataset per rappresentare i valori di due diverse feature numeriche. ↩︎