Machine Learning Academy · Lektion

Klassevægte og flytning af tærsklen

De lærende angiver class_weight='balanced' i sklearn-klassifikatorer og flytter derefter beslutningstærsklen for sandsynlighedsoutput for yderligere at optimere recall for sjældne hændelser.

Lektion 4 af 413 trin

Klassevægte og flytning af tærsklen er en gratis Machine Learning Academy-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Machine Learning Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Machine Learning Academy-kurset indeholder 4 lektioner i alt.

To alternativer til resampling

Resampling, f.eks. SMOTE og underudtagning, ændrer træningsdataene fysisk. To alternative tilgange arbejder direkte med de oprindelige data: klassevægtning straffer fejlagtig klassifikation af eksempler fra mindretalsklassen hårdere under træningen, og flytning af tærsklen justerer beslutningsgrænsen efter træningen. Begge dele er enklere, hurtigere og undgår risikoen for tab af information eller syntetisk støj ved resampling.

Klassevægte: Straffel fejl i mindretalsklassen hårdere

De fleste sklearn-klassifikatorer accepterer parameteren class_weight. Når du sætter class_weight='balanced', beregnes vægte automatisk omvendt proportionalt med klassefrekvenserne: weight[c] = n_samples / (n_classes * count[c]). En fejlagtig klassifikation af et sjældent positivt eksempel straffes derefter langt hårdere end en fejlagtig klassifikation af et almindeligt negativt eksempel, hvilket får modellen til at lære mindretalsklassen bedre.

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)))

Manuel beregning af afbalancerede vægte

Du kan også angive en ordbog med brugerdefinerede vægte, hvis du ønsker mere præcis kontrol end med 'balanced'. Hvis din virksomhed f.eks. vurderer, at en overset svindelsag er 20 gange dyrere end en falsk alarm, kan du sætte class_weight={0: 1, 1: 20}. På den måde indbygges forholdet mellem omkostningerne ved fejlklassifikation direkte i træningen.

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)

Hvilke algoritmer understøtter class_weight?

I scikit-learn accepterer disse estimators class_weight: LogisticRegression, LinearSVC, SVC, SGDClassifier, DecisionTreeClassifier, RandomForestClassifier. Modeller til gradient boosting, som XGBoost og LightGBM, bruger parameteren scale_pos_weight, der har samme funktion. Neurale netværk håndterer dette gennem sample_weight i tabsfunktionen.

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__)

Flytning af tærsklen: Justering af beslutningsgrænsen

Klassifikatorer returnerer en sandsynlighedsscore; standardværdien for beslutningstærsklen er 0.5 — der forudsiges en positiv klasse, hvis proba >= 0.5. Ved ubalancerede problemer øger en sænkning af tærsklen, f.eks. til 0.3, genkaldelsen, fordi flere positive eksempler fanges, men det sker på bekostning af flere falske positive resultater. En højere tærskel øger præcisionen, men betyder, at flere positive eksempler overses. Flytning af tærsklen foretages efter træningen og kræver ikke, at modellen trænes igen.

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}')

Find den optimale tærskel

Brug funktionerne precision_recall_curve og roc_curve til at gennemgå alle mulige tærskler og beregne den tilsvarende præcision og genkaldelse. Vælg derefter den tærskel, der maksimerer F1 eller et andet forretningsmål. Det er en mere velbegrundet metode end at gætte på en tærskelværdi.

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 sammenlignet med resampling: Afvejninger

Fordele ved class_weight: ingen ændring af dataene, ingen risiko for overtilpasning til syntetiske eksempler, fungerer i en standard-sklearn-behandlingskæde og er hurtigere. Fordele ved resampling: fungerer med algoritmer, der ikke understøtter class_weight, f.eks. visse varianter af boosting, og kan hjælpe ved meget alvorlig ubalance, over 100:1, hvor klassevægtning alene ikke er tilstrækkelig. I praksis bør du prøve klassevægtning først — det er enklere og ofte tilstrækkeligt.

Flytning af tærsklen med ROC-kurven

ROC-kurven viser afvejningen mellem andelen af sande positive resultater, altså genkaldelsen, og andelen af falske positive resultater ved hver tærskel. Youdens J-statistik, TPR - FPR, maksimeres ved den optimale tærskel for afbalanceret sensitivitet og specificitet. Brug dette, når du vil tillægge begge fejltyper samme betydning.

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))

Klassevægte i XGBoost og LightGBM

XGBoost bruger scale_pos_weight — forholdet mellem negative og positive eksempler i træningssættet — til at vægte positive eksempler højere. Ved en ubalance på 95:5 skal du sætte scale_pos_weight=19, altså 95/5. LightGBM bruger på tilsvarende måde is_unbalance=True eller scale_pos_weight. Begge svarer til sklearms class_weight='balanced', men bruger en anden navngivningskonvention for parametre.

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))

Sammenligning af alle strategier mod ubalance

En systematisk sammenligning af strategier på det samme datasæt og testsæt viser, hvilken tilgang der fungerer bedst for netop dit problem og din algoritme. Kør denne sammenligning, og rapportér resultaterne i en tabel for at begrunde dit endelige valg af strategi.

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}')

Kalibrering af sandsynlighedsoutput

Klassevægte og flytning af tærsklen er begge afhængige af modellens sandsynlighedsscorer. Hvis klassifikatoren er dårligt kalibreret, f.eks. en SVM med probability=True, der bruger Platt-skalering, fungerer justering af tærsklen muligvis ikke godt. Brug sklearn.calibration.CalibratedClassifierCV, eller sammenlign kalibreringskurver med calibration_curve for at sikre, at sandsynlighederne er pålidelige, før du optimerer tærsklen.

Hurtigt tjek

Test din forståelse af klassevægte og flytning af tærsklen fra denne lektion.

Opsummering af lektionen

I denne lektion lærte du, at class_weight='balanced' straffer fejlagtige klassifikationer af mindretalsklassen proportionalt og fungerer uden at ændre træningsdataene, at flytning af tærsklen justerer beslutningsgrænsen efter træningen for at ændre afvejningen mellem genkaldelse og præcision, og at den optimale tærskel kan findes ved at maksimere F1 over præcision-genkaldelseskurven eller Youdens J over ROC-kurven. Næste gang gemmer vi trænede modeller med joblib og pickle til udrulning i produktion.

Gratis at komme i gang

Lær Python med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
30
Lektioner
120

Ofte stillede spørgsmål

Er lektionen “Klassevægte og flytning af tærsklen” gratis?

Ja — hele teksten til “Klassevægte og flytning af tærsklen” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Machine Learning Academy-kurset, skal du opgradere til CoddyKit PRO. Machine Learning Academy-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Klassevægte og flytning af tærsklen”?

De lærende angiver class_weight='balanced' i sklearn-klassifikatorer og flytter derefter beslutningstærsklen for sandsynlighedsoutput for yderligere at optimere recall for sjældne hændelser. Du øver dig i Machine Learning Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Machine Learning Academy?

Der kræves ingen tidligere erfaring. Machine Learning Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.

Hvor lang tid tager lektionen “Klassevægte og flytning af tærsklen”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Machine Learning Academy-lektion?

Ja. Alle Machine Learning Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Registrering af ubalance: Klassefordeling og faldgruber ved baseline
  2. Tilfældig oversampling og SMOTE
  3. Tilfældig undersampling og Cluster Centroids
  4. Klassevægte og flytning af tærsklen
← Tilbage til Machine Learning Academy