Niveau
Moyen
Env.
Google Colab
Code
Python
Sources

Evaluez l’importance des variables (« Feature Importance »)

Le Machine Learning fait sans doute peur aujourd’hui à cause de son coté boite noire.

Il est vrai que cette approche s’éloigne complètement de l’approche dite traditionnelle à base de règles, qui s’apparente souvent d’ailleurs avec le temps plutôt à un amoncellement d’exceptions autour de règles bien établies. Bref, L’approche « mathématiques » (statistiques / Probabilités) fait peur car nous comprenons difficilement la raison des choix du modèle qui a été conçu. Dans cet article nous allons voir comment ces approches statistiques permettent de trouver quelles variables permettent de déterminer ce ou ces choix du modèle.

C’est finalement bien là l’essence du Machine Learning: Trouver les éléments/variables qui on conduit à faire un choix ou prendre une décision non ? Alors je vous propose de découvrir cette discipline qui tente de donner plus d’explication aux decisions que prennent les modèles de ML: l’eXplainable AI (ou XAI) …

Introduction

Comme d’habitude, je vais utiliser Google colab & Python pour illustrer mes propos.

Pour cet article on va travailler sur des modèles directement interprétables (Régression linéaires, Régressions Logistiques, Random Forest & XGBoost). On appelle aussi ces modèles des modèles GlassBox, car par nature ils produisent les informations nécessaires à leur interpretation (a contrario des modèles de type Black Box). Pourquoi ? tout simplement parce que ces modèles fournissent naturellement et donc sans qu’on leur demande les informations que l’on cherche. Une autre approche consistera à extraire des informations du modèle. Nous irons à ce titre plus loin – mais dans d’autres articles – avec SHAP (Shapley Additive exPlanations) et LIME par exemple.

Voyons déjà ce que ces 3 modèles nous proposent en standard:

  • Regression liléaire (sklearn)
  • Random Forest (sklearn)
  • XGBoost Regression

Récupération du jeu de données

Je vais par ailleurs prendre comme jeu de données le jeu (si connu) des prix immobiliers de Californie founit de base avec Google colab (dans le répertoire /content/sample_data/)

from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LinearRegression 
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestRegressor
from xgboost import XGBRegressor
import matplotlib.pyplot as plt
import pandas as pd

from google.colab import data_table
# Display smart tables into colab ;-)
data_table.enable_dataframe_formatter()

train = pd.read_csv('/content/sample_data/california_housing_train.csv')
test = pd.read_csv('/content/sample_data/california_housing_test.csv')
all_data = pd.concat([train, test], axis=0)

Vous aurez peut être constaté 2 choses:

  • Pour commencer l’utilisation de l’objet data_table. C’est un petit plus (une astuce) de Google colab qui vous permet d’afficher un DataFrame de manière beaucoup plus intelligente et dynamique ! essayez et vous allez l’adopter. Vous pourrez naviguer dans votre table, faire des filtres, etc. et le tout directement dans votre Notebook.
  • Nous allons concaténer les jeux de données d’entrainement et de test. Pas d’angoisse nous re-diviserons le tout plus tard, mais celà nous permet de vérifier par exemple la qualité des données globales en une seule passe.

Analyse « technique » préalable de nos données

L’enjeu de cet article n’est pas de montrer la phase de préparation de données (plutot l’inverse en fait), néanmoins voyons ce à quoi nous avons à faire:

Regardons nos colonnes :

all_data.columns
Index(['longitude', 'latitude', 'housing_median_age', 'total_rooms',
       'total_bedrooms', 'population', 'households', 'median_income',
       'median_house_value'],
      dtype='object')

Nous avons 8 variables, et l’enjeu du modèle sera de prédire le prix d’une maison ( « median_house_value » ) en fonction d’autres variables comme le nombre de pièces, les coordonnées géographiques, etc.

Mais voilà, si nous avons des données, nous ne savons pas quelle variable influence réellement notre modèle !

Seconde étape obligatoire, vérifier que nos données sont bien renseignées (pas de Null !), pour cela une simple technique consiste à faire la somme pour chaque colonne des valeurs nulles:

all_data.isnull().sum()
longitude             0
latitude              0
housing_median_age    0
total_rooms           0
total_bedrooms        0
population            0
households            0
median_income         0
median_house_value    0
dtype: int64

Si tous les résultats sont à zéro cela signifie que nous n’aurons pas à gérer les trous dans le jeu de données (bonne nouvelle) !

Vérifions maintenant le type des variables :

all_data.dtypes
longitude             float64
latitude              float64
housing_median_age    float64
total_rooms           float64
total_bedrooms        float64
population            float64
households            float64
median_income         float64
median_house_value    float64
dtype: object

Excellent, nous n’avons que des valeurs numériques.

Nous pouvons donc commencer à créer notre modèle…

Rapide Analyse fonctionnelle des données

Il est toujours intéressant de regarder (quand c’est possible) si une relation existe entre le l’étiquette (« median_house_value ») et chacune des variables.

all_data.plot.scatter("median_income", "median_house_value")
Ce graphe montre clairement une légère corrélation (même si relativement éloignée) entre les deux variables : « median_income » et « median_house_value ». Ce jeu de donnée semble être donc un bon candidat pour une régression linéaire. Voyons ça !

Préparation des données

Avant tout nous devons préparer nos données, pour cela créons une fonction que nous utiliserons pour chaque modèle :

scaler = StandardScaler()
def prepare_data(data, scale=True):
  names = data.columns
  if (scale == True):
    scaled_data = scaler.fit_transform(data)
    data = pd.DataFrame(scaled_data, columns=names)
  #X_features = ["median_income", "latitude", "longitude", "housing_median_age", "total_bedrooms", "total_rooms", "population", "households"] 
  y_label = ["median_house_value"]
  X = data[names]
  del X["median_house_value"]
  y = data[y_label]
  x_train,x_test,y_train,y_test=train_test_split(X, y, test_size=0.2, random_state=1)
  return x_train,x_test,y_train,y_test

x_train, x_test, y_train, y_test = prepare_data(all_data)

Notez ici que j’ai mis à l’échelle nos données car elle étaient de nature différente. C’est une étape très importante pour avoir une analyse effective à posteriori de l’influence de chaque varialbes du modèle.

Régression Linéaire

La modélisation d’une regression linéaire avec sklearn est plutôt rapide:

model_1_regression = LinearRegression()
model_1_regression.fit(x_train, y_train)
y_test_prediction = model_1_regression.predict(x_test)
print(f"Score / training data: {round(model_1_regression.score(x_train, y_train)*100, 1)} %")
print(f"Score / test data: {round(model_1_regression.score(x_test, y_test)*100, 1)} %")
Score / training data: 64.2 %
Score / test data: 62.3 %

Le score est lui plutôt mauvais. Ce n’est pas étonnant quand on voir le graphe précédent et la dispersion des données autour d’une droite potentielle. Un score mérité donc ! mais ce n’est pas vraiment ce qui nous intéresse ici.

Regardons comment chaque variable à influencé le modèle :

def display_feat_imp_reg(reg):
  feat_imp_reg = model_1_regression.coef_[0]
  reg_feat_importance = pd.DataFrame(columns=["Feature Name", "Feature Importance"])
  reg_feat_importance["Feature Name"] = pd.Series(reg.feature_names_in_)
  reg_feat_importance["Feature Importance"] = pd.Series(feat_imp_reg)
  reg_feat_importance.plot.barh(y="Feature Importance", x="Feature Name", title="Feature importance", color="red")

display_feat_imp_reg(model_1_regression)

Clairement la variable (colonne) median_income a été la plus influente pour obtenir ce résultat. On voir ensuite que le nombre de chambre est aussi un facteur de choix important juste après.

Ok, voyons maintenant avec un algorithme de type « Random Forest »…

« Random Forest »

Une fois de plus avec sklearn celà ne pose pas de problème et l’entrainement est plutot rapide:

model_2_rforest = RandomForestRegressor()
model_2_rforest.fit(x_train, y_train)
print(f"Score / training data: {round(model_2_rforest.score(x_train, y_train)*100, 1)} %")
print(f"Score / test data: {round(model_2_rforest.score(x_test, y_test)*100, 1)} %")
Score / training data: 97.5 %
Score / test data: 81.0 %

Sans surprise le score est bien meilleur, mais voyons quelles ont été les variables/colonnes à l’origine de ce score:

def display_feat_imp_rforest(rforest):
  feat_imp = rforest.feature_importances_
  df_featimp = pd.DataFrame(feat_imp, columns = {"Feature Importance"})
  df_featimp["Feature Name"] = x_train.columns
  df_featimp = df_featimp.sort_values(by="Feature Importance", ascending=False)
  print(df_featimp)
  df_featimp.plot.barh(y="Feature Importance", x="Feature Name", title="Feature importance", color="red")

display_feat_imp_rforest(model_2_rforest)
   Feature Importance        Feature Name
7            0.520823       median_income
0            0.160318           longitude
1            0.150686            latitude
2            0.063610  housing_median_age
5            0.034908          population
4            0.026310      total_bedrooms
3            0.024800         total_rooms
6            0.018545          households
Une fois de plus et sans surprise c’est la variable « median_income » qui est de loin (cette fois ci) à l’origine de ce score. On remarque qu’n seconde position ce sont par contre les coordonnées géographique qui aident à la décision/calcul du cout.

Regression avec XGBoost

Pour finir nous allons utiliser mon petit favori l’algorithme XGBoost pour ce calcul de régression:

model_3_xgboost = XGBRegressor(n_estimators=50, 
                     max_depth=7, 
                     eta=0.1, 
                     subsample=0.7, 
                     colsample_bytree=0.8, 
                     objective ='reg:squarederror')
model_3_xgboost.fit(x_train, y_train)
scores = cross_val_score(model_3_xgboost, x_train, y_train,cv=10)
print("Training Data Mean cross-validation score: %.2f" % scores.mean())
scores = cross_val_score(model_3_xgboost, x_test, y_test,cv=10)
print("Test Data Mean cross-validation score: %.2f" % scores.mean())
Training Data Mean cross-validation score: 0.83
Test Data Mean cross-validation score: 0.78

Je n’ai pas ajusté les hyperparametres ici (d’où un score très discutable), mais regardons surtout l’influence des variables :

def display_feat_imp_rforest(model):
  feature_imp = model_3_xgboost.get_booster().get_score(importance_type='weight')
  keys = list(feature_imp.keys())
  values = list(feature_imp.values())
  data = pd.DataFrame(data=values, index=keys, columns=["score"]).sort_values(by = "score", ascending=False)
  data.nlargest(40, columns="score").plot(kind='barh', figsize = (10,5)) 

display_feat_imp_rforest(model_3_xgboost)

Voilà que l’ordre est encore bouleversé: nous avons maintenant les coordonnées géographiques en première place vient ensuite la variable median_income !

Conclusion

Voilà qui nous donne à réfléchir. Nous avons 3 modèles qui ont donné 3 résultats différents. Et … les scores qui sont aussi différents nous orientent sur l’importance des variable sur un modèle fiable bien sur. A vrai dire ce n’est pas toujours idéal comme approche car pas tellement fiable. On peut être en effet être tenté de le comparer chacun des graphiques que l’on a vu mais il faut garder en tête que chacun des modèles est différent et que donc ils peuvent prendre des décisions de manières totalement différentes (ce que nous avons vu).

Il faut donc avoir une autres approche: calculer la permutation importance par exemple, vérifier la corrélations entre variables, ou encore calculer les valeurs de SHAP (Shapley Additive exPlanations), mais ça c’est une autre histoire que nous verrons dans un autre article.

Partager cet article

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.