น้ำหนักคลาสและการเลื่อนเกณฑ์ตัดสินใจ
ผู้เรียนจะกำหนด class_weight='balanced' ในตัวจำแนกของ sklearn แล้วเลื่อนเกณฑ์ตัดสินใจบนผลลัพธ์ความน่าจะเป็นเพื่อเพิ่มค่าการเรียกคืนสำหรับเหตุการณ์ที่พบได้น้อย
น้ำหนักคลาสและการเลื่อนเกณฑ์ตัดสินใจ เป็นบทเรียน Machine Learning Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Machine Learning Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Machine Learning Academy มีบทเรียนทั้งหมด 4 บทเรียน
บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ
Two Alternatives to Resampling
Resampling (SMOTE, undersampling) physically changes the training data. Two alternative approaches work directly with the original data: class weighting penalises misclassifying minority samples more heavily during training, and threshold moving adjusts the decision boundary after training. Both are simpler, faster, and avoid the information loss or synthetic-noise risks of resampling.
Class Weights: Penalise Minority Errors More
Most sklearn classifiers accept a class_weight parameter. Setting class_weight='balanced' automatically computes weights inversely proportional to class frequencies: weight[c] = n_samples / (n_classes * count[c]). Misclassifying a rare positive sample is then penalised much more than misclassifying a common negative sample, pushing the model to learn the minority class better.
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)))Computing Balanced Weights Manually
You can also pass a dictionary of custom weights if you want finer control than 'balanced'. For example, if your business says a missed fraud is 20x more costly than a false alarm, set class_weight={0: 1, 1: 20}. This directly encodes the misclassification cost ratio into training.
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)Which Algorithms Support class_weight?
In scikit-learn, these estimators accept class_weight: LogisticRegression, LinearSVC, SVC, SGDClassifier, DecisionTreeClassifier, RandomForestClassifier. Gradient boosting models (XGBoost, LightGBM) use a scale_pos_weight parameter that plays the same role. Neural networks handle it through sample_weight in the loss function.
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__)Threshold Moving: Adjusting the Decision Boundary
Classifiers output a probability score; the default decision threshold is 0.5 — predict positive if proba >= 0.5. For imbalanced problems, lowering the threshold (e.g., to 0.3) increases recall (catches more positives) at the cost of more false positives. Raising it increases precision at the cost of missing more positives. Threshold moving is applied after training and does not require retraining the model.
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}')Finding the Optimal Threshold
Use the precision_recall_curve and roc_curve functions to sweep all possible thresholds and compute corresponding precision and recall. Then choose the threshold that maximises F1 (or any other business metric). This is more principled than guessing a threshold value.
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 vs Resampling: Trade-Offs
class_weight advantages: no data modification, no risk of overfitting to synthetic samples, works inside a standard sklearn Pipeline, faster. Resampling advantages: works with algorithms that do not support class_weight (e.g., some boosting variants), can help with very severe imbalance (>100:1) where class weighting alone is not enough. In practice, try class weighting first — it is simpler and often sufficient.
Threshold Moving With ROC Curve
The ROC curve shows the trade-off between true-positive rate (recall) and false-positive rate at every threshold. The Youden's J statistic (TPR - FPR) is maximised at the optimal threshold for balanced sensitivity and specificity. Use this when you want equal consideration of both error types.
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))Class Weights in XGBoost and LightGBM
XGBoost uses scale_pos_weight — the ratio of negative to positive samples in the training set — to upweight positive examples. For 95:5 imbalance, set scale_pos_weight=19 (95/5). LightGBM uses is_unbalance=True or scale_pos_weight similarly. Both are equivalent to sklearn's class_weight='balanced' but use a different parameter name convention.
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))Comparing All Imbalance Strategies
A systematic comparison across strategies on the same dataset and test set reveals which approach works best for your specific problem and algorithm. Run this comparison and report results in a table to justify your final strategy choice.
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}')Calibrating Probability Outputs
Class weights and threshold moving both rely on the model's probability scores. If the classifier is poorly calibrated (e.g., SVM with probability=True using Platt scaling), threshold tuning may not work well. Use sklearn.calibration.CalibratedClassifierCV or compare calibration curves with calibration_curve to ensure probabilities are reliable before threshold optimisation.
Quick Check
Test your understanding of class weights and threshold moving from this lesson.
Lesson Recap
In this lesson you learned: class_weight='balanced' penalises minority-class misclassifications proportionally and works without modifying training data, threshold moving adjusts the decision boundary post-training to shift the recall-precision trade-off, and the optimal threshold can be found by maximising F1 over the precision-recall curve or Youden's J over the ROC curve. Next up we save trained models with joblib and pickle for production deployment.
เรียนรู้ Python ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 30
- บทเรียน
- 120
คำถามที่พบบ่อย
บทเรียน “น้ำหนักคลาสและการเลื่อนเกณฑ์ตัดสินใจ” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “น้ำหนักคลาสและการเลื่อนเกณฑ์ตัดสินใจ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Machine Learning Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Machine Learning Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “น้ำหนักคลาสและการเลื่อนเกณฑ์ตัดสินใจ”
ผู้เรียนจะกำหนด class_weight='balanced' ในตัวจำแนกของ sklearn แล้วเลื่อนเกณฑ์ตัดสินใจบนผลลัพธ์ความน่าจะเป็นเพื่อเพิ่มค่าการเรียกคืนสำหรับเหตุการณ์ที่พบได้น้อย คุณปฏิบัติ Machine Learning Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Machine Learning Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Machine Learning Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “น้ำหนักคลาสและการเลื่อนเกณฑ์ตัดสินใจ” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Machine Learning Academy นี้ได้ไหม
ได้ บทเรียน Machine Learning Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การตรวจจับความไม่สมดุล: การกระจายของคลาสและข้อผิดพลาดของค่าพื้นฐาน
- การสุ่มเพิ่มตัวอย่างและ SMOTE
- การสุ่มลดตัวอย่างและ ClusterCentroids
- น้ำหนักคลาสและการเลื่อนเกณฑ์ตัดสินใจ