MACHINE LEARNING

The Ultimate Guide to Gradient Descent in ML: Finding Your Way Down the Mountain

July 13, 2026 · 29 min read
gradient_descent

The Problem 

Imagine you are trekking down the foggy hills of Kudremukh in Karnataka, and suddenly, a thick cloud rolls in, reducing your visibility to zero. You cannot see the base camp at the bottom of the valley, but you must reach it before sunset. How do you find your way down when you can only see the ground right beneath your feet?


What is gradient descent in ml? (And Why Should You Care?)

The Foot-Step Analogy

Since you cannot see the valley, you have to rely on your feet. You stand still and feel the slope of the ground.

The Formal Definition

In Machine Learning, gradient descent in ml is an optimization algorithm used to minimize the errors made by a model. It acts exactly like that blind trekker, adjusting the model’s internal settings (called weights) step-by-step in the direction that reduces the prediction error (the “slope”) until the error is as close to zero as possible.

 

Why Do We Need It?

Why can’t we just use a direct mathematical formula to calculate the perfect settings instantly?

For tiny datasets, we can. But imagine a massive dataset like Jio’s 400 million users. Calculating the perfect settings in one single mathematical leap requires inverting giant matrices. This would instantly freeze and crash even the world’s fastest supercomputers.

Gradient descent in ml is the savior here. It doesn’t try to be a genius in one step. Instead, it takes small, computationally cheap steps to guide the model to the best settings without breaking your computer.

Where is gradient descent in ml Used in Real Life?

1. Cricket Shot Selection (Batting Coach Example)

Imagine a young cricketer in a Mumbai academy who wants to become a perfect batsman. He has many things to adjust — bat angle, foot movement, shoulder position, timing, etc.

The coach cannot tell him the “perfect position” in one go. Instead, the coach watches him play one ball and says:

After every ball (every step), the coach gives small feedback to reduce the mistake. Slowly, ball by ball, the batsman improves and gets closer to the perfect shot.

In ML terms: Gradient Descent is like that coach. It watches the model’s mistakes (errors) after every step and makes small changes in the model’s settings (weights) so that the total mistakes keep reducing. Just like the batsman slowly becomes better.


2. Street Food Vendor Adjusting Masala (Chaat Seller Example)

A chaat seller in Delhi’s Chandni Chowk wants to make the perfect spicy-sweet-sour balance that customers love.

He cannot guess the exact amount of masala, imli, and green chutney in one try. So he does this:

In ML terms: Gradient Descent works the same way. The “taste” is the error (loss). The seller (model) keeps making small adjustments in the recipe (weights) to reduce the complaints (error) until the chaat becomes perfect for most people.


3. Auto-Rickshaw Driver Finding the Best Route in Bangalore Traffic

An auto driver in Bangalore wants to reach from Majestic to Whitefield in the shortest time possible.

He cannot see the full road because of heavy traffic and rain. So he drives like this:

In ML terms: Gradient Descent is like this auto driver. The “shortest time” is the lowest error. The driver (model) cannot see the final answer directly, so he makes small, smart changes (steps) based on current conditions (gradient) until he reaches the best possible route (minimum loss).

The Math Behind gradient descent in ml 

To understand how the computer takes these steps, let’s look at the math that powers gradient descent in ML.

The core formula used to update the model’s settings is:

w_new = w_old – η × (∂L / ∂w)

Let’s break this down symbol by symbol:

The Cost Function: Our “Pain Meter”

To know how bad our model’s predictions are, we use a Cost Function (often called Loss, denoted by L). A common one is the Mean Squared Error (MSE):

L = (1/N) · Σᵢ (y_actual – y_predicted)²

In human terms, the Cost Function is a pain meter.

What do Weights and Biases physically represent?

When we build a model, we use the equation of a line:

y = w · x + b

Gradient descent in ml gently turns these w and b knobs, step-by-step, until the music (our prediction) sounds absolutely perfect!

1: Why Simple Models Fail ?

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# 1. Generate Synthetic Punjab Wheat Yield Data
# We set a random seed so the data is exactly the same every time you run it
np.random.seed(42)

# X represents Nitrogen Fertilizer input (normalized to -1 to 1 for mathematical stability)
X = np.linspace(-1, 1, 100)

# Y represents Wheat Yield in Quintals per Hectare
# The true relationship is quadratic: too much fertilizer reduces yield
noise = np.random.normal(0, 0.15, size=100)
Y = -1.5 * (X**2) + 0.5 * X + 1.2 + noise

# 2. Pre-compute Gradient Descent for both models to ensure smooth animation
epochs = 300
learning_rate = 0.15

# — Model 1: Linear Model (Wrong/Too-Simple Approach) —
# Equation: y = w1 * x + b
w1_linear, b_linear = 0.0, 0.0
linear_loss_history = []
linear_preds_history = []

# — Model 2: Polynomial Model (Correct/Gradient Descent Approach) —
# Equation: y = w2 * x^2 + w1 * x + b
w2_poly, w1_poly, b_poly = 0.0, 0.0, 0.0
poly_loss_history = []
poly_preds_history = []

# Manual Gradient Descent Loop
for epoch in range(epochs):
# — Linear Model Step —
y_pred_linear = w1_linear * X + b_linear
loss_linear = np.mean((Y – y_pred_linear)**2)
linear_loss_history.append(loss_linear)
linear_preds_history.append(y_pred_linear)

# Gradients for Linear Model
dw1_linear = -2 * np.mean((Y – y_pred_linear) * X)
db_linear = -2 * np.mean(Y – y_pred_linear)

# Update weights
w1_linear -= learning_rate * dw1_linear
b_linear -= learning_rate * db_linear

# — Polynomial Model Step —
y_pred_poly = w2_poly * (X**2) + w1_poly * X + b_poly
loss_poly = np.mean((Y – y_pred_poly)**2)
poly_loss_history.append(loss_poly)
poly_preds_history.append(y_pred_poly)

# Gradients for Polynomial Model
dw2_poly = -2 * np.mean((Y – y_pred_poly) * (X**2))
dw1_poly = -2 * np.mean((Y – y_pred_poly) * X)
db_poly = -2 * np.mean(Y – y_pred_poly)

# Update weights
w2_poly -= learning_rate * dw2_poly
w1_poly -= learning_rate * dw1_poly
b_poly -= learning_rate * db_poly

# 3. Set up Matplotlib Figure for Animation (2×2 Grid)
# Top Row: Model fits | Bottom Row: Live Loss Curves
fig, axs = plt.subplots(2, 2, figsize=(14, 10))
fig.patch.set_facecolor(‘#F5F5F5′) # Light grey background for clean look

# Subplot references
ax_lin = axs[0, 0]
ax_lin_loss = axs[1, 0]
ax_poly = axs[0, 1]
ax_poly_loss = axs[1, 1]

# Plot static farm data points on both top subplots
ax_lin.scatter(X, Y, color=’#FF5722′, alpha=0.7, edgecolors=’k’, label=’Punjab Farm Data’)
ax_poly.scatter(X, Y, color=’#4CAF50′, alpha=0.7, edgecolors=’k’, label=’Punjab Farm Data’)

# Initialize empty line objects to update during animation
line_linear_fit, = ax_lin.plot([], [], color=’red’, lw=3, label=’Linear Fit (Underfit)’)
line_poly_fit, = ax_poly.plot([], [], color=’green’, lw=3, label=’Polynomial Fit (Correct)’)

line_linear_loss, = ax_lin_loss.plot([], [], color=’red’, lw=2, label=’Linear Loss’)
line_poly_loss, = ax_poly_loss.plot([], [], color=’green’, lw=2, label=’Polynomial Loss’)

# Format the fit plots
for ax in [ax_lin, ax_poly]:
ax.set_xlim(-1.1, 1.1)
ax.set_ylim(min(Y) – 0.5, max(Y) + 0.5)
ax.set_xlabel(‘Fertilizer Usage (Normalized)’, fontsize=10)
ax.set_ylabel(‘Wheat Yield (Quintals/Hectare)’, fontsize=10)
ax.grid(True, linestyle=’–‘, alpha=0.5)
ax.legend(loc=’upper left’)

# Format the loss plots
for ax in [ax_lin_loss, ax_poly_loss]:
ax.set_xlim(0, epochs)
ax.set_xlabel(‘Epochs’, fontsize=10)
ax.set_ylabel(‘Mean Squared Error (MSE)’, fontsize=10)
ax.grid(True, linestyle=’–‘, alpha=0.5)

# Set static Y limits for loss curves based on maximum initial losses
ax_lin_loss.set_ylim(0, max(linear_loss_history) * 1.1)
ax_poly_loss.set_ylim(0, max(poly_loss_history) * 1.1)

# Animation Update Function
def update(frame):
# Update fit lines with predictions at current frame
line_linear_fit.set_data(X, linear_preds_history[frame])
line_poly_fit.set_data(X, poly_preds_history[frame])

# Update loss lines up to current frame
line_linear_loss.set_data(range(frame), linear_loss_history[:frame])
line_poly_loss.set_data(range(frame), poly_loss_history[:frame])

# Update subplot titles with live loss values
ax_lin.set_title(f”Linear Model (Wrong Approach)\nLive Loss (MSE): {linear_loss_history[frame]:.4f}”, fontsize=11, color=’red’, weight=’bold’)
ax_poly.set_title(f”Polynomial Model (Gradient Descent)\nLive Loss (MSE): {poly_loss_history[frame]:.4f}”, fontsize=11, color=’green’, weight=’bold’)

# Dynamic main title narrating the story frame-by-frame
if frame == 0:
story_title = “Epoch 0 — Both starting blind”
elif frame < 50:
story_title = f”Epoch {frame} — Getting warmer! Models adjusting weights…”
elif frame < 150:
story_title = f”Epoch {frame} — Simple model stuck, gradient descent in ml learning fast!”
else:
story_title = f”Epoch {frame} — Simple model failed. gradient descent in ml nailed it!”

fig.suptitle(story_title, fontsize=16, fontweight=’bold’, color=’#333333′)

return line_linear_fit, line_poly_fit, line_linear_loss, line_poly_loss

# Create animation
ani = FuncAnimation(fig, update, frames=epochs, interval=50, blit=False)

# Save animation as GIF
ani.save(‘comparison_gradient_descent_in_ml.gif’, writer=’pillow’, fps=10)
plt.close()

# 4. Post-Training Comparison Plot (Actual vs Predicted & Residuals)
# Using the final trained Polynomial Model
final_y_pred_poly = w2_poly * (X**2) + w1_poly * X + b_poly
residuals = Y – final_y_pred_poly

fig_comp, (ax_act_pred, ax_res) = plt.subplots(1, 2, figsize=(14, 6))
fig_comp.patch.set_facecolor(‘#F5F5F5′)

# Left Plot: Actual vs Predicted
ax_act_pred.scatter(Y, final_y_pred_poly, color=’#4CAF50′, alpha=0.7, edgecolors=’k’)
ideal_line = np.linspace(min(Y), max(Y), 100)
ax_act_pred.plot(ideal_line, ideal_line, color=’red’, linestyle=’–‘, lw=2, label=’Perfect Prediction (Y = Y_pred)’)
ax_act_pred.set_title(‘Actual vs. Predicted Yield’, fontsize=14, fontweight=’bold’)
ax_act_pred.set_xlabel(‘Actual Yield (Quintals/Hectare)’, fontsize=12)
ax_act_pred.set_ylabel(‘Predicted Yield (Quintals/Hectare)’, fontsize=12)
ax_act_pred.grid(True, linestyle=’–‘, alpha=0.5)
ax_act_pred.legend()

# Right Plot: Residuals Plot
ax_res.scatter(final_y_pred_poly, residuals, color=’#9C27B0′, alpha=0.7, edgecolors=’k’)
ax_res.axhline(0, color=’red’, linestyle=’–‘, lw=2)
ax_res.set_title(‘Residuals Plot (Errors vs. Predictions)’, fontsize=14, fontweight=’bold’)
ax_res.set_xlabel(‘Predicted Yield (Quintals/Hectare)’, fontsize=12)
ax_res.set_ylabel(‘Residuals (Actual – Predicted)’, fontsize=12)
ax_res.grid(True, linestyle=’–‘, alpha=0.5)

plt.tight_layout()
plt.savefig(‘crop_yield_residuals_comparison.png’)
plt.close()

View post on imgur.com

Comparison Animation

What You Will See in This GIF:

2: How gradient descent in ml Actually Learns ?

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# 1. Generate Synthetic Mumbai House Price Data
# We set a random seed so the data is exactly the same every time you run it
np.random.seed(42)

# flat_area_1000sqft represents the size of the flat in thousands of square feet (0.5 to 3.5)
flat_area_1000sqft = np.linspace(0.5, 3.5, 100)

# flat_price_crores represents the price of the flat in Crores (INR)
# The true relationship is linear with some real-world noise added
noise = np.random.normal(0, 0.3, size=100)
flat_price_crores = 1.8 * flat_area_1000sqft + 0.5 + noise

# 2. Pre-compute Gradient Descent step-by-step to store history for the animation
epochs = 150
learning_rate = 0.05

# Initialize parameters to zero (starting completely blind)
weight_slope = 0.0
bias_intercept = 0.0

# Lists to store history for animation frames
weight_history = []
bias_history = []
loss_history = []
predictions_history = []

# Manual Gradient Descent Loop
for epoch in range(epochs):
# Calculate current predictions: y = w * x + b
current_predictions = weight_slope * flat_area_1000sqft + bias_intercept

# Calculate Mean Squared Error (MSE) Loss
current_loss = np.mean((flat_price_crores – current_predictions) ** 2)

# Save values to history lists
weight_history.append(weight_slope)
bias_history.append(bias_intercept)
loss_history.append(current_loss)
predictions_history.append(current_predictions)

# Calculate Gradients (Partial Derivatives of Loss with respect to weight and bias)
gradient_weight = -2 * np.mean((flat_price_crores – current_predictions) * flat_area_1000sqft)
gradient_bias = -2 * np.mean(flat_price_crores – current_predictions)

# Update weight and bias by stepping in the opposite direction of the gradient
weight_slope -= learning_rate * gradient_weight
bias_intercept -= learning_rate * gradient_bias

# 3. Set up Matplotlib Figure for Animation (1 Row, 2 Columns)
# Left Plot: Live Model Fitting | Right Plot: Live Loss Curve
fig, (ax_fit, ax_loss) = plt.subplots(1, 2, figsize=(14, 6))
fig.patch.set_facecolor(‘#F9F9F9′) # Soft light grey background

# Plot static Mumbai house data points on the left subplot
ax_fit.scatter(flat_area_1000sqft, flat_price_crores, color=’#FF9900′, alpha=0.8, edgecolors=’k’, label=’Mumbai Flats (Data)’)

# Initialize empty line objects to update during animation
line_fit, = ax_fit.plot([], [], color=’#1A73E8′, lw=3, label=’Fitted Line (Model)’)
line_loss, = ax_loss.plot([], [], color=’#D93025′, lw=2.5, label=’Mean Squared Error (MSE)’)

# Format the left fit plot
ax_fit.set_xlim(0.3, 3.7)
ax_fit.set_ylim(0, 8)
ax_fit.set_xlabel(‘Area (in 1000 sq ft)’, fontsize=11)
ax_fit.set_ylabel(‘Price (in Crores INR)’, fontsize=11)
ax_fit.grid(True, linestyle=’–‘, alpha=0.5)
ax_fit.legend(loc=’upper left’)

# Format the right loss plot
ax_loss.set_xlim(0, epochs)
ax_loss.set_ylim(0, max(loss_history) * 1.1)
ax_loss.set_xlabel(‘Epochs’, fontsize=11)
ax_loss.set_ylabel(‘Loss (MSE)’, fontsize=11)
ax_loss.grid(True, linestyle=’–‘, alpha=0.5)
ax_loss.legend(loc=’upper right’)

# Animation Update Function
def update(frame):
# Update the fitted line with predictions from the current frame
line_fit.set_data(flat_area_1000sqft, predictions_history[frame])

# Update the loss curve line with all loss values up to the current frame
line_loss.set_data(range(frame), loss_history[:frame])

# Extract current loss value for the title
current_loss_val = loss_history[frame]

# Dynamic main title narrating the story frame-by-frame
if frame == 0:
story_title = f”Epoch 0 — Random guess! Loss = {current_loss_val:.4f}”
elif frame < 15:
story_title = f”Epoch {frame} — Learning fast! Loss = {current_loss_val:.4f}”
elif frame < 50:
story_title = f”Epoch {frame} — Getting warmer! Loss = {current_loss_val:.4f}”
elif frame < 100:
story_title = f”Epoch {frame} — Fine-tuning weights… Loss = {current_loss_val:.4f}”
else:
story_title = f”Epoch {frame} — Converged! Perfect fit. Loss = {current_loss_val:.4f}”

fig.suptitle(story_title, fontsize=14, fontweight=’bold’, color=’#333333′)

return line_fit, line_loss

# Create the animation
ani = FuncAnimation(fig, update, frames=epochs, interval=80, blit=False)

# Save the animation as a GIF
ani.save(‘training_gradient_descent_in_ml.gif’, writer=’pillow’, fps=10)
plt.close()

# 4. Post-Training Comparison Plot (Actual vs Predicted & Residuals)
# Using the final trained parameters
final_predictions = weight_slope * flat_area_1000sqft + bias_intercept
residuals = flat_price_crores – final_predictions

fig_comp, (ax_act_pred, ax_res) = plt.subplots(1, 2, figsize=(14, 6))
fig_comp.patch.set_facecolor(‘#F9F9F9′)

# Left Plot: Actual vs Predicted
ax_act_pred.scatter(flat_price_crores, final_predictions, color=’#1A73E8′, alpha=0.8, edgecolors=’k’)
ideal_line = np.linspace(min(flat_price_crores), max(flat_price_crores), 100)
ax_act_pred.plot(ideal_line, ideal_line, color=’#D93025′, linestyle=’–‘, lw=2, label=’Perfect Prediction (Y = Y_pred)’)
ax_act_pred.set_title(‘Actual vs. Predicted Prices’, fontsize=12, fontweight=’bold’)
ax_act_pred.set_xlabel(‘Actual Price (Crores INR)’, fontsize=10)
ax_act_pred.set_ylabel(‘Predicted Price (Crores INR)’, fontsize=10)
ax_act_pred.grid(True, linestyle=’–‘, alpha=0.5)
ax_act_pred.legend()

# Right Plot: Residuals Plot
ax_res.scatter(final_predictions, residuals, color=’#107C41′, alpha=0.8, edgecolors=’k’)
ax_res.axhline(0, color=’#D93025′, linestyle=’–‘, lw=2)
ax_res.set_title(‘Residuals Plot (Errors vs. Predictions)’, fontsize=12, fontweight=’bold’)
ax_res.set_xlabel(‘Predicted Price (Crores INR)’, fontsize=10)
ax_res.set_ylabel(‘Residuals (Actual – Predicted)’, fontsize=10)
ax_res.grid(True, linestyle=’–‘, alpha=0.5)

plt.tight_layout()
plt.savefig(‘mumbai_house_prices_residuals.png’)
plt.close()

 

View post on imgur.com

Training Animation

What You Will See in This GIF:

 

3: What Happens When We Push Too Far?

# Import the necessary libraries for mathematical operations and animation
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# Set a random seed for reproducibility
np.random.seed(42)

# Generate synthetic non-linear data (e.g., crop yield vs fertilizer)
total_data_points = 80
# Generate input features normalized between -1 and 1
X_raw = np.linspace(-1, 1, total_data_points)
# Generate target values using a cubic function with added noise
Y_raw = 1.5 * (X_raw**3) – 0.9 * (X_raw**2) – 0.5 * X_raw + 0.5 + np.random.normal(0, 0.12, total_data_points)

# Helper function to manually build polynomial feature matrices
def build_polynomial_features(input_x, model_degree):
feature_columns = []
for power in range(model_degree + 1):
feature_columns.append(input_x ** power)
return np.column_stack(feature_columns)

# Build polynomial feature matrices for all three models
X_features_deg1 = build_polynomial_features(X_raw, 1) # Shape: (80, 2)
X_features_deg3 = build_polynomial_features(X_raw, 3) # Shape: (80, 4)
X_features_deg8 = build_polynomial_features(X_raw, 8) # Shape: (80, 9)

# Initialize model weights to zero
weights_deg1 = np.zeros(2)
weights_deg3 = np.zeros(4)
weights_deg8 = np.zeros(9)

# Set learning rates (higher degrees require smaller learning rates to prevent exploding gradients)
learning_rate_deg1 = 0.15
learning_rate_deg3 = 0.15
learning_rate_deg8 = 0.08

# Define training parameters
total_epochs = 200

# Lists to store historical predictions and losses for each frame of the animation
history_predictions_deg1 = []
history_predictions_deg3 = []
history_predictions_deg8 = []

history_loss_deg1 = []
history_loss_deg3 = []
history_loss_deg8 = []

# Manual Gradient Descent Loop
for current_epoch in range(total_epochs):
# — Degree 1 Model Step —
predictions_deg1 = X_features_deg1 @ weights_deg1
loss_deg1 = np.mean((Y_raw – predictions_deg1) ** 2)
gradient_deg1 = -2 * (X_features_deg1.T @ (Y_raw – predictions_deg1)) / total_data_points
weights_deg1 -= learning_rate_deg1 * gradient_deg1
history_predictions_deg1.append(predictions_deg1)
history_loss_deg1.append(loss_deg1)

# — Degree 3 Model Step —
predictions_deg3 = X_features_deg3 @ weights_deg3
loss_deg3 = np.mean((Y_raw – predictions_deg3) ** 2)
gradient_deg3 = -2 * (X_features_deg3.T @ (Y_raw – predictions_deg3)) / total_data_points
weights_deg3 -= learning_rate_deg3 * gradient_deg3
history_predictions_deg3.append(predictions_deg3)
history_loss_deg3.append(loss_deg3)

# — Degree 8 Model Step —
predictions_deg8 = X_features_deg8 @ weights_deg8
loss_deg8 = np.mean((Y_raw – predictions_deg8) ** 2)
gradient_deg8 = -2 * (X_features_deg8.T @ (Y_raw – predictions_deg8)) / total_data_points
weights_deg8 -= learning_rate_deg8 * gradient_deg8
history_predictions_deg8.append(predictions_deg8)
history_loss_deg8.append(loss_deg8)

# Set up a 2×3 grid of subplots for the animation
# Top Row: Model Fits | Bottom Row: Live Loss Curves
fig, axs = plt.subplots(2, 3, figsize=(18, 10))
fig.patch.set_facecolor(‘#F5F5F5′) # Soft grey background

# Assign descriptive names to each subplot axis
ax_fit_deg1, ax_fit_deg3, ax_fit_deg8 = axs[0, 0], axs[0, 1], axs[0, 2]
ax_loss_deg1, ax_loss_deg3, ax_loss_deg8 = axs[1, 0], axs[1, 1], axs[1, 2]

# Plot static raw data points on the top row subplots
for ax in [ax_fit_deg1, ax_fit_deg3, ax_fit_deg8]:
ax.scatter(X_raw, Y_raw, color=’#34495E’, alpha=0.6, edgecolors=’k’, label=’Raw Data’)
ax.set_xlim(-1.1, 1.1)
ax.set_ylim(min(Y_raw) – 0.5, max(Y_raw) + 0.5)
ax.set_xlabel(‘Input Feature (X)’, fontsize=10)
ax.set_ylabel(‘Target (Y)’, fontsize=10)
ax.grid(True, linestyle=’–‘, alpha=0.5)

# Initialize empty line objects for the dynamic fit lines
line_fit_deg1, = ax_fit_deg1.plot([], [], color=’#E74C3C’, lw=3, label=’Degree 1 Fit’)
line_fit_deg3, = ax_fit_deg3.plot([], [], color=’#2ECC71′, lw=3, label=’Degree 3 Fit’)
line_fit_deg8, = ax_fit_deg8.plot([], [], color=’#9B59B6′, lw=3, label=’Degree 8 Fit’)

# Initialize empty line objects for the dynamic loss curves
line_loss_deg1, = ax_loss_deg1.plot([], [], color=’#E74C3C’, lw=2.5, label=’Loss (Deg 1)’)
line_loss_deg3, = ax_loss_deg3.plot([], [], color=’#2ECC71′, lw=2.5, label=’Loss (Deg 3)’)
line_loss_deg8, = ax_loss_deg8.plot([], [], color=’#9B59B6′, lw=2.5, label=’Loss (Deg 8)’)

# Format the loss subplots
for ax, max_loss in zip([ax_loss_deg1, ax_loss_deg3, ax_loss_deg8], [history_loss_deg1[0], history_loss_deg3[0], history_loss_deg8[0]]):
ax.set_xlim(0, total_epochs)
ax.set_ylim(0, max_loss * 1.1)
ax.set_xlabel(‘Epochs’, fontsize=10)
ax.set_ylabel(‘Mean Squared Error (MSE)’, fontsize=10)
ax.grid(True, linestyle=’–‘, alpha=0.5)

# Add legends to all subplots
for ax in axs.ravel():
ax.legend(loc=’upper right’)

# Animation Update Function
def update_frame(frame_index):
# Update fit lines with predictions at the current frame
line_fit_deg1.set_data(X_raw, history_predictions_deg1[frame_index])
line_fit_deg3.set_data(X_raw, history_predictions_deg3[frame_index])
line_fit_deg8.set_data(X_raw, history_predictions_deg8[frame_index])

# Update loss lines up to the current frame
line_loss_deg1.set_data(range(frame_index), history_loss_deg1[:frame_index])
line_loss_deg3.set_data(range(frame_index), history_loss_deg3[:frame_index])
line_loss_deg8.set_data(range(frame_index), history_loss_deg8[:frame_index])

# Update subplot titles with live loss values
ax_fit_deg1.set_title(f”Degree 1 (Underfit)\nLive Loss: {history_loss_deg1[frame_index]:.4f}”, fontsize=11, fontweight=’bold’, color=’#E74C3C’)
ax_fit_deg3.set_title(f”Degree 3 (Good Fit)\nLive Loss: {history_loss_deg3[frame_index]:.4f}”, fontsize=11, fontweight=’bold’, color=’#2ECC71′)
ax_fit_deg8.set_title(f”Degree 8 (Overfit)\nLive Loss: {history_loss_deg8[frame_index]:.4f}”, fontsize=11, fontweight=’bold’, color=’#9B59B6′)

# Dynamic main title narrating the story frame-by-frame
if frame_index == 0:
story_title = “Epoch 0 — All models starting blind with zero weights!”
elif frame_index < 30:
story_title = f”Epoch {frame_index} — Gradient Descent active! Models adjusting shapes…”
elif frame_index < 100:
story_title = f”Epoch {frame_index} — Degree 1 is stuck. Degree 3 & 8 are learning complex curves.”
else:
story_title = f”Epoch {frame_index} — Degree 8 is chasing noise! Degree 3 is perfectly balanced.”

fig.suptitle(story_title, fontsize=16, fontweight=’bold’, color=’#2C3E50′)

return line_fit_deg1, line_fit_deg3, line_fit_deg8, line_loss_deg1, line_loss_deg3, line_loss_deg8

# Create the animation object
ani = FuncAnimation(fig, update_frame, frames=total_epochs, interval=60, blit=False)

# Save the animation as a high-quality GIF
ani.save(‘edgecase_gradient_descent_in_ml.gif’, writer=’pillow’, fps=8)

View post on imgur.com

Edge Case Animation

What You Will See in This GIF:


Line-by-Line Code Walkthrough

Here is a breakdown of the key concepts and functions used across our Python scripts:


Quick Recap ✅


Test Yourself 🧠

1. What does the minus sign in the gradient descent in ml update formula (w_new = w_old – η × (∂L/∂w)) do?

Answer: B) It ensures we take a step in the opposite direction of the slope to go downhill.

2. If your learning rate (η) is set way too high, what is most likely to happen during training?

Answer: C) The model will take giant leaps, overshoot the minimum error, and the loss might explode.

3. In our Edge Case Animation, why did the Degree 8 polynomial model fail to be the best choice, even though it had the lowest training loss?

Answer: B) It overfit the data by chasing individual noisy points, meaning it would fail on new, unseen data.

 

 

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
// STILL BROWSING?
Build along, don't just read.
Get labs & articles matched to what you're into — free, takes 30 seconds.
Start building free
0
Would love your thoughts, please comment.x
()
x