Apprentissage Statistique / Machine avec &

Résumé: Ce calepin introduit l'utilisation de la librairie scikit-learn pour la modélisation et l'apprentissage. Pourquoi utiliser scikit-learn ? Ou non ? Liste des fonctionnalités, quelques exemples de mise en oeuvre de modélisation (régression logistique, $k$-plus proches voisins, arbres de décision, forêts aléatoires. Optimisation des paramètres (complexité) des modèles par validation croisée. Fontions de chaînage (pipeline) de transformations et estimations. D'autres fonctionalités de Scikit-learn sont abordées dans les calepins du dépot sur l'apprentissage statistique.

1 Introduction

1.1 Scikit-learn vs. R

L'objectif de ce tutoriel est d'introduire l'utilisation de la librairie scikit-learn de Python. Seule l'utilisation directe des fonctions de modélisation sont abordées d'une manière analogue à la mise en oeuvre de R dont les librairies offrent l'accès à bien plus de méthodes. La comparaison avec R repose sur les remarques suivantes.

  • Cette librairie manipule des objets de classe array de numpy chargés en mémoire et donc de taille limitée par la RAM de l'ordinateur; de façon analogue R charge en RAM des objets de type data.frame.
  • Scikit-learn (0.18) ne reconnaît pas (ou pas encore ?) la classe DataFrame de pandas; scikit-learn utilise la classe array de numpy. C'est un problème pour la gestion de variables qualitatives complexes. Une variable binaire est simplement remplacée par un codage (0,1) mais, en présence de plusieurs modalités, traiter celles-ci comme des entiers n'a pas de sens statistique et remplacer une variable qualitative par l'ensemble des indicatrices (dummy variables (0,1)) de ses modalités complique les stratégies de sélection de modèle tout en rendant inexploitable l'interprétation statistique.
  • Les implémentations en Python de certains algorithmes dans scikit-learn sont souvent plus efficaces et utilisent implicitement les capacités de parallélisation.
  • R offre beaucoup plus de possibilités pour la comparaison de modèles statistiques et leur interprétation.

En conséquences:

  • Préférer R et ses librairies si la présentation des résultats et surtout leur interprétation (modèles) est prioritaire, si l'utilisation et / ou la comparaison de beaucoup de méthodes est recherchée.
  • Préférer Python et scikit-learn pour mettre au point une chaîne de traitements (pipe line) opérationnelle de l'extraction à une analyse privilégiant la prévision brute à l'interprétation et pour des données quantitatives ou rendues quantitatives ("vectorisation" de corpus de textes).

En revanche, si les données sont trop volumineuses pour la taille du disque et distribuées sur les n\oe uds d'un cluster avec Hadoop, consulter les calepins sur l'utilisation de Spark.

1.2 Fonctions d'apprentissage de Scikit-learn

La communauté qui développe cette librairie est très active et la fait évoluer rapidement. Ne pas hésiter à consulter la documentation pour des compléments. Voici une sélection de ses principales fonctionnalités en lien avec la modélisation.

  • Transformations (standardisation, discrétisation binaire, regroupement de modalités, imputations rudimentaires de données manquantes) , "vectorisation" de corpus de textes (encodage, catalogue, Tf-idf), images;
  • Modéle linéaire général avec pénalisation (ridge, lasso, elastic net...), analyse discriminante linéaire et quadratique, $k$ plus proches voisins, processus gaussiens, classifieur bayésien naïf, arbres de régression et classification (CART), agrégation de modèles (bagging, random forest, adaboost, gradient tree boosting), perceptron multicouche (réseau de neurones), SVM (classification, régression, détection d'atypiques...);
  • Algorithmes de validation croisée (loo, k-fold, VC stratifiée...) et sélection de modèles, optimisation sur une grille de paramètres, séparation aléatoire apprentissage et test, courbe ROC;
  • Enchaînement (pipeline) de traitements.

En résumé, cette librairie est focalisée sur les aspects "machine" de l'apprentissage de données quantitatives (séries, signaux, images) volumineuses tandis que R intègre l'analyse de variables qualitatives complexes et l'interprétation statistique fine des résultats au détriment parfois de l'efficacité des calculs.

1.3 Objectif

L'objectif est d'illustrer la mise en oeuvre de quelques fonctionnalités. Consulter la documentation et ses nombreux exemples pour plus de détails sur les possibilités d'utilisation de scikit-learn.

Deux jeux de données élémentaires sont utilisés. Celui déjà étudié avec pandas et concernant le naufrage du Titanic. Il mélange des variables explicatives qualitatives et quantitatives dans un objet de la classe DataFrame. Pour être utilisé dans scikit-learn les données doivent être transformées en un objet de classe Array de numpy par le remplacement des variables qualitatives par les indicatrices de leurs modalités. L'autre ensemble de données est entièrement quantitatif. C'est un problème classique et simplifié de reconnaissance de caractères qui est inclus dans la librairie scikit-learn.

Après la phase d'exploration (calepin précédent), ce sont les fonctions de modélisation et apprentissage qui sont abordées: régression logistique (titanic), $k$- plus proches voisins (caractères), arbres de discrimination, et forêts aléatoires. Les paramètres de complexité des modèles sont optimisés par minimisation de l'erreur de prévision estimée par validation croisée *V-fold$.

D'autres fonctionnalités sont rapidement illustrées : enchaînement (pipeline) de méthodes et automatisation, détection d'observations atypiques. Leur maîtrise est néanmoins importante pour la mise en exploitation de codes complexes efficaces.

2 Extraction des échantillons

Le travail préliminaire consiste à séparer les échantillons en une partie apprentissage et une autre de test pour estimer sans biais l'erreur de prévision. L'optimisation (biais-variance) de la complexité des modèles est réalisée en minimisant l'erreur estimée par validation croisée $V-fold$.

2.1 Données "Caractères"

Elles sont disponibles dans la librairie Scikit-learn.


In [ ]:
# Importations 
import matplotlib.pyplot as plt
from sklearn import datasets
%matplotlib inline
# les données
digits = datasets.load_digits()
# Contenu et mode d'obtention
print(digits)

In [ ]:
images_and_labels = list(zip(digits.images, 
   digits.target))
for index, (image, label) in  enumerate(images_and_labels[:8]):
     plt.subplot(2, 4, index + 1)
     plt.axis('off')
     plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
     plt.title('Training: %i' % label)

In [ ]:
# variables prédictives et cible
X=digits.data
y=digits.target

In [ ]:
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.25,random_state=11)

2.2 Données "Titanic"

Les données sur le naufrage du Titanic sont décrites dans le calepin consacré à la librairie pandas. Reconstruire la table des données en lisant le fichier .csv.


In [ ]:
# Lire les données d'apprentissage
import pandas as pd
path=''  # si les données sont déjà dans le répertoire courant
# path='http://www.math.univ-toulouse.fr/~besse/Wikistat/data/'
df=pd.read_csv(path+'titanic-train.csv',skiprows=1,header=None,usecols=[1,2,4,5,9,11],
  names=["Surv","Classe","Genre","Age","Prix","Port"],dtype={"Surv":object,"Classe":object,"Genre":object,"Port":object})
df.head()

In [ ]:
df.shape # dimensions
# Redéfinir les types 
df["Surv"]=pd.Categorical(df["Surv"],ordered=False)
df["Classe"]=pd.Categorical(df["Classe"],ordered=False)
df["Genre"]=pd.Categorical(df["Genre"],ordered=False)
df["Port"]=pd.Categorical(df["Port"],ordered=False)
df.dtypes

In [ ]:
df.count()

In [ ]:
# imputation des valeurs manquantes
df["Age"]=df["Age"].fillna(df["Age"].median())
df.Port=df["Port"].fillna("S")

In [ ]:
# Discrétiser les variables quantitatives
df["AgeQ"]=pd.qcut(df.Age,3,labels=["Ag1","Ag2","Ag3"])
df["PrixQ"]=pd.qcut(df.Prix,3,labels=["Pr1","Pr2","Pr3"])
# redéfinir les noms des modalités 
df["Surv"]=df["Surv"].cat.rename_categories(["Vnon","Voui"])
df["Classe"]=df["Classe"].cat.rename_categories(["Cl1","Cl2","Cl3"])
df["Genre"]=df["Genre"].cat.rename_categories(["Gfem","Gmas"])
df["Port"]=df["Port"].cat.rename_categories(["Pc","Pq","Ps"])
df.head()

Il est nécessaire de transformer les données car scikit-learn ne reconnaît pas la classe DataFrame de pandas, ce qui est bien dommage. Les variables qualitatives sont comme précédemment remplacées par les indicatrices de leurs modalités et les variables quantitatives conservées. Cela introduit une évidente redondance dans les données mais les procédures de sélection de modèle feront le tri.


In [ ]:
# Table de départ
df.head()

In [ ]:
# Construction des indicatrices
df_q=df.drop(["Age","Prix"],axis=1)
df_q.head()
# Indicatrices
dc=pd.DataFrame(pd.get_dummies(df_q[["Surv","Classe","Genre","Port","AgeQ","PrixQ"]]))
dc.head()

In [ ]:
# Table des indicatrices
df1=pd.get_dummies(df_q[["Surv","Classe","Genre","Port","AgeQ","PrixQ"]])
# Une seule indicatrice par variable binaire
df1=df1.drop(["Surv_Vnon","Genre_Gmas"],axis=1)
# Variables quantitatives
df2=df[["Age","Prix"]]
# Concaténation
df_c=pd.concat([df1,df2],axis=1)
# Vérification
df_c.columns

Extraction des échantillons d'apprentissage et test.


In [ ]:
# variables explicatives
T=df_c.drop(["Surv_Voui"],axis=1)
# Variable à modéliser
z=df_c["Surv_Voui"]
# Extractions
from sklearn.model_selection import train_test_split
T_train,T_test,z_train,z_test=train_test_split(T,z,test_size=0.2,random_state=11)

Attention: l'échantillon test des données "Titanic" est relativement petit, l'estimation de l'erreur de prévision est donc sujette à caution car probablement de grande variance. Il suffit de changer l'initialisation (paramètre random_state) et ré-exécuter les scripts pour s'en assurer.

3 K plus proches voisins

Les images des caractères sont codées par des variables quantitatives. Le problème de reconnaissance de forme ou de discrimination est adapté à l'algorithme des $k$-plus proches voisins. Le paramètre à optimiser pour contrôler la complexité du modèle est le nombre de voisin n_neighbors. Les autres options sont décrites dans la documentation.


In [ ]:
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=10)
digit_knn=knn.fit(X_train, y_train) 
# Estimation de l'erreur de prévision
# sur l'échantillon test
1-digit_knn.score(X_test,y_test)

Optimisation du paramètre de complexité du modèle par validation croisée en cherchant l'erreur minimale sur une grille de valeurs du paramètre avec cv=5-fold cross validation et n_jobs=-1 pour une exécution en parallèle utilisant tous les processeurs sauf 1. Attention, comme la validation croisée est aléatoire, deux exécutions successives ne donnent pas le même résultat.


In [ ]:
from sklearn.model_selection import GridSearchCV
# grille de valeurs
param=[{"n_neighbors":list(range(1,15))}]
knn= GridSearchCV(KNeighborsClassifier(),param,cv=5,n_jobs=-1)
digit_knnOpt=knn.fit(X_train, y_train)
# paramètre optimal
digit_knnOpt.best_params_["n_neighbors"]

Le modèle digit_knnOpt est déjà estimé avec la valeur "optimale" du paramètre.


In [ ]:
# Estimation de l'erreur de prévision sur l'échantillon test
1-digit_knnOpt.score(X_test,y_test)

In [ ]:
# Prévision
y_chap = digit_knnOpt.predict(X_test)
# matrice de confusion
table=pd.crosstab(y_test,y_chap)
print(table)
plt.matshow(table)
plt.title("Matrice de Confusion")
plt.colorbar()
plt.show()

3.3 Régression logistique

La prévision de la survie, variable binaire des données "Titanic", se prêtent à une régression logistique. Les versions pénalisées (ridge, lasso, elastic net, lars) du modèle linéaire général sont les algorithmes les plus développés dans Scikit-learn au détriment de ceux plus classiques (forward, backward, step-wise) de sélection de variables en optimisant un critère de type AIC. Une version lasso de la régression logistique est testée afin d'introduire la sélection automatique des variables.

Estimation et erreur de prévision du modèle complet sur l'échantillon test.


In [ ]:
from sklearn.linear_model import LogisticRegression
logit = LogisticRegression()
titan_logit=logit.fit(T_train, z_train)
# Erreur sur l'écahntillon test
1-titan_logit.score(T_test, z_test)

In [ ]:
# Coefficients
titan_logit.coef_

Comme pour le modèle linéaire, il faudrait construire les commandes d'aide à l'interprétation des résultats.

Pénalisation et optimisation du paramètre par validation croisée. Il existe une fonction spécifique mais son mode d'emploi est peu documenté; GridSearchCV lui est préférée.


In [ ]:
# grille de valeurs
param=[{"C":[0.01,0.096,0.098,0.1,0.12,1,10]}]
logit = GridSearchCV(LogisticRegression(penalty="l1"),
   param,cv=5,n_jobs=-1)
titan_logitOpt=logit.fit(T_train, z_train)
# paramètre optimal
titan_logitOpt.best_params_["C"]

Estimation de l'erreur de prévision par le modèle "optimal".


In [ ]:
# Erreur sur l'échantillon test
1-titan_logitOpt.score(T_test, z_test)

Petit souci supplémentaire, l'objet produit par GridSearchCV ne connaît pas l'attribut .coef_. Il faut donc ré-estimer le modèle pour connaître les coefficients.


In [ ]:
# Estimation avec le paramètre optimal et coefficients
LogisticRegression(penalty="l1",C=titan_logitOpt.best_params_['C']).fit(T_train, z_train).coef_

Commenter : parcimonie du modèle vs. erreur de prévision.

4 Arbre de décision

4.1 Implémentation

Les arbres binaires de décision (CART: classification and regression trees) s'appliquent à tous types de variables. Les options de l'algorithme sont décrites dans la documentation. La complexité du modèle est gérée par deux paramètres : max_depth, qui détermine le nombre max de feuilles dans l'arbre, et le nombre minimales min_samples_split d'observations requises pour rechercher une dichotomie.

Attention: Même s'il s'agit d'une implémentation proche de celle originale proposée par Breiman et al. (1984) il n'existe pas (encore?) comme dans R (package rpart) un paramètre de pénalisation de la déviance du modèle par sa complexité (nombre de feuilles) afin de construire une séquence d'arbres emboîtés dans la perspective d'un élagage (pruning) optimal par validation croisée. La fonction générique de $k$-fold cross validation GridSearchCV est utilisée pour optimiser le paramètre de profondeur mais sans beaucoup de précision dans l'élagage car ce dernier élimine tout un niveau et pas les seules feuilles inutiles à la qualité de la prévision.

En revanche, l'implémentation anticipe sur celles des méthodes d'agrégation de modèles en intégrant les paramètres (nombre de variables tirées, importance...) qui leurs sont spécifiques. D'autre part, la représentation graphique d'un arbre n'est pas incluse et nécessite l'implémentation d'un autre logiciel libre: Graphviz.

Tout ceci souligne encore les objectifs de développement de cette librairie: temps de calcul et prévision brute au détriment d'une recherche d'interprétation. Dans certains exemples éventuellement pas trop compliqués, un arbre élagué de façon optimal peut en effet prévoir à peine moins bien (différence non significative) qu'une agrégation de modèles (forêt aléatoire ou boosting) et apporter un éclairage nettement plus pertinent qu'un algorithme de type "boîte noire".

4.2 Données "Titanic"

Estimation de l'arbre complet.


In [ ]:
from sklearn.tree import DecisionTreeClassifier
tree=DecisionTreeClassifier()
digit_tree=tree.fit(T_train, z_train) 
# Estimation de l'erreur de prévision
1-digit_tree.score(T_test,z_test)

Optimisation du paramètre de complexité du modèle par validation croisée en cherchant l'erreur minimale sur une grille de valeurs du paramètre avec cv=5-fold cross validation et n_jobs=-1 pour une exécution en parallèle utilisant tous les processeurs sauf 1. Attention, comme la validation croisée est aléatoire et un arbre un modèle instable, deux exécutions successives ne donnent pas nécessairement le même résultat.


In [ ]:
param=[{"max_depth":list(range(2,10))}]
titan_tree= GridSearchCV(DecisionTreeClassifier(),param,cv=5,n_jobs=-1)
titan_opt=titan_tree.fit(T_train, z_train)
# paramètre optimal
titan_opt.best_params_

La valeur "optimale" du paramètre reste trop importante pour la lisibilité de l'arbre. Une valeur plus faible est utilisée.


In [ ]:
tree=DecisionTreeClassifier(max_depth=3)
titan_tree=tree.fit(T_train, z_train)
# Estimation de l'erreur de prévision
# sur l'échantillon test
1-titan_tree.score(T_test,z_test)

Noter l'amélioration de l'erreur.


In [ ]:
# prévision de l'échantillon test
z_chap = titan_tree.predict(T_test)
# matrice de confusion
table=pd.crosstab(z_test,z_chap)
print(table)

Tracer l'arbre avec le logiciel Graphviz.


In [ ]:
from sklearn.tree import export_graphviz
from sklearn.externals.six import StringIO  
import pydotplus
dot_data = StringIO() 
export_graphviz(titan_tree, out_file=dot_data) 
graph=pydotplus.graph_from_dot_data(dot_data.getvalue()) 
graph.write_png("titan_tree.png")

L'arbre est généré dans un fichier image à visualiser pour se rende compte qu'il est plutôt mal élagué et pas directement interprétable sans les noms en clair des variables et modalités.


In [ ]:
from IPython.display import Image
Image(filename='titan_tree.png')

4.3 Données "Caractères"

La même démarche est utilisée pour ces données.


In [ ]:
# Arbre complet
tree=DecisionTreeClassifier()
digit_tree=tree.fit(X_train, y_train) 
# Estimation de l'erreur de prévision
1-digit_tree.score(X_test,y_test)

In [ ]:
# Optimisation par validation croisée
param=[{"max_depth":list(range(5,15))}]
digit_tree= GridSearchCV(DecisionTreeClassifier(),param,cv=5,n_jobs=-1)
digit_treeOpt=digit_tree.fit(X_train, y_train)
digit_treeOpt.best_params_

In [ ]:
# Estimation de l'erreur de prévision
1-digit_treeOpt.score(X_test,y_test)

In [ ]:
# Echantillon test
y_chap = digit_treeOpt.predict(X_test)
# matrice de confusion
table=pd.crosstab(y_test,y_chap)
print(table)

In [ ]:
plt.matshow(table)
plt.title("Matrice de Confusion")
plt.colorbar()
plt.show()

Comme pour les autres méthodes, l'objet GridSearchCV ne contient pas tous les attibuts, dont celui tree, et ne permet pas de construire l'arbre. Il faudrait le ré-estimer mais comme il est bien trop complexe, ce résultat n'est pas produit.

5 Forêts aléatoires

L'algorithme d'agrégation de modèles le plus utilisé est celui des forêts aléatoires (random forest) de Breiman (2001) ce qui ne signifie pas qu'il conduit toujours à la meilleure prévision. Voir la documentation pour la signification de tous les paramètres.

Plus que le nombre d'arbres n_estimators, le paramètre à optimiser est le nombre de variables tirées aléatoirement pour la recherche de la division optimale d'un noeud: max_features. Par défaut, il prend la valeur $\frac{p}{3}$ en régression et $\sqrt{p}$ en discrimination.

5.1 Données "Caractères"


In [ ]:
from sklearn.ensemble import RandomForestClassifier 
# définition des paramètres
forest = RandomForestClassifier(n_estimators=500, 
   criterion='gini', max_depth=None,
   min_samples_split=2, min_samples_leaf=1, 
   max_features='auto', max_leaf_nodes=None,
   bootstrap=True, oob_score=True)
# apprentissage et erreur out-of-bag
forest = forest.fit(X_train,y_train)
print(1-forest.oob_score_)

In [ ]:
# erreur de prévision sur le test
1-forest.score(X_test,y_test)

L'optimisation du paramètre max_features peut être réalisée en minimisant l'erreur de prévision out-of-bag. Ce n'est pas prévu, il est aussi possible comme précédemment de minimiser l'erreur par validation croisée.


In [ ]:
param=[{"max_features":list(range(4,64,4))}]
digit_rf= GridSearchCV(RandomForestClassifier(n_estimators=100),param,cv=5,n_jobs=-1)
digit_rfOpt=digit_rf.fit(X_train, y_train)
# paramètre optimal
digit_rfOpt.best_params_

Comme pour les autres méthodes, l'objet GridSearchCV ne propose pas tous les attributs et donc pas d'erreur out-of-bag ou d'importance des variables. Voir le tutoriel sur la prévision du pic d'ozone pour plus de détails.


In [ ]:
# erreur de prévision sur le test
1-digit_rfOpt.score(X_test,y_test)

In [ ]:
# prévision
y_chap = digit_rfOpt.predict(X_test)
# matrice de confusion
table=pd.crosstab(y_test,y_chap)
print(table)
plt.matshow(table)
plt.title("Matrice de Confusion")
plt.colorbar()
plt.show()

5.2 Données "Titanic"

Même démarche.


In [ ]:
# définition des paramètres
forest = RandomForestClassifier(n_estimators=500, criterion='gini', max_depth=None, 
    min_samples_split=2, min_samples_leaf=1, max_features='auto', max_leaf_nodes=None,bootstrap=True, oob_score=True)
# apprentissage
forest = forest.fit(T_train,z_train)
print(1-forest.oob_score_)

In [ ]:
# erreur de prévision sur le test
1-forest.score(T_test,z_test)

In [ ]:
# optimisation de max_features
param=[{"max_features":list(range(2,15))}]
titan_rf= GridSearchCV(RandomForestClassifier(n_estimators=100),param,cv=5,n_jobs=-1)
titan_rfOpt=titan_rf.fit(T_train, z_train)
# paramètre optimal
titan_rfOpt.best_params_

In [ ]:
# erreur de prévision sur le test
1-titan_rfOpt.score(T_test,z_test)

In [ ]:
# prévision
z_chap = titan_rfOpt.predict(T_test)
# matrice de confusion
table=pd.crosstab(z_test,z_chap)
print(table)

Modifier la valeur du paramètre pour constater sa faible influence sur la qualité plutôt médiocre du résultat.

Attention, comme déjà signalé, l'échantillon test est de relativement faible taille (autour de 180), il serait opportun d'itérer l'extraction aléatoire d'échantillons tests (validation croisée Monte Carlo) pour tenter de réduire la variance de cette estimation et avoir une idée de sa distribution.

C'est fait dans d'autres calepins du dépôt d'apprentissage.

6 Fonction pipeline

Pour enchaîner et brancher (plugin) plusieurs traitements, généralement des transformations suivies d'une modélisation. Utiliser les fonctionnalités de cette section sans modération afin d'optimiser la structure et l'efficacité (parallélisation) de codes complexes.

6.1 Familles de transformations (transformers)

Classification ou régression sont souvent la dernière étape d'un procédé long et complexe. Dans la "vraie vie", les données ont besoin d'être extraites, sélectionnées, nettoyées, standardisées, complétées... (data munging) avant d'alimenter un algorithme d'apprentissage. Pour structurer le code, Sciki-learn propose d'utiliser le principe d'une API (application programming interface) nommée transformer.

Ces fonctionnalités sont illustrées sur les mêmes données de reconnaissance de caractères.


In [ ]:
# Rechargement des données
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
digits = load_digits()
X, y = digits.data, digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

# Plot
sample_id = 42
plt.imshow(X[sample_id].reshape((8, 8)), interpolation="nearest", cmap=plt.cm.Blues)
plt.title("y = %d" % y[sample_id])
plt.show()

Normalisations, réductions


In [ ]:
import numpy as np
from sklearn.preprocessing import StandardScaler
tf = StandardScaler()
tf.fit(X_train, y_train)
Xt_train = tf.transform(X)  
print("Moyenne avant centrage et réduction =", np.mean(X_train))
print("Moyenne après centrage et réduction =", np.mean(Xt_train))
# See also Binarizer, MinMaxScaler, Normalizer, ...

In [ ]:
# Raccourci: Xt = tf.fit_transform(X)
tf.fit_transform(X)

In [ ]:
# NB. La standardisation préalable est indispensable pour certains algorithmes
# notamment les SVM
from sklearn.svm import SVC
clf = SVC()
# Calcul des scores (bien classés)
print("Sans standardisation =", clf.fit(X_train, y_train).score(X_test, y_test))
print("Avec standardisation =", clf.fit(tf.transform(X_train), y_train).score(tf.transform(X_test), y_test))

Sélection de variables par élimination pas à pas

La proicédure RFE (récursive feature selection) supprime une à une les variables les moins significatives ou moins importantes au sens du critère du modèle utilisé; dans cet exemple, il s'agit des forêts aléatoires.


In [ ]:
# Sélection de variables par élémination pas à pas
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestClassifier
tf = RFE(RandomForestClassifier(), n_features_to_select=10, verbose=1)
Xt = tf.fit_transform(X_train, y_train)
print("Shape =", Xt.shape)

# Variables (pixels) sélectionnées
plt.imshow(tf.get_support().reshape((8, 8)), interpolation="nearest", cmap=plt.cm.Blues)
plt.show()

Décomposition, factorisation, réduction de dimension

Possibilité, par exemple, de récupérer les q premières composantes principales de l'ACP comme résultat d'une transformation.


In [ ]:
# par ACP ou SVD
from sklearn.decomposition import PCA
tf = PCA(n_components=2)
Xt_train = tf.fit_transform(X_train)

Fonction de transformation définie par l'utilisateur

Une fonction de transformation ou transformer est définie et s'applique à un jeu de données avec la syntaxe ci-dessous.


In [ ]:
from sklearn.preprocessing import FunctionTransformer
def increment(X):
    return X + 1
tf = FunctionTransformer(func=increment)
Xt = tf.fit_transform(X)
print(X[0])
print(Xt[0])

6.4 Pipelines

Des transformations sont chaînées en une séquence constituant un pipeline.


In [ ]:
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import RFE
#tf = RFE(RandomForestClassifier(), n_features_to_select=10)
# La succession de deux transformeurs constituent un transformeur
tf = make_pipeline(StandardScaler(), RFE(RandomForestClassifier(),n_features_to_select=10))
tf.fit(X_train, y_train)

In [ ]:
Xt_train = tf.transform(X_train)
print("Mean =", np.mean(Xt_train))
print("Shape =", Xt_train.shape)

Une chaîne de transformations suivi d'un classifieur construisent un nouveau classifieur


In [ ]:
clf = make_pipeline(StandardScaler(), 
                    RFE(RandomForestClassifier(), n_features_to_select=10), 
                    RandomForestClassifier())
clf.fit(X_train, y_train)
print(clf.predict_proba(X_test)[:5])

In [ ]:
# L'hyperparamètre est accessible
print("n_features =", clf.get_params()["rfe__estimator__n_estimators"])

L'optimisation des paramètres par validation croisée est obtenue avec la même fonction mais peut prendre du temps si plusieurs paramètres sont cocernés! Le pipeline construit à titre illustratif n'est certainement pas optimal.


In [ ]:
grid = GridSearchCV(clf, param_grid={"rfe__estimator__n_estimators": [5, 10],
                    "randomforestclassifier__max_features": [0.1, 0.25, 0.5]})
grid.fit(X_train, y_train)
print("Valeurs optimales =", grid.best_params_)

6.5 Union de caractéristiques

Des transformations sont appliquées en parallèle pour réunir en un seul ensemble des transformations des données.


In [ ]:
from sklearn.pipeline import make_union
from sklearn.decomposition import PCA, FastICA
tf = make_union(PCA(n_components=10), FastICA(n_components=10))
Xt_train = tf.fit_transform(X_train)
print("Shape =", Xt_train.shape)

6.6 Compositions emboîtées

Comme des pipelines and des unions sont eux-mêmes des estimateurs, ils peuvent être composés dans une structure emboîtée pour construire des combinaisons complexes de modèles comme ceux remportant les concours de type *kaggle.

Les données initiales sont unies aux composantes de l'ACP, puis les variables les plus importantes au sens des forêts aléatoires sont sélectionnées avant de servir à l'apprentissage d'un réseau de neurones. Ce n'est sûrement pas une stratégie optimale !


In [ ]:
from sklearn.ensemble import RandomForestClassifier
from sklearn.neural_network import MLPClassifier

clf = make_pipeline(
    # Build features
    make_union(
        FunctionTransformer(func=lambda X: X), PCA(),), 
    # Select the best features
    RFE(RandomForestClassifier(), n_features_to_select=10),
    # Train
    MLPClassifier(max_iter=500)
)

clf.fit(X_train, y_train)

Effectivement la combinaison n'est pas optimale:


In [ ]:
# erreur de test
1-clf.score(X_test,y_test)