Salta al contenuto
Note per Studenti Esercizio - Ricerca del miglior C per una SVM lineare sul dataset digits (laboratorio 10)

Esercizio - Ricerca del miglior C per una SVM lineare sul dataset digits (laboratorio 10)

Teoria: Apprendimento automatico nell'IA - supervisionato, SVM e valutazioneQuadro del machine learning nel corso di IA. Tre forme di apprendimento: supervisionato (coppie ingresso-uscita, classificazione se l'uscita è discreta e regressione se è un numero), non supervisionato (pattern senza feedback, per esempio clustering) e per rinforzo (ricompense). Un'ipotesi h approssima la funzione vera f e si valuta sul test set, non sul training set: un modello troppo semplice ha bias alto (underfitting), uno troppo complesso varianza alta (overfitting). Servono training, validation e test set; con pochi dati si usa la k-fold cross-validation. Alberi di decisione: si sceglie l'attributo con massimo guadagno d'informazione Gain = B(p/(p+n)) - somma (Nk/N) B(pk/Nk), si pota con il test del chi quadro (Delta > Delta*), si riduce la varianza con bagging e random forest. Perdita L1 e L2, regressione lineare e discesa del gradiente w <- w - alpha dL/dw. SVM: separatore a margine massimo, vettori di supporto, kernel trick con K(xj,xk). Pipeline scikit-learn del laboratorio 10: load_digits, train_test_split, SVC(C, kernel, gamma), fit, predict, classification_report, confusion_matrix, cross_val_score.Apprendimento automatico nell'IA - supervisionato, SVM e valutazione →, sezioni 3, 6 e 7. Sul parametro CC e sulla SVM a margine morbido: Support vector machines e metodi kernelUna SVM cerca l'iperpiano $w\cdot x+b=0$ che separa due classi con il margine più largo possibile: normalizzando $y_i(w\cdot x_i+b)\ge1$ il margine totale vale $2/|w|$, quindi si risolve $\min\frac12|w|^2$ (problema convesso, nessun minimo locale). Con classi sovrapposte si ammettono errori con le variabili di scarto $\xi_i$ e il parametro $C$ (soft margin, equivalente alla hinge loss più una penalità su $|w|^2$); $C$ piccolo = margine largo e più bias, $C$ grande = margine stretto e più varianza, si sceglie per cross-validation. La soluzione dipende solo dai vettori di supporto ($w=\sum\alpha_iy_ix_i$) e solo tramite prodotti scalari, per questo si può sostituire $x_i\cdot x_j$ con un kernel $K(x_i,x_j)=\langle\phi(x_i),\phi(x_j)\rangle$ (polinomiale, RBF) senza calcolare $\phi$: così si ottengono bordi non lineari. La SVR usa lo stesso schema con un tubo di tolleranza $\varepsilon$. Nell'esame le SVM sono solo nella parte teorica.Support vector machines e metodi kernel → e Esercizio - SVM a margine rigido e morbido a mano. Sulle metriche: Metriche di classificazioneIn classificazione binaria ogni previsione è vero positivo (TP), vero negativo (TN), falso positivo (FP, errore di tipo I) o falso negativo (FN, errore di tipo II). Da queste quattro quantità: accuracy $=\frac{TP+TN}{TP+TN+FP+FN}$, specificità $=\frac{TN}{TN+FP}$, precision $=\frac{TP}{TP+FP}$, recall $=\frac{TP}{TP+FN}$, e la loro media armonica $F_1=\frac{2PR}{P+R}$. Con dati sbilanciati l'accuracy inganna (un modello che predice sempre la classe maggioritaria ha 99%): si usano precision, recall, F1, ROC-AUC, la cross-validation stratificata e il riequilibrio con undersampling o oversampling (non SMOTE). Cambiando la soglia sulla probabilità si ottiene la curva ROC (TPR contro FPR) e l'area AUC. Approfondimento: non nel programma di Telecomunicazioni.Metriche di classificazione →.

Testo. Creare una SVM con kernel lineare usando scikit-learn. Verificare le prestazioni al variare dell'iperparametro CC usando la funzione numpy.logspace nell'intervallo −20:0:20-20:0:20 (cioè start=-20, stop=0, num=20: venti valori da 10−2010^{-20} a 10010^0, equispaziati in scala logaritmica) sul dataset digits.

(a) Trovare la miglior configurazione di CC con una cross-validation a 3 fold sul dataset digits. (b) Fare il grafico del punteggio ottenuto per ogni configurazione, con i valori di CC sull'asse xx e il punteggio di CV sull'asse yy (media e deviazione standard). Suggerimento: usare la scala logaritmica. (c) Addestrare e testare il modello sul dataset digits con la miglior configurazione trovata.

Soluzione

(a) Scelta di CC con la cross-validation

Per ogni valore di CC si fa una 3-fold cross-validation sull'intero dataset (1 797 immagini 8×88\times8, 10 classi): cross_val_score addestra 3 volte su due terzi dei dati e valuta sul terzo rimasto (per un classificatore le fold sono stratificate, cioè mantengono le proporzioni delle classi), poi si tengono la media e la deviazione standard dei 3 punteggi. Si sceglie il CC con la media più alta.

python
import numpy as np
from sklearn import datasets, svm, metrics
from sklearn.model_selection import cross_val_score, train_test_split

X, y = datasets.load_digits(return_X_y=True)        # X ha forma (1797, 64): le immagini sono gia' appiattite
svc = svm.SVC(kernel="linear")
C_s = np.logspace(-20, 0, 20)                       # 20 valori tra 1e-20 e 1, in scala logaritmica

scores, scores_std = [], []
for C in C_s:
    svc.C = C
    s = cross_val_score(svc, X, y, cv=3, n_jobs=1)  # 3 punteggi (accuracy), uno per fold
    scores.append(np.mean(s)); scores_std.append(np.std(s))

best = int(np.argmax(scores)); best_C = C_s[best]
print("Il C con il punteggio piu' alto e':", best_C, "punteggio:", round(scores[best], 4))

I venti punteggi medi (dal più basso al più alto, come nell'output del corso): 0,1642, 0,1653 (×12), 0,1658, 0,3790, 0,9160, 0,9438, 0,9438, 0,9449, 0,9460.0{,}1642,\ 0{,}1653\ (\times12),\ 0{,}1658,\ 0{,}3790,\ 0{,}9160,\ 0{,}9438,\ 0{,}9438,\ 0{,}9449,\ 0{,}9460. Il valore migliore è 0,94600{,}9460 per C=6,95⋅10−4C=6{,}95\cdot10^{-4} (C=10−20+20k/19C=10^{-20+20k/19} con k=16k=16 nell'ordine crescente). I valori rilevanti, con la deviazione standard sulle 3 fold:

CC punteggio medio dev. standard
da 10−2010^{-20} a 4,8⋅10−74{,}8\cdot10^{-7} 0,1650{,}165 0,090{,}09
5,5⋅10−65{,}5\cdot10^{-6} 0,3790{,}379 0,0160{,}016
6,2⋅10−56{,}2\cdot10^{-5} 0,9160{,}916 0,0040{,}004
6,95⋅10−4\mathbf{6{,}95\cdot10^{-4}} 0,946\mathbf{0{,}946} 0,0140{,}014
7,8⋅10−37{,}8\cdot10^{-3} 0,9450{,}945 0,0090{,}009
8,9⋅10−28{,}9\cdot10^{-2} 0,9440{,}944 0,0100{,}010
11 0,9440{,}944 0,0100{,}010

Il corso osserva che l'ultima parte della curva (gli ultimi quattro valori) dà prestazioni confrontabili: si sceglie il massimo dell'accuratezza, ma i quattro valori sono statisticamente indistinguibili (le differenze, 0,0020{,}002, sono molto minori della deviazione standard). A parità di prestazioni conviene il CC più piccolo, cioè il modello più regolarizzato.

(b) Il grafico

python
import matplotlib.pyplot as plt
plt.figure()
plt.semilogx(C_s, scores)
plt.semilogx(C_s, np.array(scores) + np.array(scores_std), "b--")   # media + deviazione standard
plt.semilogx(C_s, np.array(scores) - np.array(scores_std), "b--")   # media - deviazione standard
plt.xlabel("Parametro C"); plt.ylabel("Punteggio di CV"); plt.ylim(0, 1.1)
plt.show()

La curva ha tre zone:

  1. C≲5⋅10−7C\lesssim5\cdot10^{-7}: punteggio costante ≈0,165\approx0{,}165. Con CC minuscolo la penalità sugli errori è quasi nulla, il modello è sotto-adattato (underfitting): tutti i coefficienti duali restano a 00 e il classificatore degenera, predicendo la stessa cifra per tutti gli esempi (provato sull'intero dataset con C=10−20C=10^{-20}: sempre la cifra 3, tutti i 1 797 punti sono vettori di supporto). Il punteggio è vicino alla probabilità di indovinare a caso (0,100{,}10).
  2. 10−6≲C≲10−410^{-6}\lesssim C\lesssim10^{-4}: salita ripida (0,3790{,}379 a 5,5⋅10−65{,}5\cdot10^{-6}, 0,9160{,}916 a 6,2⋅10−56{,}2\cdot10^{-5}).
  3. C≳10−3C\gtrsim10^{-3}: plateau attorno a 0,9440{,}944-0,9460{,}946: oltre non serve penalizzare di più gli errori (con C=1C=1 il modello classifica perfettamente il training e ha 436 vettori di supporto su 1 797; con C=6,95⋅10−4C=6{,}95\cdot10^{-4} ne ha 648).

(c) Addestramento e test con il miglior CC

Per il test si ricarica il dataset come in aula, si appiattiscono le immagini e si divide senza rimescolare (shuffle=False): 75% training (1 347 immagini, le prime) e 25% test (450 immagini, le ultime).

python
digits = datasets.load_digits()
data = digits.images.reshape((len(digits.images), -1))
X_train, X_test, y_train, y_test = train_test_split(data, digits.target, test_size=0.25, shuffle=False)

svc = svm.SVC(kernel="linear", C=best_C)
svc.fit(X_train, y_train)
predicted = svc.predict(X_test)
print(metrics.classification_report(y_test, predicted))

Output (identico a quello del corso):

              precision    recall  f1-score   support
           0       0.98      0.98      0.98        43
           1       0.96      0.96      0.96        46
           2       1.00      1.00      1.00        43
           3       0.95      0.77      0.85        47
           4       0.96      0.94      0.95        48
           5       0.92      0.98      0.95        45
           6       0.98      0.98      0.98        47
           7       0.90      0.96      0.92        45
           8       0.78      0.88      0.83        41
           9       0.89      0.87      0.88        45
    accuracy                           0.93       450

L'accuratezza sul test è 0,9290{,}929 (418418 cifre su 450450), inferiore a quella della CV (0,9460{,}946). Il corso nota che le due cifre non sono confrontabili: la CV è fatta su tutto il dataset con fold stratificate, il test è fatto sul solo ultimo quarto, senza rimescolare, cioè su una parte diversa del dataset da quella usata per l'addestramento. Le classi più difficili sono la 3 (recall 0,770{,}77: 3636 giuste su 4747, FN=11FN=11; precision 0,950{,}95: FP=2FP=2) e la 8 (precision 0,780{,}78, recall 0,880{,}88): dalla matrice di confusione la 3 viene scambiata soprattutto con la 8 (6 volte), poi con la 7 (3 volte) e con la 5 (2 volte). Da qui F1(3)=2⋅362⋅36+2+11=0,847F_1(3)=\tfrac{2\cdot36}{2\cdot36+2+11}=0{,}847.

Perché si usa la CV e non il test per scegliere CC. Se CC fosse scelto guardando il test, l'accuratezza riportata sarebbe ottimistica: il test "trapela" nel modello. Qui CC si sceglie con la CV (sul training), poi il test misura solo la generalizzazione finale.

Versione ripasso

Teoria: Apprendimento automatico nell'IA - supervisionato, SVM e valutazioneQuadro del machine learning nel corso di IA. Tre forme di apprendimento: supervisionato (coppie ingresso-uscita, classificazione se l'uscita è discreta e regressione se è un numero), non supervisionato (pattern senza feedback, per esempio clustering) e per rinforzo (ricompense). Un'ipotesi h approssima la funzione vera f e si valuta sul test set, non sul training set: un modello troppo semplice ha bias alto (underfitting), uno troppo complesso varianza alta (overfitting). Servono training, validation e test set; con pochi dati si usa la k-fold cross-validation. Alberi di decisione: si sceglie l'attributo con massimo guadagno d'informazione Gain = B(p/(p+n)) - somma (Nk/N) B(pk/Nk), si pota con il test del chi quadro (Delta > Delta*), si riduce la varianza con bagging e random forest. Perdita L1 e L2, regressione lineare e discesa del gradiente w <- w - alpha dL/dw. SVM: separatore a margine massimo, vettori di supporto, kernel trick con K(xj,xk). Pipeline scikit-learn del laboratorio 10: load_digits, train_test_split, SVC(C, kernel, gamma), fit, predict, classification_report, confusion_matrix, cross_val_score.Apprendimento automatico nell'IA - supervisionato, SVM e valutazione →, Support vector machines e metodi kernelUna SVM cerca l'iperpiano $w\cdot x+b=0$ che separa due classi con il margine più largo possibile: normalizzando $y_i(w\cdot x_i+b)\ge1$ il margine totale vale $2/|w|$, quindi si risolve $\min\frac12|w|^2$ (problema convesso, nessun minimo locale). Con classi sovrapposte si ammettono errori con le variabili di scarto $\xi_i$ e il parametro $C$ (soft margin, equivalente alla hinge loss più una penalità su $|w|^2$); $C$ piccolo = margine largo e più bias, $C$ grande = margine stretto e più varianza, si sceglie per cross-validation. La soluzione dipende solo dai vettori di supporto ($w=\sum\alpha_iy_ix_i$) e solo tramite prodotti scalari, per questo si può sostituire $x_i\cdot x_j$ con un kernel $K(x_i,x_j)=\langle\phi(x_i),\phi(x_j)\rangle$ (polinomiale, RBF) senza calcolare $\phi$: così si ottengono bordi non lineari. La SVR usa lo stesso schema con un tubo di tolleranza $\varepsilon$. Nell'esame le SVM sono solo nella parte teorica.Support vector machines e metodi kernel →.

Procedura. C_s = np.logspace(-20, 0, 20); per ogni CC: svc.C = C, cross_val_score(svc, X, y, cv=3) (3 fold stratificate), si tengono media e deviazione standard. Si sceglie il CC con la media più alta; poi si addestra su training (75%, shuffle=False) e si testa.

Risultati. Punteggi medi: 0,16420{,}1642, poi 0,16530{,}1653 (12 volte), 0,16580{,}1658, 0,37900{,}3790, 0,91600{,}9160, 0,94380{,}9438, 0,94380{,}9438, 0,94490{,}9449, 0,94600{,}9460 per C=6,95⋅10−4C=6{,}95\cdot10^{-4}. Gli ultimi quattro sono indistinguibili (differenze 0,0020{,}002 contro deviazione standard 0,010{,}01): a parità conviene il CC più piccolo.

Grafico (scala logaritmica): (1) C≲5⋅10−7C\lesssim5\cdot10^{-7}: punteggio costante 0,1650{,}165, modello degenerato (predice una sola cifra, underfitting); (2) salita ripida tra 10−610^{-6} e 10−410^{-4}; (3) plateau ≈0,945\approx0{,}945 per C≳10−3C\gtrsim10^{-3}. Vettori di supporto: 648 con il CC migliore, 436 con C=1C=1.

Test (450 immagini): accuratezza 0,9290{,}929 (418418 giuste), meno della CV (0,9460{,}946) perché i test sono diversi (ultimo quarto non rimescolato). Cifra 3: recall 0,770{,}77 (TP=36TP=36, FN=11FN=11), precision 0,950{,}95 (FP=2FP=2), F1=0,847F_1=0{,}847; cifra 8: precision 0,780{,}78.

python
svc = svm.SVC(kernel="linear", C=best_C).fit(X_train, y_train)
print(metrics.classification_report(y_test, svc.predict(X_test)))

Errori tipici: scegliere CC sul test set (leakage); dimenticare la scala logaritmica del grafico; confrontare l'accuratezza di CV con quella del test come se fossero la stessa misura; scegliere sempre il CC massimo.

Esercizi su questo argomento

Teoria collegata