Skip to main content

Efficient Polling-Based Learning Rate Optimization for Neural Networks

Choose the learning rate by measuring it on the batch, at close to the cost of plain SGD.

PyPI Python License

pip install efficient-polling-lr-scheduler

🇧🇷 Versão em português · How it works · Results · Reproducing the experiments

The Polling Method of Tan et al. picks the learning rate by trying several candidates on every batch and keeping the one that most improves batch accuracy. It works, and it multiplies the cost of training by the number of candidates. This repository replicates it and adds two extensions that keep the selection and drop most of the cost:

  • Efficient Polling polls on demand. An exponential backoff stretches the gap between polls while the choice is stable, and a two-tier divergence guard protects the steps in between.
  • Efficient Relative Polling drops the fixed grid of candidates. The user picks one learning rate and one multiplier, and each poll tries the rate in use and its two neighbours.

Both ship as a PyTorch package. They are benchmarked against eight comparison methods and two ablations on five datasets, five seeds each.

Results at a glance

Test accuracy, mean over five seeds of 150 epochs, with the same hyperparameters on every dataset. The last row is the best of the other comparison methods on each dataset: fixed-rate SGD, Adam, the three schedulers, SPS and Armijo.

Method CIFAR-10 CIFAR-100 Fashion-MNIST MNIST Covertype Optimizer steps, × SGD
Polling (base paper) 83.83% 52.99% 91.83% 98.14% 75.15% 6.00
Efficient Polling (ours) 83.76% 25.07% 91.84% 98.82% 75.79% 1.18–2.23
Efficient Relative Polling (ours) 84.11% 53.05% 91.70% 98.59% 75.41% 1.04–1.21
Best comparison method 84.09%, Armijo 53.03%, plateau 92.70%, Adam 99.45%, Adam 75.64%, cosine 1.00
  • Efficient Relative Polling matches the base method everywhere at a fraction of its cost. It is above base Polling on four datasets and within one standard deviation of it on Fashion-MNIST, with at most 1.21× the optimizer steps of plain SGD where base Polling takes 6×. It is the best method on CIFAR-10 and CIFAR-100.
  • No method wins everywhere. Adam leads on MNIST and Fashion-MNIST, where the polling methods trail it by 0.6 to 1.3 points.
  • Efficient Polling can stall at its smallest candidate. When batch accuracy at chance rarely tells the candidates apart, the tie-break keeps choosing 1e-5 and the backoff reads the repetition as a stable choice. On CIFAR-100 every seed lost epochs this way, and two never trained.

Results has the full tables and figures, the trigger ablation, the initial-rate robustness runs and the mechanism behind the stall.

Quickstart

Polling needs to re-evaluate the model to score a candidate step, so instead of the bare optimizer.step() you pass a closure that returns (loss, score): the same contract as torch.optim.LBFGS, plus the score to maximize. make_closure builds it for you:

import torch
from efficient_polling_lr_scheduler import EfficientPollingSGD, make_closure

model = MyModel().to(device)
loss_fn = torch.nn.CrossEntropyLoss()

# Candidate LRs default to {1e-5, 1e-4, 1e-3, 1e-2, 1e-1} around lr.
optimizer = EfficientPollingSGD(model, lr=1e-3)

for inputs, targets in train_loader:
    inputs, targets = inputs.to(device), targets.to(device)
    info = optimizer.step(make_closure(model, loss_fn, inputs, targets))
    # info.lr, info.loss, info.polled, info.spike, info.rolled_back, ...

No learning-rate schedule, no warmup, no tuning: the learning rate is measured. PollingSGD gives the base method, which polls every batch. Efficient Relative Polling takes one rate instead of a grid, and either extension can wrap an optimizer you already have:

from efficient_polling_lr_scheduler import EfficientPollingOptimizer, EfficientRelativePollingSGD

# one rate and one multiplier instead of a fixed grid
optimizer = EfficientRelativePollingSGD(model, lr=1e-3, multiplier=10.0, lr_max=1e-1)

# or Efficient Polling around any optimizer
optimizer = EfficientPollingOptimizer(
    torch.optim.SGD(model.parameters(), lr=1e-3, momentum=0.9),
    candidate_lrs=(1e-5, 1e-4, 1e-3, 1e-2, 1e-1),
    module=model,          # so BatchNorm buffers are restored between trials
    max_poll_interval=64,  # backoff cap; 0 polls every batch
)

The package also ships the two comparison baselines that derive the step size from the current batch, the stochastic Polyak step-size (SPS) and Armijo backtracking line search, and optional training helpers that accept a plain optimizer and a torch.optim.lr_scheduler too, so every method goes through the same loop:

from efficient_polling_lr_scheduler import SPSSGD, ArmijoSGD, fit

optimizer = SPSSGD(model, lr=1e-3, max_lr=0.1)  # or ArmijoSGD(model, lr=1e-3, lr_max=0.1)

history = fit(model, train_loader, val_loader, optimizer, loss_fn, epochs=150)
print(history.best_val_acc, sum(history.polls), sum(history.optimizer_steps))

API

Object Role
EfficientPollingSGD / EfficientPollingOptimizer Efficient Polling: polls on demand, with the divergence guard
PollingSGD / PollingOptimizer base method: polls every batch
EfficientRelativePollingSGD / EfficientRelativePollingOptimizer Efficient Relative Polling (experimental): three candidates around the rate in use, uncapped TCP-style backoff, restarts from the best point
EfficientRelativeEpochPolling the same at epoch granularity, driven by fit(..., epoch_polling=...) over a plain optimizer
SPSSGD / SPSOptimizer comparison baseline: stochastic Polyak step-size
ArmijoSGD / ArmijoOptimizer comparison baseline: stochastic Armijo backtracking line search
TRIGGERS the three polling triggers used in the ablation: "backoff" (default), "fixed", "random"
make_closure, accuracy, negative_loss batch closure and selection criteria (accuracy, or loss)
CRITERIA what a poll ranks its trials by, the criterion of every polling optimizer: "score" (the closure's score, batch accuracy) or "loss"; Efficient Relative Polling defaults to "loss" since 2.1.0, the others to "score"
StepInfo, EpochStats, History telemetry: chosen LR, polls, spikes, rollbacks, optimizer steps
fit, train_epoch, evaluate optional training loop helpers, accepting a torch.optim.lr_scheduler for the plain-optimizer comparison methods
StateSnapshot exact save/restore of parameters, buffers and optimizer state

Notes. Despite the distribution name, these are not torch.optim.lr_scheduler.LRScheduler subclasses: they wrap the optimizer and are driven entirely through optimizer.step(closure), so there is no separate scheduler.step() to call after it. Candidate learning rates are absolute and applied to every parameter group, overriding per-group learning rates. Pass module= (or the model itself as the first argument) whenever the forward pass mutates buffers, so trials cannot leak BatchNorm statistics. The closure must not call backward() or zero_grad(): the optimizer owns both.

Documentation

  • How it works: the three methods, their rules and hyperparameters, and the cost model.
  • Results: every table and figure on the five datasets, the trigger ablation and the initial-rate robustness runs.
  • Reproducing the experiments: setup, datasets, the command line and the notebook, and how the repository is laid out.

Citation

If you use this work, please cite:

@misc{souzasilva_efficient_polling_lr_scheduler,
  title  = {Efficient Polling-Based Learning Rate Optimization for Neural Networks},
  author = {de Souza Silva, Jos{\'e} R. and B. A. da Silva, Luiz Henrique and B. de Souza, Caio B. and Balieiro, Andson M.},
  year   = {2026},
  note   = {Centro de Inform{\'a}tica (CIn), Universidade Federal de Pernambuco (UFPE)},
  url    = {https://github.com/luiz-linkezio/Efficient-Polling-Based-Learning-Rate-Optimization-for-Neural-Networks}
}

The base Polling method is from Tan, Choong and Lau, Expediting Convergence via Polling Optimisation for Gradient Descent in Neural Networks (2025). To cite the software specifically, add note = {Python package \texttt{efficient-polling-lr-scheduler}} or reference the PyPI project.

🧑‍💻 Authors


Luiz Henrique
Developer
LinkedIn
Portfolio

José Ronaldo
Developer
LinkedIn
Portfolio

Universidade Federal de Pernambuco, Recife, Brazil. The paper additionally credits Caio B. B. de Souza (UPE) and Andson M. Balieiro (CIn/UFPE); see Citation.

License

MIT, see the LICENSE file in this repository.

Release files for efficient-polling-lr-scheduler 2.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for efficient-polling-lr-scheduler 2.1.0
File Size Uploaded
efficient_polling_lr_scheduler-2.1.0.tar.gz 98.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for efficient-polling-lr-scheduler 2.1.0
File Interpreter ABI Platform
efficient_polling_lr_scheduler-2.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 134.4 kB

Release files / efficient_polling_lr_scheduler-2.1.0.tar.gz

Download URL efficient_polling_lr_scheduler-2.1.0.tar.gz
Size 98.3 kB
Tags Source
SHA-256 checksum
How to use checksums
0beade8286de6a04503a32275d191ea79e82e952dabd5ed7f0a402a05f7cfb16
BLAKE2b-256 checksum
How to use checksums
bc23ee5a8852bdb731df0dd837e721e4b4f40cc94f7eb2378dc5d892187efd1f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / efficient_polling_lr_scheduler-2.1.0-py3-none-any.whl

Download URL efficient_polling_lr_scheduler-2.1.0-py3-none-any.whl
Size 36.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a22f6658ab3cab9b27b84a3fa3a69bd4d337923d48128bb320ed164c5a29e1bf
BLAKE2b-256 checksum
How to use checksums
198c58cc20048bbb0ef4b7ab223d154d3d5331fdd8b981c847c86f0e5cba46ef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.1.0 This release

2 release files

2.0.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page