Part XII: Edge, Embedded, Streaming, and Federated Sensor AI
Chapter 60: Streaming Inference and Online Learning

Concept-drift detection (ADWIN, DDM) and adaptation

"My accuracy did not fall off a cliff. The world walked out from under it, one quiet step at a time, and for three weeks I congratulated myself on numbers that no longer meant anything."

A Chastened AI Agent

Prerequisites

This section assumes you have an online model that updates as data arrives (Section 60.4) and that you can compute a running error signal on a stream, which is the prequential machinery of Section 60.6. The statistical backbone here is the concentration inequality: if you have met the idea that a sample mean stays close to its true mean with high probability (Chapter 4), you have what you need. Drift detection is a cousin of the change-point methods in Chapter 12; the difference, developed below, is that here the signal we watch is the model's own loss, not the raw sensor value.

The Big Picture

A deployed sensor model rests on a silent assumption: that the joint distribution generating today's data is the one it was trained on. Physical systems violate this constantly. A bearing wears, a patient's medication changes, a factory swaps a supplier, a phone gets a new user. When the mapping from sensor input to correct label shifts, a frozen model degrades, and it usually degrades quietly rather than crashing. This section gives you two things: a precise vocabulary for how streams drift, and two workhorse detectors, DDM and ADWIN, that raise an alarm the moment the ground has moved enough to matter. Detection is only half the job. The other half is what you do when the alarm fires, which is where windows, resets, and adaptive ensembles earn their keep.

What drift is, and which kind you are fighting

Formally, concept drift is any change over time in the joint distribution \(P_t(X, y)\) of sensor features \(X\) and target \(y\). It splits usefully by factoring the joint into \(P_t(X)\,P_t(y \mid X)\). A change in \(P_t(X)\) alone, the inputs move but the decision boundary does not, is virtual drift; it can often be absorbed by the online normalization of Section 60.3. A change in \(P_t(y \mid X)\), where the very meaning of a feature vector changes, is real drift, and it is the dangerous one because no amount of rescaling fixes a boundary that has genuinely moved. The what matters for the how: virtual drift is a preprocessing problem, real drift is a relearning problem.

Drift also has a temporal shape. Sudden drift is an abrupt switch, a sensor is recalibrated overnight and every reading jumps. Gradual drift interleaves old and new concepts with the new one winning over days. Incremental drift moves continuously through intermediate states, the slow creep of a fouling gas sensor. Recurring drift cycles back, the daily and seasonal regimes of a building's HVAC load (Chapter 39). The shape decides your defense: an abrupt switch wants a fast, forgetful detector; a recurring pattern wants a memory of past concepts you can reactivate rather than relearn from zero.

Key Insight

You almost never observe \(P_t(y \mid X)\) directly, because in production the true labels arrive late, sparsely, or never. So drift detectors do not watch the distribution. They watch a proxy: the model's own stream of prediction errors. A well-calibrated model on stationary data produces a stable error rate; when \(P_t(y \mid X)\) shifts, the error rate rises. Detecting drift therefore reduces to detecting a change in the mean of a binary loss sequence. That reframing is what lets a few kilobytes of running statistics stand in for an unknowable distribution.

DDM: watch the error rate for a statistically significant rise

The Drift Detection Method (Gama et al., 2004) is the canonical baseline and the easiest to reason about. Treat each prediction as a Bernoulli trial that is wrong with probability \(p_t\). After \(n\) samples the observed error rate is \(p_t\) with standard deviation \(s_t = \sqrt{p_t(1 - p_t)/n}\). DDM tracks the running minimum of \(p_t + s_t\), calling that best-ever operating point \((p_{\min}, s_{\min})\), and compares the current \(p_t + s_t\) against it. Two thresholds fire:

\[ p_t + s_t \ge p_{\min} + 2\,s_{\min} \quad\text{(warning)}, \qquad p_t + s_t \ge p_{\min} + 3\,s_{\min} \quad\text{(drift)}. \]

The two-sigma warning level says something may be starting; the three-sigma drift level says it has arrived. The gap between them is the method's cleverest feature: samples seen between a warning and a confirmed drift are exactly the fresh, post-change examples you want in your rebuilt model, so DDM tells you not only when to retrain but which recent buffer to retrain on. The why of the sigma bands is the normal approximation to the binomial: under a stable error rate, a three-sigma excursion is a genuinely rare event, so an alarm is unlikely to be noise. The cost is DDM's blind spot: it assumes errors accumulate fast enough to move the running mean, so it detects sudden and gradual drift well but can lag badly on very slow incremental drift, where the mean creeps up under the radar.

Field Story: a continuous glucose monitor after a sensor change

A wearable that estimates blood glucose from an electrochemical patch (Chapter 33) runs an online calibration model against occasional fingerstick reference readings, which serve as ground-truth labels. Every time the user replaces the disposable sensor, the electrode's sensitivity jumps: textbook sudden, real drift, because the same current now means a different glucose level. DDM sees the calibration error rate cross its warning band within a handful of references and confirms drift shortly after. The system does not wait for a clinician; it flags the interval since the warning as post-change data and refits the calibration curve on it, restoring agreement with the fingersticks before the user notices a bad reading. Without a detector the stale model would have under-reported a dangerous low for hours.

ADWIN: an adaptive window that forgets exactly what it should

ADWIN (ADaptive WINdowing, Bifet and Gavaldà, 2007) attacks the same problem from a different angle and comes with a guarantee DDM lacks. It keeps a variable-length window \(W\) of the most recent values of whatever stream you feed it, typically the \(0/1\) loss. The rule is simple to state: as long as the window looks stationary, let it grow; the instant it does not, drop the stale prefix. Concretely, ADWIN considers every way to split \(W\) into an older subwindow \(W_0\) and a newer one \(W_1\). If the means \(\hat\mu_0\) and \(\hat\mu_1\) differ by more than a threshold \(\epsilon_{\text{cut}}\), it declares drift and discards \(W_0\):

\[ |\hat\mu_0 - \hat\mu_1| \ge \epsilon_{\text{cut}}, \qquad \epsilon_{\text{cut}} = \sqrt{\frac{1}{2m}\,\ln\frac{4|W|}{\delta}}, \]

where \(m\) is the harmonic mean of the two subwindow lengths and \(\delta\) is the confidence parameter. The bound comes from Hoeffding's inequality, which is why ADWIN can promise that a false alarm happens with probability at most \(\delta\) and that a real change of a given size is detected within a bounded delay. The how in practice is an exponential-histogram data structure that compresses the window so the whole thing runs in \(O(\log |W|)\) memory and amortized time, small enough for a microcontroller. The payoff over DDM is that the window length is chosen by the data, not a hyperparameter: on a long stationary stretch the window grows and estimates get tighter; at a change it snaps short and forgets fast. That single property makes ADWIN the drift-detection engine that adaptive learners like Adaptive Random Forest wrap around each of their components.

The Right Tool

The exponential-histogram bookkeeping behind ADWIN, the buckets, the merges, the per-split Hoeffding test, is roughly 200 lines of fiddly, off-by-one-prone code that is easy to get subtly wrong. River ships it, tested and \(O(\log n)\), behind a two-method interface.

from river import drift

adwin = drift.ADWIN(delta=0.002)      # 1 detector, fully configured
for loss in loss_stream:              # loss = 0 if correct else 1
    adwin.update(loss)
    if adwin.drift_detected:
        model = model.clone()         # reset the learner on confirmed drift
        print("drift: reset model")
Listing 60.5.1. A production-grade ADWIN detector wired to a model reset in eight lines. Hand-rolling the exponential histogram and its confidence bound is a few hundred lines and a week of edge cases; River collapses it to update() and a drift_detected flag. Swapping in drift.binary.DDM() changes only the constructor.

Listing 60.5.1 hides the statistics, not the decision: what you do when drift_detected turns true is the design choice the library deliberately leaves to you, and it is the subject of the next paragraph.

Adapting once the alarm fires

A detector is a trigger, not a cure. The crudest response is a hard reset: throw away the model and relearn from the post-drift buffer. It is correct for sudden real drift and disastrous for a false alarm, because you have just discarded a good model. A gentler family is windowed retraining, where you keep only the most recent window of data (Section 60.1) and refit continuously, letting the window length, or ADWIN's own adaptive window, control how fast old concepts fade. The most robust production answer is an adaptive ensemble: run several models, attach a drift detector to each, and when one member's detector fires, reset or down-weight only that member while the others carry the prediction. This is exactly how the Adaptive Random Forest (Gomes et al., 2017) survives drift with no accuracy cliff, and it dovetails with the continual-learning methods of Chapter 62 when the model is a network that must not catastrophically forget.

Two engineering realities temper all of this. First, labels arrive late. Every scheme above assumes you learn whether a prediction was right; when labels come hours later or only for a sampled fraction, your detector runs on delayed loss and your alarm is delayed with it, which you must budget for in the safety case. Second, every detector trades false alarms against detection delay, governed by \(\delta\) for ADWIN or the sigma multipliers for DDM. Loosen the threshold and you react fast but thrash on noise, retraining the model into instability; tighten it and you are calm but slow to notice a real shift. Choosing that operating point is not a default you accept; it is a decision you make against the cost of a stale prediction versus the cost of an unnecessary retrain, and it belongs in the same leakage-safe evaluation discipline (Chapter 5) as every other hyperparameter.

Exercise

Generate a synthetic binary-loss stream: 2000 samples at a \(0.10\) error rate, then 2000 at \(0.30\) (a sudden drift), then 2000 that ramp linearly from \(0.10\) to \(0.30\) (incremental drift). Feed the stream to both river.drift.ADWIN and river.drift.binary.DDM and record the sample index at which each first reports drift for the two change types. Which detector fires sooner on the sudden change? Which lags more on the incremental ramp, and does that match the blind spot described for DDM? Then rerun ADWIN with delta at \(0.05\) and \(0.0001\) and report how detection delay and the number of false alarms in the first stationary block trade off.

Self-Check

1. Why do practical drift detectors watch the model's error stream rather than the input distribution \(P_t(X)\) directly, and which factor of \(P_t(X,y)\) does a rising error rate actually reveal?

2. DDM uses a two-sigma warning level below its three-sigma drift level. What concrete use do the samples collected between the warning and the confirmation serve?

3. ADWIN's cut threshold \(\epsilon_{\text{cut}}\) comes from Hoeffding's inequality. What guarantee does that give you about false alarms, and what does the parameter \(\delta\) control?

What's Next

In Section 60.6, we make the error signal these detectors depend on rigorous. Drift detection is only as trustworthy as the running loss you feed it, so we turn to prequential (test-then-train) evaluation: how to compute honest streaming accuracy, precision, and recall with fading factors, so that both your dashboards and your drift alarms rest on metrics that reflect the model's behavior right now rather than a stale lifetime average.