Obalanserade klasser
Kunna hantera skeva klasser med omviktning och rätt mått.
Förkunskaper
Intuition
1 % av transaktionerna är bedrägerier. Din modell säger «inget bedrägeri» om allt och får 99 % träffsäkerhet.
Den har inte lärt sig något alls.
Träffsäkerhet är oanvändbart vid obalans. Använd i stället:
| Mått | Svarar på |
|---|---|
| Precision | av dem vi larmade om, hur många var äkta? |
| Recall | av de äkta, hur många hittade vi? |
| F1 | harmoniskt medel av de två |
| PR-AUC | hela avvägningen, känslig för den sällsynta klassen |
| ROC-AUC | hela avvägningen — men ser optimistisk ut vid stark obalans |
Precision och recall drar åt olika håll. Larmar du om allt får du perfekt recall och usel precision. Larmar du bara om det du är säker på blir det tvärtom. Vilken som är viktigast avgörs av vad ett fel kostar — inte av statistiken.
Formellt
Tre familjer av åtgärder:
| Åtgärd | Hur | Kommentar |
|---|---|---|
| Klassvikter | class_weight="balanced" | enklast, oftast först; ändrar förlusten, inte datan |
| Översampling | duplicera eller generera (SMOTE) | risk för överanpassning på den lilla klassen |
| Undersampling | kasta bort från majoriteten | slänger information |
| Tröskeljustering | ändra 0,5 till något annat | gratis, och ofta det mest verksamma |
Den sista raden är underskattad. En modell tränad utan några åtgärder alls, men med tröskeln satt efter kostnad, slår ofta en omsamplad modell med tröskel 0,5.
Tröskeln ska väljas efter kostnad, inte efter symmetri. Om ett missat bedrägeri kostar 5 000 kr och ett falsklarm kostar 50 kr i granskningstid ska du larma så snart sannolikheten överstiger ungefär
Alltså 1 %, inte 50 %. Att behålla standardtröskeln är i praktiken att påstå att de två feltyperna kostar lika mycket.
Två regler som är lätta att bryta:
- Omsampla bara träningsdatan. Validering och test ska ha den verkliga klassfördelningen, annars mäter du på en värld som inte finns.
- Omsampla inne i korsvalideringen, inte före. SMOTE före uppdelningen skapar syntetiska punkter från testvecket och ger läckage.
Om den sällsynta klassen har extremt få exempel (tiotal, inte tusental) är klassificering ofta fel ansats. Överväg anomalidetektion, som modellerar den normala klassen och larmar på avvikelser — den behöver inte exempel på det ovanliga.
Kod
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (classification_report, average_precision_score,
roc_auc_score, precision_recall_curve)
X, y = make_classification(n_samples=20000, weights=[0.99], flip_y=0.01, random_state=0)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)
print("andel positiva:", round(float(y.mean()), 4)) # 0.0155
for vikt in (None, "balanced"):
m = LogisticRegression(max_iter=2000, class_weight=vikt).fit(Xtr, ytr)
p = m.predict_proba(Xte)[:, 1]
print(f"class_weight={vikt}: ROC-AUC {roc_auc_score(yte, p):.3f} "
f"PR-AUC {average_precision_score(yte, p):.3f}")
# class_weight=None: ROC-AUC 0.962 PR-AUC 0.585
# class_weight=balanced: ROC-AUC 0.962 PR-AUC 0.580
# ← vikterna ändrade knappt rangordningen; det är tröskeln som gör jobbet
# Välj tröskel efter kostnad
C_FP, C_FN = 50, 5000
m = LogisticRegression(max_iter=2000).fit(Xtr, ytr)
p = m.predict_proba(Xte)[:, 1]
def kostnad(t):
pred = p >= t
fp = int(((pred == 1) & (yte == 0)).sum())
fn = int(((pred == 0) & (yte == 1)).sum())
return fp * C_FP + fn * C_FN, fp, fn
for t in (0.5, 0.2, 0.05, C_FP / (C_FP + C_FN)):
k, fp, fn = kostnad(t)
print(f" tröskel {t:.3f}: kostnad {k:>8,} kr (FP {fp:>4}, FN {fn:>3})")
bast = min(np.linspace(0.001, 0.999, 999), key=lambda t: kostnad(t)[0])
print("billigaste tröskel:", round(float(bast), 3))
Utskriften visar det viktiga: skillnaden mellan tröskel 0,5 och en kostnadsvald tröskel är oftast större än skillnaden mellan modeller. Och den kostar ingen träningstid alls.
Behärskning innebär
- Väljer mått som fungerar vid obalans
- Använder klassvikter eller omsampling
- Sätter beslutströskeln efter kostnad
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- scikit-learn User Guide (BSD-3) — BSD-3-Clause
- Google — Rules of Machine Learning — CC BY 4.0
- Dive into Deep Learning (CC BY-SA 4.0) — CC BY-SA 4.0