Machine Learning Academy · Lekcja

Wagi klas i przesuwanie progu

Uczą się Państwo ustawiać class_weight='balanced' w klasyfikatorach sklearn, a następnie przesuwać próg decyzyjny dla wyników prawdopodobieństwa, aby dodatkowo optymalizować czułość dla rzadkich zdarzeń.

Lekcja 4 z 413 kroki

Wagi klas i przesuwanie progu to bezpłatna lekcja Machine Learning Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Machine Learning Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Machine Learning Academy zawiera 4 lekcji w sumie.

Dwie alternatywy dla resamplingu

Resampling (SMOTE, undersampling) fizycznie zmienia dane treningowe. Dwa alternatywne podejścia działają bezpośrednio na oryginalnych danych: ważenie klas nakłada podczas trenowania większą karę za błędną klasyfikację próbek klasy mniejszościowej, a przesuwanie progu dostosowuje granicę decyzyjną po zakończeniu trenowania. Oba podejścia są prostsze, szybsze i pozwalają uniknąć utraty informacji lub ryzyka związanego ze sztucznym szumem powstającym podczas resamplingu.

Wagi klas: większa kara za błędy klasy mniejszościowej

Większość klasyfikatorów sklearn przyjmuje parametr class_weight. Ustawienie class_weight='balanced' automatycznie oblicza wagi odwrotnie proporcjonalne do częstości występowania klas: weight[c] = n_samples / (n_classes * count[c]). Błędna klasyfikacja rzadkiej próbki pozytywnej jest wtedy karana znacznie surowiej niż błędna klasyfikacja często występującej próbki negatywnej, co skłania model do lepszego uczenia się klasy mniejszościowej.

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

Ręczne obliczanie zbalansowanych wag

Można również przekazać słownik własnych wag, jeśli potrzebna jest większa kontrola niż zapewnia 'balanced'. Jeśli na przykład z punktu widzenia firmy przeoczenie oszustwa kosztuje 20 razy więcej niż fałszywy alarm, należy ustawić class_weight={0: 1, 1: 20}. W ten sposób stosunek kosztów błędnej klasyfikacji zostaje bezpośrednio zakodowany w procesie trenowania.

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)

Które algorytmy obsługują class_weight?

W scikit-learn parametr class_weight przyjmują następujące estymatory: LogisticRegression, LinearSVC, SVC, SGDClassifier, DecisionTreeClassifier, RandomForestClassifier. Modele gradient boosting (XGBoost, LightGBM) używają parametru scale_pos_weight, który pełni tę samą funkcję. Sieci neuronowe obsługują to za pomocą sample_weight w funkcji straty.

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

Przesuwanie progu: dostosowywanie granicy decyzyjnej

Klasyfikatory zwracają wynik prawdopodobieństwa; domyślny próg decyzyjny wynosi 0.5 — model przewiduje klasę pozytywną, gdy proba >= 0.5. W przypadku niezbalansowanych problemów obniżenie progu (np. do 0.3) zwiększa czułość (pozwala wykryć więcej przypadków pozytywnych), ale powoduje więcej fałszywych alarmów. Podwyższenie progu zwiększa precyzję, ale prowadzi do przeoczenia większej liczby przypadków pozytywnych. Przesuwanie progu stosuje się po zakończeniu trenowania i nie wymaga ponownego trenowania modelu.

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

Wyznaczanie optymalnego progu

Należy użyć funkcji precision_recall_curve i roc_curve, aby przeanalizować wszystkie możliwe progi i obliczyć odpowiadające im wartości precyzji oraz czułości. Następnie należy wybrać próg maksymalizujący wynik F1 (lub inną metrykę biznesową). Jest to bardziej uzasadnione podejście niż zgadywanie wartości progu.

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 a resampling: kompromisy

Zalety class_weight: brak modyfikacji danych, brak ryzyka przeuczenia na sztucznych próbkach, działanie wewnątrz standardowego potoku sklearn, większa szybkość. Zalety resamplingu: działa z algorytmami, które nie obsługują class_weight (np. z niektórymi wariantami boosting), i może pomóc przy bardzo silnym niezbalansowaniu (>100:1), gdy samo ważenie klas nie wystarcza. W praktyce warto najpierw wypróbować ważenie klas — jest prostsze i często wystarczające.

Przesuwanie progu z użyciem krzywej ROC

Krzywa ROC pokazuje kompromis między odsetkiem prawdziwie pozytywnych (czułością) a odsetkiem fałszywie pozytywnych dla każdego progu. Statystyka J Youdena (TPR - FPR) osiąga maksimum przy optymalnym progu zapewniającym zrównoważoną czułość i swoistość. Należy użyć tego podejścia, gdy oba rodzaje błędów mają być traktowane jednakowo.

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

Wagi klas w XGBoost i LightGBM

XGBoost używa parametru scale_pos_weight — stosunku liczby próbek negatywnych do pozytywnych w zbiorze treningowym — aby zwiększyć wagę przykładów pozytywnych. Przy niezbalansowaniu 95:5 należy ustawić scale_pos_weight=19 (95/5). LightGBM używa podobnie parametru is_unbalance=True lub scale_pos_weight. Oba rozwiązania są odpowiednikiem class_weight='balanced' ze sklearn, ale stosują inną konwencję nazewnictwa parametrów.

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

Porównanie wszystkich strategii radzenia sobie z niezbalansowaniem

Systematyczne porównanie strategii na tym samym zbiorze danych i zbiorze testowym pokazuje, które podejście najlepiej sprawdza się w konkretnym problemie i algorytmie. Należy przeprowadzić takie porównanie i przedstawić wyniki w tabeli, aby uzasadnić ostateczny wybór strategii.

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

Kalibracja wyników prawdopodobieństwa

Ważenie klas i przesuwanie progu opierają się na wynikach prawdopodobieństwa modelu. Jeśli klasyfikator jest słabo skalibrowany (np. SVM z probability=True wykorzystujący skalowanie Platta), dostrajanie progu może nie działać dobrze. Należy użyć sklearn.calibration.CalibratedClassifierCV lub porównać krzywe kalibracji za pomocą calibration_curve, aby przed optymalizacją progu upewnić się, że prawdopodobieństwa są wiarygodne.

Szybkie sprawdzenie

Sprawdź swoją wiedzę na temat wag klas i przesuwania progu z tego rozdziału.

Podsumowanie rozdziału

W tym rozdziale nauczyłeś się, że: class_weight='balanced' proporcjonalnie zwiększa karę za błędną klasyfikację klasy mniejszościowej i działa bez modyfikowania danych treningowych, przesuwanie progu dostosowuje granicę decyzyjną po zakończeniu trenowania, zmieniając kompromis między czułością a precyzją, a także że optymalny próg można wyznaczyć, maksymalizując wynik F1 na krzywej precision-recall lub wartość J Youdena na krzywej ROC. Następnie zapiszemy wytrenowane modele za pomocą joblib i pickle na potrzeby wdrożenia produkcyjnego.

Bezpłatny start

Ucz się Python dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
30
Lekcje
120

Często zadawane pytania

Czy lekcja „Wagi klas i przesuwanie progu” jest bezpłatna?

Tak — pełny tekst „Wagi klas i przesuwanie progu” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Machine Learning Academy, przejdź na CoddyKit PRO. Kurs Machine Learning Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Wagi klas i przesuwanie progu”?

Uczą się Państwo ustawiać class_weight='balanced' w klasyfikatorach sklearn, a następnie przesuwać próg decyzyjny dla wyników prawdopodobieństwa, aby dodatkowo optymalizować czułość dla rzadkich zdar… Ćwiczysz Machine Learning Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Machine Learning Academy?

Nie wymagamy żadnego doświadczenia. Machine Learning Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Wagi klas i przesuwanie progu”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Machine Learning Academy?

Tak. Każda lekcja Machine Learning Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Wykrywanie niezbalansowania: rozkład klas i pułapki modelu bazowego
  2. Losowe nadpróbkowanie i SMOTE
  3. Losowe podpróbkowanie i Cluster Centroids
  4. Wagi klas i przesuwanie progu
← Powrót do Machine Learning Academy