Learning Rate Finder

See what an LR range test curve looks like before you run one. Set a range and a step count, and the simulated curve unfolds on a log scale with the steepest descent point marked.

The curve is generated, not measured. Use it to learn how to read a real test, then run the sweep on your own model with the snippet further down.

LR Range Test Configuration

0.90
2.0e-6
Optimal LR (Steepest)
0.7671
Minimum Loss
1.0e-6
Suggested LR
0.0155
Divergence LR

Optimal is the steepest descent point, where the smoothed curve falls fastest. Suggested sits one decade below the minimum loss, which is a cautious habit rather than a published rule. These come from the simulated curve, not from your model.

Loss vs Learning Rate (Log Scale)

Raw Loss Smoothed Loss Optimal LR Suggested LR

Loss Gradient (Steepness)

Schedule Comparison

One-Cycle Policy Cosine Annealing + Warm Restarts Step Decay

How this page computes its curve

Read this first, because it changes how you should use the numbers above. The curve on this page is generated, not measured. It comes from a closed-form function with a fixed base loss per loss type, plus a small random noise term. It never sees your model, your data, or your optimizer.

So treat the four readouts as a worked illustration of what a range test looks like and how to read one. They are not a result for your network. To get a number that applies to your training run, copy the snippet below and run the real sweep. The shape you see here is what you should expect to see there.

import torch

# Real LR range test. Swap in your own model, optimizer and loader.
lo, hi, steps = 1e-7, 1.0, 200
mult = (hi / lo) ** (1 / steps)
lr = lo
opt = torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9)
lrs, losses, ema, beta = [], [], 0.0, 0.9

for i, (x, y) in enumerate(loader):
    if i >= steps:
        break
    for g in opt.param_groups:
        g['lr'] = lr
    opt.zero_grad()
    loss = criterion(model(x), y)
    loss.backward()
    opt.step()
    ema = beta * ema + (1 - beta) * loss.item()
    losses.append(ema / (1 - beta ** (i + 1)))   # bias-corrected
    lrs.append(lr)
    lr *= mult

# Take the rate where the smoothed curve falls fastest,
# not where it bottoms out. The minimum is usually already unstable.

What a learning rate range test does

Leslie Smith introduced the test in Cyclical Learning Rates for Training Neural Networks (2015). You run one short training pass, raising the learning rate from very small to very large, and record the loss at every step. One run, one curve, no grid search.

That curve has three parts. While the rate is too small the loss barely moves, because the updates are tiny and progress is impractically slow. Then the loss drops steeply. That middle band is large enough to learn and small enough to stay stable, and it's the part you care about.

Past it, the loss climbs or swings wildly. Updates now overshoot the minima and the run diverges. Pick your rate from the steepest part of the descent, a little below the point of fastest drop, so you keep the speed and leave yourself a margin.

Why the learning rate matters

It sets the step size of every gradient update. Too small and training crawls or settles somewhere poor. Too large and it diverges. Width, depth and regularisation change what a model can represent, but the learning rate decides whether training works at all.

The right value moves with the loss surface, the batch size, the architecture, the optimizer, and how far into training you are. What suits a ResNet-50 on ImageNet won't suit a Transformer on text. That's the argument for measuring it rather than reaching for a default, since the test probes your own model on your own data.

Batch size is the coupling people miss. Goyal et al. showed in Accurate, Large Minibatch SGD that multiplying the batch by k and the learning rate by that same k holds ImageNet accuracy out to very large batches, given a short warmup.

Scaling by the square root of k is the more cautious habit. Either way, a rate you measured at one batch size doesn't carry over unchanged to another.

Smoothing a noisy loss curve

Raw training loss jumps around because every mini-batch holds different samples. That noise hides the trend you're trying to read. An exponential moving average fixes it by weighting recent steps more heavily than old ones.

Beta sets the strength. At 0.0 you see the raw loss, and at 0.99 the curve is heavily damped. Somewhere between 0.8 and 0.95 usually reads well, though that band is a working habit rather than a published result.

Too little smoothing leaves the curve unreadable. Too much introduces lag, which drags the apparent best point toward rates that are actually too high. Dividing by 1 minus beta to the power t removes the bias the average carries over its first few steps.

One-cycle against warm restarts

Once you have a rate, you need a schedule. Two of them hold up well.

One-cycle comes from Smith's A Disciplined Approach to Neural Network Hyper-Parameters (2018). The rate climbs to a maximum, then anneals back toward zero over the rest of the run. Smith puts the lower bound a factor of 10 or 20 below that maximum when only one cycle is used. Momentum runs the opposite way across the same cycle, roughly 0.95 down to 0.85 and back up.

Paired with a large maximum rate this gives what Smith named super-convergence, training networks an order of magnitude faster than standard schedules.

Warm restarts come from SGDR (Loshchilov and Hutter, 2016). Cosine annealing walks the rate down to near zero, then snaps it back up and repeats, which gives the optimizer a chance to leave a basin it has settled into. Each period either stays fixed or grows by a factor after every cycle.

The paper reports comparable results in 2 to 4 times fewer epochs than the schedule it replaced. Checkpoint at the end of each cycle and you get snapshot ensembling nearly for free.

Training one model, start with one-cycle. Building an ensemble, warm restarts earn their keep.

Other schedules worth knowing

Step decay cuts the rate by a fixed factor at set epochs. PyTorch's own ImageNet example still ships StepLR(optimizer, step_size=30, gamma=0.1), a tenfold cut at epochs 30, 60 and 90. It's old, and it still competes.

Exponential decay multiplies by a constant below 1 every step or epoch. Polynomial decay follows a curve you shape with a power parameter. ReduceLROnPlateau watches a validation metric and only cuts when progress stalls, so it reacts to the run rather than to a calendar.

Adam, AdamW and LAMB adapt a rate per parameter, which softens how much the global value matters. It doesn't remove it. The paper to read is Loshchilov and Hutter's Decoupled Weight Decay Regularization (ICLR 2019). Separating weight decay from the gradient update improved how well Adam generalised, enough to compete with SGD plus momentum, which it had usually lost to.

Running the test on your own model

Common questions

What is a learning rate range test?

You raise the learning rate from very small to very large across one training epoch, recording the loss at each step. Plotting loss vs learning rate on a log scale reveals the optimal learning rate at the steepest descent point. This gives you a data-driven starting point instead of guessing.

How do I pick the optimal learning rate from the curve?

Look for the point where the smoothed loss curve is decreasing most steeply. That point is found by taking the gradient of the loss curve and picking the learning rate with the most negative slope. fastai's own guidance is blunter. Don't just take the minimum, pick a value in the middle of the sharpest downward slope.

What is the difference between warm restarts and one-cycle policy?

Warm restarts periodically reset the learning rate back to a high value and anneal it down again, creating multiple training cycles. The one-cycle policy uses a single cycle: warm up to a maximum LR, then anneal down to near zero. One-cycle typically achieves super-convergence with fewer epochs, while warm restarts are better for snapshot ensembling.

Why should I use a log scale for the learning rate axis?

Learning rates span several orders of magnitude (e.g., 1e-7 to 1). On a linear scale, almost everything would be compressed near zero. A log scale spaces these values evenly, so you can clearly see the flat region, optimal region, and divergence region of the loss curve.

How many steps should I use for an LR range test?

Typically 100 to 300 steps is sufficient. You need enough steps to cover 5-6 orders of magnitude and see the whole curve, but too many steps can cause weights to diverge so badly that the early part of the curve becomes meaningless. Running through 10-25% of one epoch usually works well.

Related tools

About the author

Michael Lip builds open-source ML tools and developer utilities at zovo.one. ml0x is part of the Zovo Tools network, a collection of free, privacy-first tools for developers and data scientists. No tracking, no accounts required, no data leaves your browser.

Last updated: May 25, 2026