متعادل کردن داده در پایتون — بخش اول: وزن دهی دسته ها
یکی از مباحث مهم و اولیه در یادگیری ماشین، متعادل کردن داده است. در این آموزش از مجله تم آف، با وزن دهی دسته ها در پایتون آشنا میشویم که یکی از راههای متعادل کردن داده است. روش دیگر متعادل کردن دادهها در بخش دوم این آموزش مطرح شده است و به تغییر مجموعه داده اختصاص دارد.
دلایل عدم تعادل دادهها
در مسائل طبقهبندی، تعداد دادههای موجود در هر دسته حائز اهمیت است و به هم خوردن این تعادل، باعث مشکلاتی در مدلسازی میشود. دو دلیل عمده برای این اتفاق وجود دارد:
- ممکن است مدل تنها دستههایی با تعداد دادههای بیشتر را آموزش دیده باشد.
- ممکن است مدل برای تمامی دستهها از دقت خوبی برخوردار باشد، ولی با گرفتن دادههای جدید که در اقلیّت هستند، همان دقت را نداشته باشد.
اما دلیل به وجود آمدن مشکل اول چیست؟ با افزایش سهم یک دسته از کل، اثرگذاری آن دسته در دقت و خطا نیز افزایش مییابد، به همین دلیل، مدل سعی میکند تا آن دسته را با دقت بالاتری پیشبینی کند که در نهایت منجر به افزایش دقت در کل مجموعه داده و کاهش دقت در دستههای کوچک میشود.
برای مثال، فرض کنید یک مسئله طبقهبندی با 4 دسته وجود دارد که سهم هر دسته از کل مجموعه داده به شرح زیر است:
سهم از کل | دسته |
9% | 1 |
12% | 2 |
71% | 3 |
8% | 4 |
اگر یک مدل Dummy (مدلی که بدون توجه با داده ورودی، تنها Label مربوط به دسته غالب را در خروجی میدهد) روی این مجموعه داده آموزش دهیم، خیلی ساده به دقت %71 میرسیم که در ظاهر عدد بسیار مناسبی است. اما با بررسی Recall و F1 Score مربوط به سایر دستهها، متوجه اشکال مدل میشویم.
به این ترتیب، نیاز است تا این مشکل برطرف شود. به طور کلی با دو استراتژی متفاوت میتوانیم این مشکل را رفع کنیم:
- وزندهی به هر دسته با توجه به تعداد دادههای آن
- تغییر مجموعه داده:
- حذف بخشی از دادههای دستههای موجود در اکثریت
- افزایش دادههای دستههای موجود در اقلیت
برای یادگیری برنامهنویسی با زبان پایتون، پیشنهاد میکنیم به مجموعه آموزشهای مقدماتی تا پیشرفته پایتون تم آف مراجعه کنید که لینک آن در ادامه آورده شده است.
- برای مشاهده مجموعه آموزشهای برنامه نویسی پایتون (Python) — مقدماتی تا پیشرفته + اینجا کلیک کنید.
متعادل کردن داده در پایتون: روش وزن دهی دسته ها
در این مطلب میخواهیم از روش اول استفاده کنیم.
به این منظور وارد محیط برنامهنویسی شده و کتابخانههای مورد نیاز را فراخوانی میکنیم:
import numpy as np
import sklearn.metrics as met
import sklearn.datasets as dt
import sklearn.linear_model as li
import sklearn.model_selection as ms
هر فراخوانی به ترتیب برای موارد زیر مورد استفاده قرار خواهد گرفت:
- کار و عملیات روی آرایهها
- محاسبه معیارهای سنجش مدلها
- فراخوانی داده
- استفاده از مدلهای خطی
- تقسیم داده
حال Random State را تنظیم میکنیم:
np.random.seed(0)
برای شروع، ابتدا باید یک مجموعه داده انتخاب و آن را فراخوانی کنیم. به این منظور از مجموعه داده IRIS استفاده خواهیم کرد:
IRIS = dt.load_iris()
X = IRIS.data
Y = IRIS.target
TN = IRIS.target_names
به این ترتیب، ویژگیهای ورودی، ویژگیهای خروجی و اسم دستهها استخراج میشود.
حال تعداد دادههای هر کلاس را محاسبه میکنیم:
N = {tn: Y[Y == i].size for i, tn in enumerate(TN)}
به این ترتیب، تعداد دادههای هر دسته با کلید مربوط به اسم مروبوطه در دیکشنری N ذخیره میشود. با خروجی گرفتن از N خواهیم داشت:
{'setosa': 50, 'versicolor': 50, 'virginica': 50}
به این ترتیب، میبینیم که دستهها سهم یکسانی از مجموعه داده را شامل میشود.
حال مجموعه دادههای آموزش و آزمایش را جدا میکنیم:
trX, teX, trY, teY = ms.train_test_split(X, Y,
train_size=0.7,
random_state=0)
یک مدل «رگرسیون لجستیک» (Logistic Regression) روی مجموعه داده آموزش میدهیم:
Model = li.LogisticRegression(random_state=0)
Model.fit(trX, trY)
دقت مدل را برای مجموعه داده آموزش و آزمایش محاسبه و نمایش میدهیم:
trAc = 100 * Model.score(trX, trY)
teAc = 100 * Model.score(teX, teY)
print(f'Train Accuracy: {round(trAc, 2)} %')
print(f'Test Accuracy: {round(teAc, 2)} %')
که خواهیم داشت:
trAc = 98.09 % teAc = 97.77 %
به این ترتیب مشاهده میکنیم که دقت بسیار مناسب است.
حال Classification Report را محاسبه و نشان میدهیم:
tePr = Model.predict(teX)
teCR = met.classification_report(teY, tePr, target_names=TN)
print(f'Test Classification Report:n{teCR}')
که در این بخش نیز خروجی زیر حاصل میشود:
Test Classification Report: precision recall f1-score support setosa 1.00 1.00 1.00 16 versicolor 1.00 0.94 0.97 18 virginica 0.92 1.00 0.96 11 accuracy 0.98 45 macro avg 0.97 0.98 0.98 45 weighted avg 0.98 0.98 0.98 45
و به این ترتیب مشاهده میکنیم که مقدار Recall و F1 Score برای تمامی دستهها مناسب است و بنابراین در این حالت مشکلی وجود ندارد.
حال مقداری عدم تعادل در اندازه دستهها ایجاد میکنیم. برای این منظور، 50 داده از دسته اول، 10 داده از دسته دوم و 15 داده از دسته سوم انتخاب میکنیم.
حال شماره دادهها را به شکل زیر تعیین میکنیم:
ind = list(range(0, 50)) + list(range(50, 60)) + list(range(100, 115))
حال ویژگیهای ورودی و خروجی را نیز براساس ind دوباره تعریف میکنیم:
X = IRIS.data[ind]
Y = IRIS.target[ind]
حال اگر دیکشنری N را نمایش دهیم، به اعداد زیر میرسیم:
{'setosa': 50, 'versicolor': 10, 'virginica': 15}
به این ترتیب، دستههای نامتعادل ایجاد شدند.
حال اگر مدل را آموزش دهیم به دقتهای زیر میرسیم:
Train Accuracy: 100.0 % Test Accuracy: 78.26 %
که به نظر میرسد مدل از شرایط نسبتاً مناسبی برخوردار است. حال اگر teCR را نمایش دهیم:
Test Classification Report: precision recall f1-score support setosa 0.92 1.00 0.96 12 versicolor 0.67 0.33 0.44 6 virginica 0.57 0.80 0.67 5 accuracy 0.78 23 macro avg 0.72 0.71 0.69 23 weighted avg 0.78 0.78 0.76 23
متوجه میشویم که Recall و F1 Score برای دستههای Versicolor و Virginica مناسب نیست.
بنابراین، اثر نامتعادل بودن دستهها به این شکل مشاهده میشود.
رفع مشکل
حال میخواهیم با تعریف وزن برای هر دسته، تعادل را بین دستهها برقرار کنیم. پس از اعمال وزنها، هر دسته تاثیر برابری بر روی نتیجه خواهد داشت. تاثیر هر دسته با تعداد داده و وزن آن دسته مرتبط است، بنابراین:
$$w_{1} n_{1}=w_{2} n_{2}$$
از طرفی مجموع این مقادیر نیز برابر 1 خواهد بود:
$$w_{1} n_{1}+w_{2} n_{2}=1$$
از حل این دستگاه دومعادله و دو مجهول به جوابهای زیر میرسیم:
$$begin{aligned}
&w_{1}=frac{1}{2 times n_{1}} \
&w_{2}=frac{1}{2 times n_{2}}
end{aligned}$$
که اگر این روابط را تعمیم دهیم، خواهیم داشت:
$$w_{x}=frac{1}{C times n_{i}}$$
حال رابطه نهایی را موارد محیط برنامهنویسی میکنیم. برای این کار ابتدا یک دیکشنری برای وزنها درست کرده و وزن هر دسته را در مقابل Label همان دسته وارد میکنیم:
nTotal = Y.size
W = {i: (nTotal - N[TN[i]]) / (2 * nTotal) for i in range(3)}
به این ترتیب، دیکشنری وزنها ایجاد میشود که مقادیر موجود در آن بهصورت زیر است:
{0: 0.16, 1: 0.43, 2: 0.40}
حال مشاهده میکنیم که مجموع وزنها 1 و مقدار وزن هر دسته متناسب با تعداد دادههای آن داده است.
اکنون مدل ایجاد شده را تغییر داده و وزن دستهها را به آن میدهیم:
Model = li.LogisticRegression(random_state=0, class_weight=W)
پس از آموزش مدل، دقتها به مقادیر زیر میرسد:
Train Accuracy: 94.23 % Test Accuracy: 78.26 %
و مشاهده میکنیم که دقت بر روی مجموعه داده آموزش، واقعیتر شده است.
همچنین Classification Report به شکل زیر درخواهد آمد:
Test Classification Report: precision recall f1-score support setosa 0.92 1.00 0.96 12 versicolor 1.00 0.17 0.29 6 virginica 0.56 1.00 0.71 5 accuracy 0.78 23 macro avg 0.83 0.72 0.65 23 weighted avg 0.86 0.78 0.73 23
که مشاهده میکنیم مقدار Recall و F1 Score برای دسته virginica بهبود یافته است.
توجه داشته باشید که کاهش معیارهای گفته شده برای دسته Versicolor را نیز داریم، اما در کل avg برای معیارهای مختلف افزایش یافته است که نشانه خوبی از اثر وزندهی است.
به این ترتیب، توانستیم یک مدل رگرسیون لجستیک (Logistic Regression) را روی مجموعه داده تغییر یافته IRIS آموزش دهیم و با وجود نامتعادل بودن دستهها، نتایج مناسبتری بگیریم.
برای بررسیهای بیشتر میتوان سایر مجموعه دادهها را بررسی کرد و نتایج مدل در وجود و عدم وجود وزنها را مقایسه کرد.
جمعبندی
در این آموزش، با دلایل عدم تعادل دادهها آشنا شدیم. همچنین، وزن دهی دسته ها در پایتون را برای متعادل کردن دادهها شرح دادیم. در آموزشهای بعدی، روش تغییر مجموعه داده برای متعادل کردن دادهها را معرفی میکنیم.