تخطَّ إلى المحتوى

تقييم النماذج: مصفوفة الالتباس و Precision و Recall

الدرس 5 من 8 · تعلّم الآلة

في الدرس السابق حصل نموذج الزهور على دقة 100%، وحذّرناك: الدقة وحدها قد تكون مضللة. تخيّل نموذجاً يكشف مرضاً خطيراً، دقته 97%. يبدو ممتازاً، أليس كذلك؟ ماذا لو عرفت أنه لم يكتشف أي مريض؟ في هذا الدرس ستتعلم كيف تقيّم النموذج تقييماً صادقاً: أين يخطئ، وأي نوع من الخطأ، وهل يمكن الوثوق برقم واحد.

سنعمل على بيانات حقيقية مرفقة مع scikit-learn: قياسات مأخوذة من عينات أورام، والمطلوب تصنيف الورم: خبيث (malignant) أم حميد (benign).

from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X = data.data
y = data.target
print(X.shape)
print(data.target_names)
print((y == 0).sum(), (y == 1).sum())
الناتج
(569, 30)
['malignant' 'benign']
212 357

569 عينة، لكل منها 30 ميزة. والتسمية 0 تعني خبيثاً (212 عينة) و 1 تعني حميداً (357 عينة).

ندرّب شجرة قرار بالخطوات التي تعرفها:

from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
print(len(y_test))
print(f'{accuracy_score(y_test, predictions):.3f}')
الناتج
143
0.944

الجديد هنا stratify=y: تجعل نسبة الخبيث إلى الحميد في بيانات الاختبار مثل نسبتها في البيانات كلها. والدقة 94.4% على 143 عينة اختبار. جيد، لكن السؤال المهم: الأخطاء الثمانية، ما نوعها؟

مصفوفة الالتباس (Confusion Matrix) جدول يقارن الحقيقة بالتوقع:

from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, predictions)
print(cm)
الناتج
[[48 5]
[ 3 87]]

الصفوف هي الحقيقة، والأعمدة هي توقع النموذج:

توقّع: خبيث توقّع: حميد
الحقيقة: خبيث 48 (أصاب) 5 (فاته!)
الحقيقة: حميد 3 (إنذار كاذب) 87 (أصاب)

الأرقام على القطر (48 و 87) توقعات صحيحة. والباقي نوعان من الخطأ مختلفان تماماً في خطورتهما:

  • 5 أورام خبيثة قال عنها حميدة: مريض يعود لبيته مطمئناً وهو يحتاج علاجاً. خطأ قد يكلف حياة.
  • 3 أورام حميدة قال عنها خبيثة: إنذار كاذب. مزعج ومقلق، لكن فحصاً إضافياً يصححه.

الدقة جمعت النوعين في رقم واحد وأخفت الفرق. مصفوفة الالتباس مثل كشف حساب مفصّل بدل الرصيد النهائي وحده.

نختار الفئة التي تهمنا (هنا: خبيث) ونسأل سؤالين:

  • الاستدعاء (Recall): من كل الأورام الخبيثة فعلاً، كم اكتشف النموذج؟
  • الضبط (Precision): من كل ما قال النموذج إنه خبيث، كم كان خبيثاً فعلاً؟
from sklearn.metrics import precision_score, recall_score, f1_score
print(f'{recall_score(y_test, predictions, pos_label=0):.3f}')
print(f'{precision_score(y_test, predictions, pos_label=0):.3f}')
print(f'{f1_score(y_test, predictions, pos_label=0):.3f}')
الناتج
0.906
0.941
0.923

pos_label=0 تحدد الفئة التي نقيسها. والحساب من المصفوفة مباشرة:

  • Recall يساوي 48 ÷ (48 + 5) = 0.906: اكتشف 90.6% من الأورام الخبيثة وفاته الباقي.
  • Precision يساوي 48 ÷ (48 + 3) = 0.941: حين يقول «خبيث» يكون محقاً في 94.1% من المرات.
  • F1: رقم واحد يجمع الاثنين، ويكون عالياً فقط إذا كان كلاهما عالياً.

تشبيه يساعدك على التفريق: نموذجك صياد سمك. Recall: كم سمكة في البحيرة وقعت في شبكته؟ و Precision: كم مما في شبكته سمك فعلاً وليس أحذية قديمة؟

يعتمد على ثمن كل خطأ في مشكلتك:

المشكلة الخطأ الأخطر المقياس الأهم
تشخيص مرض خطير تفويت مريض Recall
كشف احتيال بنكي تفويت عملية احتيال Recall
فلتر الرسائل المزعجة حذف رسالة مهمة Precision
ترشيح مقاطع للأطفال عرض مقطع غير مناسب Precision

والاثنان يتعاكسان غالباً: نموذج يصرخ «خبيث» عند أدنى شك يرفع Recall ويكثر الإنذارات الكاذبة، فينخفض Precision.

والدالة classification_report تطبع المقاييس كلها لكل فئة دفعة واحدة:

from sklearn.metrics import classification_report
print(classification_report(y_test, predictions, target_names=data.target_names))
الناتج
precision recall f1-score support
malignant 0.94 0.91 0.92 53
benign 0.95 0.97 0.96 90
accuracy 0.94 143
macro avg 0.94 0.94 0.94 143
weighted avg 0.94 0.94 0.94 143

العمود support عدد العينات الحقيقية من كل فئة في بيانات الاختبار.

لنرَ الفخ الذي بدأنا به الدرس. بيانات فيها 1000 معاملة بنكية، 30 منها فقط احتيال. و «نموذج كسول» يقول عن كل معاملة إنها سليمة دون أن ينظر إليها:

import numpy as np
from sklearn.dummy import DummyClassifier
rng = np.random.default_rng(42)
X_tx = rng.normal(size=(1000, 3))
y_tx = np.zeros(1000, dtype=int)
y_tx[:30] = 1
lazy = DummyClassifier(strategy='most_frequent')
lazy.fit(X_tx, y_tx)
lazy_pred = lazy.predict(X_tx)
print(f'{accuracy_score(y_tx, lazy_pred):.2f}')
print(f'{recall_score(y_tx, lazy_pred):.2f}')
print(confusion_matrix(y_tx, lazy_pred))
الناتج
0.97
0.00
[[970 0]
[ 30 0]]

دقة 97%، و Recall صفر: لم يكتشف عملية احتيال واحدة من الثلاثين! DummyClassifier نموذج لا يتعلم شيئاً، ووظيفته أن يكون خط الأساس الذي تقارن به.

هل الرقم مستقر؟ التحقق المتقاطع

Section titled “هل الرقم مستقر؟ التحقق المتقاطع”

دقتنا 0.944 جاءت من تقسيمة واحدة للبيانات. ماذا لو وقعت العينات السهلة في الاختبار بالصدفة؟ التحقق المتقاطع (Cross-Validation) يقسم البيانات إلى خمسة أجزاء، ويدرّب خمس مرات: في كل مرة جزء مختلف للاختبار والأربعة الباقية للتدريب:

from sklearn.model_selection import cross_val_score
scores = cross_val_score(
DecisionTreeClassifier(max_depth=3, random_state=42), X, y, cv=5
)
print(scores.round(3))
print(f'{scores.mean():.3f}')
الناتج
[0.912 0.895 0.939 0.956 0.894]
0.919

خمس نتائج تتراوح بين 89.4% و 95.6%، ومتوسطها 91.9%. هذا تقدير أصدق من رقم واحد، ويخبرك أيضاً بمقدار التذبذب. مثل الحكم على طالب من خمسة امتحانات بدل امتحان واحد قد يكون حالفه فيه الحظ.

  1. درّب شجرة قرار بعمق 2 على بيانات الأورام، واطبع مصفوفة الالتباس و Recall للفئة الخبيثة.
  2. قارن النتيجة بنموذج DummyClassifier(strategy='most_frequent') على البيانات نفسها.
  3. احسب متوسط الدقة بالتحقق المتقاطع مع cv=10.
  4. تحدٍّ: ارجع لنموذج الزهور من الدرس السابق، واطبع classification_report له، وحدد أي نوعين من الزهور يخلط بينهما النموذج إن أخطأ.
  • الاكتفاء بالدقة: خصوصاً مع البيانات غير المتوازنة. اطبع مصفوفة الالتباس دائماً.
  • قراءة المصفوفة بالمقلوب: في scikit-learn الصفوف للحقيقة والأعمدة للتوقع، والترتيب حسب قيم التسميات (0 ثم 1).
  • التقييم على بيانات التدريب: يعطي أرقاماً وردية لا معنى لها. قيّم دائماً على بيانات لم يرها النموذج.
  • الحكم من تقسيمة واحدة: تغيير random_state قد يغيّر الدقة عدة نقاط. استخدم التحقق المتقاطع للمقارنة بين النماذج.

سؤال

نموذج لكشف مرض خطير: أي مقياس أهم إذا كان تفويت مريض أخطر من إنذار كاذب؟

سؤال

بيانات فيها 99% رسائل سليمة و 1% مزعجة. نموذج يصنّف كل شيء «سليمة». ما دقته؟

  • الدقة نسبة التوقعات الصحيحة، وتخدعك عندما تكون إحدى الفئات نادرة.
  • مصفوفة الالتباس تفصّل الأخطاء: ما فات النموذج، وما أنذر به كذباً.
  • Recall: كم اكتشف من الحالات الحقيقية. و Precision: كم صدق حين أنذر.
  • اختر المقياس حسب ثمن كل خطأ، وقارن بنموذج كسول DummyClassifier.
  • التحقق المتقاطع يعطي تقديراً أصدق من تقسيمة واحدة.