Skip to content
Snippets Groups Projects
Commit 7f37a3f0 authored by SUR Frederic's avatar SUR Frederic
Browse files

2024-2025

parent c8ae7f9c
Branches
No related tags found
No related merge requests found
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# Introduction à l'apprentissage automatique: TP3 - Exercice 1 # Introduction à l'apprentissage automatique: TP3 - Exercice 1
<br> <br>
## Détection de spam ## Détection de spam
<br> <br>
Dans ce TP, nous allons entraîner des classifieurs pour décider si un mail est un spam ou non. Dans ce TP, nous allons entraîner des classifieurs pour décider si un mail est un spam ou non.
<br> <br>
Tout d'abord, quelques indications sur l'utilisation des méthodes d'apprentissage de `scikit-learn`. Tout d'abord, quelques indications sur l'utilisation des méthodes d'apprentissage de `scikit-learn`.
Les méthodes d'apprentissage supervisé de `scikit-learn` permettent de définir un objet, doté de différents attributs et méthodes, dont `cross_val_score` (pour calculer un score de validation croisée), `fit` (pour procéder à l'apprentissage), `predict` (pour prédire les classes des éléments d'une base de test), ou `score` pour calculer la proportion d'observations bien classées dans la base de test, sur laquelle on peut comparer la classe prédite à la "vraie" classe. Les méthodes d'apprentissage supervisé de `scikit-learn` permettent de définir un objet, doté de différents attributs et méthodes, dont `cross_val_score` (pour calculer un score de validation croisée), `fit` (pour procéder à l'apprentissage), `predict` (pour prédire les classes des éléments d'une base de test), ou `score` pour calculer la proportion d'observations bien classées dans la base de test, sur laquelle on peut comparer la classe prédite à la "vraie" classe.
Ci-dessous, un exemple d'utilisation de la classification au plus proche voisin, dans un scénario où on suppose disposer d'une base d'apprentissage $(X_{train},y_{train})$, et d'une base de test $X_{test}$ pour laquelle on connaît $y_{test}$, de manière à valider l'apprentissage sur la base de test. Si on veut changer de classifieur, il suffit d'utiliser un autre constructeur que `neighbors.KNeighborsClassifier` et de passer les paramètres adéquats. Ci-dessous, un exemple d'utilisation de la classification au plus proche voisin, dans un scénario où on suppose disposer d'une base d'apprentissage $(X_{train},y_{train})$, et d'une base de test $X_{test}$ pour laquelle on connaît $y_{test}$, de manière à valider l'apprentissage sur la base de test. Si on veut changer de classifieur, il suffit d'utiliser un autre constructeur que `neighbors.KNeighborsClassifier` et de passer les paramètres adéquats.
```python ```python
# (le code suivant ne peut pas être exécuté "tel quel"...) # (le code suivant ne peut pas être exécuté "tel quel"...)
# classifieur au plus proche voisin (on peut changer le paramètre n_neighbors): # classifieur au plus proche voisin (on peut changer le paramètre n_neighbors):
knn = neighbors.KNeighborsClassifier(n_neighbors=1) knn = neighbors.KNeighborsClassifier(n_neighbors=1)
# calcul d'un score moyen de validation croisée "à 5 plis" sur (X_train,y_train) # calcul d'un score moyen de validation croisée "à 5 plis" sur (X_train,y_train)
scores = cross_val_score(knn,X_train,y_train,cv=5) scores = cross_val_score(knn,X_train,y_train,cv=5)
print("score moyen de validation croisée: %0.3f (+/- %0.3f)" % (scores.mean(),2*scores.std())) print("score moyen de validation croisée: %0.3f (+/- %0.3f)" % (scores.mean(),2*scores.std()))
# la prédiction d'une nouvelle observation consistera à chercher le p.p.v. dans X_train, # la prédiction d'une nouvelle observation consistera à chercher le p.p.v. dans X_train,
# et à associer la classe de ce p.p.v., donnée par y_train: # et à associer la classe de ce p.p.v., donnée par y_train:
knn.fit(X_train,y_train) knn.fit(X_train,y_train)
# Remarque: il n'y a pas d'apprentissage à proprement parler pour les p.p.v., # Remarque: il n'y a pas d'apprentissage à proprement parler pour les p.p.v.,
# il s'agit juste de préciser la base dans laquelle seront cherchés les plus proches voisins # il s'agit juste de préciser la base dans laquelle seront cherchés les plus proches voisins
# on stocke dans y_pred les classes prédites sur un ensemble de test X_test: # on stocke dans y_pred les classes prédites sur un ensemble de test X_test:
y_pred = knn.predict(X_test) y_pred = knn.predict(X_test)
# calcul d'un score lorsqu'on connaît les vraies classes des observations de X_test: # calcul d'un score lorsqu'on connaît les vraies classes des observations de X_test:
# (proportion d'observations pour lesquelles y_test==y_pred) # (proportion d'observations pour lesquelles y_test==y_pred)
score = knn.score(X_test,y_test) score = knn.score(X_test,y_test)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## 1. Préliminaires ## 1. Préliminaires
<br> <br>
Commençons par charger les bibliothèques utiles au TD. Commençons par charger les bibliothèques utiles au TD.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
import numpy as np import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
from sklearn import neighbors, linear_model, naive_bayes, metrics from sklearn import neighbors, linear_model, naive_bayes, metrics
%matplotlib inline %matplotlib inline
# pour ignorer les avertissements "future warning"
#from warnings import simplefilter
#simplefilter(action='ignore', category=FutureWarning)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Ensuite, on charge les données: récupérez au préalable le fichier `spambase.data` disponible sur le [UCI Machine Learning Repository](http://archive.ics.uci.edu/ml/datasets/Spambase) (cliquez sur "Download"). La description complète de la base est dans le fichier `spambase.name`, à ouvrir avec un éditeur de texte. Ensuite, on charge les données: récupérez au préalable le fichier `spambase.data` disponible sur le [UCI Machine Learning Repository](http://archive.ics.uci.edu/ml/datasets/Spambase) (cliquez sur "Download"). La description complète de la base est dans le fichier `spambase.name`, à ouvrir avec un éditeur de texte.
<br> <br>
La cellule suivante charge les données. On forme une base d'entraînement avec 80% des données (choix aléatoire), et on garde 20% des données pour faire une base de test. Dans la cellule suivante, on fixe la graîne du générateur aléatoire (`random_state=1`, la valeur est arbitraire) de manière à ce que l'on ait tous les mêmes résultats afin de faciliter la comparaison. La cellule suivante charge les données. On forme une base d'entraînement avec 80% des données (choix aléatoire), et on garde 20% des données pour faire une base de test. Dans la cellule suivante, on fixe la graîne du générateur aléatoire (`random_state=1`, la valeur est arbitraire) de manière à ce que l'on ait tous les mêmes résultats afin de faciliter la comparaison.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
data = np.loadtxt('spambase.data', delimiter=',') data = np.loadtxt('spambase.data', delimiter=',')
X_train, X_test, y_train, y_test = train_test_split(data[:,:-1], data[:,-1], test_size=0.2, random_state=1) X_train, X_test, y_train, y_test = train_test_split(data[:,:-1], data[:,-1], test_size=0.2, random_state=1)
# pour vérifier que les données sont bien chargées: # pour vérifier que les données sont bien chargées:
print("dataset:") print("dataset:")
print(data) print(data)
print("\nTOTAL - nombre d'observations, nombre de caractéristiques:") print("\nTOTAL - nombre d'observations, nombre de caractéristiques:")
print(data.shape) print(data.shape)
print("\nAPPRENTISSAGE - nombre d'observations, nombre de caractéristiques:") print("\nAPPRENTISSAGE - nombre d'observations, nombre de caractéristiques:")
print(X_train.shape) print(X_train.shape)
print("\nAPPRENTISSAGE - nombre de labels associés aux obervations:") print("\nAPPRENTISSAGE - nombre de labels associés aux obervations:")
print(y_train.shape) print(y_train.shape)
print("\nTEST - nombre d'observations, nombre de caractéristiques:") print("\nTEST - nombre d'observations, nombre de caractéristiques:")
print(X_test.shape) print(X_test.shape)
print("\nTEST - nombre de labels associés aux obervations:") print("\nTEST - nombre de labels associés aux obervations:")
print(y_test.shape) print(y_test.shape)
print("\nobservations, base d'apprentissage:") print("\nobservations, base d'apprentissage:")
print(X_train) print(X_train)
print("\nlabels associés, base d'apprentissage:") print("\nlabels associés, base d'apprentissage:")
print(y_train) print(y_train)
print("\nproportion de spams dans la base d'apprentissage:")
print(np.mean(y_train))
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
__Question 1__. A partir de la description de la base de données, justifiez la manière employée pour charger les données en `X` (observations) et `y` (labels). Quelles sont les caractéristiques des observations, les labels, et quel est le rapport avec le problème initial? __Question 1__. A partir de la description de la base de données, justifiez la manière employée pour charger les données en `X` (observations) et `y` (labels). Quelles sont les caractéristiques des observations, les labels, et quel est le rapport avec le problème initial?
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<font color=red> <font color=red>
Votre réponse: Votre réponse:
</font> </font>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
__Remarque importante__: lorsqu'on teste des classifieurs, il est important de comparer les scores de classification obtenus à celui d'un "dummy classifier" (un classifieur fictif): un classifieur qui fait une prévision sans tenir compte des observations. Par exemple, ici un classifieur qui classerait toute observation comme "non spam" aurait raison dans presque 60% des cas. On espère donc que les classifieurs réels soient meilleurs.
%% Cell type:markdown id: tags:
## 2. Classification aux plus proches voisins ## 2. Classification aux plus proches voisins
<br> <br>
Mettez en oeuvre les classifications au plus proche voisin et aux 5 plus proches voisins. Vous calculerez le score moyen de validation croisée à 5 plis sur la base d'apprentissage ainsi que le score obtenu sur la base de test. Vous vous inspirerez du code détaillé en introduction. Mettez en oeuvre les classifications au plus proche voisin et aux 5 plus proches voisins. Vous calculerez le score moyen de validation croisée à 5 plis sur la base d'apprentissage ainsi que le score obtenu sur la base de test. Vous vous inspirerez du code détaillé en introduction.
__Question 2__. Quelle est la métrique utilisée pour déterminer les plus proches voisins? Quel est ce "score" calculé exactement? Quel lien entre score de validation croisée et score sur la base de test? __Question 2__. Quelle est la métrique utilisée pour déterminer les plus proches voisins? Quel est ce "score" calculé exactement? Quel lien entre score de validation croisée et score sur la base de test?
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# votre code ici pour 1-ppv: # votre code ici pour 1-ppv:
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# votre code ici pour 5-ppv: # votre code ici pour 5-ppv:
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<font color=red> <font color=red>
Votre réponse: Votre réponse:
</font> </font>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
__Question 3__. Pourquoi la métrique utilisée n'est-elle pas adaptée aux observations ? __Question 3__. Pourquoi la métrique utilisée n'est-elle pas adaptée aux observations ?
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<font color=red> <font color=red>
Votre réponse: Votre réponse:
</font> </font>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
__Question 4__. Pré-traitez les données par standardisation, comme expliqué ici sur [la documentation scikit-learn](https://scikit-learn.org/stable/modules/preprocessing.html) (utilisez `StandardScaler` de manière à utiliser la même normalisation sur la base d'apprentissage et sur la base de test, c'est important), puis recalculez les scores des deux classifieurs précédents. __Question 4__. Pré-traitez les données par standardisation, comme expliqué ici sur [la documentation scikit-learn](https://scikit-learn.org/stable/modules/preprocessing.html) (utilisez `StandardScaler` de manière à utiliser la même normalisation sur la base d'apprentissage et sur la base de test, c'est important), puis recalculez les scores des deux classifieurs précédents.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# votre code: # votre code:
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<font color=red> <font color=red>
Votre réponse: Votre réponse:
</font> </font>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## 3. Classifieur naïf de Bayes gaussien et classifieur de la régression logistique ## 3. Classifieur naïf de Bayes gaussien et classifieur de la régression logistique
<br> <br>
__Question 5__. Pourquoi le classifieur naïf de Bayes gaussien ne nécessite-t-il pas de standardisation préalable des données ? (vous pouvez vérifier que la normalisation joue tout de même un faible rôle: elle a sans doute une influence sur le comportement de l'algorithme d'estimation des paramètres). __Question 5__. Pourquoi le classifieur naïf de Bayes gaussien ne nécessite-t-il pas de standardisation préalable des données ? (vous pouvez vérifier que la normalisation joue tout de même un faible rôle: elle a sans doute une influence sur le comportement de l'algorithme d'estimation des paramètres).
Mettez en oeuvre le classifieur naïf de Bayes gaussien (lisez le début de la [documentation](https://scikit-learn.org/stable/modules/naive_bayes.html) où vous retrouverez le contenu du cours, puis la syntaxe de `GaussianNB` [ici](https://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.GaussianNB.html)), ainsi que le classifieur de la régression logistique ([documentation](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html)). Mettez en oeuvre le classifieur naïf de Bayes gaussien (lisez le début de la [documentation](https://scikit-learn.org/stable/modules/naive_bayes.html) où vous retrouverez le contenu du cours, puis la syntaxe de `GaussianNB` [ici](https://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.GaussianNB.html)).
Pour ce dernier classifieur, passez l'option `max_iter=2000` si vous avez un avertissement concernant la convergence de l'optimisation, de la manière suivante:
`LR = linear_model.LogisticRegression(max_iter=2000)`
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# votre code pour le classifieur naïf de Bayes gaussien (sur les données originales): # votre code pour le classifieur naïf de Bayes gaussien (sur les données originales):
``` ```
%% Cell type:markdown id: tags:
Mettez en oeuvre le classifieur de la régression logistique ([documentation](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html)).
Passez l'option `max_iter=2000` si vous avez un avertissement concernant la convergence de l'optimisation, de la manière suivante:
`LR = linear_model.LogisticRegression(max_iter=2000)`
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# votre code pour la régression logistique (sur les données standardisées): # votre code pour la régression logistique (sur les données standardisées):
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<font color=red>
Votre réponse:
</font>
%% Cell type:markdown id: tags:
## 4. Analyse des résultats ## 4. Analyse des résultats
<br> <br>
On dispose des matrices de confusion, décrites [ici](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html), et des rapports de classification, décrits [](https://scikit-learn.orgz/stable/modules/generated/sklearn.metrics.classification_report.html). On dispose des matrices de confusion, décrites [ici](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html), et des rapports de classification, décrits [](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.classification_report.html).
__Question 6__. Affichez ces matrices et rapports pour les quatre classifieurs testés. __Question 6__. Affichez ces matrices et rapports sur la base test pour les quatre classifieurs étudiés.
%% 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 7__. Ici, par quoi pourraient s'expliquer les performances modestes du classifieur naïf de Bayes ? __Question 7__. Ici, par quoi pourraient s'expliquer les performances modestes du classifieur naïf de Bayes ?
A ce stade, quel classifieur préfére-t-on et pourquoi? Dans une application de détection de spams, cherche-t-on réellement à minimiser le taux d'erreur global? A ce stade, quel classifieur préfére-t-on et pourquoi? Dans une application de détection de spams, cherche-t-on réellement à minimiser le taux d'erreur global?
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<font color=red> <font color=red>
Réponse: Réponse:
</font> </font>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## 5. Toutes les erreurs ne se valent pas... ## 5. Toutes les erreurs ne se valent pas...
Le classifieur bayésien naïf gaussien et le classifieur de la régression logistique s'appuient tous deux sur la règle du maximum a posteriori. Ils permettent d'estimer la probabilité a posteriori $p(C_1|x)$ et détectent un spam lorsque $p(C_1|x)>1/2$, où $C_1$ désigne la classe "spam" et $x$ est une observation. Les deux classifieurs mettent en oeuvre le classifieur de Bayes, qui minimise le risque moyen de prédiction (le taux d'erreur). Le taux d'erreur "compte" de la même manière les erreurs sur les deux classes. Le classifieur bayésien naïf gaussien et le classifieur de la régression logistique s'appuient tous deux sur la règle du maximum a posteriori. Ils permettent d'estimer la probabilité a posteriori $p(C_1|x)$ et détectent un spam lorsque $p(C_1|x)>1/2$, où $C_1$ désigne la classe "spam" et $x$ est une observation. Les deux classifieurs mettent en oeuvre le classifieur de Bayes, qui minimise le risque moyen de prédiction (le taux d'erreur). Le taux d'erreur "compte" de la même manière les erreurs sur les deux classes.
Si on préfère réduire le taux de faux positif de la méthode (proportion de mails détectés à tort comme "spam"), on peut relever le seuil de probabilité. Si on préfère réduire le taux de faux positif de la méthode (proportion de mails détectés à tort comme "spam"), on peut relever le seuil de cette probabilité.
Les classifieurs `LogisticRegression` et `GaussianNB` possèdent tous deux une méthode `predict_proba` qui, pour un tableau d'observations, fournit la probabilité a posteriori de chaque classe, comme l'affiche la cellule suivante. On remarque que pour chaque observation $x$, $p(C_0|x)+p(C_1|x)=1$. (attention, la documentation n'est pas très claire, `predict_proba` fournit bien la probabilité a posteriori, et pas la vraisemblance $p(x|C_k)$) Les classifieurs `LogisticRegression` et `GaussianNB` possèdent tous deux une méthode `predict_proba` qui, pour un tableau d'observations, fournit la probabilité a posteriori de chaque classe, comme l'affiche la cellule suivante. On remarque que pour chaque observation $x$, $p(C_0|x)+p(C_1|x)=1$. (attention, la documentation n'est pas très claire, `predict_proba` fournit bien la probabilité a posteriori, et pas la vraisemblance $p(x|C_k)$)
Remarquons qu'aucune probabilité n'est fournie par la classification aux plus proches voisins. Remarquons qu'aucune probabilité n'est fournie par la classification aux plus proches voisins.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
print("probabilités a posteriori pour GNB:") print("probabilités a posteriori pour GNB:")
print(GNB.predict_proba(X_test)) print(GNB.predict_proba(X_test))
print("\nprobabilités a posteriori pour LR:") print("\nprobabilités a posteriori pour LR:")
print(LR.predict_proba(X_test_standard)) print(LR.predict_proba(X_test_standard))
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# faites varier le seuil de détection p: # faites varier le seuil de détection p:
p=0.5 # constatez que p=0.5 fournit les mêmes résultats pour y_pred_lr et y_pred_LRb p=0.5 # constatez que p=0.5 fournit les mêmes résultats pour y_pred_lr et y_pred_LRb
y_pred_LRb = (LR.predict_proba(X_test_standard)[:,1] >= p).astype(int) y_pred_LRb = (LR.predict_proba(X_test_standard)[:,1] >= p).astype(int)
#print(y_pred_LRb) # pour visualiser les classes prédites #print(y_pred_LRb) # pour visualiser les classes prédites
#print(y_pred_lr) #print(y_pred_lr)
score_LRb = 1-np.mean(np.abs(y_test-y_pred_LRb)) # calcul du taux de reconnaissance score_LRb = 1-np.mean(np.abs(y_test-y_pred_LRb)) # calcul du taux de reconnaissance
print("\nClassification de la régression logistique pour un seuil de probabilité p=%.3f" %p) print("\nClassification de la régression logistique pour un seuil de probabilité p=%.3f" %p)
print('score sur la base de test: %.3f' %score_LRb) print('score sur la base de test: %.3f' %score_LRb)
print(metrics.classification_report(y_test,y_pred_LRb)) print(metrics.classification_report(y_test,y_pred_LRb))
print("matrice de confusion:") print("matrice de confusion:")
print(metrics.confusion_matrix(y_test,y_pred_LRb)) print(metrics.confusion_matrix(y_test,y_pred_LRb))
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
__Question 8__. Quelle valeur du seuil de probabilité $p$ faut-il choisir pour assurer un rappel de la classe "non spam" d'au moins 0.98? __Question 8__. Quelle valeur du seuil de probabilité $p$ faut-il choisir pour assurer un rappel de la classe "non spam" d'au moins 0.98?
Que penser de cet algorithme de détection de spam? Que penser de cet algorithme de détection de spam?
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<font color=red> <font color=red>
Réponse: Réponse:
</font> </font>
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
``` ```
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment