{"id":"l-ml-2-4","courseId":"ml-crash-course","lang":"en","kind":"lesson","summary":"Control overfitting by penalising large coefficients with Ridge and Lasso, scale features so the penalty is fair, and use Lasso’s zeros as automatic feature selection.","html":"<p>Last lesson, degree-2 polynomial features gave the diabetes model 65 columns, and the test score dropped from 0.49 to 0.42. You could throw the extra columns away. Regularisation offers something better: keep them all, but make the model pay for every unit of coefficient it uses. Columns that earn their keep get weight; the rest get shrunk.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">A penalty on the loss</h2>\n<p>Ordinary least squares minimises the squared error and nothing else. Regularised regression minimises the squared error <strong>plus</strong> a penalty that grows with the size of the coefficients:</p>\n<ul>\n<li><strong>Ridge</strong> adds <code dir=\"ltr\">alpha * sum(coef ** 2)</code>, the L2 penalty.</li>\n<li><strong>Lasso</strong> adds <code dir=\"ltr\">alpha * sum(abs(coef))</code>, the L1 penalty (scikit-learn also scales the error term slightly differently, which is why good <code dir=\"ltr\">alpha</code> values for the two models differ).</li>\n</ul>\n<p><code dir=\"ltr\">alpha</code> sets the exchange rate. At 0 you’re back to least squares. As it grows, a big coefficient must buy a big reduction in error to be worth it. Coefficients that only fit noise can’t, so they shrink. Squared values make Ridge shrink everything smoothly; absolute values make Lasso push weak coefficients all the way to zero.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Scale first, then penalise</h2>\n<p>The penalty adds up coefficients as if they were comparable. They only are if the features share a scale. Measure spend in cents instead of dollars and its coefficient becomes 100 times smaller, so the penalty barely notices it. Always put a <code dir=\"ltr\">StandardScaler</code> in front of a penalised model.</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=\"Run this example\"><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>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><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>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><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, Ridge, Lasso</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.model_selection </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.pipeline </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.preprocessing </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> PolynomialFeatures, StandardScaler</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">def</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> degree2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(model):</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline(PolynomialFeatures(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">degree</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\">include_bias</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\">), StandardScaler(), model)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"no penalty:\"</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\">(degree2(LinearRegression()).fit(X_train, y_train).score(X_test, y_test), </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:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> alpha </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</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\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">10</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    m </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> degree2(Ridge(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">alpha</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">alpha)).fit(X_train, y_train)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Ridge alpha=</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">alpha</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:&#x3C;5}</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\">m.score(X_train, y_train)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.3f}</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\">m.score(X_test, y_test)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.3f}</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=\"0\" hidden></div></div>\n<p>As <code dir=\"ltr\">alpha</code> rises the training score falls steadily (the model is allowed less freedom) while the test score climbs from 0.42 to 0.50 at <code dir=\"ltr\">alpha=100</code>, better than the plain ten-feature model. Push to 1,000 and both scores drop: now the model underfits. Regularisation turns the overfitting/underfitting trade-off into a dial.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Lasso picks features for you</h2>\n<p>Run the same experiment with Lasso and count the coefficients that end up at exactly zero.</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=\"Run this example\"><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>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><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>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><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\"> Lasso</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.model_selection </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.pipeline </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.preprocessing </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> PolynomialFeatures, StandardScaler</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> alpha </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.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\">, </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\">5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    m </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> make_pipeline(PolynomialFeatures(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">degree</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\">include_bias</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\">), StandardScaler(),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                      Lasso(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">alpha</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">alpha)).fit(X_train, y_train)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    zeros </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (m[</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].coef_ </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">==</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).sum()</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\">\"Lasso alpha=</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">alpha</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:&#x3C;4}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> test R2 </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">m.score(X_test, y_test)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:.3f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  zero coefficients: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">zeros</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> of 65\"</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\">names </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> m[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].get_feature_names_out()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">kept </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [n </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> n, c </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> zip</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(names, m[</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].coef_) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> c </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:#0A3069;--shiki-dark:#A5D6FF\">\"kept at alpha=5:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, kept)</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>At <code dir=\"ltr\">alpha=2</code> Lasso reaches a test R² of 0.53, the best regression score in this section, while ignoring 39 of the 65 columns. At <code dir=\"ltr\">alpha=5</code> it keeps 11 columns: BMI, blood pressure and <code dir=\"ltr\">s5</code> from the earlier models, plus a few interactions such as <code dir=\"ltr\">bmi bp</code>. <code dir=\"ltr\">m[-1]</code> reaches the last step of the pipeline, and <code dir=\"ltr\">m[0].get_feature_names_out()</code> names the generated columns.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Taming correlated coefficients</h2>\n<p>Remember <code dir=\"ltr\">s1</code> at -918 and <code dir=\"ltr\">s2</code> at +508 in the plain regression, two correlated blood measurements pulling against each other? With standardised features the same story shows up on a smaller scale, and the penalty settles it.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 660 230\" role=\"img\" aria-labelledby=\"l-ml-2-4-t1\"><defs><marker id=\"arrow-l-ml-2-4-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-ml-2-4-t1\">Coefficients for s1 and s2 on standardised features. Least squares: s1 minus 44, s2 plus 25. Ridge with alpha 10: s1 minus 13, s2 near zero. Lasso with alpha 1: s1 minus 8, s2 exactly zero.</title>\n  <line class=\"d-line\" x1=\"330\" y1=\"20\" x2=\"330\" y2=\"200\"/>\n  <text class=\"d-label-muted\" x=\"330\" y=\"220\" text-anchor=\"middle\">0</text>\n  <text class=\"d-label\" x=\"20\" y=\"48\">Least squares</text>\n  <rect class=\"d-box-warn\" x=\"198\" y=\"30\" width=\"132\" height=\"22\" rx=\"3\"/><text class=\"d-code\" x=\"190\" y=\"46\" text-anchor=\"end\">s1 −44</text>\n  <rect class=\"d-box-accent\" x=\"330\" y=\"56\" width=\"74\" height=\"22\" rx=\"3\"/><text class=\"d-code\" x=\"412\" y=\"72\">s2 +25</text>\n  <text class=\"d-label\" x=\"20\" y=\"113\">Ridge, alpha=10</text>\n  <rect class=\"d-box-warn\" x=\"291\" y=\"95\" width=\"39\" height=\"22\" rx=\"3\"/><text class=\"d-code\" x=\"283\" y=\"111\" text-anchor=\"end\">s1 −13</text>\n  <rect class=\"d-box-accent\" x=\"330\" y=\"121\" width=\"2\" height=\"22\" rx=\"1\"/><text class=\"d-code\" x=\"340\" y=\"137\">s2 +0.5</text>\n  <text class=\"d-label\" x=\"20\" y=\"178\">Lasso, alpha=1</text>\n  <rect class=\"d-box-warn\" x=\"305\" y=\"160\" width=\"25\" height=\"22\" rx=\"3\"/><text class=\"d-code\" x=\"297\" y=\"176\" text-anchor=\"end\">s1 −8</text>\n  <text class=\"d-code\" x=\"340\" y=\"198\">s2 0 (dropped)</text>\n</svg><figcaption>What happens to two correlated coefficients</figcaption></figure>\n<p>Ridge spreads weight sensibly between correlated features instead of letting them cancel each other out; Lasso tends to keep one and drop the other. Either way the coefficients become stable enough that a small change in the training data won’t flip them.</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\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Tuning alpha on the test set</span></p>\n<p><code dir=\"ltr\">alpha</code> is a hyperparameter like the polynomial degree. Sweeping it and reporting the best test score, as the demos above do to show the shape of the curve, gives an optimistic number. In a real project you pick <code dir=\"ltr\">alpha</code> on validation data. <code dir=\"ltr\">RidgeCV</code> and <code dir=\"ltr\">LassoCV</code> do it with built-in cross-validation, and section 4’s <code dir=\"ltr\">GridSearchCV</code> does it for any model.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">Ridge or Lasso?</h2>\n<p>Both cure the same disease, so the choice comes down to what you want from the final model. Ridge usually wins when many features each carry a little signal, which is common with measurements that overlap, like the six blood readings here. Lasso wins when a few features matter and the rest are noise, and it hands you a shorter shopping list of inputs, which matters when every feature has to be collected, cleaned and monitored in production. When features come in correlated groups, Lasso’s habit of keeping one member, somewhat arbitrarily, can be unstable from one training run to the next. <code dir=\"ltr\">ElasticNet</code> mixes the two penalties for exactly that case. On a dataset this size, the honest answer is to try both on validation data and keep whichever wins, then prefer the simpler one when they tie.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>A sensible default</span></p>\n<p>For a linear model on more than a handful of features, start with <code dir=\"ltr\">Ridge</code> inside a pipeline with <code dir=\"ltr\">StandardScaler</code>, and treat plain <code dir=\"ltr\">LinearRegression</code> as the exception. Reach for <code dir=\"ltr\">Lasso</code> when you want fewer features in the final model.</p>\n</aside>\n<p>That closes regression. The same penalty idea comes straight back in the next section: <code dir=\"ltr\">LogisticRegression</code> is regularised by default, with a parameter <code dir=\"ltr\">C</code> that works as the inverse of <code dir=\"ltr\">alpha</code>. Section 3 switches the target back to churn, yes or no.</p>\n","toc":[{"id":"sec-1","text":"A penalty on the loss","level":2},{"id":"sec-2","text":"Scale first, then penalise","level":2},{"id":"sec-3","text":"Lasso picks features for you","level":2},{"id":"sec-4","text":"Taming correlated coefficients","level":2},{"id":"sec-5","text":"Ridge or Lasso?","level":2}],"takeaways":["Regularisation adds a penalty on coefficient size to the loss, so the model trades a little training fit for simpler, more stable coefficients.","Ridge penalises squared coefficients and shrinks them all; Lasso penalises absolute values and sets some exactly to zero.","<code dir=\"ltr\">alpha</code> is the strength of the penalty: 0 is plain least squares, very large values flatten the model into underfitting.","Scale features before a penalised model, or the penalty punishes features for their units rather than their usefulness.","Choose <code dir=\"ltr\">alpha</code> on validation data or with cross-validation, the same way you choose any hyperparameter."],"further":[{"title":"Ridge regression and classification","url":"https://scikit-learn.org/stable/modules/linear_model.html#ridge-regression-and-classification"},{"title":"Lasso","url":"https://scikit-learn.org/stable/modules/linear_model.html#lasso"},{"title":"Ridge","url":"https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html"}],"quiz":[{"q":"<p>You raise Ridge’s <code dir=\"ltr\">alpha</code> from 1 to 100,000. What happens to the model?</p>","options":[{"text":"Coefficients shrink toward zero and predictions approach the mean of the target; the model underfits.","why":"Correct. A huge penalty makes any non-zero coefficient too expensive, so the model falls back to roughly the intercept."},{"text":"Coefficients grow so the model fits the training data more closely.","why":"A larger <code dir=\"ltr\">alpha</code> makes large coefficients more expensive, not cheaper."},{"text":"Most coefficients become exactly zero, leaving a few features.","why":"That’s Lasso’s behaviour. Ridge shrinks coefficients smoothly but almost never to exactly zero."}],"answer":0},{"q":"<p>Why put <code dir=\"ltr\">StandardScaler</code> before <code dir=\"ltr\">Lasso</code> in the pipeline?</p>","options":[{"text":"Lasso can’t handle negative numbers without scaling.","why":"Lasso works on any real numbers. The issue is fairness of the penalty."},{"text":"The penalty treats all coefficients alike, so features must share a scale for it to judge them on usefulness rather than units.","why":"Correct. A feature measured in large units needs a tiny coefficient and escapes the penalty; one in small units gets punished."},{"text":"Scaling makes Lasso converge to the same answer as ordinary least squares.","why":"Scaling doesn’t remove the penalty; with <code dir=\"ltr\">alpha &gt; 0</code> Lasso still differs from least squares."}],"answer":1},{"q":"<p>You need a churn model that uses as few of 60 candidate features as possible. Which is the natural first choice?</p>","options":[{"text":"Ridge, because it shrinks all 60 coefficients.","why":"Ridge keeps every feature with a small weight, so you’d still need all 60 inputs at prediction time."},{"text":"Plain linear regression, then drop the features with the smallest coefficients.","why":"Unpenalised coefficients of correlated features are unstable, so “smallest” is unreliable."},{"text":"An L1 (Lasso-style) penalty, because it drives unhelpful coefficients to exactly zero.","why":"Correct. The zeros are a built-in feature selection; you can then inspect which features survived."}],"answer":2}],"exercise":{"kind":"python","starter":"from sklearn.datasets import load_diabetes\nfrom sklearn.linear_model import Lasso, Ridge\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import PolynomialFeatures, StandardScaler\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)\n\n# 1. Pipeline: degree-2 PolynomialFeatures (include_bias=False) -> StandardScaler -> Lasso(alpha=1)\nlasso_model = make_pipeline(PolynomialFeatures(degree=2, include_bias=False), Lasso(alpha=1))\nlasso_model.fit(X_train, y_train)\n\n# 2. Test R2 and the number of coefficients that are exactly zero\nlasso_r2 = None\nn_zero = None\n\n# 3. Names of the generated columns Lasso kept (non-zero coefficient), as a list of strings\nkept = None\n","solution":"from sklearn.datasets import load_diabetes\nfrom sklearn.linear_model import Lasso, Ridge\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import PolynomialFeatures, StandardScaler\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)\n\nlasso_model = make_pipeline(PolynomialFeatures(degree=2, include_bias=False), StandardScaler(), Lasso(alpha=1))\nlasso_model.fit(X_train, y_train)\n\nlasso_r2 = lasso_model.score(X_test, y_test)\ncoefs = lasso_model[-1].coef_\nn_zero = int((coefs == 0).sum())\n\nnames = lasso_model[0].get_feature_names_out()\nkept = [name for name, c in zip(names, coefs) if c != 0]\nprint(round(lasso_r2, 3), n_zero, len(kept))\n","checks":[{"id":"scaled","test":"[type(s).__name__ for s in lasso_model] == ['PolynomialFeatures', 'StandardScaler', 'Lasso'] and lasso_model[-1].alpha == 1"},{"id":"r2","test":"lasso_r2 is not None and abs(float(lasso_r2) - 0.515) < 0.005"},{"id":"zeros","test":"n_zero is not None and int(n_zero) == int((lasso_model[-1].coef_ == 0).sum()) == 30"},{"id":"kept","test":"kept is not None and len(kept) == 35 and 'bmi' in list(kept) and all(isinstance(k, str) for k in kept)"}],"title":"Let Lasso choose the features","prompt":"<p>The starter pipeline forgets something important. Fix it, then inspect what Lasso did.</p>\n<ol>\n<li><code dir=\"ltr\">lasso_model</code>: degree-2 <code dir=\"ltr\">PolynomialFeatures(include_bias=False)</code>, then <code dir=\"ltr\">StandardScaler</code>, then <code dir=\"ltr\">Lasso(alpha=1)</code>, fitted on the training data.</li>\n<li><code dir=\"ltr\">lasso_r2</code>: its test R². <code dir=\"ltr\">n_zero</code>: how many of its 65 coefficients are exactly zero.</li>\n<li><code dir=\"ltr\">kept</code>: a list with the names of the generated columns whose coefficient is not zero.</li>\n</ol>\n","hints":["Without a scaler the penalty judges features by their units. Add <code dir=\"ltr\">StandardScaler()</code> between the two existing steps.","<code dir=\"ltr\">lasso_model[-1].coef_</code> gives the Lasso coefficients; <code dir=\"ltr\">(coef == 0).sum()</code> counts zeros.","<code dir=\"ltr\">lasso_model[0].get_feature_names_out()</code> lists the 65 generated column names in the same order as the coefficients."],"explanation":"<p>With scaling in place, Lasso at <code dir=\"ltr\">alpha=1</code> reaches a test R² of about 0.515 and sets 30 of 65 coefficients to zero, keeping 35 columns. Without the scaler, the diabetes features’ tiny scale means the penalty overwhelms most coefficients for the wrong reason: their units, not their usefulness. Pairing names with coefficients is how you report which inputs the final model actually needs.</p>\n","checkLabels":{"scaled":"The pipeline is PolynomialFeatures → StandardScaler → Lasso(alpha=1)","r2":"lasso_r2 is the test R²","zeros":"n_zero counts the zero coefficients","kept":"kept lists the names of the non-zero columns"}},"words":749,"readMinutes":4}