
خیلی وقت ها داده اولیه ما ممکنه نامتوازن باشه . این توضیح با Outlier متفاوته ما در Outlier داده هایی داشتیم که از رنج عادی خارج بودن , مثلا درآمد همه بین ۱ میلیون تا ۱۰۰ میلیون بود ولی یهو یک نفر ۱۰۰ میلیارد درآمد داشت .
ولی در Imbalanced Data ما با مقدار غیرعادی طرف نیستیم؛
بلکه با تعداد غیرمتعادل در بین کلاسها مواجهیم.
خلاصه:
Outlier → مقدارش غیرعادیه.
Imbalanced Data → تعدادش غیرمتعادل بین کلاسهاست.
مشکلش چیه ؟
وقتی با دادههای نامتوازن (Imbalanced) طرفیم، داریم با مدلی کار میکنیم که خیلی به داده اکثریت اتکا میکنه و ممکنه تو پیشبینی دستهی اقلیت (مهم!) اشتباههای جدی بکنه.
به زبان ساده : داده ما اونقدر مطمئن و منصفانه نیست که بشه به پیشبینیهاش اعتماد کامل کرد.
مثلا اگر ما یک دیتاست داشته باشیم که نصف تارگت مثبت و نصف اون منفی باشن اون موقع ما دیتاست Balance داریم در غیر این صورت ImBalance
یک نکته مهم :
ممکنه نتیجه Accuracy مدل شما خیلی عالی باشه و کار کنه ولی متوجه نشید که داده های ناتراز چه بلایی سر نتیجه مدل اورده , پس حواستون باشه که این مقاله بسیار بسیار مهمه !
مسئلهی نامتوازن بودن دادهها (Imbalanced Data) بیشتر توی Classification خودش رو نشون میده، ولی در رگرسیون هم دادههایی داریم که مشکلساز میشن. البته توی رگرسیون دیگه اسمش Imbalanced نیست، بلکه بیشتر به شکل Skewed Distribution (توزیع کجشده)، یا وجود Outlierهای پراثر مطرح میشه.
حالا در ادامه یاد میگیریم که داده های نامتوازن رو هم در رگرسیون و هم در Classification هندل کنیم .
همانطور که گفتم داده نامتوازن در رگرسیون شکل متفاوتی با کلسیفیکیشن داره . ابتدا با مفهوم چولگی یا Skewed آشنا بشید :

در رگرسیون، ممکن است مقادیر هدف در بازههای خاصی تمرکز داشته باشند و نمونههای پرت (outliers) یا دادههای نادر در بعضی بازهها وجود داشته باشد.
داده های ما اگر به صورت توزیع نورمال نباشند قطعا به مشکل میخوریم و یک داده اشتباه در نهایت بدست میاریم :
stats.skew(data['Target'])
که اگر مثبت باشه چولگی به راست و منفی باشه چولگی به چپ و ۰ باشه یعنی نرمال
در رگرسیون شما به ۳ روش میتونید داده ها رو متوازن کنید که هر ۳ در کد زیر نوشته شده :
۱-تبدیل لگاریتمی (اضافه کردن ۱ برای مثبت کردن داده ها)
۲-تبدیل ریشه دوم
۳-تبدیل BoxCox
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from scipy import stats np.random.seed(42) n_samples = 1000 X = np.random.exponential(scale=2, size=n_samples) y = 3 * X + np.random.normal(0, 1, n_samples) data = pd.DataFrame({'Feature': X, 'Target': y})
print(stats.skew(data['Target']))
در قسمت بالا کد دیتاست تستی خودمون رو میبینید , و نتیجه چولگی : 1.830
حالا میام به هر ۳ روش چولگی رو هندل میکنم :
#z تبدیل به روش لگاریتمی data['Target_log'] = np.log1p(data['Target'] - data['Target'].min() + 1) # تبدیل ریشه دوم data['Target_sqrt'] = np.sqrt(data['Target'] - data['Target'].min() + 1) # تبدیل Box-Cox data['Target_boxcox'], _ = stats.boxcox(data['Target'] - data['Target'].min() + 1)
با این کد میتونید نتیجه و حالت اولیه رو به صورت تصویری رسم کنید :
plt.figure(figsize=(12, 8)) plt.subplot(2, 2, 1) sns.histplot(data['Target'], kde=True) plt.title('Before Balancing') plt.subplot(2, 2, 2) sns.histplot(data['Target_log'], kde=True) plt.title('Balancing in Log Scale') plt.subplot(2, 2, 3) sns.histplot(data['Target_sqrt'], kde=True) plt.title('Balancing in Sqrt Scale') plt.subplot(2, 2, 4) sns.histplot(data['Target_boxcox'], kde=True) plt.title('Balancing in Box-Cox Scale') plt.tight_layout() plt.show()
بعد از انجام Balancing میتونیم نتیجه رو به شکل زیر ببینیم :

توی این مقاله به نظرم خیلی عالی تونسته این مبحث رو توضیح بده .
برای هندل کردن داده های نامتوازن در کلسیفیکیشن ۳ روش اصلی داریم :
روش اول : Over Sampling (نمونهبرداری از اکثریت ها): تعداد نمونههای کلاس اقلیت رو افزایش میدیم.
یه روش معروف برای این کار SMOTE هست که دادههای مصنوعی برای کلاس اقلیت تولید میکنه.
روش دوم : Under Sampling (نمونهبرداری از اقلیت ها): تعداد نمونههای کلاس اکثریت رو کاهش میدیم تا تعادل برقرار بشه.
روش سوم : Class Weighting (وزندهی به کلاسها): به کلاس اقلیت وزن بیشتری میدیم تا مدل بهش توجه بیشتری کنه، بدون اینکه دادهها رو تغییر بدیم.

dataframe['Target'].value_counts()
خب اول یک دیتاست آماده میکنیم :
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, confusion_matrix from imblearn.over_sampling import SMOTE from imblearn.under_sampling import RandomUnderSampler np.random.seed(42) X, y = make_classification(n_classes=2, class_sep=2, weights=[0.9, 0.1], n_informative=2, n_redundant=0, n_samples=1000, n_features=2, n_clusters_per_class=1, random_state=42 ) data = pd.DataFrame({'Feature1': X[:, 0], 'Feature2': X[:, 1], 'Target': y}) data['Target'].value_counts()
نتیجه :

895 تا ۰ داریم
105 تا ۱ داریم .
یعنی رسما داده نامتوازن داریم و تقریبا ۹۰ درصد تفاوت بینشون وجود داره (تصویر اول صفحه) .
حالا میریم اوکیشون کنیم :
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # Base Model model_base = LogisticRegression(random_state=42) model_base.fit(X_train, y_train) y_pred_base = model_base.predict(X_test) print(classification_report(y_test, y_pred_base)) # Over Sampling (SMOTE) smote = SMOTE(random_state=42) X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train) model_smote = LogisticRegression(random_state=42) model_smote.fit(X_train_smote, y_train_smote) y_pred_smote = model_smote.predict(X_test) print("\nSMOTE (Over Sampling):") print(classification_report(y_test, y_pred_smote)) # Under Sampling undersampler = RandomUnderSampler(random_state=42) X_train_under, y_train_under = undersampler.fit_resample(X_train, y_train) model_under = LogisticRegression(random_state=42) model_under.fit(X_train_under, y_train_under) y_pred_under = model_under.predict(X_test) print("\nUnder Sampling:") print(classification_report(y_test, y_pred_under))
# Class Weighting model_weighted = LogisticRegression(class_weight='balanced', random_state=42) model_weighted.fit(X_train, y_train) y_pred_weighted = model_weighted.predict(X_test) print("\nClass Weighting:") print(classification_report(y_test, y_pred_weighted)) plt.figure(figsize=(15, 10)) plt.subplot(2, 2, 1) cm_base = confusion_matrix(y_test, y_pred_base) sns.heatmap(cm_base, annot=True, fmt='d', cmap='Blues', cbar=False) plt.title('Confusion Matrix') plt.xlabel('Predicted') plt.ylabel('Data') plt.subplot(2, 2, 2) cm_smote = confusion_matrix(y_test, y_pred_smote) sns.heatmap(cm_smote, annot=True, fmt='d', cmap='Blues', cbar=False) plt.title('Confusion Matrix (SMOTE)') plt.xlabel('Predicted') plt.ylabel('Data') plt.subplot(2, 2, 3) cm_under = confusion_matrix(y_test, y_pred_under) sns.heatmap(cm_under, annot=True, fmt='d', cmap='Blues', cbar=False)
plt.title('Confusion Matrix (Under Sampling)') plt.xlabel('Predicted') plt.ylabel('Data') plt.subplot(2, 2, 4) cm_weighted = confusion_matrix(y_test, y_pred_weighted) sns.heatmap(cm_weighted, annot=True, fmt='d', cmap='Blues', cbar=False) plt.title('Confusion Matrix (Class Weighting)') plt.xlabel('Predicted') plt.ylabel('Data') plt.tight_layout() plt.show()
در نهایت :
ما در نتیجه معیار های Precission , Recall , f1-score , support , Confusion matrix رو داریم که در این پست بهشون اشاره شده .
Precision (دقت): درصد پیشبینیهای درست برای یک کلاس از کل پیشبینیهایی که برای اون کلاس انجام شده
Recall (بازخوانی یا Sensitivity): درصد نمونههای واقعی یک کلاس که درست پیشبینی شدن. یعنی:
F1-Score: میانگین هارمونیک Precision و Recall. این معیار وقتی مهمه که بخوای تعادل بین دقت و بازخوانی رو بررسی کنی
Support: تعداد نمونههای واقعی هر کلاس در مجموعه تست.

همانطور که میبینید زیاد تفاوت خاصی بین UnderSampling , OverSampling و حالت اولیه نداشت .
چرا ؟ چون این یک دیتای تسته و زیاد نتونسته IMBalanced های خوبی رو برامون طراحی کنه
به همین خاطر زیاد تفاوتی نداره .
در قسمت های بعدی و پروژه های بعدی با داده های واقعی سر و کار داریم و اونجاست که بیشتر متوجه لزوم مبحث Sampling میشیم .
این هم تصویر Confusion Matrix تارگت که در این پست کامل بهش اشاره شده :
