الوحدة 4 · العمل بالطريقة الصحيحة
التحقق المتقاطع
18 دقراءة 4 دتمرين Python
الدرس في لمحة
استبدل التقسيم الواحد إلى تدريب واختبار بالتحقق المتقاطع k-fold، واقرأ متوسط نتائج الطيّات وتشتّتها، واحكم ما إذا كان الفرق بين النماذج حقيقيًا أم ضجيجًا.
- 1خمسة تقسيمات بدل واحد
- 2التحقق المتقاطع لنماذج الانقطاع
- 3ما الذي يمكنك استنتاجه
- 4اختيار k، وما لا يصلحه التحقق المتقاطع
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
طوال القسم 3 جاءت كل مقارنة مع تحفّظ: “على 100 عميل اختبار، النقطتان تعنيان عميلين”. حققت الغابة 0.76، والـ boosting 0.78، والانحدار اللوجستي 0.72. فهل كان الـ boosting أفضل حقًا، أم حالفه الحظ مع بيانات الاختبار؟ تقسيم واحد لا يستطيع أن يخبرك. تحتاج إلى عدة تقسيمات، وهذا ما يمنحك إياه التحقق المتقاطع (cross-validation).
خمسة تقسيمات بدل واحد
يقطّع التحقق المتقاطع k-fold بيانات التدريب إلى k طيّة متساوية (5 هي القيمة الافتراضية المعتادة). ويدرّب k نموذجًا: كلٌّ منها يتعلّم من k−1 طيّة ويُقيَّم على الطيّة التي لم يرَها. ينتهي بك الأمر بـ k نتيجة، وكل صف تدريب يكون قد وقع في طيّة تحقق مرة واحدة بالضبط.
متوسط النتائج الـ k تقدير أثبت من أي تقسيم منفرد. والتشتّت بينها لا يقل قيمة: فهو يبيّن كم يمكن أن تتحرك النتيجة لأسباب لا علاقة لها بالنموذج.
التحقق المتقاطع لنماذج الانقطاع
تنفّذ cross_val_score الحلقة كلها لمقياس واحد؛ وتتعامل cross_validate مع عدة مقاييس دفعة واحدة. في التصنيف، مرّر StratifiedKFold مع shuffle=True كي تحافظ كل طيّة على نسبة الانقطاع البالغة 55% وتُخلط الصفوف أولًا. أنشئ الـ splitter مرة واحدة وأعد استخدامه لكل نموذج كي تواجه كلها الطيّات نفسها تمامًا.
import pandas as pd
from sklearn.dummy import DummyClassifier
from sklearn.ensemble import HistGradientBoostingClassifier, RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_validate, train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
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"])
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
models = {
"dummy": DummyClassifier(),
"logistic": make_pipeline(StandardScaler(), LogisticRegression()),
"tree depth 3": DecisionTreeClassifier(max_depth=3, random_state=42),
"forest": RandomForestClassifier(n_estimators=200, min_samples_leaf=5, random_state=42),
"boosting": HistGradientBoostingClassifier(max_depth=3, learning_rate=0.05,
max_iter=100, random_state=42),
}
for name, model in models.items():
r = cross_validate(model, X_train, y_train, cv=cv, scoring=["accuracy", "roc_auc"])
acc, auc = r["test_accuracy"], r["test_roc_auc"]
print(f"{name:13} accuracy {acc.mean():.3f} ± {acc.std():.3f} ROC AUC {auc.mean():.3f} ± {auc.std():.3f}")اقرأ التشتّت أولًا. تتراوح دقة الانحدار اللوجستي بين 0.68 و0.81 عبر الطيّات، وهذا تأرجح بـ 13 نقطة لا سبب له إلا أيّ العملاء وقع في أيّ طيّة. وفي مواجهة هذا الضجيج، لا تعني فروق الـ accuracy بين النماذج الحقيقية الأربعة (من 0.73 إلى 0.76) شيئًا يُذكر: فكل فجوة أصغر من انحراف معياري واحد.
أما الـ ROC AUC، الذي يقيّم الترتيب كاملًا، فأكثر حسمًا: يقف الانحدار اللوجستي والغابة عند نحو 0.81، والـ boosting عند نحو 0.79، وتتأخر الشجرة بعمق 3 عند 0.74 مع أكبر تشتّت. هذا نمط يمكنك التصرّف بناءً عليه.
بالنسبة إلى قاعدة الـ 80 يومًا، يعني التحقق المتقاطع إعادة اختيار الحدّ الفاصل على كل مجموعة من طيّات التدريب وتقييمه على الطيّة المحجوزة. وبهذه الطريقة يبلغ متوسطها 0.70 من الـ accuracy. وكل نموذج حقيقي يتفوّق عليها، ببضع نقاط.
ما الذي يمكنك استنتاجه
مجتمعةً، يحكي التحقق المتقاطع قصة أهدأ مما حكاه التقسيم الواحد في القسم 3. كانت دقة الـ boosting البالغة 0.78 على الاختبار حظًا جزئيًا؛ فعبر خمس طيّات هي 0.75، ضمن هامش الضجيج مع كل ما عداها، وهو يرتّب أسوأ من الانحدار اللوجستي. والغابة والانحدار اللوجستي متعادلان في الترتيب. ومع التعادل، فضّل النموذج الأبسط: الانحدار اللوجستي يتدرّب في أجزاء من الثانية، ومعاملاته قابلة للشرح، ولا شيء فيه يُضبط سوى C.
هذه هي مكافأة المتشكّك. فبدون التحقق المتقاطع كنت ستطلق الـ boosting بسبب رقم حالفه الحظ.
اختيار k، وما لا يصلحه التحقق المتقاطع
- k = 5 هو المعيار. وk = 10 يعطي كل نموذج بيانات تدريب أكثر وتقديرًا أقل تشاؤمًا بقليل، بضعف الكلفة. مع مجموعات البيانات الصغيرة جدًا (بضع عشرات من الصفوف)، تساعد الطيّات الأكثر؛ ومع الكبيرة، تكفي حتى 3 طيّات.
- نتائج الطيّات ليست مستقلة (فطيّات التدريب متداخلة)، لذا عامِل الانحراف المعياري كدليل تقريبي على الضجيج، لا كهامش خطأ رسمي.
- التحقق المتقاطع لا يحميك من التسرّب. فإذا تسلّلت معلومات من طيّة التحقق إلى التدريب، تصبح كل طيّة متفائلة. ودرس تسرّب البيانات يدور حول ذلك بالضبط.
حتى الآن استخدم كل نموذج الأعمدة الرقمية الثمانية. لكن البيانات تحتوي أيضًا على الدول والشرائح ودرجة رضا نصف فارغة تحتاج إلى ترميز وملء قبل أي شيء. يبني الدرس القادم pipeline يفعل كل ذلك داخل كل طيّة، فيبقى التحقق المتقاطع أمينًا.
أهم النقاط
- يدرّب التحقق المتقاطع k-fold عدد k من النماذج، كلٌّ منها يُقيَّم على طيّة (fold) مختلفة، فيُستخدم كل صف تدريب للتحقق مرة واحدة بالضبط.
- متوسط نتائج الطيّات يقدّر الأداء؛ وانحرافها المعياري يخبرك كم يمكن أن يضلّلك تقسيم واحد.
- استخدم
StratifiedKFoldمعshuffle=Trueوrandom_stateثابت في التصنيف، ومرّر الـ splitter نفسه إلى كل نموذج تقارنه. - تُعيد
cross_validateعدة مقاييس دفعة واحدة، إضافة إلى أزمنة التدريب والتقييم. - طبّق التحقق المتقاطع على بيانات التدريب لتختار؛ واحتفظ ببيانات الاختبار لفحص نهائي واحد.