Navigation

Introduction to AI

Machine Learning

Deep Learning

Generative AI

Tools & Frameworks

General

Learning Rate Range Test

The learning rate range test helps identify the optimal learning rate range for training neural networks.

What Is the LR Range Test

The test trains the model for several epochs while gradually increasing the learning rate from a very small to a very large value. By plotting loss vs. learning rate, we can identify:

  • Minimum learning rate: Below which there’s no improvement
  • Optimal learning rate: Where loss decreases fastest
  • Maximum learning rate: Where loss starts to diverge

Implementing the LR Range Test

import matplotlib.pyplot as plt
import numpy as np

def lr_range_test(model, train_loader, device, start_lr=1e-7, end_lr=10, 
                  epochs=1, accumulation_steps=1):
    """
    Run learning rate range test.
    
    Returns: losses and learning rates for plotting.
    """
    model = model.to(device)
    optimizer = torch.optim.SGD(model.parameters(), lr=start_lr, momentum=0.9)
    
    num_batches = len(train_loader) * epochs
    lr_schedule = np.exp(np.linspace(np.log(start_lr), np.log(end_lr), num_batches))
    
    losses = []
    learning_rates = []
    smoothed_losses = []
    
    model.train()
    running_loss = 0.0
    
    epoch_count = 0
    batch_count = 0
    
    for epoch in range(epochs):
        for inputs, targets in train_loader:
            # Update learning rate
            lr = lr_schedule[batch_count]
            for param_group in optimizer.param_groups:
                param_group['lr'] = lr
            
            inputs, targets = inputs.to(device), targets.to(device)
            
            # Forward pass
            outputs = model(inputs)
            loss = F.cross_entropy(outputs, targets)
            
            # Gradient accumulation
            loss = loss / accumulation_steps
            loss.backward()
            
            # Update weights
            if (batch_count + 1) % accumulation_steps == 0:
                optimizer.step()
                optimizer.zero_grad()
            
            running_loss += loss.item() * accumulation_steps
            learning_rates.append(lr)
            losses.append(running_loss)
            running_loss = 0.0
            
            batch_count += 1
        
        epoch_count += 1
    
    # Smooth losses for cleaner visualization
    window_size = 20
    for i in range(len(losses)):
        start = max(0, i - window_size)
        smoothed_losses.append(np.mean(losses[start:i+1]))
    
    return learning_rates, losses, smoothed_losses

Plotting and Analysis

def plot_lr_range_test(lrs, losses, smoothed_losses=None):
    """Plot LR range test results."""
    plt.figure(figsize=(12, 4))
    
    plt.subplot(1, 2, 1)
    plt.plot(lrs, losses, alpha=0.3, color='blue')
    if smoothed_losses:
        plt.plot(lrs, smoothed_losses, color='blue', linewidth=2)
    plt.xscale('log')
    plt.xlabel('Learning Rate')
    plt.ylabel('Loss')
    plt.title('Learning Rate Range Test')
    plt.grid(True, alpha=0.3)
    
    plt.subplot(1, 2, 2)
    plt.plot(lrs, np.log(losses), alpha=0.3, color='blue')
    if smoothed_losses:
        plt.plot(lrs, np.log(smoothed_losses), color='blue', linewidth=2)
    plt.xscale('log')
    plt.xlabel('Learning Rate')
    plt.ylabel('Log Loss')
    plt.title('Log Loss vs Learning Rate')
    plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('lr_range_test.png')
    plt.show()


# Usage
lrs, losses, smoothed = lr_range_test(
    model, train_loader, device='cuda',
    start_lr=1e-6, end_lr=10, epochs=1
)
plot_lr_range_test(lrs, losses, smoothed)

Interpreting Results

Loss curve typically has three regions:

1. Too Low LR (left flat region)
   - Loss doesn't decrease
   - Learning rate: 1e-7 to 1e-5
   
2. Optimal LR (steepest descent)
   - Loss decreases fastest
   - Learning rate: 1e-4 to 1e-2
   
3. Too High LR (right increasing region)
   - Loss starts to increase
   - Learning rate: > 0.1

Selecting Learning Rate from Test

def find_optimal_lr(lrs, losses, smoothed_losses):
    """Find optimal learning rate from range test."""
    # Use smoothed losses
    losses = smoothed_losses
    
    # Find minimum loss and its index
    min_idx = np.argmin(losses)
    min_loss = losses[min_idx]
    min_lr = lrs[min_idx]
    
    # Find left boundary (where loss starts decreasing significantly)
    for i in range(min_idx, -1, -1):
        if losses[i] > losses[0] * 0.95:  # Within 5% of initial loss
            left_lr = lrs[i]
            break
    
    # Recommended: start at left boundary, peak around 10x min_lr
    max_lr = min_lr * 10
    
    return {
        'min_loss_lr': min_lr,
        'recommended_min_lr': left_lr,
        'recommended_max_lr': max_lr,
        'stochastic_range_min': lrs[min_idx // 2],
        'stochastic_range_max': min_lr * 10
    }


# For one-cycle policy:
# min_lr = recommended_min_lr
# max_lr = recommended_max_lr
# div_factor = max_lr / min_lr  # Usually 10-20

Practical Recommendations

  • Run the test for 1 epoch on a subset of data
  • Use SGD with momentum for accurate results
  • Plot both raw and smoothed losses
  • The optimal LR is typically where loss decreases fastest
  • For one-cycle policy: max_lr = 10×min_lr
  • If loss diverges: decrease end_lr and retest