CLASSE QUINTA • ALBERI DECISIONALI

Alberi Decisionali e Foreste Casuali

Dalle semplici domande ai modelli più potenti del Machine Learning

Immagina di fare una serie di domande per prendere una decisione. Gli alberi decisionali fanno esattamente questo, ma in modo automatico e matematico. E quando metti insieme tanti alberi... ottieni una foresta!

🌳 Cosa sono gli Alberi Decisionali?

🤔 Un'analogia semplice

Pensa a quando devi decidere se uscire con l'ombrello:

Domanda 1: C'è il sole?
→ SÌ: Non prendo l'ombrello
→ NO: Domanda 2: Le previsioni dicono pioggia?
   → SÌ: Prendo l'ombrello
   → NO: Domanda 3: Le nuvole sono scure?
      → SÌ: Prendo l'ombrello
      → NO: Non prendo l'ombrello

Un albero decisionale fa esattamente questo, ma con i dati!

🏠
Prezzo Casa
> €300.000?
📏
Metratura
> 120 m²?
💰
Alto
€400k+
💸
Medio
€250k
🏢
Basso
< €200k

✅ Vantaggi

  • Facili da capire: Puoi vedere le regole
  • Non bisogno di scaling: Lavora bene con dati grezzi
  • Gestisce feature miste: Numeriche e categoriche insieme
  • Feature importance: Dice cosa è importante
  • Robusti agli outlier: Non si fanno ingannare

❌ Svantaggi

  • Tendono a overfitting: Memorizzano i dati
  • Instabili: Piccoli cambiamenti → albero diverso
  • Non ottimali: Non trovano sempre la soluzione migliore
  • Problemi con relazioni lineari: Meglio la regressione
  • Profondità: Alberi profondi sono difficili da interpretare

🎯 Quando usare gli Alberi Decisionali?

✅ PERFETTO PER:

• Problemi di classificazione (si/no, categorie)

• Dataset piccoli/medi (fino a ~10.000 campioni)

• Interpretabilità importante (spiegare le decisioni)

• Feature di vario tipo (numeriche, categoriche, date)

❌ MEGLIO ALTRO PER:

• Dataset enormi (milioni di campioni)

• Relazioni lineari molto forti

• Previsioni con alta precisione necessaria

🔍 Come funzionano internamente gli Alberi Decisionali?

Vediamo passo-passo come un albero impara dai dati.

🚫 COPIA BLOCCATA
# ALBERI DECISIONALI - SPIEGAZIONE PASSO PASSO import numpy as np import pandas as pd from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor, plot_tree from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, mean_squared_error import matplotlib.pyplot as plt print("🌳 ALBERI DECISIONALI - TUTTO SPIEGATO PASSO PASSO") print("=" * 80) # ------------------------------------------------------------ # PASSO 1: CREIAMO UN DATASET SEMPLICE PER SPIEGARE # ------------------------------------------------------------ print("\n📊 PASSO 1: DATASET SEMPLICE PER CAPIRE") print("-" * 50) print("Creiamo dati su case per capire come funziona:") # Features semplici: metratura e numero camere X_simple = np.array([ [80, 2], # Casa piccola, 2 camere [120, 3], # Casa media, 3 camere [150, 4], # Casa grande, 4 camere [90, 2], # Casa medio-piccola, 2 camere [130, 3], # Casa media-grande, 3 camere [180, 5], # Casa molto grande, 5 camere [70, 1], # Casa piccolissima, 1 camera [110, 3], # Casa media, 3 camere [140, 4], # Casa grande, 4 camere [100, 2] # Casa media, 2 camere ]) # Target: costo (1 = costosa, 0 = economica) # Logica: se metratura > 100 OPPURE camere >= 4 → costosa y_simple = np.array([0, 1, 1, 0, 1, 1, 0, 1, 1, 0]) print("\n🏠 DATASET CASE (per allenare l'albero):") df_simple = pd.DataFrame(X_simple, columns=['Metratura (m²)', 'Camere']) df_simple['Costosa'] = y_simple print(df_simple) print("\n🎯 LOGICA UMANA:") print("Una casa è costosa se:") print("1. Metratura > 100 m² OPPURE") print("2. Numero camere >= 4") # ------------------------------------------------------------ # PASSO 2: COSTRUIAMO L'ALBERO A MANO (PER CAPIRE) # ------------------------------------------------------------ print("\n\n🔍 PASSO 2: COME L'ALBERO SCEGLIE LE DOMANDE") print("-" * 50) print("L'albero cerca la DOMANDA MIGLIORE per dividere i dati.") print("La 'bontà' di una domanda si misura con:") print("• GINI impurity: Quanto sono 'impuri' i gruppi dopo la divisione") print("• Entropia: Quanto disordine c'è nei gruppi") print("• Information gain: Quanta informazione guadagniamo dividendo") print("\n📊 CALCOLIAMO PER LA NOSTRA ROOT (tutte le case):") # Calcoliamo l'impurità iniziale (tutte le case insieme) n_total = len(y_simple) n_costose = sum(y_simple) n_economiche = n_total - n_costose # Impurità GINI iniziale p_costose = n_costose / n_total p_economiche = n_economiche / n_total gini_iniziale = 1 - (p_costose**2 + p_economiche**2) print(f"Case totali: {n_total}") print(f"Case costose: {n_costose} ({p_costose*100:.0f}%)") print(f"Case economiche: {n_economiche} ({p_economiche*100:.0f}%)") print(f"GINI impurity iniziale: {gini_iniziale:.3f}") print("\n🎯 PROVIAMO DIVERSE DOMANDE:") print("1. 'Metratura > 100?'") print("2. 'Camere >= 3?'") print("3. 'Camere >= 4?'") # Calcoliamo per la domanda "Metratura > 100?" metratura_soglia = 100 sinistra = y_simple[X_simple[:, 0] <= metratura_soglia] # Metratura <= 100 destra = y_simple[X_simple[:, 0] > metratura_soglia] # Metratura > 100 # GINI per sinistra n_sx = len(sinistra) if n_sx > 0: p1_sx = sum(sinistra == 1) / n_sx p0_sx = sum(sinistra == 0) / n_sx gini_sx = 1 - (p1_sx**2 + p0_sx**2) else: gini_sx = 0 # GINI per destra n_dx = len(destra) if n_dx > 0: p1_dx = sum(destra == 1) / n_dx p0_dx = sum(destra == 0) / n_dx gini_dx = 1 - (p1_dx**2 + p0_dx**2) else: gini_dx = 0 # GINI dopo la divisione gini_dopo = (n_sx/n_total)*gini_sx + (n_dx/n_total)*gini_dx information_gain = gini_iniziale - gini_dopo print(f"\n📈 RISULTATO DOMANDA 'Metratura > 100?':") print(f"• Sinistra (<=100): {len(sinistra)} case, GINI: {gini_sx:.3f}") print(f"• Destra (>100): {len(destra)} case, GINI: {gini_dx:.3f}") print(f"• GINI dopo divisione: {gini_dopo:.3f}") print(f"• Information Gain: {information_gain:.3f}") print("\n💡 INTERPRETAZIONE:") print(f"Information Gain di {information_gain:.3f} significa che") print("questa domanda riduce l'impurità del {:.1f}%".format(information_gain/gini_iniziale*100)) # ------------------------------------------------------------ # PASSO 3: COSTRUIAMO L'ALBERO CON SCKIT-LEARN # ------------------------------------------------------------ print("\n\n🤖 PASSO 3: ALBERO DECISIONALE AUTOMATICO") print("-" * 50) print("Ora facciamo fare tutto a scikit-learn:") # Creiamo e alleniamo l'albero tree_clf = DecisionTreeClassifier( max_depth=2, # Massimo 2 livelli (per semplicità) random_state=42 # Per risultati riproducibili ) tree_clf.fit(X_simple, y_simple) print("✅ Albero decisionale addestrato!") print(f"\n🎯 PROFONDITÀ ALBERO: {tree_clf.get_depth()}") print(f"🎯 FOGLIE (decisioni finali): {tree_clf.get_n_leaves()}") # Predizioni y_pred = tree_clf.predict(X_simple) accuracy = accuracy_score(y_simple, y_pred) print(f"\n📊 PERFORMANCE:") print(f"• Accuracy: {accuracy:.3f} ({accuracy*100:.1f}% corrette)") print(f"• Case classificate correttamente: {sum(y_simple == y_pred)}/{len(y_simple)}") # ------------------------------------------------------------ # PASSO 4: VISUALIZZIAMO L'ALBERO # ------------------------------------------------------------ print("\n\n🌲 PASSO 4: VISUALIZZAZIONE DELL'ALBERO") print("-" * 50) print("Vediamo l'albero che è stato creato:") print("\n📊 SU GOOGLE COLAB, SCOMMENTA PER VEDERE L'ALBERO:") """ plt.figure(figsize=(12, 8)) plot_tree(tree_clf, feature_names=['Metratura', 'Camere'], class_names=['Economica', 'Costosa'], filled=True, # Coloriamo i nodi rounded=True, # Nodi arrotondati fontsize=10) plt.title("Albero Decisionale - Classificazione Case") plt.show() """ print("\n🎯 INTERPRETAZIONE DELL'ALBERO:") print("Leggiamo l'albero dall'alto verso il basso:") print("1. ROOT: Prima domanda (più importante)") print("2. BRANCHES: Risposte possibili (True/False)") print("3. LEAVES: Decisioni finali (classi)") print("4. GINI: Impurità nel nodo (0 = puro, 0.5 = massima impurità)") print("5. SAMPLES: Numero di campioni nel nodo") print("6. VALUE: Distribuzione classi [economiche, costose]") print("7. CLASS: Decisione finale") # Estraiamo le regole print("\n📝 REGOLE TROVATE DALL'ALBERO:") print("(Possiamo leggerle dall'albero visualizzato)") # ------------------------------------------------------------ # PASSO 5: ANALIZZIAMO I PARAMETRI DELL'ALBERO # ------------------------------------------------------------ print("\n\n🔧 PASSO 5: PARAMETRI IMPORTANTI DEGLI ALBERI") print("-" * 50) print("I parametri controllano come cresce l'albero:") parameters_info = { 'max_depth': "Massima profondità (livelli). Più è alto, più complesso l'albero.", 'min_samples_split': "Minimo campioni per dividere un nodo. Più è alto, più semplice l'albero.", 'min_samples_leaf': "Minimo campioni in una foglia. Previene foglie con pochi esempi.", 'max_features': "Massimo features considerate per dividere. Previene overfitting.", 'criterion': "Come misurare la bontà di una divisione (gini o entropy).", 'random_state': "Per risultati riproducibili." } print("\n🎯 PARAMETRI PRINCIPALI:") for param, desc in parameters_info.items(): print(f"\n• {param}:") print(f" {desc}") if hasattr(tree_clf, param): value = getattr(tree_clf, param) print(f" Valore usato: {value}") # ------------------------------------------------------------ # PASSO 6: ALBERO PER REGRESSIONE (PREVISIONE PREZZI) # ------------------------------------------------------------ print("\n\n📈 PASSO 6: ALBERO DECISIONALE PER REGRESSIONE") print("-" * 50) print("Gli alberi possono fare anche regressione (previsione di numeri):") # Creiamo prezzi reali invece di classi prezzi = np.array([ 200000, # 80m², 2 camere 350000, # 120m², 3 camere 500000, # 150m², 4 camere 250000, # 90m², 2 camere 380000, # 130m², 3 camere 650000, # 180m², 5 camere 150000, # 70m², 1 camera 320000, # 110m², 3 camere 450000, # 140m², 4 camere 280000 # 100m², 2 camere ]) print("\n🏠 DATASET CON PREZZI REALI:") df_prezzi = pd.DataFrame(X_simple, columns=['Metratura', 'Camere']) df_prezzi['Prezzo'] = prezzi print(df_prezzi) # Albero di regressione tree_reg = DecisionTreeRegressor( max_depth=2, random_state=42 ) tree_reg.fit(X_simple, prezzi) print("\n✅ Albero di regressione addestrato!") # Predizioni prezzi_predetti = tree_reg.predict(X_simple) mse = mean_squared_error(prezzi, prezzi_predetti) rmse = np.sqrt(mse) print(f"\n📊 PERFORMANCE REGRESSIONE:") print(f"• Mean Squared Error: {mse:,.0f}") print(f"• Root MSE: €{rmse:,.0f}") print(f"• Errore percentuale medio: {(rmse/np.mean(prezzi)*100):.1f}%") print("\n💰 CONFRONTO PREZZI REALI vs PREDETTI:") for i in range(len(prezzi)): print(f"Casa {i+1}: Reale €{prezzi[i]:,.0f}, Predetto €{prezzi_predetti[i]:,.0f}, " f"Differenza €{abs(prezzi[i] - prezzi_predetti[i]):,.0f}") # ------------------------------------------------------------ # PASSO 7: FEATURE IMPORTANCE (COSA CONTA DAVVERO) # ------------------------------------------------------------ print("\n\n🎯 PASSO 7: FEATURE IMPORTANCE") print("-" * 50) print("L'albero ci dice quali features sono più importanti:") importance = tree_clf.feature_importances_ feature_names = ['Metratura', 'Camere'] print("\n📊 IMPORTANZA RELATIVA DELLE FEATURES:") for name, imp in zip(feature_names, importance): print(f"• {name}: {imp:.3f} ({imp*100:.1f}%)") print(f"\n💡 INTERPRETAZIONE:") print(f"La feature più importante è '{feature_names[np.argmax(importance)]}'") print("con il {:.1f}% dell'importanza totale.".format(max(importance)*100)) print("\n🎯 COME SI CALCOLA L'IMPORTANZA:") print("1. Per ogni feature, si somma il 'Gain' (riduzione impurità)") print("2. Si normalizza per somma totale = 1") print("3. Più alto il valore, più la feature è importante") #⚠️ PASSO 8: Overfitting e Come Evitarlo '''Gli alberi decisionali tendono a "memorizzare" i dati invece di imparare regole generali. COS'È L'OVERFITTING? Overfitting = L'albero impara troppo bene i dati di training • Perfetto sui dati di allenamento • Scadente su dati nuovi (non visti prima) • Come un bambino che memorizza le risposte invece di capire il problema'''
🚫 COPIA BLOCCATA
# OVERFITTING DEMONSTRATION print("\n🎯 DIMOSTRAZIONE OVERFITTING:") print("=" * 60) # Creiamo dataset più complesso per vedere overfitting np.random.seed(42) X_complex = np.random.randn(100, 2) # 100 punti, 2 features y_complex = (X_complex[:, 0] ** 2 + X_complex[:, 1] ** 2 > 1).astype(int) # Dividiamo in training e test X_train, X_test, y_train, y_test = train_test_split( X_complex, y_complex, test_size=0.3, random_state=42 ) print(f"📊 Dataset: {len(X_complex)} punti totali") print(f"• Training: {len(X_train)} punti") print(f"• Test: {len(X_test)} punti") # Albero SENZA limiti (overfitting) tree_overfit = DecisionTreeClassifier(random_state=42) tree_overfit.fit(X_train, y_train) # Albero CON limiti (meno overfitting) tree_regularized = DecisionTreeClassifier( max_depth=4, min_samples_split=10, min_samples_leaf=5, random_state=42 ) tree_regularized.fit(X_train, y_train) # Valutiamo train_score_overfit = tree_overfit.score(X_train, y_train) test_score_overfit = tree_overfit.score(X_test, y_test) train_score_reg = tree_regularized.score(X_train, y_train) test_score_reg = tree_regularized.score(X_test, y_test) print(f"\n📈 ALBERO OVERFITTING (senza limiti):") print(f"• Training accuracy: {train_score_overfit:.3f} ({train_score_overfit*100:.1f}%)") print(f"• Test accuracy: {test_score_overfit:.3f} ({test_score_overfit*100:.1f}%)") print(f"• Differenza: {abs(train_score_overfit - test_score_overfit):.3f} ← GRANDE GAP!") print(f"\n📊 ALBERO REGOLARIZZATO (con limiti):") print(f"• Training accuracy: {train_score_reg:.3f} ({train_score_reg*100:.1f}%)") print(f"• Test accuracy: {test_score_reg:.3f} ({test_score_reg*100:.1f}%)") print(f"• Differenza: {abs(train_score_reg - test_score_reg):.3f} ← PICCOLO GAP!") print(f"\n💡 CONCLUSIONE:") print("L'albero senza limiti memorizza i dati (overfitting)") print("L'albero con limiti generalizza meglio (migliore su dati nuovi)")

🛡️ COME EVITARE L'OVERFITTING:

1. Limitare la profondità (max_depth): Alberi più corti generalizzano meglio

2. Minimo campioni per dividere (min_samples_split): Almeno 10-20 campioni

3. Minimo campioni per foglia (min_samples_leaf): Almeno 5-10 campioni

4. Validazione incrociata: Testa su più "fold" diversi

5. Pruning (potatura): Rimuovi rami non utili dopo la crescita

max_depth
Profondità massima
Tipico: 3-10
min_samples_split
Min per dividere
Tipico: 10-20
min_samples_leaf
Min per foglia
Tipico: 5-10

🌲🌲🌲 Foreste Casuali (Random Forests)

Se un albero è bravo, una foresta di alberi è ancora meglio!

🤔 Analogia: Il Comitato di Esperti

Immagina di dover prendere una decisione importante:

Un solo esperto (albero singolo): Può sbagliare, ha i suoi pregiudizi

Un comitato di 100 esperti (foresta): Ognuno vota, si prende la decisione più votata

Risultato: Più robusto, meno errori, più stabile!

🎯 COME FUNZIONANO LE FORESTE CASUALI:

1. Bagging (Bootstrap Aggregating):

• Crea 100 versioni diverse del dataset (campioni casuali con ripetizione)

• Ogni albero si allena su un dataset leggermente diverso

2. Random Feature Selection:

• Ogni albero usa solo un sottoinsieme casuale delle features

• Obbliga gli alberi a essere diversi tra loro

3. Voting/Averaging:

Classificazione: Maggioranza dei voti

Regressione: Media delle predizioni

🚫 COPIA BLOCCATA
# FORESTE CASUALI - IMPLEMENTAZIONE print("\n🌲🌲🌲 FORESTE CASUALI") print("=" * 60) from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor # 1. FORESTA CASUALE per CLASSIFICAZIONE print("\n1️⃣ FORESTA CASUALE - CLASSIFICAZIONE") print("-" * 40) # Dataset Iris (classico per test) from sklearn.datasets import load_iris iris = load_iris() X_iris, y_iris = iris.data, iris.target # Dividiamo X_train_iris, X_test_iris, y_train_iris, y_test_iris = train_test_split( X_iris, y_iris, test_size=0.3, random_state=42 ) print(f"📊 Dataset Iris:") print(f"• Features: {iris.feature_names}") print(f"• Classi: {iris.target_names}") print(f"• Campioni totali: {len(X_iris)}") print(f"• Training: {len(X_train_iris)}, Test: {len(X_test_iris)}") # Albero singolo vs Foresta single_tree = DecisionTreeClassifier(max_depth=4, random_state=42) random_forest = RandomForestClassifier( n_estimators=100, # 100 alberi max_depth=4, # profondità massima per albero random_state=42, n_jobs=-1 # usa tutti i processori ) # Allenamento single_tree.fit(X_train_iris, y_train_iris) random_forest.fit(X_train_iris, y_train_iris) # Valutazione tree_train_score = single_tree.score(X_train_iris, y_train_iris) tree_test_score = single_tree.score(X_test_iris, y_test_iris) forest_train_score = random_forest.score(X_train_iris, y_train_iris) forest_test_score = random_forest.score(X_test_iris, y_test_iris) print(f"\n📊 PERFORMANCE COMPARATIVE:") print(f"\n🌳 ALBERO SINGOLO:") print(f"• Training: {tree_train_score:.3f} ({tree_train_score*100:.1f}%)") print(f"• Test: {tree_test_score:.3f} ({tree_test_score*100:.1f}%)") print(f"• Differenza: {abs(tree_train_score - tree_test_score):.3f}") print(f"\n🌲🌲🌲 FORESTA CASUALE (100 alberi):") print(f"• Training: {forest_train_score:.3f} ({forest_train_score*100:.1f}%)") print(f"• Test: {forest_test_score:.3f} ({forest_test_score*100:.1f}%)") print(f"• Differenza: {abs(forest_train_score - forest_test_score):.3f}") print(f"\n💡 MIGLIORAMENTO FORESTA:") improvement = forest_test_score - tree_test_score print(f"• +{improvement:.3f} accuracy su test set") print(f"• +{improvement*100:.1f}% di accuratezza in più!") # 2. FORESTA CASUALE per REGRESSIONE print("\n\n2️⃣ FORESTA CASUALE - REGRESSIONE") print("-" * 40) # Dataset Boston (prezzi case) from sklearn.datasets import fetch_california_housing housing = fetch_california_housing() X_housing, y_housing = housing.data[:300], housing.target[:300] # Solo 300 per velocità print(f"\n🏠 Dataset Prezzi Case (California):") print(f"• Features: {housing.feature_names}") print(f"• Campioni: {len(X_housing)}") print(f"• Target: Prezzo medio casa (in $100.000)") # Regressione X_train_h, X_test_h, y_train_h, y_test_h = train_test_split( X_housing, y_housing, test_size=0.3, random_state=42 ) # Modelli tree_reg_single = DecisionTreeRegressor(max_depth=5, random_state=42) forest_reg = RandomForestRegressor( n_estimators=100, max_depth=5, random_state=42, n_jobs=-1 ) tree_reg_single.fit(X_train_h, y_train_h) forest_reg.fit(X_train_h, y_train_h) # Predizioni y_pred_tree = tree_reg_single.predict(X_test_h) y_pred_forest = forest_reg.predict(X_test_h) # Metriche from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score print(f"\n📊 METRICHE REGRESSIONE:") print(f"\n🌳 ALBERO SINGOLO:") print(f"• MSE: {mean_squared_error(y_test_h, y_pred_tree):.4f}") print(f"• MAE: {mean_absolute_error(y_test_h, y_pred_tree):.4f}") print(f"• R²: {r2_score(y_test_h, y_pred_tree):.4f}") print(f"\n🌲🌲🌲 FORESTA CASUALE:") print(f"• MSE: {mean_squared_error(y_test_h, y_pred_forest):.4f}") print(f"• MAE: {mean_absolute_error(y_test_h, y_pred_forest):.4f}") print(f"• R²: {r2_score(y_test_h, y_pred_forest):.4f}") print(f"\n💡 CONCLUSIONI:") print("La foresta riduce l'errore del {:.1f}% rispetto all'albero singolo".format( (mean_squared_error(y_test_h, y_pred_tree) - mean_squared_error(y_test_h, y_pred_forest)) / mean_squared_error(y_test_h, y_pred_tree) * 100 ))

✅ VANTAGGI FORESTE CASUALI

  • Maggiore accuratezza: Più robuste degli alberi singoli
  • Meno overfitting: La diversità degli alberi aiuta
  • Feature importance: Più stabile e affidabile
  • Gestione outlier: Più robuste ai dati strani
  • Parallelizzabili: Gli alberi si allenano in parallelo

❌ SVANTAGGI FORESTE CASUALI

  • Lentezza: 100 alberi sono più lenti di 1
  • Memoria: Occupano più spazio
  • Poco interpretabili: Non puoi "leggere" una foresta
  • Black box: Difficile spiegare le decisioni
  • Iperparametri: Più complessi da ottimizzare

🔧 Parametri Chiave delle Foreste Casuali

n_estimators
Numero alberi
Tipico: 100-500
Più alberi = più accuratezza (fino a un certo punto)
max_depth
Profondità alberi
Tipico: Nessuna limitazione
Lasciare crescere completamente
max_features
Features per split
Tipico: sqrt(n_features)
Aumenta la diversità
bootstrap
Campionamento
True di default
Campioni casuali con ripetizione

🎯 CONSIGLI PRATICI:

Per iniziare: n_estimators=100, max_depth=None

Per velocità: n_estimators=50, n_jobs=-1 (tutti i core)

Per massima accuratezza: n_estimators=500, max_depth=None

Per interpretabilità: Usa un albero singolo, non la foresta

💪 Esercizi Pratici

Esercizio 1: Albero Decisionale Base FACILE

Obiettivo: Classificare fiori Iris con un albero decisionale

Steps:

  1. Carica il dataset Iris di scikit-learn
  2. Dividi in training (70%) e test (30%)
  3. Crea un albero con max_depth=3
  4. Allenalo e valuta l'accuracy
  5. Visualizza l'albero con plot_tree

Esercizio 2: Confronto Albero vs Foresta MEDIO

Obiettivo: Confrontare performance di albero singolo e foresta

Dataset: Breast Cancer (tumori al seno)

Tasks:

  1. Carica il dataset breast_cancer
  2. Allena un albero singolo (max_depth=5)
  3. Allena una foresta (n_estimators=100)
  4. Confronta accuracy, precision, recall
  5. Analizza feature importance di entrambi

Esercizio 3: Regressione con Foreste DIFFICILE

Obiettivo: Prevedere prezzi di case con regressione

Dataset: California Housing

Tasks:

  1. Carica il dataset California Housing
  2. Preprocessa i dati (scaling opzionale)
  3. Allena una Random Forest Regressor
  4. Ottimizza n_estimators e max_depth
  5. Calcola MSE, MAE, R²
  6. Visualizza predizioni vs valori reali
🚫 COPIA BLOCCATA
# SOLUZIONE ESERCIZIO 1 - BASE from sklearn.datasets import load_iris from sklearn.tree import DecisionTreeClassifier, plot_tree from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, classification_report import matplotlib.pyplot as plt # 1. Carica dataset iris = load_iris() X, y = iris.data, iris.target # 2. Dividi X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42 ) # 3. Crea e allena albero tree = DecisionTreeClassifier(max_depth=3, random_state=42) tree.fit(X_train, y_train) # 4. Predici e valuta y_pred = tree.predict(X_test) accuracy = accuracy_score(y_test, y_pred) print(f"🎯 ESERCIZIO 1 - ALBERO IRIS") print(f"Accuracy: {accuracy:.3f} ({accuracy*100:.1f}%)") print(f"\nReport di classificazione:") print(classification_report(y_test, y_pred, target_names=iris.target_names)) # 5. Visualizza (scommenta su Colab) """ plt.figure(figsize=(15, 10)) plot_tree(tree, feature_names=iris.feature_names, class_names=iris.target_names, filled=True, rounded=True) plt.title("Albero Decisionale - Iris Dataset") plt.show() """ print("\n✅ ESERCIZIO COMPLETATO!") print("Prova a cambiare max_depth e vedere come cambia l'albero.")

🚀 Progetto: Sistema di Rilevamento Frodi

Costruiamo un sistema per rilevare transazioni fraudolente usando le foreste casuali.

📊 Scenario Reale:

Una banca vuole rilevare transazioni fraudolente in tempo reale.

Dati disponibili:

  • Importo della transazione
  • Ora del giorno
  • Posizione geografica
  • Tipo di commerciante
  • Storico del cliente

Obiettivo: Classificare ogni transazione come "legittima" o "sospetta"

🚫 COPIA BLOCCATA
# PROGETTO: RILEVAMENTO FRODI CON FORESTE CASUALI import pandas as pd import numpy as np from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score from sklearn.preprocessing import StandardScaler import seaborn as sns import matplotlib.pyplot as plt print("🚀 PROGETTO: SISTEMA DI RILEVAMENTO FRODI") print("=" * 80) # 1. CREIAMO DATASET SIMULATO (per mancanza di dati reali) print("\n📊 1. CREAZIONE DATASET SIMULATO") print("-" * 40) np.random.seed(42) n_samples = 10000 # Features simulate data = { 'importo': np.random.exponential(100, n_samples), # Importo transazione 'ora': np.random.randint(0, 24, n_samples), # Ora del giorno 'distanza_casa': np.random.uniform(0, 100, n_samples), # Distanza da casa 'tipo_commerciante': np.random.choice([0, 1, 2, 3], n_samples), # Categoria 'importo_medio_cliente': np.random.normal(50, 20, n_samples), # Storico cliente 'n_transazioni_giorno': np.random.poisson(3, n_samples) # Frequenza } # Creiamo target (fraudolento = 1) # Logica: più alto importo + notte + lontano da casa = più sospetto df = pd.DataFrame(data) fraud_prob = ( (df['importo'] > 200) * 0.4 + (df['ora'] > 22) * 0.3 + (df['distanza_casa'] > 50) * 0.3 + np.random.normal(0, 0.1, n_samples) ) df['fraudolento'] = (fraud_prob > 0.5).astype(int) print(f"Dataset creato: {len(df)} transazioni") print(f"• Transazioni legittime: {sum(df['fraudolento'] == 0)}") print(f"• Transazioni fraudolente: {sum(df['fraudolento'] == 1)}") print(f"• Percentuale fraudolente: {sum(df['fraudolento'] == 1)/len(df)*100:.2f}%") # 2. PREPROCESSING print("\n🔧 2. PREPROCESSING DEI DATI") print("-" * 40) # Separazione features e target X = df.drop('fraudolento', axis=1) y = df['fraudolento'] # Split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) # Scaling (opzionale per Random Forest, ma utile per visualizzazione) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) print(f"• Training set: {len(X_train)} samples") print(f"• Test set: {len(X_test)} samples") print(f"• Dimensionalità: {X_train.shape[1]} features") # 3. MODELLO RANDOM FOREST print("\n🌲 3. ADDESTRAMENTO RANDOM FOREST") print("-" * 40) # Creazione modello rf_model = RandomForestClassifier( n_estimators=200, max_depth=10, min_samples_split=20, min_samples_leaf=10, random_state=42, class_weight='balanced', # Importante per dataset sbilanciati n_jobs=-1 ) print("Allenamento modello in corso...") rf_model.fit(X_train_scaled, y_train) print("✅ Modello addestrato!") # 4. VALUTAZIONE print("\n📊 4. VALUTAZIONE DEL MODELLO") print("-" * 40) # Predizioni y_pred = rf_model.predict(X_test_scaled) y_pred_proba = rf_model.predict_proba(X_test_scaled)[:, 1] # Probabilità fraudolento print("\n🎯 CLASSIFICATION REPORT:") print(classification_report(y_test, y_pred, target_names=['Legittima', 'Fraudolenta'])) print("\n📈 MATRICE DI CONFUSIONE:") cm = confusion_matrix(y_test, y_pred) print(cm) # Metriche aggiuntive accuracy = (cm[0,0] + cm[1,1]) / cm.sum() precision = cm[1,1] / (cm[1,1] + cm[0,1]) recall = cm[1,1] / (cm[1,1] + cm[1,0]) f1 = 2 * (precision * recall) / (precision + recall) roc_auc = roc_auc_score(y_test, y_pred_proba) print(f"\n📊 METRICHE DETTAGLIATE:") print(f"• Accuracy: {accuracy:.4f}") print(f"• Precision (fraud): {precision:.4f}") print(f"• Recall (fraud): {recall:.4f}") print(f"• F1-Score: {f1:.4f}") print(f"• ROC-AUC: {roc_auc:.4f}") # 5. FEATURE IMPORTANCE print("\n🎯 5. FEATURE IMPORTANCE") print("-" * 40) importance = pd.DataFrame({ 'feature': X.columns, 'importance': rf_model.feature_importances_ }).sort_values('importance', ascending=False) print("\n📊 IMPORTANZA FEATURES (top 10):") for i, row in importance.head(10).iterrows(): print(f"{row['feature']:30s}: {row['importance']:.4f} ({row['importance']*100:.1f}%)") # 6. OTTIMIZZAZIONE IPERPARAMETRI (OPZIONALE) print("\n⚙️ 6. OTTIMIZZAZIONE IPERPARAMETRI (Grid Search)") print("-" * 40) print("Ricerca dei migliori parametri...") # Parametri da testare param_grid = { 'n_estimators': [100, 200, 300], 'max_depth': [5, 10, 15], 'min_samples_split': [10, 20, 30] } # Grid Search (scommenta per usare - richiede tempo) """ grid_search = GridSearchCV( RandomForestClassifier(random_state=42, class_weight='balanced'), param_grid, cv=5, scoring='f1', n_jobs=-1 ) grid_search.fit(X_train_scaled, y_train) print(f"Migliori parametri: {grid_search.best_params_}") print(f"Miglior F1-Score: {grid_search.best_score_:.4f}") """ print("\n💡 CONSIGLI PER IL DEPLOYMENT:") print("1. Monitora le performance nel tempo") print("2. Aggiorna il modello con nuovi dati periodicamente") print("3. Implementa un sistema di allerta per transazioni sospette") print("4. Considera il bilanciamento tra falsi positivi e falsi negativi") print("5. Testa su dati reali prima del deployment completo") print("\n✅ PROGETTO COMPLETATO!") print("Hai costruito un sistema di rilevamento frodi funzionante!")

📈 METRICHE IMPORTANTI PER IL PROGETTO:

Recall (Sensibilità): Quante frodi reali catturiamo? (importante!)

Precision: Quante delle nostre segnalazioni sono vere frodi?

F1-Score: Media armonica tra precision e recall

ROC-AUC: Capacità di distinguere frodi da non frodi

Matrice di Confusione: Visualizza errori del modello

🎯 Riepilogo e Prossimi Passi

🌳 ALBERI DECISIONALI

Punti Chiave:

  • Facili da interpretare e visualizzare
  • Non richiedono scaling dei dati
  • Tendono all'overfitting
  • Utili per dataset piccoli/medi
  • Ottimi per spiegare decisioni

🌲🌲🌲 FORESTE CASUALI

Punti Chiave:

  • Più accurate degli alberi singoli
  • Meno sensibili all'overfitting
  • Più robuste e stabili
  • Più lente e complesse
  • Black box - difficile da interpretare

🎯 QUANDO USARE COSA?

Usa un ALBERO SINGOLO quando:

• Devi spiegare le decisioni a non tecnici

• Il dataset è piccolo e semplice

• L'interpretabilità è più importante dell'accuratezza

Usa una FORESTA CASUALE quando:

• Ti serve la massima accuratezza

• Il dataset è complesso o rumoroso

• Puoi sacrificare l'interpretabilità

• Hai abbastanza potenza computazionale

🎓 Verifica di Apprendimento

Domanda 1: Cos'è l'impurità GINI?

A) Una misura della qualità del vino italiano

B) Una misura di quanto un nodo è "puro" (0 = tutte stesso tipo, 0.5 = massima varietà)

C) Il nome del creatore degli alberi decisionali

Risposta corretta: B

Domanda 2: Perché le foreste casuali sono migliori degli alberi singoli?

A) Perché sono più verdi ecologicamente

B) Perché combinano molti alberi, riducendo varianza e overfitting

C) Perché usano solo features casuali, non tutte

Risposta corretta: B

Domanda 3: Cosa significa "max_depth" in un albero decisionale?

A) La profondità massima delle radici dell'albero

B) Il numero massimo di livelli (domande) che l'albero può avere

C) La massima distanza tra due foglie dell'albero

Risposta corretta: B

🚀 Pronto per il Prossimo Livello!

Hai imparato come prendere decisioni automatiche con alberi e foreste.
Nel prossimo modulo esploreremo le Reti Neurali Artificiali - il cervello dell'AI moderna!

Vai al Modulo 3: Reti Neurali →