CLASSE QUINTA • SCKIT-LEARN AVANZATO

Approfondimento Scikit-Learn

Masterizza la libreria ML più utilizzata al mondo

Benvenuto nella Classe Quinta! Ora che conosci le basi, esploriamo Scikit-Learn a livello professionale: pipeline complesse, ottimizzazione avanzata e tecniche per dataset reali.

🏗️ Architettura di Scikit-Learn

Scikit-Learn ha un design modulare che rende il ML accessibile ma potente.

📦 Moduli Principali

  • sklearn.datasets: Dataset pre-caricati
  • sklearn.model_selection: Cross-validation, grid search
  • sklearn.preprocessing: Scalers, encoding
  • sklearn.linear_model: Regressione lineare/logistica
  • sklearn.ensemble: Random Forest, Gradient Boosting
  • sklearn.metrics: Metriche di valutazione
  • sklearn.pipeline: Pipeline automatizzate

🎯 API Consistency

Tutti i modelli in Scikit-Learn seguono la stessa API:

🚫 COPIA BLOCCATA
# STESSA API PER TUTTI I MODELLI from sklearn.ensemble import RandomForestClassifier from sklearn.svm import SVC from sklearn.linear_model import LogisticRegression # 1. Inizializzazione model1 = RandomForestClassifier() model2 = SVC() model3 = LogisticRegression() # 2. Training (sempre .fit()) model1.fit(X_train, y_train) model2.fit(X_train, y_train) model3.fit(X_train, y_train) # 3. Predizioni (sempre .predict()) predictions1 = model1.predict(X_test) predictions2 = model2.predict(X_test) predictions3 = model3.predict(X_test) # 4. Valutazione (sempre .score()) score1 = model1.score(X_test, y_test) score2 = model2.score(X_test, y_test) score3 = model3.score(X_test, y_test)
1
Dati Grezzi
2
Preprocessing
3
Feature
Selection
4
Training
Modello
5
Valutazione
6
Deployment

💡 Perché Scikit-Learn è così popolare?

1. API consistente: Una volta imparato un modello, sai usarli tutti

2. Documentazione eccellente: Esempi chiari per ogni funzione

3. Performance ottimizzate: Sotto il cofano usa NumPy e Cython

4. Comunità enorme: 10+ anni di sviluppo, usato in produzione ovunque

🔧 Pipeline Avanzate con ColumnTransformer

Le pipeline automatizzano il flusso di lavoro ML, prevenendo errori e data leakage.

🚫 COPIA BLOCCATA
# PIPELINE COMPLETA CON COLUMNTRANSFORMER import numpy as np import pandas as pd from sklearn.model_selection import train_test_split, cross_val_score from sklearn.preprocessing import StandardScaler, OneHotEncoder, MinMaxScaler from sklearn.impute import SimpleImputer from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix import warnings warnings.filterwarnings('ignore') print("🔧 PIPELINE AVANZATA SCKIT-LEARN") print("=" * 80) # ------------------------------------------------------------ # PASSO 1: CREIAMO DATASET COMPLESSO (SIMULAZIONE DATI REALI) # ------------------------------------------------------------ print("\n📊 PASSO 1: CREAZIONE DATASET COMPLESSO") print("-" * 50) np.random.seed(42) n_samples = 1000 # Features di vario tipo data = { # Numeriche continue 'età': np.random.normal(35, 10, n_samples), 'reddito': np.random.lognormal(10, 0.5, n_samples), 'spese_mensili': np.random.exponential(1000, n_samples), # Numeriche discrete 'figli': np.random.poisson(1.5, n_samples), 'anni_esperienza': np.random.randint(0, 40, n_samples), # Categoriche nominali 'città': np.random.choice(['Milano', 'Roma', 'Napoli', 'Torino', 'Bologna', 'Firenze'], n_samples), 'titolo_studio': np.random.choice(['Diploma', 'Laurea', 'Master', 'Dottorato', 'Nessuno'], n_samples), # Categoriche ordinali 'rating_cliente': np.random.choice([1, 2, 3, 4, 5], n_samples, p=[0.1, 0.2, 0.3, 0.25, 0.15]), # Booleane 'possiede_casa': np.random.choice([0, 1], n_samples, p=[0.6, 0.4]), 'ha_mutuo': np.random.choice([0, 1], n_samples, p=[0.7, 0.3]), # Con valori mancanti (simulazione dati reali) 'risparmi': np.where(np.random.random(n_samples) > 0.1, np.random.exponential(5000, n_samples), np.nan) } # Target: approvato credito (1) o rifiutato (0) # Logica complessa per simulare decisione di credito reddito_norm = (data['reddito'] - np.mean(data['reddito'])) / np.std(data['reddito']) spese_norm = (data['spese_mensili'] - np.mean(data['spese_mensili'])) / np.std(data['spese_mensili']) rating_norm = data['rating_cliente'] / 5.0 # Probabilità di approvazione basata su multiple features prob_approvazione = ( 0.4 * (reddito_norm > 0).astype(int) + 0.3 * (spese_norm < 1).astype(int) + 0.2 * rating_norm + 0.1 * data['possiede_casa'] ) # Aggiungiamo rumore prob_approvazione += np.random.normal(0, 0.2, n_samples) # Target binario data['credito_approvato'] = (prob_approvazione > 0.5).astype(int) # Creiamo DataFrame df = pd.DataFrame(data) print("✅ Dataset creato con successo!") print(f"📊 Dimensioni: {df.shape[0]} campioni × {df.shape[1]} colonne") print("\n🔍 PRIME 5 RIGHE:") print(df.head()) print("\n📈 DISTRIBUZIONE TARGET:") print(df['credito_approvato'].value_counts()) print(f"Percentuale approvati: {(df['credito_approvato'].mean()*100):.1f}%") print("\n🎯 VALORI MANCANTI:") print(df.isnull().sum()) # ------------------------------------------------------------ # PASSO 2: ANALISI TIPI DI FEATURE # ------------------------------------------------------------ print("\n\n🔍 PASSO 2: IDENTIFICAZIONE TIPI DI FEATURE") print("-" * 50) # Classifichiamo le features numeric_features = ['età', 'reddito', 'spese_mensili', 'risparmi'] discrete_features = ['figli', 'anni_esperienza', 'rating_cliente'] categorical_features = ['città', 'titolo_studio'] binary_features = ['possiede_casa', 'ha_mutuo'] target = 'credito_approvato' print("🎯 CLASSIFICAZIONE FEATURES:") print(f"• Numeriche continue: {len(numeric_features)}") print(f"• Numeriche discrete: {len(discrete_features)}") print(f"• Categoriche nominali: {len(categorical_features)}") print(f"• Binarie: {len(binary_features)}") print(f"• Target: {target}") # Separiamo features e target X = df.drop(target, axis=1) y = df[target] print(f"\n📊 X shape: {X.shape}") print(f"📊 y shape: {y.shape}") # ------------------------------------------------------------ # PASSO 3: PIPELINE DI PREPROCESSING AVANZATA # ------------------------------------------------------------ print("\n\n🔧 PASSO 3: CREAZIONE PIPELINE DI PREPROCESSING") print("-" * 50) print("Creiamo pipeline separate per ogni tipo di feature:") # 1. Pipeline per features numeriche continue numeric_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='median')), # Riempie valori mancanti con mediana ('scaler', StandardScaler()) # Standardizza (media=0, dev=1) ]) # 2. Pipeline per features numeriche discrete discrete_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='most_frequent')), # Riempie con moda ('scaler', MinMaxScaler()) # Scala tra 0 e 1 ]) # 3. Pipeline per features categoriche categorical_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='constant', fill_value='missing')), # Riempie con 'missing' ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False)) # One-hot encoding ]) # 4. Pipeline per features binarie (nessuna trasformazione necessaria) binary_transformer = 'passthrough' # Mantiene così come sono # ------------------------------------------------------------ # PASSO 4: COLUMNTRANSFORMER - APPLICA TRASFORMAZIONI DIVERS PER COLONNE DIVERS # ------------------------------------------------------------ print("\n\n🎯 PASSO 4: COLUMNTRANSFORMER PER GESTIRE TUTTE LE FEATURE") print("-" * 50) preprocessor = ColumnTransformer( transformers=[ ('num', numeric_transformer, numeric_features), ('disc', discrete_transformer, discrete_features), ('cat', categorical_transformer, categorical_features), ('binary', binary_transformer, binary_features) ], remainder='drop' # Scarta colonne non specificate ) print("✅ Preprocessor creato con successo!") print("\n🎯 COSA FA IL PREPROCESSOR:") print("1. Features numeriche: Imputazione + Standardizzazione") print("2. Features discrete: Imputazione + MinMax scaling") print("3. Features categoriche: Imputazione + One-hot encoding") print("4. Features binarie: Mantiene così come sono") # Test del preprocessor print("\n🔧 TEST DEL PREPROCESSOR (prime 5 righe trasformate):") X_transformed = preprocessor.fit_transform(X.head()) print(f"Shape dopo trasformazione: {X_transformed.shape}") print(f"Numero features dopo one-hot: {X_transformed.shape[1]}") # ------------------------------------------------------------ # PASSO 5: PIPELINE COMPLETA (PREPROCESSING + MODELLO) # ------------------------------------------------------------ print("\n\n🤖 PASSO 5: PIPELINE COMPLETA ML") print("-" * 50) # Creiamo pipeline completa pipeline = Pipeline(steps=[ ('preprocessor', preprocessor), ('classifier', RandomForestClassifier( n_estimators=100, random_state=42, n_jobs=-1 # Usa tutti i core CPU )) ]) print("✅ Pipeline completa creata!") print("\n🎯 STRUTTURA PIPELINE:") for step_name, step in pipeline.steps: print(f"• {step_name}: {step}") # ------------------------------------------------------------ # PASSO 6: TRAIN/TEST SPLIT E TRAINING # ------------------------------------------------------------ print("\n\n🎯 PASSO 6: TRAINING DELLA PIPELINE") print("-" * 50) # Split dei dati X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y # Mantiene distribuzione target ) print(f"📊 SPLIT DATI:") print(f"• Training: {X_train.shape[0]} campioni") print(f"• Test: {X_test.shape[0]} campioni") print(f"• Distribuzione target training: {y_train.value_counts().to_dict()}") print(f"• Distribuzione target test: {y_test.value_counts().to_dict()}") # Training della pipeline print("\n🚀 TRAINING DELLA PIPELINE...") pipeline.fit(X_train, y_train) print("✅ Training completato!") # ------------------------------------------------------------ # PASSO 7: VALUTAZIONE DEL MODELLO # ------------------------------------------------------------ print("\n\n📊 PASSO 7: VALUTAZIONE COMPLETA DEL MODELLO") print("-" * 50) # Predizioni y_pred = pipeline.predict(X_test) y_pred_proba = pipeline.predict_proba(X_test)[:, 1] # Metriche from sklearn.metrics import (accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, confusion_matrix) print("🎯 METRICHE DI PERFORMANCE:") print(f"• Accuracy: {accuracy_score(y_test, y_pred):.3f}") print(f"• Precision: {precision_score(y_test, y_pred):.3f}") print(f"• Recall: {recall_score(y_test, y_pred):.3f}") print(f"• F1-Score: {f1_score(y_test, y_pred):.3f}") print(f"• ROC-AUC: {roc_auc_score(y_test, y_pred_proba):.3f}") # Matrice di confusione print("\n📋 MATRICE DI CONFUSIONE:") cm = confusion_matrix(y_test, y_pred) print(f" Predetto NO Predetto SI") print(f"Reale NO (TN): {cm[0,0]:8} (FP): {cm[0,1]:8}") print(f"Reale SI (FN): {cm[1,0]:8} (TP): {cm[1,1]:8}") # Classification report print("\n📄 CLASSIFICATION REPORT:") print(classification_report(y_test, y_pred, target_names=['Rifiutato', 'Approvato'])) # ------------------------------------------------------------ # PASSO 8: CROSS-VALIDATION PER VALUTAZIONE ROBUSTA # ------------------------------------------------------------ print("\n\n🔄 PASSO 8: CROSS-VALIDATION CON PIPELINE") print("-" * 50) # Cross-validation con la pipeline completa cv_scores = cross_val_score( pipeline, X, y, cv=5, # 5-fold CV scoring='roc_auc', # Usiamo ROC-AUC n_jobs=-1 # Parallelizza ) print("🎯 ROC-AUC SCORES CON 5-FOLD CV:") for fold, score in enumerate(cv_scores, 1): print(f" Fold {fold}: {score:.3f}") print(f"\n📊 Media CV: {cv_scores.mean():.3f} (+/- {cv_scores.std()*2:.3f})") print(f"📈 ROC-AUC sul test set: {roc_auc_score(y_test, y_pred_proba):.3f}") # ------------------------------------------------------------ # PASSO 9: FEATURE IMPORTANCE (anche dopo preprocessing!) # ------------------------------------------------------------ print("\n\n🎯 PASSO 9: ANALISI FEATURE IMPORTANCE") print("-" * 50) print("Anche con pipeline complessa, possiamo estrarre feature importance:") # Estraiamo il classificatore dalla pipeline classifier = pipeline.named_steps['classifier'] # Otteniamo feature names dopo preprocessing preprocessor = pipeline.named_steps['preprocessor'] preprocessor.fit(X_train) # Costruiamo feature names feature_names = [] # Numeriche feature_names.extend(numeric_features) # Discrete feature_names.extend(discrete_features) # Categoriche (dopo one-hot) cat_encoder = preprocessor.named_transformers_['cat'].named_steps['onehot'] for i, cat in enumerate(categorical_features): categories = cat_encoder.categories_[i] feature_names.extend([f'{cat}_{c}' for c in categories]) # Binarie feature_names.extend(binary_features) print(f"📊 Numero totale features dopo preprocessing: {len(feature_names)}") # Feature importance dal Random Forest if hasattr(classifier, 'feature_importances_'): importance_df = pd.DataFrame({ 'Feature': feature_names, 'Importance': classifier.feature_importances_ }).sort_values('Importance', ascending=False) print("\n🎯 TOP 10 FEATURES PIÙ IMPORTANTI:") print(importance_df.head(10).to_string(index=False)) print(f"\n📈 IMPORTANZA CUMULATIVA TOP 5 FEATURES: " f"{(importance_df.head(5)['Importance'].sum()*100):.1f}%") # ------------------------------------------------------------ # PASSO 10: PREDIZIONI SU NUOVI DATI # ------------------------------------------------------------ print("\n\n🔮 PASSO 10: PREDIZIONI SU NUOVI DATI CON LA PIPELINE") print("-" * 50) print("Creiamo nuovi candidati per il credito:") nuovi_candidati = pd.DataFrame({ 'età': [25, 45, 32, 55, 28], 'reddito': [30000, 60000, 45000, 80000, 35000], 'spese_mensili': [1500, 3000, 2000, 4000, 1800], 'figli': [0, 2, 1, 3, 0], 'anni_esperienza': [3, 20, 8, 30, 5], 'città': ['Milano', 'Roma', 'Napoli', 'Torino', 'Bologna'], 'titolo_studio': ['Laurea', 'Master', 'Diploma', 'Dottorato', 'Laurea'], 'rating_cliente': [4, 5, 3, 5, 4], 'possiede_casa': [0, 1, 0, 1, 0], 'ha_mutuo': [0, 1, 0, 1, 0], 'risparmi': [5000, 20000, 8000, 50000, 3000] }) print("📊 NUOVI CANDIDATI:") print(nuovi_candidati) # Predizioni predizioni = pipeline.predict(nuovi_candidati) probabilità = pipeline.predict_proba(nuovi_candidati)[:, 1] print("\n💰 RISULTATI PREDIZIONI:") for i in range(len(nuovi_candidati)): decisione = "APPROVATO" if predizioni[i] == 1 else "RIFIUTATO" prob = probabilità[i] * 100 print(f"\nCandidato {i+1}:") print(f" • Decisione: {decisione}") print(f" • Probabilità approvazione: {prob:.1f}%") print(f" • Età: {nuovi_candidati.iloc[i]['età']}, Reddito: €{nuovi_candidati.iloc[i]['reddito']:,.0f}") print(f" • Città: {nuovi_candidati.iloc[i]['città']}, Titolo studio: {nuovi_candidati.iloc[i]['titolo_studio']}") # ------------------------------------------------------------ # PASSO 11: SALVATAGGIO E CARICAMENTO PIPELINE # ------------------------------------------------------------ print("\n\n💾 PASSO 11: SALVATAGGIO E CARICAMENTO DELLA PIPELINE") print("-" * 50) import joblib # Salva la pipeline completa joblib.dump(pipeline, 'pipeline_credito.pkl') print("✅ Pipeline salvata come 'pipeline_credito.pkl'") # Simulazione di caricamento in un'altra sessione print("\n🔧 SIMULAZIONE CARICAMENTO IN NUOVA SESSIONE:") loaded_pipeline = joblib.load('pipeline_credito.pkl') # Test con un nuovo candidato nuovo_candidato = pd.DataFrame([{ 'età': 30, 'reddito': 40000, 'spese_mensili': 1800, 'figli': 1, 'anni_esperienza': 7, 'città': 'Milano', 'titolo_studio': 'Laurea', 'rating_cliente': 4, 'possiede_casa': 0, 'ha_mutuo': 0, 'risparmi': 10000 }]) predizione_caricata = loaded_pipeline.predict(nuovo_candidato)[0] probabilità_caricata = loaded_pipeline.predict_proba(nuovo_candidato)[0, 1] * 100 print(f"🎯 PREDIZIONE CON PIPELINE CARICATA:") print(f"• Decisione: {'APPROVATO' if predizione_caricata == 1 else 'RIFIUTATO'}") print(f"• Probabilità: {probabilità_caricata:.1f}%") print("✅ La pipeline funziona perfettamente anche dopo il salvataggio!") # ------------------------------------------------------------ # RIEPILOGO FINALE # ------------------------------------------------------------ print("\n\n🎉 PIPELINE SCKIT-LEARN MASTERATA!") print("=" * 80) print("\n✅ COSA HAI IMPARATO OGGI:") print("1. Gestione di dataset con tipi di feature diversi") print("2. Creazione pipeline con ColumnTransformer") print("3. Trasformazioni specifiche per ogni tipo di feature") print("4. Cross-validation con pipeline completa") print("5. Feature importance anche dopo preprocessing complesso") print("6. Salvataggio e caricamento di pipeline complete") print("7. Predizioni su nuovi dati con tutto automatizzato") print("\n🎯 VANTAGGI DELLE PIPELINE:") print("• ✅ Prevenzione data leakage") print("• ✅ Riproducibilità completa") print("• ✅ Codice più pulito e mantenibile") print("• ✅ Facile deployment") print("• ✅ Cross-validation corretta") print("\n🚀 PROSSIMI PASSI:") print("1. Sperimenta con diversi preprocessori") print("2. Prova GridSearchCV con pipeline") print("3. Aggiungi feature selection nella pipeline") print("4. Crea pipeline per regressione (non solo classificazione)") print("\n📊 PERFORMANCE FINALE DEL MODELLO:") print(f"• ROC-AUC: {roc_auc_score(y_test, y_pred_proba):.3f}") print(f"• F1-Score: {f1_score(y_test, y_pred):.3f}") print(f"• Cross-validation: {cv_scores.mean():.3f} (+/- {cv_scores.std()*2:.3f})") print("\n🌟 COMPLIMENTI! HAI MASTERIZZATO LE PIPELINE DI SCKIT-LEARN!")
7+
Tipi di
Preprocessing
4
Pipeline
Specializzate
0.92
ROC-AUC
Score
.pkl
Pipeline
Salvabile

💡 Best Practices per Pipeline Professionali

1. Always use ColumnTransformer: Gestisci ogni tipo di feature separatamente

2. Impute before splitting: MAI imputare prima dello split train/test

3. Save the entire pipeline: Includi preprocessing nel modello salvato

4. Use sparse matrices for categorical: Risparmia memoria con one-hot encoding

5. Document transformation steps: Tieni traccia di ogni trasformazione

🎯 Hyperparameter Tuning Avanzato

GridSearchCV e RandomizedSearchCV per ottimizzare i modelli.

🚫 COPIA BLOCCATA
# HYPERPARAMETER TUNING AVANZATO CON GRIDSEARCHCV import numpy as np import pandas as pd from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split, GridSearchCV, RandomizedSearchCV from sklearn.preprocessing import StandardScaler from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.svm import SVC from sklearn.metrics import classification_report, roc_auc_score import warnings warnings.filterwarnings('ignore') print("🎯 HYPERPARAMETER TUNING AVANZATO") print("=" * 80) # ------------------------------------------------------------ # PASSO 1: CREAZIONE DATASET COMPLESSO # ------------------------------------------------------------ print("\n📊 PASSO 1: CREAZIONE DATASET COMPLESSO PER TUNING") print("-" * 50) # Creiamo un dataset complesso con rumore e features irrilevanti X, y = make_classification( n_samples=2000, n_features=20, n_informative=8, # Solo 8 features informative n_redundant=5, # 5 features ridondanti n_repeated=2, # 2 features ripetute n_classes=2, weights=[0.7, 0.3], # Classi sbilanciate flip_y=0.05, # 5% di rumore random_state=42 ) # Aggiungiamo nomi alle features feature_names = [f'feature_{i}' for i in range(X.shape[1])] print(f"✅ Dataset creato:") print(f"• Campioni: {X.shape[0]}") print(f"• Features: {X.shape[1]}") print(f"• Features informative: 8") print(f"• Features ridondanti: 5") print(f"• Features ripetute: 2") print(f"• Rumore: 5%") print(f"• Distribuzione classi: {np.bincount(y)}") # Split dei dati X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) print(f"\n📊 SPLIT DATI:") print(f"• Training: {X_train.shape[0]} campioni") print(f"• Test: {X_test.shape[0]} campioni") # ------------------------------------------------------------ # PASSO 2: BASELINE SENZA TUNING # ------------------------------------------------------------ print("\n\n📈 PASSO 2: BASELINE SENZA TUNING") print("-" * 50) print("Testiamo 3 modelli con parametri di default:") models = { 'Random Forest': RandomForestClassifier(random_state=42, n_jobs=-1), 'Gradient Boosting': GradientBoostingClassifier(random_state=42), 'SVM': SVC(random_state=42, probability=True) } baseline_results = {} for name, model in models.items(): print(f"\n🎯 {name}:") model.fit(X_train, y_train) y_pred = model.predict(X_test) y_pred_proba = model.predict_proba(X_test)[:, 1] if hasattr(model, 'predict_proba') else None accuracy = model.score(X_test, y_test) roc_auc = roc_auc_score(y_test, y_pred_proba) if y_pred_proba is not None else None baseline_results[name] = { 'accuracy': accuracy, 'roc_auc': roc_auc, 'model': model } print(f" • Accuracy: {accuracy:.3f}") if roc_auc: print(f" • ROC-AUC: {roc_auc:.3f}") # ------------------------------------------------------------ # PASSO 3: GRID SEARCH PER RANDOM FOREST # ------------------------------------------------------------ print("\n\n🔍 PASSO 3: GRID SEARCH PER RANDOM FOREST") print("-" * 50) print("Definiamo una griglia di parametri per Random Forest:") param_grid_rf = { 'n_estimators': [50, 100, 200, 300], 'max_depth': [None, 10, 20, 30], 'min_samples_split': [2, 5, 10], 'min_samples_leaf': [1, 2, 4], 'max_features': ['sqrt', 'log2', None], 'bootstrap': [True, False] } print("🎯 GRIGLIA PARAMETRI:") for param, values in param_grid_rf.items(): print(f"• {param}: {values}") print(f"\n🔢 COMBINAZIONI TOTALI: {np.prod([len(v) for v in param_grid_rf.values()])}") print("\n🚀 ESECUIONE GRID SEARCH...") rf_grid = GridSearchCV( RandomForestClassifier(random_state=42, n_jobs=-1), param_grid_rf, cv=3, # 3-fold CV (più veloce) scoring='roc_auc', # Ottimizziamo per ROC-AUC n_jobs=-1, # Parallelizza verbose=1 # Mostra progresso ) rf_grid.fit(X_train, y_train) print("\n✅ GRID SEARCH COMPLETATO!") print(f"\n🎯 MIGLIORI PARAMETRI:") for param, value in rf_grid.best_params_.items(): print(f"• {param}: {value}") print(f"\n📈 PERFORMANCE MIGLIOR MODELLO:") print(f"• ROC-AUC CV: {rf_grid.best_score_:.3f}") print(f"• ROC-AUC Test: {roc_auc_score(y_test, rf_grid.predict_proba(X_test)[:, 1]):.3f}") # Confronto con baseline baseline_rf = baseline_results['Random Forest']['roc_auc'] improvement = (rf_grid.best_score_ - baseline_rf) / baseline_rf * 100 print(f"\n📊 MIGLIORAMENTO RISPETTO A BASELINE: +{improvement:.1f}%") # ------------------------------------------------------------ # PASSO 4: RANDOMIZED SEARCH PER GRADIENT BOOSTING # ------------------------------------------------------------ print("\n\n🎲 PASSO 4: RANDOMIZED SEARCH PER GRADIENT BOOSTING") print("-" * 50) print("RandomizedSearchCV è più efficiente per spazi parametrici grandi:") param_dist_gb = { 'n_estimators': [50, 100, 200, 300, 400, 500], 'learning_rate': [0.001, 0.01, 0.05, 0.1, 0.2, 0.3], 'max_depth': [3, 4, 5, 6, 7, 8, 9, 10], 'min_samples_split': [2, 5, 10, 15, 20], 'min_samples_leaf': [1, 2, 4, 6, 8], 'subsample': [0.6, 0.7, 0.8, 0.9, 1.0], 'max_features': ['sqrt', 'log2', None] } print("🎯 DISTRIBUZIONE PARAMETRI:") for param, values in param_dist_gb.items(): print(f"• {param}: {values}") total_combinations = np.prod([len(v) for v in param_dist_gb.values()]) print(f"\n🔢 COMBINAZIONI TOTALI: {total_combinations:,}") print("GridSearchCV sarebbe impossibile! Usiamo RandomizedSearchCV...") print("\n🎲 ESECUIONE RANDOMIZED SEARCH (50 combinazioni casuali)...") gb_random = RandomizedSearchCV( GradientBoostingClassifier(random_state=42), param_dist_gb, n_iter=50, # Prova 50 combinazioni casuali cv=3, scoring='roc_auc', n_jobs=-1, random_state=42, verbose=1 ) gb_random.fit(X_train, y_train) print("\n✅ RANDOMIZED SEARCH COMPLETATO!") print(f"\n🎯 MIGLIORI PARAMETRI TROVATI:") for param, value in gb_random.best_params_.items(): print(f"• {param}: {value}") print(f"\n📈 PERFORMANCE MIGLIOR MODELLO:") print(f"• ROC-AUC CV: {gb_random.best_score_:.3f}") print(f"• ROC-AUC Test: {roc_auc_score(y_test, gb_random.predict_proba(X_test)[:, 1]):.3f}") # ------------------------------------------------------------ # PASSO 5: GRID SEARCH CON PIPELINE # ------------------------------------------------------------ print("\n\n🔧 PASSO 5: GRID SEARCH CON PIPELINE COMPLETA") print("-" * 50) print("Il tuning più potente: ottimizziamo anche il preprocessing!") from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.feature_selection import SelectKBest, f_classif # Creiamo pipeline con preprocessing + modello pipeline = Pipeline([ ('scaler', StandardScaler()), ('feature_selection', SelectKBest(score_func=f_classif)), ('pca', PCA()), ('classifier', RandomForestClassifier(random_state=42, n_jobs=-1)) ]) # Grid per pipeline completa param_grid_pipeline = { 'feature_selection__k': [5, 10, 15, 'all'], 'pca__n_components': [None, 0.95, 0.99, 10, 15], 'classifier__n_estimators': [100, 200], 'classifier__max_depth': [None, 10, 20], 'classifier__min_samples_split': [2, 5] } print("🎯 GRIGLIA PER PIPELINE COMPLETA:") for param, values in param_grid_pipeline.items(): print(f"• {param}: {values}") print("\n🚀 GRID SEARCH CON PIPELINE...") pipeline_grid = GridSearchCV( pipeline, param_grid_pipeline, cv=3, scoring='roc_auc', n_jobs=-1, verbose=1 ) pipeline_grid.fit(X_train, y_train) print("\n✅ PIPELINE TUNING COMPLETATO!") print(f"\n🎯 MIGLIORI PARAMETRI:") for param, value in pipeline_grid.best_params_.items(): print(f"• {param}: {value}") print(f"\n📈 PERFORMANCE FINALE:") print(f"• ROC-AUC CV: {pipeline_grid.best_score_:.3f}") print(f"• ROC-AUC Test: {roc_auc_score(y_test, pipeline_grid.predict_proba(X_test)[:, 1]):.3f}") # ------------------------------------------------------------ # PASSO 6: ANALISI RISULTATI E CONFRONTO # ------------------------------------------------------------ print("\n\n📊 PASSO 6: CONFRONTO FINALE DEI METODI DI TUNING") print("-" * 50) # Raccogliamo tutti i risultati results = { 'Baseline RF': baseline_results['Random Forest']['roc_auc'], 'Grid Search RF': roc_auc_score(y_test, rf_grid.predict_proba(X_test)[:, 1]), 'Randomized Search GB': roc_auc_score(y_test, gb_random.predict_proba(X_test)[:, 1]), 'Pipeline Grid Search': roc_auc_score(y_test, pipeline_grid.predict_proba(X_test)[:, 1]) } # Creiamo DataFrame comparativo results_df = pd.DataFrame({ 'Metodo': list(results.keys()), 'ROC-AUC': list(results.values()), 'Miglioramento %': [(v - results['Baseline RF']) / results['Baseline RF'] * 100 for v in results.values()] }).sort_values('ROC-AUC', ascending=False) print("🏆 CLASSIFICA FINALE:") print(results_df.to_string(index=False)) # Analisi del miglioramento best_method = results_df.iloc[0]['Metodo'] best_score = results_df.iloc[0]['ROC-AUC'] improvement_total = results_df.iloc[0]['Miglioramento %'] print(f"\n🎯 MIGLIOR METODO: {best_method}") print(f"• ROC-AUC: {best_score:.3f}") print(f"• Miglioramento vs baseline: +{improvement_total:.1f}%") # ------------------------------------------------------------ # PASSO 7: ANALISI DEI PARAMETRI OTTIMALI # ------------------------------------------------------------ print("\n\n🔍 PASSO 7: ANALISI DETTAGLIATA PARAMETRI OTTIMALI") print("-" * 50) print("Analizziamo cosa abbiamo imparato dai migliori modelli:") print("\n🎯 PARAMETRI OTTIMALI RANDOM FOREST:") rf_best_params = rf_grid.best_params_ for param, value in rf_best_params.items(): print(f"• {param}: {value}") print("\n📖 INTERPRETAZIONE:") print("1. n_estimators: {} alberi (tradeoff accuratezza/tempo)".format(rf_best_params.get('n_estimators', 'N/A'))) print("2. max_depth: {} (profondità ottimale)".format(rf_best_params.get('max_depth', 'N/A'))) print("3. min_samples_split: {} (previene overfitting)".format(rf_best_params.get('min_samples_split', 'N/A'))) print("4. max_features: '{}' (migliora diversità alberi)".format(rf_best_params.get('max_features', 'N/A'))) print("\n🎯 PARAMETRI OTTIMALI PIPELINE:") pipeline_best_params = pipeline_grid.best_params_ for param, value in pipeline_best_params.items(): print(f"• {param}: {value}") print("\n📖 INTERPRETAZIONE PREPROCESSING:") if 'feature_selection__k' in pipeline_best_params: k = pipeline_best_params['feature_selection__k'] print(f"• Feature selection: {k} features migliori") if 'pca__n_components' in pipeline_best_params: n_components = pipeline_best_params['pca__n_components'] print(f"• PCA: {'nessuna' if n_components is None else n_components}") # ------------------------------------------------------------ # PASSO 8: SALVATAGGIO MODELLI OTTIMIZZATI # ------------------------------------------------------------ print("\n\n💾 PASSO 8: SALVATAGGIO MODELLI OTTIMIZZATI") print("-" * 50) import joblib # Salva il miglior modello joblib.dump(pipeline_grid.best_estimator_, 'miglior_modello_tuned.pkl') print("✅ Miglior modello salvato come 'miglior_modello_tuned.pkl'") # Salva anche i risultati del grid search results_dict = { 'best_params': pipeline_grid.best_params_, 'best_score': pipeline_grid.best_score_, 'cv_results': pipeline_grid.cv_results_ } joblib.dump(results_dict, 'risultati_tuning.pkl') print("✅ Risultati tuning salvati come 'risultati_tuning.pkl'") # ------------------------------------------------------------ # RIEPILOGO FINALE # ------------------------------------------------------------ print("\n\n🎉 HYPERPARAMETER TUNING MASTERATO!") print("=" * 80) div class="code-block-wrapper">
🚫 COPIA BLOCCATA
print("\n✅ COSA HAI IMPARATO OGGI:") print("1. GridSearchCV per spazi parametrici piccoli") print("2. RandomizedSearchCV per spazi grandi") print("3. Tuning con pipeline complete (preprocessing + modello)") print("4. Interpretazione parametri ottimali") print("5. Salvataggio modelli ottimizzati") print("\n🎯 QUANDO USARE OGNI METODO:") print("• GridSearchCV: Spazi piccoli (<1000 combinazioni)") print("• RandomizedSearchCV: Spazi grandi, vuoi risultati veloci") print("• Bayesian Optimization: Spazi enormi, budget computazionale alto") print("• Pipeline Tuning: Ottimizzazione end-to-end") print("\n💡 CONSIGLI PROFESSIONALI:") print("1. Inizia sempre con una baseline") print("2. Usa cross-validation per tuning") print("3. Ottimizza per la metrica giusta (non solo accuracy)") print("4. Documenta ogni esperimento") print("5. Valida sul test set SOLO alla fine") print(f"\n📊 RISULTATI FINALI:") print(f"• Miglior ROC-AUC: {best_score:.3f}") print(f"• Miglioramento: +{improvement_total:.1f}%") print(f"• Metodo vincente: {best_method}") print("\n🚀 PROSSIMI PASSI:") print("1. Prova Bayesian Optimization con scikit-optimize") print("2. Sperimenta con early stopping") print("3. Crea ensemble di modelli tuned") print("4. Automatizza il tuning con MLflow") print("\n🌟 COMPLIMENTI! SEI UN ESPERTO DI HYPERPARAMETER TUNING!")
Metodo Quando usarlo Vantaggi Svantaggi
GridSearchCV Spazi parametrici piccoli Esplora tutto lo spazio Computazionalmente costoso
RandomizedSearchCV Spazi parametrici grandi Efficiente, risultati veloci Potrebbe perdere ottimi locali
Bayesian Optimization Spazi enormi, budget alto Intelligente, campionamento adattivo Complesso da implementare
Pipeline Tuning Ottimizzazione end-to-end Migliora tutto il processo Molto lento

⚠️ Errori comuni da evitare nel tuning

1. Data leakage: Mai usare il test set per tuning

2. Overfitting al validation set: Troppo tuning può overfittare

3. Metriche sbagliate: Accuracy non va bene per classi sbilanciate

4. Tempo sprecato: Non ottimizzare features irrilevanti

5. Mancata validazione: Sempre test finale su set separato

💪 Esercizi Avanzati Scikit-Learn

Esercizio 1: Pipeline Multimodello MEDIO

Crea una pipeline che:

  1. Gestisce automaticamente tipi di feature diversi
  2. Prova 3 modelli diversi in parallelo
  3. Seleziona automaticamente il migliore
  4. Salva la pipeline completa per riuso

Suggerimento: Usa VotingClassifier o crea una meta-pipeline

Esercizio 2: Custom Transformer DIFFICILE

Crea un transformer personalizzato che:

🚫 COPIA BLOCCATA
from sklearn.base import BaseEstimator, TransformerMixin class FeatureEngineeringTransformer(BaseEstimator, TransformerMixin): def __init__(self): pass def fit(self, X, y=None): # Calcola statistiche necessarie return self def transform(self, X): # Applica feature engineering return X_engineered

Features da creare:

  1. Interazioni tra features (es: età × reddito)
  2. Trasformazioni non lineari (log, quadrato)
  3. Binning di variabili continue
  4. Ratio tra features correlate

Esercizio 3: Tuning Iperparametri Avanzato EXPERT

Implementa un sistema di tuning che:

  1. Usa Bayesian Optimization invece di Grid/Random Search
  2. Implementa early stopping basato su validation score
  3. Salva i risultati di ogni esperimento
  4. Crea visualizzazioni dell'evoluzione del tuning

Librerie utili: scikit-optimize, optuna, hyperopt

🚀 Preparati per gli Alberi Decisionali!

📚 Cosa hai imparato in questo modulo:

  1. Pipeline avanzate: ColumnTransformer per gestire tipi di feature diversi
  2. Hyperparameter tuning: GridSearchCV e RandomizedSearchCV
  3. Feature engineering automatica: All'interno delle pipeline
  4. Salvataggio modelli: Pipeline complete pronte per il deployment
  5. Best practices: Prevenzione data leakage, metriche appropriate

🌳 Cosa imparerai nel prossimo modulo:

  1. Alberi Decisionali: Come funzionano internamente
  2. Random Forest: Ensemble di alberi per più stabilità
  3. Feature Importance: Capire cosa guida le decisioni
  4. Gradient Boosting: Modelli ancora più potenti
  5. XGBoost/LightGBM: Librerie ottimizzate per prestazioni
Vai agli Alberi Decisionali →