Deep learning / Backpropagation and optimization
Response:
Contents
- Understanding Sum of Squared Residuals
- Partial Derivatives for Gradient Descent
- Implementation of Basic Gradient Descent
- Adaptive Learning Rate Implementation
- Visualization of Gradient Descent Progress
- Response:
- Real-World Application - Housing Price Prediction
- Implementing Mini-batch Gradient Descent
- Momentum-Based Gradient Descent
- Early Stopping Implementation
- Response:
- Advanced Cost Surface Visualization
- Real-World Application - Temperature Prediction
- Gradient Descent with Constraints
- Additional Resources
Understanding Sum of Squared Residuals
In linear regression, the Sum of Squared Residuals (SSR) measures the total deviation between predicted and actual values. It serves as our cost function, quantifying how well our model fits the data by summing the squared differences between predicted and observed values.
import numpy as np
import matplotlib.pyplot as plt
def calculate_ssr(X, y, slope, intercept):
# Calculate predicted values using current parameters
y_pred = slope * X + intercept
# Calculate residuals (differences between actual and predicted)
residuals = y - y_pred
# Return sum of squared residuals
return np.sum(residuals**2)
# Example usage
X = np.array([1, 2, 3, 4, 5])
y = np.array([2.1, 3.8, 6.2, 7.8, 9.3])
ssr = calculate_ssr(X, y, slope=2, intercept=0)
print(f"Sum of Squared Residuals: {ssr:.2f}")
Partial Derivatives for Gradient Descent
Understanding partial derivatives is crucial for gradient descent as they indicate the direction of steepest descent for each parameter. We compute these derivatives with respect to both slope and intercept to determine how to adjust our parameters.
def compute_gradients(X, y, slope, intercept):
# Compute predictions
y_pred = slope * X + intercept
# Partial derivative with respect to slope
d_slope = -2 * np.sum(X * (y - y_pred))
# Partial derivative with respect to intercept
d_intercept = -2 * np.sum(y - y_pred)
return d_slope, d_intercept
# Example usage
X = np.array([1, 2, 3, 4, 5])
y = np.array([2.1, 3.8, 6.2, 7.8, 9.3])
d_slope, d_intercept = compute_gradients(X, y, slope=2, intercept=0)
print(f"Gradient for slope: {d_slope:.4f}")
print(f"Gradient for intercept: {d_intercept:.4f}")
Implementation of Basic Gradient Descent
The gradient descent algorithm iteratively updates parameters by moving in the direction opposite to the gradient. The learning rate controls the size of these steps, while the number of iterations determines how long the optimization runs.
def gradient_descent(X, y, learning_rate=0.01, n_iterations=1000):
# Initialize parameters
slope = 0
intercept = 0
# Store history for visualization
history = []
for i in range(n_iterations):
# Compute gradients
d_slope, d_intercept = compute_gradients(X, y, slope, intercept)
# Update parameters
slope -= learning_rate * d_slope
intercept -= learning_rate * d_intercept
# Store current state
history.append((slope, intercept, calculate_ssr(X, y, slope, intercept)))
return slope, intercept, history
# Example usage
optimal_slope, optimal_intercept, history = gradient_descent(X, y)
print(f"Optimal slope: {optimal_slope:.4f}")
print(f"Optimal intercept: {optimal_intercept:.4f}")
Adaptive Learning Rate Implementation
Adaptive learning rates improve convergence by adjusting the step size dynamically. This implementation uses a simple decay schedule that reduces the learning rate as optimization progresses, allowing for more precise parameter updates near the optimum.
def adaptive_gradient_descent(X, y, initial_lr=0.01, n_iterations=1000):
slope = 0
intercept = 0
history = []
for i in range(n_iterations):
# Compute adaptive learning rate
current_lr = initial_lr / (1 + i/100)
# Compute gradients
d_slope, d_intercept = compute_gradients(X, y, slope, intercept)
# Update parameters with adaptive learning rate
slope -= current_lr * d_slope
intercept -= current_lr * d_intercept
history.append((slope, intercept, calculate_ssr(X, y, slope, intercept)))
return slope, intercept, history
Visualization of Gradient Descent Progress
Creating visualizations helps understand how gradient descent converges to the optimal solution. This implementation plots the parameter updates and cost function evolution over iterations, providing insights into the optimization process.
def visualize_gradient_descent(history):
iterations = range(len(history))
slopes, intercepts, costs = zip(*history)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
# Plot parameter evolution
ax1.plot(iterations, slopes, label='Slope')
ax1.plot(iterations, intercepts, label='Intercept')
ax1.set_xlabel('Iteration')
ax1.set_ylabel('Parameter Value')
ax1.legend()
ax1.set_title('Parameter Evolution')
# Plot cost function
ax2.plot(iterations, costs)
ax2.set_xlabel('Iteration')
ax2.set_ylabel('SSR Cost')
ax2.set_title('Cost Function Evolution')
plt.tight_layout()
plt.show()
[Continuing with the remaining slides...]
Response:
Real-World Application - Housing Price Prediction
Implementing gradient descent for predicting housing prices based on square footage demonstrates practical application. This example includes data preprocessing, model training, and evaluation using real estate data.
import numpy as np
import pandas as pd
# Sample housing data (square footage, price in thousands)
data = {
'sqft': [1400, 1600, 1700, 1875, 1100, 1550, 2350, 2450, 1425, 1700],
'price': [245, 312, 279, 308, 199, 219, 405, 324, 319, 255]
}
# Normalize features for better convergence
def normalize_features(X):
return (X - np.mean(X)) / np.std(X)
# Prepare data
df = pd.DataFrame(data)
X = normalize_features(df['sqft'].values)
y = df['price'].values
# Train model using gradient descent
slope, intercept, history = gradient_descent(X, y, learning_rate=0.01, n_iterations=1500)
# Make predictions
y_pred = slope * X + intercept
mse = np.mean((y - y_pred)**2)
print(f"Mean Squared Error: {mse:.2f}")
Implementing Mini-batch Gradient Descent
Mini-batch gradient descent reduces computational overhead by updating parameters using smaller subsets of data. This implementation includes batch sampling and parameter updates, offering a balance between computational efficiency and convergence stability.
def minibatch_gradient_descent(X, y, batch_size=4, learning_rate=0.01, n_iterations=1000):
slope = 0
intercept = 0
n_samples = len(X)
history = []
for i in range(n_iterations):
# Random batch sampling
indices = np.random.permutation(n_samples)[:batch_size]
X_batch = X[indices]
y_batch = y[indices]
# Compute gradients on batch
d_slope, d_intercept = compute_gradients(X_batch, y_batch, slope, intercept)
# Update parameters
slope -= learning_rate * d_slope
intercept -= learning_rate * d_intercept
# Store full dataset cost for monitoring
history.append((slope, intercept, calculate_ssr(X, y, slope, intercept)))
return slope, intercept, history
# Example usage
mb_slope, mb_intercept, mb_history = minibatch_gradient_descent(X, y)
print(f"Mini-batch GD - Final slope: {mb_slope:.4f}, intercept: {mb_intercept:.4f}")
Momentum-Based Gradient Descent
Momentum helps accelerate gradient descent by accumulating previous gradient updates, particularly useful for escaping local minima and handling pathological curvature in the loss landscape.
def momentum_gradient_descent(X, y, learning_rate=0.01, momentum=0.9, n_iterations=1000):
slope = 0
intercept = 0
velocity_slope = 0
velocity_intercept = 0
history = []
for i in range(n_iterations):
# Compute gradients
d_slope, d_intercept = compute_gradients(X, y, slope, intercept)
# Update velocities
velocity_slope = momentum * velocity_slope - learning_rate * d_slope
velocity_intercept = momentum * velocity_intercept - learning_rate * d_intercept
# Update parameters using velocities
slope += velocity_slope
intercept += velocity_intercept
history.append((slope, intercept, calculate_ssr(X, y, slope, intercept)))
return slope, intercept, history
Early Stopping Implementation
Early stopping prevents overfitting by monitoring the convergence rate and stopping when improvements become negligible. This implementation tracks the cost function's progress and stops when the improvement falls below a threshold.
def early_stopping_gradient_descent(X, y, learning_rate=0.01, patience=10, min_delta=1e-4):
slope = 0
intercept = 0
best_cost = float('inf')
patience_counter = 0
history = []
while patience_counter < patience:
# Compute gradients
d_slope, d_intercept = compute_gradients(X, y, slope, intercept)
# Update parameters
slope -= learning_rate * d_slope
intercept -= learning_rate * d_intercept
# Calculate current cost
current_cost = calculate_ssr(X, y, slope, intercept)
history.append((slope, intercept, current_cost))
# Check for improvement
if current_cost < best_cost - min_delta:
best_cost = current_cost
patience_counter = 0
else:
patience_counter += 1
return slope, intercept, history
[Continuing with the remaining slides...]
Response:
Advanced Cost Surface Visualization
This implementation creates a 3D visualization of the cost surface, showing how SSR varies with different combinations of slope and intercept parameters, helping understand the optimization landscape.
def visualize_cost_surface(X, y, history=None):
# Create parameter grid
slope_range = np.linspace(-2, 4, 100)
intercept_range = np.linspace(-2, 4, 100)
slope_grid, intercept_grid = np.meshgrid(slope_range, intercept_range)
# Calculate cost for each parameter combination
cost_grid = np.zeros_like(slope_grid)
for i in range(len(slope_range)):
for j in range(len(intercept_range)):
cost_grid[i,j] = calculate_ssr(X, y, slope_grid[i,j], intercept_grid[i,j])
# Create 3D surface plot
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
surface = ax.plot_surface(slope_grid, intercept_grid, cost_grid,
cmap='viridis', alpha=0.8)
# Plot optimization path if history provided
if history:
slopes, intercepts, costs = zip(*history)
ax.plot(slopes, intercepts, costs, 'r-', linewidth=2, label='Optimization path')
ax.set_xlabel('Slope')
ax.set_ylabel('Intercept')
ax.set_zlabel('Cost (SSR)')
plt.colorbar(surface)
plt.show()
Real-World Application - Temperature Prediction
Implementing gradient descent for temperature prediction using historical weather data demonstrates another practical application with time series components.
# Generate synthetic temperature data
np.random.seed(42)
days = np.arange(100)
baseline_temp = 20
seasonal_component = 5 * np.sin(2 * np.pi * days / 365)
noise = np.random.normal(0, 1, 100)
temperatures = baseline_temp + seasonal_component + noise
def temperature_prediction_model(X, y, learning_rate=0.001, n_iterations=2000):
# Initialize parameters for quadratic fit
a, b, c = 0, 0, 0
history = []
for i in range(n_iterations):
# Compute predictions
y_pred = a * X**2 + b * X + c
# Compute gradients
d_a = -2 * np.sum(X**2 * (y - y_pred))
d_b = -2 * np.sum(X * (y - y_pred))
d_c = -2 * np.sum(y - y_pred)
# Update parameters
a -= learning_rate * d_a
b -= learning_rate * d_b
c -= learning_rate * d_c
# Store history
cost = np.sum((y - y_pred)**2)
history.append((a, b, c, cost))
return a, b, c, history
# Train model
X = days
y = temperatures
a, b, c, history = temperature_prediction_model(X, y)
print(f"Quadratic coefficients: a={a:.6f}, b={b:.6f}, c={c:.6f}")
Gradient Descent with Constraints
Implementing constrained gradient descent allows optimization while respecting parameter bounds, crucial for many real-world applications where parameters must stay within specific ranges.
def constrained_gradient_descent(X, y, bounds, learning_rate=0.01, n_iterations=1000):
# Initialize parameters within bounds
slope = np.random.uniform(bounds['slope'][0], bounds['slope'][1])
intercept = np.random.uniform(bounds['intercept'][0], bounds['intercept'][1])
history = []
for i in range(n_iterations):
# Compute gradients
d_slope, d_intercept = compute_gradients(X, y, slope, intercept)
# Update parameters with bounds checking
new_slope = slope - learning_rate * d_slope
new_intercept = intercept - learning_rate * d_intercept
# Apply constraints
slope = np.clip(new_slope, bounds['slope'][0], bounds['slope'][1])
intercept = np.clip(new_intercept, bounds['intercept'][0], bounds['intercept'][1])
history.append((slope, intercept, calculate_ssr(X, y, slope, intercept)))
return slope, intercept, history
# Example usage with bounds
bounds = {
'slope': (0, 5), # Positive slope only
'intercept': (-2, 2) # Limited intercept range
}
Additional Resources
- ArXiv: "An Overview of Gradient Descent Optimization Algorithms" - https://arxiv.org/abs/1609.04747
- ArXiv: "Adaptive Subgradient Methods for Online Learning and Stochastic Optimization" - https://arxiv.org/abs/1212.5701
- ArXiv: "On the Convergence of Gradient Descent for Finding the Riemannian Center of Mass" - https://arxiv.org/abs/1201.0925
- Recommended Searches:
- "Gradient Descent Variants and Applications"
- "Advanced Optimization Techniques in Machine Learning"
- "Practical Applications of Gradient Descent in Data Science"