Le fonctionnement des forêts aléatoires doit maintenant être un peu plus clair pour vous. Il est temps de passer à la pratique en observant les performances de ce type de modèle sur des données réelles 🙂
Dans ce chapitre, on va appliquer l’algorithme des forêts aléatoires sur un exemple concret. Le jeu de données que j’ai choisi est assez connu : il permet de reconnaître l’activité physique à partir de données du smartphone. Il est simple mais possède de nombreuses variables (> 500) ce qui va nous permettre d’étudier un certain nombre de choses. Prêt·e ? Affutez votre Notebook et téléchargez le nouveau dataset ici.
Le dataset
En plus de charger le dataset, vous pouvez aussi observer le fichier de description des différentes variables afin d’avoir une meilleur idée des données à disposition.
Dans un premier temps, étudions le dataset à notre disposition : le "Human Activity Recognition Using Smartphones Data Set".
Ce jeu de données contient les logs de capteurs de smartphone sur une trentaine d'individus en train d'effectuer des activités (s'assoir, se mettre debout, marcher, etc). L'objectif sera de prédire à partir des logs de capteurs le type d'activités que le sujet est en train d'effectuer.
En regardant le fichier de description du dataset, on peut observer qu'il y a beaucoup de features (561). D'emblée, en observant ce qu'elles désignent, on peut se dire qu'il y a une certaine redondance entre toutes ces variables. Dans un premier temps, on va effectuer une modélisation "brute" sans se soucier de nettoyer le jeu de données.
Dans un second temps, on va utiliser cette première modélisation pour mieux comprendre le dataset et ainsi effectuer une seconde modélisation plus efficace en éliminant des variables peu importantes.
On commence par charger les données.
import pandas as pd
train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")
On regarde la taille des données.
print(train.shape)
(7352, 563)
Les données représentent des vecteurs de différentes mesures effectuées par le téléphone (accélération, secousses, etc). La dernière colonne représente l'activité, c'est ce qu'on va essayer de prédire à partir du reste.
Regardons tout d'abord s'il existe des valeurs manquantes :
train.isna().sum()
tBodyAcc-mean()-X 0 tBodyAcc-mean()-Y 0 tBodyAcc-mean()-Z 0 tBodyAcc-std()-X 0 tBodyAcc-std()-Y 0 .. angle(X,gravityMean) 1 angle(Y,gravityMean) 1 angle(Z,gravityMean) 1 subject 1 Activity 1 Length: 563, dtype: int64
Nous allons d'abord supprimer les valeurs manquantes de la target
train = train.loc[train.Activity.notna()]
Ensuite nous allons imputer les valeurs manquantes par la médiane.
train = train.fillna(train.median(), inplace=True)
Vérifions que les données ne contiennent plus de valeurs manquantes :
train.isna().sum().sum()
0
Faites, au besoin, la même chose pour le test, et enfin séparons X et y pour le train et pour le test.
X_train = train[train.columns[:-2]]
y_train = train['Activity']
X_test = test[test.columns[:-2]]
y_test = test['Activity']
On va d'abord éliminer les features redondantes (intuitivement, les coordonnées polaires et cartésiennes doivent être corrélées par exemple ... ) Une première manière de faire serait de réfléchir et se renseigner sur le domaine d'études en question pour pouvoir éliminer des variables qui transmettent des informations similaires où n'influencent pas ou très peu la prédiction que l'on veut effectuer.
La seconde manière est d'utiliser justement une forêt aléatoire (!) de laquelle on va extraire l'importance des features qui la constituent, et ainsi déterminer quelles sont les features les plus importantes à partir de ça.
Application des forêts aléatoires
Une fois les données chargées, on peut déclarer un nouveau modèle de forêts aléatoires pour la classification logiquement appelé dans scikit sklearn.RandomForestClassifier
. On définit comme hyperparamètres 500 pour le nombre d'arbres.
from sklearn.ensemble import RandomForestClassifier
rfc = RandomForestClassifier(n_estimators=500, oob_score=True)
On peut maintenant entraîner notre modèle sur les données brutes, sans autre forme de procès.
model = rfc.fit(X_train, y_train)
Voyons les performances de ce modèle sur le jeu de données d'entraînement
from sklearn.metrics import accuracy_score
pred = rfc.predict(X_test)
print("accuracy {:.2f}".format(accuracy_score(y_test, pred)))
0.93
Pas mal 😎 !
Maintenant que notre modèle est créé, on peut effectuer une sélection des features les plus importantes. Pour cela on va utiliser la fonction SelectFromModel
qui utilise la propriété du modèle qu'on vient de créer model.feature_importances_
qui permet d'évaluer l'importance relative des features fournies à la base (sur une échelle de 0 à 1). Intuitivement, cette importance est calculée en considérant que plus une feature est haute, plus elle contribue à une fraction plus élevée du jeu de donnée d'entraînement et donc des données au global. On considère donc qu'elle a plus d'importance que les features plus bas dans l'arbre. Cette fraction est utilisée comme estimateur de l'importance de la feature dans cet arbre, qu'on peut ensuite généraliser à la forêt entière.
Si on a peu de features, on pourrait les afficher sur un histogramme afin d'évaluer à l'œil si il n'y a pas déjà une sélection à faire comme ici.
Donc en utilisant SelectFromModel
avec un seuil d'importance choisi à l'aide de l'argument threshold
, on peut créer une sélection des features qui sont les plus importantes à la création d'un modèle.
from sklearn.feature_selection import SelectFromModel
select = SelectFromModel(rfc, prefit=True, threshold=0.003)
X_train2 = select.transform(X_train)
print(X_train2.shape)
(7352, 84)
On a divisé par 5 le nombre de features utilisées, pas mal mais voyons si les performances restent similaires. À l'aide de l'argument threshold, on peut choisir le seuil d'importance que l'on souhaite pour les features à sélectionner. On calcule en même temps le gain de temps car c'est ce qui nous intéresse principalement dans l'amélioration des performances.
import timeit
rfc2 = RandomForestClassifier(n_estimators=500, oob_score=True)
start_time = timeit.default_timer()
rfc2 = rfc2.fit(X_train2, y_train)
X_test2 = select.transform(X_test)
pred = rfc2.predict(X_test2)
elapsed = timeit.default_timer() - start_time
accuracy = accuracy_score(y_test, pred)
print("accuracy {:.2f} time {:.2f}s".format(accuracy, elapsed))
accuracy 0.89 time 16.38s
Et pour le modèle avec toutes les features
accuracy 0.93 time 50.04s
On a donc plus que divisé par trois le temps de calcul, sans trop perdre en performances ! C'est plus que respectable pour un premier jet.
Conclusion
A vous maintenant de bidouiller afin d'améliorer les performances du modèle finale en modifiant les différents hyperparamètres de contrôle et trouver la bonne balance entre performances de classification / temps de calcul. Bien sûr le ratio souhaité dépendra de la problématique : est-ce qu'on veut une très bonne performance et peu importe le temps de calcul, ou bien un temps de calcul le plus rapide possible mais avec une performance minimum etc etc