Laboratorio: Regressione e Classificazione
Benvenuto nel laboratorio pratico dedicato alla Regressione e alla Classificazione. In questa sezione implementerai modelli lineari e logistici in Python, analizzerai le metriche di accuratezza tramite le matrici di confusione e ti cimenterai con sfide di programmazione avanzate.
1. Configurazione e Download
Ti consigliamo di svolgere questi laboratori offline nel tuo notebook Jupyter laboratorio_ml.ipynb locale.
Download dei File del Laboratorio:
2. Esecuzione dei Laboratori
Esercizio 2.1: Regressione Lineare con Dati Hardcoded
Iniziamo addestrando un modello elementare su pochi campioni definiti direttamente nel codice.
import numpy as npfrom sklearn.linear_model import LinearRegressionfrom sklearn.metrics import mean_squared_error, r2_score
# Feature X: Ore studio (deve essere bidimensionale per Scikit-Learn)X = np.array([[2], [4], [6], [8], [10], [12]])
# Target y: Voto ottenutoy = np.array([4.5, 5.5, 6.0, 7.0, 8.5, 9.0])
# Inizializziamo e addestriamo il modelloreg = LinearRegression()reg.fit(X, y)
# Prevediamo i valori sul nostro datasety_pred = reg.predict(X)
print(f"Modello addestrato: y = {reg.coef_[0]:.2f} * x + {reg.intercept_:.2f}")print(f"MSE (Errore Quadratico Medio): {mean_squared_error(y, y_pred):.3f}")print(f"R² (Coefficiente di Determinazione): {r2_score(y, y_pred):.3f}")Esercizio 2.2: Regressione Lineare da file CSV
Carichiamo un dataset di 100 studenti salvato in un file CSV esterno, addestriamo la regressione lineare e tracciamo il grafico dei risultati.
import pandas as pdfrom sklearn.linear_model import LinearRegressionfrom sklearn.metrics import mean_squared_error, r2_scoreimport matplotlib.pyplot as plt
# A. Carichiamo i dati dal file CSVdf = pd.read_csv("studenti_voti.csv")
# B. Prepariamo le variabili per Scikit-LearnX = df[["ore_studio"]] # Doppie parentesi quadre per estrarre la feature come DataFrame bidimensionaley = df["voto_esame"]
# C. Inizializziamo e addestriamo il modelloreg_pandas = LinearRegression()reg_pandas.fit(X, y)
# D. Calcoliamo le previsioni del modelloy_pred = reg_pandas.predict(X)
# E. Valutiamo le metriche prestazionaliprint("\n--- Risultati del Modello su 100 studenti ---")print(f"Retta di regressione: y = {reg_pandas.coef_[0]:.3f} * x + {reg_pandas.intercept_:.3f}")print(f"MSE: {mean_squared_error(y, y_pred):.3f}")print(f"R²: {r2_score(y, y_pred):.3f}")
# F. Disegniamo il grafico finaleplt.figure(figsize=(8, 5))plt.scatter(X, y, color="blue", alpha=0.6, label="Dati Reali (Studenti)")plt.plot(X, y_pred, color="red", linewidth=2.4, label="Retta di Regressione")plt.xlabel("Ore di Studio Settimanali")plt.ylabel("Voto dell'Esame (1-10)")plt.title("Regressione Lineare: Ore di Studio vs Voto Conseguito")plt.grid(True, linestyle="--", alpha=0.5)plt.legend()plt.show()Esercizio 2.3: Regressione Logistica con Dati Hardcoded
Addestriamo un classificatore binario elementare su dati inseriti a mano nel codice.
from sklearn.linear_model import LogisticRegressionfrom sklearn.metrics import accuracy_score
# Dataset di addestramento# Feature: [Ore_Studio, Presenze_Percentuale]X_train = [[12, 90], [5, 60], [15, 95], [8, 70], [20, 100], [4, 50]]y_train = [1, 0, 1, 0, 1, 0]
# Dati del test set (nuovi studenti)X_test = [[10, 80], [6, 65]]y_test = [1, 0]
# Inizializziamo e addestriamo il classificatore logisticoclf = LogisticRegression()clf.fit(X_train, y_train)
# Eseguiamo le previsioni sul test sety_pred = clf.predict(X_test)y_prob = clf.predict_proba(X_test) # Restituisce le probabilità [Classe 0, Classe 1]
print(f"Previsioni per gli studenti di test: {y_pred}")print(f"Probabilità stimate (Bocciato vs Promosso):\n{y_prob}")print(f"Accuratezza sul test set: {accuracy_score(y_test, y_pred) * 100:.1f}%")Esercizio 2.4: Classificazione Logistica e Matrice di Confusione
Generiamo un dataset sintetico più grande, eseguiamo la suddivisione in Train/Test set (80/20) e calcoliamo tutte le metriche viste a lezione tramite la matrice di confusione.
from sklearn.datasets import make_classificationfrom sklearn.model_selection import train_test_splitfrom sklearn.linear_model import LogisticRegressionfrom sklearn.metrics import accuracy_score, confusion_matrix, precision_score, recall_score
# 1. Generiamo un dataset sintetico di classificazione binaria (600 campioni)X, y = make_classification(n_samples=600, n_features=2, n_redundant=0, n_clusters_per_class=1, random_state=42)
# 2. Dividiamo il dataset in Training Set (80%) e Test Set (20%)X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"Dimensioni del Train Set: {X_train.shape[0]} campioni")print(f"Dimensioni del Test Set: {X_test.shape[0]} campioni\n")
# 3. Inizializziamo e addestriamo il modello di Regressione Logisticaclf = LogisticRegression()clf.fit(X_train, y_train)
# 4. Prevediamo i risultati per i dati del Test Set (nascosti in addestramento)y_pred = clf.predict(X_test)
# 5. Calcoliamo e stampiamo le metriche di valutazione sul Test Setaccuracy = accuracy_score(y_test, y_pred)precision = precision_score(y_test, y_pred)recall = recall_score(y_test, y_pred)
print("--- Valutazione del Modello sul Test Set ---")print(f"Accuratezza (Accuracy): {accuracy * 100:.1f}%")print(f"Precisione (Precision): {precision * 100:.1f}%")print(f"Sensibilità (Recall): {recall * 100:.1f}%")
# 6. Generiamo e visualizziamo la Matrice di Confusionecm = confusion_matrix(y_test, y_pred)print("\nMatrice di Confusione:")print(cm)3. Coding Challenges
Mettiti alla prova modificando ed estendendo il codice Python scritto finora sul tuo notebook.
Challenge 1: L’effetto del Bias (Regressione Lineare)
- Task: Modifica l’inizializzazione del modello di regressione lineare nella Esercizio 2.1 impostando il parametro
fit_intercept=False:reg = LinearRegression(fit_intercept=False) - Domanda: Esegui nuovamente il codice. Qual è la nuova equazione della retta? Qual è il nuovo valore dell’MSE? Confrontalo con quello precedente dell’Esercizio 2.1 (): l’errore è aumentato o diminuito? Perché?
Visualizza la risoluzione e il codice
Risoluzione e Codice:
# Modifica dell'inizializzazione del modello reg_no_bias = LinearRegression(fit_intercept=False) reg_no_bias.fit(X, y)Nuove previsioni
y_pred_no_bias = reg_no_bias.predict(X)
print(f”Modello senza bias: y = {reg_no_bias.coef_[0]:.2f} * x”) print(f”Nuovo MSE: {mean_squared_error(y, y_pred_no_bias):.3f}”)
Risultati attesi a schermo:
Modello senza bias: y = 0.87 * x
Nuovo MSE: 2.403
Spiegazione concettuale:
Il nuovo MSE è di 2.403, un valore notevolmente più alto rispetto allo 0.048 ottenuto con il bias abilitato (circa 50 volte superiore!).
Forzando la retta a passare per l’origine tramite fit_intercept=False, abbiamo eliminato il bias . Questo significa che se uno studente studia 0 ore, il modello è obbligato a prevedere un voto pari a 0. I dati reali però ci dicono che anche con pochissime ore il voto di partenza è più alto (l’intercetta originale era ). Rimuovendo questo grado di libertà, la retta si inclina forzatamente per compensare, ma perde gran parte della sua precisione ed adattabilità, portando ad errori di previsione molto più elevati.
Challenge 2: Soglia Decisionale Personalizzata (Regressione Logistica)
- Task: Utilizza il modello addestrato nel Esercizio 2.3. Scrivi uno script Python che estrae le probabilità della classe 1 per il test set dal vettore
y_prob(prodotto daclf.predict_proba(X_test)) e applica manualmente la soglia decisionale di per determinare la classe prevista (1 se probabilità , altrimenti 0). - Domanda: Quali sono le nuove previsioni del modello per i due studenti del test set con questa soglia più rigida?
Visualizza la risoluzione e il codice
Risoluzione e Codice:
# y_prob contiene [prob_classe_0, prob_classe_1] per ciascun elemento. # Estraiamo solo le probabilità della classe 1 (colonna indice 1) prob_classe_1 = y_prob[:, 1]Impostiamo la nuova soglia decisionale
soglia_personalizzata = 0.8
Applichiamo la soglia: True (diventa 1) se >= 0.8, False (diventa 0) altrimenti
y_pred_personalizzato = (prob_classe_1 >= soglia_personalizzata).astype(int)
print(f”Probabilità classe 1 (Promosso): {prob_classe_1}”) print(f”Previsioni con soglia standard 0.5: {y_pred}”) print(f”Previsioni con soglia rigida 0.8: {y_pred_personalizzato}”)
Risultati attesi a schermo:
Probabilità classe 1 (Promosso): [0.5128965 0.00228039]
Previsioni con soglia standard 0.5: [1 0]
Previsioni con soglia rigida 0.8: [0 0]
Spiegazione concettuale:
Con la soglia standard a , il primo studente (che ha il di probabilità di promozione) veniva classificato come 1 (Promosso). Alzando la soglia all’ (), il modello diventa più “severo”: poiché , anche questo studente viene ora classificato come 0 (Bocciato). Modificando la soglia possiamo rendere un modello più conservativo o più ottimista a seconda delle esigenze (ad esempio, essere molto conservativi prima di approvare una transazione bancaria sospetta o una diagnosi clinica).
Challenge 3: Calcolo delle metriche Precision e Recall da codice
- Task: Utilizza la matrice di confusione
cmcalcolata sul test set del Esercizio 2.4. Scompatta i quattro valori usando il metodo.ravel()di NumPy:Scrivi il codice Python per calcolare la Precision e il Recall del modello sul test set e stampali a schermo.TN, FP, FN, TP = cm.ravel()
Visualizza la risoluzione e il codice
Risoluzione e Codice:
# Scompattiamo la matrice di confusione dell'Esercizio 2.4 TN, FP, FN, TP = cm.ravel()Calcoliamo Precision e Recall (aggiungiamo un controllo per evitare divisioni per zero)
precision = TP / (TP + FP) if (TP + FP) > 0 else 0 recall = TP / (TP + FN) if (TP + FN) > 0 else 0
print(f”Veri Negativi (TN): {TN}, Falsi Positivi (FP): {FP}”) print(f”Falsi Negativi (FN): {FN}, Veri Positivi (TP): {TP}”) print(f”Precision: {precision:.2f} (ovvero {precision * 100:.1f}%)”) print(f”Recall: {recall:.2f} (ovvero {recall * 100:.1f}%)”)
Risultati attesi a schermo:
Veri Negativi (TN): 58, Falsi Positivi (FP): 3
Falsi Negativi (FN): 2, Veri Positivi (TP): 57
Precision: 0.95 (ovvero 95.0%)
Recall: 0.97 (ovvero 96.6%)
Spiegazione concettuale:
Nel test set dell’Esercizio 2.4 (120 studenti totali), il modello commette 3 Falsi Positivi (studenti bocciati previsti erroneamente come promossi) e 2 Falsi Negativi (studenti promossi previsti erroneamente come bocciati). Ne consegue una Precision del (l’affidabilità delle previsioni positive) e un Recall del (la percentuale di promossi reali che siamo riusciti a identificare).
Challenge 4: Simulazione di Early Stopping in Python
- Task: Durante l’addestramento di una rete neurale su 10 iterazioni, registriamo l’errore sul Train Set e sul Validation Set a ogni passaggio. Supponi di avere i seguenti due array con gli errori misurati:
Scrivi uno script Python che simula l’algoritmo di Early Stopping: scorrendo gli elementi dei due array, deve identificare l’indice dell’iterazione in cui l’errore sul Validation Set smette di scendere e inizia a risalire, e stampare a schermo l’iterazione ottimale salvata (1-indexed) e il relativo valore minimo di Validation Loss.train_errors = [0.95, 0.70, 0.50, 0.35, 0.22, 0.15, 0.10, 0.07, 0.05, 0.03]val_errors = [0.98, 0.75, 0.55, 0.40, 0.30, 0.28, 0.32, 0.38, 0.45, 0.52]
- Domanda: In quale iterazione si verifica il blocco e qual è la configurazione ottimale salvata?
Visualizza la risoluzione e il codice
Risoluzione e Codice:
# Definiamo gli array degli errori train_errors = [0.95, 0.70, 0.50, 0.35, 0.22, 0.15, 0.10, 0.07, 0.05, 0.03] val_errors = [0.98, 0.75, 0.55, 0.40, 0.30, 0.28, 0.32, 0.38, 0.45, 0.52]Variabili di supporto
miglior_val_loss = float(‘inf’) miglior_iterazione = -1
Scorriamo i dati
for idx, val_loss in enumerate(val_errors): # Se la loss attuale è migliore, aggiorniamo il record if val_loss < miglior_val_loss: miglior_val_loss = val_loss miglior_iterazione = idx + 1 # Convertiamo in 1-indexed else: # Se la loss inizia a risalire, l’Early Stopping interviene print(f”Overfitting rilevato all’iterazione {idx + 1} (Val Loss: {val_loss:.2f})”) print(“L’addestramento viene interrotto!”) break
print(f”\nRisultato finale:”) print(f”Iterazione ottimale salvata: {miglior_iterazione}”) print(f”Minima Validation Loss: {miglior_val_loss:.2f}”)
Risultati attesi a schermo:
Overfitting rilevato all’iterazione 7 (Val Loss: 0.32) L’addestramento viene interrotto!
Risultato finale: Iterazione ottimale salvata: 6 Minima Validation Loss: 0.28
Spiegazione concettuale:
Fino all’iterazione 6, l’errore del Validation Set scende progressivamente fino a un minimo di 0.28. All’iterazione 7, l’errore sul Validation sale a 0.32 (mentre la train loss continua a scendere da 0.15 a 0.10). Questo è il segnale classico dell’inizio dell’overfitting. Lo script rileva questa salita, arresta l’addestramento e seleziona come modello migliore quello congelato al termine dell’iterazione 6.