ml-models / Specialized learning / Online learning core

Online learning

Most models are trained once on a fixed dataset. Online learning updates a model one example (or one small batch) at a time as data arrives, which is what you need when data never stops coming, does not fit in memory, or changes under you. This page covers learning from a stream with scikit-learn's partial_fit, measuring an online learner by regret, adapting to concept drift, and the bandit problem, where the learner also chooses which data it gets to see.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. The online protocol
  5. Regret
  6. Concept drift
  7. partial_fit in scikit-learn
  8. Bandits
  9. Worked example
  10. A stream that drifts
  11. Regret on the stable part
  12. Three headlines: a Bernoulli bandit
  13. Implementation
  14. Tradeoffs
  15. Connections
  16. Read first
  17. Read next
  18. References

Problem

A fraud model scores card payments. You trained it on last quarter's data and it worked. Then fraudsters changed tactics, a new merchant category launched, and a holiday shifted spending. The model still answers every request, with the same confidence, and gets steadily worse. Retraining from scratch every night on all history is slow and still averages the old world with the new one.

A second version of the same problem: a news site must choose which of three headlines to show. It only learns whether a headline gets clicks by showing it. Show the current favourite every time and you never find out that another headline is better; show them all equally and you waste traffic on the bad ones. Here the learner's own choices decide which labels it collects.

Without online learning, a model is frozen at the moment it was fitted. Online methods keep it current in constant memory, at the cost of new failure modes: forgetting, sensitivity to the step size, and noisy updates.

Intuition

Think of a thermostat rather than a blueprint. It does not plan the year's heating in advance; it measures, nudges, and measures again. An online learner does the same: for each new example it first predicts, then sees the true answer, scores its mistake, and nudges its weights a little to make that mistake smaller. It never revisits old data. Its memory of the past lives only in its current weights.

How big each nudge is decides everything. Big nudges forget the past quickly, which is good when the world changes and bad when it does not (the model chases noise). Small nudges average over a long history, which is good when the world is stable and bad after a change (the model is stuck in the past). A step size that shrinks over time, the classic choice for a fixed problem, slowly turns the learner into a frozen model.

Bandits add a second dial: explore or exploit. You can only learn how good an option is by trying it. The good strategies try each option enough to be confident, then spend most of their time on the best one, and they keep a little doubt alive for options they have tried only rarely.

The online learning loop: predict, reveal, score, update, repeat Four boxes in a cycle. An example x arrives from the stream and the model predicts with its current weights. Then the true label y is revealed and the loss is scored; this score is the honest, prequential test error because the model had not seen the example. Then the weights take one gradient step. The loop repeats with the next example, and the old example is discarded. Under the loop, a note says the stream itself may drift: the rule mapping x to y can change partway through. stream gives xexample t predict ŷ = f(x; w)current weights reveal y, score ℓ(ŷ, y)test-then-train w ← w − η ∇ℓone step, drop x next example uses the updated w drift: the rule y = g(x) can change mid-stream, so yesterday's best w is not today's

Mechanics

The online protocol

At each round t = 1, 2, …, T the learner holds weights wt, receives xt, predicts, then suffers loss ℓt(wt). Online gradient descent updates

wt+1 = wt − ηt ∇ℓt(wt)

which is stochastic gradient descent (see optimizers) with each example used once. Scoring each example before training on it is called prequential evaluation; its running average is an unbiased estimate of the learner's current error, without a held-out set.

Regret

With no assumption that the data are i.i.d., or even random, the yardstick is the best single fixed model chosen with hindsight:

RegretT = Σt=1..T ℓt(wt) − minw Σt=1..T ℓt(w)

An algorithm "learns" if regret is sublinear, so RegretT/T → 0: on average it does as well as the best fixed model. For convex losses with bounded gradients, online gradient descent with ηt ∝ 1/√t has regret O(√T) (Zinkevich, 2003); for strongly convex losses, ηt ∝ 1/t gives O(log T). Regret against a fixed comparator is the wrong yardstick once the data drift; then the comparator is allowed to move, and decaying step sizes are no longer the right choice.

Concept drift

Drift means the joint distribution p(x, y) changes over time. Covariate shift changes p(x) only; concept drift changes p(y | x), the rule itself. It can be sudden, gradual, or recurring (seasons). The standard responses are:

  • Forget on purpose. A constant step size weights an example k steps old by roughly (1 − η·c)k, an exponential moving window. A sliding-window refit does the same with a hard edge.
  • Detect and reset. Monitor the prequential error; when it rises significantly (tests such as DDM, Page–Hinkley or ADWIN), retrain or raise the step size. A later page on monitoring and drift covers detection in production.
  • Ensembles over time. Keep models trained on different periods and weight them by recent accuracy.

partial_fit in scikit-learn

Estimators that can learn incrementally expose partial_fit(X, y). SGDClassifier.partial_fit runs one epoch of SGD over the batch it is given (it calls _partial_fit with max_iter=1), and needs classes= on the first call because a batch may not contain every label. It keeps the step counter t_ across calls: _partial_fit sets it to 1.0 only if it does not exist yet (lines 655–656), and each fit adds the number of samples it processed (line 776). The schedules read that counter inside the Cython loop (_sgd_fast.pyx.tp 457–488):

  • "optimal", the classifier's default: η = 1 / (α·(t0 + t − 1)), with t0 chosen by Bottou's heuristic so the first step is a sensible size.
  • "invscaling", the regressor's default: η = eta0 / tpower_t, with eta0=0.01 and power_t=0.25.
  • "constant": η = eta0, which keeps adapting forever.
  • "pa1"/"pa2": the passive-aggressive updates (Crammer et al., 2006), which step only as far as needed to fix the current mistake.

Because t_ only grows, a long-running partial_fit loop with a decaying schedule ends up taking tiny steps: it converges on a stationary stream and stops adapting on a drifting one.

ComponentShape/ParamsMeaningSource
SGDClassifierloss="hinge", alpha=0.0001, learning_rate="optimal", eta0=0.01, power_t=0.5Linear classifier trained by SGD; supports partial_fit._stochastic_gradient.py 958–1427
SGDRegressorloss="squared_error", learning_rate="invscaling", eta0=0.01, power_t=0.25Linear regressor trained by SGD._stochastic_gradient.py 1802–2122
partial_fit (classifier)X [n, d], y [n], classesOne SGD epoch over this batch; classes required on the first call.848–909
partial_fit (regressor)X [n, d], y [n]One SGD epoch over this batch.1540–1578
t_float, starts at 1.0Samples seen so far, kept across partial_fit calls; drives decaying schedules.1779
step-size schedulesoptimal, invscaling, constantHow ηt is computed per sample._sgd_fast.pyx.tp 457–488
update with L2 decayw.scale(1 − η α), then w += update·xWeight decay applied as a multiplicative shrink each step._sgd_fast.pyx.tp 516–542

Bandits

A K-armed bandit has arms with unknown mean rewards μ1, …, μK. Each round the learner pulls one arm and sees only that arm's reward. Its (pseudo-)regret after T rounds is

RegretT = Σt (μ* − μat) = Σa Δa · E[Na(T)]

where Δa = μ* − μa is arm a's gap and Na its pull count. Lai and Robbins (1985) showed any consistent policy must pull each bad arm Ω(log T) times, so logarithmic regret is the best possible. Three standard policies:

  • ε-greedy. With probability ε pull a random arm, otherwise the arm with the best empirical mean. Simple, but with fixed ε it keeps exploring forever, so its regret grows linearly at ε·(average gap) per round.
  • UCB1 (Auer, Cesa-Bianchi and Fischer, 2002). Pull each arm once, then the arm maximizing μ̂a + √(2 ln t / Na). The bonus is large for rarely tried arms ("optimism in the face of uncertainty") and shrinks as evidence accumulates. Regret O(Σa log T / Δa).
  • Thompson sampling (Thompson, 1933). Keep a posterior per arm, for 0/1 rewards a Beta(1 + successes, 1 + failures); each round draw one sample from each posterior and pull the arm with the largest draw. It explores in proportion to the probability that each arm is the best, and matches the Lai–Robbins bound for Bernoulli arms.

Contextual bandits (LinUCB, linear Thompson sampling) condition the choice on features xt; full reinforcement learning adds state that the actions change.

Worked example

Every number below comes from tested helpers in ml_models_docs.py with fixed seeds.

A stream that drifts

400 examples arrive one at a time: x uniform on [−1, 1], y = w·x + Gaussian noise (σ = 0.3), with w = 2.0 for the first 200 examples and w = -1.0 after. Three learners fit ŷ = w·x with squared loss, gradient 2(ŷ − y)x, starting from w = 0:

  • frozen: least squares on the first 100 examples (w = 2.015), then never updated;
  • 1/t step: ηt = 0.5/t;
  • constant step: η = 0.1.

The constant-step learner's first three rounds:

txyw beforeŷlossw after
1-0.095-0.8240.0000-0.00000.67970.0157
2-0.069-0.3870.0157-0.00110.14920.0210
30.016-0.1320.02100.00030.01760.0206

Round 1: ŷ = 0, so the step is −0.1 × 2 × (0 − (-0.824)) × (-0.095) = 0.0157. The early inputs happen to be near zero, so the weight barely moves: an online learner's progress depends on which examples arrive. Prequential mean squared error in windows of 50:

Examples1–5051–100101–150151–200201–250251–300301–350351–400
frozen0.0880.0870.0950.1123.1073.2272.3262.710
1/t step0.5440.2050.1900.1642.1121.9341.2431.353
constant step0.3340.0920.1020.1170.5960.1190.0770.106

Before the drift the frozen model is best (its error, about 0.10, is close to the noise variance 0.09), and the 1/t learner is still converging. After the drift the frozen model's error jumps to 3.11. The 1/t learner has shrunk its step to 0.0025 by round 200, so its weight crawls from 1.56 to only 1.07 by round 400. The constant-step learner pays some noise before the drift but has reached w = -0.85 within 50 rounds of it and ends at -0.97.

Prequential error before and after the drift for three learners Mean squared error in eight windows of 50 examples, plotted as three lines. A dashed vertical line marks the drift at example 200. Before it, all three lines are low, the frozen model lowest. After it, the frozen model's line jumps to about 3.1 and stays there; the 1/t learner jumps to about 2.1 and only falls to 1.4; the constant-step learner spikes to 0.60 in the first window after the drift and returns to about 0.11. 0 3.5 window MSE example index (1–400) drift at 200 frozen 1/t step constant step 50 100 150 200 250 300 350 400

Regret on the stable part

On the first 200 examples (no drift), online gradient descent with ηt = 0.5/√t against the best fixed weight chosen with hindsight:

TBest fixed wLearner's total lossBest total lossRegretRegret / T
102.2847.490.706.790.679
252.0678.992.006.990.280
502.05211.634.377.260.145
1002.01516.108.747.360.074
2002.00926.7519.117.650.038

Almost all of the regret, 6.79, is paid in the first 10 rounds while the weight climbs from 0; afterwards it grows by only 0.86 over 190 rounds. Regret per round falls from 0.679 to 0.038: sublinear regret, the online learner's guarantee. Squared loss here is strongly convex, so the growth is even slower than the √T worst case.

Three headlines: a Bernoulli bandit

Three arms with click rates 0.25, 0.45, 0.55, 1000 rounds, averaged over 50 seeds. Within a seed every policy sees the same reward draws. ε = 0.1; ε-greedy, UCB1 and greedy pull each arm once first, Thompson sampling starts from Beta(1, 1).

PolicyRegret at 10Regret at 50Regret at 100Regret at 250Regret at 500Regret at 1000Best-arm share
greedy1.25.210.024.047.093.051%
ε-greedy1.34.68.115.223.135.276%
UCB11.24.78.417.429.045.468%
Thompson1.24.57.312.216.220.284%

Greedy commits to whichever arm looked best after a few pulls; in many seeds that is the wrong one, and its regret grows almost linearly (93.0 after 1000 rounds). Thompson sampling has the least regret, 20.2, and the slope of its curve keeps falling. UCB1 is behind ε-greedy at this horizon (45.4 against 35.2): its √(2 ln t/N) bonus is conservative, so with gaps of 0.1 it keeps checking the second arm for a long time. Its guarantee is asymptotic. ε-greedy's regret never stops growing at the floor of ε × mean gap = 0.0133 per round, while UCB1's per-round regret keeps shrinking like (log t)/t, so over a long enough horizon UCB1 wins.

The first seven UCB1 rounds of seed 5 (bonus computed before the pull):

tPulls (N)SuccessesUCB score per armPullReward
10, 0, 00, 0, 0untried, untried, untriedarm 10
21, 0, 00, 0, 01.177, untried, untriedarm 20
31, 1, 00, 0, 01.482, 1.482, untriedarm 30
41, 1, 10, 0, 01.665, 1.665, 1.665arm 10
52, 1, 10, 0, 01.269, 1.794, 1.794arm 21
62, 2, 10, 1, 01.339, 1.839, 1.893arm 31
72, 2, 20, 1, 11.395, 1.895, 1.895arm 20

At t = 4 all three arms have one pull and no success, so their scores tie and the lowest index wins. At t = 5 arm 1 has two pulls, so its bonus is smaller; arm 2 wins the tie with arm 3 and pays 1. At t = 6 arm 2's mean is 0.5, yet arm 3 still wins (1.893 against 1.839): its single pull gives it a bigger bonus, which outweighs arm 2's success. Early on, UCB1 is driven by the bonus, not the means. On the same seed, Thompson sampling's 1000 pulls ended at 16, 18, 966 per arm, with posteriors Beta(4, 14), Beta(6, 14), Beta(535, 433). The best arm's posterior is sharp, and the two bad arms were tried just enough to be confidently ruled out.

Cumulative regret of four bandit policies over 1000 rounds Average cumulative regret over 50 seeds against rounds 1 to 1000. Greedy rises almost in a straight line to 93. UCB1 curves upward more slowly to 45. ε-greedy ends at 35 and keeps a constant slope. Thompson sampling flattens fastest and ends at 20. 0 100 0 1000 round cumulative regret greedy ε-greedy UCB1 Thompson

Implementation

Streaming a classifier with partial_fit, scoring each batch before training on it, and a constant step so it keeps adapting; then Thompson sampling for a Bernoulli bandit in plain Python:

import numpy as np, random
from sklearn.linear_model import SGDClassifier

clf = SGDClassifier(loss="log_loss", learning_rate="constant", eta0=0.01, alpha=1e-4)
classes = np.array([0, 1])
errors = []
for i, (X_batch, y_batch) in enumerate(stream_of_batches()):   # never holds the full data
    if i > 0:
        errors.append(1 - clf.score(X_batch, y_batch))          # prequential: test, then train
    clf.partial_fit(X_batch, y_batch, classes=classes if i == 0 else None)

def thompson(pull, n_arms, rounds):
    wins, losses = [0] * n_arms, [0] * n_arms
    for _ in range(rounds):
        draws = [random.betavariate(1 + wins[a], 1 + losses[a]) for a in range(n_arms)]
        a = max(range(n_arms), key=draws.__getitem__)
        if pull(a):          # 1 if the user clicked
            wins[a] += 1
        else:
            losses[a] += 1
    return wins, losses

Other scikit-learn estimators with partial_fit include MultinomialNB, MiniBatchKMeans, IncrementalPCA and MLPClassifier. Preprocessing must be online too: a StandardScaler fitted on the first day leaks a stale mean into every later prediction, so update it with its own partial_fit or use stateless features such as hashing. In PyTorch, online learning is just a training loop that calls optimizer.step() once per arriving batch and never loops over epochs (see the training loop).

Tradeoffs

  • Online versus periodic retraining. Online updates react within seconds and use constant memory, but a bad batch (a bot attack, a logging bug) goes straight into the weights. Nightly retraining on a window is easier to test, roll back and reproduce. Many systems retrain periodically and update only a few parameters (a bias term, a bandit) online.
  • Step size is the forgetting rate. Decaying schedules (sklearn's defaults) suit stationary data; under drift, use a constant step, a sliding window or a drift detector that resets.
  • Catastrophic forgetting. A neural net trained online on a changed distribution can lose old skills entirely; replay buffers or regularizing toward old weights reduce it.
  • Delayed and biased labels. Fraud labels arrive weeks later, and a model that blocks transactions never sees whether the blocked ones were fraud. Bandit-style exploration or holdout traffic is the only fix for the second.
  • Bandits versus A/B tests. A bandit earns more during the experiment by shifting traffic to the winner; an A/B test with fixed allocation gives cleaner, unbiased effect estimates. Logged bandit data needs propensity weighting to evaluate a new policy (see causal inference).
  • Policy choice. Thompson sampling is usually the best default for Bernoulli rewards and is easy to extend; UCB gives deterministic, analyzable behaviour; ε-greedy is the easiest to explain but never stops paying for exploration unless ε decays.
Why does the 1/t learner fail after the drift even though it converged nicely before?

Its step at round 200 is 0.5/200 = 0.0025, so each new example moves the weight very little. Effectively it weights all 200 old examples equally with the new ones.

An arm has been pulled 4 times with 1 success at round t = 20. What is its UCB1 score?

0.25 + √(2 ln 20 / 4) = 0.25 + √(1.498) ≈ 0.25 + 1.224 = 1.474.

Why must SGDClassifier.partial_fit be given classes on its first call?

The first batch may not contain every label, and the estimator has to allocate one weight vector per class (or one for binary) before it can train. Later batches are checked against those classes.

Connections

Read first

Read next

No page builds on this one yet.

References

  • Zinkevich. Online Convex Programming and Generalized Infinitesimal Gradient Ascent. ICML 2003.
  • Hazan. Introduction to Online Convex Optimization. 2016, revised 2019. arXiv:1909.05207
  • Bottou. Large-Scale Machine Learning with Stochastic Gradient Descent. COMPSTAT 2010.
  • Crammer, Dekel, Keshet, Shalev-Shwartz and Singer. Online Passive-Aggressive Algorithms. JMLR, 2006.
  • Gama, Žliobaitė, Bifet, Pechenizkiy and Bouchachia. A Survey on Concept Drift Adaptation. ACM Computing Surveys, 2014.
  • Lai and Robbins. Asymptotically Efficient Adaptive Allocation Rules. Advances in Applied Mathematics, 1985.
  • Auer, Cesa-Bianchi and Fischer. Finite-time Analysis of the Multiarmed Bandit Problem. Machine Learning, 2002.
  • Thompson. On the Likelihood that One Unknown Probability Exceeds Another in View of the Evidence of Two Samples. Biometrika, 1933.
  • Russo, Van Roy, Kazerouni, Osband and Wen. A Tutorial on Thompson Sampling. 2018. arXiv:1707.02038
  • Lattimore and Szepesvári. Bandit Algorithms. Cambridge University Press, 2020.
  • scikit-learn 1.9.1: SGDClassifier, SGDRegressor, step-size schedules.