Pesi delle classi e spostamento della soglia
Imparerete a impostare class_weight='balanced' nei classificatori sklearn e a spostare la soglia decisionale sugli output di probabilità per ottimizzare ulteriormente il recall degli eventi rari.
Pesi delle classi e spostamento della soglia è una lezione Machine Learning Academy gratuita su CoddyKit. Questa è la lezione 4 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.
Due alternative al ricampionamento
Il ricampionamento, come SMOTE e l’undersampling, modifica fisicamente i dati di addestramento. Esistono due approcci alternativi che operano direttamente sui dati originali: la ponderazione delle classi penalizza maggiormente gli errori sui campioni della classe minoritaria durante l’addestramento, mentre lo spostamento della soglia modifica il confine decisionale dopo l’addestramento. Entrambi sono più semplici e rapidi ed evitano i rischi di perdita di informazioni o di rumore sintetico associati al ricampionamento.
Pesi delle classi: penalizzare maggiormente gli errori sulla classe minoritaria
La maggior parte dei classificatori di sklearn accetta un parametro class_weight. Impostando class_weight='balanced', i pesi vengono calcolati automaticamente in modo inversamente proporzionale alla frequenza delle classi: weight[c] = n_samples / (n_classes * count[c]). Un errore di classificazione su un campione positivo raro viene quindi penalizzato molto più di un errore su un campione negativo comune, spingendo il modello ad apprendere meglio la classe minoritaria.
from sklearn.linear_model import LogisticRegression
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, stratify=y, random_state=42)
sc = StandardScaler()
X_tr = sc.fit_transform(X_train)
X_te = sc.transform(X_test)
# With balanced class weights
lr = LogisticRegression(class_weight='balanced').fit(X_tr, y_train)
print('--- class_weight=balanced ---')
print(classification_report(y_test, lr.predict(X_te)))Calcolo manuale dei pesi bilanciati
È anche possibile passare un dizionario di pesi personalizzati per ottenere un controllo più preciso rispetto a 'balanced'. Ad esempio, se secondo i requisiti aziendali una frode non rilevata è 20 volte più costosa di un falso allarme, imposti class_weight={0: 1, 1: 20}. In questo modo il rapporto tra i costi degli errori di classificazione viene codificato direttamente nell’addestramento.
from sklearn.linear_model import LogisticRegression
from sklearn.utils.class_weight import compute_class_weight
import numpy as np
# Compute balanced weights automatically
y_train = np.array([0] * 475 + [1] * 25)
classes = np.unique(y_train)
weights = compute_class_weight('balanced', classes=classes, y=y_train)
weight_dict = dict(zip(classes, weights))
print('Auto-balanced weights:', weight_dict)
# Custom: penalty 10x higher for missing positive
custom_weights = {0: 1, 1: 10}
print('Custom weights:', custom_weights)
lr = LogisticRegression(class_weight=custom_weights)
# lr.fit(X_train, y_train)Quali algoritmi supportano class_weight?
In scikit-learn, i seguenti estimator accettano class_weight: LogisticRegression, LinearSVC, SVC, SGDClassifier, DecisionTreeClassifier, RandomForestClassifier. I modelli di gradient boosting, come XGBoost e LightGBM, usano un parametro scale_pos_weight che svolge lo stesso ruolo. Le reti neurali lo gestiscono tramite sample_weight nella funzione di perdita.
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
# All support class_weight='balanced'
models = [
LogisticRegression(class_weight='balanced'),
DecisionTreeClassifier(class_weight='balanced'),
RandomForestClassifier(class_weight='balanced'),
SVC(class_weight='balanced', probability=True)
]
print('All these models support class_weight:')
for m in models:
print(' ', m.__class__.__name__)Spostamento della soglia: modifica del confine decisionale
I classificatori producono un punteggio di probabilità; la soglia decisionale predefinita è 0.5: viene predetta la classe positiva se proba >= 0.5. Nei problemi sbilanciati, abbassare la soglia, ad esempio a 0.3, aumenta il recall, intercettando più positivi, al costo di un maggior numero di falsi positivi. Alzandola aumenta la precision, al costo di non rilevare più campioni positivi. Lo spostamento della soglia viene applicato dopo l’addestramento e non richiede di addestrare nuovamente il modello.
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, recall_score, f1_score
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
import numpy as np
X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, stratify=y, random_state=42)
sc = StandardScaler()
X_tr_s = sc.fit_transform(X_tr)
X_te_s = sc.transform(X_te)
lr = LogisticRegression(class_weight='balanced').fit(X_tr_s, y_tr)
proba = lr.predict_proba(X_te_s)[:, 1]
print(f'{'Threshold':>10} {'Precision':>10} {'Recall':>8} {'F1':>6}')
for t in [0.2, 0.3, 0.4, 0.5, 0.6]:
preds = (proba >= t).astype(int)
p = precision_score(y_te, preds, zero_division=0)
r = recall_score(y_te, preds)
f = f1_score(y_te, preds, zero_division=0)
print(f'{t:>10.1f} {p:>10.4f} {r:>8.4f} {f:>6.4f}')Individuazione della soglia ottimale
Usi le funzioni precision_recall_curve e roc_curve per esaminare tutte le soglie possibili e calcolare la precision e il recall corrispondenti. Scelga quindi la soglia che massimizza l’F1, o qualsiasi altra metrica aziendale. Questo approccio è più rigoroso che scegliere una soglia a intuito.
from sklearn.metrics import precision_recall_curve, f1_score
import numpy as np
# proba and y_te from previous step
precisions, recalls, thresholds = precision_recall_curve(y_te, proba)
# F1 at each threshold
f1_scores = 2 * (precisions * recalls) / (precisions + recalls + 1e-8)
best_idx = np.argmax(f1_scores)
best_threshold = thresholds[best_idx]
best_f1 = f1_scores[best_idx]
print(f'Optimal threshold: {best_threshold:.4f}')
print(f'Best F1 at that threshold: {best_f1:.4f}')
# Apply the optimal threshold
y_pred_opt = (proba >= best_threshold).astype(int)
print('Minority class recall:', recall_score(y_te, y_pred_opt).round(4))class_weight e ricampionamento: compromessi
Vantaggi di class_weight: nessuna modifica dei dati, nessun rischio di overfitting su campioni sintetici, compatibilità con una Pipeline standard di sklearn e maggiore velocità. Vantaggi del ricampionamento: funziona con algoritmi che non supportano class_weight, ad esempio alcune varianti di boosting, e può essere utile in presenza di uno sbilanciamento molto grave (>100:1), quando la sola ponderazione delle classi non è sufficiente. Nella pratica, provi prima la ponderazione delle classi: è più semplice e spesso sufficiente.
Spostamento della soglia con la curva ROC
La curva ROC mostra il compromesso tra il tasso di veri positivi (recall) e il tasso di falsi positivi a ogni soglia. La statistica J di Youden (TPR - FPR) raggiunge il massimo in corrispondenza della soglia ottimale per bilanciare sensibilità e specificità. Usi questo metodo quando desidera attribuire la stessa importanza a entrambi i tipi di errore.
from sklearn.metrics import roc_curve
import numpy as np
fpr, tpr, thresholds = roc_curve(y_te, proba)
# Youden's J: maximise TPR - FPR
j_scores = tpr - fpr
best_idx = np.argmax(j_scores)
optimal_threshold = thresholds[best_idx]
print(f'Optimal threshold (Youden J): {optimal_threshold:.4f}')
print(f'TPR: {tpr[best_idx]:.4f} FPR: {fpr[best_idx]:.4f}')
y_pred_youden = (proba >= optimal_threshold).astype(int)
from sklearn.metrics import classification_report
print(classification_report(y_te, y_pred_youden))Pesi delle classi in XGBoost e LightGBM
XGBoost usa scale_pos_weight, cioè il rapporto tra i campioni negativi e positivi nel set di addestramento, per aumentare il peso degli esempi positivi. Con uno sbilanciamento di 95:5, imposti scale_pos_weight=19 (95/5). LightGBM usa in modo analogo is_unbalance=True oppure scale_pos_weight. Entrambi sono equivalenti a class_weight='balanced' di sklearn, ma usano una convenzione diversa per il nome del parametro.
import xgboost as xgb
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, stratify=y, random_state=42)
neg, pos = np.bincount(y_tr)
scale = neg / pos
print(f'scale_pos_weight = {scale:.1f}')
clf = xgb.XGBClassifier(scale_pos_weight=scale, random_state=42, eval_metric='logloss')
clf.fit(X_tr, y_tr)
print('AUC:', roc_auc_score(y_te, clf.predict_proba(X_te)[:, 1]).round(4))Confronto tra tutte le strategie per gestire lo sbilanciamento
Un confronto sistematico tra le strategie, eseguito sullo stesso dataset e sullo stesso set di test, mostra quale approccio funziona meglio per il problema e l’algoritmo specifici. Esegua questo confronto e riporti i risultati in una tabella, così da motivare la scelta della strategia finale.
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline as ImbPipeline
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import roc_auc_score
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
import numpy as np
X, y = make_classification(n_samples=1000, weights=[0.95, 0.05], random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, stratify=y, random_state=0)
sc = StandardScaler()
X_tr_s = sc.fit_transform(X_tr)
X_te_s = sc.transform(X_te)
strategies = [
('Baseline', None, {}),
('class_weight', None, {'class_weight': 'balanced'}),
('RUS', RandomUnderSampler(random_state=0), {}),
('SMOTE', SMOTE(random_state=0), {})
]
for name, sampler, kwargs in strategies:
Xr, yr = (sampler.fit_resample(X_tr_s, y_tr) if sampler else (X_tr_s, y_tr))
lr = LogisticRegression(**kwargs).fit(Xr, yr)
auc = roc_auc_score(y_te, lr.predict_proba(X_te_s)[:, 1])
print(f'{name:15s}: AUC={auc:.4f}')Calibrazione delle probabilità prodotte
La ponderazione delle classi e lo spostamento della soglia si basano entrambi sui punteggi di probabilità del modello. Se il classificatore è calibrato male, ad esempio una SVM con probability=True che usa la calibrazione di Platt, la regolazione della soglia potrebbe non funzionare bene. Usi sklearn.calibration.CalibratedClassifierCV oppure confronti le curve di calibrazione con calibration_curve per assicurarsi che le probabilità siano affidabili prima di ottimizzare la soglia.
Verifica rapida
Verifichi la propria comprensione della ponderazione delle classi e dello spostamento della soglia trattati in questa lezione.
Riepilogo della lezione
In questa lezione ha imparato che: class_weight='balanced' penalizza proporzionalmente gli errori di classificazione sulla classe minoritaria e funziona senza modificare i dati di addestramento; lo spostamento della soglia modifica il confine decisionale dopo l’addestramento, alterando il compromesso tra recall e precision; e la soglia ottimale può essere individuata massimizzando l’F1 sulla curva precision-recall oppure la J di Youden sulla curva ROC. Nella prossima lezione salveremo i modelli addestrati con joblib e pickle per la distribuzione in produzione.
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 «Pesi delle classi e spostamento della soglia» è gratuita?
Sì — il testo completo di «Pesi delle classi e spostamento della soglia» è 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 «Pesi delle classi e spostamento della soglia»?
Imparerete a impostare class_weight='balanced' nei classificatori sklearn e a spostare la soglia decisionale sugli output di probabilità per ottimizzare ulteriormente il recall degli eventi rari. 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 4 di 4.
Quanto tempo richiede la lezione «Pesi delle classi e spostamento della soglia»?
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
- Rilevare lo sbilanciamento: distribuzione delle classi e insidie dei baseline
- Random oversampling e SMOTE
- Random undersampling e Cluster Centroids
- Pesi delle classi e spostamento della soglia