<jupyter_start><jupyter_text>Human Activities - Classification Pour ce TP nous allons mettre en pratique plusieurs méthodes de classification supervisée sur des données d'enregistrement d'acceleromètres (de smart-phones).Les données sources sont disponibles sur [ici](http://bertrand.michel.perso.math.cnrs.fr/Enseignements/Data/UCI-HAR-Dataset.zip), voir aussi la description sur le site de [la plateforme de données UCI](https://archive.ics.uci.edu/ml/datasets/human+activity+recognition+using+smartphones)."The experiments have been carried out with a group of 30 volunteers within an age bracket of 19-48 years. Each person performed six activities (WALKING, WALKING_UPSTAIRS, WALKING_DOWNSTAIRS, SITTING, STANDING, LAYING) wearing a smartphone (Samsung Galaxy S II) on the waist. Using its embedded accelerometer and gyroscope, we captured 3-axial linear acceleration and 3-axial angular velocity at a constant rate of 50Hz. The experiments have been video-recorded to label the data manually.The sensor signals (accelerometer and gyroscope) were pre-processed by applying noise filters and then sampled in fixed-width sliding windows of 2.56 sec and 50% overlap (128 readings/window). The sensor acceleration signal, which has gravitational and body motion components, was separated using a Butterworth low-pass filter into body acceleration and gravity. The gravitational force is assumed to have only low frequency components, therefore a filter with 0.3 Hz cutoff frequency was used. From each window, a vector of features was obtained by calculating variables from the time and frequency domain. See 'features_info.txt' for more details."L'objectif de ce TP est de retrouver l'activité à partir de toutes ces descripteurs (features). Notez que dans un contexte plus réaliste il vous faudrait créer par vosu même tous ces features qui décrivent les séries temporelles des accelerations enregistrées.<jupyter_code>%matplotlib inline
from pylab import *
import pandas as pd
from sklearn.decomposition import PCA
from sklearn import preprocessing<jupyter_output><empty_output><jupyter_text>Importation des donnéesLes commandes suivantes permettent de récupérer les données.Pour simplifier on n'utilise pour le TP que les données du dossier "train".> Vérifiez que vous comprenez l'ensemble des commandes ci-dessous.<jupyter_code>votre_path= r'C:\Users\anass\Desktop\Statistical Learning\tp2\UCI-HAR-Dataset'<jupyter_output><empty_output><jupyter_text>Importation des features<jupyter_code>data_path = votre_path + r"\train\X_train.txt"
activity_features = pd.read_csv(data_path,delim_whitespace=True,
                                header=None)<jupyter_output><empty_output><jupyter_text>Importation des activités :<jupyter_code>data_path = votre_path + r"\train\y_train.txt"
activity  =    pd.read_csv(data_path,delim_whitespace=True,header=None)
activity  =  activity.values[:,0] -  1 
# la première activité sera 0 (plus pratique en python)
activity_names = ['WALKING','WALKING_UPSTAIRS','WALKING_DOWNSTAIRS','SITTING','STANDING','LAYING']<jupyter_output><empty_output><jupyter_text>La variable à prédire  est la variable d'activité. Il s'agit donc d'un problème de classification (à 6 classes).Pour cela on dispose des variables du tableau `activity_features`. > Combien de variables sont disponibles pour construire le prédicteur de l'activité ?  De combien d'observations dispose-t-on ?<jupyter_code>activity_features.shape #why 561<jupyter_output><empty_output><jupyter_text>Pour de nombreux méthodes d'appprentissage statistique, il est conseiller de préalablement standardiser les données. > Effectuer une standardisation des features (activity_features) avec la fonction  [scale()](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.scale.html) de scikit-learn.<jupyter_code>features_nor = preprocessing.scale(activity_features)### TODO ###  données standardisées<jupyter_output><empty_output><jupyter_text>Importation des sujets :<jupyter_code>data_path = votre_path + r"\train\subject_train.txt"
sujet =  pd.read_csv(data_path,delim_whitespace=True,header=None)
sujet =sujet.values[:,0]<jupyter_output><empty_output><jupyter_text>A chaque sujet correspond plusieurs observations.> Indiquer le nombre d'observations de chaque sujet<jupyter_code>{i :list(sujet).count(i) for i in set(list(sujet))}<jupyter_output><empty_output><jupyter_text>Problème de classification à deux classes  Problème de classification à deux classesDans toute la première partie du TP, on considère un problème de classification à deux classes. > Extraire les donnnés pour les activités  'WALKING_DOWNSTAIRS' (activity = 3)  et 'SITTING' (activity =4) uniquement. On appele `features34` et `activity34` les données correspondantes.<jupyter_code>n,p = shape(features_nor)
index_act34 = [i for i in range(n) if activity[i] in {2,3} ] 
activity34 = [activity[i] for i in index_act34]
features34 = [features_nor[i] for i in index_act34]
print(shape(features34))<jupyter_output>(2272, 561)<jupyter_text>Présélection de features  pour réduire la dimension (screening)Pour diminuer les temps de calul on peut pré-sélectionner les 100 features les plus discriminants via un critère univarié. > Utiliser le critère  [SelectKBest](http://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.SelectKBest.html) basé sur la F-value (variance inter / variance intra).Attention, cette méthode ne garantit en rien que l'on a choisi le "meilleur" groupe de 100 variables pour le problème de classification.<jupyter_code>from sklearn.feature_selection import SelectKBest, f_classif
Selec = SelectKBest(k=100)
features34 = Selec.fit_transform(features34,activity34)
shape(features34)<jupyter_output><empty_output><jupyter_text>Ajustement et prédiction pour des méthodes classiques de classification > Pour chacune des méthodes de classificiation listées ci-dessous, ajuster un predicteur de l'activité en utilisant comme données d'apprentissage la base (features34,activity34) :      - Classifieur naif bayesien avec la fonction `GaussianNB()`   - Classifieur des k plus proches voisins avec `neighbors.KNeighborsClassifier()` avec 5 plus proches voisins.  - Classifieur par régression logistique avec pénalisation ridge, en utilisant la fonction `linear_model.LogisticRegression() `. Indiquer la pénalité "l2" dans les arguments de la fonction et choisir le solveur "saga" (voir la doc de la fonction).Une présentation de chacune des ces fonctions est disponible sur cette [page](http://scikit-learn.org/stable/user_guide.html).<jupyter_code>from sklearn.naive_bayes import GaussianNB 
gnb =  GaussianNB()
gnb.fit(features34,activity34)

from sklearn import neighbors
nn = neighbors.KNeighborsClassifier(n_neighbors=5)
nn.fit(features34,activity34)

from sklearn import linear_model
logit = linear_model.LogisticRegression(penalty='l2', solver='saga', max_iter=697 ) #max_iter  
logit.fit(features34,activity34)<jupyter_output><empty_output><jupyter_text>Pour chacune de ces méthodes, on peut faire une prédiction grâce à la méthode `predict()`. par exemple pour le classifieur naif bayesien :<jupyter_code>feat_pred_gnb = gnb.predict(features34)
print(feat_pred_gnb)<jupyter_output>[3 3 3 ... 2 2 2]<jupyter_text>> Faire de même une prédiction pour le classifieur des plus proches voisins et pour la régression logistique.<jupyter_code>feat_pred_nn = nn.predict(features34)
print(feat_pred_nn)<jupyter_output>[3 3 3 ... 2 2 2]<jupyter_text>> Ces prédictions sont-elles cohérentes entre elles ?<jupyter_code>feat_pred_logit = logit.predict(features34)
print(feat_pred_logit)<jupyter_output>[3 3 3 ... 2 2 2]<jupyter_text>> Pour chaque prédicteur :> - Calculer le taux d'erreur "à la main" en comparant les observations et les prédictions.> - Calculer le taux de bon classement avec la méthode `score()` (disponible pour tout predicteur)<jupyter_code>Nobse=len(feat_pred_gnb)
taux_gnb=sum([int(activity34[i]==feat_pred_gnb[i]) for i in range(Nobse)])/Nobse
taux_nn=sum([int(activity34[i]==feat_pred_nn[i]) for i in range(Nobse)])/Nobse
taux_logit =sum([int(activity34[i]==feat_pred_logit[i]) for i in range(Nobse)])/Nobse
taux_gnb,taux_nn,taux_logit , gnb.score(features34,activity34) ,nn.score(features34,activity34) ,logit.score(features34,activity34)<jupyter_output><empty_output><jupyter_text>Evidemment ce n'est pas pas la bonne façon d'estimer le risque de l'estimateur : estimer de l'erreur de généralisation en évaluant les erreurs sur l'échantillon qui a déjà servi à ajuster le prédicteur conduit la plupart du temps à une estimation trop optimiste.  Evaluation de l'erreur par découpage train / test Nous allons maintenant évaluer l'erreur de généralisation en conservant une partie de l'échantillon pour évaluer les erreurs du prédicteur ajusté. Pour cela on découpe aléatoirement l'échantillon initial en deux parties :  - l'ensemble d'apprentissage : utilisé pour ajuster les prédicteurs ;- l'ensemble de test (ou de validation) : utilisé pour évaluer les performances des prédicteurs. > Utiliser la fonction train_test_split du module [model_selection](http://scikit-learn.org/stable/modules/classes.htmlmodule-sklearn.model_selection) pour construire un échantillon d'apprentissage de taille 60% (et donc 40% réservées aux données de test)<jupyter_code>from sklearn import model_selection

features_train, features_test, activity_train, activity_test = model_selection.train_test_split(features34,activity34,train_size=0.6)
print(len(activity_train)/ float(len(activity34)))
print(len(activity_test)/ float(len(activity34)))
print(shape(features_test))<jupyter_output>0.5999119718309859
0.40008802816901406
(909, 100)<jupyter_text>> Evaluer le risque d'un classifieur par plus proches voisins (knn) avec 5 voisins sur ce découpage des données : ajuster sur les données d'apprentissage et évaluer les erreurs sur le test.  >> Vérifier que l'estimation de l'erreur ainsi obtenue est plus élevée que l'estimation obtenue précédemment, en ajustant et évaluant l'erreur sur le même échantillon (échantillon complet, sans découpage)<jupyter_code>nn_2 = neighbors.KNeighborsClassifier(n_neighbors=5)
nn_2.fit(features_train,activity_train)
print(nn_2.score(features_train,activity_train) , nn_2.score(features_test,activity_test) , nn_2.score(features34,activity34),taux_nn)
#######################################


gnb_2 =  GaussianNB()
gnb_2.fit(features_train,activity_train)
print(gnb_2.score(features_train,activity_train) , gnb_2.score(features_test,activity_test) , gnb_2.score(features34,activity34),taux_gnb)

#####################################
logit_2 = linear_model.LogisticRegression(penalty='l2', solver='saga', max_iter=1000 ) #max_iter  
logit_2.fit(features_train,activity_train)
print(logit_2.score(features_train,activity_train) , logit_2.score(features_test,activity_test) , logit_2.score(features34,activity34),taux_logit)<jupyter_output>0.9992663242846662 1.0 0.9995598591549296 0.9995598591549296
0.9977989728539985 0.9955995599559956 0.996919014084507 0.996919014084507
1.0 1.0 1.0 1.0<jupyter_text>> Reproduire la procédure 100 fois et dresser le boxplot de l'estimation de l'erreur sur ces 100 runs.<jupyter_code>score_train_test = [] # pour stockage des scores
for simu in range(100) :
    features_train, features_test, activity_train, activity_test = model_selection.train_test_split(features34,activity34,train_size=0.6)
    nn_train_test = neighbors.KNeighborsClassifier(n_neighbors=5)
    nn_train_test.fit(features_train,activity_train)
    score_train_test.append(nn_train_test.score(features_test,activity_test))

B = plt.boxplot(score_train_test)<jupyter_output><empty_output><jupyter_text>> Tracer la courbe d'erreur du classifieur par plus proches voisins en fonction du nombre de voisins utilisés (de 1 à 15), en utilisant cette méthode de découpage train / test répétée 20 fois. En réalité, si l'on désire une estimation du modèle sélectionné ainsi, il faut découper l'échantillon disponible non pas en deux mais en trois parties :- l'ensemble d'apprentissage : utilisé pour construire des modèles ;- l'ensemble de validation : utilisé pour choisir un modèle ;- l'ensemble de test : utilisé pour évaluer les performances du modèle  finalement choisi.  Ceci est notamment important lorsque l'on souhaite comparer deux méthodes qui ont toutes les deux fait intervenir un réglage de paramètres (ici le nombre de voisins).> **Question bonus** (finir le tp avant de répondre à cette question) : Mettre en oeuvre cette méthode pour estimer l'erreur de généralisation du prédicteur knn pour lequel le nombre de voisins est choisi via l'échantillon de validation.  Estimation du risque par validation croisée On présente ci-dessous la méthode du de validation croisée de type **k-fold**.L'idée principale est de faire jouer à chaque observation à la fois le rôle de donnée d'apprentissage et celui de donnée de validation. Le principe consiste à partitionner aléatoirement les données $D=(\mathbf{x}_i,y_i)_{1\leq i\leq N} $ en $K$ blocs disjoints de tailles comparables :$D^1,\ldots,D^K$. On prend généralement $K=5$ ou $10$. La méthode du **leave-one-out (Loo)** correspond au choix $K=N$. On estime alors la qualité d'un predicteur $\hat f$ de lafaçon suivante :- pour tout $k \in \{1,\dots, K\}$, on construit un predicteur $\hat f^k$ (du même type que $\hat f$) sur l'ensemble d'apprentissage $D^{-k} := D\setminus D^k$ ;- on estime le risque du prédicteur $\hat f$ par   $$\frac{1}{N}\sum_{k=1}^K\sum_{\mathbf{x}_i\in D^k} \ell(\hat f^k(\mathbf{x}_i),y_i),$$où $\ell$ est la perte utilisée.On construit donc autant de prédicteurs qu'il y a de blocs dansl'ensemble d'apprentissage. Chaque prédicteur est évalué sur les éléments dubloc qui n'a pas été utilisé pour l'apprentissage (et qui joue donc le rôle d'ensemble de validation). Lorsque l'on souhaite régler un paramètre en utilisant la validation croisée, par exemple le nombre de voisins $k$ pour knn, on procède comme suit :- on évalue les performances des prédicteurs par validation croisée comme expliqué ci-dessus ;- on détermine la valeur optimale du paramètre ;- on construit finalement un predicteur pour le paramètre sélectionné, en utilisant comme base d'apprentissage toutes les données.**Comparaison entre LOO et methodes k folds, quelques repères :** - LOO a un coût computationnel plus élevé que les méthodes k-fold pour k petit (pour k = 5 ou 10 par exemple)- L'estimation de l'erreur fournie par LOO a généralement une variance plus élevée que celles obtenues par un k-fold pour k petit.- En revanche, si l'erreur de classification décroit très rapidement avec $n$, les méthodes k-fold avec k petit peuvent surestimer significativement l'erreur de généralisation.- En général, il est souvent recommandé d'utiliser les méthodes k-fold avec k = 5 ou 10. L'ensemble des méthodes de validation croisée disponibles sous `sklearn` sont disponibles dans le module [model_selection](http://scikit-learn.org/stable/modules/classes.htmlmodule-sklearn.model_selection). Voir aussi le [guide](https://scikit-learn.org/stable/modules/cross_validation.html).   Estimation de l'erreur par 10-fold :<jupyter_code>nn_val_croisee = neighbors.KNeighborsClassifier(n_neighbors = 10)
scores = model_selection.cross_val_score(nn_val_croisee, features34, activity34, cv=10)
print(scores)
print(mean(scores))<jupyter_output><empty_output><jupyter_text>Attention cependant, avec cette méthode, la fonction `cross_val_score` ne permute pas préalablement les données avant de définir les blocs. Voir cette [note](https://scikit-learn.org/stable/modules/cross_validation.htmla-note-on-shuffling) dans la doc de sklearn. > Modifier la procédure pour que les données soient bien initiallement permutées. On utilise pour cela un "iterateur" de validation croisée, que l'on définit ici avec la fonction `KFold`, et que l'on fournit en argument de la fonction `cross_val_score`.<jupyter_code>my_kfold = model_selection.KFold(### TO DO ###)

nn_val_croisee = neighbors.KNeighborsClassifier(n_neighbors = 10)

scores = model_selection.cross_val_score(estimator=### TO DO ###,
                        X=### TO DO ###,
                        y=### TO DO ###,
                        cv=### TO DO ###,
                        n_jobs=-1) # permet de répartir les calculs sur plusieurs coeurs<jupyter_output><empty_output><jupyter_text>Estimation de l'erreur par Loo> Utiliser la fonction LeaveOneOut de `model_selection` pour évaluer l'erreur du classifieur de plus proches voisin (pour 10 voisins). > La question de la permutation aléatoire initiale des données se pose-t-elle ici aussi ?  Sélection du nombre de plus proches voisins par validation croisée 10 fold > Selectionner un nombre de voisins pour le classifieur de plus proches voisins par validation croisée 10 fold.  La fonction GridSearchCVEn apprentissage statistique, on a très souvent recours à la validation croisée pour régler des paramètres de la méthode utilisée. La fonction [gridSearchCV](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html) permet d'effectuer cette tâche de façon simple, en répartissant éventuellement les calculs sur plusieurs coeurs (voir plus bas).<jupyter_code>from sklearn.model_selection import GridSearchCV
from time import time

# la grille de parametres a regler sont definis dans un dictionnaire (dict)
tuned_parameters = {'n_neighbors': range(2,20)}
start = time()

my_kfold = model_selection.KFold(n_splits=10, shuffle=True, random_state=0)
nnGrid = GridSearchCV(neighbors.KNeighborsClassifier(),
                      tuned_parameters,
                      cv=5)
nnGrid.fit(features34, activity34)
print(str(time() - start)+ " sec")

# le meilleur modele 
print(nnGrid.best_params_)<jupyter_output><empty_output><jupyter_text>> Si votre machine le permet, utiliser maintenant plusieurs coeurs (argument "n_jobs= ") et comparer les temps de calcul.  Classification multiclasses Pour réduire les temps de calcul (il s'agit d'un TP...) nous appliquons la même stratégie de présélection de features que nous avons utilisé pour la classification binaire.  Présélection de featuresPour réduire les temps de calcul (il s'agit d'un TP...) nous appliquons la même stratégie de présélection de features que pour le cas à deux classes :<jupyter_code>Selec = SelectKBest(f_classif, k=100)
features_all_activities = Selec.fit_transform(activity_features, activity)
shape(features_all_activities)<jupyter_output><empty_output><jupyter_text>Classifieur naif bayesien multiclasses<jupyter_code>gnb = GaussianNB()
scores  = model_selection.cross_val_score(gnb, features_all_activities, activity, cv=my_kfold)
print(scores)
print(mean(scores))<jupyter_output><empty_output><jupyter_text>Classifieur des k plus proches voisins multiclasses<jupyter_code>nn = neighbors.KNeighborsClassifier(n_neighbors = 10)
scores  = model_selection.cross_val_score(nn, features_all_activities, activity, cv=my_kfold)
print(scores)
print(mean(scores))<jupyter_output><empty_output><jupyter_text>> Effectuer un découpage apprentissage / test des données (avec toutes les classes).> Utiliser la fonction `GridSearchCV` sur l'échantillon d'apprentissage pour choisir le nombre de voisins pour l'estimateur knn. Dresser et afficher la matrice de confusion sur les données de test, voir [ici](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html) et [ici](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.plot_confusion_matrix.html).  Regression logistique Lasso > Ajuster et évaluer un modèle logistique Lasso sur les données avec la fonction [LogisticRegressionCV](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegressionCV.html).  Analyse en composantes principales sur la tableau des features.L'analyse en composantes principales est une méthode de réduction de dimension, de visualisation et de compression de données. Elle consiste à déterminer le sous-espace vectoriel de dimension $q$ qui approche au mieux possible un nuage de points dans $\mathbb R^d$ (au sens de la projection euclidienne sur cet espace).Les directions du sous-espace vectoriel approximant peuvent être vues comme de nouvelles variables, qui s'éxpriment comme des combinaisons linéaires des variables initiales. Par ailleurs, ces nouvelles variables, appelées **composantes principales**, sont dirigées selon les premières directions propres de la matrice de covariance empirique des données.On représente alors les données dans le nouveau système de variables issu de l'ACP.  ![analyse en composantes principales](http://www.nlpca.org/fig_pca_principal_component_analysis.png)Un descriptif de la fonction `sklearn.decomposition.PCA()` de la librairie scikit-learn est disponible [ici](http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html).> Tracer le nuage des deux premières composantes de l'ACP en distinguant les 5 activités. > Dans son écriture, le problème de l'ACP ignore la classification en classes d'activité. Les classes d'activités sur ces deux premières composantes sont elles bien séparées ? > En particulier, vérifier graphiquement que les classes 3 et 4 (pour lesquelles nous avons proposé des classifieurs plus haut) ne sont pas facilement séparées par les deux premiers axes de l'APC.<jupyter_code>from sklearn.decomposition import PCA
pca = PCA(n_components=5)
print(pca)

### TODO ###    # ajuster la PCA sur les données features_nor
### TODO ###    # transformer (prediction) les données features_nor pour le modele PCA
ACP0 = ### TODO ### extraction premiere composante
ACP1 = ### TODO ### extraction deuxieme composante

plt.figure(figsize=(12, 12))
for c, i, act in zip("rgbycm", range(0,6),range(0,6)):
    plt.scatter(### TODO ###)
# iterer sur les activités et utiliser un zip pour
# faciliter la representation graphique des objets 
# sur lesquels on itere
plt.legend()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08374406717780211, 'fraction_numerical': 0.019989047097480832, 'mean_word_length': 6.128497072218607, 'pattern_counts': {'":': 0, '<': 95, '<?xml version=': 0, '>': 125, 'https://': 8, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '25', 'dir': 'jupyter-structured-clean-dedup', 'id': '19309', 'license': 'MIT', 'n_tokens_mistral': 7357, 'n_tokens_neox': 7024, 'n_words': 2215, 'path': 'TP-HumanActivity-Classification(2).ipynb', 'repo_name': 'RandomAnass/Jupyter-books', 'size': '21912'}