EduFlow

الوحدة 1 · ما هو تعلّم الآلة

خطوط الأساس: النموذج الذي يجب التغلّب عليه

17 دقراءة 4 دتمرين Python

الدرس في لمحة

ابنِ خطَّي الأساس اللذين يجب أن يتفوّق عليهما كل نموذج، أي نموذج وهمي يتنبأ بالفئة الأكثر شيوعًا وقاعدة عمل من سطر واحد، واحكم على النموذج بمقدار تحسّنه عليهما لا برقمه المجرّد.

  1. 1أغبى نموذج ممكن
  2. 2القاعدة التي كان سيكتبها شخص ما
  3. 3خطوط الأساس في الانحدار
  4. 4التحسّن لا النتيجة

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

شجرتك بعمق 3 تتنبأ بالانقطاع بدقة 73% على عملاء لم ترَهم. قبل أن تعرض هذا الرقم على أحد، اطرح السؤال الذي يطرحه كل عالم بيانات متمرّس أولًا: مقارنةً بماذا؟ بلا مقارنة، 73% ليست جيدة ولا سيئة. إنها مجرد رقم.

أغبى نموذج ممكن

خط الأساس الأول يتجاهل الـ features تمامًا. في التصنيف يتنبأ بالفئة الأكثر شيوعًا للجميع. وفي الانحدار يتنبأ بالمتوسط. يوفّر scikit-learn كليهما كـ estimators حقيقية، فيندمجان في الكود نفسه الذي تكتبه لأي نموذج.

Python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.dummy import DummyClassifier

churn = pd.read_csv("churn.csv")
features = ["tenure_days", "orders", "total_spent", "avg_order_value",
            "days_since_last_order", "used_coupon", "support_tickets", "mobile_share"]
X_train, X_test, y_train, y_test = train_test_split(
    churn[features], churn["churned"], test_size=0.25, random_state=42,
    stratify=churn["churned"])

dummy = DummyClassifier(strategy="most_frequent").fit(X_train, y_train)
print("dummy predicts:", dummy.predict(X_test)[:5])
print("dummy accuracy:", dummy.score(X_test, y_test))

التنبؤ بـ “منقطع” لكل عميل يصيب في 55% من الحالات، لأن 55% من العملاء انقطعوا فعلًا. هذا هو الحدّ الأدنى. ودقة شجرتك البالغة 73% تزيد عليه بـ 18 نقطة، وهذا تقدّم حقيقي، لكنك لم تنتهِ من الأسئلة بعد.

القاعدة التي كان سيكتبها شخص ما

خط الأساس الثاني هو الذي يؤلم. قبل تعلّم الآلة، كان أحدهم في Cartwheel سيكتب قاعدة: “إذا لم يطلب العميل شيئًا منذ N يومًا، فهو في طريقه إلى الانقطاع”. تختار N بالطريقة التي تدرّب بها نموذجًا: على بيانات التدريب وحدها.

Python
import pandas as pd
from sklearn.model_selection import train_test_split

churn = pd.read_csv("churn.csv")
features = ["tenure_days", "orders", "total_spent", "avg_order_value",
            "days_since_last_order", "used_coupon", "support_tickets", "mobile_share"]
X_train, X_test, y_train, y_test = train_test_split(
    churn[features], churn["churned"], test_size=0.25, random_state=42,
    stratify=churn["churned"])

def rule_accuracy(days, X, y):
    predicted = (X["days_since_last_order"] > days).astype(int)
    return (predicted == y).mean()

candidates = range(30, 301, 10)
best_days = max(candidates, key=lambda d: rule_accuracy(d, X_train, y_train))
print("best cut-off on training data:", best_days, "days")
print("rule accuracy on test data:", rule_accuracy(best_days, X_test, y_test))

تحقق القاعدة 72% على بيانات الاختبار. وتحقق الشجرة 73%. على 100 عميل اختبار، هذا فرق شخص واحد.

مخطط أعمدة لدقة الاختبار: النموذج الوهمي للفئة الأكثر شيوعًا 55%، وقاعدة الـ 80 يومًا 72%، والشجرة بعمق 3 بنسبة 73%. النموذج الوهمي 55% قاعدة 80 يومًا 72% شجرة بعمق 3 73% طول العمود = الدقة (0% عند المحور، و100% عند 610)
الـ accuracy على عملاء الاختبار المئة أنفسهم

هذا ليس فشلًا. بل هو أنفع ما تعلّمته حتى الآن. فهو يخبرك أن معظم الإشارة الدالّة على الانقطاع تكمن في عمود واحد، هو حداثة آخر طلب، وأن شجرة بثمانية features لم تضف عليه حتى الآن شيئًا يُذكر. ويعطيك أيضًا هدفًا: من الآن فصاعدًا، على النموذج أن يتفوّق على 72%، لا على 55%، كي يستحق الإطلاق والشرح والمراقبة.

خطوط الأساس في الانحدار

المقابل في الانحدار هو DummyRegressor، الذي يتنبأ بمتوسط التدريب لكل صف. وقيمة score (أي R²) على بيانات الاختبار قريبة من 0، لأن R² معرَّف على أنه التحسّن مقارنةً بالتنبؤ بالمتوسط. ستستخدمه في القسم 2.

Python
from sklearn.datasets import load_diabetes
from sklearn.dummy import DummyRegressor
from sklearn.model_selection import train_test_split

X, y = load_diabetes(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
dummy = DummyRegressor(strategy="mean").fit(X_train, y_train)
print("predicts", round(dummy.predict(X_test)[0], 1), "for everyone")
print("R² on test:", round(dummy.score(X_test, y_test), 3))

التحسّن لا النتيجة

من الآن فصاعدًا، اعرض النموذج بمقدار تحسّنه على خطوط الأساس: “accuracy بنسبة 73%، مقابل 55% للفئة الأكثر شيوعًا و72% لقاعدة الـ 80 يومًا”. هذه الجملة الواحدة تخبر القارئ أكثر مما يخبره أي رقم منفرد، وتحميك من خداع نفسك.

أبقِ خطوط الأساس في كودك، بجانب النماذج، وأعد تشغيلها كلما تغيّرت البيانات أو التقسيم. فخط الأساس المحسوب مرة واحدة على تقسيم قديم ثم المقتبس إلى الأبد أسوأ من عدم وجوده، لأنه يبدو كأنه دليل. عمليًا، يجب أن يضع جدول المقارنة في آخر الـ notebook النموذجَ الوهمي في الأعلى، وأفضل قاعدة تحته، ثم كل النماذج بعد ذلك، وكلها محسوبة على صفوف الاختبار نفسها تمامًا.

وكن صادقًا بشأن الضجيج أيضًا. مع 100 عميل اختبار، العميل الواحد يساوي نقطة مئوية واحدة، لذا قد تظهر فروق من نقطتين أو ثلاث أو تختفي مع خلط مختلف. سيستبدل القسم 4 التقسيمَ الواحد بالتحقق المتقاطع، الذي يخبرك كم تتذبذب النتيجة. وحتى ذلك الحين، عامِل الفجوات الصغيرة على أنها تعادل.

والـ accuracy ليست الطريقة الوحيدة للمقارنة أيضًا. فالنموذج الذي يرتّب العملاء بحسب خطر انقطاعهم يستطيع أن يسلّم فريق التسويق أكثر 30 عميلًا مرجّحًا للانقطاع، وهذا ما لا تستطيعه قاعدة نعم/لا. يقيس القسم 3 ذلك بالضبط. لكن أولًا، يفتح القسم 2 أبسط نموذج على الإطلاق، الخط المستقيم، لترى ما معنى “التعلّم” بالأرقام.

أهم النقاط

  • النتيجة لا تعني شيئًا حتى تقارنها بخط أساس (baseline) مبني على التقسيم نفسه.
  • يتنبأ DummyClassifier بالفئة الأكثر شيوعًا ويتنبأ DummyRegressor بالمتوسط؛ وهما يحدّدان الحدّ الأدنى.
  • القاعدة البسيطة المضبوطة على بيانات التدريب هي خط الأساس الذي يُبقيك صادقًا بشأن ما إذا كان النموذج يستحق كلفته.
  • حين تهيمن فئة واحدة، تكافئ الـ accuracy تجاهلَ الفئة النادرة؛ فقد تعني نتيجة 95% أن النموذج لم يجد شيئًا.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

نموذج لكشف الاحتيال يحقق accuracy بنسبة 97%. ونسبة المعاملات الاحتيالية 3%. ما أول ما يجب أن تتحقق منه؟

السؤال 2 من 4

تستخدم قاعدة خط الأساس لديك حدًّا فاصلًا عند 80 يومًا. كيف كان يجب اختيار الرقم 80؟

السؤال 3 من 4

يحقق DummyRegressor() على بيانات السكري قيمة R² قرابة 0 على بيانات الاختبار. لماذا؟

السؤال 4 من 4

شجرة تتفوّق على النموذج الوهمي بـ 18 نقطة، لكنها تتفوّق على قاعدة من سطر واحد بنقطة واحدة. ما الخطوة التالية الأكثر قابلية للدفاع عنها؟

تدريب

تمرين Python

تفوّق على خطوط الأساس، أو لا

ضع نموذج أقرب k جيران بجانب خطَّي الأساس على التقسيم نفسه.

  1. dummy_acc: دقة نموذج DummyClassifier الذي يتنبأ بالفئة الأكثر شيوعًا على بيانات الاختبار.
  2. best_days: الحدّ الفاصل من range(30, 301, 10) الذي يعطي أفضل accuracy على بيانات التدريب للقاعدة “منقطع إذا كان days_since_last_order > الحدّ”. ثم rule_acc: دقة هذه القاعدة على بيانات الاختبار.
  3. knn_acc: دقة KNeighborsClassifier() بإعداداته الافتراضية على بيانات الاختبار.

ملفات البيانات: churn.csv

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.dummy import DummyClassifier
from sklearn.neighbors import KNeighborsClassifier

churn = pd.read_csv("churn.csv")
features = ["tenure_days", "orders", "total_spent", "avg_order_value",
            "days_since_last_order", "used_coupon", "support_tickets", "mobile_share"]
X_train, X_test, y_train, y_test = train_test_split(
    churn[features], churn["churned"], test_size=0.25, random_state=42,
    stratify=churn["churned"])

# 1. Majority-class baseline: test accuracy
dummy_acc = None

# 2. Rule baseline: churned if days_since_last_order > best_days.
#    Pick best_days from range(30, 301, 10) using the TRAINING data only.
best_days = None
rule_acc = None   # the rule's accuracy on the test data

# 3. A k-nearest-neighbours model (default settings): test accuracy
knn_acc = None
جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

(لا مخرجات)

الفحوص

  • dummy_acc هي دقة الفئة الأكثر شيوعًا على بيانات الاختبار
  • best_days هو أفضل حدّ فاصل على بيانات التدريب
  • rule_acc هي دقة تلك القاعدة على بيانات الاختبار
  • knn_acc هي دقة نموذج k-NN على بيانات الاختبار