Bagging (Bootstrap Aggregating) : Guide complet — Principes, Exemples et Implémentation Python
Résumé
Le bagging (Bootstrap Aggregating) est une méthode d’ensemble introduite par Léo Breiman en 1996. Son principe est simple mais puissant : entraîner plusieurs modèles identiques sur des échantillons bootstrap (tirages avec remise) du jeu d’entraînement, puis agréger leurs prédictions par moyenne (régression) ou vote majoritaire (classification). Le bagging réduit considérablement la variance des modèles instables — comme les arbres de décision — sans augmenter le biais. Il forme le fondement théorique de nombreuses méthodes d’ensemble modernes, dont la Random Forest.
Principe mathématique du bagging bootstrap
Échantillonnage bootstrap
Soit un jeu d’entraînement D = {(x₁, y₁), …, (xₙ, yₙ)} de taille n. Le bootstrap consiste à générer B échantillons D₁, D₂, …, D_B de même taille n, chacun obtenu par tirage avec remise depuis D.
Pour chaque échantillon D_b, on entraîne un modèle f_b. Après les B entraînements, on dispose d’un ensemble de modèles {f₁, f₂, …, f_B}.
Agrégation des prédictions
En régression, la prédiction finale est la moyenne arithmétique :
ŷbagging(x) = (1/B) × Σ{b=1}^{B} f_b(x)
En classification, on utilise le vote majoritaire : la classe prédite est celle qui reçoit le plus de voix parmi les B modèles.
Réduction de variance
Supposons que chaque modèle f_b(x) ait la même variance σ² et que les modèles soient indépendants (approximation). La variance du prédicteur agrégé est :
Var(ŷ_bagging) = σ² / B
La variance diminue donc proportionnellement au nombre B de modèles. En pratique, les modèles ne sont pas parfaitement indépendants (ils partagent le même jeu d’origine), mais la réduction de variance reste substantielle — en particulier pour les modèles à haute variance comme les arbres de décision non élagués.
Note importante : le bagging n’a guère d’effet sur les modèles à faible variance et biais élevé (régression linéaire, par exemple). Son efficacité est maximale pour les algorithmes instables : ceux dont les prédictions varient fortement en réponse à de petits changements dans les données d’entraînement.
Probabilité d’inclusion bootstrap
Chaque observation a une probabilité de 1 − (1 − 1/n)ⁿ ≈ 1 − e⁻¹ ≈ 0,632 d’être sélectionnée dans un échantillon bootstrap donné. Environ 63,2 % des données originales apparaissent dans chaque échantillon, les 36,8 % restants constituent l’échantillon Out-of-Bag (OOB), utilisable pour l’estimation de l’erreur sans jeu de validation séparé.
Intuition : pourquoi le bagging bootstrap fonctionne
Imaginez que vous consultiez B médecins indépendants pour un diagnostic complexe. Chaque médecin a son propre raisonnement, ses propres biais, ses propres angles morts. Individuellement, chacun peut se tromper. Mais si vous prenez la décision majoritaire parmi les B avis, l’erreur collective est généralement moindre que celle d’un seul médecin.
Le bagging bootstrap applique exactement cette logique aux modèles de machine learning :
- Chaque échantillon bootstrap est l’« expérience vécue » unique de chaque modèle.
- Les modèles apprennent des patterns légèrement différents parce qu’ils voient des sous-ensembles différents des données.
- En agrégeant, on lisse les erreurs idiosyncratiques de chaque modèle.
C’est l’équivalent algorithmique de la sagesse des foules : la moyenne de nombreuses opinions imparfaites est souvent meilleure que l’opinion la plus éclairée prise isolément.
Implémentation Python avec scikit-learn
BaggingClassifier sur DecisionTreeClassifier
Comparons un arbre de décision unique avec un ensemble baggé de 50 arbres :
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier, RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report
import numpy as np
# Données synthétiques
X, y = make_classification(
n_samples=5000, n_features=20, n_informative=15,
n_redundant=5, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Arbre de décision unique (profondeur non limitée)
tree = DecisionTreeClassifier(random_state=42)
tree.fit(X_train, y_train)
tree_pred = tree.predict(X_test)
tree_acc = accuracy_score(y_test, tree_pred)
print(f"Arbre seul : {tree_acc:.4f}")
# Bagging de 50 arbres de décision
bagging = BaggingClassifier(
estimator=DecisionTreeClassifier(),
n_estimators=50,
max_samples=1.0,
bootstrap=True,
random_state=42,
n_jobs=-1
)
bagging.fit(X_train, y_train)
bag_pred = bagging.predict(X_test)
bag_acc = accuracy_score(y_test, bag_pred)
print(f"Bagging (50 arbres): {bag_acc:.4f}")
# Comparaison avec Random Forest
rf = RandomForestClassifier(n_estimators=50, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
rf_acc = accuracy_score(y_test, rf.predict(X_test))
print(f"Random Forest : {rf_acc:.4f}")
La différence est souvent frappante : un arbre profond surajuste (overfitting) et peut atteindre une précision correcte sur l’entraînement mais médiocre sur le test. Le bagging bootstrap réduit ce surajustement en moyennant les prédictions de 50 arbres qui ont chacun appris sur des données légèrement différentes.
BaggingRegressor pour la régression
from sklearn.datasets import make_regression
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import BaggingRegressor
from sklearn.metrics import mean_squared_error, r2_score
# Données de régression
X_reg, y_reg = make_regression(
n_samples=3000, n_features=15, noise=15.0, random_state=42
)
X_train_r, X_test_r, y_train_r, y_test_r = train_test_split(
X_reg, y_reg, test_size=0.2, random_state=42
)
# Arbre seul
tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(X_train_r, y_train_r)
print(f"Arbre seul (MSE) : {mean_squared_error(y_test_r, tree_reg.predict(X_test_r)):.2f}")
# BaggingRegressor
bag_reg = BaggingRegressor(
estimator=DecisionTreeRegressor(),
n_estimators=100,
random_state=42
)
bag_reg.fit(X_train_r, y_train_r)
bag_pred_r = bag_reg.predict(X_test_r)
print(f"Bagging (MSE) : {mean_squared_error(y_test_r, bag_pred_r):.2f}")
print(f"Bagging (R²) : {r2_score(y_test_r, bag_pred_r):.4f}")
Estimation Out-of-Bag (OOB)
L’échantillon Out-of-Bag permet d’estimer l’erreur de généralisation sans jeu de validation séparé. Chaque observation est prédite par les modèles qui ne l’ont pas vue pendant l’entraînement :
bagging_oob = BaggingClassifier(
estimator=DecisionTreeClassifier(),
n_estimators=100,
oob_score=True,
random_state=42,
n_jobs=-1
)
bagging_oob.fit(X_train, y_train)
print(f"Score OOB : {bagging_oob.oob_score_:.4f}")
print(f"Score test : {bagging_oob.score(X_test, y_test):.4f}")
Le score OOB est une estimation non biaisée de la performance en généralisation, comparable à une validation croisée mais beaucoup plus rapide car elle réutilise l’entraînement existant.
Visualisation de la convergence du bagging
import matplotlib.pyplot as plt
# Analyse de la convergence en fonction du nombre d'estimateurs
scores = []
for n in range(1, 101):
model = BaggingClassifier(
estimator=DecisionTreeClassifier(),
n_estimators=n,
random_state=42
)
model.fit(X_train, y_train)
scores.append(accuracy_score(y_test, model.predict(X_test)))
plt.figure(figsize=(10, 6))
plt.plot(range(1, 101), scores, marker='o', markersize=3,
linewidth=1.5, color='steelblue')
plt.axhline(y=tree_acc, color='red', linestyle='--',
label=f'Arbre seul ({tree_acc:.3f})')
plt.xlabel("Nombre d'estimateurs (B)")
plt.ylabel('Précision')
plt.title('Convergence du bagging bootstrap en fonction de B')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
Cette courbe montre typiquement une amélioration rapide jusqu’à B ≈ 20-30, puis un plateau où ajouter des estimateurs n’apporte plus de gain significatif.
Hyperparamètres clés du bagging bootstrap
| Paramètre | Description | Valeur par défaut | Impact |
|---|---|---|---|
n_estimators |
Nombre de modèles dans l’ensemble | 10 | ↑ améliore la réduction de variance, mais rend les gains marginaux au-delà d’un certain seuil |
max_samples |
Proportion ou nombre d’échantillons par modèle | 1.0 (100 %) | Plus petit = plus de diversité, mais chaque modèle est moins précis |
max_features |
Proportion ou nombre de features par modèle | 1.0 | Limiter augmente la diversité (principe de la Random Forest) |
bootstrap |
Tirage avec remise des échantillons | True | Sans bootstrap, le bagging perd son effet de régularisation |
bootstrap_features |
Tirage avec remise des features | False | Utile quand le nombre de features est très élevé |
oob_score |
Calcul du score Out-of-Bag | False | Permet l’estimation de l’erreur sans validation croisée |
Guide pratique de réglage
- Commencez avec
n_estimators=50à100. Augmenter au-delà de 100 apporte rarement un gain significatif. - max_samples=0.8 peut améliorer la diversité des modèles au prix d’une légère baisse de performance individuelle.
- max_features < 1.0 transforme progressivement le bagging bootstrap en Random Forest : c’est le mécanisme de double randomisation (échantillons × features) qui rend la Random Forest si puissante.
- Activez
oob_score=Truepour obtenir une estimation gratuite de la généralisation pendant l’entraînement.
Avantages du bagging bootstrap
- Réduction massive de la variance : particulièrement efficace pour les modèles instables (arbres de décision profonds, k-NN avec petit k, réseaux de neurones).
- Parallélisation triviale : chaque modèle f_b est entraîné indépendamment des autres — le bagging bootstrap s’adapte parfaitement au calcul distribué avec
n_jobs=-1. - Pas de surajustement accru : contrairement au boosting, le bagging ne surajuste pas quand on augmente B.
- Estimation OOB gratuite : l’échantillon Out-of-Bag fournit une validation intégrée sans coût supplémentaire.
- Simplicité conceptuelle : le principe est facile à expliquer et à implémenter.
Limites du bagging bootstrap
- Inefficace pour les modèles stables : la régression linéaire, la régression logistique ou les SVM linéaires bénéficient peu du bagging bootstrap car leur variance est déjà faible.
- Interprétabilité réduite : un ensemble de 100 arbres est beaucoup moins interprétable qu’un seul arbre.
- Coût computationnel : entraîner B modèles coûte B fois plus en temps et en mémoire.
- Pas de correction du biais : si les modèles de base ont un biais élevé (sous-apprentissage), le bagging ne le corrige pas — il ne réduit que la variance.
- Moins puissant que le boosting : dans de nombreux benchmarks, le gradient boosting et XGBoost surpassent le bagging simple en précision pure, au prix d’une plus grande complexité.
- Pas de sous-échantillonnage des features par défaut : contrairement à la Random Forest, le bagging standard utilise toutes les features pour chaque modèle, ce qui limite la diversité de l’ensemble.
4 cas d’usage concrets du bagging bootstrap
1. Détection de fraude bancaire
Les transactions frauduleuses sont rares et les données sont déséquilibrées. Un seul arbre de décision a tendance à surajuster sur les patterns spécifiques au jeu d’entraînement. Le bagging de 100 arbres, chacun entraîné sur un sous-échantillon bootstrap différent, capture une plus grande variété de schémas de fraude tout en réduisant les faux positifs :
bagging_fraud = BaggingClassifier(
estimator=DecisionTreeClassifier(max_depth=8),
n_estimators=100,
max_samples=0.7,
oob_score=True,
random_state=42
)
bagging_fraud.fit(X_train_fraud, y_train_fraud)
2. Prédiction de prix immobilier
Les données immobilières contiennent de nombreuses interactions non linéaires entre les variables (quartier × surface × année de construction). Un BaggingRegressor avec 200 arbres capture ces interactions mieux qu’un modèle linéaire, tout en restant plus robuste qu’un seul arbre sujet au surajustement.
3. Diagnostic médical assisté
L’analogie des médecins multiples est ici littérale : entraîner 50 classifieurs sur des échantillons bootstrap de données médicales permet de combiner différentes « perspectives » sur les mêmes symptômes, réduisant le risque qu’un pattern aberrant dans l’ensemble d’entraînement influence le diagnostic final. Le score OOB offre une validation rigoureuse sans réduire la taille du jeu d’entraînement.
4. Classification de texte avec des classifieurs faibles
On peut appliquer le bagging bootstrap à n’importe quel classifieur. Par exemple, des classifieurs Naïve Bayes individuellement médiocres sur un corpus de texte peuvent atteindre une précision remarquable quand on en agrège 50 par vote majoritaire — chaque classifieur capturant des aspects différents du vocabulaire grâce aux échantillons bootstrap.
Le bagging bootstrap dans l’écosystème des méthodes d’ensemble
Le bagging occupe une place centrale dans la famille des méthodes d’ensemble :
- Random Forest = bagging + sélection aléatoire des features (article #025). C’est le cas le plus célèbre et le plus utilisé du bagging bootstrap.
- AdaBoost = pondération adaptative des échantillons mal classés (article #030). Contrairement au bagging, les échantillons sont tirés sans remise avec des poids ajustés.
- Gradient Boosting = apprentissage séquentiel des erreurs résiduelles (article #027). Chaque modèle corrige les erreurs du précédent, là où le bagging entraîne tous les modèles en parallèle.
La distinction fondamentale est claire : le bagging cherche à réduire la variance par agrégation de modèles indépendants, tandis que le boosting cherche à réduire le biais par apprentissage séquentiel des erreurs.
Conclusion
Le bagging bootstrap est l’une des idées les plus élégantes du machine learning moderne. En exploitant la puissance de la moyenne sur des modèles entraînés sur des échantillons bootstrap différents, il transforme des algorithmes instables en prédicteurs robustes et fiables. Bien que des méthodes plus sophistiquées comme le gradient boosting le dépassent en précision brute sur certains benchmarks, le bagging reste incontournable pour sa simplicité, sa parallélisation native et sa résistance au surajustement.
Pour tout problème où un arbre de décision seul surajuste, le bagging bootstrap est la première amélioration à tenter — souvent avec des résultats remarquables.
Voir aussi
- Résoudre le Problème d’Entretien Maximum Subarray avec Python
- Implémenter l’Algorithme de Dekker en Python : Synchronisation des Threads Efficace

