Anima Mundi

Boost ton ML : XGBoost facile & efficace avec R !

Cet article requiert d'avoir quelques notions de base du langage R. Il s'adresse à tout professionnel ou amateur de la modélisation (pardon, du Machine Learning ;-)). L'objectif est d'acquérir le savoir-faire nécessaire pour entraîner et évaluer les modèles XGBoost avec R. Mon choix s'est porté sur XGBoost car en plus d'être très performant pour une large palette de problématiques, il a d'autres caractéristiques qui le rendent assez indispensable à avoir dans sa boîte à outils. Nous nous attacherons dans ce qui suit à écarter toutes les difficultés & complications de manière claire et pragmatique.

A l'issue de ce tuto, tu sauras donc :

  1. Ce qu'est XGBoost
  2. Comment préparer tes données
  3. Comment ajuster et optimiser ton modèle avec XGBoost
  4. Comment évaluer, visualiser et explorer ton modèle

Régression Linéaire

I. XGBoost, pourquoi on en parle autant ?

Parce qu'il est vraiment BON et aussi un peu beaucoup grâce à Kaggle.

En effet, son utilisation est assez systématique dans les solutions gagnantes de Challenges Kaggle portant sur des données structurées. XGBoost a une excellente précision et s'adapte bien à tous types de données et de problématiques, ce qui en fait l'algo idéal quand la performance et la rapidité priment.

Nous verrons dans la suite ce qui le rend si performant.

X G B O O S T (Sans exagération aucune.)

II. XGBoost, qu'est ce que c'est & comment ça marche ?

XGBoost (eXtreme Gradient Boosting) est une implémentation open source optimisée et parallélisée du Gradient Boosting, créée par Tianqi Chen, Doctorant à l'Université de Washington. XGBoost utilise des arbres de décision (comme Random Forest) pour résoudre des problèmes de classification (binaire & multiclasse), de classement (ranking) et de régression. Nous sommes donc dans le domaine de l'apprentissage supervisé.

XGBoost fait partie de la famille des méthodes ensembliste. La différence par rapport aux méthodes classiques, c'est qu'au lieu d'entraîner le meilleur modèle possible sur les données, on va en entraîner des milliers sur des sous-parties diverses du jeu de données d'apprentissage, puis les faire voter pour prendre notre décision.

En effet, avant de prendre une décision "difficile", n'est-il pas d'usage de demander leur avis à plusieurs personnes de son entourage ? Ainsi, on recueille plusieurs points de vue sur le problème, plusieurs manières de l'appréhender, et on a donc plus d'informations pour prendre la décision finale.

Maintenant, XGBoost comprend quelques subtilités qui le rendent véritablement supérieur. Parmi elles, le procédé de Boosting. Le principe du boosting est d'améliorer la qualité de prédiction d'un modèle médiocre (weak learner) en donnant de plus en plus de poids aux valeurs difficiles à prédire au cours de l'apprentissage. Ainsi, on oblige le modèle à s'améliorer.

Cependant comme on dit, "With great power comes great responsibility". XGBoost a un peu plus de paramètres à tuner que la moyenne, mais nous verrons qu'il n'y a là rien de bien compliqué quand les choses sont clairement définies. De plus, nous verrons que le package caret apporte une assistance non négligeable dans la mise en oeuvre. Je vais aussi m'efforcer de donner un paramétrage générique et réutilisable pour une diversité de problématiques.

III. XGBoost, pourquoi c'est génial ?

IV. XGBoost en pratique

1. Les données

library(tidyverse)
library(xgboost)
library(caret)
library(readxl)

Le jeu de données choisi comprend 9568 lignes de données collectées sur une centrale électrique au gaz sur une période de 6 ans (2006 à 2011), en fonctionnement à pleine puissance.

Il est à télecharger ici

# Import & création de noms de colonnes plus parlants :
df <- read_excel("Folds5x2_pp.xlsx")
colnames(df) <- c("Temp","VEchap","PressAtm","Humid","ProdElec")
df <- read_excel("C:/Users/M034879/Desktop/corridordigital.github.io/Folds5x2_pp.xlsx")
colnames(df) <- c("Temp","VEchap","PressAtm","Humid","ProdElec")

Voici un aperçu des données :

DT::datatable(head(df,n = 50))

Les variables sont des moyennes horaires de mesures faites grâce à des capteurs dans la centrale. Chaque ligne correspond à une heure de mesure. Elles sont brutes, ni centrées ni réduites :

Prédicteurs (X) :

Variable réponse à prédire (y) :

2. Un peu d'exploration

Nous sommes en présence d'un jeu de données 100% quantitatif. Commençons donc par regarder rapidement les corrélations entre variables.

Pour rappel, le coefficient de corrélation de Pearson [1,1] et mesure l'intensité de la liaison qui peut exister entre deux variables.

"La liaison peut être négative ou positive. Il est égal à 1 dans le cas où l'une des variables est une fonction affine croissante de l'autre variable, à -1 dans le cas où une variable est une fonction affine et décroissante. Les valeurs intermédiaires renseignent sur le degré de dépendance linéaire entre les deux variables. Plus le coefficient est proche des valeurs extrêmes -1 et 1, plus la corrélation linéaire entre les variables est forte"" (Source)

La direction de l'ellipse (haut ou bas) renseigne sur le signe de la corrélation, la couleur sur son intensité :

library(corrplot)
corrplot(cor(df), method = "ellipse")

En représentant les nuages de points des variables deux à deux :

library(GGally)
ggpairs(df)

Logiquement, donc, nous devrions voir que ProdEle décroit en fonction de la Temp :

ggplot(data = df, aes(x = Temp,y = ProdElec)) + geom_point() + geom_smooth(method = 'lm')

Et percevoir une relation croissante entre PressATm et ProdElec :

ggplot(data = df, aes(x = PressAtm,y = ProdElec)) + geom_point() + geom_smooth(method = 'lm')

3. Maintenant, le modèle !

Commençons par diviser le jeu de données en un échantillon de d’entraînement appelé training et un second de test appelé testing:

set.seed(1337) # Pour la 'reproductibilité'

inTrain <- createDataPartition(y = df$ProdElec, p = 0.85, list = FALSE) # 85% des données dans le train, et le rest dans le test 
training <- df[inTrain,]
testing <- df[-inTrain,]

On sépare ensuite le vecteur réponse y de la matrice X des prédicteurs pour les deux échantillons.

Dans le cas où l'on a des variables qualitatives dans les données, il faut les encoder en variables binaires (one-hot encoding/dummy) Les matrices doivent obligatoirement être converties au format xgb.DMatrix :

X_train = xgb.DMatrix(as.matrix(training %>% select(-ProdElec)))
y_train = training$ProdElec
X_test = xgb.DMatrix(as.matrix(testing %>% select(-ProdElec)))
y_test = testing$ProdElec

Passons aux choses sérieuses !


Nous allons utiliser le super package caret (si vous êtes utilisateur R & que vous ne le connaissez pas, courez ICI !).

On va donc commencer par définir un objet trainControl, qui permet de contrôler la manière dont se fait l'entraînement du modèle, assuré par la fonction train().

Ici, nous choisissons une validation croisée (method = 'cv') à 5 folds (number = 5). On choisit également d'autoriser la parallélisation des calculs (allowParallel = TRUE), de réduire la verbosité (verboseIter = FALSE).

xgb_trcontrol = trainControl(
  method = "cv",
  number = 5,  
  allowParallel = TRUE,
  verboseIter = FALSE,
  returnData = FALSE
  )

On définit ensuite une grille de paramètres du modèle XGBoost appelée xgbGrid.

xgbGrid <- expand.grid(nrounds = c(100,200),  
                       max_depth = c(3, 5, 10, 15, 20),
                       colsample_bytree = seq(0.5, 0.9, length.out = 5),
                       ## valeurs par défaut : 
                       eta = 0.1,
                       gamma=0,
                       min_child_weight = 1,
                       subsample = 1
                      )

caret testera chaque combinaisons de ces paramètres dans un modèle distinct. Je me risque ici à faire une description simplifiée de quelques paramètres essentiels:

Vous trouverez ici une explication approfondie et exhaustive.

On a ici 40 combinaisons de paramètres possibles, donc 40 modèles à entraîner. Voici une dizaine d'exemples de combinaisons de paramètres :

DT::datatable(xgbGrid[sample(40,10),])

Allons-y pour l'entraînement :

set.seed(0) 
xgb_model = train(
  X_train, y_train,  
  trControl = xgb_trcontrol,
  tuneGrid = xgbGrid,
  method = "xgbTree"
)

Si c'est un peu lent, c'est normal. Souvenez-vous qu'on entraîne un grand nombre de modèles avec validation croisée.

4. Place à l'évaluation du modèle

Voici les paramètres du modèle optimal trouvé par caret :

xgb_model$bestTune

Et les metrics de performance :

predicted = predict(xgb_model, X_test)
residuals = y_test - predicted
RMSE = sqrt(mean(residuals^2))

y_test_mean = mean(y_test)
tss =  sum((y_test - y_test_mean)^2 )
rss =  sum(residuals^2)
rsq  =  1 - (rss/tss)

l'Erreur-type de prédiction des valeurs de ProdElec (RMSE) sur échantillont de test est de r round(RMSE,3), sachant que ProdElec prend des valeurs entre r round(min(df$ProdElec),3) et r round(max(df$ProdElec),3)

Le coefficient de détermination () de test est de r round(rsq,3)

Pas mal non ?

Graphiquement, lorsqu'on représente ce que prédit le modèle en fonction de l'observé :

options(repr.plot.width=8, repr.plot.height=4)
my_data = as.data.frame(cbind(predicted = predicted,
                            observed = y_test))
# Plot predictions vs test data
ggplot(my_data,aes(predicted, observed)) + geom_point(color = "darkred", alpha = 0.5) + 
    geom_smooth(method=lm)+ ggtitle('Linear Regression ') + ggtitle("Modèle XGBoost: Prédictions VS Observé (test)") +
      xlab("ProdElec prédite") + ylab("ProdElec observée (réél)") + 
        theme(plot.title = element_text(color="darkgreen",size=16,hjust = 0.5),
         axis.text.y = element_text(size=12), axis.text.x = element_text(size=12,hjust=.5),
         axis.title.x = element_text(size=14), axis.title.y = element_text(size=14))

V. Conclusions

On est donc plutôt bons. Voir même très très bons ! :-)

Nous avons pris ici un exemple plutôt simple : 4 prédicteurs, moins de 10 000 lignes, des variables quantitatives uniquement, pas de données manquantes, et des données avec une structure bien présente (corrélations entre variables) et assez facile à déceler graphiquement. J'ai fait ce choix pour concentrer l'attention sur la méthode plutôt que sur les données et leur compréhension. Ce problème aurait pu être résolu assez efficacement et plus rapidement avec un modèle plus simple (régression linéaire, par exemple).

**Il faut savoir que XGBoost brille véritablement en conditions ... eXtrêmes, ou plutôt dans les conditions normales que l'on rencontre *dans la vraie vie*** : données massives, mix de variables quantitatives et qualitatives (one-hot encodées), données manquantes par-ci par-là, liaisons non-linéaires complexes, etc.

Petite précaution à prendre cependant, XGBoost n'est pas le meilleur choix pour les jeux de données comptant plus de colonnes que de lignes, et pour les données 100% qualitatives. Pour d'autres applications telles que la reconnaissance d'image, la Computer Vision, le traitement du langage naturel, XGBoost n'est pas un choix judicieux non plus.

Voilà ! Les commmentaires/questions/critiques/corrections sont les bienvenus.

Merci = D


Sinon pour aller plus loin :

▶️ Watch on YouTube

VI. Sources :

#AI #Tech #french