Formation ML / Apprentissage supervisé - Régression

Random Forest Regressor

Intermédiaire 40 min 14 sections

Cent arbres entraînés sur des échantillons et features tirés au sort, dont la moyenne bat un arbre seul.

Objectifs d'apprentissage

  • Comprendre le principe du bagging et des forêts aléatoires
  • Implémenter un Random Forest avec scikit-learn
  • Analyser l'importance des features
  • Optimiser les hyperparamètres

Prérequis

Modules Régression linéaire et Arbre de décision recommandés

Le code de cette page s'exécute dans votre navigateur. Le premier lancement télécharge Python et ses bibliothèques.

Théorie

Qu'est-ce qu'une forêt aléatoire?

Une forêt aléatoire (Random Forest) est un algorithme d'ensemble qui combine plusieurs arbres de décision pour obtenir de meilleures prédictions.

Principe du Bagging (Bootstrap Aggregating):

  1. Créer plusieurs échantillons aléatoires des données (avec remise)
  2. Entraîner un arbre de décision sur chaque échantillon
  3. Agréger les prédictions de tous les arbres

Spécificité du Random Forest:

En plus du bagging, chaque split d'arbre ne considère qu'un sous-ensemble aléatoire des features.

Avantages:

  • Réduit l'overfitting (par rapport à un seul arbre)
  • Robuste aux outliers et au bruit
  • Calcule automatiquement l'importance des features
  • Pas besoin de normalisation des données

Inconvénients:

  • Moins interprétable qu'un seul arbre
  • Plus lent à entraîner
  • Nécessite plus de mémoire
Théorie

Schéma: Architecture d'une forêt aléatoire

Comment fonctionne le Random Forest:

flowchart TD D["Dataset Original
(100 échantillons)"] subgraph Bootstrap ["Bootstrap Sampling"] B1["Échantillon 1
(100 avec remise)"] B2["Échantillon 2
(100 avec remise)"] B3["Échantillon 3
(100 avec remise)"] end subgraph Trees ["Arbres de Décision"] T1["Arbre 1"] T2["Arbre 2"] T3["Arbre 3"] end subgraph Predictions ["Prédictions"] P1["Pred 1
250k"] P2["Pred 2
245k"] P3["Pred 3
255k"] end AGG["Moyenne
250k"] D --> B1 --> T1 --> P1 D --> B2 --> T2 --> P2 D --> B3 --> T3 --> P3 P1 --> AGG P2 --> AGG P3 --> AGG class AGG ml-node-accent

Randomisation des features à chaque split:

flowchart LR F["Toutes les Features
(surface, nb_rooms, ...)"] S["Sélection Aléatoire
(sqrt(n) features)"] B["Meilleur Split
parmi la sélection"] F --> S --> B class B ml-node-accent

Cette double randomisation (échantillons + features) réduit la corrélation entre les arbres et améliore la généralisation.

Exemple concret - Prédiction du prix d'un appartement:

Un appartement de $\textcolor{#3498db}{85 m^2}$ avec $\textcolor{#e67e22}{3}$ pièces.

Prédictions de chaque arbre:

  • $\textcolor{#9B7AC4}{Arbre 1}$ : $\textcolor{#9B7AC4}{248000}$ EUR (entraîné sur échantillon A)
  • $\textcolor{#F7E64D}{Arbre 2}$ : $\textcolor{#F7E64D}{252000}$ EUR (entraîné sur échantillon B)
  • $\textcolor{#e74c3c}{Arbre 3}$ : $\textcolor{#e74c3c}{250000}$ EUR (entraîné sur échantillon C)

Agrégation (moyenne):

$$\text{Prix} = \frac{\textcolor{#9B7AC4}{248000} + \textcolor{#F7E64D}{252000} + \textcolor{#e74c3c}{250000}}{3} = \textcolor{#27ae60}{\mathbf{250000}} \text{ EUR}$$

Légende des couleurs:

  • $\textcolor{#3498db}{Bleu}$ : surface ($\textcolor{#3498db}{85 m^2}$)
  • $\textcolor{#e67e22}{Orange}$ : nombre de pièces ($\textcolor{#e67e22}{3}$)
  • $\textcolor{#9B7AC4}{Violet}$ : prédiction Arbre 1
  • $\textcolor{#F7E64D}{Jaune}$ : prédiction Arbre 2
  • $\textcolor{#e74c3c}{Rouge}$ : prédiction Arbre 3
  • $\textcolor{#27ae60}{Vert}$ : prédiction finale (moyenne)

Pourquoi c'est puissant? Chaque arbre peut se tromper un peu, mais en moyennant leurs prédictions, les erreurs individuelles se compensent!

Avancé Exercice manuel: À vous de calculer!

Objectif: Comprendre le fonctionnement de Random Forest à la main (agrégation, vote majoritaire).

CONTEXTE

Vous avez entraîné un Random Forest avec 5 arbres pour classifier des emails (SPAM/HAM). Un nouvel email arrive avec les caractéristiques suivantes :

  • Nombre de liens : 3
  • Mots en majuscules : 15

Les 5 arbres donnent les prédictions suivantes :

ArbrePrédictionConfiance
1SPAM0.8
2HAM0.6
3SPAM0.9
4SPAM0.7
5HAM0.55

PARTIE 1 : Vote majoritaire

1.1) Combien d'arbres prédisent SPAM ? HAM ? 1.2) Quelle est la prédiction finale du Random Forest ?

PARTIE 2 : Probabilité moyenne

2.1) Calculez la probabilité moyenne pour la classe SPAM 2.2) Calculez la probabilité moyenne pour la classe HAM 2.3) Quelle est la confiance finale du modèle ?

PARTIE 3 : Interprétation

3.1) Pourquoi Random Forest est-il plus robuste qu'un seul arbre ? 3.2) Que se passe-t-il si on augmente le nombre d'arbres ?

Avancé Solution de l'exercice manuel

SOLUTION DÉTAILLÉE

PARTIE 1 : Vote majoritaire

1.1) Décompte :

  • SPAM : Arbres 1, 3, 4 → $\textcolor{#e74c3c}{3 votes}$
  • HAM : Arbres 2, 5 → $\textcolor{#27ae60}{2 votes}$

1.2) Prédiction finale :

$\boxed{\text{SPAM} \text{ (3 votes contre 2)}}$

PARTIE 2 : Probabilité moyenne

2.1) Probabilité SPAM :

$$P(SPAM) = \frac{0.8 + (1-0.6) + 0.9 + 0.7 + (1-0.55)}{5}$$

$$= \frac{0.8 + 0.4 + 0.9 + 0.7 + 0.45}{5} = \frac{3.25}{5} = \textcolor{#e74c3c}{\mathbf{0.65}}$$

2.2) Probabilité HAM :

$$P(HAM) = 1 - 0.65 = \textcolor{#27ae60}{\mathbf{0.35}}$$

2.3) Confiance finale : $\boxed{65\%}$ pour SPAM

PARTIE 3 : Interprétation

3.1) Random Forest est plus robuste car :

  • Chaque arbre voit des données différentes (bootstrap)
  • Les erreurs individuelles se compensent
  • Le vote/moyenne réduit la variance

3.2) Plus d'arbres → prédictions plus stables (jusqu'à convergence)

Légende : $\textcolor{#e74c3c}{Rouge}$: SPAM, $\textcolor{#27ae60}{Vert}$: HAM

Code

Explorer les données

# Fonction pour formater les nombres au style francais (espace comme separateur)
def fmt(n, decimals=0):
    return f"{n:,.{decimals}f}".replace(",", " ")

print("=" * 70)
print("   EXPLORATION DU DATASET IMMOBILIER")
print("   Pour l'entrainement de notre Random Forest")
print("=" * 70)

# =================================================================
# 1. APERCU DES DONNEES
# =================================================================
print("\n" + "-" * 40)
print("1. APERCU DU DATASET")
print("-" * 40)
print("""
Ce dataset contient des biens immobiliers avec leurs caracteristiques
et prix de vente. Nous allons utiliser Random Forest pour predire
le prix a partir de la surface et du nombre de pieces.
""")
display(df.head(10), title="Dataset Immobilier")

# =================================================================
# 2. DIMENSIONS
# =================================================================
print("\n" + "-" * 40)
print("2. DIMENSIONS DU DATASET")
print("-" * 40)
n_samples = len(df)
n_features = 2
print(f"""
Nombre de biens immobiliers : {n_samples}
Nombre de features          : {n_features} (surface, nb_rooms)
Variable cible              : price (prix en euros)

Ce dataset est adapte pour une foret aleatoire:
- Assez d'echantillons pour le bootstrap sampling
- Features numeriques directement utilisables
""")

# =================================================================
# 3. STATISTIQUES DESCRIPTIVES
# =================================================================
print("\n" + "-" * 40)
print("3. STATISTIQUES DESCRIPTIVES")
print("-" * 40)
print("""
Comprendre la distribution des donnees aide a interpreter
les predictions du modele.
""")
display(df.describe().round(2), title="Statistiques")

# Interpretation
print(f"""
INTERPRETATION:

SURFACE:
  - Moyenne: {df['surface'].mean():.0f} m2
  - Etendue: {df['surface'].min():.0f} a {df['surface'].max():.0f} m2
  - Ecart-type: {df['surface'].std():.0f} m2

NOMBRE DE PIECES:
  - Moyenne: {df['nb_rooms'].mean():.1f} pieces
  - Etendue: {df['nb_rooms'].min():.0f} a {df['nb_rooms'].max():.0f} pieces

PRIX:
  - Moyenne: {fmt(df['price'].mean())} EUR
  - Etendue: {fmt(df['price'].min())} a {fmt(df['price'].max())} EUR
  - Prix median: {fmt(df['price'].median())} EUR
""")

# =================================================================
# 4. CORRELATIONS
# =================================================================
print("\n" + "-" * 40)
print("4. MATRICE DE CORRELATION")
print("-" * 40)
print("""
Les correlations montrent les liens entre variables.
Plus la correlation avec le prix est forte, plus la feature
est potentiellement predictive.
""")
corr_matrix = df.corr().round(3)
display(corr_matrix, title="Correlations")

# Interpreter les correlations
corr_surface = corr_matrix.loc['surface', 'price']
corr_rooms = corr_matrix.loc['nb_rooms', 'price']

print(f"""
INTERPRETATION DES CORRELATIONS:

Surface <-> Prix: {corr_surface:.3f}
  - {'Forte' if abs(corr_surface) > 0.7 else 'Moderee' if abs(corr_surface) > 0.4 else 'Faible'} correlation {'positive' if corr_surface > 0 else 'negative'}
  - {'La surface est un bon predicteur du prix' if abs(corr_surface) > 0.5 else 'Relation moderee'}

Nb_rooms <-> Prix: {corr_rooms:.3f}
  - {'Forte' if abs(corr_rooms) > 0.7 else 'Moderee' if abs(corr_rooms) > 0.4 else 'Faible'} correlation {'positive' if corr_rooms > 0 else 'negative'}

Surface <-> Nb_rooms: {corr_matrix.loc['surface', 'nb_rooms']:.3f}
  - Les grandes surfaces ont generalement plus de pieces
""")

# =================================================================
# CONCLUSION
# =================================================================
print("\n" + "=" * 70)
print("SYNTHESE POUR LE RANDOM FOREST")
print("=" * 70)
print(f"""
- {n_samples} echantillons disponibles (suffisant pour le bootstrap)
- Les deux features sont correlees au prix
- Pas de normalisation necessaire (Random Forest y est insensible)
- Les donnees sont pretes pour l'entrainement!
""")
Ctrl+Entrée

Cliquez sur « Exécuter » pour voir le résultat.

Code

Visualiser les relations

# Fonction pour formater les nombres au style francais (espace comme separateur)
def fmt(n, decimals=0):
    return f"{n:,.{decimals}f}".replace(",", " ")

print("=" * 70)
print("   VISUALISATION DES RELATIONS")
print("   Comprendre les liens entre features et prix")
print("=" * 70)

# =================================================================
# 1. CREATION DES GRAPHIQUES
# =================================================================
print("\n" + "-" * 40)
print("1. SCATTER PLOTS: FEATURES vs PRIX")
print("-" * 40)
print("""
Ces graphiques montrent comment le prix varie en fonction de chaque feature.
Un Random Forest peut capturer des relations non-lineaires!
""")

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Surface vs Prix
axes[0].scatter(df['surface'], df['price'], alpha=0.6, color='#9B7AC4',
                edgecolors='black', linewidth=0.5)
axes[0].set_xlabel('Surface (m2)', fontsize=12)
axes[0].set_ylabel('Prix (euros)', fontsize=12)
axes[0].set_title('Surface vs Prix', fontsize=14, fontweight='bold')
axes[0].grid(True, alpha=0.3)

# Ajouter une tendance
z = np.polyfit(df['surface'], df['price'], 1)
p = np.poly1d(z)
axes[0].plot(df['surface'].sort_values(), p(df['surface'].sort_values()),
             'r--', linewidth=2, label=f'Tendance lineaire')
axes[0].legend()

# Nb rooms vs Prix
axes[1].scatter(df['nb_rooms'], df['price'], alpha=0.6, color='#C09CF0',
                edgecolors='black', linewidth=0.5)
axes[1].set_xlabel('Nombre de pieces', fontsize=12)
axes[1].set_ylabel('Prix (euros)', fontsize=12)
axes[1].set_title('Nb Pieces vs Prix', fontsize=14, fontweight='bold')
axes[1].grid(True, alpha=0.3)

# Moyenne par nombre de pieces
mean_by_rooms = df.groupby('nb_rooms')['price'].mean()
axes[1].plot(mean_by_rooms.index, mean_by_rooms.values, 'ro-',
             linewidth=2, markersize=8, label='Moyenne par nb pieces')
axes[1].legend()

plt.tight_layout()
plt.show()

# =================================================================
# 2. INTERPRETATION DES GRAPHIQUES
# =================================================================
print("\n" + "-" * 40)
print("2. INTERPRETATION")
print("-" * 40)

# Calculer le prix moyen par m2
prix_m2 = df['price'].mean() / df['surface'].mean()

print(f"""
GRAPHIQUE 1 - Surface vs Prix:
- Relation {'lineaire' if df['surface'].corr(df['price']) > 0.9 else 'globalement lineaire avec dispersion'}
- Prix moyen par m2: {fmt(prix_m2)} EUR/m2
- La surface est le principal determinant du prix

GRAPHIQUE 2 - Nb Pieces vs Prix:
- Tendance croissante avec le nombre de pieces
- Dispersion plus importante (a nb pieces egal, les prix varient)
- Relation moins directe qu'avec la surface

POUR LE RANDOM FOREST:
- Peut capturer les interactions entre surface et nb_rooms
- N'est pas limite aux relations lineaires
- Chaque arbre peut trouver des regles differentes
""")

# =================================================================
# 3. DISTRIBUTION DES PRIX
# =================================================================
print("\n" + "-" * 40)
print("3. DISTRIBUTION DES PRIX")
print("-" * 40)

plt.figure(figsize=(10, 5))
plt.hist(df['price'], bins=20, color='#9B7AC4', edgecolor='black', alpha=0.7)
plt.axvline(df['price'].mean(), color='#F7E64D', linewidth=2,
            linestyle='--', label=f'Moyenne: {fmt(df["price"].mean())} EUR')
plt.axvline(df['price'].median(), color='#27ae60', linewidth=2,
            linestyle='--', label=f'Mediane: {fmt(df["price"].median())} EUR')
plt.xlabel('Prix (euros)', fontsize=12)
plt.ylabel('Nombre de biens', fontsize=12)
plt.title('Distribution des prix immobiliers', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(True, alpha=0.3, axis='y')
plt.show()

print(f"""
La distribution des prix permet de comprendre:
- Le marche couvre une gamme de {fmt(df['price'].min())} a {fmt(df['price'].max())} EUR
- La majorite des biens sont proches de la moyenne
- {'Distribution a peu pres symetrique' if abs(df['price'].mean() - df['price'].median()) < df['price'].std()*0.1 else 'Distribution legerement asymetrique'}
""")
Ctrl+Entrée

Cliquez sur « Exécuter » pour voir le résultat.

Contenu verrouillé
6 / 14

Continuez votre apprentissage

Vous avez exploré 6 sections de ce module. Connectez-vous pour débloquer le reste du cours, incluant les exercices pratiques et les solutions.

Ctrl ou Cmd plus Entrée exécute le code. Tab indente ; Échap puis Tab quitte l'éditeur.