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.
Scikit-learn
vs. RL'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.
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. scikit-learn
sont souvent plus efficaces et utilisent implicitement les capacités de parallélisation.En conséquences:
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.
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.
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.
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.
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$.
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)
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.
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()
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.
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".
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')
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.
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.
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()
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.
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.
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()
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))
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()
In [ ]:
# par ACP ou SVD
from sklearn.decomposition import PCA
tf = PCA(n_components=2)
Xt_train = tf.fit_transform(X_train)
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])
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_)
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)
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)