Commit 0c755731 authored by François Bouchet's avatar François Bouchet
Browse files

Ajout cours régression & exercices associés

parent 9e9b418f
This diff is collapsed.
%% Cell type:markdown id: tags:
# Data Mining & Visualisation (DaMiVis)
# Séance 7 - Classification
# Séance 7&8 - Classification
# Exercices
*(NOM Prénom -- A EDITER)*
%% Cell type:code id: tags:
``` python
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
```
%% Cell type:markdown id: tags:
### Arbre de décision sur iris
Entraîner un arbre de décision sur les données Iris et évaluer sa performance en fonction des différentes métriques d'évaluation présentées en séparant bien un ensemble d'entraînement (90% des données) et un ensemble de test (10%).
%% Cell type:code id: tags:
``` python
```
%% Cell type:markdown id: tags:
### Classifieur sur Exam2017
Reprendre le jeu de données Exam2017 et entraîner un classifieur (au choix) pour prédire si un étudiant va valider ou non le cours. On prendra soin de ne sélectionner que les attributs pertinents pour cette tâche. On utilisera une validation croisée.
%% Cell type:code id: tags:
``` python
```
%% Cell type:markdown id: tags:
### Classifieur sur "Drinks by Country"
Reprendre le jeu de données "drinks by country". Entraîner plusieurs classifieurs permettant de prédire le continent d'un pays en fonction de sa consommation de bière, vin et spiritueux. Estimer la performance de chacun des classifieurs à l'aide du kappa de Cohen. Tester notamment différentes valeurs d'hyperparamètres pour chacun.
Quel classifieur donne le meilleur résultat ? Quel est le kappa associé ?
%% Cell type:code id: tags:
``` python
```
%% Cell type:markdown id: tags:
### Régresseur sur données météorologiques
* Récupérer le jeu de données météorologique dans la ville de Szeged (Hongrie) entre 2006 et 2010 (source : https://www.kaggle.com/budincsevity/szeged-weather) qui est dans le sous-dossier data de Gitlab
* Charger le jeu de données
* Pouvez-vous prédire la température apparente en fonction de l'humidité ?
%% Cell type:code id: tags:
``` python
```
......
%% Cell type:markdown id: tags:
# Data Mining & Visualisation (DaMiVis)
# Séances 7&8 - Apprentissage supervisé
*(François Bouchet - francois.bouchet@lip6.fr)*
%% Cell type:code id: tags:
``` python
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
```
%% Cell type:markdown id: tags:
## Rappels
**Objectif :**
1) Trouver un modèle prédisant un aspect des données (*variable prédite* ou *classe*) à partir d'une combinaison d'autres aspects (*variables prédictives*, *attributs* ou *features*)
2) Utiliser ce modèle pour prédire la classe de nouvelles données
**Familles :**
1) Classifieur : variable prédite = binaire ou catégorielle
2) Régresseur : variable prédite = valeur continue
%% Cell type:markdown id: tags:
## Méthodologie de construction d'un modèle de prédiction
1. Choisir une méthode de classification
2. Choisir les hyperparamètres du modèle
3. Mettre en forme les données
4. Construire un modèle
5. Evaluer le modèle de prédiction
6. Répéter les étapes jusqu'à satisfaction
%% Cell type:markdown id: tags:
### Présentation du jeu de données d'exemple
Quelle soit la méthode de prédiction choisie, il faut mettre en forme ses données sous forme d'une table (tableau `array` de numpy ou ou `DataFrame` de pandas) dans lequel chaque colonne représente un *attribut* (feature) et chaque ligne une *instance* (sample). Le nombre d'attributs est `n_features` et le nombre d'instances est `n_samples`.
Pour illustrer la méthodologie générale, nous allons travailler sur un jeu de données classique : [Iris](https://en.wikipedia.org/wiki/Iris_flower_data_set), qui présente pour différentes fleurs présente la largeur et la longueur des pétales et des sépales.
<img src="img/petal-sepal.jpg" width="300"/>
%% Cell type:code id: tags:
``` python
# chargement du jeu de données (inclus dans la bibliothèque Seaborn)
iris = sns.load_dataset('iris')
iris.head()
```
%% Output
sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa
%% Cell type:markdown id: tags:
On peut visualiser rapidement les données en croisant les variables deux à deux grâce à la fonction `pairplot` de la bibliothèque `seaborn` :
%% Cell type:code id: tags:
``` python
sns.set()
sns.pairplot(iris, hue="species", height=1.5)
```
%% Output
<seaborn.axisgrid.PairGrid at 0x18a4a14d1d0>
%% Cell type:markdown id: tags:
### Choix d'une méthode de classification
Différents algorithmes ont différentes contraintes et possibilités de "découper l'espace". En pratique, on essaie souvent différents algorithmes de différentes familles pour estimer le plus efficace pour une tâche donnée. Nous allons pour l'instant nous concentrer sur un exemple simple de classifieur : **les K plus proches voisins** (KNN).
Le principe est simple : pour classer un nouveau point, on recherche dans les données les K points les plus proches et on regarde la classe de la majorité de ces points. Par exemple, avec un modèle 3NN, si 2 voisins sont rouges et un voisin est bleu, alors le nouveau point sera classé en rouge.
Nous verrons plus tard d'autres exemples plus complexes, et potentiellement plus puissants.
%% Cell type:markdown id: tags:
Avec Scikit-learn, choisir un modèle consiste à trouver la classe correspondante dans la bibliothèque. Pour les K plus proches voisins il s'agit de la classe : `sklearn.neighbors.KNeighborsClassifier`
%% Cell type:code id: tags:
``` python
from sklearn.neighbors import KNeighborsClassifier
model = KNeighborsClassifier(n_neighbors=1)
```
%% Cell type:markdown id: tags:
### Choix des hyperparamètres
Une fois le modèle choisi, il faut choisir les paramètres associés à celui-ci (on parle souvent d'`hyperparamètres` pour les distinguer des paramètres des données). Par exemple, pour les K plus proches voisins, 2 questions se posent :
* combien de voisins considère-t-on ? (i.e. la valeur de K)
* comment mesurer la distance entre deux points ? (distance Euclidienne ou autre ? Par défaut, KNN utilise la distance de Minkowski définie en dimension $n$ comme $d(x,y) = (\sum_{i=1}^{n}{\left|x_i - y_i\right|}^p)^{1/p}$, équivalent à la distance euclidienne pour $p=2$)
Selon les modèles, ce nombre d'hyperparamètres peut être plus ou moins élevé, avec plus ou moins d'options possibles.
%% Cell type:markdown id: tags:
Avec Scikit-learn, choisir des hyperparamètres consiste à passer des valeurs lors de la création du modèle (`n_neighbors=1` ci-dessus).
%% Cell type:markdown id: tags:
**Attention :** le modèle est différent d'une instance du modèle. Pour l'instant notre modèle n'a pas encore été appliqué sur les données.
%% Cell type:markdown id: tags:
### Mise en forme des données
Par convention, on stocke souvent cette matrice d'attributs/instances dans une variable `X`.
En plus de cette matrice, il est nécessaire de stocker dans une autre structure (tableau `array` de numpy à 1 dimension ou `Series` de pandas) l'étiquette, c'est-à-dire la *classe* que l'on souhaite prédire. Ici, il s'agit de la colonne `species` correspondant à l'espèce d'iris correspondante. Ces étiquettes sont souvent par convention stockées dans une variable `y` : elles sont le *vecteur cible* de la prédiction.
En résumé :
<img src="img/data-samples-features.png" width="400" />
%% Cell type:markdown id: tags:
Dans le cas du jeu de données Iris :
%% Cell type:code id: tags:
``` python
# création de la matrice d'attributs
X_iris = iris.drop('species', axis=1)
# création du vecteur cible (étiquettes)
y_iris = iris['species']
```
%% Cell type:markdown id: tags:
On a donc `n_samples = 150` et `n_features = 4`
%% Cell type:code id: tags:
``` python
X_iris.shape
```
%% Output
(150, 4)
%% Cell type:markdown id: tags:
### Construction du modèle
Il est désormais temps d'appliquer le modèle aux données, c'est-à-dire d'entraîner celui-ci afin de créer une instance qui correspond au plus près aux données.
Avec Scikit-learn, ceci se fait à l'aide de la fonction `fit()` du modèle :
%% Cell type:code id: tags:
``` python
model.fit(X_iris, y_iris)
```
%% Output
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=None, n_neighbors=1, p=2,
weights='uniform')
%% Cell type:markdown id: tags:
### Evaluation du modèle
Pour évaluer le modèle, il faut maintenant l'appliquer à des données et comparer la *valeur réelle* (étiquette) et la *valeur prédite*. Se pose alors la question de la méthode d'évaluation.
Avec Scikit-learn, pour appliquer la méthode sur notre jeu de données de départ (**attention :** approche biaisée - on y reviendra), on utilise la fonction `predict()` du modèle.
%% Cell type:code id: tags:
``` python
y_iris_pred = model.predict(X_iris)
```
%% Cell type:markdown id: tags:
On peut maintenant comparer les valeurs connues aux valeurs prédites :
%% Cell type:code id: tags:
``` python
tmp = pd.DataFrame(y_iris_pred)
tmp.rename(columns={0:'species_pred'}, inplace=True)
y_iris_eval = pd.DataFrame(y_iris).join(tmp)
y_iris_eval
```
%% Output
species species_pred
0 setosa setosa
1 setosa setosa
2 setosa setosa
3 setosa setosa
4 setosa setosa
5 setosa setosa
6 setosa setosa
7 setosa setosa
8 setosa setosa
9 setosa setosa
10 setosa setosa
11 setosa setosa
12 setosa setosa
13 setosa setosa
14 setosa setosa
15 setosa setosa
16 setosa setosa
17 setosa setosa
18 setosa setosa
19 setosa setosa
20 setosa setosa
21 setosa setosa
22 setosa setosa
23 setosa setosa
24 setosa setosa
25 setosa setosa
26 setosa setosa
27 setosa setosa
28 setosa setosa
29 setosa setosa
.. ... ...
120 virginica virginica
121 virginica virginica
122 virginica virginica
123 virginica virginica
124 virginica virginica
125 virginica virginica
126 virginica virginica
127 virginica virginica
128 virginica virginica
129 virginica virginica
130 virginica virginica
131 virginica virginica
132 virginica virginica
133 virginica virginica
134 virginica virginica
135 virginica virginica
136 virginica virginica
137 virginica virginica
138 virginica virginica
139 virginica virginica
140 virginica virginica
141 virginica virginica
142 virginica virginica
143 virginica virginica
144 virginica virginica
145 virginica virginica
146 virginica virginica
147 virginica virginica
148 virginica virginica
149 virginica virginica
[150 rows x 2 columns]
%% Cell type:markdown id: tags:
Et construire une matrice de confusion :
%% Cell type:code id: tags:
``` python
from sklearn.metrics import confusion_matrix
confusion_matrix(y_iris, y_iris_pred)
```
%% Output
array([[50, 0, 0],
[ 0, 50, 0],
[ 0, 0, 50]], dtype=int64)
%% Cell type:markdown id: tags:
#### Taux de classification correct
Nombre de valeurs bien classées (diagonale de la matrice de confusion) :
%% Cell type:code id: tags:
``` python
from sklearn.metrics import accuracy_score
accuracy_score(y_iris, y_iris_pred)
```
%% Output
1.0
%% Cell type:markdown id: tags:
Pour rendre l'exemple un peu plus intéressant, on va introduire quelques modifications :
%% Cell type:code id: tags:
``` python
y_iris_pred[17] = "virginica"
y_iris_pred[42] = "versicolor"
y_iris_pred[58] = "setosa"
y_iris_pred[79] = "setosa"
y_iris_pred[111] = "virginica"
y_iris_pred[134] = "versicolor"
y_iris_pred[148] = "setosa"
```
%% Cell type:markdown id: tags:
On a alors :
%% Cell type:code id: tags:
``` python
confusion_matrix(y_iris, y_iris_pred)
```
%% Output
array([[48, 1, 1],
[ 2, 48, 0],
[ 1, 1, 48]], dtype=int64)
%% Cell type:code id: tags:
``` python
accuracy_score(y_iris, y_iris_pred)
```
%% Output
0.96
%% Cell type:markdown id: tags:
On s'intéresse souvent à des classifieurs binaires. Si l'on souhaitait faire un classifieur visant à prédire si un iris est de type "setosa" ou non, on peut remplacer "virginica" et "versicolor" par "non-setosa":
%% Cell type:code id: tags:
``` python
y_iris_seto = y_iris.replace({"virginica":"non-setosa", "versicolor":"non-setosa"})
y_iris_seto_pred = pd.Series(y_iris_pred).replace({"virginica":"non-setosa", "versicolor":"non-setosa"})
```
%% Cell type:code id: tags:
``` python
confusion_matrix(y_iris_seto, y_iris_seto_pred)
```
%% Output
array([[97, 3],
[ 2, 48]], dtype=int64)
%% Cell type:markdown id: tags:
Et le taux de prédiction correct (accuracy) :
%% Cell type:code id: tags:
``` python
accuracy_score(y_iris_seto, y_iris_seto_pred)
```
%% Output
0.9666666666666667
%% Cell type:markdown id: tags:
#### Taux de vrais positifs / vrais négatifs / faux positifs / faux négatifs
Quand on a un classifieur binaire cherchant à prédire si une instance est dans une classe $C$ :
* Les **vrais positifs** sont les instances prédites correctement comme appartenant à $C$
* Les **vrais négatifs** sont les instances prédites correctement comme n'appartenant pas à $C$
* Les **faux positifs** sont les instances prédites comme appartenant à $C$ alors qu'elles ne sont pas dans $C$
* Les **faux négatifs** sont les instances prédites comme n'appartenant pas à $C$ alors qu'elles sont dans $C$
En résumé :
<img src="img/vrai-faux-positif-negatif.jpg" width="300"/>
%% Cell type:markdown id: tags:
* Pour les VP, on parle aussi de *sensibilité*
* Pour les VN, on parle aussi de *spécificité*
* Pour les FP, on parle aussi d'erreur $\alpha$
* Pour les FN, on parle aussi d'erreur $\beta$
%% Cell type:markdown id: tags:
On peut ainsi définir 4 taux différents :
* le taux de vrais positifs : $\frac{VP}{VP+FP}$
* le taux de vrais négatifs : $\frac{VN}{VN+FN}$
* le taux de faux positifs : $\frac{FP}{VP+FP}$
* le taux de faux négatifs : $\frac{FN}{VN+FN}$
%% Cell type:markdown id: tags:
Deux matrices de confusion peuvent avoir le même taux de précision mais pas les mêmes taux d'erreur $\alpha$ et $\beta$, autrement dit, pas la même sensibilité et spécificité. Par exemple :
%% Cell type:code id: tags:
``` python
conf_mat1 = np.array([[38, 8],
[12, 42]])
conf_mat1
```
%% Output
array([[38, 8],
[12, 42]])
%% Cell type:code id: tags:
``` python
conf_mat2 = np.array([[50, 20],
[0, 30]])
conf_mat2
```
%% Output
array([[50, 20],
[ 0, 30]])
%% Cell type:markdown id: tags:
Les 2 matrices ont un taux de classification correcte de 80%
La matrice 1 est plus *spécifique* : quand on dit qu'un élément est de classe $C$, on a 83% de chances davoir raison. Par contre, on rate un certain nombre d'éléments de classe $C$.
La matrice 2 est plus *sensible* : si un élément est de classe $C$, il sera forcément détecté. Par contre des éléments qui ne sont pas dans $C$ seront aussi classés à tort dedans (seules 71% des éléments prédits dans $C$ le sont vraiment).
Selon l'objectif de la classification, on peut vouloir privilégier une erreur au détriment de l'autre (i.e. si faire une erreur $\alpha$ n'a pas le même coût que faire une erreur $\beta$).
%% Cell type:markdown id: tags:
#### Précision, rappel et Fscore
La précision et le rappel sont deux notions proches :
* $precision = \frac{VP}{VP+FP}$
* $rappel = \frac{VP}{VP+FN}$
Le Fscore est la moyenne harmonique des deux tel que :
* $Fscore = 2 * \frac{precision * rappel}{precision + rappel}$
%% Cell type:markdown id: tags:
Toutes ces valeurs peuvent se calculer directement :
%% Cell type:code id: tags:
``` python
from sklearn.metrics import precision_score, recall_score, f1_score
precision_score(y_iris_seto, y_iris_seto_pred, pos_label="setosa")
```
%% Output
0.9411764705882353
%% Cell type:code id: tags:
``` python
recall_score(y_iris_seto, y_iris_seto_pred, pos_label="setosa")
```
%% Output
0.96
%% Cell type:code id: tags:
``` python
f1_score(y_iris_seto, y_iris_seto_pred, pos_label="setosa")
```
%% Output
0.9504950495049505
%% Cell type:markdown id: tags:
#### Kappa et prise en compte de la chance
Supposons un système de classification visant à prédire le décrochage dans un cours en ligne massif dans lequel seuls 10% des élèves sont encore présents à la fin du cours. On entraîne un classifieur qui obtient un taux de classification correct de 85%. Qu'en penser ?
%% Cell type:markdown id: tags:
##### Principe
Le kappa de Cohen ($\kappa$) est une mesure de prise en compte de la chance d'avoir une classification correcte "par hasard". Il est défini comme $\kappa = \frac{P(accord\ observé) - P(accord\ attendu)}{1 - P(accord\ attendu)}$.
$\kappa \in [-1,1]$ tel que :
* $\kappa = 1$ correspond à un accord parfait
* $\kappa = -1$ correspond à un désaccord parfait
* $\kappa = 0$ correspond à un système qui ne fait pas mieux que la chance
**Attention :**
* des kappas ne sont pas comparables entre deux ensembles de données différents
* si les classes sont très inégales, le kappa sous-estime un peu la performance du système (mais mieux que le biais positif du taux de classification correct)
%% Cell type:markdown id: tags:
##### Exemple
Si on reprend la matrice `conf_mat1` précédente :
%% Cell type:code id: tags:
``` python
conf_mat1
```
%% Output
array([[38, 8],
[12, 42]])
%% Cell type:markdown id: tags:
En faisant les sommes sur chaque ligne et colonne, on a :
%% Cell type:code id: tags:
``` python
conf_mat1_som = np.array([[38, 8, 38+8],
[12, 42, 12+42],
[38+12, 42+8, 38+8+12+42]
])
conf_mat1_som
```
%% Output
array([[ 38, 8, 46],
[ 12, 42, 54],
[ 50, 50, 100]])
%% Cell type:markdown id: tags:
La probabilité d'avoir raison "par hasard" en prédisant l'appartenance à $C$ est :
%% Cell type:code id: tags:
``` python
0.46*0.50
```
%% Output
0.23
%% Cell type:markdown id: tags:
La probabilité d'avoir raison "par hasard" en prédisant la non-appartenance à $C$ est :
%% Cell type:code id: tags:
``` python
0.54*0.50
```
%% Output
0.27
%% Cell type:markdown id: tags:
Donc la probabilité d'avoir raison "par hasard" globalement est :
%% Cell type:code id: tags:
``` python
0.27+0.23
```
%% Output
0.5
%% Cell type:markdown id: tags:
Le kappa vaut donc :
%% Cell type:code id: tags:
``` python
(0.38+0.42 - 0.5)/(1-0.5)
```
%% Output
0.6000000000000001
%% Cell type:markdown id: tags:
On peut le retrouver directement à partir des données (pour l'iris setosa) avec :
%% Cell type:code id: tags:
``` python
from sklearn.metrics import cohen_kappa_score
cohen_kappa_score(y_iris_seto, y_iris_seto_pred)
```
%% Output
0.9253731343283582
%% Cell type:markdown id: tags:
### Répéter avec un autre modèle ?
Le premier critère pour choisir un modèle est bien sûr la performance de celui-ci. Néanmoins, de manière générale, il est toujours bon de tester plus d'un modèle. De plus, au-delà du choix du modèle le plus performant, parmi les questions à se poser lors du choix d'un classifieur parmi plusieurs :
* veut-on un classifieur discriminant ou probabiliste ?
* veut-on un système que l'on puisse introspecter ou non ?
* y a-t-il des contraintes de temps pour l'entraînement du modèle (si on doit l'entraîner régulièrement) ?
%% Cell type:markdown id: tags:
### Validité du modèle ?
%% Cell type:markdown id: tags:
#### Ensembles d'entraînement et de test
Ici, nous avons entraîné un modèle sur 150 fleurs puis appliqué ce même modèle sur les mêmes 150 fleurs : on ne sait donc pas comment il se comportera sur de nouvelles données !
Il est nécessaire de mettre de côté au préalable un ensemble de test qui n'aura vocation qu'à évaluer le modèle entraîné.
%% Cell type:code id: tags:
``` python
from sklearn.model_selection import train_test_split
# séparation en 2 ensembles des données
X_train, X_test, y_train, y_test = train_test_split(X_iris, y_iris, random_state=0,
train_size=0.5, test_size=0.5)
# entraîner le modèle sur le premier ensemble
model.fit(X_train, y_train)
# évaluer le modèle sur le second ensemble
y_model = model.predict(X_test)
accuracy_score(y_test, y_model)
```
%% Output
0.9066666666666666
%% Cell type:markdown id: tags:
#### Validation croisée
**Problème :** Avec la méthode précédente, on peut regretter de perdre une partie du jeu de données à des fins de test : que se passe-t-il si des données intéressantes sont exclues par hasard ? C'est d'autant plus ennuyeux si le jeu de données est petit.
%% Cell type:markdown id: tags:
##### Principe
La validation croisée permet de "faire tourner" les ensembles de tests et d'entraînement. A minima, dans le cas précédent, on pourrait tester d'inverser les 2 ensembles :
%% Cell type:code id: tags:
``` python
# entraîner le modèle sur le deuxième ensemble
model.fit(X_test, y_test)
# évaluer le modèle sur le premier ensemble
y_model = model.predict(X_train)
accuracy_score(y_train, y_model)
```
%% Output
0.96
%% Cell type:markdown id: tags:
##### Cas 1 : K-fold
On peut étendre l'idée en ayant plus de 2 sous-ensembles, pour limiter la taille de l'échantillon de test. Par exemple, en découpant en 5 l'échantillon (on parle de *5-fold cross-validation*) :
%% Cell type:code id: tags:
``` python
from sklearn.model_selection import cross_val_score
cross_val_score(model, X_iris, y_iris, cv=5)
```
%% Output
array([0.96666667, 0.96666667, 0.93333333, 0.93333333, 1. ])
%% Cell type:markdown id: tags:
##### Cas 2 : Leave-one-out
En poussant la logique jusqu'au bout, on peut découper les données en 150 et utiliser 149 instances pour entraîner le modèle et une seule pour le tester - et ce, 150 fois de suite (on parle de *leave-one-out cross-validation*) :
%% Cell type:code id: tags:
``` python
from sklearn.model_selection import cross_val_score
scores=cross_val_score(model, X_iris, y_iris, cv=50)
print(scores)
scores.mean()
```
%% Output
[1. 1. 1. 1. 1. 1.
0.66666667 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1.
1. 0.66666667 0.66666667 1. 0.66666667 1.
1. 1. 1. 1. 1. 1.
1. 1. 1. 0.33333333 1. 1.
1. 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1.
1. 1. ]
0.96
%% Cell type:markdown id: tags:
*Note :* en fait, scikit-learn n'autorise pas à avoir plus de folds que d'instances dans une classe pour éviter d'avoir des exemples non-représentés.
%% Cell type:markdown id: tags:
#### Sous-apprentissage ou surapprentissage ?
Comment améliorer un classifieur ?
* utiliser un autre classifieur plus complexe (qui s'adapte mieux aux données)
* utiliser un autre classifieur moins complexe (qui généralise mieux quand de nouvelles données un peu différentes sont vues)
* recueillir plus de données (pour avoir des exemples plus représentatifs de la réalité)
* utiliser de nouveaux attributs pour qualifier les données
%% Cell type:markdown id: tags:
##### Complexité du modèle
On veut généralement trouver un équilibre entre le biais et la variance (bias-variance trade-off) :
%% Cell type:markdown id: tags:
<img src="img/bias-variance-1.png" width="700"/>
<img src="img/bias-variance-2.png" width="700"/>
%% Cell type:markdown id: tags:
* un modèle trop simple va avoir du mal à modéliser les données (underfit / *sous-apprentissage*) : on dit que le modèle a un *biais* élevé. Sa performance sur les données d'entraînement peut être moyenne, mais il s'applique assez bien à de nouvelles données
* un modèle trop complexe va modéliser "trop bien" les données qui lui sont montrées (overfit / *surapprentissage*) : on dit que le modèle a une *variance* élevée. Sa performance sur les données d'entraînement pourra être très élevée, mais il aura du mal à s'appliquer à de nouvelles données qui ne sont jamais exactement identiques aux anciennes (chute de performance)
%% Cell type:markdown id: tags:
Il faut donc trouver un modèle avec un bon équilibre entre les deux, en limitant la complexité du modèle :
<img src="img/courbe-validation.png" width="400"/>
* à gauche : modèle trop simple, performance faible partout
* à droite : modèle trop complexe, performance forte sur les données d'entraînement mais faible sur les données de test
* au centre : modèle de complexité appropriée, optimal sur les données de test
%% Cell type:markdown id: tags:
##### Taille des données
A complexité égale, lorsqu'on entraîne un modèle sur un jeu de données d'entraînement :
* si sa taille est trop faible :
* le modèle va être très proche des données vues : score élevé sur données d'entraînement
* le modèle va être éloigné des données de test : score nettement plus faible sur données de test
* mauvaise généralisation : *surapprentissage*
* si sa taille est élevée :
* bonne généralisation : écart faible sur l'ensemble d'entraînement et de test
* ajouter de nouvelles données n'améliorera plus la qualité de ce modèle
* seul un modèle plux complexe pourra (éventuellement) avoir une meilleure performance
* seul un modèle plus complexe pourra (éventuellement) avoir une meilleure performance
%% Cell type:markdown id: tags:
<img src="img/courbe-apprentissage.png" width="400"/>
%% Cell type:markdown id: tags:
## Quelques classifieurs
%% Cell type:markdown id: tags:
### K plus proches voisins (KNN)
Dans Scikit-learn : `sklearn.neighbors.KNeighborsClassifier`
%% Cell type:markdown id: tags:
**Principes :**
* pas de modèle global
* classification en fonction des classes des K points les plus proches
**Hyperparamètres :**
* K : nombre de voisins
* si faible: instable
* si élevé : peut comparer à des instances assez éloignées
* $d$ : la distance à utiliser
%% Cell type:markdown id: tags:
**Avantages :**
* seulement 2 hyperparamètres
* robustesse au bruit et aux valeurs manquantes
* puissance représentative élevée (pas forcément des frontières linéaires)
* pas besoin de modèles
**Limites :**
* difficulté dans le choix d'une bonne distance $d$
* besoin de supprimer les attributs non pertinents au préalable
* besoin de normaliser les attributs ou d'utiliser une distance pondérée
* absence de modèle : interprétation difficile, taille importante si nombre d'instances élevées
%% Cell type:markdown id: tags:
### Arbres de décision
Dans Scikit-learn : `sklearn.tree.DecisionTreeClassifier`
%% Cell type:markdown id: tags:
**Principes :**
* Extraire un ensemble de règles successives de la forme: $(a_1=v_1) \& (a_2=v_2) \& (a_3 >= v_3) ... (a_n < v_n) => (C = c_k)$
**Hyperparamètres :** aucun à spécifier obligatoirement. On peut jouer sur la profondeur de l'arbre pour limiter le surapprentissage.
**Variables :** numériques ou catégorielles
**Avantages :** facile à interpréter, classer de nouveaux exemples est rapide
**Limites :**
* données classables de manière déterministe (1 valeur = 1 classe, toute inconsistence entraîne une erreur dans la construction)
* sensible au surapprentissage sur des ensembles réduits
* arbre appris non nécessairement optimal, une instance supplémentaire peut complètement changer l'aspect de l'arbre
%% Cell type:markdown id: tags:
**Exemple :** soit le jeu de données mélangeant 4 catégories selon 2 attributs suivants
%% Cell type:code id: tags:
``` python
from sklearn.datasets import make_blobs
X, y = make_blobs(n_samples=300, centers=4,
random_state=0, cluster_std=1.0)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='rainbow');
```
%% Output
%% Cell type:markdown id: tags:
Entraînement du modèle :
%% Cell type:code id: tags:
``` python
from sklearn import tree
mytree = tree.DecisionTreeClassifier().fit(X, y)
```
%% Cell type:markdown id: tags:
On peut visualiser le découpage de l'espace à l'aide de la fonction suivante :
%% Cell type:code id: tags:
``` python
def visualize_classifier(model, X, y, ax=None, cmap='rainbow'):
ax = ax or plt.gca()
# Plot the training points
ax.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=cmap,
clim=(y.min(), y.max()), zorder=3)
ax.axis('tight')
ax.axis('off')
xlim = ax.get_xlim()
ylim = ax.get_ylim()
# fit the estimator
model.fit(X, y)
xx, yy = np.meshgrid(np.linspace(*xlim, num=200),
np.linspace(*ylim, num=200))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
# Create a color plot with the results
n_classes = len(np.unique(y))
contours = ax.contourf(xx, yy, Z, alpha=0.3,
levels=np.arange(n_classes + 1) - 0.5,
cmap=cmap, #clim=(y.min(), y.max()),
zorder=1)
ax.set(xlim=xlim, ylim=ylim)
```
%% Cell type:code id: tags:
``` python
visualize_classifier(mytree, X, y)
```
%% Output
%% Cell type:markdown id: tags:
Pour visualiser l'arbre de décision, il est malheureusement nécessaire d'installer un package externe : Graphviz. Il faut d'abord installer la bibliothèque Python faisant le lien : `conda install python-graphviz`
Puis :
%% Cell type:code id: tags:
``` python
# commande permettant à Graphviz d'être trouvé
# (chemin à modifier en fonction de son installation)
import os
os.environ["PATH"] += os.pathsep + "C:/Users/Francois/Miniconda3/Library/bin/graphviz/"
```
%% Cell type:code id: tags:
``` python
with open("blob_classifier.txt", "w") as f:
f = tree.export_graphviz(mytree, out_file=f)
```
%% Cell type:markdown id: tags:
<img src="img/blob-decision-tree.png" width="400"/>
%% Cell type:code id: tags:
``` python
from graphviz import Digraph, Source
dot = tree.export_graphviz(mytree)
src = Source(dot)
# Export en PDF externe
src.render('blob-tree.gv', view=True)
# Export en SVG
from IPython.display import SVG
SVG(src.pipe(format="svg")); # ; pour cacher le résultat
```
%% Cell type:markdown id: tags:
<img src="img/blob-decision-tree-2.png"/>
%% Cell type:markdown id: tags:
### Classifieur bayesien naïf
Dans Scikit-learn : `sklearn.naive_bayes.GaussianNB`
%% Cell type:markdown id: tags:
**Principes :**
* Repose sur le théorème de Bayes :
$$
P(C~|~{\rm features}) = \frac{P({\rm features}~|~C)P(C)}{P({\rm features})}
$$
Donc choisir entre deux classes $C_1$ et $C_2$ revient à calculer :
$$
\frac{P(C_1~|~{\rm features})}{P(C_2~|~{\rm features})} = \frac{P({\rm features}~|~C_1)}{P({\rm features}~|~C_2)}\frac{P(C_1)}{P(C_2)}
$$
Il faut donc un modèle génératif permettant de calculer $P({\rm features}~|~C_i)$
* utilisation d'hypothèses "naïves" concernant le modèle génératif pour approximer les données et ensuite classer celles-ci avec la classification bayesienne. On s'intéressera uniquement ici à des modèles de gaussiennes, où l'on suppose que chaque classe est issue d'une distribution gaussienne simple.
%% Cell type:markdown id: tags:
**Hyperparamètres :** aucun (choix des hypothèses naïves appropriées)
**Variables :** numériques
**Avantages :**
* on obtient une classification probabiliste (*i.e.* pour chaque point, on connait sa probabilité d'appartenir à chaque classe)
* simple à interpréter
* robuste au bruit dans les données
* fonctionne bien sur des jeux de données de taille réduite
**Limites :**
* puissance représentative plus faible que les arbres de décision
* hypothèse de non covariance par forcément réaliste
%% Cell type:markdown id: tags:
**Exemple :** données en 2 dimensions
%% Cell type:code id: tags:
``` python
from sklearn.datasets import make_blobs
X, y = make_blobs(100, 2, centers=2, random_state=2, cluster_std=1.5)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='RdBu');
```
%% Output
%% Cell type:markdown id: tags:
En supposant qu'il n'y a pas de covariance (lien entre les variables X et Y), on obtient le découpage suivant :
<img src="img/classification-naive-bayes-gaussian.png"/>
%% Cell type:markdown id: tags:
Création et entraînement du modèle :
%% Cell type:code id: tags:
``` python
from sklearn.naive_bayes import GaussianNB
model = GaussianNB()
model.fit(X, y);
```
%% Cell type:markdown id: tags:
Application du modèle à de nouvelles données fictives pour repérer la limite entre les deux classes :
%% Cell type:code id: tags:
``` python
rng = np.random.RandomState(0)
# génération de données aléatoires dans la zone comprise entre les points (-6, -14) et (8, 4)
Xnew = [-6, -14] + [14, 18] * rng.rand(2000, 2)
# prédiction pour chacun des 2000 points
ynew = model.predict(Xnew)
```
%% Cell type:code id: tags:
``` python
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='RdBu')
lim = plt.axis()
plt.scatter(Xnew[:, 0], Xnew[:, 1], c=ynew, s=20, cmap='RdBu', alpha=0.1)
plt.axis(lim);
```
%% Output
%% Cell type:markdown id: tags:
Estimation de la probabilité d'une instance pour chacune des 2 catégories :
%% Cell type:code id: tags:
``` python
yprob = model.predict_proba(Xnew)
yprob[-8:].round(2) # affichage des 8 derniers points
```
%% Output
array([[0.89, 0.11],
[1. , 0. ],
[1. , 0. ],
[1. , 0. ],
[1. , 0. ],
[1. , 0. ],
[0. , 1. ],
[0.15, 0.85]])
%% Cell type:markdown id: tags:
## Quelques régresseurs
La semaine prochaine !
%% Cell type:markdown id: tags:
## Exercices classification
%% Cell type:markdown id: tags:
### Arbre de décision sur iris
Entraîner un arbre de décision sur les données Iris et évaluer sa performance en fonction des différentes métriques d'évaluation présentées en séparant bien un ensemble d'entraînement (90% des données) et un ensemble de test (10%).
%% Cell type:markdown id: tags:
### Classifieur sur Exam2017
Reprendre le jeu de données Exam2017 et entraîner un classifieur (au choix) pour prédire si un étudiant va valider ou non le cours. On prendra soin de ne sélectionner que les attributs pertinents pour cette tâche. On utilisera une validation croisée.
%% Cell type:markdown id: tags:
### Classifieur sur "Drinks by Country"
Reprendre le jeu de données "drinks by country". Entraîner plusieurs classifieurs permettant de prédire le continent d'un pays en fonction de sa consommation de bière, vin et spiritueux. Estimer la performance de chacun des classifieurs à l'aide du kappa de Cohen. Tester notamment différentes valeurs d'hyperparamètres pour chacun.
Quel classifieur donne le meilleur résultat ? Quel est le kappa associé ?
%% Cell type:markdown id: tags:
### Régresseur sur données météorologiques
* Récupérer le jeu de données météorologique dans la ville de Szeged (Hongrie) entre 2006 et 2010 (source : https://www.kaggle.com/budincsevity/szeged-weather) qui est dans le sous-dossier data de Gitlab
* Charger le jeu de données
* Pouvez-vous prédire la température apparente en fonction de l'humidité ?
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment