2.5 Arborii de decizie
Un arbore de decizie pune întrebări simple în cascadă („glucoza > 140?") până ajunge la un răspuns. Este modelul cel mai ușor de explicat — și fundamentul Random Forest și Gradient Boosting.
Teorie
Construirea arborilor
Fiecare nod alege întrebarea care separă cel mai bine clasele (feature + prag). Datele se împart pe cele două ramuri și procesul se repetă recursiv până când nodurile sunt „pure" (o singură clasă) sau se atinge o limită. Frunza dă predicția: clasa majoritară din ea.
Criterii de separare: Gini și entropia
Impuritate Gini: G = 1 − Σ pᵢ² — probabilitatea ca două exemple
luate la întâmplare din nod să aibă clase diferite. 0 = nod pur.
Entropia: H = −Σ pᵢ·log₂(pᵢ) — câtă
„dezordine" e în nod. Se alege separarea cu cel mai mare câștig informațional.
Exemplu: un nod cu 50/50 din două clase are Gini = 1 − (0.5² + 0.5²) = 0.5 (maxim); un nod cu 90/10 are Gini = 1 − (0.81 + 0.01) = 0.18. În practică, Gini și entropia dau arbori aproape identici; Gini e implicit fiind mai rapid.
Controlul adâncimii și pruning
Lăsat liber, arborele crește până memorează fiecare exemplu — overfitting garantat. Frâne:
| Parametru | Efect |
|---|---|
max_depth | adâncimea maximă a arborelui |
min_samples_split | minim de exemple ca un nod să se mai împartă |
min_samples_leaf | minim de exemple într-o frunză |
ccp_alpha | pruning: taie după construire ramurile care nu ajută |
Pre-pruning = limitezi creșterea din start (parametrii de sus);
post-pruning = lași arborele să crească, apoi tai ramurile slabe (ccp_alpha).
Interpretare și utilizarea scikit-learn
from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt
model = DecisionTreeClassifier(max_depth=4, criterion="gini", random_state=42)
model.fit(X_train, y_train)
# desenezi arborele — argument imbatabil la explicarea soluției
plt.figure(figsize=(16, 8))
plot_tree(model, feature_names=X_train.columns,
class_names=True, filled=True, fontsize=8)
plt.show()
# ce feature-uri contează
for f, imp in sorted(zip(X_train.columns, model.feature_importances_),
key=lambda t: -t[1]):
print(f"{f}: {imp:.3f}")
Problemă rezolvată: clasificarea tipului de vin
Pe baza analizelor chimice (aciditate, zahăr, pH, alcool), determinăm tipul vinului. Arborele oferă și explicația: care analize contează cel mai mult.
Soluția pas cu pas
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")
feats = [c for c in train.columns if c not in ("SampleID", "tip")]
# alegem adâncimea prin cross-validation
for d in [3, 4, 5, 6, 8, None]:
m = DecisionTreeClassifier(max_depth=d, random_state=42)
print(d, cross_val_score(m, train[feats], train["tip"], cv=5).mean().round(3))
model = DecisionTreeClassifier(max_depth=5, random_state=42)
model.fit(train[feats], train["tip"])
pd.DataFrame({
"SampleID": test["SampleID"],
"tip": model.predict(test[feats])
}).to_csv("submission.csv", index=False)
Observație tipică: adâncimea None (nelimitată) dă scor de validare mai slab
decât 5 — overfitting vizibil în cifre.
Probleme propuse
Un nod are 8 exemple din clasa A și 2 din clasa B. Calculează impuritatea Gini și entropia. O separare le împarte în (8A, 0B) și (0A, 2B) — care e Gini-ul mediu ponderat după separare?
Antrenează un arbore cu max_depth=3 pe Iris, desenează-l cu plot_tree
și tradu în cuvinte drumul complet către clasa „virginica".
Pentru max_depth de la 1 la 20, desenează acuratețea pe train și pe validare pe
același grafic. Unde se despart curbele?
Problemă tabelară rezolvată cu succes cu modele pe bază de arbori.