{"id":"l-ml-3-4","courseId":"ml-crash-course","lang":"ar","kind":"lesson","summary":"اقرأ مصفوفة الالتباس، واحسب الـ precision والـ recall والـ F1 والـ ROC AUC باستخدام scikit-learn، واختر المقياس الذي يطابق كلفة كل نوع من الأخطاء على الشركة.","html":"<p>يحقق كلٌّ من الانحدار اللوجستي وقاعدة الـ 80 يومًا accuracy بقيمة 0.72 على بيانات اختبار الانقطاع. فهل هما جيدان بالقدر نفسه؟ الـ accuracy تقول نعم. وفريق التسويق سيخالف ذلك فور أن يرى من يُعلِّمه كلٌّ منهما. فالـ accuracy تمزج خطأين مختلفين جدًا في رقم واحد، وترمي الترتيب الذي جعل قائمة الاتصالات مفيدة. هذا الدرس يفكّكهما.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">أربع نتائج</h2>\n<p>كل تنبؤ في مسألة نعم/لا يقع في واحدة من أربع خانات. سمِّ المنقطعين <strong>إيجابيات (positives)</strong> (فهم الفئة التي تحاول العثور عليها).</p>\n<ul>\n<li><strong>إيجابي صحيح (TP)</strong>: تنبأنا بالانقطاع، وانقطع فعلًا. وصل التسويق إلى الشخص الصحيح.</li>\n<li><strong>إيجابي كاذب (FP)</strong>: تنبأنا بالانقطاع، وبقي. عرض أُهدر على عميل وفيّ.</li>\n<li><strong>سلبي كاذب (FN)</strong>: تنبأنا بالبقاء، وانقطع. عميل خسرناه دون محاولة.</li>\n<li><strong>سلبي صحيح (TN)</strong>: تنبأنا بالبقاء، وبقي. تُرك وشأنه عن صواب.</li>\n</ul>\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\"> confusion_matrix</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 style=\"color:#1F2328;--shiki-dark:#E6EDF3\">pred </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> model.predict(X_test)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">rule </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (X_test[</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:#0550AE;--shiki-dark:#79C0FF\"> 80</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:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"model:</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">\\n</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, confusion_matrix(y_test, pred))</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\">\"rule:</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">\\n</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, confusion_matrix(y_test, rule))</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>يرتّب scikit-learn المصفوفة بحيث تكون الفئات الفعلية صفوفًا والفئات المتنبأ بها أعمدة، وكلاهما بترتيب تصاعدي (0 ثم 1). لذا تُقرأ مصفوفة النموذج <code dir=\"ltr\">[[35, 10], [18, 37]]</code> هكذا:</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 560 280\" role=\"img\" aria-labelledby=\"l-ml-3-4-t1\"><defs><marker id=\"arrow-l-ml-3-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-3-4-t1\">شبكة اثنين في اثنين. بقي فعلًا وتُنبّئ ببقائه: 35 سلبيًا صحيحًا. بقي فعلًا وتُنبّئ بانقطاعه: 10 إيجابيات كاذبة. انقطع فعلًا وتُنبّئ ببقائه: 18 سلبيًا كاذبًا. انقطع فعلًا وتُنبّئ بانقطاعه: 37 إيجابيًا صحيحًا.</title>\n  <text class=\"d-label-strong\" x=\"330\" y=\"24\" text-anchor=\"middle\">المتنبأ به</text>\n  <text class=\"d-label\" x=\"250\" y=\"50\" text-anchor=\"middle\">بقاء (0)</text>\n  <text class=\"d-label\" x=\"420\" y=\"50\" text-anchor=\"middle\">انقطاع (1)</text>\n  <text class=\"d-label-strong\" x=\"40\" y=\"170\" text-anchor=\"middle\" transform=\"rotate(-90 40 170)\">الفعلي</text>\n  <text class=\"d-label\" x=\"150\" y=\"115\" text-anchor=\"end\">بقي (0)</text>\n  <text class=\"d-label\" x=\"150\" y=\"215\" text-anchor=\"end\">انقطع (1)</text>\n  <rect class=\"d-box-success\" x=\"170\" y=\"64\" width=\"160\" height=\"90\" rx=\"8\"/>\n  <text class=\"d-label-strong\" x=\"250\" y=\"105\" text-anchor=\"middle\">35</text>\n  <text class=\"d-label\" x=\"250\" y=\"130\" text-anchor=\"middle\">سلبيات صحيحة</text>\n  <rect class=\"d-box-warn\" x=\"340\" y=\"64\" width=\"160\" height=\"90\" rx=\"8\"/>\n  <text class=\"d-label-strong\" x=\"420\" y=\"105\" text-anchor=\"middle\">10</text>\n  <text class=\"d-label\" x=\"420\" y=\"130\" text-anchor=\"middle\">إيجابيات كاذبة</text>\n  <rect class=\"d-box-warn\" x=\"170\" y=\"164\" width=\"160\" height=\"90\" rx=\"8\"/>\n  <text class=\"d-label-strong\" x=\"250\" y=\"205\" text-anchor=\"middle\">18</text>\n  <text class=\"d-label\" x=\"250\" y=\"230\" text-anchor=\"middle\">سلبيات كاذبة</text>\n  <rect class=\"d-box-success\" x=\"340\" y=\"164\" width=\"160\" height=\"90\" rx=\"8\"/>\n  <text class=\"d-label-strong\" x=\"420\" y=\"205\" text-anchor=\"middle\">37</text>\n  <text class=\"d-label\" x=\"420\" y=\"230\" text-anchor=\"middle\">إيجابيات صحيحة</text>\n</svg><figcaption>مصفوفة الالتباس للانحدار اللوجستي على 100 عميل اختبار</figcaption></figure>\n<p>مصفوفة القاعدة هي <code dir=\"ltr\">[[31, 14], [14, 41]]</code>. التنبؤات الصحيحة نفسها، 72، لكن الأخطاء مختلفة: النموذج يهدر عروضًا أقل (10 مقابل 14) ويفوّت منقطعين أكثر (18 مقابل 14). أيّهما أفضل يعتمد على كلفة كل خطأ، والـ accuracy لا تستطيع أن تخبرك بذلك.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ Precision والـ Recall والـ F1</h2>\n<p>نسبتان تحوّلان المصفوفة إلى أسئلة يطرحها المدير:</p>\n<ul>\n<li><strong>الـ Precision</strong> = TP / (TP + FP): من بين العملاء الذين علّمناهم، كم منهم كان راحلًا فعلًا؟ النموذج: 37 / 47 = 0.79. القاعدة: 0.75.</li>\n<li><strong>الـ Recall</strong> = TP / (TP + FN): من بين العملاء الذين رحلوا، كم منهم علّمنا؟ النموذج: 37 / 55 = 0.67. القاعدة: 0.75.</li>\n</ul>\n<p>بينهما توتّر مدمج. علّم أشخاصًا أكثر فترتفع الـ recall بينما تنخفض الـ precision عادةً؛ وعلّم عددًا أقل فيحدث العكس. أما <strong>الـ F1</strong> فهو المتوسط التوافقي للاثنين، <code dir=\"ltr\">2 * P * R / (P + R)</code>. ولا يكون مرتفعًا إلا إذا كانا مرتفعين معًا، فلا يستطيع النموذج التلاعب به بتعظيم أحدهما. استخدمه حين تحتاج إلى رقم واحد ويكون للخطأين وزن متقارب.</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.metrics </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> classification_report, roc_auc_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\"> 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:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(classification_report(y_test, model.predict(X_test), </span><span style=\"color:#953800;--shiki-dark:#FFA657\">target_names</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"stayed\"</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>\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\">\"model ROC AUC:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(roc_auc_score(y_test, 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:#0550AE;--shiki-dark:#79C0FF\">3</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\">\"rule  ROC AUC:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(roc_auc_score(y_test, X_test[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"days_since_last_order\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</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>تطبع <code dir=\"ltr\">classification_report</code> الـ precision والـ recall والـ F1 لكل فئة، مع الـ support (عدد الحالات الحقيقية). اقرأ صف “churned” للسؤال العملي؛ أما صف “stayed” فهو التحليل نفسه مع تبادل الأدوار.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الـ ROC AUC: تقييم الترتيب</h2>\n<p>تعتمد الـ precision والـ recall على الحدّ الفاصل 0.5. أما <strong>الـ ROC AUC</strong> فلا تستخدم حدًّا فاصلًا إطلاقًا. إنها تسأل: إذا اخترت منقطعًا واحدًا وباقيًا واحدًا عشوائيًا، فكم مرة يعطي النموذج المنقطعَ الدرجةَ الأعلى؟ القيمة 0.5 تعني أن الدرجات ليست أفضل من رمي عملة؛ و1.0 تعني أن كل منقطع يتقدّم على كل باقٍ.</p>\n<p>تأخذ <code dir=\"ltr\">roc_auc_score</code> درجات، لا labels: مرّر <code dir=\"ltr\">predict_proba(...)[:, 1]</code>. وأي درجة ترتّب العملاء تصلح، فيمكنك تقييم القاعدة بالقيمة الخام لـ <code dir=\"ltr\">days_since_last_order</code>. تحصل القاعدة على 0.77؛ ويحصل الانحدار اللوجستي على 0.81. هذه الفجوة هي الفرق الحقيقي بينهما، وقد أخفاه تعادل الـ accuracy: ترتيب النموذج أفضل بشكل ملموس، ولهذا كانت قائمة العشرة الأعلى خطرًا في <a href=\"/ar/lesson/ml-crash-course/l-ml-3-1\">درس الانحدار اللوجستي</a> دقيقة إلى هذا الحد.</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\">precision_score</code> و<code dir=\"ltr\">recall_score</code> الـ label 1 على أنه الإيجابي ما لم تقل غير ذلك. في <code dir=\"ltr\">load_breast_cancer</code>، 0 يعني خبيثًا و1 يعني حميدًا، فالـ recall المُبلَغ عنها بقيمة 0.99 هي recall للأورام <strong>الحميدة</strong>. وللفئة التي يهتم بها الطبيب، مرّر <code dir=\"ltr\">pos_label=0</code> أو اقرأ صف الخبيث في <code dir=\"ltr\">classification_report</code>. تحقّق من <code dir=\"ltr\">target_names</code> قبل اقتباس أي مقياس خاص بفئة.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">اختيار المقياس</h2>\n<p>ابدأ من كلفة كل خطأ، ثم اختر:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>الموقف</th>\n<th>راقب</th>\n<th>السبب</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>تفويت حالة مكلف (فحص السرطان، الاحتيال)</td>\n<td>الـ Recall</td>\n<td>كل سلبي كاذب هو الخطأ المكلف</td>\n</tr>\n<tr>\n<td>التصرّف بشأن حالة مكلف (عروض غالية، حظر حسابات)</td>\n<td>الـ Precision</td>\n<td>كل إيجابي كاذب يهدر مالًا أو ثقة</td>\n</tr>\n<tr>\n<td>ستتصرّف بشأن أفضل N بحسب الدرجة</td>\n<td>الـ ROC AUC، أو نسبة الإصابة في أفضل N</td>\n<td>جودة الترتيب هي ما يهم</td>\n</tr>\n<tr>\n<td>الخطآن مهمان، والمطلوب رقم واحد</td>\n<td>الـ F1</td>\n<td>يعاقب إهمال أيٍّ منهما</td>\n</tr>\n</tbody>\n</table></div><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>“precision بقيمة 0.79 وrecall بقيمة 0.67 عند العتبة الافتراضية، وROC AUC بقيمة 0.81” تخبر صاحب القرار بما يفعله النموذج وتترك مجالًا للحديث عن المفاضلات. أما رقم F1 أو accuracy وحيد فينهي الحديث مبكرًا جدًا.</p>\n</aside>\n<p>تحرّكت الـ precision والـ recall في اتجاهين متعاكسين بين النموذج والقاعدة لأنهما يستخدمان فعليًا حدّين فاصلين مختلفين. ويمكنك أن تحرّك هذا الحدّ بنفسك، عن قصد، وهذا موضوع الدرس القادم.</p>\n","toc":[{"id":"sec-1","text":"أربع نتائج","level":2},{"id":"sec-2","text":"الـ Precision والـ Recall والـ F1","level":2},{"id":"sec-3","text":"الـ ROC AUC: تقييم الترتيب","level":2},{"id":"sec-4","text":"اختيار المقياس","level":2}],"takeaways":["تعدّ مصفوفة الالتباس (confusion matrix) السلبيات الصحيحة والإيجابيات الكاذبة والسلبيات الكاذبة والإيجابيات الصحيحة؛ وفي scikit-learn تمثّل الصفوفُ الفئاتِ الفعلية والأعمدةُ الفئاتِ المتنبأ بها.","الـ precision هي نسبة الحالات المُعلَّمة التي هي حقيقية؛ والـ recall هي نسبة الحالات الحقيقية التي عُلِّمت.","الـ F1 هو المتوسط التوافقي للـ precision والـ recall، ومفيد حين تحتاج إلى رقم واحد يعاقب إهمال أيٍّ منهما.","يقيس الـ ROC AUC جودة الترتيب عبر كل العتبات: أي احتمال أن ينال مثال إيجابي عشوائي درجة أعلى من مثال سلبي عشوائي.","تحقّق دائمًا من الفئة الإيجابية: في <code dir=\"ltr\">load_breast_cancer</code> الورم الخبيث هو 0، فالـ recall للخبيث يحتاج إلى <code dir=\"ltr\">pos_label=0</code>."],"further":[{"title":"Classification metrics","url":"https://scikit-learn.org/stable/modules/model_evaluation.html#classification-metrics"},{"title":"confusion_matrix","url":"https://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html"},{"title":"roc_auc_score","url":"https://scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_auc_score.html"}],"quiz":[{"q":"<p>تُعيد <code dir=\"ltr\">confusion_matrix(y_test, pred)</code> القيمة <code dir=\"ltr\">[[35, 10], [18, 37]]</code> لبيانات الانقطاع (1 = منقطع). كم عميلًا منقطعًا فات النموذج؟</p>","options":[{"text":"10","why":"هذه الخانة العلوية اليمنى، أي عملاء بقوا لكنهم عُلِّموا كمنقطعين (إيجابيات كاذبة)."},{"text":"35","why":"هذه الخانة العلوية اليسرى، أي عملاء بقوا وتنبّأ النموذج ببقائهم بشكل صحيح."},{"text":"37","why":"هذه الخانة السفلية اليمنى، أي المنقطعون الذين التقطهم النموذج."},{"text":"18","why":"صحيح. الصف 1 هو المنقطعون فعليًا؛ والعمود 0 هو “متنبأ ببقائهم”. هؤلاء الـ 18 سلبيات كاذبة."}],"answer":3},{"q":"<p>كل عرض استرجاع يكلّف 40 دولارًا، لذا لا يريد فريق التسويق التواصل إلا مع العملاء المرجّح جدًا انقطاعهم. أيّ مقياس يجب أن تراقبه عن كثب؟</p>","options":[{"text":"الـ precision، لأنها تقيس كم من العملاء الذين تواصلنا معهم كانوا منقطعين فعلًا.","why":"صحيح. كل إيجابية كاذبة تعني 40 دولارًا أُنفقت على عميل كان سيبقى على أي حال."},{"text":"الـ recall، لأنها تعدّ كم منقطعًا تصل إليه.","why":"الـ recall تكافئ التواصل مع عدد أكبر من الناس، وهذا بالضبط ما يحاجج العرض المكلف ضده."},{"text":"الـ accuracy، لأنها توازن بين نوعي الخطأ.","why":"تزن الـ accuracy الخطأين بالتساوي، لكن الإيجابيات الكاذبة هنا تكلّف مالًا والسلبيات الكاذبة لا تكلّف شيئًا إضافيًا."}],"answer":0},{"q":"<p>للنموذج A قيمة ROC AUC تساوي 0.81 وللنموذج B قيمة 0.77، لكن كليهما يحقق accuracy بقيمة 0.72 عند العتبة الافتراضية. ماذا يخبرك ذلك؟</p>","options":[{"text":"فرق الـ AUC لا بد أنه خطأ حسابي، ما دامت الـ accuracy متساوية.","why":"المقياسان يقيسان أشياء مختلفة. الـ accuracy تنظر إلى عتبة واحدة؛ والـ AUC تنظر إلى الترتيب كله."},{"text":"B أفضل لأنه يبلغ الـ accuracy نفسها بترتيب أضعف.","why":"الترتيب الأضعف عيب؛ فهو يعني أن درجات B تفصل المنقطعين عن الباقين بشكل أسوأ."},{"text":"A يرتّب العملاء أفضل، فسيعطي قائمة اتصالات أفضل في معظم أحجام القوائم.","why":"صحيح. تلخّص الـ AUC جودة الترتيب، وعليها تعتمد قائمة أفضل N."}],"answer":2},{"q":"<p>نموذج على <code dir=\"ltr\">load_breast_cancer</code> يُبلغ أن <code dir=\"ltr\">recall_score(y_test, pred) = 0.99</code>. لماذا قد يضلّل هذا الرقمُ الطبيبَ؟</p>","options":[{"text":"لأن الـ recall لا يمكن أن تتجاوز 0.95 على البيانات الطبية، فهذا خطأ برمجي.","why":"لا يوجد سقف كهذا؛ فهذه البيانات سهلة الفصل حقًا."},{"text":"لأن الـ label 1 هنا يعني حميدًا، فهذه recall للأورام الحميدة، لا الخبيثة التي يهتم بها الطبيب.","why":"صحيح. مرّر <code dir=\"ltr\">pos_label=0</code> (أو اقرأ التقرير لكل فئة) لتحصل على الـ recall للخبيث."},{"text":"لأن الـ recall تتجاهل السلبيات الكاذبة، وهي ما يهمّ في الطب.","why":"الـ recall مبنية على السلبيات الكاذبة؛ والمشكلة في الفئة التي تُحسب لها."}],"answer":1}],"exercise":{"kind":"python","starter":"from sklearn.datasets import load_breast_cancer\nfrom sklearn.metrics import confusion_matrix, precision_score, recall_score, roc_auc_score\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.tree import DecisionTreeClassifier\n\ndata = load_breast_cancer()\nprint(data.target_names)          # index 0 = malignant, 1 = benign\nX, y = load_breast_cancer(return_X_y=True, as_frame=True)\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)\n\nmodel = DecisionTreeClassifier(max_depth=2, random_state=42).fit(X_train, y_train)\npred = model.predict(X_test)\n\n# 1. Confusion matrix (rows = actual, columns = predicted)\ncm = None\n\n# 2. Recall and precision for MALIGNANT tumours\nrecall_malignant = recall_score(y_test, pred)\nprecision_malignant = precision_score(y_test, pred)\n\n# 3. ROC AUC of the model's scores\nauc = None\n","solution":"from sklearn.datasets import load_breast_cancer\nfrom sklearn.metrics import confusion_matrix, precision_score, recall_score, roc_auc_score\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.tree import DecisionTreeClassifier\n\ndata = load_breast_cancer()\nprint(data.target_names)          # index 0 = malignant, 1 = benign\nX, y = load_breast_cancer(return_X_y=True, as_frame=True)\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)\n\nmodel = DecisionTreeClassifier(max_depth=2, random_state=42).fit(X_train, y_train)\npred = model.predict(X_test)\n\ncm = confusion_matrix(y_test, pred)\n\nrecall_malignant = recall_score(y_test, pred, pos_label=0)\nprecision_malignant = precision_score(y_test, pred, pos_label=0)\n\nauc = roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])\nprint(cm, recall_malignant, precision_malignant, auc)\n","checks":[{"id":"cm","test":"cm is not None and [list(map(int, r)) for r in cm] == [[50, 3], [10, 80]]"},{"id":"recall","test":"abs(float(recall_malignant) - 50 / 53) < 1e-6"},{"id":"precision","test":"abs(float(precision_malignant) - 50 / 60) < 1e-6"},{"id":"auc","test":"auc is not None and 0.9 < float(auc) < 0.97"}],"title":"مقاييس للفئة التي تهمّ","prompt":"<p>شجرة بعمق 2 تفحص أورام الثدي. في هذه البيانات <strong>0 يعني خبيثًا</strong> و1 يعني حميدًا. قِس النموذج من وجهة نظر الطبيب.</p>\n<ol>\n<li><code dir=\"ltr\">cm</code>: مصفوفة الالتباس لـ <code dir=\"ltr\">y_test</code> مقابل <code dir=\"ltr\">pred</code>.</li>\n<li><code dir=\"ltr\">recall_malignant</code> و<code dir=\"ltr\">precision_malignant</code>: الـ recall والـ precision مع اعتبار الخبيث الفئة الإيجابية.</li>\n<li><code dir=\"ltr\">auc</code>: قيمة الـ ROC AUC من الاحتمالات التي يتنبأ بها النموذج.</li>\n</ol>\n","hints":["<code dir=\"ltr\">confusion_matrix(y_test, pred)</code>: الـ labels الحقيقية أولًا.","يحسب الكود المبدئي المقاييس للـ label 1 (حميد). أضف <code dir=\"ltr\">pos_label=0</code>.","تريد <code dir=\"ltr\">roc_auc_score</code> درجات للـ label الإيجابي في <code dir=\"ltr\">y_test</code> (أي 1). استخدم <code dir=\"ltr\">model.predict_proba(X_test)[:, 1]</code>، لا <code dir=\"ltr\">predict</code>."],"explanation":"<p>تُظهر المصفوفة <code dir=\"ltr\">[[50, 3], [10, 80]]</code> ثلاثة أورام خبيثة صُنّفت حميدة: وهذا هو الخطأ الخطِر. الـ recall للخبيث هي 50/53 ≈ 0.94؛ والـ precision هي 50/60 ≈ 0.83، لأن 10 أورام حميدة عُلِّمت. ومع القيمة الافتراضية <code dir=\"ltr\">pos_label=1</code> كنت ستُبلغ عن recall الحميد (0.89) كأنها تخصّ السرطان. والـ AUC نحو 0.94: الأوراق الأربع للشجرة تعطي ترتيبًا خشنًا لكنه مفيد.</p>\n","checkLabels":{"cm":"cm هي مصفوفة الالتباس","recall":"recall_malignant تعتبر الخبيث (0) الفئة الإيجابية","precision":"precision_malignant تعتبر الخبيث (0) الفئة الإيجابية","auc":"auc محسوبة من الاحتمالات"}},"words":666,"readMinutes":4}