Le Support Vector Machines (SVM) per i problemi di Classificazione nel Machine Learning con scikit-learn

SVM Support Vector Machine header

Le Support Vector Machines (SVM) sono uno strumento fondamentale nell’ambito del Machine Learning, particolarmente utile per affrontare problemi di classificazione e regressione. La loro efficacia si manifesta soprattutto in situazioni dove le dimensioni dei dati sono molto maggiori rispetto al numero di esempi di addestramento disponibili.

Le Support Vector Machine

Le Support Vector Machines (SVM) hanno una storia affascinante che inizia negli anni ’60 e ’70 con il lavoro pionieristico di Vladimir Vapnik e Alexey Chervonenkis presso l’Istituto di Controllo e Calcolo dell’Accademia delle Scienze dell’URSS. In quegli anni, stavano sviluppando la teoria dell’apprendimento statistico, che avrebbe posto le basi concettuali per le SVM.

Il vero punto di svolta si è verificato negli anni ’90, quando Vapnik e Chervonenkis hanno sviluppato l’algoritmo di apprendimento delle SVM. Questo ha segnato l’inizio del successo delle SVM nel campo dell’apprendimento automatico. Le SVM hanno rapidamente guadagnato popolarità grazie alle loro eccezionali prestazioni in problemi di classificazione, soprattutto quando il numero di dimensioni delle caratteristiche superava di gran lunga il numero di esempi di addestramento.

Un grande contributo alle SVM è stato l’introduzione del kernel trick. Questa tecnica ha permesso alle SVM di gestire dati non lineari mappandoli in uno spazio di dimensioni superiori, aprendo così la strada a un’ampia gamma di applicazioni più complesse.

Quindi, le Support Vector Machines (SVM) sono un potente algoritmo di apprendimento supervisionato utilizzato per problemi di classificazione e regressione. L’obiettivo principale delle SVM è trovare l’iperpiano ottimale che separa le classi nel miglior modo possibile.

Dato un insieme di punti di addestramento (x_i, y_i), dove x_i rappresenta le caratteristiche del punto e y_i è l’etichetta di classe associata (generalmente -1 o +1 per problemi di classificazione binaria), l’iperpiano ottimale è definito come:

 w \cdot x + b = 0

Dove w è il vettore dei pesi (coefficienti) e b è il termine di bias.

Poi si deve trovare il margine massimale. Il margine è la distanza tra l’iperpiano e i punti più vicini di ciascuna classe. L’obiettivo delle SVM è massimizzare questo margine. Il margine è calcolato come la distanza tra due iperpiani paralleli (uno per ciascuna classe) più vicini al piano di separazione. La distanza tra questi due iperpiani è:

 \frac{2}{\lVert w \rVert}

Dove \lVert w \rVert rappresenta la norma euclidea di w.

Per trovare l’iperpiano ottimale, risolviamo il seguente problema di ottimizzazione:

 \text{minimize } \frac{1}{2} \lVert w \rVert^2

soggetto ai vincoli:

 y_i(w \cdot x_i + b) \geq 1 \text{ per } i = 1, 2, …, n

Questi vincoli assicurano che ogni punto di addestramento si trovi oltre il margine corretto rispetto all’iperpiano.

Introduzione del Margine Softer

In alcuni casi, i dati potrebbero non essere linearmente separabili. In questi casi, si utilizza una versione modificata del problema di ottimizzazione, introducendo variabili di slack \xi_i \geq 0 per consentire un errore nel margine:

 y_i(w \cdot x_i + b) \geq 1 - \xi_i

e minimizzando:

 \frac{1}{2} \lVert w \rVert^2 + C \sum_{i=1}^{n} \xi_i

Dove (C) è un parametro di regolarizzazione che controlla il trade-off tra il massimizzare il margine e la riduzione degli errori di classificazione.

Il Kernel Trick

Per gestire problemi non lineari, si può mappare i dati in uno spazio di dimensioni superiori utilizzando una funzione kernel \Phi(x). Questo permette di calcolare l’iperpiano in uno spazio di dimensioni superiori senza effettivamente eseguire la trasformazione. Un esempio comune è il kernel RBF (Radial Basis Function):

 K(x, x') = \exp \left( -\gamma \lVert x - x' \rVert^2 \right)

Dove \gamma è un parametro di larghezza del kernel.

In sintesi, le SVM utilizzano la geometria degli spazi vettoriali e la teoria dell’ottimizzazione per trovare l’iperpiano che meglio separa le classi, garantendo un margine massimo. Il kernel trick consente alle SVM di gestire efficacemente anche problemi non lineari senza dover esplicitamente eseguire la trasformazione delle caratteristiche.

Le Support Vector Machines (SVM) in scikit-learn

Le Support Vector Machines (SVM) sono implementate in scikit-learn, una delle librerie più popolari per il Machine Learning in Python. Scikit-learn fornisce una vasta gamma di algoritmi di apprendimento automatico, tra cui anche le SVM, rendendole facilmente accessibili agli sviluppatori e ai ricercatori.

All’interno di scikit-learn, le SVM sono implementate attraverso il modulo sklearn.svm. Questo modulo offre diverse classi per SVM, tra cui:

  • SVC: Per problemi di classificazione con SVM.
  • NuSVC: Per problemi di classificazione con SVM con supporto per la selezione degli errori di classificazione.
  • SVR: Per problemi di regressione con SVM.

Queste classi offrono molte opzioni per personalizzare il comportamento delle SVM, come la scelta del kernel (lineare, polinomiale, RBF, ecc.), i parametri di regolarizzazione, i parametri del kernel e così via.

Esempio di un problema di Classificazione con le Support Vector Machines (SVM)

Il problema di classificazione affrontato in questo esempio utilizza il famoso dataset Iris. Questo dataset consiste in misurazioni di lunghezza e larghezza dei sepali e dei petali di tre specie di iris: Iris-setosaIris-versicolor e Iris-virginica. L’obiettivo è quello di predire correttamente la specie di iris basandosi su queste misurazioni. Carichiamo il dataset incluso in scikit.

import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# Caricamento del dataset Iris
iris = load_iris()

# Creazione del DataFrame di pandas
df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
df['target'] = iris.target
df.head()

Eseguendo questa porzione di codice, vedrai il tipo di misurazioni contenute all’interno del dataset, che per comodità viene caricato su un pandas Dataframe.

SVM Support Vector Machine - iris dataset

Adesso che abbiamo il dataset con le feature all’interno ed il target per poter valutare la classificazione. Suddividiamolo in due porzioni, X_train e y_train destinate per l’apprendimento del modello e X_test e y_test per verificare la bontà del modello. Poi creiamo un modello SVC e addestriamolo con i dati a disposizione. Al termine della fase di apprendimento del modello, si effettuerà una valutazione tra i valori predetti y_pred e quelli reali y_test, ottenendo il valore dell’accuratezza.

from sklearn.svm import SVC

# Divisione del dataset in set di addestramento e di test
X_train, X_test, y_train, y_test = train_test_split(df.drop('target', axis=1), df['target'], test_size=0.2, random_state=42)

# Creazione e addestramento del classificatore SVM
clf = SVC(kernel='linear')
clf.fit(X_train, y_train)

# Predizione sui dati di test
y_pred = clf.predict(X_test)

# Calcolo dell'accuratezza
accuracy = accuracy_score(y_test, y_pred)
print("Accuracy:", accuracy)

Eseguendo il codice si ottiene:

Accuracy: 1.0

Da questo valore, si deduce che il nostro modello è molto affidabile (almeno per quanto riguarda i valori testati).

Se vogliamo visualizzare come i valori del dataset si distribuiscono nello spazio delle features possiamo usare il seguente codice:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

X = iris.data
y = iris.target

# Create scatterplot to visualize data points
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=ListedColormap(['red', 'green', 'blue']), edgecolor='k', s=20)
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.title('SVM Classification Result')
plt.show()

Eseguendo si ottiene la distribuzione degli elementi del dataset in base alle prime due features (su 4 esistenti).

SVM Support Vector Machine - dataset scatterplot 1

La rappresentazione che abbiamo ottenuto è bidimensionale, ma dato che il modello SVC è stato addestrato su un dataset con quattro features, la sua rappresentazione corretta dovrebbe essere quadridimensionale. Le quattro features nel dataset Iris includono lunghezza e larghezza del sepalo, e lunghezza e larghezza del petalo.

Tuttavia, poiché visualizzare un grafico quadridimensionale è molto complesso, in genere si utilizzano grafici bidimensionali o tridimensionali per rappresentare visivamente il risultato di un modello di classificazione. Nell’esempio precedente, abbiamo scelto di utilizzare solo le prime due features (lunghezza e larghezza del sepalo) per la visualizzazione, mentre le altre due features sono state mantenute costanti a zero.

Questa è una semplificazione per permettere una visualizzazione più chiara e intuitiva. Tuttavia, è importante tenere presente che stiamo visualizzando solo una parte dello spazio delle features e che la visualizzazione non tiene conto delle altre due features del dataset Iris. Vediamo insieme tutte le possibili combinazioni dei rapporti tra le features.

KNN for classification - scatter plot all features

La Decision Boundary

La “Decision Boundary” (o confine decisionale) è una linea, un iperpiano o una superficie nello spazio delle features che separa le diverse classi nei problemi di classificazione. In altre parole, è il confine che il modello di classificazione utilizza per distinguere tra le diverse categorie di dati.

Nel contesto dell’apprendimento supervisionato, quando addestriamo un modello di classificazione, l’obiettivo è quello di trovare una Decision Boundary che minimizzi l’errore di classificazione sui dati di addestramento. Questa Decision Boundary può essere lineare, se il problema è lineamente separabile, o complessa se il problema richiede una separazione non lineare.

Per esempio, considerando un problema di classificazione binaria in cui i dati sono rappresentati da punti in uno spazio bidimensionale. La Decision Boundary sarebbe una linea che separa i punti di una classe da quelli dell’altra classe. Se hai più di due classi, la Decision Boundary può essere un iperpiano o una superficie che separa le diverse classi nello spazio delle features.

Una buona Decision Boundary è quella che generalizza bene ai dati non visti, quindi un’importante considerazione nell’addestramento dei modelli di classificazione è trovare un equilibrio tra la complessità del modello e la capacità di generalizzazione. Un modello troppo semplice potrebbe non essere in grado di catturare la complessità dei dati, mentre un modello troppo complesso potrebbe soffrire di overfitting, cioè potrebbe adattarsi eccessivamente ai dati di addestramento, perdendo la capacità di generalizzare ai dati nuovi e non visti.

Ritornando al nostro esempio. Anche se il nostro problema è quadridimensionale (4 features), possiamo ancora visualizzare la Decision Boundary, ma dovremo fare delle semplificazioni. Una possibilità è quella di proiettare lo spazio delle features su uno spazio di dimensione inferiore, ad esempio utilizzando una tecnica di riduzione della dimensionalità come la PCA (Principal Component Analysis).

Per proiettare lo spazio delle features su uno spazio bidimensionale utilizzando la tecnica di riduzione della dimensionalità, possiamo impiegare la Principal Component Analysis (PCA) o la Linear Discriminant Analysis (LDA). In questo esempio, utilizzeremo la PCA per ridurre le dimensioni dello spazio delle features a 2.

Ecco come puoi modificare il codice precedente per includere la riduzione della dimensionalità utilizzando PCA e visualizzare le Decision Boundary in uno spazio bidimensionale:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

# Caricamento del dataset Iris
iris = load_iris()

# Creazione del DataFrame di pandas
df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
df['target'] = iris.target

# Divisione del dataset in set di addestramento e di test
X_train, X_test, y_train, y_test = train_test_split(df.drop('target', axis=1), df['target'], test_size=0.2, random_state=42)

# Applicazione della PCA per ridurre le caratteristiche a 2 dimensioni
pca = PCA(n_components=2)
X_train_pca = pca.fit_transform(X_train)
X_test_pca = pca.transform(X_test)

# Creazione e addestramento del classificatore SVM
clf = SVC(kernel='linear')
clf.fit(X_train_pca, y_train)

# Predizione sui dati di test
y_pred = clf.predict(X_test_pca)

# Calcolo dell'accuratezza
accuracy = accuracy_score(y_test, y_pred)
print("Accuracy:", accuracy)

# Creazione della decision boundary
x_min, x_max = X_train_pca[:, 0].min() - 1, X_train_pca[:, 0].max() + 1
y_min, y_max = X_train_pca[:, 1].min() - 1, X_train_pca[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
                     np.arange(y_min, y_max, 0.02))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

# Visualizzazione della decision boundary
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, alpha=0.8)
plt.scatter(X_train_pca[:, 0], X_train_pca[:, 1], c=y_train, marker='o', edgecolors='k')
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.title('Decision Boundary with PCA')
plt.show()

Eseguendo si ottiene:

Accuracy: 0.9666666666666667

Ed il seguente grafico con le decision boundary che segnano le 3 diverse aree appartenenti alle 3 classi.

SVM Support Vector Machine - decision boundary with PCA

Proiezioni bidimensionali delle decision boundary del modello quadrimensionale senza la riduzione di dimensionalità porterebbero a conclusioni errate.

Se invece si vuole ragionare bidimensionalmente direttamente sulle feature, bisogna necessariamente sceglierne 2 delle quattro e costruire un nuovo modello di apprendimento SVM esclusivo su quelle due. Prendiamo per esempio le feature 1 e 2 del databases iris.

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC

# Caricamento del dataset Iris
iris = load_iris()

# Creazione del DataFrame di pandas
df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
df['target'] = iris.target

# Prendiamo solo le prime due colonne del DataFrame per visualizzare
X = df.iloc[:, :2]
y = df['target']

# Divisione del dataset in set di addestramento e di test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Creazione e addestramento del classificatore SVM
clf = SVC(kernel='linear')
clf.fit(X_train, y_train)

# Calculation of model accuracy
accuracy = accuracy_score(y_test, y_pred)
print("Accuracy:", accuracy)


# Creazione della mesh grid per visualizzare la decision boundary
x_min, x_max = X.iloc[:, 0].min() - 1, X.iloc[:, 0].max() + 1
y_min, y_max = X.iloc[:, 1].min() - 1, X.iloc[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.01),
                     np.arange(y_min, y_max, 0.01))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

# Plot dei punti del dataset e della decision boundary
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, alpha=0.8)
plt.scatter(X_train.iloc[:, 0], X_train.iloc[:, 1], c=y_train, marker='o', edgecolors='k', label='Train')
plt.scatter(X_test.iloc[:, 0], X_test.iloc[:, 1], c=y_test, marker='x', edgecolors='k', label='Test')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.title('Decision Boundary with SVM')
plt.legend()
plt.show()

Eseguendo si ottiene:

Accuracy: 0.9

E come grafico delle decision boundary.

SVM Support Vector Machine - decision boundary 2 features

Quando usare le SVM nei problemi di classificazione?

Le Support Vector Machines (SVM) sono una scelta appropriata per vari scenari di classificazione, ma ci sono considerazioni specifiche che possono guidare la scelta tra SVM, KNN e altri metodi. Ecco alcuni punti da considerare quando decidere se utilizzare le SVM rispetto a KNN o altri metodi di classificazione:

  • Dimensione e complessità dei dati: Le SVM tendono ad avere buone prestazioni quando ci sono molte caratteristiche (dimensionalità elevata) e il numero di esempi di addestramento è relativamente piccolo. In confronto, KNN può diventare computazionalmente inefficiente e meno efficace con un numero elevato di caratteristiche o un numero molto grande di punti dati.
  • Complessità del modello: Le SVM sono in grado di gestire efficacemente problemi con decision boundary complessi, anche in spazi di alta dimensionalità, grazie all’uso di kernel non lineari. Tuttavia, KNN tende a essere più adatto per problemi in cui la decision boundary è più semplice o in cui la struttura dei dati è più “locale”, cioè quando i punti dati simili tendono a raggrupparsi insieme nello spazio delle caratteristiche.
  • Interpretabilità del modello: KNN fornisce una classificazione basata sulla “vicinanza” nei dati di addestramento, il che può essere più interpretabile rispetto alla natura “black-box” delle SVM, specialmente quando si utilizzano kernel complessi. Quindi, se l’interpretabilità del modello è una priorità, KNN potrebbe essere preferibile.
  • Robustezza ai dati rumorosi: Le SVM tendono ad essere più robuste ai dati rumorosi rispetto a KNN. Poiché KNN si basa sulla vicinanza nei dati di addestramento, è sensibile ai punti dati rumorosi o ai valori anomali. Le SVM, d’altra parte, cercano di massimizzare il margine tra le classi, riducendo l’impatto dei singoli punti anomali.
  • Dimensionalità dei dati: Se si ha a che fare con un numero molto elevato di caratteristiche, le SVM possono essere preferibili a KNN in quanto sono meno sensibili alla “maldicenza della dimensionalità” (il fenomeno in cui la capacità di generalizzazione dei modelli diminuisce con l’aumentare della dimensionalità).

In sintesi, le SVM sono spesso preferite quando si tratta di problemi di classificazione con un numero elevato di dimensioni, un numero limitato di esempi di addestramento e decision boundary complesse. Tuttavia, la scelta tra SVM, KNN e altri metodi di classificazione dipenderà sempre dalla specificità del problema, dalle caratteristiche dei dati e dalle esigenze dell’applicazione.

Lascia un commento