سنركز هنا على نظم التصنيف في تعلم الآلة. وسنغطي المواضيع التالية:
- قاعدة البيانات المعدلة من المعهد الوطني للمعايير والتكنولوجيا - MNIST
- تدريب مصنف ثنائي
- مقاييس الأداء
- تصنيف الأصناف المتعددة
- تحليل الأخطاء
- التصنيف متعدد التسميات
- التصنيف متعدد المخرجات
- فهرس الترجمة
سنعمل في هذا الفصل على مجموعة بيانات MNIST، وهي عبارة عن 70,000 صورة صغيرة من الأرقام المكتوبة بخط اليد من قبل طلاب المدارس الثانوية وموظفي مكتب الإحصاء الأمريكي. ,تحمل كل صورة اسم الرقم الذي تمثله. إنها من أكثر مجموعات البيانات دراسة من قبل الباحثين، ويطلق عليها غالباً "Hello World" في تعلم الآلة، فكلما توصل أحدهم إلى خوارزمية تصنيف جديدة يبدأ بهذه المجموعة ليرى أداء خوارزميته عليها. يمكننا القول أنه ما من شخص بدأ بتعلم الآلة إلا وعمل على هذه المجموعة عاجلاً أم آجلاً.
يوفر Scikit-Learn العديد من الدوال التي تساعد على تنزيل مجموعات البيانات الشائعة وMINST واحدة منهن. يجلب الكود التالي مجموعة البيانات 1
>>> from sklearn.datasets import fetch_openml
>>> mnist = fetch_openml('mnist_784', version=1)
>>> mnist.keys()
dict_keys(['data', 'target', 'feature_names', 'DESCR', 'details', 'categories', 'url'])
تحتوي مجموعات البيانات التي تُحمل بواسطة Scikit-Learn عامةً على بنية قاموس موحدة تقريباً، كما يلي
- DESCR يصف مجموعة البيانات
- data يحتوي على مصفوفة سطورها الأمثلة وأعمدتها الميزات
- target يحتوي مصفوفة أسماء الأمثلة
لنلقي نظرة على هذه المصفوفات:
>>> X, y = mnist["data"], mnist["target"]
>>> X.shape
(70000, 784)
>>> y.shape
(70000,)
لدينا 70,000 صورة، ولكل صورة 784 ميزة، حيث أن كل صورة هي 28x28 بكسل، وكل ميزة تمثل بكسل واحد يأخذ قيمة بين ال 0 أبيض وال 255 أسود.
لنلقي نظرة على أحد هذه الأرقام الموجودة في مجموعة البيانات. كل ما علينا فعله هو أخذ أحد متجهات الأمثلة، وإعادة تشكله إلى مصفوفة 28x28، ثم عرضه باستخدام imshow()
الموجودة في Matplotlib:
import matplotlib as mpl
import matplotlib.pyplot as plt
some_digit = X[0]
some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap = mpl.cm.binary, interpolation="nearest")
plt.axis("off")
plt.show()
كما نرى فإنه يشبه الرقم 5، وهذا مايخبرنا به اسمه بالفعل:
>>> y[0]
'5'
لاحظ أن الاسم عبارة عن نص string، وغالباً ما تكون الأرقام أفضل في هذا المجال لذا دعونا نحول y إلى الأعداد الصحيحة:
>>> y = y.astype(np.uint8)
إنظر الشكل 3-1 الذي يحتوي بعد الصور الأخرى من مجموعة بيانات MNIST سيعطيك احساساً بتعقيد مهمة التصنيف.
<<>> الشكل 3-1 بعض الأرقام من مجموعة بيانات MNIST
تذكر! يجب علينا دائماً إنشاء مجموعة اختبار ووضعها جانباً قبل التعمق في تفحص البيانات. لحسن الحظ فقد قُسمت هذه المجموعة بالفعل إلى مجموعة تدريب (أول 60,000 صورة) ومجموعة اختبار (آخر 10,000 صورة):
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
أحد النقاط الجيدة أيضاً في Scikit-Learn أنها تعطينا مجموعة التدريب وقد تم خلطها بالفعل مسبقاً بما يضمن تشابه كل طيات التحقق المتقاطع (فلا نريد أن نفقد بعض الأرقام في احدى الطيات). ذلك أن بعض خوارزميات التعلم حساسة لترتيب أمثلة التدريب، وستعمل بشكل سيء إذا حصلت على العديد من الأمثلة المتشابهة على التوالي. فيضمن خلط مجموعة البيانات عدم حدوث ذلك2.
سنبسط المشكلة حالياً ونحاول فقط تحديد رقم واحد وليكون على سبيل المثال الرقم 5. سيكون نموذج الكشف عن الرقم 5 هذا مثالاً لمصنف ثنائي، قادر على التمييز بين صنفين فقط، إما 5 أو ليس 5. سنقوم فيما يلي بتجهيز المتجهات الهدف لهذا المصنف لتكون على صنفين فقط:
y_train_5 = (y_train == 5) # True for all 5s, False for all other digits.
y_test_5 = (y_test == 5)
يعتبر Stochastic Gradient Descent (SGD) من أفضل خوارزميات التصنيف التي يمكن أن نبدء بها، ولدى Scikit-Learn فئة خاصة له بإسم SGDClassifier
. ويتميز بقدرته على العمل بكفاءة مع مجموعات البيانات الكبيرة. إضافة إلى تعامله مع أمثلة مجموعة التدريب واحداً تلو الآخر ما يجعله مناسباً أيضاً للتعلم المستمر online كما سنرى لاحقاً. لننشئ مصنف من فئة SGDClassifier
وندربه على مجموعة التدريب بأكملها:
from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5)
ملاحظة يعتمد SGDClassifier
على العشوائية أثناء التدريب (ومن هنا جاء اسمه) فإذا كنت تريد نتائج قابلة للتكرار عليك تحديد قيمة المعامل random_state
.
والآن يمكننا استخدام هذا المصنف لاكتشاف صور الرقم 5:
>>> sgd_clf.predict([some_digit])
array([ True])
يخمن المصنف أن هذه الصورة تمثل الرقم 5 وهو على صواب بهذه الحالة بالذات، والآن لنقيّم أداء هذا النموذج.
تقييم أداء المصنفات غالباً ما يكون أصعب من تقييم اداء الإنحدار، لذلك سنبذل جزء كبيراً من هذا الفصل على هذا الموضوع، فهناك العديد من مقاييس الأداء، لذا حضر فنجان قهوة آخر واستعد لتعلم العديد من المفاهيم والأختصارات الجديدة.
استخدام التحقق المتقاطع من الطرق الجيدة لتقييم أداء النموذج، تماماً كما فعلنا في الفصل الثاني.
نحتاج إلى المزيد من التحكم في عملية التحقق المتقاطع في بعض الأحيان أكثر مما هو جاهز وموجود في Scikit-Learn. في هذه الحالات يمكننا كتابة دالة التحقق المتقاطع بأنفسنا، سيكون الكود واضح ومباشر، يقوم الكود التالي بنفس الشيء تقريباً مثل وظيفة cross_val_score()
في Scikit-Learn ويطبع نفس النتيجة:
from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone
skfolds = StratifiedKFold(n_splits=3, random_state=42)
for train_index, test_index in skfolds.split(X_train, y_train_5):
clone_clf = clone(sgd_clf)
X_train_folds = X_train[train_index]
y_train_folds = y_train_5[train_index]
X_test_fold = X_train[test_index]
y_test_fold = y_train_5[test_index]
clone_clf.fit(X_train_folds, y_train_folds)
y_pred = clone_clf.predict(X_test_fold)
n_correct = sum(y_pred == y_test_fold)
print(n_correct / len(y_pred)) # prints 0.9502, 0.96565 and 0.96495
تقوم الفئة StratifiedKFold
بأخذ عينات طبقية (كما وضحنا في الفصل الثاني) لإنتاج طيات تحتوي على نسبة متماثلة من كل فئة. ينسخ الكود التالي المصنف sgd_clf
مع كل تكرار، ثم يُدرب هذا المصنف على احد طيات التدريب، ويقوم بالتنبؤات على احدى طيات الاختبار، ثم يحسب عدد التنبؤات الصحيحة ويطبع نسبتها.
والآن لنقييم النموذج SGDClassifier
باستخدام الدالة cross_val_score()
التي تؤدي التحقق المتقاطع على ثلاث طيات. تذكر أن التحقق المتقاطع بثلاث طيات يعني: تقسيم مجموعة التدريب إلى ثلاثة طيات، وتدريب المصنف على طيتين ثم عمل التنبؤات وتقييمها على الطية المتبقية باستخدام النموذج الذي قمنا بتدريبه على الطيتين السابقتين.
>>> from sklearn.model_selection import cross_val_score
>>> cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.96355, 0.93795, 0.95615])
والآن، رائع! فالدقة أعلى من 93% (نسبة التنبؤات الصحيحة) على جميع طيات التحقق المتقاطع. عمل رائع كما يبدو, صحيح؟ لكن قبل أن تشعر بالحماس الشديد، لنلقي نظرة على مصنف غبي جداً سنبنيه لكي يتنبأ بأي صورة نعطيه إيها على أنها لا تحتوي الرقم 5، بمعنى أنه يضعها بفئة not-5.
from sklearn.base import BaseEstimator
class Never5Classifier(BaseEstimator):
def fit(self, X, y=None):
pass
def predict(self, X):
return np.zeros((len(X), 1), dtype=bool)
هل تستطيع تخمين دقة هذا النموذج الغبي؟ دعنا نرى:
>>> never_5_clf = Never5Classifier()
>>> cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.91125, 0.90855, 0.90915])
تبلغ دقته أكثر من 90% وهذه صحيح ذلك أن حوالي 10% فقط من الصور تحتوي على الرقم 5 حقيقة، لذلك إذا خمنت دائماً أن الصورة لا تحتوي على الرقم 5، فستكون مصيباً وعلى حق في 90% من الحالات.
وهذا بالضبط سبب عدم اعتبار الدقة عموماً كمقياس أداء للمصنفات، خاصة عندما نتعامل مع مجموعات بيانات منحرفة (عندما تكون بعض الفئات أكثر تكراراً من غيرها).
تعتبر طريقة أفضل لتقييم أداء المصنفات، فكرتها العامة هي حساب عدد المرات التي تم تصنيف فيها الأمثلة التي في الحقيقة هي من الفئة A على أنها من الفئة B خطاً، فعلى سبيل المثال، لمعرفة عدد المرات التي أخطأ فيها المصنف بين الصورة التي تحتوي الرقم 5 وبالصور التي تحتوي الرقم 3، يجب علينا أن نبحث في السطر الخامس والعمود الثالث من مصفوفة الارتباك.
ولحساب مصفوفة الإرتباك، نحتاج إلى مجموعة من التنبؤات لكي يتم مقارنتها مع القيم الفعلية، يمكن عمل هذه التنبؤات على مجموعة الاختبار، لكن دعها على طرف حالياً (وتذكر أنك تحتاج إلى استخدامها فقط في نهاية المشروع، عندما يتكون لديك مصنف جاهز للإطلاق)، وبدلاً من ذلك يمكننا استخدام الدالة 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
التي تعيد درجات التقييم، هذا يعني أننا سنحصل على تنبؤ نظيف لكل مثال في مجموعة التدريب (تعني كلمة "نظيف" هنا أن التنبؤ تم بواسطة نموذج لم يرى البيانات التي تنبأ بها مطلقاً أثناء التدريب).
نحن جاهزون الآن للحصول على مصفوفة الارتباك باستخدم confusion_matrix()
، ذلك فقط بتمرير الفئات الحقيقة أو المستهدفة y_train_5
والفئات التي تنبأ بها المصنف y_train_pred
:
>>> from sklearn.metrics import confusion_matrix
>>> confusion_matrix(y_train_5, y_train_pred)
array([[53057, 1522],
[ 1325, 4096]])
يمثل السطر في مصفوفة الارتباك الفئة الحقيقة، بينما يمثل العمود الفئة المتنبأ بها أو الفئة المتوقعة.
السطر الأول في هذه المصفوفة صوراً لا تحتوي الرقم 5 (فئة سلبية) وقد تم تصنيف 53,057 منهم بشكل صحيح أي على أنهم لا يحتوا الرقم 5 ويطلق عليها true negatives، بينما تم تصنيف 1,522 المتبقية بشكل خاطئ على أنها تحوي الرقم 5 ويطلق عليها false positive.
السطر الثاني في هذه المصفوفة صوراً تحتوي الرقم 5 (فئة إيجابية) وقد تم تصنيف 1,325 خطأ على أنها لا تحتوي الرقم 5 ويطلق عليها false negative، بينما تم تصنيف 4,096 المتبقية بشكل صحيح على أنها تحتوي الرقم 5 true positive.
يكون المصنف مثالي عندما يحتوي على إيجابيات وسلبيات حقيقية فقط، أي أن مصفوفة الأربتاك الخاصة به سكون لها قيم غير صفرية على قطرها الرئيسي فقط (من الأعلى اليسار إلى الأسفل اليمين):
>>> y_train_perfect_predictions = y_train_5 # لنتظاهر أننا وصلنا إلى الكمال
>>> confusion_matrix(y_train_5, y_train_perfect_predictions)
array([[54579, 0],
[ 0, 5421]])
تعطينا مصفوفة الأرتباك الكثير من المعلومات، لكن أحياناً نفضل مقياساً موجزاً، والدقة هو أحد مقاييس التنبؤات الإيجابية للمصنف (المعادلة 3-1)
المعادلة 3-1. الدقة
حيث TP هو عدد الإيجابيات الحقيقية، وFP هو عدد الإيجابيات الخاطئة.
الطريقة البسيطة للحصول على دقة كاملة 100% هي إجراء تنبؤ إيجابي واحد والتأكد من صحته، وبما أن مقياس الدقة يتجاهل كافة الحالات الإيجابية باستثناء واحد فقط فلن يكون هذا المقياس مفيداً جداً، ولذلك تستخدم الدقة عادة إلى جانبة مقياس التذكر، ويسمى أيضاً الحساسية أو المعدل الإيجابي الحقيقي: وهو نسبة الحالات الإيجابية التي تم اكتشافها بشكل صحيح بواسطة المصنف (المعادلة 3-2)
المعادلة 3-2. التذكر
حيث FN هو عدد السلبيات الخاطئة
إذا كنت مرتبكاً بشأن مصفوفة الارتباك، فسيساعدك الشكل 3-2
يوفر Scikit-Learn العديد من الدوال لحساب مقاييس المصنف، بما في ذلك الدقة والتذكر:
>>> from sklearn.metrics import precision_score, recall_score
>>> precision_score(y_train_5, y_train_pred) # == 4096 / (4096 + 1522)
0.7290850836596654
>>> recall_score(y_train_5, y_train_pred) # == 4096 / (4096 + 1325)
0.7555801512636044
تتضح الأمور أكثر الآن فلايبدو نموذج كشف الرقم 5 دقيقاً كما كان عندما نظرنا إلى دقته. فعندما يدعي أن الصورة تحتوي الرقم خمسة فإنه على صواب بنسبة 72.9% فقط، اضافة أنه يكتشف 75.6% فقط من الصور التي تحتوي الرقم 5 حقاً.
سيكون من الملائم أكثر أن نجمع الدقة والتذكر في مقياس واحد يسمى F1، خاصة إذا اردنا طريقة بسيطة لمقارنة نوعين من المصنفات. قيمة F1 هي المتوسط التوافقي للدقة والتذكر (المعادلة 3-3)، في حين أن المتوسط العادي يعامل جميع القيم بالتساوي، فإن المتوسط التوافقي يعطي وزناً أكبر للقيم المنخفضة. ونتيجة لذلك سحيصل المصنف على درجة F1 عالية، إذا كانت كل من قيم التذكر والدقة مرتفعتين.
المعادلة 3-3. F1
نستطيع حسبا قيمة F1 عبر الدالة f1_score()
الموجودة في Scikit-Learn:
>>> from sklearn.metrics import f1_score
>>> f1_score(y_train_5, y_train_pred)
0.7420962043663375
يفضل المقياس F1 المصنفات التي لها نفس الدقة والتذكر، لكن هذا ليس ما نريده دائماً، فأحياناً يجب أن نهتم بالدقة أكثر من التذكر وأحياناً أخرى العكس. على سبيل المثال، إذا دربنا مصنف على اكتشاف مقاطع الفيديو الآمنة للأطفال، فيجب أن نفضل المصنف الذي يرفض العديد من مقاطع الفيديو الجيدة (تذكر منخفض) لكنه يعطي مقاطع الفيديو الآمنة فقط (دقة عالية)، بدلاً من المصنف الذي يملك نسبة تذكر عالية لكنه يسمح بمرور بعض المقاطع الغير مناسبة للأطفال، (علاوة على أنه في مثل هذه الحالة قد ترغب بإضافة موظفي بشري ليقوم بالتدقيق مقاطع الفيديو التي تأتي من نموذجنا لزيادة التأكد من أنها مناسبة للأطفال). لنفترض أننا دربنا مصنف لاكتشاف اللصوص على كاميرة المراقبة، فمن الطبيعي أن تكون الدقة 30% فقط طالما أنه يتذكر 99% (حتماً سيكون على حارس الأمن تفتيش بعض من لم يقم بالسرقة، لكن سيتم القبض على جميع السارقين تقريباً).
لا يمكننا الحصول على قيم كبيرة لكلا النسبتين - لسوء الحظ - فزيادة الدقة تقلل التذكر، والعكس صحيح، وهذا ما يسمى الموازنة بين الدقة والتذكر
سنلقي نظرة على كيفية اتخاذ المصنف SGDClassifier
لقرارات التصنيف حتى نفهم هذه الموازنة. يحسب المصنف النتيجة بناء على دالة القرار الموجودة لديه، فإذا كانت النتيجة أكبر من عتبة معينة فسيصنفها بالفئة الإيجابية، وإلا فسيسندها إلى الفئة السلبية.
يوضح الشكل 3-3 بعض الأرقام المرتبة حسب النتيجة الأدنى على اليسار إلى الأعلى على اليمين. ولنفترض أن العتبة موضوعة على السهم المركزي (بين رقمي ال 5) سنجد 4 تصنيفات ايجابية حقيقية (هي بالحقيقة تحتوي على الرقم 5) وسنجد أيضاً على يمين تلك العتبة تصنيف ايجابي خاطئ وحيد (هي بالحقيقة تحتوي على الرقم 6).
تبلغ الدقة على هذه العتبة 80% (4 من أصل 5)، لكن من أصل 6 صور هي في الحقيقة تحتوي على الرقم 5 استطاع المصنف أن يكتشف فقط 4، لذلك فإن قيمة التذكر تكون 67% (4 من أصل 6). إذا قمنا الآن برفع الحد الأدنى (حركنا السهم الموجود إلى اليمين) فإن الإيجابيات الخاطئة الصورة التي تحتوي الرقم 6 تصبح ايجابية حقيفية، بالتالي فإن الدقة تزداد إلى 100% في هذه الحالة، لكن احد الإيجابيات الحقيقية ستصبح سلبية خاطئة وبالتالي فإن نسبة التذكر ستنخفض إلى 50%. والعكس صحيح فإن تقليل العتبة (تحريك السهم باتجاه اليسار) سيزيد من التذكر ويقلل من الدقة.
لا يسمح Scikit-Learn بتحديد العتبة مباشرة أثناء التنبؤ، لكنه يعيد كافة النتائج لكي تستخدمها حتى تتخاذ القرار بنفسك، لذلك وبدلاً من استدعاء الدالة predict()
يمكنك استدعاء الدالة decision_function()
التي تعيد نتيجة كل حالة، ثم بإمكانك عملة التبنؤ بناء على العتبة التي تريدها:
>>> y_scores = sgd_clf.decision_function([some_digit])
>>> y_scores
array([2412.53175101])
>>> threshold = 0
>>> y_some_digit_pred = (y_scores > threshold)
array([ True])
يستخدم المصنف SGDClassifier
عتبة صفرية عند استدعاء الدالة predict()
لذلك فإن استدعاؤها سيعيد نفس نتيجة الكود السابق، دعنا نزيد تلك العتبة ونرى النتائج:
>>> threshold = 8000
>>> y_some_digit_pred = (y_scores > threshold)
>>> y_some_digit_pred
array([False])
يؤكد الكود السابق أن زيادة العتبة (تحريك السهم باتجاه اليمين) يقلل التذكر (يزيد الدقة) حيث أن الصورة some_digit
تحتوي الرقم 5 وقد كشفها المصنف عندما كانت العتبة 0، لكنها فوتها عندما تم زيادة العتبة إلى 8,000.
والسؤال الملح الآن هو كيف نحدد العتبة التي يجب أن نستخدمها؟ للإجابة على هذا السؤال نحتاج أولا إلى الحصول على نتيجة كل الأمثلة الموجودة في مجموعة التدريب وذلك باستخدام الدالة cross_val_predict()
مرة أخرى، لكن هذه المرة سنحدد أننا نريد نتائج القرار بدلاً من التنبؤات:
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function")
نستطيع الآن حساب الدقة والتذكر بإستخدام هذه النتائج على أي قيمة للعتبة نريدها وذلك باستخدام الدالة precision_recall_curve()
:
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)
نستطيع أخيراً تمثيل الدقة والتذكر مقابل العتبة على رسم بياني باستخدام Matplotlib كما في الشكل 3-4:
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, add the legend, axis label and grid
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
plt.show()
ملاحظة قد تتسائل عن سبب وراء كون منحنى الدقة أكثر وعورة من منحنى التذكر في الشكل 3-4، وسبب هو أن الدقة قد تنقص في بعض الأحيان عندما نرفع العتبة (نحرك السهم نحو اليمين) على الرغم من أنها سترتفع بشكل عام، ولفهم السبب وراء ذلك ارجع الشكل 3-3 ولاحظ ما يحدث عندما نحرك العتبة رقماً واحداً إلى اليمين، ستنتقل الدقة من 4/5 أو 80% إلى 3/4 أو 75%، لكن على الناحية الأخرى لايمكن أن ينخفض التذكر إلا عند زيادة العتبة مايفسر شكل منحنيه السلس.
هناك طريقة اخرى لتحديد توازن جيد بين الدقة والتذكر، وذلك برسم الدقة مقابل التذكر مباشرة، كما هو موضح في الشكل 3-5 وقد تم تمييز العتبة نفسها عليه
يوضح الشكل أن الدقة تبدأ بالإنخفاض الحاد عند التذكر بقيمة 80%. وسنرغب بتحديد نقطة التوازن الدقة / الاسترجاع مباشرة قبل ذلك الإنخفاض، يمكن أن يكون التذكر حوالي 60% لكن طبعاً هذه النسبة تعتمد على مشروعك.
لنفترض أنك تستهدف دقة 90% وتفحصت الشكل 3-4 فوجدت أن العتبة المناسبة بحدود 8,000، ولكي نكون أكثر دقة يمكننا البحث عن الحد الأدنى الذي يمنحنا دقة 90% (يمكننا استخدام الدالة np.argmax()
الذي يعيد أول دليل لأكبر قيمة، وفي هذه الحالة يعني أول قيمة True
):
threshold_90_precision = thresholds[np.argmax(precisions >= 0.90)] # ~7816
يمكننا تشغل الكود التالي للتنبؤ بالقيم على مجموعة التدريب - مبدئياً - بدلاً من استدعاء الدالة predict()
التي تتضمن عتبة ذات قيمة 0:
y_train_pred_90 = (y_scores >= threshold_90_precision)
دعنا نتحقق من دقة وتذكر هذه التنبؤات أو التوقعات:
>>> precision_score(y_train_5, y_train_pred_90)
0.9000380083618396
>>> recall_score(y_train_5, y_train_pred_90)
0.4368197749492714
قمنا ببناء مصنف بدقة 90% كما ترى، رائع صحيح؟ يبدو من السهل تدريب مصنف بأي دقة نريدها، كل ما علينا فعله هو تحديد عتبة مرتفعة بما يكفي، وبذلك نحصل عليه. لكن إنتظر ليس بهذه السرعة فالمصنف عالي الدقة وذو تذكر منخفض لن يكون ذا فائدة حقيقية.
ملاحظة اذا قال لك زميلك في العمل دعنا ندرب مصنف بدقة تصل إلى 99% يجب أن تسأله وكم نسبة تذكره؟
أحدى أدوات القياس الأخرى الشائعة والمستخدمة مع المصنفات الثنائية، مشابهة جداً لمنحنى الدقة / التذكر، لكن بدلاً من رسم الدقة مقابل التذكر فإن ROC يرسم المعدل الإيجابيات الحقيقي (هو اسم آخر للتذكر) مقابل معدل الإيجابيات الخاطئية FPR (نسبة الحالات السلبية التي تم تصنيفها بشكل غير صحيح على أنها موجبة) وتساوي واحد ناقص المعدل السلبي الحقيقي (نسبة الحالات السلبية والمصنفة بشكل صحيح على أنها سلبية) يسمى TNR أيضاً بالنوعية أي أن منحنى ROC يرسم الحساسية أو التذكر مقابل 1 - النوعية.
نحتاج إلى حساب TPR وFPR لقيم عتبات مختلفة حتى نستطيع رسم منحنى ال ROC، يمكننا استخدام الدالة roc_curve()
لحساب هذه النسب:
from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)
يمكننا رسم FPR مقابل TPR باستخدام Matplotlib، كما في الشكل 3-6:
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()
علينا دراسة التوازن مرة أخرى فكلما ارتفع التذكر TPR زادت الإيجابيات الخاطئة FPR التي ينتجها المصنف، يكون المصنف جيداً عندما يبعد بأكبر قدر ممكن عن الخط المنقط - والذي يمثل مصنف عشوائي بحت - باتجاه الزاوية اليسرى العلوية.
احدى الطرق لمقارنة المصنفات هي قياس المساحة المنطقة التي تحت المنحى AUC، وبالعودة إلى الفقرة السابقة يكون المصنف مثالياً عندما يكون له ROC AUC = 1 في حين أن المصنف العشوائي تماماً سيكون له ROC AUC = 0.5. يوفر Scikit-Learn دالة لحساب ROC AUC:
>>> from sklearn.metrics import roc_auc_score
>>> roc_auc_score(y_train_5, y_scores)
0.9611778893101814
ملاحظة قد نتسائل عن كيفية تحديد أي المقياسين نستخدم ROC أو الدقة / التذكر خاصة أنهما يتشابهان كثيراً. تقول القاعدة التجريبية بتفضيل منحنى الدقة / التذكر عندما يكون عدد الأمثلة في فئة الإيجابيات قليل مقارنة بباقي عناصر مجموعة التدريب، أو عندما ينصب اهتمامنا بشكل أكبر على الإيجابيات الخاطئة أكثر من السلبيات الخاطئة، وباستخدم منحنى ROC خلاف تلك الأسباب. فمنحنى ROC السابق والمساحة تحت المنحنى ROC AUC قد تعطي إنطباع أن المصنف جيد جداً، لكن ذلك يرجع غالباً إلى وجود عدد قليل من الأمثلة الإيجابية (التي تحتوي الرقم 5 حقيقة) مقارنة بالسلبيات (التي لا تحتوي الرقم 5)، ويوضح منحنى الدقة / التذكر أن المصنف لديه مجال للتحسين.
دعنا ندرب مصنف من نوع RandomForestClassifier
ونقارن منحنى ROC والمساحة تحت ذلك المنحنى ROC AUC مع منحنى والمساحة تحت المنحنى للمصنف SGDClassifier
السابق. سنحتاج بداية إلى نتائج كل مثال في مجموعة التدريب لكن نظراً للطريقة التي يعمل بها مصنف الغابة العشوائية فإن الفئة RandomForestClassifier
لا تحتوي على الدالة decision_function()
وتحوي بدلاً عنها الدالة predict_proba()
- تحوي المصنفات بمكتبة Scikit-Learn احد هاتين الدالتين - التي تعيد مصفوفة سطورها الأمثلة، وأعمدتها الأصناف، وقيمها احتمال أن ينتمي المثال (السطر) إلى الصنف (العمود).
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 فنحن بحاجة إلى النتئج وليس الأحتمالات، والحل بسيطة هو باستخدام احتمال الصنف الإيجابي كنتيجة:
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، وسيكون مفيداً لورسمنا منحنى ال ROC الخاص بالمصنف SGDClassifier
على نفس الرسم البياني فسيكون بإمكاننا مقارنة المصنفين بسهولة (الشكل 3-7):
plt.plot(fpr, tpr, "b:", label="SGD")
plot_roc_curve(fpr_forest, tpr_forest, "Random Forest")
plt.legend(loc="lower right")
plt.show()
يوضح الشكل 3-7 أن منحنى ال ROC الخاصة بالمصنف RandomForestClassifier
أفضل بكثير من منحنى المصنف SGDClassifier
فهو يقترب من الزاوية العلوية اليسرى أكثر، وبالتالي فإن المساحة تحتى المنحنى ROC AUC ستكون أكبر وبالنتيجة فإن مصنف الغابة العشوائية أفضل بشكل ملحوظ:
>>> roc_auc_score(y_train_5, y_scores_forest)
0.9983436731328145
تمرين حاول قياس نتائج الدقة والتذكر، ستشير الأرقام إلى حالة جيدة نوعاً فستكون الدقة حول ال 99% والتذكر 86.6%
عملنا حتى الآن على تدريب المصنفات الثنائية، واستكشفنا مقاييس الأداء المختلفة، وكيفية تقييم تلك المصنفات باستخدام التحقق المتقاطع، وناقشنا الموازنة بين الدقة والتذكر وكيف نفضل أحدها على الأخرى حسب الحالة التي لدينا، ثم عملنا على مقارنة عدة نماذج تصنيف باستخدام منحنيات ROC والمساحة التي تحتها ROC AUC، وآمل أن تكون قد اتضحت هذه الأفكار لديك، وأن تكون جاهزاً للعمل على اكتشاف أكثر من الرقم 5.
تميز المصنفات الثنائية بين صنفين وتعمل المصنفات المتعددة على تصنيف أكثر من صنفين، تسمى أيضاً المصنفات متعددة الحدود.
تستطيع بعض الخوارزميات (مثل Random Forest وBayes) تصنيف العديد من الأصناف مباشرة، في حين أن بعض الخوارزميات الأخرى (مثل Support Vector Machine وLinear) لا تستطيع تمييز أكثر من صنفين أبداً، لكن هناك بعض الاستراتيجيات التي يمكننا اتبعها لجعل المصنفات الثنائية تصنف العديد من الأصناف.
تتمثل احدى هذه الاستراتيجيات في تدريب 10 مصنفات ثنائية يعمل كل واحد منهم على تمييز احد الأرقام من 0 إلى 9، ثم عندما نريد التنبؤ بالصنف احدى الصور، سنمرر هذه الصورة على المصنفات العشرة لنحصل على نتيجة كل واحد منهم ثم نختار صنف المصنف ذو النتيجة الأعلى. تسمى هذه الاستراتيجية واحد مقابل الكل OvA أو واحد مقابل الباقي OvR.
تتمثل الاستراتيجية الأخرى بتدريب مصنف ثنائي لكل زوج من الأرقام مثلاً يكون الصنف الأول هو الرقم 0 والصنف الثاني هو الرقم 1، والمصنف الثاني للتميز بين الرقم 0 والرقم 2، والمصنف الثالث للتميز بين الرقم 1 والرقم 2 وهكذا، وتسمى هذه الاستراتيجية واحد مقابل واحد OvO، فإذا كان لدينا N صنف سيكون علينا تدريب N * (N -1) / 2 مصنف. ومن أجل بيانات MNIST يتوجب علينا تدريب 45 مصنفاً ثنائياً، بالتالي عندما نريد تصنيف احدى الصور سنمررها إلى ال 45 مصنف ثم نرى أي صنف سيكون الأكثر تكراراً من ثنائيات التصنيف. تكمن الميزة الرئيسية لاستراتيجية OvO في أن كل مصنف سيحتاج إلى التدرب على جزء محدد (صنفين فقط) من مجموعة بيانات التدريب للفئتين التين يجب تميزهما.
بعض الخوارزميات (مثل SVM) تعمل بشكل سيء عندما يزداد حجم عينة التدريب، لذلك يفضل استخدام OvO لهذه الخوارزميات نظراً لسرعة تدريب العديد من المصنفات على مجموعات تدريب صغيرة بدلاً من تدريب عدد قليل من المصنفات على مجموعات تدريب كبيرة، ومع ذلك بالنسبة لمعظم خوارزميات التصنيف الثنائي فإن استراتيجية OvA تعمل بشكل أفضل.
يكتشف Scikit-Learn تلقائياً عندما نحاول استخدام خوارزمية تصنيف ثنائية لمهمة تصنيف متعددة الأصناف، ويقوم باتباع استراتيجية OvA باستثناء المصنفات من نوع SVM التي يتبع من أجلها استراتيجية OvO. والآن دعنا نجرب SGDClassifier
لتصنيف كافة الأرقام:
>>> sgd_clf.fit(X_train, y_train) # y_train, not y_train_5
>>> sgd_clf.predict([some_digit])
array([5], dtype=uint8)
قام الكود السابق - بسهولة - بتدريب SGDClassifier
على مجموعة بيانات التدريب باستخدام الفئات الأصلية y_train
ذات الفئات من 0 إلى 9، بدلاً من الفئات الثنائية التي عملنا عليها سابقاً (إما 5 أو ليست 5)، ثم قام بالتنبؤ صحيح أيضاً، عمل Scikit-Learn خلف الكواليس على تدريب 10 مصنفات ثنائية، وحصل على نتئائج قرارهم للصورة some_digit
ثم حدد الصنف الذي حصل على أعلى نتيجة.
لتحقق من صحة ذلك يمكنن استدعاء الدالة decision_function()
التي ستعيد في هذه الحالة مصفوفة تحوي 10 نتائج، نتيجة لكل صنف:
>>> some_digit_scores = sgd_clf.decision_function([some_digit])
>>> some_digit_scores
array([[-15955.22627845, -38080.96296175, -13326.66694897,
573.52692379, -17680.6846644 , 2412.53175101,
-25526.86498156, -12290.15704709, -7946.05205023,
-10631.35888549]])
سنرى أيضاً من خلال الكود التالي أن أعلى نتيجة هي حقاً للصنف 5:
>>> np.argmax(some_digit_scores)
5
>>> sgd_clf.classes_
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)
>>> sgd_clf.classes_[5]
5
ملاحظة عند تدريب المصنف فإنه يحتفظ بقائمة الأصناف المستهدفة في المصفوفة classes_
مرتبة حسب القيمة، في حالتنا هذه فإن دليل المصفوفة يتطابق مع اسم الصنف كما في المثال السابق، لكن بشكل عام لن نكون محظوظين بنفس الطريقة.
يمكننا استخدام الفئات OneVsOneClassifier
و OneVsRestClassifier
لإجبار Scikit-Learn على استخدام استراتيجية OvO و OvR، وذلك بتمرير المصنف إلى احد هذه الفئات ثم استخدم كائن هذه الفئات كما نستخدم المصنف نفسه، كما في الكود التالي:
>>> from sklearn.multiclass import OneVsOneClassifier
>>> ovo_clf = OneVsOneClassifier(SGDClassifier(random_state=42))
>>> ovo_clf.fit(X_train, y_train)
>>> ovo_clf.predict([some_digit])
array([5], dtype=uint8)
>>> len(ovo_clf.estimators_)
45
سيكون تدريب RandomForestClassifier
بنفس السهولة:
>>> forest_clf.fit(X_train, y_train)
>>> forest_clf.predict([some_digit])
array([5], dtype=uint8)
لكن باستخدام RandomForestClassifier
لن يكن على Scikit-Learn اتباع احدى استراتيجيتي OvO أو OvA لأن خوارزمية الغابات العشوائية تستطيع التصنيف مباشرة إلى فئات متعددة. ويمكننا استدعاء الدالة predict_proba()
للحصول على مصفوفة الاحتمالات التي أنشئها المصنف للأمثلة التي مررناها للتنبؤ مقابل الأصناف التي توقعها:
>>> forest_clf.predict_proba([some_digit])
array([[0. , 0. , 0.01, 0.08, 0. , 0.9 , 0. , 0. , 0. , 0.01]])
واضح أن المصنف واثق إلى حد ما من توقعه فالإحتما 0.9 الموجود في المصفوفة السابقة عند الدليل 5 يعني أن احتمال أن تكون الصورة تحتوي الرقم 5 هو 90%، وأيضاً من المصفوفة السابقة نكتشف أن المصنف يعتقد أن الصورة قد تكون 2 أو 3 أو 9 بنسب احتمال 1% , 8% , 1% على التوالي.
نحتاج الآن إلى تقييم هذه المصنفات كالعادة نحتاج إلى التحقق المتقاطع. دعنا نقيم دقة SGDClassifier
باستخدام الدالة cross_val_score()
:
>>> cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
array([0.8489802 , 0.87129356, 0.86988048])
كما يتضح من المصفوفة السابقة فإن دقة المصنف SGDClassifier
أكثر 84% في جميع طيات الاختبار، وإذا استخدمنا مصنفاً عشوائياً فسنحصل على دقة 10% لذا فهذه دقة مقبولة، لكن لايزال بإمكاننا العمل على تحسينها بشكل أكبر، على سبيل المثال يمكننا معايرة المدخلات فقط لتزداد الدقة فوق 89%:
>>> from sklearn.preprocessing import StandardScaler
>>> scaler = StandardScaler()
>>> X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))
>>> cross_val_score(sgd_clf, X_train_scaled, y_train, cv=3, scoring="accuracy")
array([0.89707059, 0.8960948 , 0.90693604])
يجب اتباع خطوات مشروع تعلم الآلة المذكورة في الفصل (؟؟؟) عندما يكون هذا مشروعاً حقيقياً، لاستكشاف خيارات تحضير البيانات، وتجربة عدة نماذج، ثم وضع قائمة مختصرة لأفضلها أداء، وضبط برامترات التحكم الخاصة بها باستخدام الفئة GridSearchCV
ثم أتمتة أكبر قدر ممكن من هذا الخطوات كما فعلنا في الفصل السابق. لنفترض أننا وجدنا نموذج واعداً أوردنا ايجاد طريقة لتحسينه، فإحدى طرق القيام بذلك هي تحليل أنواع الأخطاء التي يرتكبها.
أولاً يمكننا تفحص مصفوفة الارتباك. سنحتاج إلى عمل التنبؤات باستخدام الدالة cross_val_predict()
ثم نستدعي الدالة confusion_matrix()
تماماً كما فعلنا سابقاً:
>>> y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
>>> conf_mx = confusion_matrix(y_train, y_train_pred)
>>> conf_mx
array([[5578, 0, 22, 7, 8, 45, 35, 5, 222, 1],
[ 0, 6410, 35, 26, 4, 44, 4, 8, 198, 13],
[ 28, 27, 5232, 100, 74, 27, 68, 37, 354, 11],
[ 23, 18, 115, 5254, 2, 209, 26, 38, 373, 73],
[ 11, 14, 45, 12, 5219, 11, 33, 26, 299, 172],
[ 26, 16, 31, 173, 54, 4484, 76, 14, 482, 65],
[ 31, 17, 45, 2, 42, 98, 5556, 3, 123, 1],
[ 20, 10, 53, 27, 50, 13, 3, 5696, 173, 220],
[ 17, 64, 47, 91, 3, 125, 24, 11, 5421, 48],
[ 24, 18, 29, 67, 116, 39, 1, 174, 329, 5152]])
كما نرى فمن أجل 10 أصناف سيتولد لدينا عدد كبير من الأرقام (100 رقم) وسيكون من الأنسب لو حولنا إلى شكل بياني باستخدام الدالة matshow()
الموجودة في Matplotlib:
plt.matshow(conf_mx, cmap=plt.cm.gray)
plt.show()
<p align="center">
<img src="classification-assets/10x10 confusion matrix.jpg" alt="مصفوفة الارتباط للأصناف العشرة"/>
</p>
تبدو مصفوفة الارتباك جيدة إلى حد ما، نظراً لأن القطر الرئيسي يميل إلى اللون الأبيض ما يعني أن معظم الصور قد تم تصنيفها بشكل صحيح، ويبدو الرقم 5 أغمق قليلاً من الأرقام الأخرى، ما قد يعني أن هناك عدداً أقل من الصور التي تحتوي الرقم 5 أو أن المصنف لا يعمل جيداً على هذا الرقم كما هو الحال على باقي الأرقام، ويمكننا التحقق من كلا الفرضيتين.
سنركز أولاً على الخطأ الموجود في الرسم البياني السابق وسنقوم بقسمة كل قيمة في مصفوفة الأرتباك على عدد الصور في الصنف المقابل وذلك لمقارنة نسبة الخطاً بدلاً من عدد الأخطاء (الشيء الذي جعل المقارنة مع الأصناف الأكثر عدد غير عادل).
row_sums = conf_mx.sum(axis=1, keepdims=True)
norm_conf_mx = conf_mx / row_sums
سنملئ القطر الرئيسي بالأصفار وذلك لإظهار الأخطاء فقط على الرسم البياني:
np.fill_diagonal(norm_conf_mx, 0)
plt.matshow(norm_conf_mx, cmap=plt.cm.gray)
plt.show()
<p align="center">
<img src="classification-assets/10x10 confusion matrix error rate.jpg" alt="مصفوفة الارتباط لمعدل الأخطاء"/>
</p>
يتضح في الرسم البياني السابق أنواع الأخطاء التي يرتكبها المصنف بشكل أكبر - تذكر أن السطور تمثل الأصناف الحقيقية، والأعمدة الأصناف المتنبأ بها - يخبرنا العمود رقم 8 ذو الأوان الساطعة أن العديد من الصور صنفت على أنها تحتوي الرقم 8 بشكل خاطئ مع أن السطر رقم 8 ليس بهذا السوء فألوانه داكنة نوعاً ما ويخبرنا بأن الصور التي تحتوي الرقم 8 حقيقة يتم تصنيفها بشكل صحيح، يوضح أيضاً هذا الرسم البياني أن مصفوفة الارتباك ليست بالضرورة متناظرة، فيمكننا ملاحظة أن الصور التي تحتوي الرقم 3 و 5 غالباً ما يتم الخلط بينهما في كلا الاتجاهين.
يعيطنا تحليل مصوفة الارتباك رؤية واضحة حول طرق تحسين المصنف، فبالنظر إلى الرسم البياني السابق يبدو واضحاً أننا يجب أن نبذل جهداً لتقليل عدد ال 8 الخاطئة، فمثلاً يمكننا جمع المزيد من بيانات التدريب للأرقام التي تبدو مثل 8 ولكنها ليست كذلك، حتى يتمكن المصنف من تمييزها عن الأرقام التي تحتوي الرقم 8 حقيقة، أو يمكننا العمل على هندسة ميزات جديدة يمكنها مساعدة المصنف، على سبيل المثال كتابة خوارزمية لحساب عدد الحلقات المغلقة (لأن 8 تحتوي حلقتين، 6 تحوي حلقة، 5 لا تحتوي أي حلقة) أو يمكننا معالجة الصور (باستخدام Scikit-Image أو Pilow أو OpenCV) قبل تمريرها للمصنف وذلك لإبراز بعض الأنماط بشكل أكبر مثل الحلقات المغلقة.
يعتبر تحليل الأخطاء الفردية أيضاً طريقة لاكتساب الرؤئ حول ما يقوم به المصنف ولماذا يفشل في بعض الأصناف، ولكنه أكثر صعوبة واستهلاكاً للوقت. لكن دعنا نجرب قليلاً ونرسم أمثل من الصنف 3 والصنف 5 وذلك باستخدام الدالة plot_digits()
من Matplotlib والتي تستخدم imshow()
داخلياً
cl_a, cl_b = 3, 5
X_aa = X_train[(y_train == cl_a) & (y_train_pred == cl_a)]
X_ab = X_train[(y_train == cl_a) & (y_train_pred == cl_b)]
X_ba = X_train[(y_train == cl_b) & (y_train_pred == cl_a)]
X_bb = X_train[(y_train == cl_b) & (y_train_pred == cl_b)]
plt.figure(figsize=(8,8))
plt.subplot(221); plot_digits(X_aa[:25], images_per_row=5)
plt.subplot(222); plot_digits(X_ab[:25], images_per_row=5)
plt.subplot(223); plot_digits(X_ba[:25], images_per_row=5)
plt.subplot(224); plot_digits(X_bb[:25], images_per_row=5)
plt.show()
تظهر الكتلتين الموجودتين على اليسار أرقاماً مصنفة على أنها تحتوي الرقم 3، والكتلتين الموجوديتن على اليمين صوراً مصنفة على أنها الرقم 5، بعض الأرقام التي يخطئ المصنف بها على سبيل المثال في أسفل اليسار وأعلى اليمين مكتوبة بشكل سيء لدرجة أن حتى الإنسان سواجه مشكلة في تصنيفها مثل الرقم 5 في الصف الأول والعمود الثاني يبدو حقاً كأنه الرقم 3، ومع ذلك فإن معظم الصور التي تم تصنيفها بشكل خاطئ تبدو لنا كأخطاء واضحة، ومن الصعب فهم سبب ارتكاب المصنف لأخطاء مثلها3. والسبب هو أننا استخدما SGDClassifier
البسيط، فهو نموذج خطي وكل ما يفعله هو تخصيص وزن لكل صنف ولكل بكسل، بالتالي عندما يرى صورة جديدة فإنه يجمع كل قيم البكسلات بعد ضربها بالوزن للحصول على نتيجة كل صنف. وبما أن 3 و 5 تختلف فقط ببعض البكسلات فإن هذا النموذج سيتربك بسهولة عند التفريق بينهما.
يكمن الفرق الرئيسي بين 3 و 5 في موضع الخط الصغير الذي يربط الخط العلوي بالقوس السفلي، فإذا رسمنا الرقم 3 بإزاحة التقاطع قليلاً إلى اليسار فسيصنف على أنه 5 والعكس صحيح، بمعنى آخر، هذا المصنف حساس للغاية لتحويل الصور وتدويرها. لذ فإن إحدى الطرق لتقليل الارتباك بين 3 و 5 ستكون معالجة الصور مسبقاً للتأكد من أنها متمركزة جيداً وليست مستديرة بشكل كبير. وهذا سيساعد على الأرجح في تقليل الأخطاء الأخرى أيضاً.
رأينا حتى الآن أن كل مثال في مجموعة التدريب له صنف واحد فقط، لكن في بعض الحالات قد نرغب أن يقوم المصنف بإخراج عدة أصناف لكل مثال، لنأخذ مصنف التعرف على الوجوه كمثال فمالذي يجب أن يفعله إذا تعرف على أكثر من وجه في نفس الصورة؟ يجب أن يقوم بارجاع تسمية لكل وجه تعرف عليه، ولنفترض أننا قمنا بتدريبه للتعرف على 3 وجوه هم عبدالله وعبدالرحمن وعبدالعزيز، فعندما نمرر له صورة لعبدالله وعبدالعزيز يجب أن يكون خرجه [1, 0, 1] (أي أن عبدالله موجود، عبدالرحمن غير موجود، وعبدالعزيز موجود). وهذا ما يسمى بنظام التصنيف متعدد التسميات.
سنأخذ مثلاً أبسط من التعرف على الوجوه لأغراض التوضيح:
from sklearn.neighbors import KNeighborsClassifier
y_train_large = (y_train >= 7)
y_train_odd = (y_train % 2 == 1)
y_multilabel = np.c_[y_train_large, y_train_odd]
knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train, y_multilabel)
ينشئ الكود السابق مصفوفة y_multilabel
تحتوي على تسميتن لكل صورة، يشير الأول إلى ما إذا كان الرقم كبيراً أم لا (7 أو 8 أو 9) ويشير الثاني إلى ما إذا كان فردياً أو زوجياً. ثم يقوم بإنشاء مصنف من نوع KNeighborsClassifier
الذي يدعم التصنيف معدد التسميات (لكن ليس كل المصنفات تفعل) ويدربه باستخدام مصوفة الأهداف ذات التسميتن.
نستطيع الآن إجراء التنبؤ ولاحظ أنه ينتج عنه تسميتان:
>>> knn_clf.predict([some_digit])
array([[False, True]])
والتنبؤ صحيح، فالرقم 5 ليس كبيراً وهو رقم فردي.
هناك العديد من الطرق لتقييم المصنف متعدد التسميات، واختيار مقياس صحيح بناء على مشروعك. على سبيل المثال، تتمثل إحدى الطرق في قياس نتيجة F1 لكل تسمية أو يمكننا استخدام أي مقياس ثنائي على كل تسمية من المقاييس التي ناقشناها سابقاً، ثم حساب متوسط النتيجة، كما يقوم الكود التالي بحساب متوسط F1 على كل التسميات:
>>> y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3)
>>> f1_score(y_multilabel, y_train_knn_pred, average="macro")
0.976410265560605
يفترض الكود السابق أن جميع التسميات لها الأهمية نفسها، قد لا يكون الأمر كذلك، خصوصاً إذا كانت صور عبدالله أكثر من صور عبدالرحمن وعبدالعزيز، عندنا قد نرغب باعطاء اهمية أكبر لنتيجة التصنيف على صوره، احدى الخيارات هي اعطاء وزن لتسمية عبدالله أكثر، ثم نستطيع حساب المتوسط الموزون وذلك بتمرير average="weighted
في الكود السابق4
التصنيف متعدد المخرجات هو النوع الأخير من مهام التصنيف الذي سنناقشه، إنه مجرد تعميم للتصنيف متعددة التسميات حيث يمكن أن يكون لكل تسمية عدة أصناف بدلاً من فقط True, False.
دعنا نبني نظاماً يزيل التشويش من الصور لتوضيح هذا النوع، سيأخذ المصنف الصورة المشوشة كدخل ونأمل أنه سيتنبئ بالصورة النظيفة كخرج، على شكل مصفوفة بكسلات، تماماً مثل الصور التي في MNIST. لو دققنا النظر في عمل هذا المصنف سنجده مصنف متعدد التسميات لكن كل تسمية هي بكسل قيمته بين ال 0 وال 255 وهذا ما يميزه عم المصنف متعدد التسميات.
ملاحظة يمكن للفاصل بين التصنيف والإنحدار أن يكون ضبابياً، كما في مثال التصنيف متعدد المخرجات، لأن توقع شدة البكسل (القيمة بين 0 وال 255) قد تكون أقرب إلى الإنحدار منها إلى التصنيف، ولا تقتصر أنظمة التصنيف متعددة المخرجات على مهام التصنيف، حيث يمكن أن يكون الخرج يحتوي على عدة تسميات (labels) وعدة مخرجات (values) في نفس الوقت.
سنقوم بإنشاء مجموعتي تدريب واختبار بعد اضافة تشويش على صور مجموعة MNIST إلى كل بكسل عن طريق الدالة randint()
وسيكون هدف هذه المجموعات هو الصور الاصلية:
noise = np.random.randint(0, 100, (len(X_train), 784))
X_train_mod = X_train + noise
noise = np.random.randint(0, 100, (len(X_test), 784))
X_test_mod = X_test + noise
y_train_mod = X_train
y_test_mod = X_test
دعنا نلقي نظرة على صورة من مجموعة الاختبار (نعم نحن نتطفل على بيانات الإختبار، ويجب أن تكون خجلاً من نفسك الآن :P ):
سنجد على اليسار صورة الدخل وهي مشوشة، وعلى اليمين الصورة الهدف النظيفة، دعنا ندرب المصنف ليقوم بتنظيف هذه الصور:
knn_clf.fit(X_train_mod, y_train_mod)
clean_digit = knn_clf.predict([X_test_mod[some_index]])
plot_digit(clean_digit)
يبدو قريباً بما فيه الكفاية من الصورة الأصلية، وبهذا سنختتم كلامنا على التصنيف، آملاً أنك صرت تعرف كيفية تحديد مقاييس جيدة لمهام التصنيف، واختيار الموازنة الجيدة بين الدقة / التذكر، وبناء أنظمة تصنيف جيدة بشكل عام لمجموعة متنوعة من المهام.
المصطلح | الترجمة |
---|---|
class | فئة |
cross-validation | التحقق المتقاطع |
k-folds | الطيات |
Stratified Sampling | أخذ عينات طبقية |
skewed dataset | مجموعة بيانات منحرفة |
true positives | ايجابيات حقيقية |
false positives | ايجابيات خاطئة |
true negatives | سلبيات حقيقية |
false negatives | سلبيات خاطئة |
recall | التذكر |
sensitivity | الحساسية |
true positive rate | معدل الإيجابي الحقيقي |
Confusion Matrix | مصفوفة الإرتباك |
receiver operating characteristic ROC | خاصية تشغيل المستقْبل |
Area Under the Curve - AUC | المساحة تحت المنحنى |
one-vs-all | واحد مقابل الكل |
Multilabel Classification | التصنيف متعدد التسميات |
Multioutput Classification | التصنيف متعدد المخرجات |
Footnotes
-
يخزن Scikit-Learn مجموعات البيانات التي يتم تنزيلها بشكل افتراضي في هذا المسار $HOME/scikit_learn_data. ↩
-
قد يكون خلط الأمثلة فكرة سيئة في بعض الأحيان، على سبيل المثال: إذا كنت تعمل على بيانات السلاسل الزمنية (مثل أسعار سوق الأسهم أو الأحوال الجوية). ↩
-
تذكر أن دماغنا أداة رائعة للتعرف على الأنماط، وأن عيوننا تقوم بالكثير من المعالجة المسبقة المعقدة قبل أن ترسل أي معلومات إلى وعينا، لذا فإن حقيقة أنه تبدو بسيطة لا تعني أنها كذلك. ↩
-
توفر Scikit-Learn عدداً قليلاً من خيارات المتوسط الأخرى، ومقاييس المصنفات متعددة التسميات، انظر مستندات التوثيق لمزيد من المعلومات. ↩