xbe.at

Deep learning / Backpropagation and optimization

Response:

7 min read, 15 sections

Contents
  1. Understanding Sum of Squared Residuals
  2. Partial Derivatives for Gradient Descent
  3. Implementation of Basic Gradient Descent
  4. Adaptive Learning Rate Implementation
  5. Visualization of Gradient Descent Progress
  6. Response:
  7. Real-World Application - Housing Price Prediction
  8. Implementing Mini-batch Gradient Descent
  9. Momentum-Based Gradient Descent
  10. Early Stopping Implementation
  11. Response:
  12. Advanced Cost Surface Visualization
  13. Real-World Application - Temperature Prediction
  14. Gradient Descent with Constraints
  15. 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"

More in Backpropagation and optimization

  1. Saddle Points in Deep Learning Optimization10 min

    Saddle points are critical points in the loss landscape of neural networks where the gradient is zero, but they are neither local minima nor maxima. They...

  2. Overcoming Vanishing Gradients in Deep Neural Networks10 min

    The vanishing gradient problem can be understood through the chain rule of calculus in neural networks. As gradients flow backward through many layers...

  3. SGD with Momentum in Deep Learning11 min

    Stochastic Gradient Descent (SGD) with Momentum is an optimization algorithm used in deep learning to accelerate convergence and reduce oscillations during...

  4. Optimizing Neural Network Weights with Gradient Descent9 min

    Gradient descent forms the backbone of neural network optimization by iteratively adjusting weights to minimize the loss function. The process involves...

  5. Step-by-Step Guide to Gradient Descent Predictions7 min

    Gradient Descent is a fundamental optimization algorithm in machine learning used to minimize a cost function and improve model performance. It iteratively...

  6. Optimizing Neural Network Training with Learning Rate Control10 min

    Learning rate control is crucial for optimizing neural network performance. It involves adjusting the step size during gradient descent to find the right...