{"id":"l-ml-2-2","courseId":"ml-crash-course","lang":"ar","kind":"lesson","summary":"اكتب الـ gradient descent بيدك في NumPy لتدريب خط مستقيم، وشاهد كيف يجعله معدّل التعلّم يزحف أو يتقارب أو ينفجر، واشرح لماذا يجب توحيد مقاييس الـ features.","html":"<p>أعاد <code dir=\"ltr\">LinearRegression().fit</code> أفضل خط فورًا، دون أي بحث ظاهر. فللخط المستقيم معادلة دقيقة، وهذا منصف. لكن معظم النماذج (الانحدار اللوجستي، والشبكات العصبية، والـ gradient boosting الذي ستتعرّف عليه في القسم 3) ليس لها معادلة. إنها تجد معاملاتها بالبحث، وتكاد كلها تستخدم شكلًا ما من البحث نفسه: الـ gradient descent (النزول بالتدرّج).</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">التعلّم نزولًا على المنحدر</h2>\n<p>خذ النموذج المعتمد على مؤشر كتلة الجسم وحده من الدرس السابق: <code dir=\"ltr\">prediction = w * x + b</code>. لأي زوج من <code dir=\"ltr\">w</code> و<code dir=\"ltr\">b</code> يمكنك حساب متوسط الخطأ المربّع على بيانات التدريب. تخيّل كل قيمة ممكنة لـ <code dir=\"ltr\">w</code> مرتّبةً من اليسار إلى اليمين، والخسارة المقابلة لكل منها ارتفاعًا. في الخط المستقيم مع الخطأ المربّع، تكون هذه الصورة وعاءً أملس، وأفضل <code dir=\"ltr\">w</code> تقع في قاعه.</p>\n<p>لا يمكنك رؤية الوعاء، لكن في أي نقطة يمكنك حساب ميله: <strong>التدرّج (gradient)</strong>. إذا كان الميل موجبًا، فزيادة <code dir=\"ltr\">w</code> تجعل الأمور أسوأ، فأنقصها. وإذا كان سالبًا، فزِدها. خذ خطوة صغيرة عكس الميل، وأعد الحساب، وكرّر. هذا هو <strong>الـ gradient descent</strong>.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 270\" role=\"img\" aria-labelledby=\"l-ml-2-2-t1\"><defs><marker id=\"arrow-l-ml-2-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-2-2-t1\">منحنى خسارة على شكل حرف U فوق المعامل w. نقاط تحدّد الخطوات المتتالية من أعلى اليسار نزولًا نحو القاع، وكل خطوة أقصر من سابقتها مع تسطّح الميل.</title>\n  <line class=\"d-line\" x1=\"40\" y1=\"240\" x2=\"640\" y2=\"240\"/>\n  <text class=\"d-label-muted\" x=\"620\" y=\"262\">w</text>\n  <text class=\"d-label-muted\" x=\"46\" y=\"30\">الخسارة</text>\n  <path class=\"d-line\" d=\"M80 30 Q340 450 600 30\" fill=\"none\"/>\n  <circle class=\"d-dot\" cx=\"110\" cy=\"76\" r=\"7\"/>\n  <circle class=\"d-dot\" cx=\"190\" cy=\"170\" r=\"7\"/>\n  <circle class=\"d-dot\" cx=\"250\" cy=\"215\" r=\"7\"/>\n  <circle class=\"d-dot\" cx=\"295\" cy=\"234\" r=\"7\"/>\n  <circle class=\"d-dot\" cx=\"325\" cy=\"239\" r=\"7\"/>\n  <path class=\"d-arrow\" d=\"M118 84 L180 160\" marker-end=\"url(#arrow-l-ml-2-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M200 177 L240 208\" marker-end=\"url(#arrow-l-ml-2-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M260 220 L285 230\" marker-end=\"url(#arrow-l-ml-2-2-1)\"/>\n  <text class=\"d-label\" x=\"140\" y=\"60\">ميل كبير، خطوة كبيرة</text>\n  <text class=\"d-label\" x=\"360\" y=\"200\">مسطّح عند القاع</text>\n</svg><figcaption>يأخذ الـ gradient descent خطوات نزولًا على منحنى الخسارة</figcaption></figure>\n<p>تصغر الخطوات من تلقاء نفسها كلما اقتربت من القاع، لأن الميل يصبح أكثر تسطّحًا. وحجم كل خطوة هو التدرّج مضروبًا في <strong>معدّل التعلّم (learning rate)</strong>، وهو hyperparameter تختاره أنت.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ gradient descent في ثمانية أسطر</h2>\n<p>مع الخطأ المربّع، للتدرّجات معادلات قصيرة: الميل بالنسبة إلى <code dir=\"ltr\">w</code> هو <code dir=\"ltr\">2 * mean(error * x)</code> وبالنسبة إلى <code dir=\"ltr\">b</code> هو <code dir=\"ltr\">2 * mean(error)</code>، حيث <code dir=\"ltr\">error = prediction - actual</code>. إليك ذلك في NumPy، بعد توحيد مقياس مؤشر كتلة الجسم إلى متوسط 0 وانحراف معياري 1 أولًا (سترى السبب بعد قليل).</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\"> numpy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> np</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.datasets </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_diabetes</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\"> LinearRegression</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X, y </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_diabetes(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">return_X_y</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\">as_frame</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>\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>\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\"> ((X_train[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"bmi\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> X_train[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"bmi\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].mean()) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> X_train[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"bmi\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].std(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">ddof</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)).to_numpy()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">target </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> y_train.to_numpy()</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">w, b, learning_rate </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0.0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.1</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> step </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> range</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">101</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">):</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    error </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (w </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> x </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> target</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> step </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 25</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ==</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</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\">\"step </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">step</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:3}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  loss </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">np.mean(error </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:#CF222E;--shiki-dark:#FF7B72\">:9.1f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  w </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">w</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:6.2f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  b </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">b</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:7.2f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    w </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> learning_rate </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> np.mean(error </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> x)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    b </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> learning_rate </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 2</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> *</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> np.mean(error)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">exact </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> LinearRegression().fit(x.reshape(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), target)</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\">\"LinearRegression:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, exact.coef_.round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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\">(exact.intercept_, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>تنخفض الخسارة من نحو 29,900 إلى 3,931 خلال 25 خطوة ثم تتوقف عن الحركة. وقيمتا <code dir=\"ltr\">w</code> و<code dir=\"ltr\">b</code> اللتان تستقر عندهما تطابقان <code dir=\"ltr\">LinearRegression</code> حتى منزلتين عشريتين. لقد درّبت نموذجًا بيدك.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">معدّل التعلّم يحسم كل شيء</h2>\n<p>غيّر <code dir=\"ltr\">learning_rate</code> وأعد التشغيل. مع <code dir=\"ltr\">0.01</code>، لا تبلغ مئة خطوة إلا <code dir=\"ltr\">w ≈ 40</code> و<code dir=\"ltr\">b ≈ 134</code>: البحث يزحف. ومع <code dir=\"ltr\">0.5</code>، يصل في خطوة واحدة. ومع <code dir=\"ltr\">1.05</code>، تتجاوز كل خطوة القاع بأكثر من سابقتها، وبعد مئة خطوة تصبح الخسارة بالتريليونات. هذا هو <strong>التباعد (divergence)</strong>، وحين ترى خسارة تكبر بدل أن تصغر، فمعدّل التعلّم الكبير جدًا هو المشتبه الأول.</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\">nan</code>، نادرًا ما تعني أن النموذج غير مناسب للبيانات. بل تعني غالبًا أن معدّل التعلّم كبير جدًا بالنسبة إلى مقياس الـ features. اخفض المعدّل عشر مرات، أو وحّد مقاييس الـ features، قبل أن تغيّر أي شيء آخر.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">لماذا نوحّد مقاييس الـ features</h2>\n<p>الجداول الحقيقية تخلط المقاييس. في بيانات الانقطاع، يصل <code dir=\"ltr\">total_spent</code> إلى الآلاف بينما <code dir=\"ltr\">used_coupon</code> إما 0 أو 1. ومع اثنين من الـ features كهذين، لم يعد سطح الخسارة وعاءً مستديرًا بل واديًا طويلًا ضيقًا. ومعدّل التعلّم الصغير بما يكفي للبقاء مستقرًا في الاتجاه الشديد الانحدار يكون بطيئًا على نحو مؤلم في الاتجاه المسطّح، فيتعرّج المسار عبر الوادي.</p>\n<p>والمقياس يؤثر أيضًا في نماذج لا تستخدم التدرّجات إطلاقًا. فأقرب k جيران يقيس المسافة بين العملاء، وفرق 300 دولار في الإنفاق يطغى على فرق أربع تذاكر دعم. وتوحيد كل عمود إلى متوسط 0 وانحراف معياري 1 يضعها جميعًا على قدم المساواة.</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.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.neighbors </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> KNeighborsClassifier</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.preprocessing </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StandardScaler</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">churn </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churn.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">features </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"tenure_days\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"total_spent\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"avg_order_value\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">            \"days_since_last_order\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"used_coupon\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"support_tickets\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"mobile_share\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X_train, X_test, y_train, y_test </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split(</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    churn[features], churn[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">test_size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.25</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    stratify</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">churn[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"churned\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">scaler </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StandardScaler().fit(X_train)          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\"># learn means and stds from TRAIN only</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X_train_s, X_test_s </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> scaler.transform(X_train), scaler.transform(X_test)</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\">\"learned means:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, scaler.mean_[:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">raw </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> KNeighborsClassifier().fit(X_train, y_train).score(X_test, y_test)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">scaled </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> KNeighborsClassifier().fit(X_train_s, y_train).score(X_test_s, y_test)</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\">\"k-NN raw </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">raw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.2f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  scaled </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">scaled</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.2f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>توحيد المقاييس وحده يرفع k-NN من 0.72 إلى 0.75، متجاوزًا قاعدة الـ 80 يومًا من القسم 1. النموذج نفسه والبيانات نفسها؛ والتغيير الوحيد أن كل feature صار له صوت عادل.</p>\n<p>الـ scaler أيضًا estimator. له <code dir=\"ltr\">fit</code> (تعلّم متوسط كل عمود وانحرافه المعياري) و<code dir=\"ltr\">transform</code> (تطبيقهما)، وتسري عليه القاعدة نفسها التي تسري على النماذج: درّبه على بيانات التدريب وحدها. والتنقّل بين <code dir=\"ltr\">X_train_s</code> و<code dir=\"ltr\">X_test_s</code> يدويًا يصبح عرضة للأخطاء، لذا يجمع القسم 4 الـ scaler والنموذج في كائن pipeline واحد.</p>\n<aside class=\"callout callout-note\" 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\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">ملاحظة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>ليست كل النماذج تهتم</span></p>\n<p>أشجار القرار والنماذج المجمّعة المبنية منها تقسم البيانات على feature واحد في كل مرة، مستخدمة ترتيب القيم فقط، لذا لا يغيّر توحيد المقاييس تنبؤاتها. والمربعات الصغرى العادية تعدّل معاملاتها لتعوّض. توحيد المقاييس مهم للنماذج المدرَّبة بالتدرّج، والنماذج المعتمدة على المسافة، وأي نموذج فيه عقوبة على حجم المعاملات، وهذا بالضبط ما يضيفه آخر درس في هذا القسم.</p>\n</aside>\n<p>سيجد الـ gradient descent أفضل خط، لكن “الأفضل على بيانات التدريب” قد يكون فخًّا حين يكون النموذج مرنًا. في الدرس القادم يصبح الخط منحنيًا، وسترى كيف ينحني أكثر مما يجب.</p>\n","toc":[{"id":"sec-1","text":"التعلّم نزولًا على المنحدر","level":2},{"id":"sec-2","text":"الـ gradient descent في ثمانية أسطر","level":2},{"id":"sec-3","text":"معدّل التعلّم يحسم كل شيء","level":2},{"id":"sec-4","text":"لماذا نوحّد مقاييس الـ features","level":2}],"takeaways":["تدريب النموذج يعني البحث عن قيم المعاملات التي تجعل دالة الخسارة أصغر ما يمكن.","يكرّر الـ gradient descent خطوة واحدة: احسب ميل دالة الخسارة لكل معامل، ثم حرّك كل معامل قليلًا في اتجاه النزول.","يحدّد معدّل التعلّم (learning rate) حجم الخطوة: الصغير جدًا يزحف، والكبير جدًا يتجاوز الهدف ويتباعد.","الـ features ذات المقاييس المتباعدة جدًا تمطّ سطح دالة الخسارة، فتبطّئ الـ gradient descent وتشوّه النماذج المعتمدة على المسافة مثل k-NN.","يتعلّم <code dir=\"ltr\">StandardScaler</code> متوسط كل عمود وانحرافه المعياري من بيانات التدريب، ثم يعيد المعايرة إلى متوسط 0 وانحراف معياري 1."],"further":[{"title":"StandardScaler","url":"https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html"},{"title":"Stochastic Gradient Descent","url":"https://scikit-learn.org/stable/modules/sgd.html"},{"title":"Importance of feature scaling","url":"https://scikit-learn.org/stable/auto_examples/preprocessing/plot_scaling_importance.html"}],"quiz":[{"q":"<p>أثناء الـ gradient descent، كان تدرّج دالة الخسارة بالنسبة إلى <code dir=\"ltr\">w</code> موجبًا. ماذا يفعل التحديث بـ <code dir=\"ltr\">w</code>؟</p>","options":[{"text":"يزيد <code dir=\"ltr\">w</code>، لأن التدرّج الموجب يعني أن <code dir=\"ltr\">w</code> يجب أن تكبر.","why":"التدرّج الموجب يعني أن الخسارة ترتفع حين تكبر <code dir=\"ltr\">w</code>. والتحرّك معه صعود إلى أعلى."},{"text":"ينقص <code dir=\"ltr\">w</code>، لأن التحديث يطرح حاصل ضرب معدّل التعلّم في التدرّج.","why":"صحيح. التحرّك عكس التدرّج هو ما يجعل الخسارة تنخفض."},{"text":"يُبقي <code dir=\"ltr\">w</code> كما هي حتى يصبح التدرّج صفرًا.","why":"التدرّج غير الصفري هو بالضبط إشارة التحرّك. ولا يكون صفرًا إلا عند القاع."}],"answer":1},{"q":"<p>ضاعفت معدّل التعلّم فصارت الخسارة المطبوعة 3,900 ← 12,000 ← 95,000 ← 2,000,000. ماذا حدث؟</p>","options":[{"text":"وجد النموذج قاعًا أفضل في مكان أبعد.","why":"القاع الأفضل سيُظهر خسارة أقل، لا خسارة تنفجر."},{"text":"في البيانات قيم شاذة كشفها المعدّل الأكبر.","why":"القيم الشاذة لا تجعل الخسارة تكبر بلا حدود خطوة بعد خطوة. أما تجاوز القاع فيفعل."},{"text":"كل خطوة تتجاوز القاع بأكثر من سابقتها، فيتباعد البحث.","why":"صحيح. اخفض معدّل التعلّم (أو وحّد مقاييس الـ features) حتى تنخفض الخسارة باطّراد."}],"answer":2},{"q":"<p>أين يجب أن تدرّب الـ <code dir=\"ltr\">StandardScaler</code>؟</p>","options":[{"text":"على بيانات التدريب وحدها، ثم استخدمه لتحويل بيانات التدريب والاختبار معًا.","why":"صحيح. يجب أن تُعامَل بيانات الاختبار كبيانات المستقبل، التي لا تستطيع رؤيتها حين تدرّب الـ scaler."},{"text":"على البيانات كاملة قبل التقسيم، ليستخدم الجزآن المتوسط والانحراف المعياري نفسيهما.","why":"هذا يسمح لإحصاءات بيانات الاختبار بالتأثير في المعالجة المسبقة، وهو تسرّب صغير يجعل النتائج متفائلة. والـ pipelines في القسم 4 تمنعه."},{"text":"على بيانات التدريب والاختبار كلٍّ على حدة، ليكون متوسط كل جزء 0.","why":"عندها تتحوّل القيمة الخام نفسها إلى قيم معايَرة مختلفة في كل جزء، ويرى النموذج مدخلات غير متّسقة."}],"answer":0},{"q":"<p>أيّ نموذج تتغيّر تنبؤاته حين تعيد التعبير عن <code dir=\"ltr\">total_spent</code> بالسنتات بدل الدولارات؟</p>","options":[{"text":"شجرة القرار، لأن عتباتها بالدولار.","why":"الشجرة ستتعلّم ببساطة عتبات بالسنتات. وترتيب القيم، وهو كل ما يستخدمه التقسيم، لا يتغيّر."},{"text":"أقرب k جيران، لأن المسافات ستصبح خاضعة لهيمنة <code dir=\"ltr\">total_spent</code>.","why":"صحيح. النماذج المعتمدة على المسافة تعامل مدى أكبر بمئة مرة على أنه أهم بمئة مرة."},{"text":"الانحدار الخطي العادي، لأن معامله يتغيّر.","why":"يصغر المعامل مئة مرة ليعوّض، وتبقى التنبؤات متطابقة. المقياس مهم للنماذج ذات الـ regularization، لا للمربعات الصغرى العادية."}],"answer":1}],"exercise":{"kind":"python","starter":"import numpy as np\nfrom sklearn.datasets import load_diabetes\nfrom sklearn.model_selection import train_test_split\n\nX, y = load_diabetes(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)\nbmi = X_train[\"bmi\"]\nx = ((bmi - bmi.mean()) / bmi.std(ddof=0)).to_numpy()   # standardised feature\ntarget = y_train.to_numpy()\n\nw, b = 0.0, 0.0\nlearning_rate = 0.05\nlosses = []\n\nfor step in range(200):\n    error = (w * x + b) - target\n    losses.append(np.mean(error ** 2))\n    # Update w and b here: step against the gradient of the mean squared error\n    pass\n","solution":"import numpy as np\nfrom sklearn.datasets import load_diabetes\nfrom sklearn.model_selection import train_test_split\n\nX, y = load_diabetes(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)\nbmi = X_train[\"bmi\"]\nx = ((bmi - bmi.mean()) / bmi.std(ddof=0)).to_numpy()   # standardised feature\ntarget = y_train.to_numpy()\n\nw, b = 0.0, 0.0\nlearning_rate = 0.05\nlosses = []\n\nfor step in range(200):\n    error = (w * x + b) - target\n    losses.append(np.mean(error ** 2))\n    w -= learning_rate * 2 * np.mean(error * x)\n    b -= learning_rate * 2 * np.mean(error)\n\nprint(round(w, 2), round(b, 2), round(losses[-1], 1))\n","checks":[{"id":"losses","test":"len(losses) == 200 and all(float(losses[i + 1]) <= float(losses[i]) + 1e-9 for i in range(199))"},{"id":"loss-drops","test":"float(losses[-1]) < 0.2 * float(losses[0])"},{"id":"w","test":"abs(float(w) - 45.97) < 0.5"},{"id":"b","test":"abs(float(b) - 154.34) < 0.5"}],"title":"درّب خطًّا بالـ gradient descent","prompt":"<p>أكمل حلقة الـ gradient descent كي تطابق <code dir=\"ltr\">prediction = w * x + b</code> مع هدف بيانات السكري، حيث <code dir=\"ltr\">x</code> هو مؤشر كتلة الجسم بعد توحيد مقياسه.</p>\n<p>داخل الحلقة، حدّث <code dir=\"ltr\">w</code> و<code dir=\"ltr\">b</code> بطرح حاصل ضرب <code dir=\"ltr\">learning_rate</code> في تدرّج كلٍّ منهما. مع متوسط الخطأ المربّع، التدرّج هو <code dir=\"ltr\">2 * mean(error * x)</code> لـ <code dir=\"ltr\">w</code> و<code dir=\"ltr\">2 * mean(error)</code> لـ <code dir=\"ltr\">b</code>. أبقِ القائمة <code dir=\"ltr\">losses</code>: فالفحوص تعتمد عليها.</p>\n","hints":["احسب التدرّجين كليهما من قيمة <code dir=\"ltr\">error</code> نفسها قبل تغيير أيٍّ من المعاملين.","<code dir=\"ltr\">w -= learning_rate * 2 * np.mean(error * x)</code>","افعل الشيء نفسه مع <code dir=\"ltr\">b</code> باستخدام <code dir=\"ltr\">np.mean(error)</code>. يجب أن تستقر القيم النهائية قرب <code dir=\"ltr\">w ≈ 46</code> و<code dir=\"ltr\">b ≈ 154</code>."],"explanation":"<p>في كل دورة تحسب خطأ الخط الحالي، وتحوّله إلى ميل لكل معامل، ثم تخطو عكس ذلك الميل. مع معدّل تعلّم 0.05 تنخفض الخسارة في كل خطوة وتستقر المعاملات حيث يضعها <code dir=\"ltr\">LinearRegression</code> (<code dir=\"ltr\">w ≈ 45.97</code> و<code dir=\"ltr\">b ≈ 154.34</code>). وإذا عكست الإشارة (<code dir=\"ltr\">+=</code>)، تكبر الخسارة مع كل خطوة: أنت تصعد المنحدر.</p>\n","checkLabels":{"losses":"لا تزيد الخسارة أبدًا من خطوة إلى التي تليها","loss-drops":"الخسارة النهائية أقل من 20% من الخسارة الابتدائية","w":"تتقارب w إلى نحو 45.97","b":"تتقارب b إلى نحو 154.34"}},"words":667,"readMinutes":4}