آنچه در این مقاله میخوانید [پنهانسازی]
در ارزیابی مدل روی داده نامتوازن، تکیه بر Accuracy معمولا گمراهکننده است؛ چون یک مدل میتواند با حدس زدن همیشگی اکثریت، Accuracy بالا بگیرد اما اقلیت مهم را از دست بدهد. معیارهای دقیقتری مثل Precision، Recall، F1، PR-AUC، Balanced Accuracy و MCC به همراه انتخاب آستانه تصمیم بر اساس هزینه خطا، تصویر واقعبینانهتری از عملکرد میدهند.
چرا Accuracy روی داده نامتوازن گمراهکننده است؟
اگر فقط 1 درصد نمونهها مثبت باشند، مدلی که همیشه «منفی» بگوید 99 درصد Accuracy میگیرد، در حالی که هیچ مورد مثبتی را پیدا نمیکند. در مسائل حساس مثل تقلب، تشخیص بیماری یا خطای تولید، این مدل عملا بیاستفاده است. مشکل اینجاست که Accuracy به توزیع کلاسها و هزینه نامتقارن خطا بیتوجه است و خطاهای مهم (False Negative یا False Positive) را همارزش میگیرد.
معیارهای مناسبتر برای داده نامتوازن
Confusion Matrix: نقطه شروع تصمیمگیری
Confusion Matrix تعداد True Positive، False Positive، True Negative و False Negative را نشان میدهد. با نگاه به این چهارخانه میتوانید ببینید مدل دقیقا کجا اشتباه میکند و آیا نوع خطا با هدف کسبوکاری شما همخوان است یا نه.
Precision، Recall و F1
- Precision: از بین پیشبینیهای مثبت، چند درصد واقعا مثبتاند؟ وقتی هزینه هشدار اشتباه بالاست (مثلا بن شدن کاربر)، Precision مهم است.
- Recall: از بین مثبتهای واقعی، چند درصد را کشف کردیم؟ وقتی جا افتادن یک مثبت خطرناک است (مثلا سرطان)، Recall اولویت دارد.
- F1: میانگین هماهنگ Precision و Recall؛ وقتی هر دو مهماند و میخواهید مصالحهای منصفانه داشته باشید.
ROC-AUC در برابر PR-AUC
ROC-AUC احتمال رتبهبندی درست مثبتها بالاتر از منفیها را میسنجد؛ اما در نامتوازنهای شدید میتواند خوشبینانه باشد چون نرخ مثبت کمه. PR-AUC (مساحت زیر منحنی Precision-Recall) حساستر به عملکرد روی کلاس مثبت است و در بسیاری از مسائل نامتوازن انتخاب بهتری برای مقایسه مدلهاست.
Balanced Accuracy، MCC و Kappa
- Balanced Accuracy: میانگین حساسیت (Recall) و ویژگی (Specificity). نسبت به توزیع کلاسها مقاومتر از Accuracy ساده است.
- MCC (Matthews Correlation Coefficient): همبستگی بین پیشبینی و واقعیت؛ عددی بین -1 تا 1 که تعادل همه خانههای ماتریس اغتشاش را در نظر میگیرد.
- Cohen’s Kappa: توافق مدل با برچسبهای واقعی فراتر از شانس. برای برآورد «بهتر از حد تصادف» بودن مفید است.
آستانه تصمیم و هزینه خطا
مدلهای احتمالاتی خروجی بین 0 و 1 میدهند و پیشفرض 0.5 همیشه منطقی نیست. آستانه را باید با توجه به هزینه نسبی FP و FN تنظیم کرد. برای مثال اگر FN (از دست رفتن یک تقلب) 10 برابر گرانتر از FP است، آستانه را پایین میآورید تا Recall بالا برود، حتی اگر Precision کمی افت کند.
چه معیاری را چه زمانی انتخاب کنیم؟
| معیار | چه زمانی مناسب است |
|---|---|
| Recall | هزینه از دست دادن مثبتها بسیار بالاست (پزشکی، امنیت، تقلب) |
| Precision | هزینه هشدار اشتباه بالاست (تحریم حساب، تماس تیم بررسی) |
| F1 | توازن بین Precision و Recall مهم است |
| PR-AUC | مقایسه مدلها وقتی کلاس مثبت نادر است |
| Balanced Accuracy | نیاز به نسخهای مقاومتر از Accuracy |
| MCC | تصویر فشرده و متوازن از عملکرد کلی |
| ROC-AUC | رتبهبندی کلی خوب است، اما داده خیلی نامتوازن نیست |
چک لیست اجرایی ارزیابی روی داده نامتوازن
- تفکیک داده با Stratified Split تا نسبت کلاسها در Train/Val/Test حفظ شود.
- انتخاب معیار بر اساس هزینه خطا قبل از آموزش مدل.
- گزارش چندمعیاره: Confusion Matrix، Precision/Recall/F1، PR-AUC و در صورت نیاز Balanced Accuracy یا MCC.
- تنظیم آستانه تصمیم روی مجموعه اعتبارسنجی (نه روی Test) با تکیه بر منحنی PR یا تابع هزینه.
- اگر Resampling انجام میدهید (SMOTE/Undersampling)، فقط داخل فیت هر Fold و بعد از Split تا نشت اطلاعات رخ ندهد.
- در صورت استفاده از هزینه نامتقارن، کلاسوزنها (class_weight) یا loss مناسب را به مدل اعمال کنید.
- در مسائل احتمالاتی، کالیبراسیون احتمالها (Platt/Isotonic) را برای آستانهگذاری دقیقتر در نظر بگیرید.
مثال عملی با Python (scikit-learn)
در این مثال یک مجموعه بسیار نامتوازن میسازیم، مدل Logistic Regression با وزندهی به کلاسها آموزش میدهیم، چند معیار کلیدی را محاسبه و آستانه تصمیم را بهینه میکنیم.
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 (
accuracy_score, precision_score, recall_score, f1_score,
roc_auc_score, average_precision_score, confusion_matrix,
matthews_corrcoef, balanced_accuracy_score, precision_recall_curve
)
# 1) ساخت داده نامتوازن
X, y = make_classification(
n_samples=10000, n_features=20, n_informative=3, n_redundant=2,
weights=[0.97, 0.03], flip_y=0.0, random_state=42
)
# 2) Split با حفظ نسبت کلاسها
X_temp, X_test, y_temp, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
X_train, X_val, y_train, y_val = train_test_split(
X_temp, y_temp, test_size=0.25, stratify=y_temp, random_state=42
) # در مجموع: 60% Train، 20% Val، 20% Test
# 3) مدل با وزندهی به کلاسها
clf = LogisticRegression(class_weight='balanced', max_iter=1000, random_state=42)
clf.fit(X_train, y_train)
# 4) ارزیابی روی Val
probs_val = clf.predict_proba(X_val)[:, 1]
pred_05 = (probs_val >= 0.5).astype(int)
acc = accuracy_score(y_val, pred_05)
prec = precision_score(y_val, pred_05, zero_division=0)
rec = recall_score(y_val, pred_05)
f1 = f1_score(y_val, pred_05)
roc = roc_auc_score(y_val, probs_val)
pr_auc = average_precision_score(y_val, probs_val)
bal_acc = balanced_accuracy_score(y_val, pred_05)
mcc = matthews_corrcoef(y_val, pred_05)
print("Accuracy@0.5:", round(acc, 4))
print("Precision@0.5:", round(prec, 4))
print("Recall@0.5:", round(rec, 4))
print("F1@0.5:", round(f1, 4))
print("ROC-AUC:", round(roc, 4))
print("PR-AUC:", round(pr_auc, 4))
print("Balanced Accuracy@0.5:", round(bal_acc, 4))
print("MCC@0.5:", round(mcc, 4))
print("Confusion Matrix@0.5:\\n", confusion_matrix(y_val, pred_05))
# 5) انتخاب آستانه با حداکثر F1
precisions, recalls, thresholds = precision_recall_curve(y_val, probs_val)
prec_t, rec_t, thr_t = precisions[:-1], recalls[:-1], thresholds
f1_scores = 2 * prec_t * rec_t / (prec_t + rec_t + 1e-8)
i_best = np.argmax(f1_scores)
best_thr = thr_t[i_best]
pred_best = (probs_val >= best_thr).astype(int)
print("Best threshold by F1:", round(float(best_thr), 4))
print("F1@best_thr:", round(f1_score(y_val, pred_best), 4))
print("Precision@best_thr:", round(precision_score(y_val, pred_best, zero_division=0), 4))
print("Recall@best_thr:", round(recall_score(y_val, pred_best), 4))
# 6) مثال آستانه بر اساس حداقل Recall هدف (مثلا 0.9)
target_recall = 0.9
idx = np.where(rec_t >= target_recall)[0]
if len(idx) > 0:
thr_high_recall = thr_t[idx[-1]] # آستانهای که هنوز Recall هدف را پوشش دهد
pred_hr = (probs_val >= thr_high_recall).astype(int)
print("Threshold for Recall>=0.9:", round(float(thr_high_recall), 4))
print("Precision@HR:", round(precision_score(y_val, pred_hr, zero_division=0), 4))
print("Recall@HR:", round(recall_score(y_val, pred_hr), 4))
else:
print("Recall 0.9 قابل دستیابی نیست؛ داده/مدل را بازبینی کنید.")
# 7) در نهایت، پس از قفل کردن آستانه روی Val، فقط یکبار روی Test ارزیابی کنید.
چگونه نتیجه را تفسیر کنیم؟
اگر Accuracy@0.5 بالا اما Recall پایین باشد، یعنی مدل مثبتهای واقعی را از دست میدهد. افزایش PR-AUC نشان میدهد که توزیع امتیازات احتمال برای مثبتها و منفیها جداشدنیتر شده است. بعد از انتخاب آستانه (براساس F1 یا هزینه خطا)، مقایسه Confusion Matrix قبل و بعد از آستانه جدید به شما میگوید توازن Precision/Recall چگونه تغییر کرده است.
اشتباهات رایج و راه پیشگیری
- بهینهسازی فقط برای Accuracy: همیشه حداقل Precision، Recall، F1 و یکی از PR-AUC یا MCC را گزارش دهید.
- Resampling قبل از Split: ابتدا Split استراتیفایشده انجام دهید، سپس داخل هر Fold/Train فقط روی بخش آموزش Resample کنید.
- انتخاب آستانه روی Test: آستانه را روی Val تنظیم کنید و فقط یکبار روی Test نهایی گزارش دهید.
- تکیه بر ROC-AUC در نامتوازنهای شدید: برای مقایسه مدلها از PR-AUC هم استفاده کنید.
- نادیده گرفتن کالیبراسیون: اگر به احتمالهای قابل اتکا برای تصمیمگیری نیاز دارید، از کالیبراسیون (Platt/Isotonic) بهره ببرید.
- گزارش میانگین کلی بدون جزئیات: همواره Confusion Matrix و معیارهای کلاس مثبت را نمایش دهید.
گام بعدی چیست؟
پیش از آموزش، معیار و هزینه خطا را روشن کنید، داده را با Split استراتیفایشده ارزیابی کنید، آستانه تصمیم را روی Val تنظیم کنید و در گزارش نهایی، تصویر چندبعدی از عملکرد (PR-AUC، F1، Confusion Matrix و در صورت نیاز MCC/Balanced Accuracy) ارائه دهید. این رویکرد، ریسک خوشبینی کاذب Accuracy را حذف میکند و مدل قابل اتکاتری برای شرایط واقعی میسازد.







