Machine Learning Academy · Lezione

Rilevare lo sbilanciamento: distribuzione delle classi e insidie dei baseline

Imparerete a calcolare le frequenze delle classi, evidenziare il problema del dummy classifier su un dataset sbilanciato e verificare che in questo caso l’accuratezza sia una metrica fuorviante.

Lezione 1 di 413 passaggi

Rilevare lo sbilanciamento: distribuzione delle classi e insidie dei baseline è una lezione Machine Learning Academy gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Machine Learning Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Machine Learning Academy include 4 lezioni in totale.

Che cos'è lo sbilanciamento tra le classi

Lo sbilanciamento tra le classi si verifica quando una classe domina il dataset. Nel rilevamento delle frodi, le transazioni fraudolente possono rappresentare lo 0,1% di tutti i record; nella diagnosi medica, una malattia rara può colpire 1 paziente su 1000. Un modello che predice sempre la classe maggioritaria raggiunge un'accuratezza del 99,9%, ignorando completamente la classe rara ma critica: per questo, in questi scenari, l'accuratezza è una metrica pericolosamente fuorviante.

Misurare la distribuzione delle classi

Prima di addestrare qualsiasi modello, esamini la distribuzione delle classi con pd.Series(y).value_counts() o np.bincount(y). Un riepilogo statistico utile è il rapporto di sbilanciamento, cioè il rapporto tra il numero di elementi della classe maggioritaria e quello della classe minoritaria. I rapporti superiori a 10:1 indicano un dataset sbilanciato; oltre 100:1 lo sbilanciamento è grave e richiede tecniche specializzate.

import numpy as np
import pandas as pd

# Simulate imbalanced binary classification dataset
np.random.seed(0)
y = np.array([0] * 950 + [1] * 50)  # 95% negative, 5% positive

counts = pd.Series(y).value_counts()
print('Class counts:\n', counts)
print()
print('Class proportions:\n', counts / len(y))
print()
print('Imbalance ratio:', counts[0] / counts[1])

La trappola dell'accuratezza con dati sbilanciati

In un dataset composto per il 95% da esempi negativi, un modello che predice sempre la classe maggioritaria (negativa) raggiunge un'accuratezza del 95% senza imparare nulla di utile. Questa è la trappola dell'accuratezza. Il modello non è assolutamente in grado di rilevare i casi positivi, cioè la classe che le interessa davvero, eppure la sua accuratezza sembra notevole. Ecco perché i dataset sbilanciati richiedono metriche che vadano oltre l'accuratezza.

from sklearn.dummy import DummyClassifier
from sklearn.metrics import accuracy_score, classification_report
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification

X, y = make_classification(n_samples=1000, weights=[0.95, 0.05],
                            random_state=42, n_features=10)

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

# Always predict majority class
dummy = DummyClassifier(strategy='most_frequent')
dummy.fit(X_train, y_train)
y_pred = dummy.predict(X_test)

print('Accuracy:', accuracy_score(y_test, y_pred).round(4))
print()
print(classification_report(y_test, y_pred))

Perché è importante la baseline DummyClassifier

DummyClassifier con strategy='most_frequent' rappresenta la baseline minima accettabile. Qualsiasi modello reale deve superarla in modo significativo, non solo in termini di accuratezza, ma anche nelle metriche importanti, come precision, recall, F1 o AUC-ROC. Se il modello reale è solo marginalmente migliore del dummy, probabilmente ha imparato anch'esso a ignorare la classe minoritaria.

from sklearn.linear_model import LogisticRegression
from sklearn.dummy import DummyClassifier
from sklearn.metrics import classification_report
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

X_train_s = StandardScaler().fit_transform(X_train)
X_test_s = StandardScaler().fit_transform(X_test)

lr = LogisticRegression().fit(X_train_s, y_train)
print('--- Logistic Regression ---')
print(classification_report(y_test, lr.predict(X_test_s)))

Precision e recall con dati sbilanciati

Per i problemi sbilanciati, le due metriche più informative sono: Precision = TP / (TP + FP), cioè, tra tutti i positivi predetti, quanti erano effettivamente positivi? Recall = TP / (TP + FN), cioè, tra tutti i positivi effettivi, quanti sono stati rilevati dal modello? Nel rilevamento delle frodi, il recall è fondamentale, perché non bisogna lasciarsi sfuggire le frodi; nel filtraggio dello spam, è importante la precision, perché non bisogna classificare erroneamente le email legittime.

from sklearn.metrics import precision_score, recall_score, f1_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
X_train_s = StandardScaler().fit_transform(X_train)
X_test_s = StandardScaler().fit_transform(X_test)

lr = LogisticRegression().fit(X_train_s, y_train)
y_pred = lr.predict(X_test_s)

print(f'Precision: {precision_score(y_test, y_pred):.4f}')
print(f'Recall:    {recall_score(y_test, y_pred):.4f}')
print(f'F1-Score:  {f1_score(y_test, y_pred):.4f}')

ROC-AUC: valutazione indipendente dalla soglia

ROC-AUC valuta il modello a tutte le possibili soglie decisionali e misura l'area sotto la curva risultante. Un'AUC pari a 0,5 indica un comportamento casuale; 1,0 è il risultato perfetto; un valore pari o superiore a 0,9 è eccellente. A differenza dell'accuratezza, l'AUC non è influenzata dallo sbilanciamento tra le classi, perché valuta la capacità di ordinamento del modello invece delle predizioni a una soglia fissa. L'AUC è la metrica primaria consigliata per la maggior parte dei problemi di classificazione binaria sbilanciati.

from sklearn.metrics import roc_auc_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

sc = StandardScaler()
X_train_s = sc.fit_transform(X_train)
X_test_s = sc.transform(X_test)

lr = LogisticRegression().fit(X_train_s, y_train)
proba = lr.predict_proba(X_test_s)[:, 1]
print('ROC-AUC:', roc_auc_score(y_test, proba).round(4))

Visualizzare lo sbilanciamento tra le classi

Un semplice grafico a barre delle frequenze delle classi comunica immediatamente lo sbilanciamento agli stakeholder. Lo includa come procedura standard nel notebook di analisi esplorativa dei dati. Se lavora con un DataFrame Pandas, verifichi anche l'eventuale sbilanciamento nascosto dovuto a distorsioni nella raccolta dei dati, anziché a una reale rarità nel mondo reale.

import matplotlib.pyplot as plt
import numpy as np

y = np.array([0] * 950 + [1] * 50)
classes, counts = np.unique(y, return_counts=True)

plt.bar(['Negative (0)', 'Positive (1)'], counts, color=['#2196F3', '#F44336'])
plt.ylabel('Count')
plt.title('Class Distribution (95:5 imbalance)')
for i, c in enumerate(counts):
    plt.text(i, c + 5, str(c), ha='center', fontweight='bold')
plt.show()

Suddivisione stratificata per preservare i rapporti

Quando suddivide un dataset sbilanciato, utilizzi stratify=y in train_test_split. Senza stratificazione, il caso potrebbe collocare tutti gli esempi della classe minoritaria in una sola suddivisione, rendendo l'addestramento o la valutazione privi di significato. La suddivisione stratificata garantisce che i set di addestramento e di test abbiano lo stesso rapporto tra le classi del dataset originale.

from sklearn.model_selection import train_test_split
import numpy as np

y = np.array([0] * 950 + [1] * 50)
X = np.random.randn(1000, 5)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

print('Train class ratio:', (y_train == 1).mean().round(4))
print('Test class ratio:', (y_test == 1).mean().round(4))
print('Expected ratio:', (y == 1).mean().round(4))

Stratified K-Fold per la convalida incrociata

Allo stesso modo, utilizzi StratifiedKFold oppure passi semplicemente cv=5 a cross_val_score: scikit-learn usa automaticamente StratifiedKFold per i classificatori. In questo modo ogni fold mantiene il rapporto tra le classi originale. Un KFold normale, in presenza di classi minoritarie ridotte, può creare fold privi di esempi positivi, causando errori o metriche fuorvianti.

from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np

y = np.array([0] * 950 + [1] * 50)
X = np.random.randn(1000, 5)

pipe = Pipeline([('sc', StandardScaler()), ('lr', LogisticRegression())])
skf = StratifiedKFold(n_splits=5)

scores = cross_val_score(pipe, X, y, cv=skf, scoring='roc_auc')
print(f'Stratified CV AUC: {np.mean(scores):.4f} +/- {np.std(scores):.4f}')

La curva precision-recall

Con dati fortemente sbilanciati, la curva precision-recall (PR) è spesso più informativa della curva ROC. La curva PR mostra la precision sull'asse y e il recall sull'asse x a tutte le soglie. L'area sotto la curva PR, ovvero il punteggio AP, varia da 0 a 1 ed è sensibile alle prestazioni sulla classe minoritaria in un modo che ROC-AUC può mascherare quando la classe maggioritaria è predominante.

from sklearn.metrics import precision_recall_curve, average_precision_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)

sc = StandardScaler()
X_train_s = sc.fit_transform(X_train)
X_test_s = sc.transform(X_test)

lr = LogisticRegression().fit(X_train_s, y_train)
proba = lr.predict_proba(X_test_s)[:, 1]

precision, recall, _ = precision_recall_curve(y_test, proba)
ap = average_precision_score(y_test, proba)

plt.plot(recall, precision, label=f'AP={ap:.3f}')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision-Recall Curve')
plt.legend()
plt.show()

Documentare lo sbilanciamento prima della modellazione

Una best practice consiste nel creare un report sullo sbilanciamento all'inizio di ogni progetto di classificazione. Registri il numero di elementi per classe, i rapporti e l'accuratezza baseline del dummy. Questo permette di definire le aspettative e obbliga il team a concordare la metrica di successo corretta prima di addestrare qualsiasi modello. Un modello di rilevamento delle frodi con un'accuratezza del 95% è solitamente inutile: gli stakeholder devono saperlo fin dall'inizio.

import numpy as np
from sklearn.dummy import DummyClassifier
from sklearn.metrics import accuracy_score, roc_auc_score

y = np.array([0] * 950 + [1] * 50)
X = np.random.randn(1000, 5)

dummy = DummyClassifier(strategy='most_frequent').fit(X, y)
dummy_acc = accuracy_score(y, dummy.predict(X))
dummy_auc = roc_auc_score(y, dummy.predict_proba(X)[:, 1])

print('=== Imbalance Report ===')
print(f'Class 0: {(y==0).sum()}  ({(y==0).mean():.1%})')
print(f'Class 1: {(y==1).sum()}  ({(y==1).mean():.1%})')
print(f'Imbalance ratio: {(y==0).sum()/(y==1).sum():.0f}:1')
print(f'Dummy accuracy: {dummy_acc:.4f}')
print(f'Dummy AUC:     {dummy_auc:.4f}')
print('Recommended metric: ROC-AUC or Precision-Recall AUC')

Verifica rapida

Verifichi la sua comprensione dello sbilanciamento tra le classi e delle insidie delle baseline affrontati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: lo sbilanciamento tra le classi rende l'accuratezza una metrica fuorviante, perché un modello che predice solo la classe maggioritaria può raggiungere un'accuratezza molto elevata; bisogna confrontare sempre il modello con una baseline DummyClassifier per verificare che stia imparando qualcosa che vada oltre il comportamento banale; e bisogna utilizzare ROC-AUC o l'AUC della curva precision-recall come metriche primarie per i dataset sbilanciati. Ora applicheremo SMOTE e il random oversampling per aumentare la rappresentazione della classe minoritaria.

Gratis per iniziare

Impara Python con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
30
Lezioni
120

Domande Frequenti

La lezione «Rilevare lo sbilanciamento: distribuzione delle classi e insidie dei baseline» è gratuita?

Sì — il testo completo di «Rilevare lo sbilanciamento: distribuzione delle classi e insidie dei baseline» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Machine Learning Academy, passa a CoddyKit PRO. Il corso Machine Learning Academy include 4 lezioni in totale.

Cosa imparerò in «Rilevare lo sbilanciamento: distribuzione delle classi e insidie dei baseline»?

Imparerete a calcolare le frequenze delle classi, evidenziare il problema del dummy classifier su un dataset sbilanciato e verificare che in questo caso l’accuratezza sia una metrica fuorviante. Eserciti Machine Learning Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Machine Learning Academy?

Non è richiesta alcuna esperienza precedente. Machine Learning Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.

Quanto tempo richiede la lezione «Rilevare lo sbilanciamento: distribuzione delle classi e insidie dei baseline»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Machine Learning Academy?

Sì. Ogni lezione Machine Learning Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Rilevare lo sbilanciamento: distribuzione delle classi e insidie dei baseline
  2. Random oversampling e SMOTE
  3. Random undersampling e Cluster Centroids
  4. Pesi delle classi e spostamento della soglia
← Torna a Machine Learning Academy