فصل سوم hands on

در فصل یک بیان کردیم که رایج ترین روش های آموزش supervised از نوع regression (مقادیر پیش بینی کننده) و classification یا predicting classes هستند. در فصل دوم به بررسی مثال پیش بینی قیمت خانه با استفاده از روش رگرسیون پرداختیم. در این فصل روش classification را بیشتر بررسی خواهیم کرد.

در این فصل دیتاست موسوم به MNIST را بررسی خواهیم کرد که در آن 70000 تصویر کوچک از دست نوشته دانش آموزان و همچنین کارمندان یک سازمان وجود دارد. این دیتاست در دنیای ماشین لرنینگ آنقدر معروف و کاربردی است که به hello world در دنیای ماشین لرنینگ معروف شده است. , و حتی هر زمان که یک روش جدید classification کشف میشود، ابتدا بر روی این دیتا ست تست میشود .

این دیتا ست توسط خود کتابخانه scikit_learn در دسترس است. (گیت هاب کتاب) (گیت های strumer) :

from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1)
mnist.keys()

دیتاست لود شده شامل یک دیکشنری به شرح زیر است:

  • DESCR که به تشریح دیتاست میپردازد.
  • data که شامل تعدادی ردیف(به ازای هر دیتا) و تعدادی ستون (به ازای هر فیچر) است.
  • target : که مربوط به هدف ماست دربرگیرنده ردیفهای با label است.

نگاهی به آرایه ها می اندازیم:

دو قسمت مهم از دیتا را به x و y نسبت میدهیم:

X, y = mnist["data"], mnist["target"]

با دستور زیر متوجه میشویم که 70 هزار تصویر داریم و هرکدام 784 فیچر دارد. (زیرا هر تصویر به تعداد 28*28 پیکسل دارد و هر پیکسل تشکیل دهنده یک فیچر است که مقدار آن از 0(سفید) تا 255 (سیاه) متغیر است.)

X.shape--> (70000, 784)
y.shape -- > (70000,)
mnist["data"]
mnist["target"]

خروجی ما از نوع دسته بندی classification است. برای مشخص کردن تعداد این دسته ها از دستور زیر استفاده میکنیم و میبینیم که 10 دسته منحصر بفرد داریم:

mnist["target"].unique()

حال می‌خواهیم یک نمونه از دیتای خود را مصور سازی کنیم:

import matplotlib as mpl

import matplotlib.pyplot as plt

برای reshape کردن یک ردیف دیتا از دیتا فریم (X)، لازم است ابتدا آن را به فرم آرایه تبدیل کنیم و سپس عنصر اول آن را فراخوانی کنیم و به متغیر some_digit نسبت دهیم:

x_array=X. to_numpy()
some_digit =x_array[0]

و سپس آن را در قالب 28*28 دربیاوریم و با استفاده از matplotlib آن را ترسیم کنیم.

some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap="binary")
plt.axis("off")
plt.show()
این شکل شبیه به عدد 5 است

به همین دلیل مقدار target نیز در ایندکس صفرم برابر با 5 (از نوع رشته) است:

y[0]--> '5'

به این دلیل که اکثر الگوریتمهای یادگیری ماشین با اعداد کار میکنند ، کل y را به عدد تبدیل میکنیم:

y = y.astype(np.uint8)

تا ص 117

جداسازی دیتای آموزشی (60 هزارتای اول) و دیتای تست (10 هزار تای آخر) :

X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]

Training a Binary Classifier

اندکی ساده تر به موضوع نگاه میکنیم و سعی میکنیم تنها یک عدد را تشخیص دهیم. برای مثال سعی میکنیم عدد 5 را تشخیص دهیم. این 5detector نمونه ای از binary classifier است و که تنها قادر به تمایز بین دو کلاس 5 و not 5 است. (دو نمونه= باینری)

y_train_5 = (y_train == 5) # True for all 5s, False for all other digits
y_test_5 = (y_test == 5)

y_train_5 نشان میدهد که آیا مقادیر y_train برابر با 5 هستند یا نه (مشابه باینری: یا درست هست و یا غلط):

حال یک classifier انتخاب میکنیم و آن را آموزش میدهیم. classifier موسوم به Gradient Descent Stochastic (SGD) از کتابخانه سایکیتلرن برای این کار مناسب است. مزیت این classifier این است که میتواند با دیتاهای با حجم بالا کار کند. این کتابخانه قادر است نمونه های Instance را نیز دریافت کند و آموزش خود را update کند به همین دلیل میتواند در دسته Online (در برابر batch) قرار گیرد.

from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5)

نکته:

عبارت stochastic به معنای تصادفی است و عملکرد آن نیز دقیقا بر اساس رندوم بودن است.

حال از مدل خود استفاده میکنیم تا تصویر عدد 5 را تشخیص دهیم:

sgd_clf.predict([some_digit])-->array([ True])

بنابراین مدل ما برای عدد 5 بدرستی کار میکند و جواب صحیح میدهد. حال به ارزیابی مدل خود میپردازیم.

Performance Measures

ارزیابی عملکرد مدل classifier از مدل regression اندکی پیچیده تر است. بنابراین بخش زیادی از این فصل را به این موضوع اختصاص میدهیم.

Measuring Accuracy Using Cross-Validation

Cross-Validation یکی از روشهای ارزیابی مدل است.

Confusion Matrix

یک روش مناسب تر برای ارزیابی مدل، استفاده از ماتریس Confusion است. این روش تعداد مواردی از کلاس A را محاسبه میکند که به اشتباه در کلاس B دسته بندی شده اند. برای مثال برای آنکه بفهمیم به چه تعداد عدد 5 به جای 3 تشخیص داده شده است، به ردیف 5 و ستون 3 از ماتریس Confusion نگاه میکنیم. در ابتدا تعدادی prediction انجام میدهیم و آن را با مقادیر صحیح مقایسه میکنیم. برای این کار میتوانیم از داده های test set استفاده کنیم. اما ترجیحا این کار را نمیکنیم زیرا test set برای ارزیابی نهایی مدل است. بنابراین از تابع cross_val_predict() استفاده میکنیم.

from sklearn.model_selection import cross_val_predict

y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)

عملکرد تابع cross_val_predict نیز مانندcross_val_score است، اما به جای برگرداندن evaluation scores مقادیر پیش بینی شده به ازای هر مقدار تست را برمیگرداند.

from sklearn.metrics import confusion_matrix

confusion_matrix(y_train_5, y_train_pred)

هر ردیف در ماتریس confusion نشانگر یک calss است. در حالی که هر ستون نمایشگر یک predicted class است. ردیف اول این ماتریس نشانگر تصویر غیر از (5-) است. بنابراین تعداد 53057 عدد از تصاویر به درستی به عنوان 5- دسته بندی شده اند. به این تعداد true negative گویند. (به درستی منفی بوده اند). در حالی که 1522 عدد از تصاویر به اشتباه 5 تشخیص داده شده‌اند (false positive). همچنین تعداد 1325 تصویر به اشتباه بعنوان 5- تشخیص داده شده اند (False negative) , و مابقی 4096 تصویر جواب درست بوده اند (true positive)

مدل Classifier مطلوب آن است که اعداد روی قطر اصلی (بالا چپ و پایین راست) ماتریس Confusion آن اعداد غیرصفر داشته باشد.

با فرض بودن در شرایط ایده آل:

y_train_perfect_predictions = y_train_5 # pretend we reached perfection
confusion_matrix(y_train_5, y_train_perfect_predictions)

precision در ماتریس confusion

این شاخص از فرمول زیر بدست می آید:

Recall or sensitivity

این شاخص نیز از فرمول زیر بدست می آید:

در تصویر زیر به خوبی معنای FP وFN و TN و TP مشخص شده است:

Precision and Recall

این مقادیر به صورت مجزا با استفاده از کتابخانه سایکیتلرن در دسترس هستند:

از ترکیب این دو شاخص برای مقایسه دو مدل استفاده میشود که به آن F1 Score نیز میگویند:

این تابع نیز در سایکیتلرن وجود دارد:

کدام شاخص مهم تر است؟

این بستگی به مسئله ما دارد و مورد به مورد متفاوت است. برای مثال برای پیشنهاد فیلم برای کودکان، ترجیح میدهیم تعداد زیادی از فیلمهای خوب را رد کند (low recal) و در عوش موارد مناسب کودکان را معرفی کند (high precision)

یا بعنوان مثال دیگر در تشخیص دزدان مغازه با پردازش تصویر، اگر precision برابر با 30 و recall آن 99 درصد باشد برای ما کافی است. زیرا در این حالت تعداد FN (تشخیص دزد نیست به اشتباه) کم خواهد بود و تعدادFP (تشخیص دزد هست به اشتباه) کم خواهد بود. و این باعث میشود نیروی پلیس درگیری کمتری داشته باشد.

precision/recall trade-off

ما نمیتوانیم به طور همزمان recall و precision را بالا نگه داریم. زیرا کم کردن یکی باعث بالا رفتن دیگری میشود. برای یادگیری بهتر این موضوع، نحوه تصمیم گیری SGDClassifier را بررسی میکنیم. این مدل برای هر نمونه یک امتیاز را با استفاده از تابع decision محاسبه میکند. اگر این امتیاز بالاتر از threshold باشد، در دسته positive قرار خواهد گرفت و در غیر این صورت در دسته negative قرار میگیرد. شکل زیر نشان میدهد که چگونه نمونه های امتیاز بالا در سمت راست و نمونه های با امتیاز پایین در سمت چپ قرار گرفته اند. اگر threshold در وسط باشد، تعداد اعداد 5 در سمت راست برابر با 4 نمونه (TP) و یک نمونه FP است که در این حالت precision ما برابر با 80 درصد و recall برابر با 67 درصد خواهد بود. حال اگر threshold را به راست یا چپ جابجا کنیم ، خواهیم دید که چگونه مقادیر precision و recall با یکدیگر تبادل مقدار خواهند داشت.

سایکیتلرن به ما این امکان را نمیدهد که به مقدار threshold دسترسی داشته باشیم اما میتوانیم مقادیر decision scores که در Predict موثر هستند را تعیین کنیم.

مدل SGDClassifier دقیقا از threshold=0 استفاده میکند . بنابر این کد قبلی نتیجه ای مشابه با متود predict خواهد داشت. حال اگر بخواهیم مقدار threshold را افزایش دهیم داریم:

بنابراین مجددا مشاهده میشود به با افزایش threshold مقدار recall کاهش می یابد. یعنی زمانی که threshold =0 باشد، عدد 5 به درستی تشخیص داده میشود. اما با افزایش threshold به 8000 این امر ممکن نیست (جواب false دریافت میشود).

چگونه مقدار Threshold را تعیین کنیم؟

ابتدا با استفاده از تابع cross_val_predict امتیازscore تمام نمونه ها در trainig_set را بدست می آوریم. اما با قید اینکه میخواهیم decision scores را بدست بیاوریم و نه prediction را.

با این امتیازات و با استفاده از تابع precision_recall_curve مقادیر precision و recall را در threshold های ممکن بدست می آوریم.

و در نهایت با استفاده از matplotlib نمودار precesion و recall بر اساس مقادیر متعدد threshold را رسم میکنیم:

def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
plt.plot(thresholds, precisions[:-1], "b--", label="Precision")
plt.plot(thresholds, recalls[:-1], "g-", label="Recall")
[...] # highlight the threshold and add the legend, axis label, and grid
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
plt.show()

یک روش دیگر برای انتخاب مقدار مناسب , Recall و precision ترسیم recall بر حسب precision است:

مشاهده میشود که افت شدید precision در حدود %recall=80 است. اینکه مقدار precision وrecall را در چه حدی قرار دهیم بستگی به مسئله ما دارد. برای مثال اگر نیاز به precision=90% داشته باشیم، به نمودار قبلی نگاه میکنیم و میبینیم که به threshold حدود 8000 نیاز داریم .

اما برای دقت بیشتر میتوانیم سرچ کنیم که با چه threshold ای میتوان به precision=90% رسید. تابع np.argmax()به ما کمک میکند که اولین index برای مقدار ماکزیمم را پیدا کنیم.

برای پیش بینی کردن با استفاده از دیتای trainings set به جای استفاده از متود predict میتوان از کد زیر استفاده کرد:

y_train_pred_90 = (y_scores >= threshold_90_precision)

حال مقادیر precision و recall این مقادیر پیش بینی شده را بدست می آوریم:

precision_score(y_train_5, y_train_pred_90)
--> 0.9000380083618396

recall_score(y_train_5, y_train_pred_90)
--> 0.4368197749492714

بنابر این توانستیم یک سیستم classifier با precision=90% داشته باشیم. همانگونه که مشخص است تنها باید threshold مناسبی را انتخاب نمود. اما مسئله هنوز به پایان نرسیده است. چون مدلی که Precision آن بالا و recall آن پایین باشد، چندان مطلوب نیست.

تا ص 146

The ROC Curve

ROC یا منحنی receiver operating characteristic از دیگر ابزارهای رایج در روش binary Classifier است.

این منحنی بسیار به منحنی precision/recall شباهت دارد. اما به جای ترسیم precision بر حسب recall ، منحنی بیانگر نرخ TP به نرخ FP است (TFR که در واقع بیان دیگری از شاخص recall است )

نرخ FPR برابر با نسبت نمونه های negative است که به اشتباه در دسته positive دسته بندی شده اند.

که برابر با یک منهای TNR است. (TNR نسبت نمونه های negative است که به اشتباه در دسته negative قرار گرفته اند). به TNR اصطلاحا specificity نیز میگویند.

بنابراین منحنی ROC ، نمایشگر sensivity یا recall بر حسب یک منهای specificity است

برای ترسیم منحنیROC ابتدا از تابع roc_curve برای محاسبه مقادیر TPR و FPR برای مقادیر متعدد threshold استفاده میکنیم.

from sklearn.metrics import roc_curve

fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)

سپس FPR را بر اساس TPR ترسیم میکنیم:

def plot_roc_curve(fpr, tpr, label=None):
 plt.plot(fpr, tpr, linewidth=2, label=label)
 plt.plot([0, 1], [0, 1], 'k--') # Dashed diagonal
 [...] # Add axis labels and grid

plot_roc_curve(fpr, tpr)
plt.show()

مجددا مشاهده میشود که در اینجا نیز یک trade off داریم. هرچه مقدار recall بیشتر باشد ( TPR) مقدار اشتباهات کلاسیفایر در تولید FPR بیشتر خواهد بود. خط نقطه چین نشانگر منحنیROC در یک purely random classifier است

یک روش برای مقایسه classifierها اندازه گیری مساحت زیر نمودار است. یک classifier عالی مساحت زیر نمودار برابر با یک خواهد داشت. اما در یک purely random classifier سطح زیر نمودار برابر با 0.5 خواهد بود. تابع سایکیتلرن یک تابع برای محاسبه این مقدار دارد:

from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5, y_scores)--> 0.9611778893101814

حال به آموزش مدل RandomForestClassifier و مقایسه منحنی ROC آن و سطح زیر نمودار آن با مدلSGDClassifier میپردازیم. ابتدا باید امتیاز هر نمونه در training set را بدست بیاوریم. اما با توجه به ساختار RandomForestClassifier این مدل فاقد متود decision_function است اما به جای آن دارای متود predict_proba است. classifierهای سایکیت لرن معمولا یک عدد از این متود دارند. متود predict_proba آرایه ای را برمیگرداند که برای هر نمونه یک ردیف و برای هر class یک ستون در نظر گرفته میشود که هر خانه نشان دهنده احتمالی است که نشان می دهد آن نمونه در آن کلاس قرار گیرد. برای مثال احتمال 70 درصد برای اینکه تصویر در کلاس 5 قرار گیرد.

from sklearn.ensemble import RandomForestClassifier
forest_clf = RandomForestClassifier(random_state=42)
y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3,
method="predict_proba")

در تابع roc_curve مقادیر labels و scores باید مشخص شوند. اما به جای scores میتوان به آن احتمال مرتبط با class را جایگزین نمود:

y_scores_forest = y_probas_forest[:, 1] # score = proba of positive
class
fpr_forest, tpr_forest, thresholds_forest =
roc_curve(y_train_5,y_scores_forest)

حال میتوان نمودار ROC را ترسیم نمود

plt.plot(fpr, tpr, "b:", label="SGD")
plot_roc_curve(fpr_forest, tpr_forest, "Random Forest")
plt.legend(loc="lower right")
plt.show()

همانگونه که در شکل بالا مشخص است، منحنی ROC در RandomForestClassifier’s از مشابه آن در SGDClassifier’s بطلوب تر است (زیرا به گوشه بالا سمت چپ متمایل تر است. بنابراین سطح زیر نمودار آن نیز مسلما بیشتر خواهد بود.

roc_auc_score(y_train_5, y_scores_forest)--> 0.9983436731328145

اگر بخواهیم مقادیر precision وrecall را بدست بیاوریم ، باید در حدود 99 درصد و 86.6 درصد بدست بیاوریم.

در ادامه به سراغ مسئله ای میرویم که بیش از یک class داشته باشیم.

ادامه از ص 150:

Multiclass Classification

قبلا بیان کردیم که classification نوع binary تنها قادر است بین دو کلاس تصمیم بگیرد. حال نوع multiclass یا multinomial را معرفی میکنیم که میتوانند در بیش از دو کلاس کار دسته بندی را انجام دهند. برخی از الگوریتم ها مانند SGD و Random Forestو naive bayes قادر به دسته بندی در بیش از دو کلاس هستند و همچنین مدل های رگرسیون لاجستیک و SVM از نوع binary classifier هستند. برای دسته بندی اعداد 0 تا 9 به ده دسته، یک روش این است که از ده Classifier باینری استفاده شود. (برای هر عدد یک binary classifier). به این روش one-versus-the-rest (OvR) یا (یکی در مقابل همه) میگویند.

یک روش دیگر استفاده از binary classifier برای جفت اعداد است. برای مثال یک مدل برای 0 و 1 . یک مدل دیگر برای 2 و 3 و … .به این روش one-versus-one (OvO) گویند. اگر N عدد داشته باشیم به N × (N – 1) / 2 مدل نیاز خواهیم داشت. برای مثال در مسئله MNIST (اعدا 0 تا 9)به 45 classifier نیاز خواهیم داشت. و برای تعیین دسته مناسب برای یک تصویر باید آن را به 45 سیستم نشان بدهیم تا ببینیم در کدام دسته قرار خواهد گرفت. مهمترین مزیت OVO این است که هر classifier تنها باید با استفاده از بخشی از training set آموزش ببیند. (فقط آن دو موردی که میخواهد تشخیص دهد)

ص 151

در برخی الگوریتم های دیگر مانند SVM روش OVO به دلیل سرعت بالاتر مناسب تر است

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد.