Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

1. Arbres de décision

Un arbre de décision est un algorithme d’apprentissage supervisé utilisé aussi bien pour la classification que pour la régression. C’est une structure en organigramme où :

  • les nœuds internes représentent des tests sur les caractéristiques (attributs),
  • les branches représentent les issues de ces tests, et
  • les nœuds feuilles représentent la sortie ou la décision finale (une étiquette de classe ou une valeur prédite).

Chaque nœud de l’arbre coupe les données en sous-ensembles selon les valeurs de caractéristiques qui optimisent un critère de partition : l’impureté de Gini ou l’entropie pour la classification, l’erreur quadratique moyenne pour la régression. Ce sont des critères de partition, et non des fonctions de coût : ils mesurent l’hétérogénéité qui subsiste dans les sous-ensembles issus de la découpe.


2. Comment fonctionnent les arbres de décision

  1. Découpe : l’algorithme part du nœud racine en sélectionnant la caractéristique qui sépare le mieux le jeu de données selon un critère choisi. Pour la classification, les métriques courantes sont l’impureté de Gini ou le gain d’information.

  2. Partitionnement récursif : le procédé s’applique récursivement aux sous-ensembles obtenus, créant les branches de l’arbre. Chaque décision vise à simplifier le problème en réduisant l’incertitude sur l’issue.

  3. Critères d’arrêt : l’algorithme continue jusqu’à atteindre une condition d’arrêt — une taille minimale de nœud ou une profondeur maximale d’arbre — pour prévenir le surapprentissage.

  4. Prédiction : une fois l’arbre entraîné, il classe de nouveaux points de données en parcourant l’arbre de la racine à une feuille, en suivant les décisions à chaque nœud.


3. Pourquoi les arbres de décision comptent-ils en recherche géoscientifique ?

Les arbres de décision sont particulièrement utiles en recherche géoscientifique, pour plusieurs raisons :

  • Interprétabilité : les arbres de décision offrent une représentation claire et visuelle de la façon dont les décisions sont prises. En géosciences, où les modèles doivent souvent être explicables à un large éventail de parties prenantes (décideurs publics, acteurs de l’environnement), les arbres de décision permettent aux utilisateurs de comprendre les prédictions et de leur faire confiance.

  • Prise en compte de relations complexes et non linéaires : les données géoscientifiques, environnementales ou climatiques par exemple, présentent souvent des relations complexes entre variables. Les arbres de décision capturent naturellement ces relations non linéaires sans prétraitement lourd des données.

  • Gestion des données manquantes : les arbres de décision peuvent traiter des jeux de données à valeurs manquantes, situation courante en géosciences vu la difficulté d’une surveillance continue en environnement hostile ou en site isolé.

  • Importance des caractéristiques : les arbres de décision classent l’importance de chaque caractéristique selon sa contribution au modèle. C’est précieux en géosciences pour identifier les facteurs (température, précipitations ou activité sismique) qui pèsent le plus sur une issue donnée (prédire des glissements de terrain ou des séismes).

  • Passage à l’échelle : les arbres de décision s’appliquent aux grands jeux de données, fréquents en géosciences, avec un surcoût de calcul minime. Ils s’adaptent aux cadres de calcul distribué pour traiter de vastes jeux de données géospatiales.


4. Applications géoscientifiques

  • Classification d’événements sismiques : les arbres de décision peuvent classer des événements sismiques (séismes, éruptions volcaniques) à partir des formes d’onde, du contenu fréquentiel et d’autres caractéristiques.

  • Prédiction du risque de glissement de terrain : ils peuvent modéliser des interactions complexes entre variables environnementales comme la pente, le type de sol et les précipitations, en identifiant les zones les plus exposées aux glissements de terrain.

  • Classification et prédiction climatiques : les arbres de décision peuvent classer des zones climatiques à partir de variables environnementales, ou prédire de futurs régimes climatiques par l’analyse d’historiques météorologiques.

🖥️ Diapositives du cours — Séance 17 (ven. 6 nov.)

2. Forêt aléatoire

2.1 Concepts

1. Qu’est-ce qu’une forêt aléatoire ?

Une forêt aléatoire est une méthode d’apprentissage ensembliste bâtie sur les fondations des arbres de décision. Plutôt que de s’appuyer sur un seul arbre de décision, les forêts aléatoires combinent les prédictions de nombreux arbres de décision pour améliorer l’exactitude et la robustesse et réduire le surapprentissage. Chaque arbre de la forêt fait sa prédiction indépendamment, et la forêt agrège ces prédictions — typiquement par vote majoritaire pour la classification ou par moyenne pour la régression — pour produire la décision finale.


2. Comment fonctionnent les forêts aléatoires ?

  1. Agrégation bootstrap (bagging) : chaque arbre de décision d’une forêt aléatoire est entraîné sur un sous-ensemble différent du jeu de données d’origine. On y parvient par bootstrap (tirage avec remise) : des échantillons aléatoires (avec remise) sont tirés du jeu de données pour chaque arbre. Cette diversité des données d’entraînement laisse les arbres développer des modèles légèrement différents, ce qui réduit la probabilité que tous les arbres commettent les mêmes erreurs.

  2. Hasard sur les caractéristiques : à chaque découpe, un sous-ensemble aléatoire de caractéristiques est proposé, plutôt que l’ensemble complet des caractéristiques. Ce hasard décorrèle les arbres entre eux, ce qui réduit encore le risque de surapprentissage et améliore la généralisation.

  3. Agrégation des prédictions : une fois que tous les arbres de la forêt ont fait leurs prédictions, la forêt les agrège en une sortie finale. Pour la classification, c’est en général un vote majoritaire entre arbres ; pour la régression, la sortie finale est la moyenne des prédictions des arbres.


3. Pourquoi les forêts aléatoires sont précieuses en recherche géoscientifique

Les forêts aléatoires corrigent certaines limites des arbres de décision isolés, ce qui les rend particulièrement utiles pour les applications géoscientifiques :

  • Exactitude accrue et surapprentissage réduit : l’approche ensembliste des forêts aléatoires améliore généralement l’exactitude des prédictions par rapport à un arbre de décision unique. Cela compte en géosciences, où les données peuvent être bruitées et où des prédictions précises sont exigées pour des applications comme l’évaluation des aléas.

  • Meilleure généralisation : les forêts aléatoires généralisent mieux que les arbres isolés grâce à la diversité de l’ensemble. Elles sont donc plus susceptibles de bien se comporter sur des données nouvelles, jamais vues — un point vital dans des contextes géoscientifiques dynamiques (régimes climatiques changeants, conditions géologiques évolutives).

  • Importance des caractéristiques et interprétabilité : comme les arbres de décision, les forêts aléatoires fournissent des scores d’importance des caractéristiques, qui indiquent lesquelles contribuent le plus aux prédictions. Cela aide les géoscientifiques à identifier les moteurs clés d’un phénomène, comme les facteurs menant aux glissements de terrain ou influençant l’activité sismique.

  • Passage à l’échelle : les forêts aléatoires passent très bien à l’échelle et se distribuent sur plusieurs processeurs, ce qui les rend adaptées aux grands jeux de données géospatiales courants en géosciences, comme les données de télédétection.

2.2 Pratique

Nous allons entraîner une forêt aléatoire pour prédire la température maximale du jour sur un site de type Seattle, à partir de quelques caractéristiques simples : la température d’hier, celle d’avant-hier, la moyenne historique pour ce jour du calendrier, et la date elle-même.

Les éditions antérieures de ce livre téléchargeaient un fichier temps.csv depuis un lien Google Docs. Ce lien est mort, et le fichier portait une colonne friend en forme de plaisanterie (une supposition aléatoire à moins de 20 degrés de la moyenne). L’édition 2026 génère un relevé quotidien équivalent dans le notebook même, si bien que nous en connaissons la structure exacte : un cycle saisonnier, une faible tendance au réchauffement, et un bruit météorologique autocorrélé. Le même jeu de données revient dans la leçon 3.10, les résultats sont donc directement comparables entre les deux leçons.

Le flux de travail ci-dessous suit l’esprit du tutoriel de forêt aléatoire de Will Koehrsen, mis à jour selon la pratique actuelle.

import numpy as np
import pandas as pd


def make_daily_temps(start="2012-01-01", end="2019-12-31", seed=42):
    """Synthetic Seattle-like daily maximum temperature record (degrees F).

    Seasonal climatology + a weak warming trend + AR(1) weather noise.
    Generated in-notebook so the lesson does not depend on a remote file.
    """
    rng = np.random.default_rng(seed)
    dates = pd.date_range(start, end, freq="D")
    day_of_year = dates.dayofyear.to_numpy()
    climatology = 62.0 - 15.0 * np.cos(2 * np.pi * (day_of_year - 203) / 365.25)
    trend = 0.05 * np.arange(len(dates)) / 365.25
    noise = np.zeros(len(dates))
    for i in range(1, len(dates)):
        noise[i] = 0.65 * noise[i - 1] + rng.normal(0.0, 3.0)
    df = pd.DataFrame(
        {
            "date": dates,
            "average": np.round(climatology, 1),  # historical average for that calendar day
            "actual": np.round(climatology + trend + noise, 1),
        }
    )
    df["temp_1"] = df["actual"].shift(1)  # yesterday's max
    df["temp_2"] = df["actual"].shift(2)  # two days ago
    df["month"] = df["date"].dt.month
    df["day"] = df["date"].dt.day
    df["doy_sin"] = np.sin(2 * np.pi * day_of_year / 365.25)
    df["doy_cos"] = np.cos(2 * np.pi * day_of_year / 365.25)
    return df.dropna().reset_index(drop=True)


df = make_daily_temps()
print(df.shape)
df.head()
(2920, 9)
Loading...
# Descriptive statistics for each column
df.describe()
Loading...

Explorer les données

Avant toute modélisation, regardez la série. Nous traçons la cible (actual), la caractéristique retardée la plus forte (temp_1) et la climatologie (average). Le cycle saisonnier domine ; le bruit météorologique s’y superpose.

import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(10, 8), sharex=True)

axes[0].plot(df["date"], df["actual"], lw=0.6, color="tab:blue")
axes[0].set_ylabel("Temperature (F)")
axes[0].set_title("Actual daily max temperature")

axes[1].plot(df["date"], df["temp_1"], lw=0.6, color="tab:orange")
axes[1].set_ylabel("Temperature (F)")
axes[1].set_title("Yesterday's max temperature (temp_1)")

axes[2].plot(df["date"], df["average"], lw=0.8, color="tab:green")
axes[2].set_ylabel("Temperature (F)")
axes[2].set_title("Historical average for the calendar day")
axes[2].set_xlabel("Date")

plt.tight_layout()
<Figure size 1000x800 with 3 Axes>

Découper en ensembles d’entraînement et de test

La cible est actual. Les caractéristiques sont les deux retards, la climatologie, et la date encodée en mois, jour et une paire sinus/cosinus pour le jour de l’année.

from sklearn.model_selection import train_test_split

features = ["temp_1", "temp_2", "average", "month", "day", "doy_sin", "doy_cos"]
X = df[features]
y = df["actual"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
print("Training features:", X_train.shape)
print("Testing features: ", X_test.shape)
Training features: (2190, 7)
Testing features:  (730, 7)

Le modèle de référence : la climatologie

from sklearn.metrics import mean_absolute_error, root_mean_squared_error, r2_score

baseline_pred = X_test["average"]
baseline_mae = mean_absolute_error(y_test, baseline_pred)
baseline_rmse = root_mean_squared_error(y_test, baseline_pred)
print(f"Climatology baseline:  MAE = {baseline_mae:.2f} F,  RMSE = {baseline_rmse:.2f} F")
Climatology baseline:  MAE = 3.25 F,  RMSE = 4.10 F

Ajuster une forêt aléatoire

Nous ajustons une forêt de 300 arbres sur l’ensemble d’entraînement et la notons sur l’ensemble de test tenu à l’écart.

import time
from sklearn.ensemble import RandomForestRegressor

rf = RandomForestRegressor(n_estimators=300, random_state=42)

t0 = time.perf_counter()
rf.fit(X_train, y_train)
rf_fit_time = time.perf_counter() - t0

rf_pred = rf.predict(X_test)
rf_mae = mean_absolute_error(y_test, rf_pred)
rf_rmse = root_mean_squared_error(y_test, rf_pred)
rf_r2 = r2_score(y_test, rf_pred)
print(f"Random forest (test): MAE = {rf_mae:.2f} F,  RMSE = {rf_rmse:.2f} F,  R2 = {rf_r2:.3f}")
print(f"Fit time: {rf_fit_time:.2f} s")
Random forest (test): MAE = 2.55 F,  RMSE = 3.21 F,  R2 = 0.923
Fit time: 1.40 s

La validation croisée plutôt qu’un découpage unique

Un seul découpage entraînement/test est un tirage aléatoire ; la validation croisée montre la dispersion. Nous exécutons une validation croisée à 5 plis sur les seules données d’entraînement, en gardant l’ensemble de test intact.

from sklearn.model_selection import KFold, cross_val_score

cv = KFold(n_splits=5, shuffle=True, random_state=42)
rf_cv_scores = -cross_val_score(
    RandomForestRegressor(n_estimators=300, random_state=42),
    X_train, y_train, cv=cv, scoring="neg_mean_absolute_error",
)
print(f"Random forest 5-fold CV MAE: {rf_cv_scores.mean():.2f} +/- {rf_cv_scores.std():.2f} F")
Random forest 5-fold CV MAE: 2.59 +/- 0.03 F

Importances des caractéristiques

Les forêts aléatoires classent les caractéristiques selon la réduction du critère de découpe qu’elles apportent sur l’ensemble des arbres. C’est une vue rapide, interne au modèle, des entrées qui comptent — calculée sur les données d’entraînement, avec des biais que nous examinons plus bas.

importances = rf.feature_importances_

# Print features sorted by importance
for name, imp in sorted(zip(features, importances), key=lambda pair: pair[1], reverse=True):
    print(f"{name:10s} importance: {imp:.3f}")

fig, ax = plt.subplots(figsize=(7, 4))
ax.bar(features, importances)
ax.set_ylabel("Importance")
ax.set_xlabel("Feature")
ax.set_title("Random forest feature importances")
plt.xticks(rotation=45)
plt.tight_layout()
temp_1     importance: 0.929
average    importance: 0.025
temp_2     importance: 0.018
day        importance: 0.010
doy_cos    importance: 0.009
doy_sin    importance: 0.008
month      importance: 0.001
<Figure size 700x400 with 1 Axes>
# Retrain using only the two most important features, chosen programmatically
top2 = [features[i] for i in np.argsort(importances)[::-1][:2]]
print("Top-2 features:", top2)

rf_top2 = RandomForestRegressor(n_estimators=300, random_state=42)
rf_top2.fit(X_train[top2], y_train)
top2_pred = rf_top2.predict(X_test[top2])
top2_mae = mean_absolute_error(y_test, top2_pred)

print(f"Full model (7 features)  test MAE: {rf_mae:.2f} F")
print(f"Top-2 model              test MAE: {top2_mae:.2f} F")
Top-2 features: ['temp_1', 'average']
Full model (7 features)  test MAE: 2.55 F
Top-2 model              test MAE: 2.69 F

Deux caractéristiques retrouvent l’essentiel de la compétence des sept : quand un modèle compact fait presque aussi bien, préférez-le — il coûte moins cher à exécuter, s’explique plus facilement et risque moins de surapprendre.

Interpréter les importances

Les importances par impureté ci-dessus sont une propriété des arbres ajustés, pas du monde. Elles sont calculées sur les données d’entraînement, et quand les caractéristiques sont corrélées, les arbres se partagent le crédit entre elles selon celle qu’un nœud s’est trouvé choisir. L’importance par permutation pose une autre question : de combien le score sur données tenues à l’écart se dégrade-t-il quand on mélange les valeurs d’une caractéristique ? Calculée sur l’ensemble de test, elle mesure ce sur quoi le modèle s’appuie réellement pour des données nouvelles.

from sklearn.inspection import permutation_importance

perm = permutation_importance(
    rf, X_test, y_test, n_repeats=20, random_state=42,
    scoring="neg_mean_absolute_error",
)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))
order = np.argsort(importances)
ax1.barh(np.array(features)[order], importances[order])
ax1.set_title("Impurity importance (training)")
order = np.argsort(perm.importances_mean)
ax2.barh(np.array(features)[order], perm.importances_mean[order],
         xerr=perm.importances_std[order])
ax2.set_title("Permutation importance (test set)")
ax2.set_xlabel("Increase in test MAE (F) when shuffled")
plt.tight_layout()
<Figure size 1100x400 with 2 Axes>

Ici, les deux classements s’accordent : temp_1 et average dominent — mais cet accord n’est pas garanti. Pour voir comment des caractéristiques corrélées trompent le classement par impureté, ajoutez-en une qui n’apporte strictement aucune information nouvelle : temp_1 convertie en Celsius, une copie parfaite aux unités près.

X_train_dup = X_train.copy()
X_test_dup = X_test.copy()
X_train_dup["temp_1_C"] = (X_train_dup["temp_1"] - 32) * 5 / 9
X_test_dup["temp_1_C"] = (X_test_dup["temp_1"] - 32) * 5 / 9

rf_dup = RandomForestRegressor(n_estimators=300, random_state=42).fit(X_train_dup, y_train)
dup_mae = mean_absolute_error(y_test, rf_dup.predict(X_test_dup))
print(f"Test MAE without the duplicate: {rf_mae:.2f} F,  with: {dup_mae:.2f} F")

comparison = pd.DataFrame({
    "without duplicate": pd.Series(importances, index=features),
    "with duplicate": pd.Series(rf_dup.feature_importances_, index=X_train_dup.columns),
}).round(3)
print(comparison)
Test MAE without the duplicate: 2.55 F,  with: 2.55 F
          without duplicate  with duplicate
average               0.025           0.025
day                   0.010           0.010
doy_cos               0.009           0.009
doy_sin               0.008           0.007
month                 0.001           0.001
temp_1                0.929           0.486
temp_1_C                NaN           0.444
temp_2                0.018           0.018

Les prédictions bougent à peine, mais l’importance de temp_1 est désormais partagée avec sa jumelle en Celsius : à chaque nœud, les arbres prennent celle des deux copies qui leur est offerte. Rien de la température d’hier n’est devenu moins informatif — seule la comptabilité a changé. Les caractéristiques du carnet lui-même sont déjà corrélées de cette façon (temp_1 avec temp_2 par persistance d’un jour à l’autre, average avec la paire doy_sin/doy_cos), lisez donc tout classement d’importances comme la façon dont ce modèle répartit le crédit entre les entrées qu’on lui a données, pas comme une mesure du monde.

Dépendance partielle

Un graphique de dépendance partielle montre comment la prédiction du modèle change quand une caractéristique varie, en moyennant sur les autres. Nous le traçons pour la caractéristique dominante.

from sklearn.inspection import PartialDependenceDisplay

disp = PartialDependenceDisplay.from_estimator(rf, X_test, ["temp_1"])
disp.axes_[0, 0].set_ylabel("Predicted max temperature (F)")
plt.gcf().set_size_inches(5, 4)
plt.tight_layout()
<Figure size 500x400 with 2 Axes>

La prédiction monte presque linéairement avec la température d’hier, ce qui correspond à la persistance AR(1) intégrée aux données. Une réserve vaut pour tout ce qui figure dans cette section : importance n’est pas causalité — elle rapporte ce que le modèle utilise pour prédire, pas ce qui pilote la température ; mélanger temp_1 casse le modèle, pas la météo.

Comparaison : gradient boosting

HistGradientBoostingRegressor applique le gradient boosting (« renforcement par gradient ») : il construit les arbres séquentiellement, chacun corrigeant les résidus du précédent. Nous l’évaluons avec le même découpage et la même validation croisée à 5 plis.

from sklearn.ensemble import HistGradientBoostingRegressor

hgb = HistGradientBoostingRegressor(random_state=42)

t0 = time.perf_counter()
hgb.fit(X_train, y_train)
hgb_fit_time = time.perf_counter() - t0

hgb_pred = hgb.predict(X_test)
hgb_mae = mean_absolute_error(y_test, hgb_pred)
hgb_rmse = root_mean_squared_error(y_test, hgb_pred)
hgb_r2 = r2_score(y_test, hgb_pred)

hgb_cv_scores = -cross_val_score(
    HistGradientBoostingRegressor(random_state=42),
    X_train, y_train, cv=cv, scoring="neg_mean_absolute_error",
)

print(f"Gradient boosting (test): MAE = {hgb_mae:.2f} F,  RMSE = {hgb_rmse:.2f} F,  R2 = {hgb_r2:.3f}")
print(f"Gradient boosting 5-fold CV MAE: {hgb_cv_scores.mean():.2f} +/- {hgb_cv_scores.std():.2f} F")
print(f"Fit times: random forest {rf_fit_time:.2f} s,  gradient boosting {hgb_fit_time:.2f} s")
Gradient boosting (test): MAE = 2.53 F,  RMSE = 3.20 F,  R2 = 0.923
Gradient boosting 5-fold CV MAE: 2.58 +/- 0.04 F
Fit times: random forest 1.40 s,  gradient boosting 2.42 s

Le gradient boosting est le choix par défaut de 2026 pour la régression tabulaire : sur des tables de caractéristiques, il égale ou bat généralement une forêt aléatoire à coût d’entraînement égal ou moindre, et son avantage croît avec la taille du jeu de données. Sur une table aussi petite, les temps de calcul sont proches et peuvent basculer d’un côté comme de l’autre. La forêt aléatoire reste un modèle de référence solide et robuste qui n’exige presque aucun réglage — c’est pourquoi nous l’enseignons en premier.

Prédictions et observations

Enfin, traçons les prédictions sur l’ensemble de test par-dessus la série observée complète.

test_dates = df.loc[X_test.index, "date"]

fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(df["date"], df["actual"], "-", lw=0.5, color="tab:blue", label="actual")
ax.plot(test_dates, rf_pred, ".", ms=3, color="tab:red", label="RF prediction (test)")
ax.set_xlabel("Date")
ax.set_ylabel("Maximum temperature (F)")
ax.set_title("Test-set predictions and observed values")
ax.legend()
plt.tight_layout()

print(f"Climatology baseline MAE: {baseline_mae:.2f} F  |  Random forest MAE: {rf_mae:.2f} F")
Climatology baseline MAE: 3.25 F  |  Random forest MAE: 2.55 F
<Figure size 1000x400 with 1 Axes>

La forêt bat le modèle de référence climatologique avec une marge nette : les caractéristiques retardées portent donc une information réelle sur la météo de demain, au-delà du cycle saisonnier. C’est cette comparaison, et non le score seul, qui prouve que le modèle a appris quelque chose.