Albero Decisionale
Impara come funzionano gli alberi decisionali, come costruire alberi di classificazione e regressione in Python con scikit-learn e visualizzarli.
Un albero decisionale è un algoritmo di machine learning supervisionato che effettua previsioni apprendendo una gerarchia di regole if-then-else dai dati di addestramento. Ogni nodo interno testa una caratteristica, ogni ramo rappresenta un risultato di quel test e ogni nodo foglia contiene una previsione (un'etichetta di classe per la classificazione, o un valore numerico per la regressione).
Questo capitolo tratta:
- Come gli alberi decisionali suddividono i dati usando misure di impurità (Gini ed entropia)
- Costruire un albero di classificazione e un albero di regressione in Python con
scikit-learn - Controllare la profondità dell'albero e prevenire l'overfitting con gli iperparametri
- Visualizzare e ispezionare un albero addestrato
- Vantaggi, limitazioni e quando usare gli alberi decisionali
Come un Albero Decisionale Divide i Dati
Durante l'addestramento, l'algoritmo esamina ogni caratteristica e ogni possibile soglia per trovare la divisione che riduce maggiormente l'impurità — una misura di quanto siano misti i classi in un nodo.
Due misure di impurità sono comuni in scikit-learn:
Impurità di Gini
L'impurità di Gini misura la probabilità di classificare erroneamente un campione scelto casualmente se fosse etichettato secondo la distribuzione delle classi nel nodo.
Gini(node) = 1 - Σ pᵢ²Un nodo puro (tutti i campioni appartengono a una classe) ha Gini = 0. Un nodo massimalmente misto ha Gini che si avvicina a 0,5 per la classificazione binaria.
Entropia e Guadagno di Informazione
L'entropia deriva dalla teoria dell'informazione. È massima quando le classi sono distribuite uniformemente e zero quando il nodo è puro.
Entropy(node) = -Σ pᵢ log₂(pᵢ)Il guadagno di informazione è la diminuzione di entropia dopo una divisione. L'algoritmo sceglie la divisione che produce il maggior guadagno di informazione. In scikit-learn, si sceglie tra le due misure tramite il parametro criterion ("gini" è il valore predefinito).
Divisione Ricorsiva
La divisione si ripete ricorsivamente su ogni nodo figlio fino a quando non viene soddisfatta una condizione di arresto: il nodo è puro, nessuna caratteristica migliora l'impurità, oppure viene raggiunto un limite di profondità o di dimensione. Questo produce la struttura ad albero binario.
Albero di Classificazione in Python
Il dataset Iris ha 150 campioni e 4 caratteristiche numeriche. L'obiettivo è prevedere una delle tre specie di fiori.
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# Load dataset
data = load_iris()
X, y = data.data, data.target
# Split: 80 % train, 20 % test
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Train — limit depth to 3 to keep the tree readable
clf = DecisionTreeClassifier(criterion="gini", max_depth=3, random_state=42)
clf.fit(X_train, y_train)
# Evaluate
y_pred = clf.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.2f}")
print(classification_report(y_test, y_pred, target_names=data.target_names))Output atteso:
Accuracy: 1.00
precision recall f1-score support
setosa 1.00 1.00 1.00 10
versicolor 1.00 1.00 1.00 9
virginica 1.00 1.00 1.00 11
accuracy 1.00 30
macro avg 1.00 1.00 1.00 30
weighted avg 1.00 1.00 1.00 30Il dataset Iris è linearmente separabile con profondità 3, quindi l'albero raggiunge un'accuratezza perfetta sul test. I dataset del mondo reale saranno più complessi.
Previsione di Nuovi Campioni
Dopo l'addestramento, chiama predict() per classificare nuove osservazioni e predict_proba() per ottenere le probabilità delle classi:
import numpy as np
# A new flower: sepal length 5.1, sepal width 3.5, petal length 1.4, petal width 0.2
new_sample = np.array([[5.1, 3.5, 1.4, 0.2]])
predicted_class = clf.predict(new_sample)
predicted_proba = clf.predict_proba(new_sample)
print("Predicted class:", data.target_names[predicted_class[0]])
print("Class probabilities:", predicted_proba)Output atteso:
Predicted class: setosa
Class probabilities: [[1. 0. 0.]]Albero di Regressione in Python
Gli alberi decisionali gestiscono anche target continui. Usa DecisionTreeRegressor al posto di DecisionTreeClassifier.
from sklearn.tree import DecisionTreeRegressor
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
# Synthetic regression dataset
X_reg, y_reg = make_regression(
n_samples=300, n_features=5, noise=20, random_state=42
)
X_train_r, X_test_r, y_train_r, y_test_r = train_test_split(
X_reg, y_reg, test_size=0.2, random_state=42
)
reg = DecisionTreeRegressor(max_depth=5, random_state=42)
reg.fit(X_train_r, y_train_r)
y_pred_r = reg.predict(X_test_r)
mse = mean_squared_error(y_test_r, y_pred_r)
r2 = r2_score(y_test_r, y_pred_r)
print(f"MSE : {mse:.2f}")
print(f"R² : {r2:.2f}")Un albero di regressione divide minimizzando l'errore quadratico medio (MSE) all'interno di ogni nodo e prevede il valore medio del target di tutti i campioni di addestramento che raggiungono una foglia.
Ottimizzazione degli Iperparametri
Senza limiti, un albero decisionale crescerà finché ogni foglia non sarà pura, memorizzando perfettamente il set di addestramento (overfitting). Gli iperparametri controllano la complessità dell'albero:
| Parametro | Predefinito | Effetto |
|---|---|---|
max_depth | None | Numero massimo di livelli. Più basso = albero più semplice. |
min_samples_split | 2 | Numero minimo di campioni richiesti per dividere un nodo. Più alto = meno divisioni. |
min_samples_leaf | 1 | Numero minimo di campioni richiesti in una foglia. Più alto = confini più uniformi. |
max_features | None | Numero di caratteristiche da considerare ad ogni divisione (utile per la selezione delle caratteristiche). |
criterion | "gini" | Misura di impurità: "gini" o "entropy" per i classificatori; "squared_error" per i regressori. |
Usa la cross-validation e la grid search per trovare la combinazione migliore:
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import GridSearchCV
data = load_iris()
X, y = data.data, data.target
param_grid = {
"max_depth": [2, 3, 4, 5, None],
"min_samples_split": [2, 5, 10],
"criterion": ["gini", "entropy"],
}
grid_search = GridSearchCV(
DecisionTreeClassifier(random_state=42),
param_grid,
cv=5,
scoring="accuracy",
)
grid_search.fit(X, y)
print("Best params :", grid_search.best_params_)
print(f"Best CV score: {grid_search.best_score_:.3f}")Output atteso (i valori possono variare leggermente tra versioni di scikit-learn):
Best params : {'criterion': 'gini', 'max_depth': 3, 'min_samples_split': 2}
Best CV score: 0.973Gestione delle Caratteristiche Categoriali
Gli alberi decisionali di scikit-learn richiedono input numerici. Codifica le colonne categoriali prima dell'addestramento:
- Categorie ordinali (es. taglia: piccola < media < grande): usa
OrdinalEncoder. - Categorie nominali (es. colore: rosso, verde, blu): usa
OneHotEncoderper evitare di implicare un ordine.
from sklearn.preprocessing import OrdinalEncoder
import numpy as np
# Encode only the categorical column; keep the numeric column as-is
sizes = np.array([["small"], ["large"], ["medium"], ["large"]])
weights = np.array([1.2, 3.4, 2.1, 4.0])
# Explicit category order: large=0, medium=1, small=2
enc = OrdinalEncoder(categories=[["large", "medium", "small"]])
sizes_encoded = enc.fit_transform(sizes)
X_encoded = np.column_stack([sizes_encoded, weights])
print(X_encoded)Output atteso:
[[2. 1.2]
[0. 3.4]
[1. 2.1]
[0. 4. ]]Consulta il capitolo sui Dati Categoriali per una guida completa.
Visualizzazione di un Albero Decisionale
Ispezionare la struttura dell'albero rivela quali caratteristiche guidano il maggior numero di divisioni e rende il modello verificabile.
Rappresentazione Testuale
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.datasets import load_iris
data = load_iris()
clf = DecisionTreeClassifier(max_depth=2, random_state=42)
clf.fit(data.data, data.target)
print(export_text(clf, feature_names=list(data.feature_names)))Output atteso:
|--- petal length (cm) <= 2.45
| |--- class: 0
|--- petal length (cm) > 2.45
| |--- petal width (cm) <= 1.75
| | |--- class: 1
| |--- petal width (cm) > 1.75
| | |--- class: 2Grafico Visivo
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris
data = load_iris()
clf = DecisionTreeClassifier(max_depth=2, random_state=42)
clf.fit(data.data, data.target)
plt.figure(figsize=(10, 5))
plot_tree(
clf,
feature_names=data.feature_names,
class_names=data.target_names,
filled=True,
rounded=True,
)
plt.title("Iris Decision Tree (max_depth=2)")
plt.tight_layout()
plt.savefig("iris_tree.png", dpi=150)
plt.show()filled=True colora ogni nodo in base alla sua classe maggioritaria; le sfumature più scure indicano una maggiore purezza della classe.
Importanza delle Caratteristiche
Dopo l'addestramento, feature_importances_ assegna a ogni caratteristica un punteggio tra 0 e 1, dove un valore più alto indica che la caratteristica ha contribuito maggiormente alla riduzione dell'impurità in tutte le divisioni:
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import numpy as np
data = load_iris()
clf = DecisionTreeClassifier(max_depth=3, random_state=42)
clf.fit(data.data, data.target)
importances = clf.feature_importances_
for name, imp in sorted(
zip(data.feature_names, importances), key=lambda x: x[1], reverse=True
):
print(f"{name:30s}: {imp:.4f}")Output atteso:
petal length (cm) : 0.5856
petal width (cm) : 0.4144
sepal length (cm) : 0.0000
sepal width (cm) : 0.0000Le caratteristiche con un'importanza pari a 0 non sono mai state usate da nessuna divisione e potrebbero essere eliminate per semplificare il modello.
Vantaggi e Limitazioni
Quando usare gli alberi decisionali
- Hai bisogno di un modello interpretabile — le regole possono essere stampate in linguaggio naturale.
- Il tuo dataset contiene un mix di caratteristiche numeriche e categoriali (dopo la codifica).
- Vuoi una baseline rapida prima di provare metodi ensemble.
- La relazione tra caratteristiche e target è non lineare o coinvolge interazioni.
Limitazioni
| Limitazione | Mitigazione |
|---|---|
| Tende all'overfitting senza regolazione | Limita max_depth, min_samples_leaf; usa la cross-validation |
| Alta varianza (piccoli cambiamenti nei dati → albero diverso) | Usa metodi ensemble: Random Forest / Bootstrap Aggregation |
| Tendenza verso caratteristiche con più valori unici | Usa max_features o normalizza i criteri di divisione |
| Scarsa capacità di estrapolazione oltre il range dei dati di addestramento | Preferisci modelli lineari per compiti di estrapolazione |
| Solo divisioni allineate agli assi | Esistono alberi obliqui ma non sono in scikit-learn |
Alberi Decisionali vs. Algoritmi Correlati
| Algoritmo | Differenza chiave |
|---|---|
| Regressione Logistica | Confine lineare; migliore per dati linearmente separabili; non gestisce automaticamente le interazioni |
| K-Nearest Neighbors | Basato sulle istanze; nessun modello esplicito; richiede la scalatura delle caratteristiche |
| Albero Decisionale | Non lineare; nessuna scalatura necessaria; altamente interpretabile |
| Random Forest (vedi Bootstrap Aggregation) | Ensemble di molti alberi; varianza molto più bassa; meno interpretabile |
Punti Chiave
- Gli alberi decisionali dividono i dati massimizzando il guadagno di informazione (o minimizzando l'impurità di Gini) a ogni nodo; il processo si ripete ricorsivamente.
DecisionTreeClassifiereDecisionTreeRegressorin scikit-learn condividono la stessa API e gli stessi nomi degli iperparametri.- Imposta sempre
max_depthomin_samples_leafper prevenire l'overfitting; ottimizzali con la grid search e la cross-validation. feature_importances_rivela su quali caratteristiche l'albero si basa maggiormente — utile per la selezione delle caratteristiche.- Gli alberi singoli sono una buona baseline interpretabile, ma i metodi ensemble come Random Forest li superano quasi sempre sui dati del mondo reale.