{"id":"l-ml-4-2","courseId":"ml-crash-course","lang":"ar","kind":"lesson","summary":"ابنِ Pipeline واحدًا يملأ القيم المفقودة ويوحّد المقاييس ويطبّق one-hot encoding على الأعمدة الصحيحة باستخدام ColumnTransformer، فتُعاد المعالجة المسبقة داخل كل طيّة تحقق متقاطع وترافق النموذج أينما ذهب.","html":"<p>ظلّ القسم 3 يترك بيانات على الطاولة. فـ <code dir=\"ltr\">country</code> و<code dir=\"ltr\">segment</code> نصوص، ولا يقبلها أي نموذج في scikit-learn مباشرة. و<code dir=\"ltr\">avg_satisfaction</code> فيه فجوات يرفضها الانحدار اللوجستي. وكنت توحّد المقاييس يدويًا، متتبّعًا <code dir=\"ltr\">X_train_s</code> و<code dir=\"ltr\">X_test_s</code>. كل خطوة من هذه الخطوات يجب أن تُتعلَّم من بيانات التدريب وحدها، وتُطبَّق بالطريقة نفسها على البيانات الجديدة، وتُعاد داخل كل طيّة تحقق متقاطع. وفعل ذلك يدويًا هو الطريقة التي يحدث بها التسرّب والأخطاء. الـ pipeline يفعله نيابةً عنك.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الـ Pipelines تجمع الخطوات في estimator واحد</h2>\n<p>الـ <code dir=\"ltr\">Pipeline</code> قائمة من الخطوات: أي عدد من <strong>المحوّلات (transformers)</strong> (كائنات لها <code dir=\"ltr\">fit</code> و<code dir=\"ltr\">transform</code>، مثل <code dir=\"ltr\">StandardScaler</code>) يليها estimator نهائي واحد. ويتصرّف الـ pipeline كنموذج واحد:</p>\n<ul>\n<li>يدرّب <code dir=\"ltr\">pipe.fit(X, y)</code> المحوّل الأول، ويحوّل البيانات، ويمرّرها إلى الخطوة التالية، وهكذا، ثم يدرّب النموذج على الناتج.</li>\n<li>يمرّر <code dir=\"ltr\">pipe.predict(X_new)</code> البيانات <code dir=\"ltr\">X_new</code> عبر المحوّلات المدرَّبة مسبقًا، ثم يتنبأ.</li>\n</ul>\n<p>استخدمت <code dir=\"ltr\">make_pipeline</code>، التي تسمّي الخطوات تلقائيًا (<code dir=\"ltr\">&quot;standardscaler&quot;</code> و<code dir=\"ltr\">&quot;logisticregression&quot;</code>). أما <code dir=\"ltr\">Pipeline([(&quot;prep&quot;, ...), (&quot;model&quot;, ...)])</code> فيتيح لك اختيار الأسماء، وستحتاج إلى ذلك في الدرس القادم حين تضبط المعاملات بأسمائها.</p>\n<p>المكسب الحقيقي يظهر في التحقق المتقاطع. تستنسخ <code dir=\"ltr\">cross_validate</code> الـ pipeline بأكمله وتعيد تدريبه في كل طيّة، فتُتعلَّم متوسطات الـ scaler ووسيطات الـ imputer وقائمة فئات الـ encoder من صفوف تدريب تلك الطيّة وحدها.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">أعمدة مختلفة، ومعالجة مختلفة</h2>\n<p>ليس كل عمود بحاجة إلى المعالجة المسبقة نفسها. يوجّه <code dir=\"ltr\">ColumnTransformer</code> كل مجموعة من الأعمدة عبر محوّلها الخاص، ثم يلصق المخرجات جنبًا إلى جنب.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 280\" role=\"img\" aria-labelledby=\"l-ml-4-2-t1\"><defs><marker id=\"arrow-l-ml-4-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-4-2-t1\">ينقسم جدول الانقطاع الخام إلى ثلاث مجموعات من الأعمدة. تمرّ الأعمدة الرقمية الثمانية عبر StandardScaler. ويمرّ avg_satisfaction عبر SimpleImputer مع مؤشّر، ثم StandardScaler. ويمرّ country وsegment عبر OneHotEncoder. تُضمّ المخرجات في 21 عمودًا وتُغذّى إلى LogisticRegression.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"110\" width=\"120\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"70\" y=\"134\" text-anchor=\"middle\">الجدول الخام</text>\n  <text class=\"d-label-muted\" x=\"70\" y=\"154\" text-anchor=\"middle\">11 عمودًا</text>\n  <path class=\"d-arrow\" d=\"M130 125 L190 50\" marker-end=\"url(#arrow-l-ml-4-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M130 138 L190 138\" marker-end=\"url(#arrow-l-ml-4-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M130 151 L190 226\" marker-end=\"url(#arrow-l-ml-4-2-1)\"/>\n  <rect class=\"d-box-accent\" x=\"195\" y=\"24\" width=\"250\" height=\"52\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"320\" y=\"46\" text-anchor=\"middle\">8 numeric</text>\n  <text class=\"d-label\" x=\"320\" y=\"66\" text-anchor=\"middle\">StandardScaler</text>\n  <rect class=\"d-box-warn\" x=\"195\" y=\"112\" width=\"250\" height=\"52\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"320\" y=\"134\" text-anchor=\"middle\">avg_satisfaction</text>\n  <text class=\"d-label\" x=\"320\" y=\"154\" text-anchor=\"middle\">SimpleImputer + مؤشّر، ثم scaler</text>\n  <rect class=\"d-box-success\" x=\"195\" y=\"200\" width=\"250\" height=\"52\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"320\" y=\"222\" text-anchor=\"middle\">country, segment</text>\n  <text class=\"d-label\" x=\"320\" y=\"242\" text-anchor=\"middle\">OneHotEncoder</text>\n  <path class=\"d-arrow\" d=\"M445 50 L505 125\" marker-end=\"url(#arrow-l-ml-4-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M445 138 L505 138\" marker-end=\"url(#arrow-l-ml-4-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M445 226 L505 151\" marker-end=\"url(#arrow-l-ml-4-2-1)\"/>\n  <rect class=\"d-box-primary\" x=\"510\" y=\"104\" width=\"180\" height=\"68\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"600\" y=\"130\" text-anchor=\"middle\">21 عمودًا</text>\n  <text class=\"d-label\" x=\"600\" y=\"152\" text-anchor=\"middle\">LogisticRegression</text>\n</svg><figcaption>ColumnTransformer داخل Pipeline</figcaption></figure>\n<p>ثلاثة محوّلات تقوم بالعمل:</p>\n<ul>\n<li><strong><code dir=\"ltr\">OneHotEncoder</code></strong> ينشئ عمود 0/1 لكل فئة: <code dir=\"ltr\">country_Egypt</code> و<code dir=\"ltr\">country_Germany</code> وهكذا. اضبط <code dir=\"ltr\">handle_unknown=&quot;ignore&quot;</code>؛ فالقيمة الافتراضية ترفع خطأً في أول مرة يصل فيها عميل من دولة لم تتضمّنها بيانات التدريب، وهذا يعني في بيئة الإنتاج خدمة تنبؤ منهارة.</li>\n<li><strong><code dir=\"ltr\">SimpleImputer</code></strong> يملأ القيم المفقودة بالمتوسط أو الوسيط أو القيمة الأكثر تكرارًا أو قيمة ثابتة، متعلَّمة من بيانات التدريب. ومع <code dir=\"ltr\">add_indicator=True</code> يضيف أيضًا عمودًا قيمته 1 حيث كانت القيمة مفقودة. نصف عملاء Cartwheel لم يجيبوا عن الاستبيان قط، وقد تكون هذه الحقيقة أهم من الدرجة نفسها.</li>\n<li><strong><code dir=\"ltr\">StandardScaler</code></strong>، كما سبق. ويتيح <code dir=\"ltr\">make_pipeline</code> صغير لمجموعة أعمدة واحدة أن تمرّ عبر خطوتين بالترتيب.</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.compose </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ColumnTransformer</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.impute </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> SimpleImputer</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.linear_model </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> LogisticRegression</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.model_selection </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StratifiedKFold, cross_val_score, 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\"> Pipeline, 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\"> OneHotEncoder, 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\">X </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> churn.drop(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">columns</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer_id\"</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:#1F2328;--shiki-dark:#E6EDF3\">y </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\">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(X, y, </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 style=\"color:#953800;--shiki-dark:#FFA657\">stratify</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">y)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">numeric </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\">preprocess </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ColumnTransformer([</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    (</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"num\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, StandardScaler(), numeric),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    (</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"sat\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, make_pipeline(SimpleImputer(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">strategy</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"median\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">add_indicator</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\">), StandardScaler()),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">     [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"avg_satisfaction\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    (</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"cat\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, OneHotEncoder(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">handle_unknown</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ignore\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"country\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"segment\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">pipe </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Pipeline([(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"prep\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, preprocess), (</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"model\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, LogisticRegression())])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">pipe.fit(X_train, y_train)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">names </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pipe.named_steps[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"prep\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].get_feature_names_out()</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:#0550AE;--shiki-dark:#79C0FF\">len</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(names), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"model inputs, e.g.\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">list</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(names[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">8</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">cv </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StratifiedKFold(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">n_splits</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">shuffle</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"all columns  ROC AUC:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, cross_val_score(pipe, X_train, y_train, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">cv</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">cv, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">scoring</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"roc_auc\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).mean().round(</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:#1F2328;--shiki-dark:#E6EDF3\">numeric_only </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline(StandardScaler(), LogisticRegression())</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"numeric only ROC AUC:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, cross_val_score(numeric_only, X_train[numeric], y_train, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">cv</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">cv, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">scoring</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"roc_auc\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).mean().round(</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=\"0\" hidden></div></div>\n<p>يقبل الـ pipeline الـ DataFrame الخام، بما فيه من نصوص وفجوات، وينتج 21 مُدخلًا للنموذج: ثمانية أرقام موحّدة المقاييس، ودرجة الرضا المملوءة ومؤشّر غيابها، وثمانية أعمدة للدول وثلاثة للشرائح. وتسردها <code dir=\"ltr\">get_feature_names_out</code> مع بادئة تسمّي المحوّل الذي أنتج كلًّا منها، وهكذا تطابق المعاملات بالأعمدة لاحقًا.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">أعمدة أكثر، ونموذج أسوأ</h2>\n<p>ثم تأتي المفاجأة: الـ pipeline الكامل يرتّب العملاء أسوأ من الأعمدة الرقمية الثمانية وحدها، 0.776 مقابل 0.807. أحد عشر عمود one-hot ودرجة رضا نصف قيمها مملوءة تعطي الانحدار اللوجستي معاملات أكثر ليلائمها بـ 298 صفًا فقط، وعلى هذه البيانات تحمل الأعمدة الإضافية ضجيجًا أكثر من الإشارة. صحيح أن نسبة الانقطاع في مصر تبدو أعلى في بيانات التدريب، لكن مع 44 عميلًا مصريًا في التدريب، هذا أضعف من أن يُراهَن عليه.</p>\n<p>هذا ليس فشلًا للـ pipeline. بل هو الـ pipeline يؤدي عمله: جعل اختبار الأعمدة الإضافية تغييرًا من خمسة أسطر، وأعطى التحقق المتقاطع إجابة واضحة. يبقى النموذج الرقمي البطل حاليًا. والأعمدة الفئوية تستحق العودة إليها حين يصبح لدى Cartwheel بضعة آلاف من العملاء، أو مع regularization أقوى.</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\">StandardScaler().fit_transform(X)</code> على الجدول كاملًا، ثم <code dir=\"ltr\">train_test_split</code> أو <code dir=\"ltr\">cross_val_score</code>، يعني أن صفوف التحقق ساهمت في حساب المتوسطات والانحرافات المعيارية. مع توحيد المقاييس يكون التسرّب صغيرًا؛ أما مع ملء القيم المفقودة أو الـ target encoding أو اختيار الـ features فقد يكون كبيرًا. ضع كل خطوة معالجة مسبقة متعلَّمة داخل الـ pipeline ومرّر إليه البيانات الخام.</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>ادخل إلى pipeline مدرَّب</span></p>\n<p>يصل بك <code dir=\"ltr\">pipe.named_steps[&quot;model&quot;].coef_</code> (أو <code dir=\"ltr\">pipe[-1].coef_</code>) إلى النموذج؛ ويُظهر <code dir=\"ltr\">pipe.named_steps[&quot;prep&quot;].named_transformers_[&quot;cat&quot;].categories_</code> الفئات التي تعلّمها الـ encoder. الـ pipelines ليست صناديق سوداء؛ إنها حاويات يمكنك فتحها.</p>\n</aside>\n<p>ويعطي الـ pipeline أيضًا كل إعداد عنوانًا: <code dir=\"ltr\">model__C</code> هو <code dir=\"ltr\">C</code> في الخطوة المسمّاة <code dir=\"ltr\">model</code>، و<code dir=\"ltr\">prep__sat__simpleimputer__strategy</code> يصل إلى الـ imputer. يستخدم الدرس القادم هذه العناوين للبحث عن أفضل الإعدادات تلقائيًا.</p>\n","toc":[{"id":"sec-1","text":"الـ Pipelines تجمع الخطوات في estimator واحد","level":2},{"id":"sec-2","text":"أعمدة مختلفة، ومعالجة مختلفة","level":2},{"id":"sec-3","text":"أعمدة أكثر، ونموذج أسوأ","level":2}],"takeaways":["يسلسل الـ <code dir=\"ltr\">Pipeline</code> محوّلات (transformers) ونموذجًا نهائيًا في estimator واحد: يدرّب <code dir=\"ltr\">fit</code> كل خطوة بالترتيب، ويمرّر <code dir=\"ltr\">predict</code> البيانات الجديدة عبر الخطوات نفسها.","يطبّق <code dir=\"ltr\">ColumnTransformer</code> معالجة مسبقة مختلفة على أعمدة مختلفة ثم يضمّ النتائج جنبًا إلى جنب.","يحوّل <code dir=\"ltr\">OneHotEncoder(handle_unknown=&quot;ignore&quot;)</code> الفئات إلى أعمدة 0/1 ولا ينهار أمام فئة لم يرَها في التدريب.","يملأ <code dir=\"ltr\">SimpleImputer(add_indicator=True)</code> الفجوات ويضيف عمودًا يسجّل القيم التي كانت مفقودة.","لأن الـ pipeline كله يُعاد تدريبه في كل طيّة، لا تتعلّم المعالجة المسبقة إلا من صفوف التدريب، وهذا يُبقي التحقق المتقاطع أمينًا."],"further":[{"title":"Pipelines and composite estimators","url":"https://scikit-learn.org/stable/modules/compose.html"},{"title":"ColumnTransformer","url":"https://scikit-learn.org/stable/modules/generated/sklearn.compose.ColumnTransformer.html"},{"title":"OneHotEncoder","url":"https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html"},{"title":"Imputation of missing values","url":"https://scikit-learn.org/stable/modules/impute.html"}],"quiz":[{"q":"<p>أثناء <code dir=\"ltr\">cross_validate(pipe, X_train, y_train, cv=5)</code>، من أين يحصل الـ <code dir=\"ltr\">StandardScaler</code> داخل <code dir=\"ltr\">pipe</code> على متوسطاته؟</p>","options":[{"text":"من طيّات التدريب الأربع في كل جولة فقط.","why":"صحيح. يُستنسخ الـ pipeline كله ويُعاد تدريبه لكل طيّة، فلا تؤثر طيّة التحقق أبدًا في المعالجة المسبقة."},{"text":"من <code dir=\"ltr\">X_train</code> كاملة، محسوبة مرة واحدة قبل إنشاء الطيّات.","why":"هذا ما يحدث إذا وحّدت المقاييس قبل التحقق المتقاطع، وهو التسرّب الذي وُجدت الـ pipelines لمنعه."},{"text":"من طيّة التحقق، كي تُمركز بيانات التحقق بشكل صحيح.","why":"تدريب أي شيء على طيّة التحقق سيسرّبها إلى النموذج."},{"text":"من بيانات الاختبار، لأنها ما سيُتنبأ به.","why":"لا تُمرَّر بيانات الاختبار إلى <code dir=\"ltr\">cross_validate</code> إطلاقًا."}],"answer":0},{"q":"<p>يصل عميل من فرنسا (غير موجودة في التدريب) إلى pipeline يستخدم فيه <code dir=\"ltr\">OneHotEncoder</code> القيمة الافتراضية لـ <code dir=\"ltr\">handle_unknown</code>. ماذا يحدث؟</p>","options":[{"text":"يحوّل الـ encoder فرنسا إلى الدولة الأكثر شيوعًا.","why":"لا يوجد encoder في scikit-learn يستبدل فئة بهذه الطريقة بصمت."},{"text":"تصبح أعمدة الدولة لهذا العميل كلها أصفارًا ويستمر التنبؤ.","why":"هذا هو السلوك مع <code dir=\"ltr\">handle_unknown=&quot;ignore&quot;</code>، لا مع القيمة الافتراضية."},{"text":"يضيف الـ encoder عمودًا جديدًا لفرنسا في الحال.","why":"تخطيط الأعمدة يُثبَّت وقت التدريب؛ ولن تستطيع معاملات النموذج استخدام عمود جديد على أي حال."},{"text":"ترفع <code dir=\"ltr\">transform</code> الخطأ <code dir=\"ltr\">ValueError: Found unknown categories ['France']</code>.","why":"صحيح. القيمة الافتراضية هي <code dir=\"ltr\">&quot;error&quot;</code>. استخدم <code dir=\"ltr\">handle_unknown=&quot;ignore&quot;</code> في أي شيء سيرى بيانات حيّة."}],"answer":3},{"q":"<p>لماذا نستخدم <code dir=\"ltr\">SimpleImputer(strategy=&quot;median&quot;, add_indicator=True)</code> مع <code dir=\"ltr\">avg_satisfaction</code>؟</p>","options":[{"text":"لأن عمود المؤشّر يتيح للنموذج أن يتعلّم أن غياب الاستبيان بحدّ ذاته معلومة، بينما يملأ الوسيط الفجوة.","why":"صحيح. الغياب كثيرًا ما يكون إشارة (فالعملاء الذين لا يجيبون عن الاستبيانات أبدًا قد يتصرّفون بشكل مختلف)."},{"text":"لأن الوسيط يجعل توزيع العمود طبيعيًا.","why":"الملء بالوسيط لا يغيّر شكل القيم المرصودة؛ بل يملأ الفجوات فقط."},{"text":"لأن المؤشّر يحلّ محل العمود الأصلي، فيتجاهل النموذج الرضا.","why":"يُضاف المؤشّر بجانب العمود المملوء، لا بدلًا منه."}],"answer":0},{"q":"<p>إضافة <code dir=\"ltr\">country</code> و<code dir=\"ltr\">segment</code> إلى pipeline الانحدار اللوجستي تخفض الـ ROC AUC بالتحقق المتقاطع من 0.807 إلى 0.776. ما أفضل تفسير؟</p>","options":[{"text":"الـ OneHotEncoder مُعدّ بشكل خاطئ، لأن المعلومات الإضافية لا يمكن أن تضرّ.","why":"الـ features الإضافية قد تضرّ؛ فكلٌّ منها يضيف معاملات قد تلائم الضجيج، خاصة مع بضع مئات من الصفوف."},{"text":"على 298 صفًا، تضيف أعمدة الـ one-hot الأحد عشر ضجيجًا أكثر من الإشارة، فاتركها خارجًا الآن.","why":"صحيح. أعد اختبارها حين تتوفر بيانات أكثر أو عقوبة أقوى."},{"text":"الدولة تسبّب الانقطاع بطريقة لا يستطيع الانحدار اللوجستي التقاطها.","why":"لا شيء هنا يدعم ادّعاءً سببيًا، والانخفاض يقول إن الأعمدة لا تساعد هذا النموذج."}],"answer":1}],"exercise":{"kind":"python","files":["churn.csv"],"starter":"import numpy as np\nimport pandas as pd\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.impute import SimpleImputer\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split\nfrom sklearn.pipeline import Pipeline, make_pipeline\nfrom sklearn.preprocessing import OneHotEncoder, StandardScaler\n\nchurn = pd.read_csv(\"churn.csv\")\nX = churn.drop(columns=[\"customer_id\", \"churned\"])\ny = churn[\"churned\"]\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)\nnumeric = [\"tenure_days\", \"orders\", \"total_spent\", \"avg_order_value\",\n           \"days_since_last_order\", \"used_coupon\", \"support_tickets\", \"mobile_share\"]\n\n# A customer the training data never prepared you for\nnew_customer = pd.DataFrame([{\n    \"country\": \"France\", \"segment\": \"government\", \"tenure_days\": 200, \"orders\": 2,\n    \"total_spent\": 150.0, \"avg_order_value\": 75.0, \"days_since_last_order\": 100,\n    \"used_coupon\": 0, \"support_tickets\": 0, \"avg_satisfaction\": np.nan, \"mobile_share\": 0.5}])\n\n# 1. Preprocess: scale `numeric`; impute (median, with indicator) then scale avg_satisfaction;\n#    one-hot encode `segment` so unseen categories don't crash\npreprocess = ColumnTransformer([\n    (\"num\", StandardScaler(), numeric),\n    (\"sat\", StandardScaler(), [\"avg_satisfaction\"]),\n    (\"cat\", OneHotEncoder(), [\"segment\"]),\n])\n\n# 2. Pipeline with steps named \"prep\" and \"model\" (LogisticRegression), fitted on the training data\npipe = Pipeline([(\"prep\", preprocess), (\"model\", LogisticRegression())])\n\n# 3. Mean cross-validated ROC AUC on the training data\ncv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\ncv_auc = None\n\n# 4. Churn probability for new_customer\nnew_prob = None\n","solution":"import numpy as np\nimport pandas as pd\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.impute import SimpleImputer\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split\nfrom sklearn.pipeline import Pipeline, make_pipeline\nfrom sklearn.preprocessing import OneHotEncoder, StandardScaler\n\nchurn = pd.read_csv(\"churn.csv\")\nX = churn.drop(columns=[\"customer_id\", \"churned\"])\ny = churn[\"churned\"]\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)\nnumeric = [\"tenure_days\", \"orders\", \"total_spent\", \"avg_order_value\",\n           \"days_since_last_order\", \"used_coupon\", \"support_tickets\", \"mobile_share\"]\n\n# A customer the training data never prepared you for\nnew_customer = pd.DataFrame([{\n    \"country\": \"France\", \"segment\": \"government\", \"tenure_days\": 200, \"orders\": 2,\n    \"total_spent\": 150.0, \"avg_order_value\": 75.0, \"days_since_last_order\": 100,\n    \"used_coupon\": 0, \"support_tickets\": 0, \"avg_satisfaction\": np.nan, \"mobile_share\": 0.5}])\n\npreprocess = ColumnTransformer([\n    (\"num\", StandardScaler(), numeric),\n    (\"sat\", make_pipeline(SimpleImputer(strategy=\"median\", add_indicator=True), StandardScaler()),\n     [\"avg_satisfaction\"]),\n    (\"cat\", OneHotEncoder(handle_unknown=\"ignore\"), [\"segment\"]),\n])\npipe = Pipeline([(\"prep\", preprocess), (\"model\", LogisticRegression())])\npipe.fit(X_train, y_train)\n\ncv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\ncv_auc = cross_val_score(pipe, X_train, y_train, cv=cv, scoring=\"roc_auc\").mean()\n\nnew_prob = pipe.predict_proba(new_customer)[0, 1]\nprint(round(cv_auc, 3), round(new_prob, 3))\n","checks":[{"id":"steps","test":"list(pipe.named_steps) == ['prep', 'model'] and type(pipe.named_steps['model']).__name__ == 'LogisticRegression'"},{"id":"fitted","test":"len(pipe.named_steps['prep'].get_feature_names_out()) == 13"},{"id":"cv-auc","test":"cv_auc is not None and 0.75 < float(cv_auc) < 0.85"},{"id":"unseen","test":"new_prob is not None and 0.0 < float(new_prob) < 1.0 and abs(float(new_prob) - float(pipe.predict_proba(new_customer)[0, 1])) < 1e-9"}],"title":"pipeline يصمد أمام البيانات الحقيقية","prompt":"<p>اجعل الـ pipeline المبدئي يتعامل مع الفجوات والمفاجآت.</p>\n<ol>\n<li><code dir=\"ltr\">preprocess</code>: وحّد مقاييس الأعمدة <code dir=\"ltr\">numeric</code>؛ وبالنسبة إلى <code dir=\"ltr\">avg_satisfaction</code>، املأ القيم المفقودة بالوسيط <strong>وأضف مؤشّر غياب</strong>، ثم وحّد المقياس؛ وطبّق one-hot encoding على <code dir=\"ltr\">segment</code> بحيث تُتجاهل الفئات غير المرئية بدل رفع خطأ.</li>\n<li><code dir=\"ltr\">pipe</code>: خطوتان باسمَي <code dir=\"ltr\">&quot;prep&quot;</code> و<code dir=\"ltr\">&quot;model&quot;</code> (نموذج <code dir=\"ltr\">LogisticRegression</code>)، مدرَّب على بيانات التدريب. يجب أن ينتج 13 مُدخلًا للنموذج.</li>\n<li><code dir=\"ltr\">cv_auc</code>: متوسط الـ ROC AUC من <code dir=\"ltr\">cross_val_score</code> باستخدام <code dir=\"ltr\">cv</code> المعطى.</li>\n<li><code dir=\"ltr\">new_prob</code>: احتمال انقطاع <code dir=\"ltr\">new_customer</code>، الذي يأتي من دولة غير مرئية وشريحة غير مرئية وليست لديه درجة رضا.</li>\n</ol>\n","hints":["لا يستطيع <code dir=\"ltr\">StandardScaler</code> وحده التعامل مع NaN. سلسِل <code dir=\"ltr\">make_pipeline(SimpleImputer(strategy=&quot;median&quot;, add_indicator=True), StandardScaler())</code>.","<code dir=\"ltr\">OneHotEncoder(handle_unknown=&quot;ignore&quot;)</code>. عمود الدولة غير مذكور في المحوّل، فيُحذف.","لا تنسَ <code dir=\"ltr\">pipe.fit(X_train, y_train)</code>؛ ثم <code dir=\"ltr\">pipe.predict_proba(new_customer)[0, 1]</code>."],"explanation":"<p>ثمانية أعمدة رقمية موحّدة المقاييس، ودرجة الرضا المملوءة مع مؤشّر غيابها، وثلاثة أعمدة للشرائح، تساوي 13 مُدخلًا. والأعمدة التي لا يذكرها <code dir=\"ltr\">ColumnTransformer</code> (هنا <code dir=\"ltr\">country</code>) تُحذف افتراضيًا (<code dir=\"ltr\">remainder=&quot;drop&quot;</code>). يحصل العميل الجديد على أعمدة شرائح كلها أصفار ودرجة رضا تساوي الوسيط مع مؤشّر قيمته 1، فيظل النموذج ينتج احتمالًا. ودون <code dir=\"ltr\">handle_unknown=&quot;ignore&quot;</code> والـ imputer، كان هذا العميل الواحد سيُسقط خدمة في بيئة الإنتاج.</p>\n","checkLabels":{"steps":"في pipe خطوتان باسمَي prep وmodel","fitted":"ينتج المعالج المسبق المدرَّب 13 مُدخلًا","cv-auc":"cv_auc هو متوسط الـ ROC AUC بالتحقق المتقاطع","unseen":"new_prob احتمال صالح للعميل غير المرئي"}},"words":614,"readMinutes":4}