Principe fondamental des SVM
Une machine à vecteurs de support cherche l'hyperplan qui sépare le mieux les classes en maximisant la marge entre les échantillons les plus proches de chaque classe, appelés vecteurs de support. Lorsque les données ne sont pas linéairement séparables, une fonction noyau projette implicitement les données dans un espace de plus grande dimension où une séparation linéaire devient possible.
Noyaux disponibles
- Linéaire (
linear) : recherche d'un hyperplan dans l'espace original. - Polynomial (
poly) : ajoute des combinaisons polynomiales des caractéristiques, par exemple :
(x1 + x2)2 = x12 + 2x1x2 + x22 - Base radiale (
rbf) : utilise une fonction gaussienne pour capturer des frontières non linéaires.
Classification avec un noyau linéaire
L'exemple suivant charge un fichier de points 2D, entraîne un classificateur SVM linéaire et trace la frontière de décision.
import numpy as np
from sklearn import svm, model_selection as ms, metrics
import matplotlib.pyplot as plt
donnees = np.loadtxt('../data/multiple2.txt', delimiter=',', dtype='f8')
X = donnees[:, :-1]
y = donnees[:, -1].astype(int)
X_train, X_test, y_train, y_test = ms.train_test_split(
X, y, test_size=0.25, random_state=5
)
classifieur = svm.SVC(kernel='linear')
classifieur.fit(X_train, y_train)
resolution = 500
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
grille_x, grille_y = np.meshgrid(
np.linspace(x_min, x_max, resolution),
np.linspace(y_min, y_max, resolution)
)
points_plats = np.c_[grille_x.ravel(), grille_y.ravel()]
grille_pred = classifieur.predict(points_plats).reshape(grille_x.shape)
print(metrics.classification_report(y_test, classifieur.predict(X_test)))
plt.figure('SVM linéaire')
plt.pcolormesh(grille_x, grille_y, grille_pred, cmap='gray', shading='auto')
plt.scatter(X_test[:, 0], X_test[:, 1], c=y_test, cmap='brg', s=80)
plt.title('Classification SVM linéaire')
plt.show()
Noyaux polynoimal et RBF
Le noyau polynomial modifie la représentation des données en aojutant des puissances, tandis que le noyau RBF mesure la similarité via une fonction gaussienne.
# Noyau polynomial de degré 3
modele_poly = svm.SVC(kernel='poly', degree=3)
modele_poly.fit(X_train, y_train)
# Noyau RBF avec paramètres de régularisation
modele_rbf = svm.SVC(kernel='rbf', C=600, gamma=0.01)
modele_rbf.fit(X_train, y_train)
Équilibrage des classes
Quand les classes sont déséquilibrées, l'option class_weight='balanced' attribue un poids plus important aux classes minoritaires, améliorant ainsi la généralisation.
modele_eq = svm.SVC(kernel='linear', class_weight='balanced')
modele_eq.fit(X_train, y_train)
Probabilités de prédiction
En activant probability=True, le modèle calcule la probabilité d'appartenance à chaque classe. Plus un point est éloigné de la frontière, plus la probabilité associée est élevée.
modele_prob = svm.SVC(kernel='rbf', C=600, gamma=0.01, probability=True)
modele_prob.fit(X_train, y_train)
echantillons = np.array([[2, 1.5], [8, 9], [4.8, 5.2], [4, 4]])
predictions = modele_prob.predict(echantillons)
probabilites = modele_prob.predict_proba(echantillons)
print(predictions)
print(probabilites)
Optimisation des hyperparamètres par grille
La classe GridSearchCV explore plusieurs combinaisons d'hyperparamètres et retient celle qui obtient la meilleure performance moyenne en validation croisée.
from sklearn import model_selection as ms
grille_params = [
{'kernel': ['linear'], 'C': [1, 10, 100, 1000]},
{'kernel': ['poly'], 'C': [1], 'degree': [2, 3]},
{'kernel': ['rbf'], 'C': [1, 10, 100, 1000], 'gamma': [1, 0.1, 0.01, 0.001]}
]
recherche = ms.GridSearchCV(
svm.SVC(probability=True),
grille_params,
cv=5
)
recherche.fit(X_train, y_train)
print('Meilleurs hyperparamètres :', recherche.best_params_)
print('Meilleur score :', recherche.best_score_)
meilleur_modele = recherche.best_estimator_
print(metrics.classification_report(y_test, meilleur_modele.predict(X_test)))
Application : prédiction d'événements
L'exemple suivant illustre une chaîne complète de traitement icnluant l'encodage de variables catégorielles avant l'application d'un SVM.
import numpy as np
from sklearn import preprocessing as pp, model_selection as ms
from sklearn import svm, metrics
class EncodeurNumerique:
def fit_transform(self, valeurs):
return valeurs.astype('i4')
def transform(self, valeurs):
return valeurs.astype('i4')
def inverse_transform(self, valeurs):
return valeurs.astype('U15')
def charger_et_encoder(chemin):
brut = []
with open(chemin, 'r') as fichier:
for ligne in fichier:
brut.append(ligne.strip().split(','))
data = np.array(brut)
data = np.delete(data, 1, axis=1)
nb_colonnes = data.shape[1]
caracteristiques, cibles = [], []
encodeurs = []
for indice in range(nb_colonnes):
colonne = data[:, indice]
if colonne[0].isdigit():
encodeur = EncodeurNumerique()
else:
encodeur = pp.LabelEncoder()
if indice != nb_colonnes - 1:
caracteristiques.append(encodeur.fit_transform(colonne))
else:
cibles = encodeur.fit_transform(colonne)
encodeurs.append(encodeur)
X = np.array(caracteristiques).T
return X, cibles, encodeurs
X, y, encodeurs = charger_et_encoder('../data/events.txt')
X_train, X_test, y_train, y_test = ms.train_test_split(
X, y, test_size=0.25, random_state=7
)
modele_evt = svm.SVC(kernel='rbf', class_weight='balanced')
modele_evt.fit(X_train, y_train)
print(metrics.classification_report(y_test, modele_evt.predict(X_test)))
# Prédiction pour un nouveau créneau
nouveau = np.array([['Tuesday', '13:30:00', '21', '23']]).T
X_nouveau = []
for i in range(len(nouveau)):
X_nouveau.append(encodeurs[i].transform(nouveau[i]))
X_nouveau = np.array(X_nouveau).T
prediction = modele_evt.predict(X_nouveau)
print(encodeurs[-1].inverse_transform(prediction))
Application : régression de trafic avec SVR
Pour prédire une valeur continue, scikit-learn propose la classe SVR, qui fonctionne sur le même principe que les SVM de classification.
import numpy as np
from sklearn import preprocessing as pp, model_selection as ms
from sklearn import svm, metrics
class EncodeurNumerique:
def fit_transform(self, valeurs):
return valeurs.astype(int)
def transform(self, valeurs):
return valeurs.astype(int)
def inverse_transform(self, valeurs):
return valeurs.astype(str)
# Chargement et transposition
donnees = np.loadtxt('../data/traffic.txt', delimiter=',', dtype='U20').T
encodeurs, X_liste = [], []
for ligne in range(len(donnees)):
if donnees[ligne][0].isdigit():
encodeur = EncodeurNumerique()
else:
encodeur = pp.LabelEncoder()
if ligne != len(donnees) - 1:
X_liste.append(encodeur.fit_transform(donnees[ligne]))
else:
y = encodeur.fit_transform(donnees[ligne])
encodeurs.append(encodeur)
X = np.array(X_liste).T
X_train, X_test, y_train, y_test = ms.train_test_split(
X, y, test_size=0.25, random_state=5
)
regresseur = svm.SVR(kernel='rbf', C=10, epsilon=0.2)
regresseur.fit(X_train, y_train)
pred = regresseur.predict(X_test)
print('R2 :', metrics.r2_score(y_test, pred))
# Prédiction sur un nouvel exemple
exemple = np.array([['Tuesday', '13:35', 'San Francisco', 'yes']]).T
X_ex = []
for i in range(len(exemple)):
X_ex.append(encodeurs[i].transform(exemple[i]))
X_ex = np.array(X_ex).T
print('Flux prédit :', int(regresseur.predict(X_ex)))