Classificateurs

Dans cette feuille, nous allons explorer l’utilisation de plusieurs classificateurs sur l’exemple des pommes et des bananes. Vous pourrez ensuite les essayer sur votre jeu de données.

Commencons par charger les utilitaires et autres librairies:

import os, re
from glob import glob as ls
import numpy as np                    # Matrix algebra library
import pandas as pd                   # Data table (DataFrame) library
import seaborn as sns; sns.set()      # Graphs and visualization library
from PIL import Image                 # Image processing library
import matplotlib.pyplot as plt       # Library to make graphs 
# Configuration intégration dans Jupyter
%matplotlib inline

## les utilitaires
%load_ext autoreload
%autoreload 2
from utilities import *

## les jeux de données
from intro_science_donnees import data

Chargement et préparation des données

On charge le jeu de données prétraité (attributs rougeur et élongation et classes des fruits), tel que fournis en semaine 3:

df = pd.read_csv("attributs.csv", index_col=0)
# standardisation
dfstd =  (df - df.mean()) / df.std()
dfstd['class'] = df['class']

On partitionne le jeu de données en ensemble de test et d’entraînement:

X = dfstd[['redness', 'elongation']]
Y = dfstd['class']
#partition des images
train_index, test_index = split_data(X, Y, seed=0)

#partition de la table des attributs
Xtrain = X.iloc[train_index]
Xtest = X.iloc[test_index]
#partition de la table des étiquettes
Ytrain = Y.iloc[train_index]
Ytest = Y.iloc[test_index]

Classificateurs basés sur les exemples (examples-based)

Nous allons maintenant voir comment appliquer des classificateurs fournis par la librairie scikit-learn. Commençons par le classificateur plus proche voisin déjà vu en semaines 3 et 4.

KNN : \(k\)-plus proche voisins

from sklearn.neighbors import KNeighborsClassifier

#définition du classificateur, ici on l'appelle classifier
# on choisit k=1
classifier = KNeighborsClassifier(n_neighbors=1)
# on l'ajuste aux données d'entrsainement
classifier.fit(Xtrain, Ytrain) 
# on calcule ensuite le taux d'erreur lors de l'entrainement et pour le test
Ytrain_predicted = classifier.predict(Xtrain)
Ytest_predicted = classifier.predict(Xtest)
# la fonction error_rate devrait etre présente dans votre utilities.py (TP3), sinon ajoutez-la
e_tr = error_rate(Ytrain, Ytrain_predicted)
e_te = error_rate(Ytest, Ytest_predicted)

print("Classificateur: 1 Neighrest Neighbor")
print("Training error:", e_tr)
print("Test error:", e_te)

Exercice : Quels sont les taux d’erreur pour l’ensemble d’entraînement et l’ensemble de test ?

BEGIN SOLUTION

0 pour l’ensemble d’entraînement et 0.2 pour l’ensemble de test

END SOLUTION

On mémorise ces taux dans une table error_rates que l’on complétera au fur et à mesure de cette feuille:

error_rates = pd.DataFrame([], columns=['entrainement', 'test'])
error_rates.loc["1 Neighrest Neighbor",:] = [e_tr, e_te]
error_rates

Fenêtres de Parzen (Parzen window ou radius neighbors)

Pour ce classificateur, on ne fixe pas le nombre de voisins mais un rayon \(r\); la classe d’un élément \(e\) est prédite par la classe majoritaire parmi les éléments de l’ensemble d’entraînement dans la sphère de centre \(e\) et de rayon \(r\).

Exercice : Complétez le code ci-dessous:

from sklearn.neighbors import RadiusNeighborsClassifier
classifier = RadiusNeighborsClassifier(radius=1.0)

# on l'ajuste aux données d'entrainement

### BEGIN SOLUTION
classifier.fit(Xtrain, Ytrain) 
### END SOLUTION

# on calcule ensuite le taux d'erreur lors de l'entrainement et pour le test

### BEGIN SOLUTION
Ytrain_predicted = classifier.predict(Xtrain)
Ytest_predicted = classifier.predict(Xtest)
### END SOLUTION

# on calcule les taux d'erreurs

### BEGIN SOLUTION
e_tr = error_rate(Ytrain, Ytrain_predicted)
e_te = error_rate(Ytest, Ytest_predicted)
### END SOLUTION

print("Classificateur: Parzen Window")
print("Training error:", e_tr)
print("Test error:", e_te)

Exercice : Complétez la table error_rates avec ce modèle, en rajoutant une ligne d’index Parzen Window.

Indication : Utiliser .loc comme ci-dessus.

### BEGIN SOLUTION
error_rates.loc["Parzen Window", :] = [e_tr, e_te]
### END SOLUTION 
error_rates
assert isinstance(error_rates, pd.DataFrame)
assert list(error_rates.columns) == ['entrainement', 'test']
assert list(error_rates.index) == ['1 Neighrest Neighbor', 'Parzen Window']
assert (0 <= error_rates).all(axis=None), "Les taux d'erreurs doivent être positifs"
assert (error_rates <= 1).all(axis=None), "Les taux d'erreurs doivent être inférieur à 1"

Exercice \(\clubsuit\) : Faites varier le rayon \(r\). Comment le taux d’erreur varie-t-il ? Vous pouvez ajouter des modèles à la table error_rates s’ils vous semblent pertinents

BEGIN SOLUTION

Si on réduit le rayon on risque de n’avoir aucun voisin autour de certains éléments, et donc d’être incapable de prédire leur classe. Réciproquement, si on augmente trop le rayon, ce sera la classe majoritaire de l’ensemble d’entrainement qui primera. Ici, ce sera une chance sur deux d’avoir une pomme ou une banane. Plus on augmente le rayon rayon, plus le taux d’erreur sera proche de 0.5 (le hasard).

END SOLUTION

Classificateurs basés sur les attributs (feature based)

Régression linéaire

Exercice : Pourquoi ne peut-on pas appliquer la méthode de régression linéaire pour classer nos pommes et nos bananes ?

BEGIN SOLUTION

Dans une régression linéaire, l’étiquette est une combinaison linéaire des attributs, donc une donnée continue. Dans notre problème de classification, l’étiquette est une donnée discrète: les classes que l’on cherche.

END SOLUTION

Arbres de décision

Les arbres de décison correspondent à des modèles avec des décisions imbriquées où chaque noeud teste une condition sur une variable. Les étiquettes se trouvent aux feuilles.

Exercice : Complétez le code ci-dessous.

from sklearn import tree

classifier = tree.DecisionTreeClassifier()
# on l'ajuste aux données d'entrainement

### BEGIN SOLUTION
classifier.fit(Xtrain, Ytrain) 
### END SOLUTION

# on calcule ensuite le taux d'erreur lors de l'entrainement et pour le test

### BEGIN SOLUTION
Ytrain_predicted = classifier.predict(Xtrain)
Ytest_predicted = classifier.predict(Xtest)
### END SOLUTION

# on calcule les taux d'erreurs

### BEGIN SOLUTION
e_tr = error_rate(Ytrain, Ytrain_predicted)
e_te = error_rate(Ytest, Ytest_predicted)
### END SOLUTION


print("Classificateur: Arbre de decision")
print("Training error:", e_tr)
print("Test error:", e_te)

Exercice : Complétez la table error_rates avec ce modèle.

### BEGIN SOLUTION
error_rates.loc['Decision Tree', :] = [e_tr, e_te]
### END SOLUTION 
print(error_rates)
assert isinstance(error_rates, pd.DataFrame)
assert list(error_rates.columns) == ['entrainement', 'test']
assert error_rates.shape[0] >= 3
assert (0 <= error_rates).all(axis=None), "Les taux d'erreurs doivent être positifs"
assert (error_rates <= 1).all(axis=None), "Les taux d'erreurs doivent être inférieur à 1"

Exercice : Représentez l’arbre de décision comme vu lors du CM5.

import matplotlib.pyplot as plt

plt.figure(figsize=(12,12)) 
### BEGIN SOLUTION
tree.plot_tree(classifier, fontsize=10) 
### END SOLUTION
plt.show()

Exercice : Interprétez cette figure.

BEGIN SOLUTION

Il n’y a qu’un seul critère de séparation des données, selon que l’attribut X[1] – l’élongation après standardisation – est supérieur ou inférieur à 0.289.

À gauche, on trouve ceux qui sont inférieurs, c’est-à-dire les pommes; à trouve les bananes.

Le critère de gini mesure l’impureté du jeu de données. À la fin on a bien tout classé donc on a des gini à 0. Au début on n’a rien classer donc on a le gini max à 0.5 (50% mal classé). De part et d’autres de l’arbre on classe cinq fruits; cela aurait pu être une valeur différente.

On est sûrement dans une situation de surapprentissage (overfitting) sur l’ensemble d’entraînement.

END SOLUTION

Perceptron

Le perceptron est un réseau de neurones artificiels à une seule couche et donc avec une capacité de modélisation limitée; pour le problème qui nous intéresse cela est suffisant. Pour plus de détails, revenez au cours

Exercice : Complétez le code ci-dessous, où l’on définit un modèle de type Perceptron avec comme paramètres \(10^{-3}\) pour la tolérence, \(36\) pour l’état aléatoire (random state) et 100 époques (max_iter)

from sklearn.linear_model import Perceptron

# définition du modèle de classificateur
### BEGIN SOLUTION
classifier = Perceptron(tol=1e-3, random_state=36, max_iter=100)
### END SOLUTION
# on l'ajuste aux données d'entrainement

### BEGIN SOLUTION
classifier.fit(Xtrain, Ytrain) 
### END SOLUTION

# on calcule ensuite le taux d'erreur lors de l'entrainement et pour le test

### BEGIN SOLUTION
Ytrain_predicted = classifier.predict(Xtrain)
Ytest_predicted = classifier.predict(Xtest)
### END SOLUTION

# on calcule les taux d'erreurs

### BEGIN SOLUTION
e_tr = error_rate(Ytrain, Ytrain_predicted)
e_te = error_rate(Ytest, Ytest_predicted)
### END SOLUTION
print("Classificateur: Perceptron")
print("Training error:", e_tr)
print("Test error:", e_te)

Exercice : Lisez la documentation de Perceptron. À quoi correspond le paramètre random_state ?

BEGIN SOLUTION

Ce paramètre permet d’initialiser le générateur pseudo aléatoire, pour que les valeurs obtenues soient reproductible entre la correction et le résultat des étudiants

END SOLUTION

### BEGIN SOLUTION
Perceptron?
### END SOLUTION

Exercice : Complétez la table error_rates avec ce modèle.

### BEGIN SOLUTION
error_rates.loc["Perceptron", :] = [e_tr, e_te]
### END SOLUTION 
error_rates
assert error_rates.shape[0] >= 4
assert error_rates.shape[1] == 2

\(\clubsuit\) Points bonus : construction du classificateur «une règle» (One Rule)

Faites cette partie ou bien passez directement à la conclusion.

En complétant le code ci-dessous, créez votre premier classificateur qui:

  • sélectionne le « bon » attribut (rougeur ou élongation pour le problème des pommes/bananes), appelé \(G\) (pour good). C’est l’attribut qui est le plus corrélé (en valeur absolue, toujours !) aux valeurs cibles \(y = ± 1\);

  • détermine une valeur seuil (threshold);

  • utilise l’attribut G et le seuil pour prédire la classe des éléments.

Un canevas de la classe OneRule est fournit dans la classe utilities.py; vous pouvez le compléter ou bien la programmer entièrement vous même.

Ce classificateur est-il basé sur les attributs ou sur les exemples?

# Use this code to test your classifier
classifier = OneRule()
classifier.fit(Xtrain, Ytrain) 
Ytrain_predicted = classifier.predict(Xtrain)
Ytest_predicted = classifier.predict(Xtest)
e_tr = error_rate(Ytrain, Ytrain_predicted)
e_te = error_rate(Ytest, Ytest_predicted)
print("Classificateur: One rule")
print("Training error:", e_tr)
print("Test error:", e_te)

Exercice : Complétez la table error_rates avec ce modèle.

### BEGIN SOLUTION
error_rates = error_rates.append(pd.DataFrame(np.array([[e_tr,e_te]]),
                   columns=['entrainement', 'test'], index =["One Rule"]))
### END SOLUTION 
print(error_rates)
assert error_rates.shape[0] >= 5
assert error_rates.shape[1] == 2
# On charge les images
dataset_dir = os.path.join(data.dir, 'ApplesAndBananasSimple')
images = load_images(dataset_dir, "*.png")
# This is what you get as decision boundary.
# The training examples are shown as white circles and the test examples are blue squares.
make_scatter_plot(X, images.apply(transparent_background_filter),
                  [], test_index, 
                  predicted_labels='GroundTruth',
                  feat = classifier.attribute, theta=classifier.theta, axis='square')

Comparez avec ce que vous auriez obtenu en utilisant les 2 attributs avec le même poids lors de la décision de classe.

make_scatter_plot(X, images.apply(transparent_background_filter),
                  [], test_index, 
                  predicted_labels='GroundTruth',
                  show_diag=True, axis='square')

Conclusion

Exercice : Comparez les taux d’erreur et d’entraînement de vos différents classificateurs pour le problème des pommes et des bananes.

BEGIN SOLUTION

TODO

Le perceptron est celui qui a la plus faible erreur.

END SOLUTION

Dans cette feuille vous avez découvert comment utiliser un certain nombre de classificateurs, voire comment implanter le vôtre, et comment jouer sur les paramètres de ces classificateurs (par exemple la tolérance du perceptron ou le nombre de voisins du KNN) pour essayer d’optimiser leur performance.

Mettez à jour votre rapport et déposez votre travail.

Vous êtes maintenant prêts pour revenir à votre analyse de données pour mettre en œuvre ces classificateurs sur votre jeu de données.

Dans le projet 1, on vous demande de choisir un seul classificateur, ainsi que ses paramètres. Nous verrons dans la seconde partie de l’UE comment comparer systématiquement les classificateurs.