Ensembler: random forest och boosting
Kunna träna random forest och gradient boosting och förklara varför ensembler generaliserar bättre.
Förkunskaper
- DBeslutsträdkrävs
Intuition
Ett enskilt beslutsträd är instabilt: byt ut några datapunkter och trädet ser helt annorlunda ut. Hög varians, låg bias.
Ensembler löser det på två helt olika sätt:
| Bagging (random forest) | Boosting (XGBoost m.fl.) | |
|---|---|---|
| Träden tränas | oberoende, parallellt | sekventiellt |
| Varje träd ser | ett bootstrap-urval + slumpade kolumner | alla data, men viktade mot tidigare fel |
| Kombineras genom | röstning eller medelvärde | summering med lärhastighet |
| Minskar | varians | bias (och varians) |
| Djupa träd? | ja, fullvuxna | nej, grunda (3–8 nivåer) |
| Risk för överanpassning | låg | hög utan reglering |
Bagging utgår från att medelvärdet av många brusiga men obiaserade skattningar har lägre varians. Därför ska varje träd vara djupt och överanpassat — medelvärdesbildningen städar upp.
Boosting bygger i stället varje nytt träd för att rätta föregående träds fel. Därför ska varje träd vara grunt: det ska bidra med en liten korrigering, inte en hel lösning.
Formellt
Varför bagging fungerar. Med oberoende skattare med varians och parvis korrelation har medelvärdet variansen
Den andra termen försvinner när växer — men den första gör det inte. Därför är det inte antalet träd som är begränsningen utan hur korrelerade de är.
Det är precis varför random forest slumpar kolumner vid varje delning, inte bara rader: utan det skulle alla träd välja samma starka feature högst upp och bli nästan identiska. Kolumnsamplingen (max_features) är den parameter som sänker , och därför den viktigaste.
Gradient boosting är gradientnedstigning i funktionsrummet. Varje nytt träd anpassas till förlustens negativa gradient med avseende på nuvarande prediktion:
Lärhastigheten (ofta 0,05–0,1) och antalet träd byter mot varandra: halvera och du behöver ungefär dubbelt så många träd.
Parametrar som betyder något:
| Random forest | Gradient boosting |
|---|---|
n_estimators (fler är aldrig sämre) | n_estimators (fler kan överanpassa) |
max_features — viktigast | learning_rate — viktigast |
min_samples_leaf | max_depth (3–8) |
subsample, colsample | |
| tidigt stopp på validering |
Skillnaden i första raden är avgörande: i random forest kan du alltid lägga till träd, i boosting måste antalet regleras.
OOB-skattning är random forests bonus: varje träd ser bara cirka 63 % av datan, så de återstående 37 % fungerar som en gratis valideringsmängd. Ingen separat uppdelning behövs.
Out-of-fold-boosting i praktiken: använd alltid tidigt stopp mot en valideringsmängd. Utan det är antalet träd en gissning, och gissar du för högt överanpassar modellen tyst.
Kod
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, HistGradientBoostingClassifier
X, y = make_classification(n_samples=4000, n_features=30, n_informative=8,
n_redundant=5, flip_y=0.05, random_state=0)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, random_state=0)
for namn, m in (("ett träd", DecisionTreeClassifier(random_state=0)),
("random forest", RandomForestClassifier(n_estimators=300, random_state=0,
oob_score=True)),
("boosting", HistGradientBoostingClassifier(random_state=0,
early_stopping=True))):
m.fit(Xtr, ytr)
extra = f" OOB {m.oob_score_:.3f}" if hasattr(m, "oob_score_") else ""
print(f"{namn:<14} träning {m.score(Xtr, ytr):.3f} test {m.score(Xte, yte):.3f}{extra}")
# Varför kolumnsampling: korrelationen mellan träden avgör
for mf in ("sqrt", 0.5, 1.0):
rf = RandomForestClassifier(n_estimators=200, max_features=mf, random_state=0)
print(f" max_features={str(mf):<5} CV {cross_val_score(rf, X, y, cv=5).mean():.4f}")
# max_features=1.0 betyder att alla träd ser alla kolumner → mer korrelerade → sämre
# Lärhastighet och antal träd byter mot varandra
for lr, n in ((0.3, 50), (0.1, 150), (0.05, 300)):
gb = HistGradientBoostingClassifier(learning_rate=lr, max_iter=n,
early_stopping=False, random_state=0)
print(f" lr={lr:<5} n={n:<4} CV {cross_val_score(gb, X, y, cv=5).mean():.4f}")
# Boosting UTAN tidigt stopp överanpassar tyst
for n in (50, 200, 1000):
gb = HistGradientBoostingClassifier(max_iter=n, learning_rate=0.1,
early_stopping=False, random_state=0).fit(Xtr, ytr)
print(f" n={n:<5} träning {gb.score(Xtr, ytr):.3f} test {gb.score(Xte, yte):.3f}")
Sista blocket är poängen: träningspoängen fortsätter uppåt medan testpoängen planar ut eller faller. I random forest händer inte det — där kan du lägga till träd utan risk.
Behärskning innebär
- Förklarar bagging och boosting
- Tränar och ställer in båda
- Vet varför ensembler generaliserar bättre
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- scikit-learn User Guide (BSD-3) — BSD-3-Clause
- Hastie, Tibshirani & Friedman — The Elements of Statistical Learning — fri att läsa online (författarnas utgåva)
- arXiv — XGBoost: A Scalable Tree Boosting System — arXiv (öppen åtkomst; licens per artikel)