• 10 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 06/02/2024

TP – Sélectionnez le nombre de voisins dans un kNN

Jusqu’à présent, nous avons parlé de l’évaluation de la performance d’un seul modèle. Mais, généralement, nous voulons essayer plusieurs modèles pour choisir le plus performant, et ensuite donner sa performance.

Sélection de modèle

Pour faire ça correctement, il faut séparer les données en trois parties : un jeu d’entraînement, un jeu de validation et un jeu de test. Le jeu d’entraînement sert à entraîner divers modèles. Le jeu de validation sert à sélectionner un modèle : on choisit celui qui a la meilleure performance sur ce jeu. Enfin, le jeu de test sert à estimer la performance en généralisation du modèle.

Alternativement, au lieu de créer un jeu d’entraînement et un jeu de validation, on peut séparer les données uniquement en deux parties : un jeu d’entraînement et un jeu de test. On fera ensuite une validation croisée sur le jeu d’entraînement. Cela nous permet de choisir un modèle (celui qui a la meilleure performance), que l’on va ensuite entraîner sur la totalité du jeu d’entraînement, puis tester sur le jeu de test. C’est cette performance finale qui est la meilleure approximation de la performance que le modèle pourra atteindre sur de nouvelles données.

On sépare le jeu de données en un jeu d’entraînement et un jeu de test. On évalue chaque modèle en validation croisée sur le jeu d’entraînement pour choisir le meilleur, que l’on applique ensuite au jeu de test.
On sépare le jeu de données en un jeu d’entraînement et un jeu de test. On évalue chaque modèle en validation croisée sur le jeu d’entraînement pour choisir le meilleur, que l’on applique ensuite au jeu de test.

Recherche sur grille (grid search)

Un cas particulier de la sélection de modèle est celui de la sélection du ou des hyperparamètres d’un même algorithme : par exemple, le nombre k de voisins dans un kNN. Dans ce cas, on crée une grille d’hyperparamètres, contenant plusieurs valeurs possibles pour chacun d’entre eux, que l’on explore pour tester toutes les combinaisons possibles. C’est ce que l’on appelle une « grid search » en anglais.

Une grille de recherche d’hyperparamètres pour deux paramètres, chacun prenant 4 valeurs.
Une grille de recherche d’hyperparamètres pour deux paramètres, chacun prenant 4 valeurs

Sélectionnez le nombre de voisins dans un kNN sur un jeu de données

Mettons donc tout cela en pratique !

Les données

Nous allons travailler avec un jeu de données qui contient des informations physico-chimiques de vins portugais (vinho verde), ainsi que leur qualité telle que notée par des humains.

Ce jeu de données est disponible dans les archives UCI (un des répertoires les plus connus de problèmes de machine learning).

Vous pouvez le télécharger ici.

Commençons par regarder les données :

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
data = pd.read_csv('winequality-white.csv', sep=";")
print(data.head())

Nos données contiennent 11 colonnes, 10 qui correspondent à divers indicateurs physico-chimiques et 1 qui est la qualité du vin.

Nous allons extraire deux arrays numpy de ces données, un qui contient les points et l’autre qui contient les étiquettes

X = data[data.columns[:-1]].values
y = data['quality'].values

On peut maintenant afficher un histogramme pour chacune de nos variables :

fig = plt.figure(figsize=(16, 12))
for feat_idx in range(X.shape[1]):
    ax = fig.add_subplot(3,4, (feat_idx+1))
    h = ax.hist(X[:, feat_idx], bins=50, color='steelblue', density=True, edgecolor='none')
    ax.set_title(data.columns[feat_idx], fontsize=14)

 

On remarque en particulier que ces variables prennent des valeurs dans des ensembles différents. Par exemple, “sulphates” varie de 0 à 1 tandis que “total sulfur dioxide” varie de 0 à 440. Il va donc nous falloir standardiser les données pour que la deuxième ne domine pas complètement la première.

Sélection de modèle

Nous allons commencer par transformer ce problème en un problème de classification : il s’agira de séparer les bons vins des vins médiocres :

y_class = np.where(y<6, 0, 1)

Séparons nos données en un jeu d’entraînement et un jeu de test. Le jeu de test contiendra 30% des données.

from sklearn import model_selection
X_train, X_test, y_train, y_test = \
	model_selection.train_test_split(X, y_class,
                                	test_size=0.3 # 30% des données dans le jeu de test
                                	)

Nous pouvons maintenant standardiser les données d’entraînement et appliquer la même transformation aux données de test :

from sklearn import preprocessing
std_scale = preprocessing.StandardScaler().fit(X_train)
X_train_std = std_scale.transform(X_train)
X_test_std = std_scale.transform(X_test)

On peut visualiser de nouveau les données pour vérifier que les différentes variables prennent des valeurs qui ont maintenant des ordres de grandewur similaires.

fig = plt.figure(figsize=(16, 12))
for feat_idx in range(X_train_std.shape[1]):
    ax = fig.add_subplot(3,4, (feat_idx+1))
    h = ax.hist(X_train_std[:, feat_idx], bins=50, color = 'steelblue', density=True, edgecolor='none')
    ax.set_title(data.columns[feat_idx], fontsize=14)

Nous allons maintenant utiliser la méthode "GridSearchCV" pour faire une validation croisée du paramètre k d’un kNN (le nombre de plus proches voisins) sur le jeu d’entraînement :

from sklearn import neighbors, metrics

# Fixer les valeurs des hyperparamètres à tester
param_grid = {'n_neighbors':[3, 5, 7, 9, 11, 13, 15]}

# Choisir un score à optimiser, ici l'accuracy (proportion de prédictions correctes)
score = 'accuracy'

# Créer un classifieur kNN avec recherche d'hyperparamètre par validation croisée
clf = model_selection.GridSearchCV(
    neighbors.KNeighborsClassifier(), # un classifieur kNN
    param_grid,     # hyperparamètres à tester
    cv=5,           # nombre de folds de validation croisée
    scoring=score   # score à optimiser
)

# Optimiser ce classifieur sur le jeu d'entraînement
clf.fit(X_train_std, y_train)

# Afficher le(s) hyperparamètre(s) optimaux
print("Meilleur(s) hyperparamètre(s) sur le jeu d'entraînement:")
print(clf.best_params_)

# Afficher les performances correspondantes
print("Résultats de la validation croisée :")
for mean, std, params in zip(
        clf.cv_results_['mean_test_score'], # score moyen
        clf.cv_results_['std_test_score'],  # écart-type du score
        clf.cv_results_['params']           # valeur de l'hyperparamètre
    ):

    print("{} = {:.3f} (+/-{:.03f}) for {}".format(
        score,
        mean,
        std*2,
        params
    ) )

La meilleure performance (~0.757) est ici atteinte avec 7 voisins.

Nous pouvons maintenant regarder la performance sur le jeu de test. GridSearchCV a automatiquement ré-entraîné le meilleur modèle sur l’intégralité du jeu d’entraînement,

y_pred = clf.predict(X_test_std)
print("\nSur le jeu de test : {:.3f}".format(metrics.accuracy_score(y_test, y_pred)))

J’obtiens ici une performance de 0.759.

Alternatives à la recherche exhaustive

La méthode de validation croisée que nous venons d’étudier est une méthode de recherche exhaustive (ou encore de recherche par force brute, ou “brute force” en anglais) : on définit des valeurs que peuvent prendre les hyperparamètres, puis on teste toutes ces valeurs pour choisir celles qui nous conviennent le mieux.

Pour certains algorithmes d’apprentissage, il est possible de déterminer efficacement les modèles obtenus pour toutes les valeurs d’un certain hyperparamètre comprises dans une fourchette donnée, sans recalculer le modèle à chaque fois ; on pourra donc faire une recherche certes toujours exhaustive, mais plus efficace. C’est le cas des approches de régression linéaire régularisée.

Dans certains cas, la théorie de l’information nous permet de calculer la valeur optimale d’un hyperparamètre grâce à une formule fermée, c’est-à-dire explicitement. L’idée générale de ces approches est d’estimer la perte d'information subie lorsque l’on utilise un certain modèle pour représenter le processus qui a généré les données ; leur but est de choisir le modèle qui donnera lieu à la plus petite perte d’information possible.

Résumé

  • Pour sélectionner un modèle, on compare les performances en validation croiséesur un jeu d’entraînement.

  • Pour sélectionner les valeurs des hyperparamètres d’un algorithme donné, on fait une grid search, dans laquelle on essaie de couvrir l’espace des valeurs pertinentes de ces hyperparamètres.

  • On peut implémenter cela très simplement en Python avec sklearn.model_selection.GridSearchCV.

Exemple de certificat de réussite
Exemple de certificat de réussite