"On commence par charger les bibliothèques utiles et définir une fonction de visualisation que nous utiliserons par la suite:"
"On commence par charger les bibliothèques utiles et définir une fonction de visualisation que nous utiliserons par la suite.\n",
"\n",
"__Remarque__: si la visualisation par la fonction `plot_classif_result_SVM` prend trop de temps, augmentez la valeur du pas `h` dans la définition de cette fonction (faites `h=0.05`)."
]
]
},
},
{
{
...
...
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
# Introduction à l'apprentissage automatique - TP4 exercice 1
# Introduction à l'apprentissage automatique - TP4 exercice 1
### SVM sur données synthétiques
### SVM sur données synthétiques
<br>
<br>
Dans la [documentation scikit-learn sur les SVM](http://scikit-learn.org/stable/modules/svm.html), lisez l'introduction.
Dans la [documentation scikit-learn sur les SVM](http://scikit-learn.org/stable/modules/svm.html), lisez l'introduction.
On utilisera la [classe SVC](http://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html).
On utilisera la [classe SVC](http://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html).
Notez la valeur par défaut de l'hyperparamètre $C$, et les fonctions noyau disponibles nativement (ainsi que leurs paramètres).
Notez la valeur par défaut de l'hyperparamètre $C$, et les fonctions noyau disponibles nativement (ainsi que leurs paramètres).
<br>
<br>
Dans les questions suivantes, nous séparerons les bases de données
Dans les questions suivantes, nous séparerons les bases de données
entre bases d'apprentissage (80% de la base initiale) et base de test (20%) en utilisant `model_selection.train_test_split`, et nous comparerons les performances des différents classifieurs en calculant le score de classification sur la base de test.
entre bases d'apprentissage (80% de la base initiale) et base de test (20%) en utilisant `model_selection.train_test_split`, et nous comparerons les performances des différents classifieurs en calculant le score de classification sur la base de test.
<br>
<br>
On commence par charger les bibliothèques utiles et définir une fonction de visualisation que nous utiliserons par la suite:
On commence par charger les bibliothèques utiles et définir une fonction de visualisation que nous utiliserons par la suite.
__Remarque__: si la visualisation par la fonction `plot_classif_result_SVM` prend trop de temps, augmentez la valeur du pas `h` dans la définition de cette fonction (faites `h=0.05`).
Commençons par un jeu de données constitué de 1000 points du plan dans deux classes linéairement séparables, obtenu avec `make_blobs` (comme dans un des TP précédents).
Commençons par un jeu de données constitué de 1000 points du plan dans deux classes linéairement séparables, obtenu avec `make_blobs` (comme dans un des TP précédents).
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` python
``` python
# génération dataset
# génération dataset
# on précise random_state pour travailler tous sur le même jeu de données
# on précise random_state pour travailler tous sur le même jeu de données
# avec cluster_std=1.5 dans la ligne suivante, les classes sont linéairement séparables
# avec cluster_std=1.5 dans la ligne suivante, les classes sont linéairement séparables
Ensuite, nous entraînons une SVM à noyau linéaire, et nous visualisons les vecteurs supports à l'aide de la fonction `plot_classif_result_SVM`.
Ensuite, nous entraînons une SVM à noyau linéaire, et nous visualisons les vecteurs supports à l'aide de la fonction `plot_classif_result_SVM`.
On affiche également le score de classification sur la base test, ainsi que le nombre de vecteurs supports pour chaque classe.
On affiche également le score de classification sur la base test, ainsi que le nombre de vecteurs supports pour chaque classe.
__Question 1__. Les résultats dans le cas où les classes sont linéairement séparables vous semblent-ils cohérents avec le cours?
__Question 1__. Les résultats dans le cas où les classes sont linéairement séparables vous semblent-ils cohérents avec le cours?
Que peut-on dire lorsque les deux classes ne sont plus linéairement séparables? (augmentez la valeur de `cluster_std`)
Que peut-on dire lorsque les deux classes ne sont plus linéairement séparables? (augmentez la valeur de `cluster_std`)
En particulier, comparez la marge, les vecteurs supports, leur nombre, le score sur la base de test, et le temps de calcul.
En particulier, comparez la marge, les vecteurs supports, leur nombre, le score sur la base de test, et le temps de calcul.
(pour obtenir deux classes non linéairement séparables, augmentez la valeur de `cluster_std` dans `make_blobs` dans la cellule précédente, et relancez la cellule suivante)
(pour obtenir deux classes non linéairement séparables, augmentez la valeur de `cluster_std` dans `make_blobs` dans la cellule précédente, et relancez la cellule suivante)
La celulle suivante permet l'apprentissage par SVM linéaire, avec différentes valeurs de l'hyperparamètre $C$.
La celulle suivante permet l'apprentissage par SVM linéaire, avec différentes valeurs de l'hyperparamètre $C$.
__Question 2__. Vérifiez que diminuer la valeur de l'hyperparamètre $C$ augmente le nombre de vecteurs supports (et change la surface de séparation). Est-ce cohérent avec la discussion du cours?
__Question 2__. Vérifiez que diminuer la valeur de l'hyperparamètre $C$ augmente le nombre de vecteurs supports (et change la surface de séparation). Est-ce cohérent avec la discussion du cours?
print("score test SVM %.3f"%SVM.score(X_test,y_test))
print("score test SVM %.3f"%SVM.score(X_test,y_test))
print("nombre de vecteurs supports: %d pour classe 0 et %d pour classe 1"%(SVM.n_support_[0],SVM.n_support_[1]))
print("nombre de vecteurs supports: %d pour classe 0 et %d pour classe 1"%(SVM.n_support_[0],SVM.n_support_[1]))
```
```
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
<fontcolor=red>
<fontcolor=red>
Réponse:
Réponse:
</font>
</font>
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
### Noyau RBF
### Noyau RBF
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
__Question 4__. Pour le noyau RBF et la valeur par défaut de $\gamma$, la cellule suivante présente différentes classifications selon les valeurs de $C$. Retrouvez les situations identifiées dans le cas linéaire.
__Question 4__. Pour le noyau RBF et la valeur par défaut de $\gamma$, la cellule suivante présente différentes classifications selon les valeurs de $C$. Retrouvez les situations identifiées dans le cas linéaire.
__Rappel__ : d'après __[la documentation](http://scikit-learn.org/stable/auto_examples/svm/plot_rbf_parameters.html)__ : _"The C parameter trades off correct classification of training examples against maximization of the decision function’s margin. For larger values of C, a smaller margin will be accepted if the decision function is better at classifying all training points correctly. A lower C will encourage a larger margin, therefore a simpler decision function, at the cost of training accuracy. In other words C behaves as a regularization parameter in the SVM."_
__Rappel__ : d'après __[la documentation](http://scikit-learn.org/stable/auto_examples/svm/plot_rbf_parameters.html)__ : _"The C parameter trades off correct classification of training examples against maximization of the decision function’s margin. For larger values of C, a smaller margin will be accepted if the decision function is better at classifying all training points correctly. A lower C will encourage a larger margin, therefore a simpler decision function, at the cost of training accuracy. In other words C behaves as a regularization parameter in the SVM."_
print("C=10000, score test SVM %.3f"%SVM.score(X_test,y_test))
print("C=10000, score test SVM %.3f"%SVM.score(X_test,y_test))
```
```
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
<fontcolor=red>
<fontcolor=red>
Réponse:
Réponse:
</font>
</font>
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
La cellule suivante permet de trouver la valeur optimale de l'hyperparamètre $C$ par validation croisée à 5 plis (5-fold cross validation) sur la base d'apprentissage. La valeur de $\gamma$ reste celle fixée par défaut.
La cellule suivante permet de trouver la valeur optimale de l'hyperparamètre $C$ par validation croisée à 5 plis (5-fold cross validation) sur la base d'apprentissage. La valeur de $\gamma$ reste celle fixée par défaut.
print('C=%.4f score de validation croisée = %.4f +/- %.4f'%(C,scores.mean(),scores.std()))
print('C=%.4f score de validation croisée = %.4f +/- %.4f'%(C,scores.mean(),scores.std()))
plt.figure()
plt.figure()
plt.semilogx(arrayC,accuracy,'-*')
plt.semilogx(arrayC,accuracy,'-*')
plt.title('score de validation croisée contre C')
plt.title('score de validation croisée contre C')
plt.grid()
plt.grid()
plt.show();
plt.show();
```
```
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
Pour trouver une valeur optimale aux hyperparamètres $\gamma$ et $C$ par validation croisée, on dispose de la fonction [GridSearchCV](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html).
Pour trouver une valeur optimale aux hyperparamètres $\gamma$ et $C$ par validation croisée, on dispose de la fonction [GridSearchCV](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html).
Cette fonction va calculer le score de validation croisée pour différentes valeurs des paramètres: ici, $C$ et $\gamma$ peuvent prendre des valeurs entre $10^{-3}$ et $10^3$. Par défaut, il s'agit de la validation croisée à 5 plis.
Cette fonction va calculer le score de validation croisée pour différentes valeurs des paramètres: ici, $C$ et $\gamma$ peuvent prendre des valeurs entre $10^{-3}$ et $10^3$. Par défaut, il s'agit de la validation croisée à 5 plis.
__Remarque__: d'une exécution à l'autre, la sélection des plis change. Cela explique que les meilleurs paramètres trouvés peuvent changer, car ici les scores de validation croisée sont proches les uns des autres.
__Remarque__: d'une exécution à l'autre, la sélection des plis change. Cela explique que les meilleurs paramètres trouvés peuvent changer, car ici les scores de validation croisée sont proches les uns des autres.
Visualisez les résultats de la cellule suivante (l'exécution peut prendre une minute):
Visualisez les résultats de la cellule suivante (l'exécution peut prendre une minute):
__Question 5__. Quel est le score de classification sur la base de test du meilleur classifieur SVM identifié? Visualisez la surface de séparation entre les classes.
__Question 5__. Quel est le score de classification sur la base de test du meilleur classifieur SVM identifié? Visualisez la surface de séparation entre les classes.
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` python
``` python
# votre code ici
# votre code ici
```
```
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
__Question 6__. (révision): Comparez au résultat de la classification de la base de test par les algorithmes de classification aux 1,5, 10 plus proches voisins, au classifieur de la régression logistique, et au classifieur naïf gaussien.
__Question 6__. (révision): Comparez au résultat de la classification de la base de test par les algorithmes de classification aux 1,5, 10 plus proches voisins, au classifieur de la régression logistique, et au classifieur naïf gaussien.
print("gamma=100, score test SVM %.3f"%SVM.score(X_test,y_test))
print("gamma=100, score test SVM %.3f"%SVM.score(X_test,y_test))
```
```
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
Avec un noyau gaussien, $k(x,y)=\exp(-\gamma||x-y||^2)$ est différent de 0 seulement lorsque $x$ et $y$ sont proches relativement à $\gamma$ (disons $||x-y||<1/\sqrt{\gamma}$).
Avec un noyau gaussien, $k(x,y)=\exp(-\gamma||x-y||^2)$ est différent de 0 seulement lorsque $x$ et $y$ sont proches relativement à $\gamma$ (disons $||x-y||<1/\sqrt{\gamma}$).
Le coefficient $\sqrt{\gamma}$ peut être vu comme l'inverse de la distance d'influence des vecteurs supports. En effet, le classifieur s'écrit:
Le coefficient $\sqrt{\gamma}$ peut être vu comme l'inverse de la distance d'influence des vecteurs supports. En effet, le classifieur s'écrit:
$$f(x) = \sum_i \lambda_i y_i k(x_i,x) + b$$
$$f(x) = \sum_i \lambda_i y_i k(x_i,x) + b$$
__Lorsque $\gamma$ est grand__, $k(x_i,x)$ a une contribution significative seulement pour $x$ proche d'un des vecteurs supports $x_i$. Ainsi, le signe de $f$ sera différent de celui de $b$ seulement pour $x$ proche d'un vecteur support $x_i$ associé à la classe $y_i$ de signe contraire à celui de $b$. On a intérêt à ce que toutes les observations soient des vecteurs supports de manière à minimiser le nombre de mauvaises classifications, donc la somme des variables d'écart. La surface de séparation est donc la superposition de disques autours des points d'une des classes, ce que l'on observe bien ici.
__Lorsque $\gamma$ est grand__, $k(x_i,x)$ a une contribution significative seulement pour $x$ proche d'un des vecteurs supports $x_i$. Ainsi, le signe de $f$ sera différent de celui de $b$ seulement pour $x$ proche d'un vecteur support $x_i$ associé à la classe $y_i$ de signe contraire à celui de $b$. On a intérêt à ce que toutes les observations soient des vecteurs supports de manière à minimiser le nombre de mauvaises classifications, donc la somme des variables d'écart. La surface de séparation est donc la superposition de disques autours des points d'une des classes, ce que l'on observe bien ici.
__Lorsque $\gamma$ est petit__, tous les $k(x_i,x)$ dans l'expression de $f$ ont une contribution.
__Lorsque $\gamma$ est petit__, tous les $k(x_i,x)$ dans l'expression de $f$ ont une contribution.
Le modèle est alors trop "moyenné" et on a une surface entre classes très régulière (presque une droite ici).
Le modèle est alors trop "moyenné" et on a une surface entre classes très régulière (presque une droite ici).
_Justification au passage_ (en complément): si $\gamma$ est petit, on identifie l'exponentielle et son développement limité à l'ordre 1: $\exp(-\gamma||x-y||^2)=1-\gamma||x-y||^2$. Ensuite:
_Justification au passage_ (en complément): si $\gamma$ est petit, on identifie l'exponentielle et son développement limité à l'ordre 1: $\exp(-\gamma||x-y||^2)=1-\gamma||x-y||^2$. Ensuite:
$$f(x) = \sum_i \lambda_i y_i k(x_i,x) + b = \sum_i \lambda_i y_i (1-\gamma||x_i-x||^2) + b = \sum_i \lambda_i y_i (1-\gamma||x_i||^2-\gamma||x||^2-2\gamma x_i \cdot x) + b $$
$$f(x) = \sum_i \lambda_i y_i k(x_i,x) + b = \sum_i \lambda_i y_i (1-\gamma||x_i-x||^2) + b = \sum_i \lambda_i y_i (1-\gamma||x_i||^2-\gamma||x||^2-2\gamma x_i \cdot x) + b $$
Comme $\sum_i \lambda_i y_i = 0$ (contrainte primale), $f(x)$ vérifie bien une relation du type $f(x) = B+ w\cdot x$, où $B=b-\sum_i \lambda_i y_i \gamma ||x_i||^2$ est une constante ne dépendant pas de $x$, et $w=- 2\gamma \left(\sum_i \lambda_i y_i x_i\right)$.
Comme $\sum_i \lambda_i y_i = 0$ (contrainte primale), $f(x)$ vérifie bien une relation du type $f(x) = B+ w\cdot x$, où $B=b-\sum_i \lambda_i y_i \gamma ||x_i||^2$ est une constante ne dépendant pas de $x$, et $w=- 2\gamma \left(\sum_i \lambda_i y_i x_i\right)$.
Cela justifie que la frontière de séparation est linéaire dans le graphique obtenu pour $\gamma=0.01$!
Cela justifie que la frontière de séparation est linéaire dans le graphique obtenu pour $\gamma=0.01$!
<br>
<br>
Il y a sous-apprentissage avec $\gamma$ petit, et sur-apprentissage avec $\gamma$ grand.
Il y a sous-apprentissage avec $\gamma$ petit, et sur-apprentissage avec $\gamma$ grand.
Lorsqu'on ne spécifie pas $\gamma$ dans `svm.SVC`, une valeur est calculée à partir des observations. Voyez dans la documentation comment la valeur par défaut de $\gamma$ est adaptée en fonction des observations.
Lorsqu'on ne spécifie pas $\gamma$ dans `svm.SVC`, une valeur est calculée à partir des observations. Voyez dans la documentation comment la valeur par défaut de $\gamma$ est adaptée en fonction des observations.
</font>
</font>
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
Dans la [documentation](https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html):
Dans la [documentation](https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html):
`if gamma='scale' (default) is passed then it uses 1 / (n_features * X.var()) as value of gamma`
`if gamma='scale' (default) is passed then it uses 1 / (n_features * X.var()) as value of gamma`
Le noyau RBF sans préciser gamma assure donc une normalisation. Attention, on normalise ici par rapport à la variance globale des caractéristiques. La normalisation par `StandardScaler` assure une normalisation caractéristique par caractéristique.
Le noyau RBF sans préciser gamma assure donc une normalisation. Attention, on normalise ici par rapport à la variance globale des caractéristiques. La normalisation par `StandardScaler` assure une normalisation caractéristique par caractéristique.
__Attention__ , si vous utilisez une version de `sklearn` inférieure à 0.22, passez à `svm.SVC` l'argument `gamma='scale'` pour observer le comportement de la version actuelle (la valeur par défaut était à l'époque `auto`).
__Attention__ , si vous utilisez une version de `sklearn` inférieure à 0.22, passez à `svm.SVC` l'argument `gamma='scale'` pour observer le comportement de la version actuelle (la valeur par défaut était à l'époque `auto`).