{"id":"l-ml-3-1","courseId":"ml-crash-course","lang":"ar","kind":"lesson","summary":"درّب نموذج انحدار لوجستي على بيانات الانقطاع، واقرأ احتمالاته ومعاملاته، واستخدم الاحتمالات لترتيب العملاء كي يتواصل فريق التسويق مع الأكثر خطرًا أولًا.","html":"<p>أجابت الشجرة بعمق 3 من القسم 1 بـ “سينقطع” أو “سيبقى”. وفريق التسويق لا يريد ذلك حقًا. لديه ميزانية لبضع عشرات من مكالمات الاسترجاع أسبوعيًا، ويريد أن يعرف بمن يتصل أولًا. هذا يتطلّب درجة ترتّب العملاء بحسب الخطر، والنموذج الكلاسيكي لذلك هو الانحدار اللوجستي (logistic regression).</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">من خط إلى احتمال</h2>\n<p>يبدأ الانحدار اللوجستي كالانحدار الخطي: اضرب كل feature في معامل، واجمع النتائج، وأضف intercept. هذا يعطيك درجة <code dir=\"ltr\">z</code> قد تكون أي رقم. ثم يضغط الدرجة عبر دالة <strong>الـ sigmoid</strong>، أي <code dir=\"ltr\">1 / (1 + e^-z)</code>، التي تحوّل أي رقم إلى قيمة بين 0 و1. الدرجات الموجبة الكبيرة تعطي احتمالات قرب 1، والسالبة الكبيرة قرب 0، والدرجة 0 بالضبط تعطي 0.5.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 640 260\" role=\"img\" aria-labelledby=\"l-ml-3-1-t1\"><defs><marker id=\"arrow-l-ml-3-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-3-1-t1\">منحنى على شكل حرف S. المحور الأفقي هو الدرجة الخطية z من سالب 6 إلى موجب 6؛ والمحور العمودي هو الاحتمال من 0 إلى 1. يمرّ المنحنى بـ 0.5 عند z تساوي 0 ويتسطّح نحو 0 و1 عند الطرفين.</title>\n  <line class=\"d-line\" x1=\"40\" y1=\"220\" x2=\"600\" y2=\"220\"/>\n  <line class=\"d-line\" x1=\"320\" y1=\"20\" x2=\"320\" y2=\"225\"/>\n  <line class=\"d-dashed d-line\" x1=\"40\" y1=\"40\" x2=\"600\" y2=\"40\"/>\n  <line class=\"d-dashed d-line\" x1=\"40\" y1=\"130\" x2=\"600\" y2=\"130\"/>\n  <text class=\"d-label-muted\" x=\"606\" y=\"44\">1.0</text>\n  <text class=\"d-label-muted\" x=\"606\" y=\"134\">0.5</text>\n  <text class=\"d-label-muted\" x=\"606\" y=\"224\">0.0</text>\n  <text class=\"d-label-muted\" x=\"40\" y=\"244\">z = −6</text>\n  <text class=\"d-label-muted\" x=\"320\" y=\"244\" text-anchor=\"middle\">0</text>\n  <text class=\"d-label-muted\" x=\"600\" y=\"244\" text-anchor=\"end\">+6</text>\n  <path class=\"d-arrow\" d=\"M40 219.6 C200 219 250 200 320 130 C390 60 440 41 600 40.4\" fill=\"none\"/>\n  <circle class=\"d-dot\" cx=\"320\" cy=\"130\" r=\"6\"/>\n  <text class=\"d-label\" x=\"80\" y=\"200\">غالبًا سيبقى</text>\n  <text class=\"d-label\" x=\"470\" y=\"70\">غالبًا سينقطع</text>\n</svg><figcaption>الـ sigmoid تحوّل الدرجة إلى احتمال</figcaption></figure>\n<p>أثناء التدريب، يعدّل النموذج المعاملات بحيث ينال المنقطعون احتمالات عالية وغير المنقطعين احتمالات منخفضة. ودالة الخسارة التي يصغّرها هي <strong>الـ log loss</strong>، التي تعاقب الإجابات الخاطئة الواثقة بشدة: التنبؤ بـ 0.99 لعميل بقي يكلّف أكثر بكثير من التنبؤ بـ 0.6. لا توجد معادلة دقيقة للقاع، فيجده scikit-learn بمُحسِّن تكراري، وهو قريب مهذَّب للـ gradient descent الذي كتبته في القسم السابق.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">تدريبه على بيانات الانقطاع</h2>\n<p>لأن المُحسِّن تكراري وللنموذج عقوبة (المزيد عن ذلك أدناه)، وحّد مقاييس الـ features أولًا. والـ pipeline يُبقي الـ scaler والنموذج معًا.</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.model_selection </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> 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>\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\">model </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline(StandardScaler(), LogisticRegression()).fit(X_train, y_train)</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:#0A3069;--shiki-dark:#A5D6FF\">\"test accuracy:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, model.score(X_test, y_test))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(model.predict_proba(X_test)[:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].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\">(model.predict(X_test)[:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</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>تُعيد <code dir=\"ltr\">predict_proba</code> عمودين، واحدًا لكل فئة بترتيب <code dir=\"ltr\">classes_</code>: احتمال البقاء، واحتمال الانقطاع. ومجموعهما 1. أما <code dir=\"ltr\">predict</code> فاختصار يصنّف الصف بـ 1 حين يكون احتمال الانقطاع 0.5 على الأقل. وهذه الـ 0.5 قيمة افتراضية، لا قانون، وآخر درس في القسم 3 يدور حول اختيارها عن قصد.</p>\n<p>الـ accuracy تبلغ 0.72، أي بمستوى قاعدة الـ 80 يومًا. لا تتوقف عن القراءة هنا: فالاحتمالات تحمل معلومات ترميها الـ accuracy.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">قراءة المعاملات</h2>\n<p>مع features موحّدة المقاييس، يقول كل معامل كم يحرّك انحرافٌ معياري واحد من ذلك الـ feature الدرجةَ <code dir=\"ltr\">z</code> (أي الـ log-odds للانقطاع). وهذا يجعلها قابلة للمقارنة بين الـ features.</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\"> 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.model_selection </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> 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>\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 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()).fit(X_train, y_train)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">coefs </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.Series(model[</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\">].coef_[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">index</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">features).sort_values()</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(coefs.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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">ranked </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> X_test.assign(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">churn_prob</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">model.predict_proba(X_test)[:, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">churned</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">y_test)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">top10 </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ranked.sort_values(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churn_prob\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">ascending</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">False</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).head(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">10</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\">(top10[[</span><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\">\"orders\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churn_prob\"</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\">]].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\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"share of the top 10 who really churned:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, top10[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].mean())</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>حداثة آخر طلب (<code dir=\"ltr\">days_since_last_order</code>، +0.93) هي المهيمنة، وهذا يطابق ما أخبرتك به قاعدة خط الأساس. ومدة العضوية الأطول تدفع أيضًا نحو الانقطاع، بينما تدفع الطلبات الأكثر والإنفاق الأكبر واستخدام القسائم، بل وتذاكر الدعم الأكثر، بعيدًا عنه. فالعملاء الذين يتواصلون مع الدعم عملاء متفاعلون؛ والصامتون هم من يرحلون. هذا هو نوع الاستنتاجات الذي يستحق أن تعود به إلى أصحاب القرار في الشركة، مع التنبيه إلى أنه ارتباط وليس سببية.</p>\n<p>ثم تأتي الثمرة: من بين العشرة الذين يصنّفهم النموذج الأكثر خطرًا، انقطع تسعة فعلًا. قاعدة نعم/لا لا تستطيع إنتاج هذه القائمة؛ أما الاحتمال فيستطيع.</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>تخطّي الـ scaler</span></p>\n<p>درّب <code dir=\"ltr\">LogisticRegression()</code> على الـ features الخام وستحصل على <code dir=\"ltr\">ConvergenceWarning</code>: بلغ المُحسِّن حدّ التكرارات قبل أن ينتهي، لأن <code dir=\"ltr\">total_spent</code> بالآلاف و<code dir=\"ltr\">used_coupon</code> بقيم 0/1 يجعلان سطح الخسارة واديًا طويلًا ضيقًا. والمعاملات التي تحصل عليها غير مكتملة. وحّد المقاييس أولًا؛ فهذا يجعل العقوبة أيضًا تعامل الـ features كلها بعدل.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">العقوبة التي لم تطلبها</h2>\n<p><code dir=\"ltr\">LogisticRegression</code> منظَّم افتراضيًا بعقوبة L2، وهي الفكرة نفسها في Ridge. تُحدَّد قوّتها بـ <code dir=\"ltr\">C</code>، وهو <strong>مقلوب</strong> <code dir=\"ltr\">alpha</code> في Ridge: قيمة <code dir=\"ltr\">C</code> الصغيرة تعني عقوبة قوية، والكبيرة تعني عقوبة ضعيفة. والقيمة الافتراضية <code dir=\"ltr\">C=1.0</code> بداية معقولة. على هذه البيانات، تقع القيم من 0.01 إلى 100 كلها في حدود نقطتين من بعضها، وهذه علامة على أن ثمانية features حسنة السلوك لا تمنح النموذج مجالًا كبيرًا للـ overfitting. أما مع مئات الـ features، فتصبح <code dir=\"ltr\">C</code> مهمة جدًا، وستضبطها بالأدوات التي في القسم 4.</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>متى يكون الانحدار اللوجستي النموذج الأول الصحيح</span></p>\n<p>إنه سريع، واحتمالاته معقولة عادةً دون أي تعديل، ومعاملاته يمكن شرحها لمدير. اجعله أول نموذج حقيقي في أي مسألة تصنيف على بيانات جدولية، مباشرة بعد خطوط الأساس. وعلى النماذج الأكثر مرونة أن تتفوّق عليه لتستحق مكانها.</p>\n</aside>\n<p>يرسم الانحدار اللوجستي حدًّا مستقيمًا واحدًا عبر فضاء الـ features. أما نموذج الدرس القادم فلا يفترض شيئًا كهذا: إنه يطرح سلسلة من أسئلة نعم/لا ويستطيع تقطيع الفضاء إلى صناديق.</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":["يحسب الانحدار اللوجستي درجة خطية ثم يضغطها عبر دالة الـ sigmoid لتصبح احتمالًا بين 0 و1.","تُعيد <code dir=\"ltr\">predict_proba</code> احتمالًا لكل فئة؛ وتطبّق <code dir=\"ltr\">predict</code> عليه حدًّا فاصلًا عند 0.5.","على features موحّدة المقاييس، كل معامل هو التغيّر في الـ log-odds لكل انحراف معياري، فتصبح إشارته وحجمه قابلين للمقارنة بين الـ features.","<code dir=\"ltr\">LogisticRegression</code> منظَّم افتراضيًا؛ و<code dir=\"ltr\">C</code> هو مقلوب قوة العقوبة، فقيمة <code dir=\"ltr\">C</code> الأصغر تعني عقوبة أقوى.","ترتيب العملاء بحسب الاحتمال أنفع غالبًا من label نعم/لا، لأنه يتيح للشركة أن تختار عدد العملاء الذين ستتعامل معهم."],"further":[{"title":"Logistic regression","url":"https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression"},{"title":"LogisticRegression","url":"https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html"}],"quiz":[{"q":"<p>الدرجة الخطية لعميل داخل نموذج انحدار لوجستي تساوي 0. ما احتمال الانقطاع الذي يعطيه النموذج؟</p>","options":[{"text":"0.5، لأن sigmoid الصفر يساوي النصف بالضبط.","why":"صحيح. الدرجة 0 تعني أن الأدلة مع الانقطاع وضده متوازنة."},{"text":"0، لأن الدرجة 0 تعني عدم الانقطاع.","why":"لا يقترب الاحتمال من 0 إلا حين تذهب الدرجة بعيدًا في السالب. والصفر يقع في منتصف المنحنى."},{"text":"يعتمد على الـ intercept، الذي يُضاف بعد الـ sigmoid.","why":"الـ intercept جزء من الدرجة الخطية، ويُضاف قبل الـ sigmoid. وبمجرد أن تكون الدرجة 0، يكون الناتج ثابتًا."},{"text":"1، لأن sigmoid الصفر يساوي 1.","why":"الـ sigmoid هي <code dir=\"ltr\">1 / (1 + e^-z)</code>؛ ومع z = 0 تصبح <code dir=\"ltr\">1 / 2</code>."}],"answer":0},{"q":"<p>على features انقطاع موحّدة المقاييس، معامل <code dir=\"ltr\">days_since_last_order</code> هو +0.93 ومعامل <code dir=\"ltr\">total_spent</code> هو -0.26. ماذا يمكنك أن تقول؟</p>","options":[{"text":"كل يوم إضافي منذ آخر طلب يؤثر بنحو 3.6 مرة أكثر من كل دولار إضافي من الإنفاق.","why":"المعاملات لكل انحراف معياري، لا لكل دولار أو لكل يوم، فالمقارنة لكل وحدة لا تصح."},{"text":"العملاء الذين أنفقوا أكثر ينقطعون أكثر.","why":"المعامل السالب يعني أن الإنفاق الأكبر مرتبط باحتمالات انقطاع أقل، مع تثبيت الـ features الأخرى."},{"text":"رفع الإنفاق عبر التسويق سيخفض الانقطاع بمقدار معروف.","why":"تصف المعاملات ارتباطات في هذه البيانات؛ ولا تخبرك بأثر أي تدخّل."},{"text":"زيادة انحراف معياري واحد في مدة الغياب ترفع احتمالات الانقطاع أكثر بكثير مما تخفضها الزيادة نفسها في الإنفاق.","why":"صحيح. على مقياس مشترك، يقارن حجم المعامل بين الآثار في النموذج المدرَّب."}],"answer":3},{"q":"<p>درّبت <code dir=\"ltr\">LogisticRegression()</code> على features الانقطاع الخام فظهر تحذير <code dir=\"ltr\">ConvergenceWarning</code>. ما أفضل إصلاح أول؟</p>","options":[{"text":"تجاهله، لأن الـ accuracy تبدو جيدة.","why":"توقّف المُحسِّن (optimiser) قبل أن ينتهي، فالمعاملات ليست تلك التي يعرّفها النموذج. أصلح السبب."},{"text":"اخفض <code dir=\"ltr\">C</code> إلى 0.0001 ليكون لدى النموذج ما يتعلّمه أقل.","why":"قيمة <code dir=\"ltr\">C</code> الضئيلة تُخفي العَرَض بتسطيح النموذج، على حساب نموذج مختلف جدًا يعاني من الـ underfitting."},{"text":"أضف <code dir=\"ltr\">StandardScaler</code> قبل النموذج في pipeline.","why":"صحيح. الـ features ذات المقاييس المتباعدة جدًا تصعّب مهمة المُحسِّن؛ وتوحيد المقاييس يزيل التحذير عادةً."}],"answer":2},{"q":"<p>يستطيع فريق التسويق الاتصال بـ 30 عميلًا هذا الأسبوع. ماذا يجب أن يعطيهم نموذجك؟</p>","options":[{"text":"العملاء الثلاثين أصحاب أعلى احتمال انقطاع متنبأ به.","why":"صحيح. الترتيب بحسب الاحتمال يضع الميزانية المحدودة حيث النموذج أكثر ثقة بأن الانقطاع قادم."},{"text":"أيّ 30 عميلًا تصنّفهم <code dir=\"ltr\">predict</code> بـ 1.","why":"قد يُصنَّف العشرات بـ 1. واختيار 30 منهم عشوائيًا يهدر الترتيب الذي حسبه النموذج أصلًا."},{"text":"العملاء الثلاثين الذين احتمالهم أقرب إلى 0.5.","why":"هؤلاء هم الحالات التي يكون النموذج أقل يقينًا بشأنها، لا الأكثر ترجيحًا للانقطاع."}],"answer":0}],"exercise":{"kind":"python","files":["churn.csv"],"starter":"import pandas as pd\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import train_test_split\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\n# 1. A pipeline: StandardScaler, then LogisticRegression; fit it on the training data\nmodel = LogisticRegression().fit(X_train, y_train)\n\n# 2. Churn probability (class 1) for every test customer\nchurn_prob = model.predict(X_test)\n\n# 3. customer_id of the 20 test customers with the highest churn probability\ncall_list = []\n\n# 4. Share of those 20 who really churned\nhit_rate = None\n","solution":"import pandas as pd\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import train_test_split\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\nmodel = make_pipeline(StandardScaler(), LogisticRegression()).fit(X_train, y_train)\n\nchurn_prob = model.predict_proba(X_test)[:, 1]\n\nranked = churn.loc[X_test.index, [\"customer_id\", \"churned\"]].assign(prob=churn_prob)\ntop20 = ranked.sort_values(\"prob\", ascending=False).head(20)\ncall_list = top20[\"customer_id\"].tolist()\n\nhit_rate = top20[\"churned\"].mean()\nprint(call_list[:5], hit_rate)\n","checks":[{"id":"pipeline","test":"[type(s).__name__ for s in model] == ['StandardScaler', 'LogisticRegression']"},{"id":"probs","test":"len(churn_prob) == 100 and abs(float(churn_prob[0]) - float(__import__('sklearn.pipeline', fromlist=['x']).make_pipeline(__import__('sklearn.preprocessing', fromlist=['x']).StandardScaler(), __import__('sklearn.linear_model', fromlist=['x']).LogisticRegression()).fit(X_train, y_train).predict_proba(X_test)[:, 1][0])) < 1e-6 and 0 < float(min(churn_prob)) < float(max(churn_prob)) < 1"},{"id":"call-list","test":"len(call_list) == 20 and set(int(c) for c in call_list) == set(churn.loc[X_test.index, 'customer_id'].to_numpy()[__import__('sklearn.pipeline', fromlist=['x']).make_pipeline(__import__('sklearn.preprocessing', fromlist=['x']).StandardScaler(), __import__('sklearn.linear_model', fromlist=['x']).LogisticRegression()).fit(X_train, y_train).predict_proba(X_test)[:, 1].argsort()[::-1][:20]].tolist())"},{"id":"hit-rate","test":"hit_rate is not None and abs(float(hit_rate) - churn.set_index('customer_id').loc[[int(c) for c in call_list], 'churned'].mean()) < 1e-9 and float(hit_rate) > 0.8"}],"title":"جهّز قائمة اتصالات يوم الاثنين","prompt":"<p>يستطيع فريق التسويق الاتصال بـ 20 عميلًا. أعطهم العشرين الصحيحين.</p>\n<ol>\n<li><code dir=\"ltr\">model</code>: pipeline يتكوّن من <code dir=\"ltr\">StandardScaler</code> ثم <code dir=\"ltr\">LogisticRegression</code>، مدرَّبًا على بيانات التدريب.</li>\n<li><code dir=\"ltr\">churn_prob</code>: احتمال الانقطاع (الفئة 1) لكل عميل في بيانات الاختبار.</li>\n<li><code dir=\"ltr\">call_list</code>: قيم <code dir=\"ltr\">customer_id</code> لعملاء الاختبار العشرين أصحاب أعلى احتمال انقطاع.</li>\n<li><code dir=\"ltr\">hit_rate</code>: نسبة من انقطعوا فعلًا من هؤلاء العشرين.</li>\n</ol>\n","hints":["<code dir=\"ltr\">make_pipeline(StandardScaler(), LogisticRegression())</code>","تعطيك <code dir=\"ltr\">predict</code> الـ labels. أما <code dir=\"ltr\">predict_proba(X_test)[:, 1]</code> فتعطيك عمود احتمالات الانقطاع.","يحتفظ <code dir=\"ltr\">X_test</code> بفهرس الصفوف الأصلي، لذا يتطابق <code dir=\"ltr\">churn.loc[X_test.index, &quot;customer_id&quot;]</code> مع <code dir=\"ltr\">churn_prob</code>. أضف الاحتمالات كعمود، ورتّب تنازليًا، وخذ <code dir=\"ltr\">head(20)</code>."],"explanation":"<p>الترتيب بحسب الاحتمال يركّز ميزانية الاتصالات: أكثر من 80% بكثير من أخطر 20 عميلًا في بيانات الاختبار انقطعوا فعلًا، مقابل 55% لو اخترت عشوائيًا. أما <code dir=\"ltr\">predict</code> فكانت ستقول “1” لعشرات العملاء دون أي ترتيب بينهم. والاحتفاظ بالفهرس الأصلي من <code dir=\"ltr\">train_test_split</code> هو ما يتيح لك ربط التنبؤات بالمعرّفات من جديد.</p>\n","checkLabels":{"pipeline":"model هو StandardScaler ← LogisticRegression","probs":"يحتوي churn_prob على احتمالات الفئة 1","call-list":"يحتوي call_list على معرّفات العملاء العشرين الأعلى خطرًا","hit-rate":"hit_rate هي نسبة من انقطعوا من قائمة الاتصال"}},"words":602,"readMinutes":4}