Forêts Aléatoires, Arbres de Décision et Méthodes d'Ensemble : Bagging et Boosting

Introduction aux Forêts Aléatoires

Les forêts aléatoires (Random Forest) constituent l'une des méthodes de machine learning les plus polyvalentes et performantes. Elles combinent les principes de l'apprentissage par ensemble avec les arbres de décision pour créer un classificateur robuste et précis. Leur popularité s'est imposée dans de nombreux domaines : finance, santé, marketing et analyse prédictive.

Principes Fondamentaux

Une forêt aléatoire repose sur deux concepts essentiels :

  • L'aspect "forêt" : aggregation de multiples arbres de décision, chacun apportant sa propre contribution à la décision finale
  • L'aspect "aléatoire" : introduction de variabilité via deux mécanismes distincts

L'Arbre de Décision : Brique Élémentaire

L'arbre de décision fonctionne comme une suite de questions binaires permettant de classifier un échantillon. Chaque nœud interne représente un test sur un attribut, chaque branche représente un résultat de ce test, et chaque feuille représente une classe ou une valeur prédite.

La qualité d'une division se mesure généralement par l'indice de Gini ou l'entropie. L'indice de Gini mesure l'impureté d'un ensemble :

Gini(D) = 1 - Σ(pᵢ)²

où pᵢ représente la proportion d'éléments appartenant à la classe i dans l'ensemble D.

Architecture de la Forêt Aléatoire

Double Randomisation

La construction d'une forêt aléatoire s'appuie sur deux sources de randomisation :

  1. Bootstrap sampling : Pour chaque arbre, on génère un échantillon d'entraînement par tirage aléatoire avec remise depuis le dataset original
  2. Sélection aléatoire des features : À chaque nœud, on considère uniquement un sous-ensemble aléatoire de variables pour déterminer la meilleure division

Processus de Construction

Soit N la taille du dataset et M le nombre total de features :

  1. Générer un échantillon bootstrap de taille N
  2. Choisir un paramètre m (généralement m ≈ √M pour la classification)
  3. Construire un arbre en sélectionnant, à chaque nœud, m features aléatoirement
  4. Laisser l'arbre croître sans élagage

Estimation de l'Erreur : OOB Error

Une caractéristique majeure des forêts aléatoires est la possibilité d'estimer l'erreur de généralisation sans recourir à un ensemble de validation séparé.

Puisque chaque arbre est construit sur un échantillon bootstrap, environ 1/3 des observations ne participent pas à sa construction. Ces échantillons "out-of-bag" (OOB) servent à :

  • Calculer les prédictions de chaque arbre sur ses observations OOB
  • Aggréger les prédictions par vote majoritaire
  • Comparer avec les valeurs réelles pour obtenir l'erreur OOB

Implémentation Python


import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
from sklearn.datasets import make_classification

# Génération de données synthétiques
X, y = make_classification(
    n_samples=1000,
    n_features=20,
    n_informative=15,
    n_redundant=5,
    n_classes=3,
    random_state=42
)

# Division train/test
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)

# Configuration et entraînement
rf_model = RandomForestClassifier(
    n_estimators=200,
    max_depth=None,
    min_samples_split=2,
    min_samples_leaf=1,
    max_features='sqrt',
    bootstrap=True,
    oob_score=True,
    n_jobs=-1,
    random_state=42
)

rf_model.fit(X_train, y_train)

# Évaluation
predictions = rf_model.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, predictions):.4f}")
print(f"OOB Score: {rf_model.oob_score_:.4f}")

# Importance des variables
feature_importances = rf_model.feature_importances_
for idx, importance in enumerate(feature_importances[:5]):
    print(f"Feature {idx}: {importance:.4f}")

Comparaison avec d'Autres Algorrithmes


import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier, GradientBoostingClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC

# Définition des classificateurs
classifiers = {
    'Decision Tree': DecisionTreeClassifier(max_depth=5, random_state=42),
    'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42),
    'AdaBoost': AdaBoostClassifier(n_estimators=100, random_state=42),
    'Gradient Boosting': GradientBoostingClassifier(n_estimators=100, random_state=42),
    'KNN': KNeighborsClassifier(n_neighbors=5),
    'SVM': SVC(kernel='rbf', random_state=42)
}

# Comparaison des performances
results = {}
for name, clf in classifiers.items():
    clf.fit(X_train, y_train)
    score = clf.score(X_test, y_test)
    results[name] = score
    print(f"{name}: {score:.4f}")

# Visualisation
plt.figure(figsize=(10, 6))
plt.bar(results.keys(), results.values(), color='steelblue')
plt.ylabel('Accuracy')
plt.title('Comparaison des Classificateurs')
plt.xticks(rotation=45, ha='right')
plt.ylim(0.8, 1.0)
plt.tight_layout()
plt.show()

Méthodes d'Ensemble : Bagging vs Boosting

Le Bagging

Le Bagging (Bootstrap Aggregating) constitue le fondement des forêts aléatoires :

  • Génération de plusieurs modèles en parallèle sur des échantillons bootstrap
  • Combinaison des prédictions par vote (classification) ou moyenne (régression)
  • Réduction de la variance sans augmentation du biais
  • Chaque modèle possède un poids égal dans la décision finale

Le Boosting

Le Boosting adopte une approche séquentielle :

  • Construction itérative de modèles, chacun corrigeant les erreurs du précédent
  • Pondération des observations : les exemples mal classés reçoivent un poids plus élevé
  • Combinaison pondérée des classificateurs faibles en un classificateur fort

AdaBoost : Adaptive Boosting

L'algorithme AdaBoost ajuste itérativement les poids des observations :

  1. Initialisation : poids uniforme wᵢ = 1/N pour chaque observation
  2. Pour chaque itération t :
    • Entraîner un classificateur faible hₜ
    • Calculer l'erreur pondérée εₜ
    • Calculer le poids du clasificateur αₜ = ½ ln((1-εₜ)/εₜ)
    • Mettre à jour les poids : augmenter pour les erreurs, diminuer pour les succès
  3. Combinaison finale : H(x) = sign(Σ αₜ hₜ(x))

from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier

# Configuration AdaBoost
adaboost_model = AdaBoostClassifier(
    estimator=DecisionTreeClassifier(max_depth=1),
    n_estimators=50,
    learning_rate=1.0,
    algorithm='SAMME.R',
    random_state=42
)

adaboost_model.fit(X_train, y_train)
adaboost_predictions = adaboost_model.predict(X_test)
print(f"AdaBoost Accuracy: {accuracy_score(y_test, adaboost_predictions):.4f}")

GBDT : Gradient Boosted Decision Trees

Le Gradient Boosting construit des arbres de manière séquentielle en optimisant une fonction de coût. Contrairement à AdaBoost qui ajuste les poids des observations, GBDT minimise directement le gradient de la fonction de perte.

Principe Algorithmique

  1. Initialisation : modèle constant F₀ minimisant la perte
  2. Itérations : pour m = 1 à M :
    • Calculer les pseudo-résidus : rᵢₘ = -∂L/∂F(xᵢ)
    • Ajuster un arbre hₘ aux pseudo-résidus
    • Mettre à jour : Fₘ = Fₘ₋₁ + η·hₘ

Le paramètre η (learning rate) contrôle la contribution de chaque arbre.


from sklearn.ensemble import GradientBoostingClassifier

# Configuration GBDT
gbdt_model = GradientBoostingClassifier(
    n_estimators=100,
    learning_rate=0.1,
    max_depth=3,
    min_samples_split=2,
    min_samples_leaf=1,
    subsample=0.8,
    random_state=42
)

gbdt_model.fit(X_train, y_train)
gbdt_predictions = gbdt_model.predict(X_test)
print(f"GBDT Accuracy: {accuracy_score(y_test, gbdt_predictions):.4f}")

Régularisation dans GBDT

Plusieurs techniques préviennent le surapprentissage :

  • Taux d'apprentissage : un η faible nécessite plus d'arbres mais offre une meilleure généralisation
  • Subsampling : utilisation d'une fraction des données pour chaque arbre
  • Early stopping : arrêt de l'apprentissage quand l'erreur de validation cesse de décroître
  • Contraintes sur les arbres : limitation de la profondeur, nombre minimum d'échantillons par feuille

from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import validation_curve

# Analyse de l'impact du learning rate
param_range = [0.01, 0.05, 0.1, 0.2, 0.5]
train_scores, val_scores = validation_curve(
    GradientBoostingClassifier(n_estimators=100, random_state=42),
    X_train, y_train,
    param_name='learning_rate',
    param_range=param_range,
    cv=5,
    scoring='accuracy'
)

print("Learning Rate | Train Score | Val Score")
for i, lr in enumerate(param_range):
    print(f"{lr:^13} | {train_scores[i].mean():^11.4f} | {val_scores[i].mean():^9.4f}")

Tableau Comparatif : Bagging vs Boosting

Caractéristique Bagging (Random Forest) Boosting (AdaBoost/GBDT)
Construction Parallèle Séquentielle
Échantillonnage Bootstrap avec remise Pondération adaptative
Poids des modèles Égaux Proportionnels à la performence
Réduction de Variance Biais et variance
Sensibilité au bruit Faible Élevée
Parallélisation Triviale Complexe

Exemple Pratique : Prédiction de Revenus

Considérons un système prédisant la tranche de revenu d'un individu :

  • Variables : âge, sexe, niveau d'éducation, secteur d'activité, lieu de résidence
  • Classes : moins de 40k, 40k-150k, plus de 150k

import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import LabelEncoder

# Création du dataset
data = {
    'age': [25, 35, 45, 55, 30, 40, 50, 60, 28, 38],
    'education_years': [12, 16, 18, 16, 14, 20, 16, 12, 18, 14],
    'experience': [2, 10, 20, 30, 5, 15, 25, 35, 4, 12],
    'sector_tech': [0, 1, 1, 0, 1, 1, 0, 0, 1, 1],
    'income_class': [0, 1, 2, 1, 1, 2, 1, 0, 1, 2]
}

df = pd.DataFrame(data)

# Préparation
features = ['age', 'education_years', 'experience', 'sector_tech']
X = df[features]
y = df['income_class']

# Entraînement
rf_income = RandomForestClassifier(
    n_estimators=100,
    max_features=2,
    random_state=42
)
rf_income.fit(X, y)

# Prédiction pour un nouvel individu
new_person = pd.DataFrame({
    'age': [35],
    'education_years': [18],
    'experience': [10],
    'sector_tech': [1]
})

prediction = rf_income.predict(new_person)
probabilities = rf_income.predict_proba(new_person)

print(f"Classe prédite: {prediction[0]}")
print(f"Probabilités: {probabilities[0]}")

Conclusions et Recommandations

Le choix entre Random Forest et méthodes de Boosting dépend du contexte :

  • Random Forest : robuste, facile à paramétrer, résistant au surapprentissage, parallélisable
  • GBDT : potentiellement plus précis, plus de paramètres à ajuster, risque de surapprentissage
  • AdaBoost : sensible au bruit, efficace pour les problèmes simples

Pour la plupart des applications, commencer par Random Forest offre un excellent compromis entre performance et simplicité. GBDT reste le choix privilégié pour les compétitions et les applications nécessitant une précision maximale.

Étiquettes: Random Forest Decision Tree Bagging AdaBoost GBDT

Publié le 9 septembre à 21h33