{"id":"l-ml-5-4","courseId":"ml-crash-course","lang":"ar","kind":"lesson","summary":"افحص أخطاء النموذج مجموعةً مجموعة، واختر معيار إنصاف يطابق الضرر، وراقب المدخلات والتنبؤات والنتائج كي تلاحظ حين يبتعد العالم عن بيانات التدريب.","html":"<p>بلغ نموذجك البطل ROC AUC على الاختبار قدره 0.81. هذا رقم واحد لـ 100 عميل من ثماني دول وثلاث شرائح. ومن الممكن تمامًا أن يكون النموذج جيدًا في المتوسط وضعيفًا لمجموعة واحدة، وأن تكون تلك المجموعة هي الأقل قدرة على الشكوى. ومن الممكن أيضًا أن يكون نموذج كان جيدًا في أكتوبر مخطئًا بصمت في مارس. يغطي هذا الدرس الأمرين: الإنصاف (fairness) قبل الإطلاق، والانجراف (drift) بعده.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">من أين يأتي الانحياز</h2>\n<p>نادرًا ما يكون الانحياز (bias) في النموذج سطرًا من الكود. إنه يصل مع البيانات ومع طريقة استخدام التنبؤات:</p>\n<ul>\n<li><strong>الانحياز التاريخي</strong>: الـ labels تسجّل قرارات سابقة، لا الحقيقة. والنموذج المدرَّب على من حصل على الموافقة يتعلّم من حصل على الموافقة، بما في ذلك أي تحيّز في تلك الموافقات.</li>\n<li><strong>التمثيل</strong>: المجموعات الصغيرة تعطي النموذج ما يتعلّمه أقل. لدى Cartwheel 17 عميلًا أردنيًا في الجدول كله؛ وأي نموذج سيكون أقل موثوقية لهم.</li>\n<li><strong>البدائل (proxies)</strong>: حذف عمود حسّاس لا يحذف معلوماته. فرسوم الشحن وأزمنة التوصيل والعملة قد تحلّ محل الدولة.</li>\n<li><strong>الاستخدام</strong>: الدرجة نفسها تعني أشياء مختلفة بحسب الإجراء. علامة انقطاع تطلق خصمًا تفيد العميل المُعلَّم؛ وعلامة تطلق تجميد الائتمان تضرّه.</li>\n</ul>\n<h2 id=\"sec-2\" tabindex=\"-1\">فحص النموذج مجموعةً مجموعة</h2>\n<p>لتفحص بإنصاف تحتاج إلى تنبؤات لعملاء كثيرين لم يتدرّب عليهم النموذج. تعطي <code dir=\"ltr\">cross_val_predict</code> كل صف تنبؤًا خارج الطيّة: فكل عميل يُقيَّم بنموذج تدرّب على الطيّات الأخرى.</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.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.metrics </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> precision_score, recall_score</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_val_predict</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>\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\">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\">model </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline(StandardScaler(), LogisticRegression())</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">prob </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> cross_val_predict(model, 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\">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\">method</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"predict_proba\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[:, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">audit </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> churn.assign(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">flagged</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(prob </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).astype(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">int</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">def</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> group_report</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(g):</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.Series({</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        \"customers\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">len</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(g),</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        \"churn_rate\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: g[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].mean(),</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        \"flag_rate\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: g[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"flagged\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].mean(),</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        \"recall\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: recall_score(g[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], g[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"flagged\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]),</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        \"precision\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: precision_score(g[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], g[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"flagged\"</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>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(audit.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"segment\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"flagged\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]].apply(group_report).round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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\">(audit.groupby(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"country\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"flagged\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]].apply(group_report).round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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>اقرأ عبر الأعمدة:</p>\n<ul>\n<li><strong>نسبة العملاء المُعلَّمين (flag rate)</strong> أو نسبة الاختيار: كم مرة تحصل كل مجموعة على العرض.</li>\n<li><strong>الـ Recall</strong>: من منقطعي كل مجموعة، كم منهم يلتقط النموذج. وهي هنا حصة العملاء المعرّضين للخطر الذين يحصلون على المساعدة.</li>\n<li><strong>الـ Precision</strong>: من العملاء المُعلَّمين في كل مجموعة، كم منهم كان راحلًا فعلًا. والـ precision المنخفضة تعني خصومات تذهب إلى أشخاص كانوا سيبقون.</li>\n</ul>\n<p>تتراوح الـ recall بين 0.71 (كندا والإمارات والمملكة المتحدة) و0.82 (مصر والأردن). والـ precision للشركات الصغيرة 0.66 مقابل 0.78 للمستهلكين الأفراد. بعض الفجوات ضجيج: فأرقام الأردن تقوم على 17 عميلًا، فيحرّكها عميل واحد بعدة نقاط. وبعضها يستحق نظرة أدقّ مع نمو البيانات. ليس الغرض من الفحص ختم نجاح/رسوب؛ بل معرفة أين يكون النموذج أضعف، كي تقرّر ما إذا كان ذلك مقبولًا.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">اختيار معنى “المنصف”</h2>\n<p>هناك عدة تعريفات معقولة، وحين تختلف النِّسب الأساسية بين المجموعات (تنقطع مصر بنسبة 67%، وألمانيا بنسبة 45%) لا يمكن أن تتحقق كلها معًا:</p>\n<ul>\n<li><strong>تساوي نِسب الاختيار</strong> (demographic parity): كل مجموعة تُعلَّم بالنسبة نفسها.</li>\n<li><strong>تساوي الـ recall</strong> (equal opportunity): المنقطعون الحقيقيون في كل مجموعة يُلتقطون بالنسبة نفسها.</li>\n<li><strong>تساوي الـ precision</strong>: العلامة تعني الخطر نفسه في كل مجموعة.</li>\n</ul>\n<p>اختر بأن تسأل من يتضرّر من أيّ خطأ. في عروض الاسترجاع، الضرر هو منقطع لا يصله العرض أبدًا، فتساوي الـ recall هو الهدف الطبيعي. وفي نموذج يجمّد الحسابات، الضرر هو الإيجابي الكاذب، فستنظر إلى معدّلات الإيجابيات الكاذبة بدلًا من ذلك. دوّن اختيارك؛ فهو قرار تجاري وأخلاقي، لا خيار تقني افتراضي.</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>“لا نستخدم الدولة، فلا يمكن أن يكون النموذج منحازًا بسببها” يُسمّى الإنصاف عبر التجاهل (fairness through unawareness)، وهو يفشل كلما ارتبطت features أخرى بالدولة. والأسوأ أنك دون العمود لا تستطيع إجراء الفحص أعلاه. أبقِ السمات الحسّاسة خارج النموذج إن اشترطت السياسة ذلك، لكن أبقِها في بيانات التقييم.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">بعد الإطلاق: الانجراف</h2>\n<p>تعلّم النموذج من العملاء كما كانوا في 30 سبتمبر 2025. والعالم يتحرّك. <strong>انجراف البيانات (data drift)</strong> تغيّر في المدخلات: تطبيق جوّال جديد يحرّك <code dir=\"ltr\">mobile_share</code>، وإضراب شركة شحن يمطّ <code dir=\"ltr\">days_since_last_order</code>. و<strong>انجراف المفهوم (concept drift)</strong> تغيّر في العلاقة نفسها: برنامج ولاء لدى منافس يجعل حتى العملاء الحديثين يرحلون. وللانقطاع <strong>تأخّر في الـ labels</strong>: فلا تعرف ما إذا كان التنبؤ صحيحًا إلا بعد 90 يومًا.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-ml-5-4-t1\"><defs><marker id=\"arrow-l-ml-5-4-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-5-4-t1\">حلقة من أربع مراحل. يوميًا: افحص توزيعات المدخلات مقابل بيانات التدريب. يوميًا: تتبّع حصة العملاء المُعلَّمين. بعد 90 يومًا: قارن التنبؤات بالنتائج الحقيقية. حين تفشل الفحوص: حقّق، ثم أعد التدريب والفحص، وهذا يعود إلى النموذج المُطلَق.</title>\n  <rect class=\"d-box-primary\" x=\"250\" y=\"10\" width=\"180\" height=\"46\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"340\" y=\"38\" text-anchor=\"middle\">النموذج المُطلَق</text>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"92\" width=\"190\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"115\" y=\"116\" text-anchor=\"middle\">فحوص انجراف المدخلات</text>\n  <text class=\"d-label-muted\" x=\"115\" y=\"137\" text-anchor=\"middle\">يوميًا، دون labels</text>\n  <rect class=\"d-box-accent\" x=\"245\" y=\"92\" width=\"190\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"340\" y=\"116\" text-anchor=\"middle\">مزيج التنبؤات</text>\n  <text class=\"d-label-muted\" x=\"340\" y=\"137\" text-anchor=\"middle\">نسبة المُعلَّمين لكل مجموعة</text>\n  <rect class=\"d-box-warn\" x=\"470\" y=\"92\" width=\"190\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"565\" y=\"116\" text-anchor=\"middle\">الأداء الحقيقي</text>\n  <text class=\"d-label-muted\" x=\"565\" y=\"137\" text-anchor=\"middle\">بعد 90 يومًا</text>\n  <rect class=\"d-box-success\" x=\"250\" y=\"178\" width=\"180\" height=\"46\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"340\" y=\"206\" text-anchor=\"middle\">حقّق، وأعد التدريب والفحص</text>\n  <path class=\"d-arrow\" d=\"M290 56 L150 90\" marker-end=\"url(#arrow-l-ml-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M340 56 L340 90\" marker-end=\"url(#arrow-l-ml-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M390 56 L530 90\" marker-end=\"url(#arrow-l-ml-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M115 148 L250 195\" marker-end=\"url(#arrow-l-ml-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M565 148 L430 195\" marker-end=\"url(#arrow-l-ml-5-4-1)\"/>\n</svg><figcaption>مراقبة نموذج انقطاع مُطلَق</figcaption></figure>\n<p>يمكن فحص انجراف المدخلات كل يوم، دون labels، بمقارنة القيم الحديثة لكل feature ببيانات التدريب. واختبار Kolmogorov–Smirnov للعيّنتين من SciPy بداية بسيطة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"1\"><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=\"1\" 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\"> numpy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> np</span></span>\n<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\"> scipy.stats </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ks_2samp</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\">training </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> churn[features]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\"># Simulate next quarter: a courier problem delays orders and a new app shifts shopping to mobile</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">rng </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> np.random.default_rng(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">7</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">recent </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> training.sample(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">150</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\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).copy()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">recent[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"days_since_last_order\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rng.integers(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">20</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">60</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">len</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(recent))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">recent[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mobile_share\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (recent[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mobile_share\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).clip(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> col </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> features:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    stat, p </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ks_2samp(training[col], recent[col])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    flag </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"DRIFT\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> p </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.01</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> else</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"\"</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\">col</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:22}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> KS </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">stat</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.2f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  p=</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">p</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.3g}</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> {</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">flag</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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=\"1\" hidden></div></div>\n<p>يضيء العمودان اللذان تغيّرا؛ ولا يضيء الباقي. في بيئة الإنتاج ستشغّل هذا وفق جدول زمني، وتطلق تنبيهًا على الانجراف المستمر لا على يوم سيئ واحد، وتقرنه بنسبة العملاء المُعلَّمين في كل مجموعة، ثم بالـ recall والـ precision لكل مجموعة حين تصل النتائج. وحين ينخفض الأداء الحقيقي، أعد التدريب على بيانات حديثة وأعد إجراء فحص هذا الدرس قبل إعادة الإطلاق.</p>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>اكتب بطاقة نموذج (model card)</span></p>\n<p>احتفظ بسجلّ من صفحة واحدة بجانب النموذج: ما الذي يتنبأ به ولمن، وتواريخ بيانات التدريب، والمقاييس الإجمالية ولكل مجموعة، ومعيار الإنصاف الذي اخترته ولماذا، ونقاط الضعف المعروفة (17 عميلًا أردنيًا)، وما الذي تجري مراقبته. إنها الوثيقة التي سيحتاجها أحدهم حين يفاجئه النموذج بعد عام.</p>\n</aside>\n<p>المراقبة تفترض أن النموذج يعمل في مكان ما. يحزم الدرس الأخير الـ pipeline البطل، ويضعه خلف API، ويغلق الحلقة.</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":"بعد الإطلاق: الانجراف","level":2}],"takeaways":["يدخل الانحياز عبر البيانات (labels تاريخية، ومجموعات ممثّلة تمثيلًا ناقصًا، وfeatures بديلة) وعبر طريقة استخدام التنبؤات؛ والنتيجة الإجمالية الجيدة قد تُخفي نتيجة سيئة لمجموعة واحدة.","افحص بمقاييس لكل مجموعة: نسبة الاختيار، والـ recall، والـ precision، محسوبة على تنبؤات خارج الطيّة (out-of-fold) أو على بيانات الاختبار.","تعريفات الإنصاف مثل تساوي نِسب الاختيار وتساوي الـ recall وتساوي الـ precision لا يمكن عادةً أن تتحقق كلها معًا؛ فاختر التعريف الذي يطابق من يتضرّر من أيّ خطأ.","حذف العمود الحسّاس لا يزيل الانحياز، لأن features أخرى تعمل كبدائل عنه؛ فأبقِه متاحًا للفحص.","راقب ثلاثة أشياء بعد الإطلاق: توزيعات المدخلات (data drift)، وتوزيع التنبؤات، والأداء الحقيقي حين تصل النتائج."],"further":[{"title":"cross_val_predict","url":"https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_predict.html"},{"title":"Model evaluation: classification metrics","url":"https://scikit-learn.org/stable/modules/model_evaluation.html#classification-metrics"}],"quiz":[{"q":"<p>الـ recall لنموذج الانقطاع 0.82 للعملاء المصريين و0.71 للعملاء الكنديين. وعروض الاسترجاع تذهب إلى العملاء المُعلَّمين. من المتضرّر؟</p>","options":[{"text":"المنقطعون الكنديون، فاحتمال تعليمهم وتقديم عرض لهم أقل.","why":"صحيح. انخفاض الـ recall يعني أن عددًا أكبر من المنقطعين الكنديين يفوت النموذج، فيحصل عدد أقل منهم على الفائدة."},{"text":"العملاء المصريون، لأن عددًا أكبر منهم يُعلَّم.","why":"أن يُعلَّم العميل هنا يعني أن يتلقّى عرضًا، وهو فائدة لا عقوبة."},{"text":"لا أحد، لأن الـ recall الإجمالية جيدة.","why":"المتوسط الإجمالي قد يُخفي مجموعة تُخدم بشكل أسوأ باستمرار."},{"text":"العملاء الكنديون الذين بقوا، لأنهم يُعلَّمون أكثر.","why":"هذا يتعلّق بالإيجابيات الكاذبة (الـ precision أو معدّل الإيجابيات الكاذبة)، لا بالـ recall."}],"answer":0},{"q":"<p>يحذف زميلك <code dir=\"ltr\">country</code> من الـ features ‘ليجعل النموذج منصفًا’. لماذا لا يكفي ذلك؟</p>","options":[{"text":"لأن حذف عمود يخفض الـ accuracy دائمًا إلى ما دون خط الأساس.","why":"قد يغيّر الـ accuracy قليلًا أو لا يغيّرها إطلاقًا؛ ومسألة الإنصاف منفصلة."},{"text":"لأن features أخرى، مثل رسوم الشحن أو أزمنة التوصيل، قد تحمل المعلومة نفسها، ولم يعد بإمكانك الفحص بحسب الدولة.","why":"صحيح. البدائل تُعيد الانحياز، ودون العمود لا تستطيع قياس ما إذا كان قد عاد."},{"text":"لأن scikit-learn يشترط بقاء السمات الحسّاسة في النموذج.","why":"لا يوجد شرط كهذا؛ المسألة تتعلّق بالبدائل والفحص."}],"answer":1},{"q":"<p>بعد شهرين من الإطلاق، ترتفع حصة العملاء المُعلَّمين كمنقطعين من 47% إلى 60%، لكن لا نتائج انقطاع معروفة بعد. ما أفضل خطوة أولى؟</p>","options":[{"text":"أعد التدريب فورًا على أحدث البيانات.","why":"ليست لديك labels جديدة بعد، ولا تعرف ما إذا كان النموذج هو الذي تغيّر أم العالم."},{"text":"اخفض العتبة كي تعود نسبة العملاء المُعلَّمين إلى 47%.","why":"هذا يُخفي الإشارة بدل أن يفسّرها."},{"text":"تجاهل الأمر حتى تصل نتائج الـ 90 يومًا.","why":"انتظار ثلاثة أشهر لمشكلة يمكنك التحقيق فيها اليوم أمر مكلف."},{"text":"قارن توزيعات المدخلات الحالية ببيانات التدريب لتعرف أيّ الـ features تحرّك، ثم اسأل الفرق المعنية في الشركة عن السبب.","why":"صحيح. فحوص انجراف البيانات تعمل دون labels وتشير عادةً إلى سبب، مثل مشكلة في التوصيل أو تطبيق جديد."}],"answer":3}],"exercise":{"kind":"python","files":["churn.csv"],"starter":"import numpy as np\nimport pandas as pd\nfrom scipy.stats import ks_2samp\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.metrics import precision_score, recall_score\nfrom sklearn.model_selection import StratifiedKFold, cross_val_predict\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\"]\ncv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\nmodel = make_pipeline(StandardScaler(), LogisticRegression())\n\n# Next quarter's customers (simulated): a premium range lifted basket sizes and a returns policy change caused more tickets\nrng = np.random.default_rng(3)\nrecent = churn[features].sample(150, random_state=2).copy()\nrecent[\"avg_order_value\"] *= 1.6\nrecent[\"support_tickets\"] += rng.integers(1, 3, len(recent))\n\n# 1. Out-of-fold churn flags (threshold 0.5) for every customer, using cross_val_predict\nflagged = None\n\n# 2. Recall per segment (a Series indexed by segment), and the segment with the lowest recall\nrecall_by_segment = None\nlowest_recall_segment = None\n\n# 3. Columns of `recent` whose distribution differs from churn[features] (KS test p < 0.01), as a list\ndrifted = []\n","solution":"import numpy as np\nimport pandas as pd\nfrom scipy.stats import ks_2samp\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.metrics import precision_score, recall_score\nfrom sklearn.model_selection import StratifiedKFold, cross_val_predict\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\"]\ncv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\nmodel = make_pipeline(StandardScaler(), LogisticRegression())\n\n# Next quarter's customers (simulated): a premium range lifted basket sizes and a returns policy change caused more tickets\nrng = np.random.default_rng(3)\nrecent = churn[features].sample(150, random_state=2).copy()\nrecent[\"avg_order_value\"] *= 1.6\nrecent[\"support_tickets\"] += rng.integers(1, 3, len(recent))\n\nprob = cross_val_predict(model, churn[features], churn[\"churned\"], cv=cv, method=\"predict_proba\")[:, 1]\nflagged = (prob >= 0.5).astype(int)\n\naudit = churn.assign(flagged=flagged)\nrecall_by_segment = audit.groupby(\"segment\")[[\"churned\", \"flagged\"]].apply(\n    lambda g: recall_score(g[\"churned\"], g[\"flagged\"]))\nlowest_recall_segment = recall_by_segment.idxmin()\n\ndrifted = [c for c in features if ks_2samp(churn[c], recent[c]).pvalue < 0.01]\nprint(recall_by_segment.round(2).to_dict(), lowest_recall_segment, drifted)\n","checks":[{"id":"flags","test":"flagged is not None and len(flagged) == 398 and set(np.unique(flagged)) <= {0, 1} and abs(int(np.sum(flagged)) - 218) <= 2"},{"id":"recall","test":"recall_by_segment is not None and set(recall_by_segment.index) == {'consumer', 'enterprise', 'small_business'} and abs(float(recall_by_segment['consumer']) - 0.7582) < 0.01"},{"id":"lowest","test":"lowest_recall_segment == 'enterprise'"},{"id":"drift","test":"sorted(drifted) == ['avg_order_value', 'support_tickets']"}],"title":"افحص بحسب الشريحة، ثم تحقّق من الانجراف","prompt":"<ol>\n<li><code dir=\"ltr\">flagged</code>: علامة انقطاع 0/1 خارج الطيّة لكل عميل، من <code dir=\"ltr\">cross_val_predict(model, ..., cv=cv, method=&quot;predict_proba&quot;)</code> وعتبة 0.5.</li>\n<li><code dir=\"ltr\">recall_by_segment</code>: الـ recall لهذه العلامات داخل كل <code dir=\"ltr\">segment</code> (كـ Series فهرسها الشريحة). و<code dir=\"ltr\">lowest_recall_segment</code>: الشريحة التي يُلتقط منقطعوها بأقل نسبة.</li>\n<li><code dir=\"ltr\">drifted</code>: قائمة أسماء الـ features التي يختلف فيها <code dir=\"ltr\">recent</code> عن <code dir=\"ltr\">churn[features]</code> بحسب اختبار KS للعيّنتين مع p &lt; 0.01.</li>\n</ol>\n","hints":["يعطيك <code dir=\"ltr\">cross_val_predict(...)[:, 1]</code> عمود احتمالات الانقطاع؛ قارنه بـ 0.5 وحوّله بـ <code dir=\"ltr\">.astype(int)</code>.","أضف العلامات كعمود، ثم <code dir=\"ltr\">audit.groupby(&quot;segment&quot;)[[&quot;churned&quot;, &quot;flagged&quot;]].apply(lambda g: recall_score(g[&quot;churned&quot;], g[&quot;flagged&quot;]))</code>.","<code dir=\"ltr\">ks_2samp(churn[c], recent[c]).pvalue</code> لكل <code dir=\"ltr\">c</code> في <code dir=\"ltr\">features</code>؛ واحتفظ بما هو أقل من 0.01."],"explanation":"<p>تتيح لك التنبؤات خارج الطيّة فحص العملاء الـ 398 كلهم دون تقييم أي منهم بنموذج تدرّب عليه. منقطعو الشركات الكبيرة هم الأقل التقاطًا (نحو 0.71، مقابل 0.76 للمستهلكين الأفراد)، لكن مع 24 عميلًا فقط من الشركات الكبيرة، تحتاج هذه الفجوة إلى بيانات أكثر قبل أن يتصرّف أحد بناءً عليها. وتلتقط فحوص KS بالضبط العمودين اللذين غيّرتهما المحاكاة: متوسط قيمة الطلب وتذاكر الدعم. هذا هو الإنذار المبكر الذي ستحصل عليه قبل أشهر من أن تؤكد نتائج الانقطاع تراجع الأداء.</p>\n","checkLabels":{"flags":"يحتوي flagged على علامات 0/1 خارج الطيّة للعملاء الـ 398 كلهم","recall":"يحتوي recall_by_segment على الـ recall لكل شريحة","lowest":"يسمّي lowest_recall_segment الشريحة الأضعف","drift":"يسرد drifted الأعمدة المنزاحة بالضبط"}},"words":717,"readMinutes":4}