Laboratorio: Alberi di Decisione
Benvenuto nel laboratorio pratico dell’Unità 3. Qui imparerai a implementare alberi decisionali e Random Forest in Python usando Scikit-Learn, regolarizzare i modelli per prevenire l’overfitting e tracciarne la struttura grafica.
1. Configurazione e Download
Ti consigliamo di svolgere questo laboratorio in locale sul tuo computer scaricando ed eseguendo il Notebook Jupyter dedicato:
2. Esecuzione del Laboratorio Passo dopo Passo
Di seguito vediamo come costruire il codice passo dopo passo, analizzando le scelte di parametri e funzioni per ciascuna sezione.
Passo 1: Importazione delle Librerie e Generazione dei Dati
Iniziamo importando i moduli necessari e generando un dataset fittizio di classificazione.
import matplotlib.pyplot as pltfrom sklearn.datasets import make_classificationfrom sklearn.model_selection import train_test_splitfrom sklearn.tree import DecisionTreeClassifier, plot_treefrom sklearn.ensemble import RandomForestClassifierfrom sklearn.metrics import accuracy_score
# 1. Generiamo un dataset sintetico (500 campioni, 2 feature per facilitare la visualizzazione)X, y = make_classification(n_samples=500, n_features=2, n_redundant=0, n_clusters_per_class=1, random_state=42) # random_state=42 blocca la casualità per riproducibilità# print(X) # Decommenta per visualizzare i dati del dataset X generatomake_classification: È una funzione di Scikit-Learn che genera dataset sintetici (casuali ma dotati di una struttura logica interna). È ideale per esercitarsi e testare modelli di classificazione senza dover caricare file esterni.n_samples=500en_features=2: Generiamo un dataset composto da 500 campioni e sole 2 caratteristiche (colonne). Manteniamo il numero di feature a 2 in modo da poter visualizzare e comprendere facilmente lo schema grafico finale dell’albero.random_state=42: Imposta un “seme” per il generatore di numeri casuali. Questo garantisce che, ogni volta che esegui il codice, l’albero si divida esattamente nello stesso modo, rendendo i tuoi esperimenti ripetibili e confrontabili.
Passo 2: Suddivisione in Training Set e Test Set
Dividiamo i dati destinando l’80% all’addestramento e il 20% alla verifica finale (test set).
# 2. Dividiamo in Train (80%) e Test set (20% per valutare l'accuratezza neutrale)X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)train_test_split: Separa i dati in Training Set (80%) per addestrare i due modelli e Test Set (20%) per valutarne l’accuratezza in modo neutrale su dati mai visti prima.test_size=0.2: Consiste nello standard di riservare il 20% dei campioni (100 in totale) come Test Set e l’80% (400 campioni) come Training Set.
Passo 3: Addestramento e Valutazione di un Singolo Albero di Decisione
Creiamo un albero decisionale limitando la profondità a max_depth=3 per evitare che cresca all’infinito memorizzando i dati a memoria (overfitting).
# 3. Addestriamo un Albero di Decisione (limite di profondità max_depth=3 per prevenire overfitting)albero = DecisionTreeClassifier(max_depth=3, random_state=42)albero.fit(X_train, y_train)
# Previsioni e valutazione dell'Albero sul Test Sety_pred_alb = albero.predict(X_test)print(f"Accuratezza del singolo Albero: {accuracy_score(y_test, y_pred_alb) * 100:.1f}%")DecisionTreeClassifier: La classe ufficiale che implementa l’algoritmo del singolo albero di decisione per la classificazione.max_depth=3: Questo parametro limita la crescita dell’albero a una profondità massima di 3 livelli. È lo strumento principale di regolarizzazione: impedendo all’albero di dividersi ulteriormente, si previene l’overfitting.- Consiglio Pratico / Esperimento: Prova a modificare questo valore impostando
max_depth=1(albero troppo semplice, underfitting) o rimuovendolo del tutto impostandomax_depth=None(albero libero di crescere, overfitting). Riesegui il codice e osserva come cambia l’accuratezza sul Test Set: noterai che in entrambi i casi le prestazioni sul Test Set calano rispetto a una profondità intermedia ottimale!
- Consiglio Pratico / Esperimento: Prova a modificare questo valore impostando
fit(X_train, y_train): Calcola ricorsivamente le soglie ottimali su ciascuna caratteristica massimizzando il Guadagno di Gini ad ogni livello.accuracy_score: Calcola la percentuale di risposte corrette del modello sul Test Set.
Passo 4: Addestramento e Valutazione della Random Forest
Creiamo una Random Forest composta da 100 alberi decisionali differenti che cooperano tra loro tramite voto di maggioranza.
# 4. Addestriamo una Random Forest (100 alberi decisionali indipendenti cooperanti per abbattere la varianza)foresta = RandomForestClassifier(n_estimators=100, max_depth=3, random_state=42)foresta.fit(X_train, y_train)
# Previsioni e valutazione della Forestay_pred_for = foresta.predict(X_test)print(f"Accuratezza della Random Forest: {accuracy_score(y_test, y_pred_for) * 100:.1f}%")RandomForestClassifier: La classe ufficiale per creare e gestire le Random Forest (apprendimento d’ensemble).n_estimators=100: Definisce il numero di alberi decisionali da addestrare in parallelo all’interno della foresta. Inserire più alberi aumenta la robustezza del modello riducendone la varianza, ma richiede più memoria e tempo di calcolo. Un valore di 100 è lo standard predefinito ottimale.max_depth=3: Anche in questo caso stiamo ponendo un limite alla profondità di ciascuno dei 100 singoli alberi componenti la foresta.
Passo 5: Visualizzazione Grafica dell’Albero di Decisione
Uno dei grandi vantaggi degli alberi di decisione è la loro interpretabilità. Possiamo disegnare l’albero per vedere esattamente quali domande ha scelto di fare l’algoritmo.
# 5. Disegniamo la struttura dell'Albero di Decisione (interpretabilità grafica)plt.figure(figsize=(12, 8))plot_tree(albero, feature_names=["Caratteristica A", "Caratteristica B"], class_names=["Classe 0", "Classe 1"], filled=True, # Colora i nodi (arancione classe 0, blu classe 1; più scuro = più puro) rounded=True) # Nodi con spigoli arrotondati per leggibilitàplt.title("Struttura dell'Albero di Decisione Addestrato (max_depth=3)")plt.show()plot_tree(...): Genera il diagramma dell’albero.filled=True: Colora i nodi per identificare visivamente la classe predominante al loro interno (es. arancione per la Classe 0 e blu per la Classe 1). L’intensità del colore indica la purezza del nodo: più il colore è scuro ed acceso, più l’impurezza di Gini del nodo è vicina a 0.rounded=True: Disegna i nodi con angoli arrotondati per migliorarne la leggibilità grafica.
Visualizza il codice completo (pronto da copiare ed eseguire)
import matplotlib.pyplot as pltfrom sklearn.datasets import make_classificationfrom sklearn.model_selection import train_test_splitfrom sklearn.tree import DecisionTreeClassifier, plot_treefrom sklearn.ensemble import RandomForestClassifierfrom sklearn.metrics import accuracy_score
X, y = make_classification(n_samples=500, n_features=2, n_redundant=0, n_clusters_per_class=1, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
albero = DecisionTreeClassifier(max_depth=3, random_state=42)albero.fit(X_train, y_train)y_pred_alb = albero.predict(X_test)print(f"Accuratezza del singolo Albero: {accuracy_score(y_test, y_pred_alb) * 100:.1f}%")
foresta = RandomForestClassifier(n_estimators=100, max_depth=3, random_state=42)foresta.fit(X_train, y_train)y_pred_for = foresta.predict(X_test)print(f"Accuratezza della Random Forest: {accuracy_score(y_test, y_pred_for) * 100:.1f}%")
plt.figure(figsize=(12, 8))plot_tree(albero, feature_names=["Caratteristica A", "Caratteristica B"], class_names=["Classe 0", "Classe 1"], filled=True, rounded=True)plt.title("Struttura dell'Albero di Decisione Addestrato (max_depth=3)")plt.show()Parte 3: Coding Challenges
Mettiti alla prova modificando ed estendendo il codice Python scritto finora sul tuo notebook.
Challenge 1: L’impatto della profondità dell’albero sull’Overfitting
- Task: Utilizza il dataset sintetico e la suddivisione generati nel laboratorio pratico. Addestra un
DecisionTreeClassifierimpostandomax_depth=None(ovvero lasciandolo crescere senza alcun vincolo). Calcola l’accuratezza del modello sia sui dati di addestramento (X_train) che sui dati di test (X_test). - Domanda: Quali valori di accuratezza ottieni sui due set? Come si spiega la differenza di performance rispetto all’albero regolarizzato con
max_depth=3?
Visualizza la risoluzione e il codice
Risoluzione e Codice:
# Inizializziamo un albero senza limiti di profondità albero_overfit = DecisionTreeClassifier(max_depth=None, random_state=42) albero_overfit.fit(X_train, y_train)Calcoliamo le previsioni su train e test
y_pred_train = albero_overfit.predict(X_train) y_pred_test = albero_overfit.predict(X_test)
print(f”Accuratezza sul Training Set: {accuracy_score(y_train, y_pred_train) * 100:.1f}%”) print(f”Accuratezza sul Test Set: {accuracy_score(y_test, y_pred_test) * 100:.1f}%”)
Risultati attesi a schermo:
Accuratezza sul Training Set: 100.0%
Accuratezza sul Test Set: 88.0% (il valore esatto può oscillare in base al dataset generato)
Spiegazione concettuale:
Con max_depth=None, l’albero raggiunge un’accuratezza del 100.0% sul Training Set. Questo accade perché l’algoritmo ha continuato a creare nodi finché ogni singola foglia non conteneva campioni purissimi della stessa classe, adattandosi perfettamente (o meglio, “memorizzando”) tutti i dati usati per l’apprendimento.
Tuttavia, sul Test Set l’accuratezza scende (ad esempio all’ , contro il dell’albero regolarizzato con max_depth=3). Questo divario netto tra train e test dimostra che l’albero non vincolato soffre di Overfitting: ha imparato il rumore e le eccezioni specifiche del train set a scapito della sua capacità di generalizzazione su campioni mai visti prima.