{"id":"l-ml-3-2","courseId":"ml-crash-course","lang":"ar","kind":"lesson","summary":"ازرع أشجار قرار على بيانات الانقطاع، واقرأها كقواعد if/else بسيطة، وتحكّم في حجمها بـ max_depth وmin_samples_leaf، واعرف متى لا يمكن الوثوق باحتمالاتها.","html":"<p>اسأل قائد فريق الدعم كيف يكتشف العملاء الذين أوشكوا على الرحيل، وستسمع شيئًا مثل: “إذا لم يطلبوا شيئًا منذ أشهر، فقد رحلوا. وإذا كانوا حديثين لكنهم لم يشتروا إلا مرة أو مرتين، فالأمر متأرجح. أما الزبائن الدائمون الذين طلبوا مؤخرًا فلا خوف عليهم”. هذه شجرة قرار (decision tree). وفي هذا الدرس تنمّي واحدة من البيانات وتتحقق إن كانت توافقه.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">عشرون سؤالًا تختارها البيانات</h2>\n<p>تتنبأ شجرة القرار بطرح أسئلة نعم/لا عن feature واحد في كل مرة. ولبنائها، تنظر الخوارزمية في كل feature وكل عتبة ممكنة، وتختار السؤال الذي يقسم عملاء التدريب إلى أنقى مجموعتين: في الحالة المثالية، كل المنقطعين في جهة وكل الباقين في الأخرى. وتُقاس النقاوة افتراضيًا بـ <strong>Gini impurity</strong> (0 لمجموعة من فئة واحدة فقط، و0.5 لخليط 50/50). ثم تكرّر البحث داخل كل مجموعة، وهكذا نزولًا.</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.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.tree </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> DecisionTreeClassifier, export_text</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\">tree </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> DecisionTreeClassifier(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">max_depth</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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\">).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\">(export_text(tree, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">feature_names</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">features, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">show_weights</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></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>تطبع <code dir=\"ltr\">export_text</code> القواعد، ويضيف <code dir=\"ltr\">show_weights=True</code> عدد عملاء التدريب من كل فئة (بقي، انقطع) الذين وصلوا إلى كل ورقة.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 270\" role=\"img\" aria-labelledby=\"l-ml-3-2-t1\"><defs><marker id=\"arrow-l-ml-3-2-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-2-t1\">سؤال الجذر: هل الأيام منذ آخر طلب 236.5 على الأكثر. إذا نعم، اسأل هل الطلبات 3.5 على الأكثر: نعم تعطي ورقة فيها 60 بقوا و67 انقطعوا، ولا تعطي 60 بقوا و13 انقطعوا. وإذا لا، اسأل هل مدة العضوية 637.5 على الأكثر: نعم تعطي 11 بقوا و84 انقطعوا، ولا تعطي 2 بقيا و1 انقطع.</title>\n  <rect class=\"d-box-primary\" x=\"235\" y=\"10\" width=\"230\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"350\" y=\"37\" text-anchor=\"middle\">days_since_last ≤ 236.5?</text>\n  <path class=\"d-arrow\" d=\"M300 54 L180 100\" marker-end=\"url(#arrow-l-ml-3-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M400 54 L520 100\" marker-end=\"url(#arrow-l-ml-3-2-1)\"/>\n  <text class=\"d-label-muted\" x=\"220\" y=\"80\">نعم</text>\n  <text class=\"d-label-muted\" x=\"470\" y=\"80\">لا</text>\n  <rect class=\"d-box\" x=\"80\" y=\"104\" width=\"200\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"180\" y=\"131\" text-anchor=\"middle\">orders ≤ 3.5?</text>\n  <rect class=\"d-box\" x=\"420\" y=\"104\" width=\"200\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"520\" y=\"131\" text-anchor=\"middle\">tenure_days ≤ 637.5?</text>\n  <path class=\"d-arrow\" d=\"M140 148 L80 196\" marker-end=\"url(#arrow-l-ml-3-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M220 148 L270 196\" marker-end=\"url(#arrow-l-ml-3-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M480 148 L430 196\" marker-end=\"url(#arrow-l-ml-3-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M560 148 L610 196\" marker-end=\"url(#arrow-l-ml-3-2-1)\"/>\n  <rect class=\"d-box-warn\" x=\"10\" y=\"200\" width=\"140\" height=\"54\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"80\" y=\"222\" text-anchor=\"middle\">60 بقوا / 67 انقطعوا</text>\n  <text class=\"d-label-strong\" x=\"80\" y=\"243\" text-anchor=\"middle\">متأرجح</text>\n  <rect class=\"d-box-success\" x=\"200\" y=\"200\" width=\"140\" height=\"54\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"270\" y=\"222\" text-anchor=\"middle\">60 بقوا / 13 انقطعوا</text>\n  <text class=\"d-label-strong\" x=\"270\" y=\"243\" text-anchor=\"middle\">يبقى</text>\n  <rect class=\"d-box-accent\" x=\"360\" y=\"200\" width=\"140\" height=\"54\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"430\" y=\"222\" text-anchor=\"middle\">11 بقوا / 84 انقطعوا</text>\n  <text class=\"d-label-strong\" x=\"430\" y=\"243\" text-anchor=\"middle\">ينقطع</text>\n  <rect class=\"d-box\" x=\"540\" y=\"200\" width=\"140\" height=\"54\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"610\" y=\"222\" text-anchor=\"middle\">2 بقيا / 1 انقطع</text>\n  <text class=\"d-label-strong\" x=\"610\" y=\"243\" text-anchor=\"middle\">3 عملاء فقط!</text>\n</svg><figcaption>شجرة الانقطاع بعمق 2</figcaption></figure>\n<p>ثلاث أوراق تطابق كلام قائد فريق الدعم حرفًا بحرف أو تكاد. العملاء الصامتون منذ زمن طويل ينقطعون (84 من 95). والزبائن الدائمون الحديثون بأربعة طلبات أو أكثر يبقون (60 من 73). والعملاء الحديثون قليلو الطلبات متأرجحون (67 من 127 ينقطعون). واكتشفت الشجرة أيضًا <strong>تفاعلًا (interaction)</strong> لم يضطر أحد إلى كتابته: عدد الطلبات مهم، لكن بين العملاء الحديثين فقط.</p>\n<p>أما الورقة الرابعة فتحذير. لقد أنفقت الشجرة سؤالًا كاملًا لعزل ثلاثة عملاء ذوي عضوية طويلة جدًا. هذا هو البحث الجشع (greedy) أثناء عمله: يختار التقسيم الذي يساعد أكثر في اللحظة الحالية، حتى لو جاءت المساعدة من مجموعة أصغر من أن تعني شيئًا.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">كيف تتنبأ الشجرة بالاحتمالات</h2>\n<p>العميل الذي يقع في ورقة يحصل على حصص الفئات في تلك الورقة كاحتمالات. قع في ورقة “الزبائن الدائمين” وستُعيد <code dir=\"ltr\">predict_proba</code> نحو <code dir=\"ltr\">[0.82, 0.18]</code>. لذا فإن شجرة بعمق 2 لا تستطيع أبدًا أن تُخرج إلا أربعة احتمالات مختلفة، واحدًا لكل ورقة. هذا خشن، لكنه صادق. وتبدأ المشكلة حين لا تكون للشجرة حدود.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">النمو أكثر من اللازم، وكيف توقفه</h2>\n<p>تواصل الشجرة غير المقيّدة التقسيم حتى تصبح كل ورقة نقية. على بيانات الانقطاع، هذا يعني 64 ورقة، بعمق 11 مستوى، ودقة تدريب 100%. كل ورقة نقية، فكل احتمال يساوي 0 أو 1 بالضبط: الشجرة تدّعي يقينًا تامًا بشأن كل عميل اختبار بينما لا تصيب إلا في 72% من الحالات. ومع ترتيب العملاء بمثل هذه الاحتمالات، يتعادل معظمهم، وتتحوّل قائمة الاتصالات من الدرس السابق إلى يانصيب.</p>\n<p>اثنان من الـ hyperparameters يتوليان معظم مهمة إبقاء الشجرة صادقة:</p>\n<ul>\n<li><code dir=\"ltr\">max_depth</code> يحدّد أقصى عدد من الأسئلة المتتالية التي تستطيع الشجرة طرحها.</li>\n<li><code dir=\"ltr\">min_samples_leaf</code> يرفض أي تقسيم يترك في ورقة عددًا من عملاء التدريب أقل من هذه القيمة. وهو يستهدف بالضبط مشكلة الورقة الرابعة أعلاه، ويتيح للشجرة أن تتعمّق حيث البيانات وفيرة وأن تبقى ضحلة حيث ليست كذلك.</li>\n</ul>\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.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.tree </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> DecisionTreeClassifier</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">churn </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churn.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">features </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"tenure_days\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"total_spent\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"avg_order_value\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">            \"days_since_last_order\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"used_coupon\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"support_tickets\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mobile_share\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X_train, X_test, y_train, y_test </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split(</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    churn[features], churn[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">test_size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.25</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    stratify</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">churn[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> settings </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [{}, {</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"max_depth\"</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 style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"min_samples_leaf\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">20</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}]:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    t </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> DecisionTreeClassifier(</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">**</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">settings).fit(X_train, y_train)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    distinct </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> len</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(t.predict_proba(X_test)[:, </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:#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:#0550AE;--shiki-dark:#79C0FF\">str</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(settings)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:24}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> leaves </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">t.get_n_leaves()</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:3}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  train </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">t.score(X_train, y_train)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.2f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  \"</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">          f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"test </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">t.score(X_test, y_test)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.2f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  distinct probabilities </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">distinct</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>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">t </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> DecisionTreeClassifier(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">min_samples_leaf</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">20</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\">).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\">(pd.Series(t.feature_importances_, </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 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\">).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=\"1\" hidden></div></div>\n<p>يقول <code dir=\"ltr\">feature_importances_</code> كم خفّض كل feature من عدم النقاوة عبر كل تقسيماته، بعد تطبيع القيم ليكون مجموعها 1. تستحوذ حداثة آخر طلب على أكثر من النصف، ويأتي عدد الطلبات ثانيًا؛ وتحصل القسائم وتذاكر الدعم على صفر لأن الشجرة لم تستخدمها قط. عامِل هذه الأرقام كوصف لهذه الشجرة، لا للعالم: فالـ features التي لم تحتجها الشجرة قد تظل مفيدة، والدرس القادم يبيّن طريقة أجدر بالثقة لقياس الأهمية.</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\">random_state</code> في <code dir=\"ltr\">train_test_split</code> وأعد التدريب: قد يبقى سؤال الجذر، لكن الفروع تحته كثيرًا ما تتغيّر. التقسيم الأول للشجرة يحسم كل ما تحته، لذا قد يُنتج تغيير صغير في البيانات شجرة مختلفة المظهر بنتيجة مشابهة. اقرأ الشجرة الواحدة كقصة واحدة محتملة، لا كحقيقة عن عملائك.</p>\n</aside>\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>تقسم الأشجار على ترتيب القيم، فلا تحتاج إلى توحيد المقاييس، ولا تكترث بالقيم الشاذة في الـ features، وتتعامل مع خليط الأرقام الكبيرة والصغيرة دون تذمّر. ولهذا تُعدّ النماذج القائمة على الأشجار الخيار الافتراضي للبيانات الجدولية عمليًا، بشرط أن تستخدم كثيرًا منها معًا.</p>\n</aside>\n<p>وعدم الاستقرار هذا فرصة أيضًا. فإذا كانت الشجرة الواحدة مليئة بالضجيج، فخذ متوسط أشجار كثيرة نمت على بيانات مختلفة قليلًا، وسيلغي الضجيج بعضه بعضًا. هذه هي الفكرة وراء الغابات العشوائية والـ gradient boosting، موضوع الدرس القادم.</p>\n","toc":[{"id":"sec-1","text":"عشرون سؤالًا تختارها البيانات","level":2},{"id":"sec-2","text":"كيف تتنبأ الشجرة بالاحتمالات","level":2},{"id":"sec-3","text":"النمو أكثر من اللازم، وكيف توقفه","level":2}],"takeaways":["تقسم شجرة القرار البيانات بسؤال نعم/لا واحد في كل مرة، وتختار في كل خطوة السؤال الذي يجعل المجموعات أنقى ما يمكن.","الشجرة المدرَّبة مجموعة من القواعد المقروءة؛ و<code dir=\"ltr\">export_text</code> تطبعها.","الأشجار غير المقيّدة تنمو حتى تصبح كل ورقة نقية، فتحفظ بيانات التدريب وتنتج احتمالات مفرطة الثقة بقيمة 0 أو 1.","يحدّ <code dir=\"ltr\">max_depth</code> و<code dir=\"ltr\">min_samples_leaf</code> من النمو؛ ويمنع <code dir=\"ltr\">min_samples_leaf</code> الشجرة من إنفاق تقسيم على حفنة من العملاء.","لا تحتاج الأشجار إلى توحيد مقاييس الـ features وتلتقط التفاعلات تلقائيًا، لكن الشجرة الواحدة غير مستقرة: تغييرات صغيرة في البيانات قد تعيد تشكيلها."],"further":[{"title":"Decision Trees","url":"https://scikit-learn.org/stable/modules/tree.html"},{"title":"export_text","url":"https://scikit-learn.org/stable/modules/generated/sklearn.tree.export_text.html"}],"quiz":[{"q":"<p>ورقة في شجرة انقطاع تضم 60 عميلًا بقوا و13 انقطعوا. ماذا تُعيد <code dir=\"ltr\">predict_proba</code> لعميل يقع فيها؟</p>","options":[{"text":"نحو <code dir=\"ltr\">[0.82, 0.18]</code>: أي حصص الفئات في تلك الورقة.","why":"صحيح. احتمال الشجرة هو نسبة عملاء التدريب من كل فئة داخل الورقة."},{"text":"<code dir=\"ltr\">[1.0, 0.0]</code>، لأن فئة الأغلبية في الورقة هي 0.","why":"هذا ما تُبلغ عنه <code dir=\"ltr\">predict</code> كـ label. أما <code dir=\"ltr\">predict_proba</code> فتحتفظ بالنِّسب."},{"text":"<code dir=\"ltr\">[0.5, 0.5]</code>، لأن الأشجار لا تقدّر الاحتمالات.","why":"الأشجار تقدّرها فعلًا، من نِسب الأوراق، وإن كان ذلك بشكل خشن."},{"text":"يعتمد على بُعد العميل عن عتبة التقسيم.","why":"داخل الورقة يحصل كل عميل على الإجابة نفسها؛ والبُعد عن العتبة لا دور له."}],"answer":0},{"q":"<p>تنبؤات شجرة غير محدودة على بيانات الاختبار لا تحتوي إلا احتمالين مختلفين، 0.0 و1.0. لماذا تُعدّ هذه مشكلة؟</p>","options":[{"text":"لأنها تعني أن في الشجرة خطأً برمجيًا ويجب إعادة تدريبها بـ <code dir=\"ltr\">random_state</code> آخر.","why":"هذا سلوك متوقَّع، لا خطأ. فكل ورقة في شجرة مكتملة النمو نقية."},{"text":"لأن الاحتمالات التي تساوي 0 أو 1 بالضبط لا يمكن استخدامها لترتيب العملاء.","why":"يمكن ترتيبها، لكن مع قيمتين فقط يكاد يتعادل كل العملاء، فلا يحمل الترتيب أي معلومة."},{"text":"لأن الشجرة تدّعي يقينًا تامًا بشأن كل عميل، وتُثبت دقتها على الاختبار البالغة 0.72 أن ذلك غير صحيح.","why":"صحيح. الأوراق النقية تنتج احتمالات مفرطة الثقة، والترتيب الذي تبنيه منها يكاد يكون بلا فائدة."}],"answer":2},{"q":"<p>أيّ هذه ميزة حقيقية للأشجار على الانحدار اللوجستي في بيانات الانقطاع؟</p>","options":[{"text":"الأشجار تعمّم دائمًا بشكل أفضل لأنها تستطيع نمذجة المنحنيات.","why":"المرونة سلاح ذو حدّين. على هذه البيانات يؤدي انحدار لوجستي مقيَّد بالمستوى نفسه أو أفضل."},{"text":"الأشجار تعطي احتمالات أنعم وأفضل معايرة.","why":"احتمالات الأشجار نِسب أوراق خشنة، وعادةً أقل نعومة من احتمالات الانحدار اللوجستي."},{"text":"الأشجار مستقرة؛ فإعادة التدريب على بيانات مختلفة قليلًا تعطي شجرة شبه مطابقة.","why":"العكس هو الصحيح. فالتقسيم الأول المختلف يغيّر كل ما تحته."},{"text":"الأشجار تلتقط تفاعلات مثل “قلة الطلبات مهمة فقط للعملاء الحديثين” دون أن تنشئ أنت الـ feature.","why":"صحيح. كل فرع يطرح أسئلته اللاحقة الخاصة، فتأتي التفاعلات مجانًا."}],"answer":3}],"exercise":{"kind":"python","files":["churn.csv"],"starter":"import pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.tree import DecisionTreeClassifier, export_text\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\ntree = DecisionTreeClassifier(max_depth=2, random_state=42).fit(X_train, y_train)\nprint(export_text(tree, feature_names=features))\n\n# 1. Rewrite the printed tree as plain Python rules. Return 1 for churn, 0 for stay.\ndef rule_predict(row):\n    if row[\"days_since_last_order\"] <= 236.5:\n        return 1\n    return 1\n\nrule_preds = [rule_predict(row) for _, row in X_test.iterrows()]\n\n# 2. A tree limited by min_samples_leaf=20 (random_state=42), fitted on the training data\nleafy = None\n\n# 3. The name of the feature with the highest importance in `leafy`\ntop_feature = None\n","solution":"import pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.tree import DecisionTreeClassifier, export_text\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\ntree = DecisionTreeClassifier(max_depth=2, random_state=42).fit(X_train, y_train)\nprint(export_text(tree, feature_names=features))\n\ndef rule_predict(row):\n    if row[\"days_since_last_order\"] <= 236.5:\n        if row[\"orders\"] <= 3.5:\n            return 1\n        return 0\n    if row[\"tenure_days\"] <= 637.5:\n        return 1\n    return 0\n\nrule_preds = [rule_predict(row) for _, row in X_test.iterrows()]\n\nleafy = DecisionTreeClassifier(min_samples_leaf=20, random_state=42).fit(X_train, y_train)\nimportances = pd.Series(leafy.feature_importances_, index=features)\ntop_feature = importances.idxmax()\nprint(top_feature)\n","checks":[{"id":"rules-test","test":"list(map(int, rule_preds)) == tree.predict(X_test).tolist()"},{"id":"rules-train","test":"[int(rule_predict(r)) for _, r in X_train.iterrows()] == tree.predict(X_train).tolist()"},{"id":"leafy","test":"leafy is not None and hasattr(leafy, 'tree_') and leafy.min_samples_leaf == 20 and leafy.get_n_leaves() == __import__('sklearn.tree', fromlist=['x']).DecisionTreeClassifier(min_samples_leaf=20, random_state=42).fit(X_train, y_train).get_n_leaves()"},{"id":"top-feature","test":"top_feature == 'days_since_last_order'"}],"title":"حوّل الشجرة إلى قواعد","prompt":"<p>الشجرة كومة من عبارات if/else. أثبت ذلك.</p>\n<ol>\n<li>شغّل الكود لطباعة الشجرة بعمق 2. أكمل <code dir=\"ltr\">rule_predict(row)</code> بحيث تُعيد بالضبط ما تتنبأ به الشجرة (1 = انقطاع، 0 = بقاء) لأي صف عميل. تقارنها الفحوص بـ <code dir=\"ltr\">tree.predict</code> على كل صفوف التدريب والاختبار.</li>\n<li><code dir=\"ltr\">leafy</code>: نموذج <code dir=\"ltr\">DecisionTreeClassifier(min_samples_leaf=20, random_state=42)</code> مدرَّب على بيانات التدريب.</li>\n<li><code dir=\"ltr\">top_feature</code>: اسم الـ feature صاحب أكبر قيمة في <code dir=\"ltr\">feature_importances_</code> داخل <code dir=\"ltr\">leafy</code>.</li>\n</ol>\n","hints":["للشجرة المطبوعة مستويان. كل سطر <code dir=\"ltr\">|---</code> شرط؛ وأسطر <code dir=\"ltr\">class:</code> هي القيم المُعادة.","تحت <code dir=\"ltr\">days_since_last_order &lt;= 236.5</code> تسأل الشجرة عن <code dir=\"ltr\">orders</code>؛ وتحت الفرع الآخر تسأل عن <code dir=\"ltr\">tenure_days</code>.","يُعيد <code dir=\"ltr\">pd.Series(leafy.feature_importances_, index=features).idxmax()</code> اسم الـ feature الأهم."],"explanation":"<p>تتحوّل الأوراق الأربع إلى أربع عبارات <code dir=\"ltr\">return</code>. فرع العملاء الحديثين يعتمد على عدد الطلبات، وفرع الصامتين منذ زمن طويل يكاد يكون انقطاعًا دائمًا باستثناء المجموعة الصغيرة من ثلاثة عملاء ذوي عضوية طويلة جدًا. هذا هو التفاعل الذي وجدته الشجرة بنفسها. ومثل هذا الكود يمكن أن يعمل في تعبير <code dir=\"ltr\">CASE</code> في SQL، وهذا أحد أسباب سهولة إطلاق الأشجار الضحلة. ويهيمن <code dir=\"ltr\">days_since_last_order</code> على الأهمية في الشجرة الأكبر أيضًا.</p>\n","checkLabels":{"rules-test":"rule_predict تطابق الشجرة على كل عملاء الاختبار","rules-train":"rule_predict تطابق الشجرة على كل عملاء التدريب","leafy":"leafy مدرَّبة بـ min_samples_leaf=20","top-feature":"top_feature يسمّي الـ feature الأهم"}},"words":637,"readMinutes":4}