Deep Learning › Training Deep Networks › Day 207
Hands-on lab — Day 207: Learning Rate Schedules
- ← Back to the Day 207 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/deep-learning/day-207-learning-rate-schedules/
Commands
Setup
pip install -r requirements/requirements.txt Run
python3 examples/learning_rate_schedules_lib.py Test
./tests/run_tests.sh File tree
examples/learning_rate_schedules_lib.py examples/test_learning_rate_schedules_lib.py expected-output/examples-run.txt expected-output/FIELDS.md expected-output/measured-values.txt expected-output/starter-run.txt expected-output/test-run.txt metadata.yml README.md requirements/requirements.txt security.md starter/learning_rate_schedules_lib.py starter/test_learning_rate_schedules_lib.py tests/run_tests.sh tests/test_learning_rate_schedules_lib.py troubleshooting.md
Lab README
Lab: Day 207 -- Learning Rate Schedules
Lesson
Day number: 207 of 365. Course: Course05-SS01 (Deep Learning - Neural Networks). Topic: Learning Rate Schedules in PyTorch.
Purpose
Build and test dynamic learning rate scheduling policies in PyTorch. Implement a custom Warmup + Cosine Annealing scheduler using torch.optim.lr_scheduler.LambdaLR, verify linear warmup scaling and smooth cosine decay, and ensure checkpoint state persistence.
Learning objectives
- Implement custom learning rate schedules with
torch.optim.lr_scheduler.LambdaLR. - Derive linear warmup formulas for early gradient stabilization.
- Apply half-period cosine decay for late-stage convergence.
- Save and restore scheduler state dictionaries across training runs.
Prerequisites
- Day 206 (Optimizers: SGD to Adam).
- Python 3.11+ with PyTorch.
Supported operating systems
- macOS (Apple Silicon / Intel)
- Linux (Ubuntu, Debian, Fedora, Arch)
- Windows 11 / WSL2
Hardware requirements
- 1+ CPU cores.
- 1 GB RAM.
- 100 MB disk space.
Required software
- Python 3.11 or newer.
- pip package manager.
- virtualenv or venv module.
Free and open-source options
PyTorch is free and open-source under the modified BSD license.
Installation
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements/requirements.txt
File structure
starter/learning_rate_schedules_lib.py: Student scaffold file.examples/learning_rate_schedules_lib.py: Complete reference implementation.tests/test_learning_rate_schedules_lib.py: Pytest automated validation suite.expected-output/: Verified output logs and baseline values.
How to run
Execute the reference demonstration script:
python3 examples/learning_rate_schedules_lib.py
What the commands do
- Executes a 100-step training simulation with Warmup + Cosine Annealing.
- Logs learning rate values across warmup, peak, and decay phases.
- Verifies smooth mathematical convergence.
Expected output
Scheduler Demo: Start LR = 0.000000, Peak LR = 0.010000, Final LR = 0.000100
Validation steps
- Verify that learning rate starts at 0.0 and rises linearly to peak at step 20.
- Confirm that learning rate decays smoothly to
min_lrat step 100. - Ensure all unit test assertions pass.
Tests
Run the test runner script:
./tests/run_tests.sh
Cleanup
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type d -name ".pytest_cache" -exec rm -rf {} +
Troubleshooting
- Learning Rate Remains Zero: Ensure division in warmup uses
float()to avoid integer truncation in Python.
Security notes
All scheduling logic runs locally in memory on CPU hardware.
Extension exercises
- Implement
CosineAnnealingWarmRestartswith cyclical period multipliers (T_mult = 2). - Add metric-based stepping with
ReduceLROnPlateau.
Navigation
- Lesson title: Learning Rate Schedules
- Day number: 207 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-207-learning-rate-schedules
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-207-learning-rate-scheduleswhen the site is running.
Expected output
FIELDS.md
# Expected Output Fields: Day 207
- `Start LR`: Learning rate at step 0.
- `Peak LR`: Maximum learning rate achieved at the end of warmup.
- `Final LR`: Annealed learning rate at the final step.
examples-run.txt
Scheduler Demo: Start LR = 0.000000, Peak LR = 0.010000, Final LR = 0.000100
measured-values.txt
Start LR: 0.000000
Peak LR: 0.010000
Final LR: 0.000100
starter-run.txt
Starter scaffold executed. Ready for student implementation.
test-run.txt
============================= test session starts ==============================
collected 3 items
tests/test_learning_rate_schedules_lib.py::test_warmup_cosine_scheduler_warmup_phase PASSED [ 33%]
tests/test_learning_rate_schedules_lib.py::test_warmup_cosine_scheduler_final_decay PASSED [ 66%]
tests/test_learning_rate_schedules_lib.py::test_scheduler_state_dict_persistence PASSED [100%]
============================== 3 passed in 0.22s ===============================
Source files
examples/learning_rate_schedules_lib.py (1562 bytes)
import torch
import math
from torch.optim.lr_scheduler import LambdaLR
from typing import List, Tuple, Dict, Any
def create_warmup_cosine_scheduler(optimizer: torch.optim.Optimizer,
warmup_steps: int,
total_steps: int,
min_lr_ratio: float = 0.01) -> LambdaLR:
def lr_lambda(current_step: int) -> float:
if current_step < warmup_steps:
return float(current_step) / float(max(1, warmup_steps))
progress = float(current_step - warmup_steps) / float(max(1, total_steps - warmup_steps))
cosine_decay = 0.5 * (1.0 + math.cos(math.pi * progress))
return min_lr_ratio + (1.0 - min_lr_ratio) * cosine_decay
return LambdaLR(optimizer, lr_lambda)
def run_scheduler_demo():
w = torch.tensor([1.0], requires_grad=True)
base_lr = 0.01
optimizer = torch.optim.SGD([w], lr=base_lr)
total_steps = 100
warmup_steps = 20
scheduler = create_warmup_cosine_scheduler(
optimizer, warmup_steps=warmup_steps, total_steps=total_steps, min_lr_ratio=0.01
)
lrs = []
for step in range(total_steps + 1):
current_lr = scheduler.get_last_lr()[0]
lrs.append(current_lr)
optimizer.zero_grad()
loss = w ** 2
loss.backward()
optimizer.step()
scheduler.step()
print(f"Scheduler Demo: Start LR = {lrs[0]:.6f}, Peak LR = {lrs[20]:.6f}, Final LR = {lrs[-1]:.6f}")
return lrs
if __name__ == "__main__":
run_scheduler_demo()
examples/test_learning_rate_schedules_lib.py (1774 bytes)
import pytest
import torch
from examples.learning_rate_schedules_lib import create_warmup_cosine_scheduler
def test_warmup_cosine_scheduler_warmup_phase():
w = torch.tensor([1.0], requires_grad=True)
opt = torch.optim.SGD([w], lr=0.1)
sched = create_warmup_cosine_scheduler(opt, warmup_steps=10, total_steps=100, min_lr_ratio=0.01)
assert sched.get_last_lr()[0] == 0.0
for _ in range(5):
sched.step()
# At step 5 (halfway through warmup), lr should be 0.05
assert pytest.approx(sched.get_last_lr()[0], abs=1e-5) == 0.05
for _ in range(5):
sched.step()
# At step 10 (end of warmup), lr should be peak 0.1
assert pytest.approx(sched.get_last_lr()[0], abs=1e-5) == 0.1
def test_warmup_cosine_scheduler_final_decay():
w = torch.tensor([1.0], requires_grad=True)
opt = torch.optim.SGD([w], lr=0.1)
sched = create_warmup_cosine_scheduler(opt, warmup_steps=10, total_steps=100, min_lr_ratio=0.01)
for _ in range(100):
sched.step()
# At final step 100, lr should decay to min_lr_ratio * base_lr = 0.001
assert pytest.approx(sched.get_last_lr()[0], abs=1e-5) == 0.001
def test_scheduler_state_dict_persistence():
w1 = torch.tensor([1.0], requires_grad=True)
opt1 = torch.optim.SGD([w1], lr=0.1)
sched1 = create_warmup_cosine_scheduler(opt1, warmup_steps=20, total_steps=100)
for _ in range(50):
sched1.step()
lr_at_50 = sched1.get_last_lr()[0]
state = sched1.state_dict()
w2 = torch.tensor([1.0], requires_grad=True)
opt2 = torch.optim.SGD([w2], lr=0.1)
sched2 = create_warmup_cosine_scheduler(opt2, warmup_steps=20, total_steps=100)
sched2.load_state_dict(state)
assert pytest.approx(sched2.get_last_lr()[0], abs=1e-5) == lr_at_50
metadata.yml (436 bytes)
lesson_id: D207
day: 207
kind: lab
languages:
- python
setup_commands:
- 'pip install -r requirements/requirements.txt'
run_commands:
- 'python3 examples/learning_rate_schedules_lib.py'
test_commands:
- './tests/run_tests.sh'
cleanup_commands:
- 'find . -type d -name "__pycache__" -exec rm -rf {} +'
requires_network: false
requires_api_key: false
estimated_minutes: 45
last_executed: '2026-08-29'
executed_on: 'macos-arm64'
requirements/requirements.txt (27 bytes)
torch>=2.2.0
pytest>=8.0.0
starter/learning_rate_schedules_lib.py (441 bytes)
import torch
import math
from torch.optim.lr_scheduler import LambdaLR
from typing import List, Tuple, Dict, Any
def create_warmup_cosine_scheduler(optimizer: torch.optim.Optimizer,
warmup_steps: int,
total_steps: int,
min_lr_ratio: float = 0.01) -> LambdaLR:
# TODO: Implement Warmup + Cosine Decay LambdaLR scheduler
pass
starter/test_learning_rate_schedules_lib.py (1774 bytes)
import pytest
import torch
from examples.learning_rate_schedules_lib import create_warmup_cosine_scheduler
def test_warmup_cosine_scheduler_warmup_phase():
w = torch.tensor([1.0], requires_grad=True)
opt = torch.optim.SGD([w], lr=0.1)
sched = create_warmup_cosine_scheduler(opt, warmup_steps=10, total_steps=100, min_lr_ratio=0.01)
assert sched.get_last_lr()[0] == 0.0
for _ in range(5):
sched.step()
# At step 5 (halfway through warmup), lr should be 0.05
assert pytest.approx(sched.get_last_lr()[0], abs=1e-5) == 0.05
for _ in range(5):
sched.step()
# At step 10 (end of warmup), lr should be peak 0.1
assert pytest.approx(sched.get_last_lr()[0], abs=1e-5) == 0.1
def test_warmup_cosine_scheduler_final_decay():
w = torch.tensor([1.0], requires_grad=True)
opt = torch.optim.SGD([w], lr=0.1)
sched = create_warmup_cosine_scheduler(opt, warmup_steps=10, total_steps=100, min_lr_ratio=0.01)
for _ in range(100):
sched.step()
# At final step 100, lr should decay to min_lr_ratio * base_lr = 0.001
assert pytest.approx(sched.get_last_lr()[0], abs=1e-5) == 0.001
def test_scheduler_state_dict_persistence():
w1 = torch.tensor([1.0], requires_grad=True)
opt1 = torch.optim.SGD([w1], lr=0.1)
sched1 = create_warmup_cosine_scheduler(opt1, warmup_steps=20, total_steps=100)
for _ in range(50):
sched1.step()
lr_at_50 = sched1.get_last_lr()[0]
state = sched1.state_dict()
w2 = torch.tensor([1.0], requires_grad=True)
opt2 = torch.optim.SGD([w2], lr=0.1)
sched2 = create_warmup_cosine_scheduler(opt2, warmup_steps=20, total_steps=100)
sched2.load_state_dict(state)
assert pytest.approx(sched2.get_last_lr()[0], abs=1e-5) == lr_at_50
tests/run_tests.sh (227 bytes)
#!/usr/bin/env bash
set -euo pipefail
echo "========================================"
echo "Running Day 207 Lab Test Suite"
echo "========================================"
pytest tests/ -v
echo "All tests passed successfully."
tests/test_learning_rate_schedules_lib.py (1774 bytes)
import pytest
import torch
from examples.learning_rate_schedules_lib import create_warmup_cosine_scheduler
def test_warmup_cosine_scheduler_warmup_phase():
w = torch.tensor([1.0], requires_grad=True)
opt = torch.optim.SGD([w], lr=0.1)
sched = create_warmup_cosine_scheduler(opt, warmup_steps=10, total_steps=100, min_lr_ratio=0.01)
assert sched.get_last_lr()[0] == 0.0
for _ in range(5):
sched.step()
# At step 5 (halfway through warmup), lr should be 0.05
assert pytest.approx(sched.get_last_lr()[0], abs=1e-5) == 0.05
for _ in range(5):
sched.step()
# At step 10 (end of warmup), lr should be peak 0.1
assert pytest.approx(sched.get_last_lr()[0], abs=1e-5) == 0.1
def test_warmup_cosine_scheduler_final_decay():
w = torch.tensor([1.0], requires_grad=True)
opt = torch.optim.SGD([w], lr=0.1)
sched = create_warmup_cosine_scheduler(opt, warmup_steps=10, total_steps=100, min_lr_ratio=0.01)
for _ in range(100):
sched.step()
# At final step 100, lr should decay to min_lr_ratio * base_lr = 0.001
assert pytest.approx(sched.get_last_lr()[0], abs=1e-5) == 0.001
def test_scheduler_state_dict_persistence():
w1 = torch.tensor([1.0], requires_grad=True)
opt1 = torch.optim.SGD([w1], lr=0.1)
sched1 = create_warmup_cosine_scheduler(opt1, warmup_steps=20, total_steps=100)
for _ in range(50):
sched1.step()
lr_at_50 = sched1.get_last_lr()[0]
state = sched1.state_dict()
w2 = torch.tensor([1.0], requires_grad=True)
opt2 = torch.optim.SGD([w2], lr=0.1)
sched2 = create_warmup_cosine_scheduler(opt2, warmup_steps=20, total_steps=100)
sched2.load_state_dict(state)
assert pytest.approx(sched2.get_last_lr()[0], abs=1e-5) == lr_at_50
Troubleshooting
Troubleshooting: Day 207 - Learning Rate Schedules
Common Issues
- UserWarning: Detected call of
lr_scheduler.step()beforeoptimizer.step():- Cause: Calling
scheduler.step()beforeoptimizer.step(). - Fix: Ensure
scheduler.step()is called afteroptimizer.step().
- Cause: Calling
Security notes
Security & Privacy: Day 207 - Learning Rate Schedules
Security Guidance
- All schedule calculations execute in local RAM on CPU.