{"id":"l-ml-4-1","courseId":"ml-crash-course","lang":"ar","kind":"lesson","summary":"استبدل التقسيم الواحد إلى تدريب واختبار بالتحقق المتقاطع k-fold، واقرأ متوسط نتائج الطيّات وتشتّتها، واحكم ما إذا كان الفرق بين النماذج حقيقيًا أم ضجيجًا.","html":"<p>طوال القسم 3 جاءت كل مقارنة مع تحفّظ: “على 100 عميل اختبار، النقطتان تعنيان عميلين”. حققت الغابة 0.76، والـ boosting 0.78، والانحدار اللوجستي 0.72. فهل كان الـ boosting أفضل حقًا، أم حالفه الحظ مع بيانات الاختبار؟ تقسيم واحد لا يستطيع أن يخبرك. تحتاج إلى عدة تقسيمات، وهذا ما يمنحك إياه التحقق المتقاطع (cross-validation).</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">خمسة تقسيمات بدل واحد</h2>\n<p>يقطّع <strong>التحقق المتقاطع k-fold</strong> بيانات التدريب إلى k طيّة متساوية (5 هي القيمة الافتراضية المعتادة). ويدرّب k نموذجًا: كلٌّ منها يتعلّم من k−1 طيّة ويُقيَّم على الطيّة التي لم يرَها. ينتهي بك الأمر بـ k نتيجة، وكل صف تدريب يكون قد وقع في طيّة تحقق مرة واحدة بالضبط.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 260\" role=\"img\" aria-labelledby=\"l-ml-4-1-t1\"><defs><marker id=\"arrow-l-ml-4-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-ml-4-1-t1\">خمسة صفوف، واحد لكل جولة. في كل صف تظهر بيانات التدريب كخمس كتل؛ تُبرَز كتلة مختلفة كطيّة تحقق في كل مرة، وتُستخدم الأربع الأخرى للتدريب. وكل جولة تنتج نتيجة واحدة.</title>\n  <text class=\"d-label-muted\" x=\"20\" y=\"20\">الجولة</text>\n  <text class=\"d-label-muted\" x=\"590\" y=\"20\">النتيجة</text>\n  <text class=\"d-label\" x=\"30\" y=\"52\">1</text>\n  <rect class=\"d-box-warn\" x=\"60\" y=\"34\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"160\" y=\"34\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"260\" y=\"34\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"360\" y=\"34\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"460\" y=\"34\" width=\"96\" height=\"28\" rx=\"4\"/>\n  <text class=\"d-code\" x=\"590\" y=\"53\">0.72</text>\n  <text class=\"d-label\" x=\"30\" y=\"92\">2</text>\n  <rect class=\"d-box\" x=\"60\" y=\"74\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box-warn\" x=\"160\" y=\"74\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"260\" y=\"74\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"360\" y=\"74\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"460\" y=\"74\" width=\"96\" height=\"28\" rx=\"4\"/>\n  <text class=\"d-code\" x=\"590\" y=\"93\">0.75</text>\n  <text class=\"d-label\" x=\"30\" y=\"132\">3</text>\n  <rect class=\"d-box\" x=\"60\" y=\"114\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"160\" y=\"114\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box-warn\" x=\"260\" y=\"114\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"360\" y=\"114\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"460\" y=\"114\" width=\"96\" height=\"28\" rx=\"4\"/>\n  <text class=\"d-code\" x=\"590\" y=\"133\">0.68</text>\n  <text class=\"d-label\" x=\"30\" y=\"172\">4</text>\n  <rect class=\"d-box\" x=\"60\" y=\"154\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"160\" y=\"154\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"260\" y=\"154\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box-warn\" x=\"360\" y=\"154\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"460\" y=\"154\" width=\"96\" height=\"28\" rx=\"4\"/>\n  <text class=\"d-code\" x=\"590\" y=\"173\">0.81</text>\n  <text class=\"d-label\" x=\"30\" y=\"212\">5</text>\n  <rect class=\"d-box\" x=\"60\" y=\"194\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"160\" y=\"194\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"260\" y=\"194\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box\" x=\"360\" y=\"194\" width=\"96\" height=\"28\" rx=\"4\"/><rect class=\"d-box-warn\" x=\"460\" y=\"194\" width=\"96\" height=\"28\" rx=\"4\"/>\n  <text class=\"d-code\" x=\"590\" y=\"213\">0.68</text>\n  <text class=\"d-label-muted\" x=\"60\" y=\"248\">المُبرَزة = طيّة التحقق، والبقية = طيّات التدريب</text>\n</svg><figcaption>التحقق المتقاطع بخمس طيّات</figcaption></figure>\n<p>متوسط النتائج الـ k تقدير أثبت من أي تقسيم منفرد. والتشتّت بينها لا يقل قيمة: فهو يبيّن كم يمكن أن تتحرك النتيجة لأسباب لا علاقة لها بالنموذج.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">التحقق المتقاطع لنماذج الانقطاع</h2>\n<p>تنفّذ <code dir=\"ltr\">cross_val_score</code> الحلقة كلها لمقياس واحد؛ وتتعامل <code dir=\"ltr\">cross_validate</code> مع عدة مقاييس دفعة واحدة. في التصنيف، مرّر <code dir=\"ltr\">StratifiedKFold</code> مع <code dir=\"ltr\">shuffle=True</code> كي تحافظ كل طيّة على نسبة الانقطاع البالغة 55% وتُخلط الصفوف أولًا. أنشئ الـ splitter مرة واحدة وأعد استخدامه لكل نموذج كي تواجه كلها الطيّات نفسها تمامًا.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"0\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.dummy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> DummyClassifier</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.ensemble </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> HistGradientBoostingClassifier, RandomForestClassifier</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.linear_model </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> LogisticRegression</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.model_selection </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StratifiedKFold, cross_validate, train_test_split</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.pipeline </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.preprocessing </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StandardScaler</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.tree </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> DecisionTreeClassifier</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">churn </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churn.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">features </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"tenure_days\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"total_spent\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"avg_order_value\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">            \"days_since_last_order\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"used_coupon\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"support_tickets\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mobile_share\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X_train, X_test, y_train, y_test </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split(</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    churn[features], churn[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">test_size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.25</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    stratify</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">churn[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">cv </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StratifiedKFold(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">n_splits</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">shuffle</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">models </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"dummy\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: DummyClassifier(),</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"logistic\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: make_pipeline(StandardScaler(), LogisticRegression()),</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"tree depth 3\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: DecisionTreeClassifier(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">max_depth</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"forest\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: RandomForestClassifier(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">n_estimators</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">200</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">min_samples_leaf</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"boosting\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: HistGradientBoostingClassifier(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">max_depth</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">learning_rate</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.05</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">                                               max_iter</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> name, model </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> models.items():</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    r </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> cross_validate(model, X_train, y_train, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">cv</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">cv, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">scoring</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"accuracy\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"roc_auc\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    acc, auc </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> r[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"test_accuracy\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], r[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"test_roc_auc\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:13}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> accuracy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">acc.mean()</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.3f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ± </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">acc.std()</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.3f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">   ROC AUC </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">auc.mean()</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.3f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ± </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">auc.std()</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.3f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>اقرأ التشتّت أولًا. تتراوح دقة الانحدار اللوجستي بين 0.68 و0.81 عبر الطيّات، وهذا تأرجح بـ 13 نقطة لا سبب له إلا أيّ العملاء وقع في أيّ طيّة. وفي مواجهة هذا الضجيج، لا تعني فروق الـ accuracy بين النماذج الحقيقية الأربعة (من 0.73 إلى 0.76) شيئًا يُذكر: فكل فجوة أصغر من انحراف معياري واحد.</p>\n<p>أما الـ ROC AUC، الذي يقيّم الترتيب كاملًا، فأكثر حسمًا: يقف الانحدار اللوجستي والغابة عند نحو 0.81، والـ boosting عند نحو 0.79، وتتأخر الشجرة بعمق 3 عند 0.74 مع أكبر تشتّت. هذا نمط يمكنك التصرّف بناءً عليه.</p>\n<p>بالنسبة إلى قاعدة الـ 80 يومًا، يعني التحقق المتقاطع إعادة اختيار الحدّ الفاصل على كل مجموعة من طيّات التدريب وتقييمه على الطيّة المحجوزة. وبهذه الطريقة يبلغ متوسطها 0.70 من الـ accuracy. وكل نموذج حقيقي يتفوّق عليها، ببضع نقاط.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">ما الذي يمكنك استنتاجه</h2>\n<p>مجتمعةً، يحكي التحقق المتقاطع قصة أهدأ مما حكاه التقسيم الواحد في القسم 3. كانت دقة الـ boosting البالغة 0.78 على الاختبار حظًا جزئيًا؛ فعبر خمس طيّات هي 0.75، ضمن هامش الضجيج مع كل ما عداها، وهو يرتّب أسوأ من الانحدار اللوجستي. والغابة والانحدار اللوجستي متعادلان في الترتيب. ومع التعادل، فضّل النموذج الأبسط: الانحدار اللوجستي يتدرّب في أجزاء من الثانية، ومعاملاته قابلة للشرح، ولا شيء فيه يُضبط سوى <code dir=\"ltr\">C</code>.</p>\n<p>هذه هي مكافأة المتشكّك. فبدون التحقق المتقاطع كنت ستطلق الـ boosting بسبب رقم حالفه الحظ.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>مقارنة النماذج على تقسيمات مختلفة</span></p>\n<p>استدعاء <code dir=\"ltr\">cross_val_score(model, X, y, cv=5)</code> لكل نموذج على حدة لا بأس به في المصنِّفات (فقيمة <code dir=\"ltr\">cv</code> الصحيحة تعطي الطيّات الطبقية نفسها غير المخلوطة في كل مرة). لكن خلط الـ splitters، أو استخدام <code dir=\"ltr\">shuffle=True</code> دون <code dir=\"ltr\">random_state</code>، أو مقارنة نتيجة تحقق متقاطع لنموذج بنتيجة اختبار واحدة لآخر، يجعل المقارنة مليئة بالضجيج أو غير عادلة. أنشئ splitter واحدًا ومرّره إلى كل شيء.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">اختيار k، وما لا يصلحه التحقق المتقاطع</h2>\n<ul>\n<li><strong>k = 5</strong> هو المعيار. و<strong>k = 10</strong> يعطي كل نموذج بيانات تدريب أكثر وتقديرًا أقل تشاؤمًا بقليل، بضعف الكلفة. مع مجموعات البيانات الصغيرة جدًا (بضع عشرات من الصفوف)، تساعد الطيّات الأكثر؛ ومع الكبيرة، تكفي حتى 3 طيّات.</li>\n<li>نتائج الطيّات ليست مستقلة (فطيّات التدريب متداخلة)، لذا عامِل الانحراف المعياري كدليل تقريبي على الضجيج، لا كهامش خطأ رسمي.</li>\n<li>التحقق المتقاطع لا يحميك من التسرّب. فإذا تسلّلت معلومات من طيّة التحقق إلى التدريب، تصبح كل طيّة متفائلة. و<a href=\"/ar/lesson/ml-crash-course/l-ml-4-4\">درس تسرّب البيانات</a> يدور حول ذلك بالضبط.</li>\n</ul>\n<aside class=\"callout callout-note\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">ملاحظة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>أين تقع بيانات الاختبار الآن</span></p>\n<p>يعمل التحقق المتقاطع على <code dir=\"ltr\">X_train</code> وحدها. إنه أداتك لاختيار النماذج والـ features والإعدادات. وحين تنتهي من الاختيار، درّب الفائز على <code dir=\"ltr\">X_train</code> كاملة وقيّمه مرة واحدة على <code dir=\"ltr\">X_test</code>. هذا الرقم الوحيد هو ما تنشره.</p>\n</aside>\n<p>حتى الآن استخدم كل نموذج الأعمدة الرقمية الثمانية. لكن البيانات تحتوي أيضًا على الدول والشرائح ودرجة رضا نصف فارغة تحتاج إلى ترميز وملء قبل أي شيء. يبني الدرس القادم pipeline يفعل كل ذلك داخل كل طيّة، فيبقى التحقق المتقاطع أمينًا.</p>\n","toc":[{"id":"sec-1","text":"خمسة تقسيمات بدل واحد","level":2},{"id":"sec-2","text":"التحقق المتقاطع لنماذج الانقطاع","level":2},{"id":"sec-3","text":"ما الذي يمكنك استنتاجه","level":2},{"id":"sec-4","text":"اختيار k، وما لا يصلحه التحقق المتقاطع","level":2}],"takeaways":["يدرّب التحقق المتقاطع k-fold عدد k من النماذج، كلٌّ منها يُقيَّم على طيّة (fold) مختلفة، فيُستخدم كل صف تدريب للتحقق مرة واحدة بالضبط.","متوسط نتائج الطيّات يقدّر الأداء؛ وانحرافها المعياري يخبرك كم يمكن أن يضلّلك تقسيم واحد.","استخدم <code dir=\"ltr\">StratifiedKFold</code> مع <code dir=\"ltr\">shuffle=True</code> و<code dir=\"ltr\">random_state</code> ثابت في التصنيف، ومرّر الـ splitter نفسه إلى كل نموذج تقارنه.","تُعيد <code dir=\"ltr\">cross_validate</code> عدة مقاييس دفعة واحدة، إضافة إلى أزمنة التدريب والتقييم.","طبّق التحقق المتقاطع على بيانات التدريب لتختار؛ واحتفظ ببيانات الاختبار لفحص نهائي واحد."],"further":[{"title":"Cross-validation: evaluating estimator performance","url":"https://scikit-learn.org/stable/modules/cross_validation.html"},{"title":"cross_validate","url":"https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_validate.html"},{"title":"StratifiedKFold","url":"https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html"}],"quiz":[{"q":"<p>في تحقق متقاطع من 5 طيّات على 298 صف تدريب، كم نموذجًا يُدرَّب، وعلى كم صفًا يتدرّب كلٌّ منها؟</p>","options":[{"text":"نموذج واحد، مدرَّب على الصفوف الـ 298 كلها ومقيَّم خمس مرات.","why":"تقييم نموذج واحد خمس مرات على بيانات تدرّب عليها يقيس الحفظ، لا التعميم."},{"text":"خمسة نماذج، كلٌّ منها مدرَّب على نحو 60 صفًا ومقيَّم على البقية.","why":"هذا معكوس. كل نموذج يُقيَّم على طيّة واحدة (نحو 60 صفًا) ويتدرّب على الطيّات الأربع الأخرى."},{"text":"خمسة نماذج، كلٌّ منها مدرَّب على نحو 238 صفًا ومقيَّم على الـ 60 المتبقية.","why":"صحيح. كل صف يقع في طيّة التحقق مرة واحدة، وفي طيّات التدريب أربع مرات."},{"text":"خمسة وعشرون نموذجًا، واحد لكل زوج من الطيّات.","why":"يدرّب الـ k-fold العادي نموذجًا واحدًا لكل طيّة. المخططات المكرّرة أو المتداخلة تدرّب أكثر، لكن هذا ليس منها."}],"answer":2},{"q":"<p>يحقق النموذج A دقة 0.75 ± 0.04 عبر خمس طيّات؛ ويحقق النموذج B دقة 0.73 ± 0.05. ما الاستنتاج المنصف؟</p>","options":[{"text":"الفرق صغير مقارنةً بالتشتّت بين الطيّات، فعاملهما على أنهما شبه متعادلين.","why":"صحيح. فجوة نقطتين داخل تشتّت من أربع إلى خمس نقاط قد تنقلب بسهولة مع خلط آخر."},{"text":"A أفضل بوضوح، لأن متوسطه أعلى.","why":"المتوسطات وحدها تتجاهل الضجيج. ومع تشتّت بهذا الحجم، لا يكون الترتيب موثوقًا."},{"text":"B أفضل، لأن الانحراف المعياري الأكبر يعني أنه يتكيّف أكثر.","why":"التشتّت الأكبر يعني أداءً أقل استقرارًا، لا نقطة قوة."}],"answer":0},{"q":"<p>لماذا تستخدم كائن <code dir=\"ltr\">StratifiedKFold(..., random_state=42)</code> نفسه لكل نموذج تقارنه؟</p>","options":[{"text":"لأن الـ splitters المختلفة ستجعل بعض النماذج تتدرّب أسرع.","why":"السرعة لا تتأثر؛ والمسألة تتعلّق بعدالة المقارنة."},{"text":"لأن scikit-learn يشترط splitter واحدًا لكل سكربت.","why":"لا توجد قاعدة كهذه؛ يمكنك إنشاء ما تشاء منها."},{"text":"لأنه يخلط البيانات بشكل مختلف لكل نموذج، وهذا أعدل.","why":"العكس هو الصحيح. الخلطات المختلفة تضيف ضجيجًا إلى المقارنة."},{"text":"لأن كل نموذج يُقيَّم حينها على الطيّات نفسها تمامًا، فتأتي الفروق من النماذج لا من التقسيمات.","why":"صحيح. المقارنات المزدوجة على طيّات متطابقة أقل ضجيجًا بكثير."}],"answer":3},{"q":"<p>بعد أن يختار التحقق المتقاطع الانحدار اللوجستي، ماذا يجب أن تفعل ببيانات الاختبار؟</p>","options":[{"text":"أضفها إلى التحقق المتقاطع لتحصل على طيّات أكثر.","why":"عندها لا يبقى شيء لتقدير نهائي غير منحاز."},{"text":"درّب النموذج المختار على بيانات التدريب كلها وقيّمه على بيانات الاختبار مرة واحدة.","why":"صحيح. نتيجة الاختبار هي الرقم الذي تنشره، ولم تؤثر في أي قرار."},{"text":"تخطّها، فنتيجة التحقق المتقاطع غير منحازة أصلًا.","why":"استُخدمت نتيجة التحقق المتقاطع للاختيار بين النماذج، فهي متفائلة قليلًا لصالح الفائز."}],"answer":1}],"exercise":{"kind":"python","files":["churn.csv"],"starter":"import pandas as pd\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import StratifiedKFold, cross_validate, train_test_split\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import StandardScaler\n\nchurn = pd.read_csv(\"churn.csv\")\nfeatures = [\"tenure_days\", \"orders\", \"total_spent\", \"avg_order_value\",\n            \"days_since_last_order\", \"used_coupon\", \"support_tickets\", \"mobile_share\"]\nX_train, X_test, y_train, y_test = train_test_split(\n    churn[features], churn[\"churned\"], test_size=0.25, random_state=42,\n    stratify=churn[\"churned\"])\n\ncv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n\n# 1. Cross-validate both models on the TRAINING data with the same `cv`,\n#    scoring both \"accuracy\" and \"roc_auc\"\nlogistic = make_pipeline(StandardScaler(), LogisticRegression())\nknn = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=15))\n\n# 2. Mean ROC AUC for each model, and the standard deviation of k-NN's fold AUCs\nlogistic_auc = None\nknn_auc = None\nknn_auc_std = None\n\n# 3. Fit the model with the higher mean AUC on all training data; score accuracy on the test set once\nfinal_test_acc = None\n","solution":"import pandas as pd\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import StratifiedKFold, cross_validate, train_test_split\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import StandardScaler\n\nchurn = pd.read_csv(\"churn.csv\")\nfeatures = [\"tenure_days\", \"orders\", \"total_spent\", \"avg_order_value\",\n            \"days_since_last_order\", \"used_coupon\", \"support_tickets\", \"mobile_share\"]\nX_train, X_test, y_train, y_test = train_test_split(\n    churn[features], churn[\"churned\"], test_size=0.25, random_state=42,\n    stratify=churn[\"churned\"])\n\ncv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n\nlogistic = make_pipeline(StandardScaler(), LogisticRegression())\nknn = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=15))\n\nr_log = cross_validate(logistic, X_train, y_train, cv=cv, scoring=[\"accuracy\", \"roc_auc\"])\nr_knn = cross_validate(knn, X_train, y_train, cv=cv, scoring=[\"accuracy\", \"roc_auc\"])\n\nlogistic_auc = r_log[\"test_roc_auc\"].mean()\nknn_auc = r_knn[\"test_roc_auc\"].mean()\nknn_auc_std = r_knn[\"test_roc_auc\"].std()\n\nwinner = logistic if logistic_auc >= knn_auc else knn\nfinal_test_acc = winner.fit(X_train, y_train).score(X_test, y_test)\nprint(round(logistic_auc, 3), round(knn_auc, 3), round(knn_auc_std, 3), final_test_acc)\n","checks":[{"id":"logistic-auc","test":"logistic_auc is not None and abs(float(logistic_auc) - 0.807) < 0.002"},{"id":"knn-auc","test":"knn_auc is not None and abs(float(knn_auc) - float(__import__('sklearn.model_selection', fromlist=['x']).cross_val_score(__import__('sklearn.pipeline', fromlist=['x']).make_pipeline(__import__('sklearn.preprocessing', fromlist=['x']).StandardScaler(), __import__('sklearn.neighbors', fromlist=['x']).KNeighborsClassifier(n_neighbors=15)), X_train, y_train, cv=__import__('sklearn.model_selection', fromlist=['x']).StratifiedKFold(n_splits=5, shuffle=True, random_state=42), scoring='roc_auc').mean())) < 1e-6"},{"id":"knn-std","test":"knn_auc_std is not None and abs(float(knn_auc_std) - float(__import__('sklearn.model_selection', fromlist=['x']).cross_val_score(__import__('sklearn.pipeline', fromlist=['x']).make_pipeline(__import__('sklearn.preprocessing', fromlist=['x']).StandardScaler(), __import__('sklearn.neighbors', fromlist=['x']).KNeighborsClassifier(n_neighbors=15)), X_train, y_train, cv=__import__('sklearn.model_selection', fromlist=['x']).StratifiedKFold(n_splits=5, shuffle=True, random_state=42), scoring='roc_auc').std())) < 1e-6"},{"id":"final","test":"final_test_acc is not None and abs(float(final_test_acc) - 0.72) < 0.005"}],"title":"الانحدار اللوجستي أم k-NN؟","prompt":"<p>احسم اختيار النموذج بالتحقق المتقاطع، ثم استخدم بيانات الاختبار مرة واحدة.</p>\n<ol>\n<li>طبّق التحقق المتقاطع على <code dir=\"ltr\">logistic</code> و<code dir=\"ltr\">knn</code> على بيانات التدريب باستخدام <code dir=\"ltr\">cv</code> المعطى، مع المقياسين <code dir=\"ltr\">&quot;accuracy&quot;</code> و<code dir=\"ltr\">&quot;roc_auc&quot;</code>.</li>\n<li><code dir=\"ltr\">logistic_auc</code> و<code dir=\"ltr\">knn_auc</code>: متوسط الـ ROC AUC لكل نموذج عبر الطيّات. و<code dir=\"ltr\">knn_auc_std</code>: الانحراف المعياري لقيم AUC الخاصة بـ k-NN عبر الطيّات.</li>\n<li><code dir=\"ltr\">final_test_acc</code>: درّب النموذج صاحب متوسط AUC الأعلى على <code dir=\"ltr\">X_train</code> كاملة، ثم احسب دقته على بيانات الاختبار.</li>\n</ol>\n","hints":["تُعيد <code dir=\"ltr\">cross_validate(model, X_train, y_train, cv=cv, scoring=[&quot;accuracy&quot;, &quot;roc_auc&quot;])</code> قاموسًا فيه المصفوفة <code dir=\"ltr\">&quot;test_roc_auc&quot;</code>.","يعمل <code dir=\"ltr\">.mean()</code> و<code dir=\"ltr\">.std()</code> مباشرة على هذه المصفوفات.","اختر الفائز بعبارة <code dir=\"ltr\">if</code> بسيطة، ثم <code dir=\"ltr\">winner.fit(X_train, y_train).score(X_test, y_test)</code>."],"explanation":"<p>يرتّب k-NN بعد توحيد المقاييس ومع 15 جارًا العملاءَ أسوأ قليلًا من الانحدار اللوجستي في المتوسط، وتتفاوت نتائج طيّاته بشكل ملحوظ. ولأن النموذجين واجها طيّات متطابقة، فالمقارنة عادلة. يُعاد تدريب الفائز على كل صفوف التدريب ويُقيَّم مرة واحدة على بيانات الاختبار (0.72): هذا هو الرقم الذي تنشره، ولم يكن له أي دور في الاختيار.</p>\n","checkLabels":{"logistic-auc":"logistic_auc هو متوسط الـ ROC AUC بالتحقق المتقاطع","knn-auc":"knn_auc هو متوسط الـ ROC AUC لـ k-NN على الطيّات نفسها","knn-std":"knn_auc_std هو تشتّت قيم AUC لـ k-NN عبر الطيّات","final":"final_test_acc هي دقة الفائز على بيانات الاختبار"}},"words":638,"readMinutes":4}