Ensemble-Methoden – Random Forest & Gradient Boosting

Fach: Data Science & Artificial Intelligence
Schulstufe: 12. Schulstufe – HTL Informatik
Voraussetzungen: Supervised Learning, Decision Trees, Python / scikit-learn
Autor: HTL Pinkafeld – IF/IT



1. Warum Ensemble-Methoden?

Ein einzelner Decision Tree hat ein bekanntes Problem: er neigt stark zu Overfitting – der Baum lernt die Trainingsdaten auswendig, generalisiert aber schlecht auf neue Daten.

Die Idee hinter Ensembles

„Viele schwache Lerner zusammen ergeben einen starken Lerner."

Statt einem einzigen komplexen Modell kombinieren Ensemble-Methoden viele einfache Modelle (sog. Base Learner) zu einer gemeinsamen Vorhersage. Die Analogie aus dem Alltag:

Vorteile gegenüber einzelnen Modellen

Eigenschaft Einzelner Decision Tree Ensemble
Overfitting Sehr anfällig Deutlich reduziert
Stabilität Instabil (kleine Änderungen → anderer Baum) Robust
Genauigkeit Mittel Hoch
Interpretierbarkeit Gut Eingeschränkt
Trainingszeit Schnell Länger

2. Grundprinzipien: Bagging und Boosting

Es gibt zwei grundlegende Strategien, wie Modelle kombiniert werden können:

2.1 Bagging (Bootstrap Aggregating)

Beim Bagging werden mehrere Modelle unabhängig voneinander auf verschiedenen Teilmengen der Trainingsdaten trainiert. Die Vorhersagen werden anschließend aggregiert.

Trainingsdaten
      │
      ├─ Zufällige Stichprobe 1 → Modell 1 ─┐
      ├─ Zufällige Stichprobe 2 → Modell 2 ─┤→ Mehrheitsvoting / Mittelwert
      ├─ Zufällige Stichprobe 3 → Modell 3 ─┤
      └─ ...                                 ┘

2.2 Boosting

Beim Boosting werden Modelle sequentiell trainiert. Jedes neue Modell konzentriert sich auf die Fehler seines Vorgängers.

Trainingsdaten
      │
      └─ Modell 1 → Fehler analysieren
                          │
                    Modell 2 (fokussiert auf Fehler von M1) → Fehler analysieren
                                                                    │
                                                             Modell 3 (fokussiert auf Fehler von M2)
                                                                    │
                                                             Finale Vorhersage (gewichtete Summe)

2.3 Gegenüberstellung

Bagging Boosting
Trainingsreihenfolge Parallel Sequentiell
Fokus Varianz reduzieren Bias reduzieren
Datenpunkte Zufällige Teilmengen Fehlklassifizierte stärker gewichtet
Overfitting-Risiko Gering Kann hoch sein bei zu vielen Iterationen
Beispiele Random Forest Gradient Boosting, XGBoost, AdaBoost

3. Random Forest

3.1 Grundidee

Random Forest ist die bekannteste Bagging-Methode. Es werden viele Decision Trees trainiert, die sich in zwei Punkten unterscheiden:

  1. Zufällige Datenstichproben: Jeder Baum wird auf einer Bootstrap-Stichprobe trainiert (~63 % der Daten)
  2. Zufällige Feature-Auswahl: Bei jedem Split wird nur eine zufällige Teilmenge der Features betrachtet

Die Zufälligkeit sorgt dafür, dass die Bäume diversifiziert sind – sie machen unterschiedliche Fehler, die sich beim Aggregieren gegenseitig aufheben.

3.2 Vorhersage

3.3 Out-of-Bag Error (OOB)

Da jeder Baum nur auf ~63 % der Daten trainiert wird, können die restlichen ~37 % (Out-of-Bag-Samples) als Validierungsset verwendet werden – ohne separaten Test-Split.

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# Daten laden und aufteilen
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.2, random_state=42
)

# Random Forest trainieren
rf = RandomForestClassifier(
    n_estimators=100,    # Anzahl der Bäume
    max_depth=None,      # Bäume wachsen vollständig
    max_features='sqrt', # √(Anzahl Features) pro Split
    oob_score=True,      # Out-of-Bag-Evaluation aktivieren
    random_state=42
)
rf.fit(X_train, y_train)

# Ergebnisse
print(f"Test Accuracy:  {accuracy_score(y_test, rf.predict(X_test)):.2%}")
print(f"OOB Accuracy:   {rf.oob_score_:.2%}")

3.4 Wichtige Hyperparameter

Parameter Bedeutung Typische Werte
n_estimators Anzahl der Bäume 100–500
max_depth Maximale Tiefe jedes Baums None, 5–20
max_features Features pro Split 'sqrt', 'log2', float
min_samples_split Mindestproben für einen Split 2–10
min_samples_leaf Mindestproben in einem Blatt 1–5

3.5 Stärken und Schwächen

✅ Stärken ❌ Schwächen
Sehr robust gegen Overfitting Weniger interpretierbar als einzelner Baum
Funktioniert gut ohne viel Tuning Hoher Speicherbedarf bei vielen Bäumen
Liefert Feature Importance Langsamer als einzelner Baum
Integrierte OOB-Validierung Schlechter bei sehr hochdimensionalen Daten
Kein Scaling der Features nötig Extrapoliert nicht gut (nur Interpolation)

4. Gradient Boosting

4.1 Grundidee

Gradient Boosting baut Bäume sequentiell, wobei jeder neue Baum den Restfehler (Residual) des bisherigen Modells minimiert.

Der Name leitet sich von der Gradientenabstieg-Optimierung ab: Anstatt Parameter eines einzelnen Modells zu optimieren, werden neue Modelle in die Richtung des stärksten Fehlerabbaus hinzugefügt.

4.2 Algorithmus Schritt für Schritt

1. Starte mit einem einfachen Modell F₀ (z.B. Mittelwert von y)

2. Für jede Iteration t = 1, 2, ..., T:
   a) Berechne die Residuen: rᵢ = yᵢ - Fₜ₋₁(xᵢ)
   b) Trainiere einen neuen Baum hₜ auf den Residuen
   c) Aktualisiere das Modell:
      Fₜ(x) = Fₜ₋₁(x) + η · hₜ(x)
      (η = Lernrate, steuert Schrittgröße)

3. Finale Vorhersage: F_T(x)

Intuition: Jeder neue Baum „repariert" die Fehler aller vorherigen Bäume. Die Lernrate η kontrolliert, wie stark jeder Baum beiträgt – kleine Werte führen zu robusteren Modellen, brauchen aber mehr Bäume.

4.3 Scikit-learn: Gradient Boosting

from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import classification_report

# Brustkrebsdatensatz (binäre Klassifikation)
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42
)

# Gradient Boosting Modell
gb = GradientBoostingClassifier(
    n_estimators=200,      # Anzahl Boosting-Runden
    learning_rate=0.1,     # Lernrate η
    max_depth=3,           # Flache Bäume sind typisch!
    subsample=0.8,         # Stochastic Gradient Boosting
    random_state=42
)
gb.fit(X_train, y_train)

print(classification_report(y_test, gb.predict(X_test),
      target_names=data.target_names))

4.4 Wichtige Hyperparameter

Parameter Bedeutung Hinweis
n_estimators Anzahl der Bäume Mehr = besser, aber langsamer
learning_rate Schrittgröße pro Baum Kleiner = robuster, mehr Bäume nötig
max_depth Tiefe jedes Baums Typisch: 3–5 (flach!)
subsample Anteil Trainingsdaten pro Baum < 1.0 = Stochastic GB

Wichtige Faustregel: learning_rate und n_estimators sind gegenläufig – kleine Lernrate braucht mehr Bäume. Typische Kombination: learning_rate=0.05, n_estimators=500.


5. XGBoost – Extreme Gradient Boosting

5.1 Was ist XGBoost?

XGBoost ist eine hochoptimierte Implementierung von Gradient Boosting, die in vielen Kaggle-Wettbewerben Maßstäbe gesetzt hat. Es bietet gegenüber scikit-learns GradientBoosting:

5.2 Installation und Verwendung

pip install xgboost
import xgboost as xgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.2, random_state=42
)

# XGBoost-Modell (scikit-learn API)
model = xgb.XGBClassifier(
    n_estimators=300,
    learning_rate=0.05,
    max_depth=4,
    subsample=0.8,
    colsample_bytree=0.8,   # Anteil Features pro Baum
    reg_alpha=0.1,           # L1-Regularisierung
    reg_lambda=1.0,          # L2-Regularisierung
    use_label_encoder=False,
    eval_metric='logloss',
    random_state=42
)

# Training mit Early Stopping
model.fit(
    X_train, y_train,
    eval_set=[(X_test, y_test)],
    early_stopping_rounds=20,  # Stoppt wenn 20 Runden keine Verbesserung
    verbose=False
)

print(f"Beste Iteration: {model.best_iteration}")
print(f"Test Accuracy: {accuracy_score(y_test, model.predict(X_test)):.2%}")

5.3 Early Stopping

Early Stopping verhindert Overfitting automatisch:

Iteration  1: Validation-Loss = 0.342
Iteration  5: Validation-Loss = 0.198
Iteration 10: Validation-Loss = 0.145  ← Minimum
Iteration 15: Validation-Loss = 0.148
Iteration 20: Validation-Loss = 0.151
Iteration 30: Validation-Loss = 0.163  ← Stop! (20 Runden ohne Verbesserung)

6. Vergleich der Ensemble-Methoden

6.1 Direkter Vergleich auf Breast-Cancer-Datensatz

from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_breast_cancer
import xgboost as xgb
import numpy as np

data = load_breast_cancer()
X, y = data.data, data.target

modelle = {
    'Decision Tree':      DecisionTreeClassifier(max_depth=5, random_state=42),
    'Random Forest':      RandomForestClassifier(n_estimators=100, random_state=42),
    'Gradient Boosting':  GradientBoostingClassifier(n_estimators=100, random_state=42),
    'XGBoost':            xgb.XGBClassifier(n_estimators=100, random_state=42,
                              eval_metric='logloss', verbosity=0),
}

print(f"{'Modell':<22} {'CV Accuracy':>12} {'Std':>8}")
print("-" * 45)
for name, model in modelle.items():
    scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
    print(f"{name:<22} {scores.mean():>11.2%} {scores.std():>7.2%}")

6.2 Wann welche Methode?

Situation Empfehlung
Schneller guter Baseline Random Forest
Maximale Genauigkeit XGBoost / LightGBM
Viele irrelevante Features Random Forest (robuster)
Strukturierte/tabellarische Daten XGBoost
Interpretierbarkeit wichtig Random Forest (Feature Importance)
Wenig Zeit für Tuning Random Forest
Kaggle-Wettbewerbe XGBoost / LightGBM

7. Hyperparameter-Tuning

Systematisches Durchsuchen aller Parameterkombinationen:

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer

data = load_breast_cancer()

param_grid = {
    'n_estimators': [100, 200, 300],
    'max_depth':    [None, 5, 10],
    'max_features': ['sqrt', 'log2'],
}

rf = RandomForestClassifier(random_state=42)
grid_search = GridSearchCV(
    rf, param_grid,
    cv=5,
    scoring='accuracy',
    n_jobs=-1,       # alle CPU-Kerne nutzen
    verbose=1
)
grid_search.fit(data.data, data.target)

print(f"Beste Parameter: {grid_search.best_params_}")
print(f"Beste CV Accuracy: {grid_search.best_score_:.2%}")

Bei vielen Hyperparametern ist Random Search effizienter als Grid Search – es werden zufällige Kombinationen getestet:

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint

param_dist = {
    'n_estimators': randint(50, 500),
    'max_depth':    [None, 3, 5, 10, 20],
    'max_features': ['sqrt', 'log2', 0.3, 0.5],
    'min_samples_leaf': randint(1, 10),
}

random_search = RandomizedSearchCV(
    RandomForestClassifier(random_state=42),
    param_dist,
    n_iter=50,       # 50 zufällige Kombinationen testen
    cv=5,
    scoring='accuracy',
    n_jobs=-1,
    random_state=42
)
random_search.fit(data.data, data.target)
print(f"Beste Parameter: {random_search.best_params_}")

7.3 Vergleich der Suchmethoden

Methode Vorteil Nachteil
Grid Search Findet garantiert das Beste im Raster Exponentiell teuer bei vielen Parametern
Random Search Deutlich schneller, meist ähnlich gut Kein Guarantee auf beste Kombination
Bayesian Optimization Intelligenteste Suche Komplexer zu implementieren (optuna, hyperopt)

8. Feature Importance und Interpretierbarkeit

8.1 Feature Importance aus Random Forest

import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer

data = load_breast_cancer()
rf = RandomForestClassifier(n_estimators=200, random_state=42)
rf.fit(data.data, data.target)

# Feature Importance als DataFrame
importance_df = pd.DataFrame({
    'feature': data.feature_names,
    'importance': rf.feature_importances_
}).sort_values('importance', ascending=True).tail(10)

# Visualisierung
importance_df.plot(kind='barh', x='feature', y='importance', legend=False)
plt.xlabel('Wichtigkeit (Gini-Impurity Reduktion)')
plt.title('Top 10 wichtigste Features – Random Forest')
plt.tight_layout()
plt.show()

8.2 Permutation Importance

Eine robustere Alternative: Features werden zufällig durchgemischt, und der Abfall in der Modellgüte wird gemessen.

from sklearn.inspection import permutation_importance

result = permutation_importance(rf, X_test, y_test, n_repeats=10, random_state=42)

perm_df = pd.DataFrame({
    'feature': data.feature_names,
    'importance_mean': result.importances_mean,
    'importance_std':  result.importances_std
}).sort_values('importance_mean', ascending=False).head(10)

print(perm_df.to_string(index=False))

8.3 SHAP-Werte (Ausblick)

Für tiefergehende Interpretierbarkeit bietet die SHAP-Bibliothek erklärbare Vorhersagen auf Einzelfall-Ebene:

# pip install shap
import shap

explainer = shap.TreeExplainer(rf)
shap_values = explainer.shap_values(X_test[:10])

# Visualisierung für einen einzelnen Datenpunkt
shap.force_plot(explainer.expected_value[1], shap_values[1][0], X_test[0],
                feature_names=data.feature_names)

9. Praktisches Projekt: Titanic-Überleben

9.1 Aufgabenstellung

Auf Basis von Passagierdaten (Alter, Klasse, Geschlecht, ...) soll vorhergesagt werden, ob eine Person den Titanic-Untergang überlebt hat.

9.2 Vollständige Pipeline

import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import classification_report, confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt

# ── 1. Daten erstellen (vereinfacht) ──────────────────────────────────────
np.random.seed(42)
n = 500

data = pd.DataFrame({
    'pclass':    np.random.choice([1, 2, 3], n, p=[0.25, 0.25, 0.5]),
    'sex':       np.random.choice(['male', 'female'], n, p=[0.65, 0.35]),
    'age':       np.random.normal(30, 14, n).clip(1, 80),
    'sibsp':     np.random.choice([0, 1, 2, 3], n, p=[0.6, 0.25, 0.1, 0.05]),
    'fare':      np.random.exponential(35, n).clip(5, 500),
    'embarked':  np.random.choice(['S', 'C', 'Q'], n, p=[0.72, 0.19, 0.09]),
})

# Überleben hängt von Klasse und Geschlecht ab
survive_prob = (
    (data['sex'] == 'female') * 0.5 +
    (data['pclass'] == 1) * 0.2 +
    (data['pclass'] == 2) * 0.1 -
    (data['age'] > 60) * 0.1 + 0.15
).clip(0.05, 0.95)
data['survived'] = np.random.binomial(1, survive_prob)

# ── 2. Preprocessing ──────────────────────────────────────────────────────
le = LabelEncoder()
data['sex_enc'] = le.fit_transform(data['sex'])
data['embarked_enc'] = le.fit_transform(data['embarked'])

features = ['pclass', 'sex_enc', 'age', 'sibsp', 'fare', 'embarked_enc']
X = data[features]
y = data['survived']

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# ── 3. Modelle vergleichen ────────────────────────────────────────────────
modelle = {
    'Random Forest':     RandomForestClassifier(n_estimators=100, random_state=42),
    'Gradient Boosting': GradientBoostingClassifier(n_estimators=100, learning_rate=0.1,
                                                     max_depth=3, random_state=42),
}

for name, model in modelle.items():
    model.fit(X_train, y_train)
    cv = cross_val_score(model, X, y, cv=5).mean()
    print(f"\n{name}  (CV: {cv:.2%})")
    print(classification_report(y_test, model.predict(X_test),
                                  target_names=['Nicht überlebt', 'Überlebt']))

# ── 4. Confusion Matrix visualisieren ────────────────────────────────────
best_model = modelle['Random Forest']
cm = confusion_matrix(y_test, best_model.predict(X_test))
sns.heatmap(cm, annot=True, fmt='d', cmap='Oranges',
            xticklabels=['Nicht überlebt', 'Überlebt'],
            yticklabels=['Nicht überlebt', 'Überlebt'])
plt.title('Confusion Matrix – Random Forest Titanic')
plt.tight_layout()
plt.show()

10. Zusammenfassung und Ausblick

Überblick Ensemble-Methoden

Methode Strategie Basis-Lerner Stärke
Random Forest Bagging + zufällige Features Decision Trees Robust, kein Tuning nötig
Gradient Boosting Boosting (Residuen) Flache Decision Trees Hohe Genauigkeit
XGBoost Optimiertes Boosting Flache Decision Trees Schnell, regularisiert

Checkliste: Ensemble-Methoden einsetzen

✅ Ausgangspunkt: Random Forest als schneller, robuster Baseline
✅ Für maximale Genauigkeit: XGBoost / Gradient Boosting
✅ Hyperparameter: n_estimators + learning_rate zuerst tunen
✅ Overfitting prüfen: Train-Score vs. Test-Score / Cross-Validation
✅ Feature Importance analysieren: irrelevante Features entfernen
✅ Early Stopping bei Boosting-Methoden aktivieren

Ausblick: Nächste Themen


Anhang: Schnellreferenz Ensemble-Methoden

# Random Forest
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
rf = RandomForestClassifier(n_estimators=100, oob_score=True, random_state=42)

# Gradient Boosting
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
gb = GradientBoostingClassifier(n_estimators=200, learning_rate=0.05, max_depth=3)

# XGBoost
import xgboost as xgb
xgb_model = xgb.XGBClassifier(n_estimators=300, learning_rate=0.05,
                                max_depth=4, subsample=0.8,
                                colsample_bytree=0.8, eval_metric='logloss')

# Hyperparameter-Tuning
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV

# Feature Importance
importances = rf.feature_importances_          # Gini-basiert
from sklearn.inspection import permutation_importance
perm = permutation_importance(rf, X_test, y_test, n_repeats=10)

HTL Pinkafeld – IF/IT | Data Science & AI | 12. Schulstufe

Vorheriges ThemaSupervised Learning Nächstes ThemaUnsupervised Learning