CatBoost : Guide Complet pour le Gradient Boosting avec Données Catégorielles
Résumé
CatBoost (abréviation de Category Boosting) est un algorithme de gradient boosting développé par Yandex en 2017. Sa particularité révolutionnaire réside dans sa capacité à gérer nativement les variables catégorielles sans nécessiter d’encodage préalable comme le one-hot encoding ou l’encoding cible classique. Cette caractéristique majeure élimine une étape fastidieuse du preprocessing tout en améliorant significativement les performances prédictives sur les données contenant de nombreuses variables catégorielles à haute cardinalité.
CatBoost résout deux problèmes fondamentaux du gradient boosting traditionnel : le target leakage (fuite d’information de la cible) et le shift de distribution entre les données d’entraînement et les données de production. Ces deux innovations — l’encodage par cible ordonnée et l’ordered boosting — font de CatBoost l’algorithme de choix pour les compétitions de machine learning sur des données tabulaires et pour les applications industrielles réelles.
Dans ce guide complet, nous explorerons en profondeur le fonctionnement mathématique de CatBoost, son implémentation pratique en Python, ses hyperparamètres essentiels, ainsi que quatre cas d’usage concrets qui illustreront sa puissance exceptionnelle.
Principe Mathématique de CatBoost
Encodage par Cible Ordonnée (Ordered Target Encoding)
Le cœur de l’innovation de CatBoost réside dans son approche d’encodage par cible ordonnée. Pour comprendre cette notion, examinons d’abord le problème que CatBoost résout.
Dans l’encoding cible classique, on remplace chaque catégorie par la moyenne de la variable cible pour cette catégorie. Le problème ? Cela crée un target leakage massif : chaque échantillon « voit » sa propre valeur cible dans l’encodage, ce qui provoque un surapprentissage catastrophique.
CatBoost contourne ce problème grâce à une idée élégante : pour un échantillon i et une catégorie c, la valeur encodée se calcule ainsi :
Valeur encodée(i, c) = (Σj < i [xj == c] · yj + prior) / (Σj < i [xj == c] + 1)
Où :
– La somme Σj < i est prise sur tous les échantillons j précédant i dans une permutation aléatoire des données
– [xj == c] est une fonction indicatrice valant 1 si l’échantillon j possède la catégorie c, 0 sinon
– yj est la valeur cible de l’échantillon j
– prior est une valeur a priori (généralement la moyenne globale de la cible) qui régularise l’estimation, surtout lorsque peu d’échantillons précèdent i
La permutation aléatoire est cruciale : elle garantit que chaque échantillon est encodé uniquement à partir d’observations « précédentes » dans un ordre tiré au hasard, éliminant ainsi le biais de target leakage. En pratique, CatBoost génère plusieurs permutations aléatoires pour construire plusieurs arbres, ce qui renforce encore la robustesse du modèle.
Ordered Boosting (Boosting Ordonné)
L’ordered boosting est la seconde innovation majeure de CatBoost. Dans le gradient boosting classique, on calcule les gradients (résidus négatifs) sur l’ensemble du jeu de données à chaque itération. Ce calcul global crée un autre type de target leakage : le modèle apprend les résidus des données qu’il a déjà vues.
CatBoost résout ce problème en calculant les gradients de manière ordonnée : pour chaque échantillon i dans la permutation, on utilise uniquement le modèle entraîné sur les échantillons j < i (précédents dans l’ordre de permutation). Autrement dit, la prédiction pour l’échantillon i repose sur un sous-modèle qui n’a jamais « vu » cet échantillon.
Cette approche élimine le predictive shift — le décalage entre la distribution des prédictions sur les données d’entraînement et celle sur les données de test — qui afflige les autres algorithmes de gradient boosting.
Formellement, à chaque boosting step t :
1. On génère une permutation aléatoire π des données
2. Pour chaque échantillon i dans l’ordre π, on calcule le gradient en utilisant le modèle Mt-1<i (entraîné uniquement sur les échantillons précédents)
3. On ajuste un nouvel arbre sur ces gradients ordonnés
Combinaison de Features Catégorielles
CatBoost ne se contente pas d’encoder les catégories individuellement. L’algorithme crée automatiquement des combinaisons de features catégorielles pour capturer les interactions entre variables.
Par exemple, si le jeu de données contient les variables catégorielles « Ville » (Paris, Lyon, Marseille) et « Type de quartier » (Centre, Périphérie, Banlieue), CatBoost créera automatiquement la combinaison (Ville, Type de quartier) produisant des catégories comme (Paris, Centre), (Lyon, Banlieue), etc.
Ces combinaisons sont générées dynamiquement pendant l’entraînement des arbres : à chaque nœud de division, l’algorithme considère non seulement les catégories individuelles mais aussi les combinaisons de deux catégories. Pour limiter l’explosion combinatoire (le produit cartésien de deux variables à 100 catégories chacune génère 10 000 combinaisons), CatBoost applique un mécanisme de sélection par split : seules les combinaisons qui améliorent significativement la réduction de loss sont conservées.
Cette approche permet à CatBoost de capturer des interactions complexes entre variables catégorielles sans intervention humaine, tout en maintenant une complexité computationnelle raisonnable.
Intuition : CatBoost Expliqué Simplement
Imaginez que vous devez prédire le prix d’un appartement. Parmi vos variables, vous avez la ville (Paris, Lyon, Marseille), qui est une variable catégorielle.
L’approche naïve consisterait à utiliser un encodage one-hot : Paris = [1, 0, 0], Lyon = [0, 1, 0], Marseille = [0, 0, 1]. Mais cette méthode pose plusieurs problèmes. D’abord, elle ne capture aucune relation entre les villes — Paris et Marseille sont aussi « éloignées » que Paris et Lyon alors que leurs marchés immobiliers peuvent être très différents. Ensuite, si vous avez 500 villes, vous créez 500 colonnes, ce qui rend le modèle lent et sujet au surapprentissage.
Une autre approche serait l’encoding cible : remplacer chaque ville par le prix moyen des appartements dans cette ville. Paris → 450 000 €, Lyon → 280 000 €, Marseille → 200 000 €. Le problème ? Si l’appartement numéro 1 est à Paris et coûte 500 000 €, son encodage utilisera… sa propre valeur de 500 000 € ! C’est comme si un élève trichait en regardant la réponse avant de répondre. C’est exactement ce qu’on appelle le target leakage.
CatBoost fait quelque chose de beaucoup plus intelligent. Il met toutes les données dans un ordre aléatoire — comme un élève qui répond à un quiz sans voir les réponses des autres. Pour l’appartement numéro 1 dans cet ordre, il dit : « Je ne connais pas encore les prix moyens par ville, donc j’utilise la moyenne globale comme point de départ (le prior). » Pour l’appartement numéro 10 qui est aussi à Paris, il dit : « Parmi les 9 appartements précédents dans mon ordre, 3 étaient à Paris avec un prix moyen de 420 000 €, donc j’utilise cette estimation. »
En mélangeant l’ordre plusieurs fois et en faisant la moyenne des résultats, CatBoost obtient un encodage fiable, sans tricher, qui fonctionne aussi bien sur les données d’entraînement que sur les nouvelles données.
C’est l’essence de l’ordered boosting : chaque échantillon est évalué par un modèle qui ne l’a « jamais vu » auparavant, exactement comme dans un véritable quiz où les questions sont posées dans un ordre différent pour chaque élève.
Implémentation Python de CatBoost
Installation et Configuration de Base
Pour utiliser CatBoost, commencez par installer la librairie :
pip install catboost scikit-learn numpy pandas
Classification avec CatBoostClassifier
Voici un exemple complet de classification binaire avec gestion native des variables catégorielles :
import numpy as np
import pandas as pd
from catboost import CatBoostClassifier, Pool
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score
# Création d'un jeu de données exemple
np.random.seed(42)
n_samples = 5000
donnees = pd.DataFrame({
'ville': np.random.choice(['Paris', 'Lyon', 'Marseille', 'Toulouse', 'Bordeaux'], n_samples),
'type_bien': np.random.choice(['Appartement', 'Maison', 'Studio', 'Loft'], n_samples),
'quartier': np.random.choice(['Centre', 'Peripherie', 'Banlieue', 'Rural'], n_samples),
'surface': np.random.uniform(20, 200, n_samples),
'etage': np.random.randint(0, 15, n_samples),
'annee_construction': np.random.randint(1950, 2024, n_samples),
})
# Variable cible binaire (exemple : vendu en moins de 3 mois)
prob_vente = (
0.3 +
0.15 * (donnees['ville'] == 'Paris').astype(float) +
0.10 * (donnees['ville'] == 'Lyon').astype(float) +
0.08 * (donnees['type_bien'] == 'Appartement').astype(float) -
0.05 * (donnees['quartier'] == 'Rural').astype(float) +
0.002 * donnees['surface'] +
np.random.normal(0, 0.1, n_samples)
)
donnees['vendu_rapide'] = (prob_vente > 0.5).astype(int)
# Séparation entraînement / test
X = donnees.drop('vendu_rapide', axis=1)
y = donnees['vendu_rapide']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Identification des colonnes catégorielles
colonnes_categorielles = ['ville', 'type_bien', 'quartier']
colonnes_numeriques = ['surface', 'etage', 'annee_construction']
indices_cat = [X.columns.get_loc(col) for col in colonnes_categorielles]
# Création du modèle CatBoost
modele = CatBoostClassifier(
iterations=1000,
learning_rate=0.05,
depth=6,
l2_leaf_reg=3,
random_strength=1,
cat_features=indices_cat,
eval_metric='AUC',
early_stopping_rounds=50,
verbose=100,
random_seed=42
)
# Entraînement avec ensemble de validation
modele.fit(
X_train, y_train,
eval_set=(X_test, y_test),
plot=False
)
# Prédictions et évaluation
y_pred = modele.predict(X_test)
y_proba = modele.predict_proba(X_test)[:, 1]
print(f"AUC : {roc_auc_score(y_test, y_proba):.4f}")
print(classification_report(y_test, y_pred))
Utilisation de l’Objet Pool pour Plus de Performance
CatBoost propose un objet Pool spécialement optimisé pour le traitement des données :
from catboost import Pool
# Création de Pool pour l'entraînement et la validation
pool_train = Pool(
data=X_train,
label=y_train,
cat_features=indices_cat
)
pool_eval = Pool(
data=X_test,
label=y_test,
cat_features=indices_cat
)
# Entraînement avec Pool
modele.fit(pool_train, eval_set=pool_eval)
L’objet Pool est fortement recommandé car il optimise la gestion mémoire et permet à CatBoost de pré-calculer les encodages catégoriels de manière plus efficace.
Régression avec CatBoostRegressor
Pour les problèmes de régression, l’API est similaire :
from catboost import CatBoostRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
# Exemple de régression : prédiction du prix
donnees['prix'] = (
200_000 +
80_000 * (donnees['ville'] == 'Paris').astype(float) +
30_000 * (donnees['type_bien'] == 'Maison').astype(float) +
1500 * donnees['surface'] -
500 * donnees['etage'] +
np.random.normal(0, 20_000, n_samples)
)
y_reg = donnees['prix']
X_reg = donnees.drop(['prix', 'vendu_rapide'], axis=1)
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
)
regresseur = CatBoostRegressor(
iterations=1000,
learning_rate=0.05,
depth=6,
loss_function='RMSE',
cat_features=indices_cat[:3],
early_stopping_rounds=50,
verbose=100,
random_seed=42
)
regresseur.fit(X_train_r, y_train_r, eval_set=(X_test_r, y_test_r))
y_pred_r = regresseur.predict(X_test_r)
print(f"R² : {r2_score(y_test_r, y_pred_r):.4f}")
print(f"MAE : {mean_absolute_error(y_test_r, y_pred_r):.2f} \u20ac")
print(f"RMSE : {np.sqrt(mean_squared_error(y_test_r, y_pred_r)):.2f} \u20ac")
Cross-Validation avec CatBoost
CatBoost propose sa propre fonction de validation croisée :
from catboost import cv
params = {
'iterations': 500,
'learning_rate': 0.05,
'depth': 6,
'loss_function': 'Logloss',
'cat_features': indices_cat,
'random_seed': 42,
'verbose': False
}
cv_data = cv(
params=params,
pool=pool_train,
fold_count=5,
plot=False
)
print(f"AUC moyen en CV : {cv_data['test-AUC-mean'].max():.4f}")
Interprétabilité : Feature Importance et SHAP Values
CatBoost offre plusieurs méthodes pour comprendre les prédictions du modèle :
# Feature importance classique
importance = modele.get_feature_importance(prettified=True)
print(importance)
# Prédiction avec SHAP values
shap_values = modele.get_feature_importance(
data=pool_train,
type='ShapValues'
)
# Visualisation de l'importance d'un échantillon individuel
import matplotlib.pyplot as plt
# Feature importance plot
importance_df = modele.get_feature_importance(prettified=True)
plt.figure(figsize=(10, 6))
plt.barh(importance_df['Feature Id'], importance_df['Importances'])
plt.xlabel('Importance')
plt.title('Importance des features (CatBoost)')
plt.tight_layout()
plt.show()
# Courbe d'apprentissage
courbe = modele.evals_result_
plt.figure(figsize=(10, 5))
plt.plot(courbe['learn']['AUC'], label='Entra\u00eenement')
plt.plot(courbe['validation']['AUC'], label='Validation')
plt.xlabel('It\u00e9ration')
plt.ylabel('AUC')
plt.legend()
plt.title('Courbe d\'apprentissage CatBoost')
plt.show()
Hyperparamètres Essentiels de CatBoost
Maîtriser les hyperparamètres de CatBoost est essentiel pour obtenir des performances optimales. Voici les plus importants :
| Hyperparamètre | Description | Valeur par défaut | Plage recommandée |
|---|---|---|---|
| iterations | Nombre d’arbres (boosting steps) | 1000 | 500–3000 |
| learning_rate | Taux d’apprentissage (eta) | 0.03 | 0.01–0.1 |
| depth | Profondeur maximale des arbres | 6 | 4–10 |
| l2_leaf_reg | Régularisation L2 sur les feuilles | 3 | 1–10 |
| rsm | Fraction aléatoire des features par split | 1.0 | 0.5–1.0 |
| random_strength | Force de la randomisation lors des splits | 1.0 | 0.5–2.0 |
| one_hot_max_size | Taille max pour l’encodage one-hot direct | 2 | 2–10 |
| bagging_temperature | Contrôle le bootstrap bayésien | 1.0 | 0–10 |
| min_data_in_leaf | Nombre minimum d’échantillons par feuille | 1 | 1–50 |
| max_ctr_complexity | Profondeur max des combinaisons catégorielles | 4 | 1–4 |
Recommandations Pratiques
Pour obtenir les meilleures performances avec CatBoost, suivez ces recommandations :
- Petits datasets (moins de 10 000 échantillons) : utilisez
depth=4-6,learning_rate=0.05-0.1,l2_leaf_reg=3-5 - Grands datasets (plus de 100 000 échantillons) : utilisez
depth=8-10,learning_rate=0.01-0.03,l2_leaf_reg=1-3 - Beaucoup de catégories : augmentez
max_ctr_complexityà 4 pour capturer plus d’interactions - Risque de surapprentissage : augmentez
l2_leaf_reg, réduisezdepth, activez l’early stopping - Optimisation systématique : utilisez la recherche par grille (grid search) ou l’optimisation bayésienne pour trouver les meilleurs hyperparamètres
from catboost import CatBoostClassifier
# Grid search intégré à CatBoost
modele_gs = CatBoostClassifier(
iterations=1000,
cat_features=indices_cat,
eval_metric='AUC',
verbose=False
)
grid = {
'learning_rate': [0.01, 0.05, 0.1],
'depth': [4, 6, 8],
'l2_leaf_reg': [1, 3, 5, 7]
}
modele_gs.grid_search(
grid,
X_train, y_train,
cv=5,
plot=False,
refit=True
)
print(f"Meilleurs paramètres : {modele_gs.get_params()}")
Avantages et Limites de CatBoost
Avantages
- Traitement natif des variables catégorielles : Aucun encodage préalable nécessaire. CatBoost gère automatiquement les variables catégorielles grâce à l’encodage par cible ordonnée, ce qui simplifie considérablement le preprocessing.
- Excellente robustesse au surapprentissage : L’ordered boosting et l’encodage par cible ordonnée éliminent le target leakage, rendant CatBoost naturellement plus robuste que XGBoost ou LightGBM sur les données catégorielles.
- Performances de prédiction exceptionnelles : Sur les données tabulaires avec de nombreuses variables catégorielles, CatBoost surpasse souvent les autres algorithmes de gradient boosting, ce qui en fait un choix privilégié pour les compétitions Kaggle.
- Prédictions rapides en production : Le modèle entraîné peut être exporté dans plusieurs formats (ONNX, C++, Python) et les prédictions sont extrêmement rapides grâce à l’optimisation des arbres.
- Support GPU : CatBoost supporte l’entraînement sur GPU (NVIDIA), ce qui accélère considérablement l’entraînement sur de grands volumes de données.
- Traitement des valeurs manquantes : CatBoost gère automatiquement les valeurs manquantes (NaN) sans imputation préalable, en apprenant la direction optimale de chaque valeur manquante dans chaque arbre.
Limites
- Temps d’entraînement plus long : L’ordered boosting nécessite de recalculer les gradients pour chaque échantillon à chaque itération, ce qui rend l’entraînement de CatBoost significativement plus lent que celui de LightGBM, notamment sur de grands datasets.
- Mémoire importante : La génération de plusieurs permutations et le stockage des gradients ordonnés consomment plus de mémoire vive que les algorithmes concurrents.
- Moins adapté aux données purement numériques : Si votre dataset ne contient que des variables numériques, CatBoost n’apporte pas d’avantage significatif par rapport à XGBoost ou LightGBM.
- Difficulté d’interprétation des combinaisons : Les combinaisons automatiques de features catégorielles, bien que performantes, peuvent rendre l’interprétation du modèle plus complexe.
4 Cas d’Usage Concrets de CatBoost
Cas 1 : Scoring de Crédit Bancaire
Dans le domaine bancaire, les variables catégorielles sont omniprésentes : profession, secteur d’activité, statut marital, type de logement, région géographique. CatBoost excelle dans ce contexte car il capture naturellement les interactions entre ces catégories.
# Exemple simplifié de scoring de crédit
donnees_credit = pd.DataFrame({
'profession': ['Cadre', 'Ouvrier', 'Employe', 'Cadre', 'Artisan'],
'secteur': ['Technologie', 'Industrie', 'Commerce', 'Sante', 'BTP'],
'region': ['Ile-de-France', 'Auvergne-Rhone-Alpes', 'PACA', 'Nouvelle-Aquitaine', 'Occitanie'],
'type_logement': ['Proprietaire', 'Locataire', 'Heberge', 'Proprietaire', 'Locataire'],
'revenu_annuel': [65000, 28000, 25000, 72000, 45000],
'duree_contrat': [36, 60, 24, 48, 36],
'defaut': [0, 1, 1, 0, 0]
})
cat_cols_credit = ['profession', 'secteur', 'region', 'type_logement']
X_credit = donnees_credit.drop('defaut', axis=1)
y_credit = donnees_credit['defaut']
indices_cat_credit = [X_credit.columns.get_loc(c) for c in cat_cols_credit]
modele_credit = CatBoostClassifier(
iterations=500,
depth=5,
learning_rate=0.05,
cat_features=indices_cat_credit,
eval_metric='F1',
random_seed=42
)
modele_credit.fit(X_credit, y_credit, verbose=False)
Cas 2 : Recommandation de Contenu
Les plateformes de streaming (musique, vidéo, lecture) utilisent des features catégorielles massives : genres artistiques, artistes, langues, tags, régions. CatBoost peut prédire la probabilité qu’un utilisateur apprécie un contenu donné.
# Exemple de recommandation musicale
donnees_musique = pd.DataFrame({
'genre': np.random.choice(['Pop', 'Rock', 'Jazz', 'Classique', 'Hip-Hop', 'Electro'], 10000),
'artiste_popularite': np.random.choice(['Inconnu', 'Emergent', 'Connu', 'Celebre'], 10000),
'langue': np.random.choice(['Francais', 'Anglais', 'Espagnol', 'Autre'], 10000),
'duree_minute': np.random.uniform(2, 8, 10000),
'heure_ecoute': np.random.randint(0, 24, 10000),
'utilisateur_age': np.random.randint(15, 70, 10000),
'aime': np.random.choice([0, 1], 10000, p=[0.6, 0.4])
})
cat_cols_musique = ['genre', 'artiste_popularite', 'langue']
X_musique = donnees_musique.drop('aime', axis=1)
y_musique = donnees_musique['aime']
indices_cat_musique = [X_musique.columns.get_loc(c) for c in cat_cols_musique]
Cas 3 : Prédiction d’Attrition Client
Dans le e-commerce et les télécommunications, prédire quels clients vont se désabonner est crucial pour la rétention. Les variables catégorielles incluent : type d’abonnement, mode de paiement, canal d’acquisition, segment client.
# Exemple de prédiction d'attrition
donnees_attrition = pd.DataFrame({
'abonnement': np.random.choice(['Basique', 'Standard', 'Premium', 'Entreprise'], 20000),
'marche_paiement': np.random.choice(['Carte', 'Prelevement', 'Virement', 'PayPal'], 20000),
'canal_acquisition': np.random.choice(['Organique', 'Publicite', 'Parrainage', 'Partenaire'], 20000),
'segment': np.random.choice(['Particulier', 'PME', 'Grand_compte'], 20000),
'anciennete_mois': np.random.randint(1, 72, 20000),
'nb_tickets_support': np.random.randint(0, 30, 20000),
'duree_contrat_mois': np.random.choice([1, 12, 24, 36], 20000),
'parti': np.random.choice([0, 1], 20000, p=[0.85, 0.15])
})
cat_cols_attrition = ['abonnement', 'marche_paiement', 'canal_acquisition', 'segment']
indices_cat_attrition = [donnees_attrition.drop('parti', axis=1).columns.get_loc(c) for c in cat_cols_attrition]
Cas 4 : Diagnostic Médical
En santé, CatBoost peut aider au diagnostic en analysant des données patient contenant de nombreuses variables catégorielles : antécédents familiaux, type de symptôme, catégorie de traitement, établissement de soins.
# Exemple simplifié de diagnostic médical
donnees_medical = pd.DataFrame({
'antecedent_familial': np.random.choice(['Aucun', 'Diabete', 'Cardiovasculaire', 'Cancer'], 8000),
'type_symptome': np.random.choice(['Douleur', 'Fievre', 'Fatigue', 'Toux', 'Vertiges'], 8000),
'categorie_traitement': np.random.choice(['Preventif', 'Curatif', 'Palliatif', 'Chirurgical'], 8000),
'etablissement': np.random.choice(['Hopital', 'Clinique', 'Cabinet', 'Urgences'], 8000),
'age_patient': np.random.randint(18, 90, 8000),
'tension_arterielle': np.random.uniform(90, 180, 8000),
'glycemie': np.random.uniform(0.7, 2.5, 8000),
'diagnostic_grave': np.random.choice([0, 1], 8000, p=[0.75, 0.25])
})
cat_cols_medical = ['antecedent_familial', 'type_symptome', 'categorie_traitement', 'etablissement']
indices_cat_medical = [donnees_medical.drop('diagnostic_grave', axis=1).columns.get_loc(c) for c in cat_cols_medical]
Voir Aussi
- Explorez la Conjecture de Goldbach en Python : Guide Pratique pour les Passionnés de Programmation
- Résolution des Équations Diophantiennes III avec Python : Guide Complet et Astuces

