# 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
🚀 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 →