در پروژههای واقعی یادگیری ماشین، مانند تشخیص تقلب بانکی یا تشخیص بیماریهای نادر، دادههای متوازن یک رؤیا هستند. وقتی 99% دادهها متعلق به یک کلاس باشند، مدلی سادهلوح که همیشه کلاس اکثریت را حدس بزند به دقت 99% میرسد، اما در عمل کاملاً بیاستفاده است. این مسئله یکی از شایعترین چالشهایی است که مهندسان یادگیری ماشین با آن روبهرو میشوند.
این درس به شما نشان میدهد چطور فراتر از توهم معیار دقت (Accuracy) بروید. با معیارهای سنجش استاندارد مثل F1 و AUPRC آشنا میشوید، تفاوت روشهای نمونهافزایی تصادفی و SMOTE را از پایه کدنویسی میکنید و یاد میگیرید با تکنیکهای بدون هزینه مانند وزندهی به کلاسها و تنظیم آستانه تصمیمگیری، عملکرد مدل را به حداکثر برسانید.
تمامی کدهای این درس سبک هستند و برای پیادهسازی آنها به پردازندههای گرافیکی یا منابع ابری گرانقیمت نیازی ندارید. میتوانید همه بخشها را بهصورت محلی روی سیستم شخصی اجرا کرده و برای نصب کتابخانههایی مثل imbalanced-learn و scikit-learn در صورت ناپایداری ارتباط، از میرورهای داخلی PyPI کمک بگیرید.
وقتی 99% از دادههای شما «عادی» هستند، دقت (accuracy) یک دروغ بزرگ است.
اهداف یادگیری
- پیادهسازی SMOTE از صفر و درک تفاوت نمونهافزایی ساختگی با کپیبرداری تصادفی
- ارزیابی دستهبندهای دادههای نامتوازن با معیارهای F1، AUPRC و ضریب همبستگی متیوز (Matthews Correlation Coefficient) بهجای دقت
- مقایسهی وزندهی به کلاسها، تنظیم آستانه و راهبردهای بازنمونهگیری، همراه با انتخاب روش مناسب بر اساس نسبت عدمتوازن
- ساخت یک پایپلاین کامل برای دادههای نامتوازن با ترکیب SMOTE، وزن کلاسها و بهینهسازی آستانه
صورت مسئله
مدلی برای تشخیص تقلب میسازید. دقت مدل به 99.9% میرسد. جشن میگیرید؛ اما بعد متوجه میشوید مدل برای تکتک تراکنشها برچسب «غیرتقلبی» پیشبینی کرده است.
این یک باگ نیست، بلکه وقتی فقط 0.1% از تراکنشها واقعاً تقلب هستند، رفتاری کاملاً منطقی از سوی مدل بهحساب میآید. مدل یاد میگیرد که با حدس زدن همیشگی کلاس اکثریت، خطای کلی به حداقل میرسد. نتیجه از نظر فنی درست است، اما در عمل کاملاً بیاستفاده خواهد بود.
این اتفاق در تمام مسائل دستهبندی دنیای واقعی تکرار میشود: تشخیص بیماری با 1% نرخ مثبت، نفوذ به شبکه با 0.01% حملات، نقصهای تولید صنعتی با 0.5% کالای معیوب، فیلتر هرزنامه با 20% هرزنامه، پیشبینی ریزش مشتریان با 5% ریزش. هرچه کلاس اقلیت پیامدهای حیاتیتری داشته باشد، احتمال وقوع آن کمتر است.
معیار دقت شکست میخورد، چون ارزش یکسانی برای همهی پیشبینیهای درست قائل است. برچسبگذاری درست یک تراکنش عادی و کشف درست یک تقلب، هر دو یک امتیاز به دقت اضافه میکنند. در حالی که کشف تقلب، اصلاً تنها دلیل ساخت این مدل بوده است. ما به معیارها، تکنیکها و استراتژیهای آموزشی نیاز داریم که مدل را وادار کنند به این کلاس نادر اما حیاتی اتنشن (attention) نشان دهد.
مبانی نظری
چرا معیار دقت شکست میخورد؟
مجموعهدادهای با 1000 نمونه را در نظر بگیرید: 990 نمونه منفی و 10 نمونه مثبت. مدلی که همیشه برچسب منفی پیشبینی میکند:
| پیشبینی مثبت | پیشبینی منفی | |
|---|---|---|
| واقعاً مثبت | 0 (TP) | 10 (FN) |
| واقعاً منفی | 0 (FP) | 990 (TN) |
این مدل حتی یک مورد تقلب، بیماری یا نقص را پیدا نکرده است؛ اما دقت آن 99% گزارش میشود. به همین دلیل دقت در مسائل نامتوازن خطرناک است.
معیارهای بهتر
دقت دستهبندی (Precision) = . از کل نمونههایی که مثبت علامت خوردهاند، چند تای آنها واقعاً مثبت هستند؟ دقت بالا یعنی هشدارهای اشتباهِ اندک.
بازیابی یا پوشش (Recall) = . از کل مواردی که واقعاً مثبت بودند، موفق به کشف چند مورد شدیم؟ پوشش بالا یعنی مثبتهای ازدسترفتهی اندک.
امتیاز F1 (F1 Score) = . میانگین همساز (harmonic mean). این معیار نسبت به میانگین حسابی، تفاوت شدید میان دقت و پوشش را بیشتر جریمه میکند.
امتیاز F-beta (F-beta Score) = . اگر باشد، پوشش اهمیت بیشتری دارد و اگر باشد، دقت دستهبندی مهمتر است. معیار F2 در سیستمهای تشخیص تقلب بسیار رایج است (از دست دادن تقلب پیامد بدتری نسبت به یک هشدار اشتباه دارد).
سطح زیر منحنی دقت-پوشش (AUPRC). مشابه AUC-ROC، اما برای دادههای نامتوازن بسیار کارآمدتر است. برای یک دستهبند تصادفی، مقدار AUPRC با نسبت کلاس مثبت برابر است (نه مقدار 0.5 که در ROC میبینیم). این ویژگی باعث میشود روند بهبود مدل بسیار واضحتر دیده شود.
ضریب همبستگی متیوز (Matthews Correlation Coefficient) = . مقداری بین 1- تا 1+ دارد. این معیار فقط وقتی امتیاز بالایی میدهد که مدل در هر دو کلاس عملکرد خوبی داشته باشد. حتی در تفاوت شدید اندازهی کلاسها نیز مقداری متوازن ارائه میدهد.
برای مدلی که بالاتر دیدیم و «همیشه منفی پیشبینی میکرد»: (تعریفنشده، معمولاً 0 در نظر گرفته میشود)، ، ، . این معیارها بهدرستی نشان میدهند که مدل عملاً بیارزش است.
پایپلاین دادههای نامتوازن
تکنیک SMOTE: نمونهافزایی ساختگی کلاس اقلیت
در روش نمونهافزایی تصادفی (Random oversampling)، نمونههای موجود از کلاس اقلیت عیناً تکثیر میشوند. این کار جواب میدهد، اما ریسک بیشبرازش (overfitting) را بالا میبرد؛ چون مدل نقاط دادهی یکسانی را بارها میبیند.
الگوریتم SMOTE نمونههای ساختگی جدیدی میسازد که باورپذیرند، اما کپی دادههای قبلی نیستند. مراحل الگوریتم:
- بهازای هر نمونه از کلاس اقلیت مثل ، تعداد همسایهی نزدیک را از بین سایر نمونههای همان کلاس پیدا کن
- یکی از همسایهها را بهتصادف انتخاب کن
- یک نمونهی جدید روی پارهخط واصل بین و آن همسایه بساز
فرمول: new_sample = x + random(0, 1) * (neighbor - x)
این کار با درونیابی (interpolation) بین نقاط واقعی کلاس اقلیت، نمونههای جدیدی در همان بخش از فضای ویژگیها میسازد، بدون اینکه دادههای قبلی را صرفاً کپی کند.
مقایسهی استراتژیهای نمونهگیری
نمونهافزایی تصادفی (Random Oversampling): تکثیر نمونههای اقلیت برای برابر شدن با تعداد کلاس اکثریت.
- مزایا: ساده، بدون از دست رفتن اطلاعات
- معایب: کپیهای دقیق باعث بیشبرازش میشوند، زمان آموزش را بالا میبرد
نمونهکاهی تصادفی (Random Undersampling): حذف نمونههای اکثریت برای رسیدن به تعداد کلاس اقلیت.
- مزایا: آموزش سریع، ساده
- معایب: دور ریختن دادههای احتمالاً مفید کلاس اکثریت، واریانس بالاتر
الگوریتم SMOTE: تولید نمونههای ساختگی اقلیت از طریق درونیابی.
- مزایا: تولید نقاط دادهی جدید، کاهش بیشبرازش در مقایسه با نمونهافزایی تصادفی
- معایب: احتمال ایجاد نمونههای پرنویز در نزدیکی مرز تصمیمگیری، نادیده گرفتن توزیع کلاس اکثریت
| استراتژی | تغییرات داده | ریسک | زمان استفاده |
|---|---|---|---|
| نمونهافزایی | تکرار دادههای اقلیت | بیشبرازش | مجموعهدادههای کوچک، عدمتوازن متوسط |
| نمونهکاهی | حذف دادههای اکثریت | از دست رفتن اطلاعات | مجموعهدادههای حجیم، نیاز به آموزش سریع |
| SMOTE | افزودن دادههای ساختگی اقلیت | نویز در مرزها | عدمتوازن متوسط، وجود نمونههای اقلیت کافی برای k-NN |
وزندهی به کلاسها (Class Weights)
بهجای دستکاری دادهها، نحوهی برخورد مدل با خطاها را تغییر دهید. بهاشتباه در دستهبندی کلاس اقلیت وزن بیشتری اختصاص دهید.
برای یک مسئلهی دودویی با 950 نمونهی منفی و 50 نمونهی مثبت:
- وزن کلاس منفی =
- وزن کلاس مثبت =
کلاس مثبت وزنی 19 برابر دریافت میکند. جریمهی خطای دستهبندی روی یک نمونهی مثبت بهاندازهی خطای 19 نمونهی منفی هزینه دارد. در نتیجه مدل ناچار میشود به کلاس اقلیت توجه کند.
در رگرسیون لجستیک، این کار تابع زیان (loss) را تغییر میدهد:
که در آن به کلاس نمونهی بستگی دارد.
وزندهی به کلاسها در امید ریاضی معادل نمونهافزایی است، اما بدون نیاز به ساخت نقاط دادهی جدید؛ بنابراین سریعتر است و خطر بیشبرازش ناشی از نمونههای تکراری را هم ندارد.
تنظیم آستانه (Threshold Tuning)
بیشتر دستهبندها احتمال خروجی میدهند. آستانهی پیشفرض 0.5 است: اگر باشد، مثبت پیشبینی میکنند. اما 0.5 انتخابی قراردادی است. وقتی کلاسها نامتوازن هستند، آستانهی بهینه معمولاً مقداری بسیار کمتر است.
مراحل کار:
- مدل را آموزش دهید
- احتمالات پیشبینیشده را روی مجموعهی اعتبارسنجی (validation set) به دست آورید
- آستانه را از 0.0 تا 1.0 تغییر دهید
- معیار F1 (یا معیار انتخابی خود) را در هر آستانه محاسبه کنید
- آستانهای را که معیار شما را بیشینه میکند انتخاب کنید
ممکن است مدل برای یک تراکنش تقلبی احتمال را خروجی دهد. در آستانهی 0.5، این مورد غیرتقلبی دستهبندی میشود؛ اما در آستانهی 0.10 بهدرستی کشف خواهد شد. کالیبره بودن احتمالات اهمیت کمتری نسبت به رتبهبندی (ranking) آنها دارد؛ تا زمانی که تقلب احتمالات بالاتری نسبت به تراکنشهای عادی دریافت کند، همواره آستانهای برای جداسازی آنها وجود خواهد داشت.
یادگیری حساس به هزینه (Cost-Sensitive Learning)
این روش حالت تعمیمیافتهی وزندهی به کلاسها است. بهجای هزینههای یکسان، هزینههای مشخصی را برای اشتباهات مختلف در نظر بگیرید:
| پیشبینی مثبت | پیشبینی منفی | |
|---|---|---|
| واقعاً مثبت | 0 (درست) | |
| واقعاً منفی | 0 (درست) |
از دست دادن یک تراکنش تقلبی (FN) معادل 100 برابر بیشتر از یک هشدار اشتباه (FP) هزینه دارد. مدل تلاش میکند کل هزینه را بهینهسازی کند، نه تعداد کل خطاها را.
اگر بتوانید هزینههای دنیای واقعی را تخمین بزنید، این روش اصولیترین رویکرد ممکن است. هزینهی تشخیص ندادن سرطان با هزینهی یک هشدار اشتباه که به یک بیوپسی اضافه ختم میشود کاملاً متفاوت است. شفاف کردن این هزینهها توازن مناسب را برقرار میکند.
فلوچارت تصمیمگیری
پیادهسازی
گام 1: تولید یک مجموعهدادهی نامتوازن
import numpy as np
def make_imbalanced_data(n_majority=950, n_minority=50, seed=42):
rng = np.random.RandomState(seed)
X_maj = rng.randn(n_majority, 2) * 1.0 + np.array([0.0, 0.0])
X_min = rng.randn(n_minority, 2) * 0.8 + np.array([2.5, 2.5])
X = np.vstack([X_maj, X_min])
y = np.concatenate([np.zeros(n_majority), np.ones(n_minority)])
shuffle_idx = rng.permutation(len(y))
return X[shuffle_idx], y[shuffle_idx]
گام 2: پیادهسازی SMOTE از صفر
def euclidean_distance(a, b):
return np.sqrt(np.sum((a - b) ** 2))
def find_k_neighbors(X, idx, k):
distances = []
for i in range(len(X)):
if i == idx:
continue
d = euclidean_distance(X[idx], X[i])
distances.append((i, d))
distances.sort(key=lambda x: x[1])
return [d[0] for d in distances[:k]]
def smote(X_minority, k=5, n_synthetic=100, seed=42):
rng = np.random.RandomState(seed)
n_samples = len(X_minority)
k = min(k, n_samples - 1)
synthetic = []
for _ in range(n_synthetic):
idx = rng.randint(0, n_samples)
neighbors = find_k_neighbors(X_minority, idx, k)
neighbor_idx = neighbors[rng.randint(0, len(neighbors))]
t = rng.random()
new_point = X_minority[idx] + t * (X_minority[neighbor_idx] - X_minority[idx])
synthetic.append(new_point)
return np.array(synthetic)
گام 3: نمونهافزایی و نمونهکاهی تصادفی
def random_oversample(X, y, seed=42):
rng = np.random.RandomState(seed)
classes, counts = np.unique(y, return_counts=True)
max_count = counts.max()
X_resampled = list(X)
y_resampled = list(y)
for cls, count in zip(classes, counts):
if count < max_count:
cls_indices = np.where(y == cls)[0]
n_needed = max_count - count
chosen = rng.choice(cls_indices, size=n_needed, replace=True)
X_resampled.extend(X[chosen])
y_resampled.extend(y[chosen])
X_out = np.array(X_resampled)
y_out = np.array(y_resampled)
shuffle = rng.permutation(len(y_out))
return X_out[shuffle], y_out[shuffle]
def random_undersample(X, y, seed=42):
rng = np.random.RandomState(seed)
classes, counts = np.unique(y, return_counts=True)
min_count = counts.min()
X_resampled = []
y_resampled = []
for cls in classes:
cls_indices = np.where(y == cls)[0]
chosen = rng.choice(cls_indices, size=min_count, replace=False)
X_resampled.extend(X[chosen])
y_resampled.extend(y[chosen])
X_out = np.array(X_resampled)
y_out = np.array(y_resampled)
shuffle = rng.permutation(len(y_out))
return X_out[shuffle], y_out[shuffle]
گام 4: رگرسیون لجستیک با وزن کلاسها
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-np.clip(z, -500, 500)))
def logistic_regression_weighted(X, y, weights, lr=0.01, epochs=200):
n_samples, n_features = X.shape
w = np.zeros(n_features)
b = 0.0
for _ in range(epochs):
z = X @ w + b
pred = sigmoid(z)
error = pred - y
weighted_error = error * weights
gradient_w = (X.T @ weighted_error) / n_samples
gradient_b = np.mean(weighted_error)
w -= lr * gradient_w
b -= lr * gradient_b
return w, b
def compute_class_weights(y):
classes, counts = np.unique(y, return_counts=True)
n_samples = len(y)
n_classes = len(classes)
weight_map = {}
for cls, count in zip(classes, counts):
weight_map[cls] = n_samples / (n_classes * count)
return np.array([weight_map[yi] for yi in y])
گام 5: تنظیم آستانه
def find_optimal_threshold(y_true, y_probs, metric="f1"):
best_threshold = 0.5
best_score = -1.0
for threshold in np.arange(0.05, 0.96, 0.01):
y_pred = (y_probs >= threshold).astype(int)
tp = np.sum((y_pred == 1) & (y_true == 1))
fp = np.sum((y_pred == 1) & (y_true == 0))
fn = np.sum((y_pred == 0) & (y_true == 1))
if metric == "f1":
precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0
score = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0.0
elif metric == "recall":
score = tp / (tp + fn) if (tp + fn) > 0 else 0.0
elif metric == "precision":
score = tp / (tp + fp) if (tp + fp) > 0 else 0.0
if score > best_score:
best_score = score
best_threshold = threshold
return best_threshold, best_score
گام 6: توابع ارزیابی
def confusion_matrix_values(y_true, y_pred):
tp = np.sum((y_pred == 1) & (y_true == 1))
tn = np.sum((y_pred == 0) & (y_true == 0))
fp = np.sum((y_pred == 1) & (y_true == 0))
fn = np.sum((y_pred == 0) & (y_true == 1))
return tp, tn, fp, fn
def compute_metrics(y_true, y_pred):
tp, tn, fp, fn = confusion_matrix_values(y_true, y_pred)
accuracy = (tp + tn) / (tp + tn + fp + fn)
precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0.0
denom = np.sqrt(float((tp + fp) * (tp + fn) * (tn + fp) * (tn + fn)))
mcc = (tp * tn - fp * fn) / denom if denom > 0 else 0.0
return {
"accuracy": accuracy,
"precision": precision,
"recall": recall,
"f1": f1,
"mcc": mcc,
}
گام 7: مقایسهی تمام روشها
X, y = make_imbalanced_data(950, 50, seed=42)
split = int(0.8 * len(y))
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]
# مدل پایه: بدون تغییر یا اعمال روش خاص
w_base, b_base = logistic_regression_weighted(
X_train, y_train, np.ones(len(y_train)), lr=0.1, epochs=300
)
probs_base = sigmoid(X_test @ w_base + b_base)
preds_base = (probs_base >= 0.5).astype(int)
# Oversampled
X_over, y_over = random_oversample(X_train, y_train)
w_over, b_over = logistic_regression_weighted(
X_over, y_over, np.ones(len(y_over)), lr=0.1, epochs=300
)
preds_over = (sigmoid(X_test @ w_over + b_over) >= 0.5).astype(int)
# SMOTE
minority_mask = y_train == 1
X_minority = X_train[minority_mask]
synthetic = smote(X_minority, k=5, n_synthetic=len(y_train) - 2 * int(minority_mask.sum()))
X_smote = np.vstack([X_train, synthetic])
y_smote = np.concatenate([y_train, np.ones(len(synthetic))])
w_sm, b_sm = logistic_regression_weighted(
X_smote, y_smote, np.ones(len(y_smote)), lr=0.1, epochs=300
)
preds_smote = (sigmoid(X_test @ w_sm + b_sm) >= 0.5).astype(int)
# وزندهی به کلاسها (Class weights)
sample_weights = compute_class_weights(y_train)
w_cw, b_cw = logistic_regression_weighted(
X_train, y_train, sample_weights, lr=0.1, epochs=300
)
probs_cw = sigmoid(X_test @ w_cw + b_cw)
preds_cw = (probs_cw >= 0.5).astype(int)
# تنظیم آستانه (روی بخشی از دادههای اعتبارسنجی از مجموعهی آموزش تنظیم شود، نه مجموعهی آزمون)
val_split = int(0.75 * len(y_train))
X_tr, X_val = X_train[:val_split], X_train[val_split:]
y_tr, y_val = y_train[:val_split], y_train[val_split:]
w_val, b_val = logistic_regression_weighted(
X_tr, y_tr, compute_class_weights(y_tr), lr=0.1, epochs=300
)
probs_val = sigmoid(X_val @ w_val + b_val)
best_thresh, best_f1 = find_optimal_threshold(y_val, probs_val, metric="f1")
preds_thresh = (probs_cw >= best_thresh).astype(int)
فایل کد، تمام این مراحل را در یک اسکریپت واحد اجرا کرده و نتایج را چاپ میکند.
استفاده در عمل
با scikit-learn و imbalanced-learn، اجرای این تکنیکها در چند خط خلاصه میشود:
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, f1_score
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y)
model_weighted = LogisticRegression(class_weight="balanced")
model_weighted.fit(X_train, y_train)
print(classification_report(y_test, model_weighted.predict(X_test)))
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)
model_smote = LogisticRegression()
model_smote.fit(X_resampled, y_resampled)
print(classification_report(y_test, model_smote.predict(X_test)))
pipeline = Pipeline([
("smote", SMOTE()),
("model", LogisticRegression(class_weight="balanced")),
])
pipeline.fit(X_train, y_train)
print(classification_report(y_test, pipeline.predict(X_test)))
پیادهسازی از صفر نشان داد هر تکنیک دقیقاً چه کار میکند: SMOTE صرفاً یک درونیابی k-NN روی کلاس اقلیت است. وزندهی به کلاسها یعنی ضرب کردن یک ضریب در تابع زیان (loss). تنظیم آستانه هم یک حلقهی for ساده روی مقادیر مرزی است؛ هیچ جادویی در کار نیست.
تحویل پروژه
از این درس خروجی زیر به دست میآید:
outputs/skill-imbalanced-data.md-- یک چکلیست تصمیمگیری برای مواجهه با مسائل دستهبندی نامتوازن
تمرینها
-
الگوریتم Borderline-SMOTE: پیادهسازی SMOTE را به شکلی تغییر دهید که دادههای ساختگی فقط برای نمونههایی از کلاس اقلیت تولید شوند که نزدیک به مرز تصمیمگیری هستند (نمونههایی که k همسایهی نزدیک آنها شامل نمونههایی از کلاس اکثریت نیز هست). روی مجموعهدادهای با کلاسهای همپوشان، نتایج را با SMOTE استاندارد مقایسه کنید.
-
بهینهسازی ماتریس هزینه: یادگیری حساس به هزینه را طوری پیادهسازی کنید که ماتریس هزینه یک پارامتر باشد. تابعی بنویسید که یک ماتریس هزینه بگیرد و پیشبینیهای بهینهای برگرداند که هزینهی مورد انتظار را به حداقل برسانند. مدل را با نسبتهای مختلف هزینه (1:10، 1:100، 1:1000) آزمایش کنید و نمودار تغییرات توازن بین دقت و پوشش را رسم کنید.
-
کالیبراسیون آستانه: روش Platt scaling را پیادهسازی کنید (یک رگرسیون لجستیک روی خروجیهای خام مدل برازش دهید تا احتمالات کالیبرهشده به دست آیند). منحنی دقت-پوشش را قبل و بعد از کالیبراسیون مقایسه کنید. نشان دهید کالیبراسیون رتبهبندی را تغییر نمیدهد (AUC ثابت میماند) اما مقادیر احتمالات را معنادارتر میکند.
-
مدلهای تجمیعی با بگینگ متوازن (Balanced Bagging): چندین مدل آموزش دهید که هر کدام روی یک نمونهی بوتاسترپ متوازن (تمام دادههای اقلیت + زیرمجموعهای تصادفی از اکثریت) آموزش دیدهاند. میانگین پیشبینی آنها را بگیرید. این رویکرد را با یک مدل تکی مجهز به SMOTE مقایسه کنید. هم کارایی و هم واریانس مدلها را در چند بار اجرا بسنجید.
-
آزمایش نسبت عدمتوازن: یک مجموعهدادهی متوازن را بردارید و نسبت عدمتوازن را مرحله به مرحله افزایش دهید (50/50، 70/30، 90/10، 95/5، 99/1). در هر نسبت، مدل را با و بدون SMOTE آموزش دهید. نمودار تغییرات F1 بر حسب نسبت عدمتوازن را برای هر دو حالت رسم کنید. در چه نسبتی SMOTE تأثیر چشمگیر خود را نشان میدهد؟
اصطلاحات کلیدی
| اصطلاح | در گفتار عامیانه | معنای دقیق فنی |
|---|---|---|
| عدمتوازن کلاسها (Class imbalance) | «یکی از کلاسها نمونههای خیلی بیشتری دارد» | چولگی شدید توزیع کلاسها در مجموعهداده، بهطوری که مدل به نفع کلاس اکثریت سوگیری پیدا میکند |
| الگوریتم SMOTE | «نمونهافزایی ساختگی» | ساخت نمونههای جدید برای کلاس اقلیت از طریق درونیابی بین نمونههای اقلیت موجود و k همسایهی اقلیت نزدیک به آنها |
| وزندهی به کلاسها (Class weights) | «گرانتر کردن خطا روی کلاسهای نادر» | ضرب کردن وزنهای خاص هر کلاس در تابع زیان (loss) تا مدل جریمهی سنگینتری برای خطای دستهبندی کلاس اقلیت در نظر بگیرد |
| تنظیم آستانه (Threshold tuning) | «جابهجا کردن مرز تصمیمگیری» | تغییر حد آستانهی احتمال دستهبندی از مقدار پیشفرض 0.5 به مقداری که معیار مورد نظر را بهینه کند |
| توازن دقت و پوشش (Precision-recall tradeoff) | «نمیشود هر دو را با هم داشت» | پایین آوردن آستانه باعث کشف مثبتهای بیشتر میشود (پوشش بالاتر)، اما هشدارهای اشتباه بیشتری هم تولید میکند (دقت پایینتر) و برعکس |
| سطح زیر منحنی دقت-پوشش (AUPRC) | «سطح زیر منحنی PR» | خلاصهسازی منحنی دقت-پوشش در قالب یک عدد؛ برای دادههای با عدمتوازن شدید بسیار آموزندهتر از AUC-ROC است |
| ضریب همبستگی متیوز (Matthews Correlation Coefficient) | «معیار متوازن» | ضریب همبستگی بین برچسبهای پیشبینیشده و واقعی که فقط در صورت عملکرد مناسب مدل روی هر دو کلاس امتیاز بالایی میدهد |
| یادگیری حساس به هزینه (Cost-sensitive learning) | «هزینهی اشتباهات مختلف یکسان نیست» | وارد کردن هزینههای واقعی اشتباهات دستهبندی در هدف آموزش، تا مدل بهجای تعداد خطاها، کل هزینه را بهینهسازی کند |
| نمونهافزایی تصادفی (Random oversampling) | «کپی کردن نمونههای اقلیت» | تکرار نمونههای کلاس اقلیت برای برابر شدن تعداد کلاسها؛ روشی ساده اما همراه با ریسک بیشبرازش روی نمونههای تکراری |
برای مطالعهی بیشتر
- مقاله SMOTE: Synthetic Minority Over-sampling Technique (چاوالا و همکاران، 2002) -- مقالهی اصلی معرفی SMOTE که همچنان پرتکرارترین مرجع در یادگیری از دادههای نامتوازن است
- مقاله Learning from Imbalanced Data (هه و گارسیا، 2009) -- مروری جامع بر روشهای نمونهگیری، یادگیری حساس به هزینه و رویکردهای الگوریتمی
- مستندات کتابخانه imbalanced-learn -- کتابخانهی پایتون حاوی انواع مشتقات SMOTE، استراتژیهای نمونهکاهی و قابلیت ادغام با پایپلاینها
- مقاله The Precision-Recall Plot Is More Informative than the ROC Plot (سایتو و رمسمایر، 2015) -- بررسی زمان و دلایل برتری منحنیهای PR نسبت به منحنیهای ROC در مسائل نامتوازن
