Hello! Welcome back to our module on regularization techniques.
In our last lesson, we implemented L2 (Ridge) regularization. We saw that by adding a penalty based on the squared values of the weights, we could discourage the model from becoming too complex. This process, also known as "weight decay," shrinks large weights towards zero, helping to prevent overfitting.
Today, we'll explore a powerful alternative: L1 regularization, also known as Lasso (Least Absolute Shrinkage and Selection Operator). Our goal is to modify a cost function and its gradient to include an L1 regularization term. While the change might seem small—we'll use absolute values instead of squares—the impact on our model's behavior is profound and has significant practical implications.
Let's get started.
1. The L1 (Lasso) Regularized Cost Function
Just like with L2, we start with our original cost function (e.g., MSE) and add a penalty term. However, for L1 regularization, the penalty is proportional to the sum of the absolute values of the weights.
The L1-regularized cost function is:
Let's break down the new penalty term:
- is the absolute value of the weight for the j-th feature.
- is the sum of the absolute values of all weights. This is also known as the L1 norm of the weight vector, written as .
- is again our regularization hyperparameter, controlling the strength of the penalty.
- As before, we do not regularize the bias term .
To see this conceptual shift from squared values to absolute values, the following video provides a great introduction.
Regularization Part 2: Lasso (L1) Regression
The video 'Regularization Part 2: Lasso (L1) Regression' by StatQuest with Josh Starmer clearly explains the foundational difference between Ridge and Lasso.
Watch from 02:23 to 02:59. This brief clip shows how simply changing the penalty term from squared slopes to the absolute value of the slopes takes us from Ridge to Lasso regression.
This change from to seems trivial, but it has a major consequence for the gradient calculation.
2. The Gradient of the L1 Penalty: A Small Complication
To update our gradient descent algorithm, we need the derivative of the new cost function. This means we need the derivative of the L1 penalty term with respect to each weight .
The derivative of the absolute value function, , is:
- +1 if
- -1 if
- Undefined if
This "undefined at zero" point is a mathematical wrinkle. In practice, we use something called a subgradient, which is a generalization of the gradient for non-differentiable functions. For our purposes, the intuition is simple:
- If a weight is positive, the L1 penalty pulls it towards zero with a constant force.
- If a weight is negative, the L1 penalty pushes it towards zero with a constant force.
- If a weight is exactly zero, the L1 penalty's gradient is zero, so it exerts no force.
So, the derivative of the L1 penalty term is , where is +1 if and -1 if .
The full gradient update for a weight becomes:
The gradient for the bias term remains unchanged.
3. Implementation from Scratch in Python
Now let's translate this into code. We'll modify our gradient computation function to account for the L1 penalty. Because the gradient depends on the sign of the weight, a conditional if/else statement is a natural way to implement this.
Here is how you would modify a gradient function to include the L1 term. Note that this is a vectorized implementation and uses a loop to apply the sign-dependent penalty to each weight.
import numpy as np
def compute_gradient_with_l1(X, y, w, b, lambda_):
"""
Computes the gradient for linear regression with L1 regularization.
Args:
X (ndarray (m,n)): Data, m examples with n features
y (ndarray (m,)) : target values
w (ndarray (n,)) : model parameters
b (scalar) : model parameter
lambda_ (float) : L1 regularization parameter
Returns:
dj_dw (ndarray (n,)): The gradient of the cost w.r.t. the parameters w.
dj_db (scalar) : The gradient of the cost w.r.t. the parameter b.
"""
m, n = X.shape
# Original gradient calculation
predictions = X @ w + b
error = predictions - y
dj_dw_original = (X.T @ error) / m
dj_db = np.sum(error) / m
# Initialize dj_dw with the original gradient
dj_dw = dj_dw_original.copy()
# Add the gradient of the L1 penalty to dj_dw
# Note: An even more efficient way is using np.sign(w)
for j in range(n):
if w[j] > 0:
dj_dw[j] += lambda_ / m
elif w[j] < 0:
dj_dw[j] -= lambda_ / m
# A more concise vectorized version would be:
# l1_grad_term = (lambda_ / m) * np.sign(w)
# dj_dw = dj_dw_original + l1_grad_term
return dj_dw, dj_db
The following article provides an excellent from-scratch implementation of Lasso Regression within a Python class, which is a great way to structure your code for future use.
Implementation of Lasso Regression From Scratch using ...
The article 'Implementation of Lasso Regression From Scratch' on GeeksforGeeks provides a complete class-based implementation. Focus on how the gradient is updated.
In the provided Python code, find the update_weights method within the LassoRegression class. Observe the for loop and the if/else block that calculates dW[j]. This is the direct implementation of the subgradient we just discussed, where the penalty self.l1_penalty is either added or subtracted based on the sign of the weight self.W[j].
For a verbal walkthrough of a similar implementation, the following video is helpful.
7.4.4. Building Lasso Regression from Scratch in Python
This video, 'Building Lasso Regression from Scratch in Python', walks through the process step-by-step. Let's focus on the gradient formulas.
Watch from 06:20 to 08:35. The presenter shows the gradient formulas for the weights (dw) and explains the conditional logic: if the weight is positive, you add the penalty lambda, and if it's negative, you subtract it. This directly corresponds to the code we just wrote.
4. The Power of Sparsity: L1 vs. L2
Here we arrive at the most important difference between L1 and L2 regularization.
- L2 (Ridge) pushes weights towards zero, but rarely makes them exactly zero.
- L1 (Lasso) can push weights all the way to zero, effectively removing the feature from the model.
This phenomenon is called sparsity. A sparse model is one where many of the parameters are zero.
Why does this happen?
Think of the gradient update. With L2, the penalty term is . As gets smaller, the penalty's push also gets smaller. It's like a spring that pulls less the closer it gets to its resting point.
With L1, the penalty term is a constant . This force doesn't diminish as the weight gets smaller. It's a constant push that can drive the weight all the way across the zero line and keep it there.
This geometric intuition is brilliantly visualized in the following image.

Because L1 can zero out weights, it performs automatic feature selection. If you have many features and suspect some are useless, Lasso can identify and discard them by setting their corresponding weights to zero.
The StatQuest video you saw earlier explains this property very clearly.
Regularization Part 2: Lasso (L1) Regression
Let's return to the StatQuest video to see the practical difference between L1 and L2.
Watch from 04:50 to 07:50. This section is crucial. It shows how Lasso can shrink coefficients all the way to zero, effectively removing 'useless variables' from an equation. This makes the model simpler and more interpretable.
Test your understanding!
Imagine you are working on a project to predict house prices. Your dataset has 200 features, including square_footage, num_bedrooms, age_of_house, and also some more obscure ones like color_of_front_door and day_of_week_listed. You suspect many of these 200 features are irrelevant.
Would you be more inclined to start with L1 (Lasso) or L2 (Ridge) regularization? Why?
Show answer
You would likely be more inclined to start with L1 (Lasso) regularization. The key reason is its ability to perform automatic feature selection. Since you suspect many features are useless, Lasso's tendency to shrink irrelevant feature weights to exactly zero would help you create a simpler, more interpretable model that only uses the most important predictors. L2 would shrink the useless feature weights, but they would remain small non-zero numbers, cluttering the model.
Conclusion
In this lesson, you've learned how to implement L1 (Lasso) regularization. While it starts with a simple change to the penalty term, it results in a profoundly different and powerful behavior.
Key Takeaways:
- L1 (Lasso) Regularization adds a penalty to the cost function equal to the sum of the absolute values of the weights, scaled by .
- The gradient of the L1 penalty is constant (except at zero), which gives it a consistent push towards zero during gradient descent.
- This property leads to sparsity, where many model weights become exactly zero.
- Because of sparsity, L1 regularization effectively performs automatic feature selection, making it excellent for models with many potentially irrelevant features.
- The implementation requires handling the non-differentiable nature of the absolute value function, often with a conditional statement or
np.sign().
Preview of the Next Lesson:
You have now implemented the core logic for both L1 and L2 regularization. In the next lesson, we will put it all together. You will apply L1 and L2 regularization to the from-scratch Linear and Logistic Regression models you've developed, creating a complete, regularized training pipeline.