Part XII: Edge, Embedded, Streaming, and Federated Sensor AI
Chapter 62: On-Device Continual Learning

Personalization on-device

"The factory shipped me one model for a million wrists. Yours is the only one I answer to now, and I never sent a single heartbeat back to prove it."

A Personal AI Agent

The Big Picture

A model leaves the factory as an average of everyone. But no one is the average. Your gait, your resting heart rate, the exact way you flick your wrist to wake the watch, the acoustic signature of your apartment: each is a personal distribution that the shipped model only approximates. Personalization is the on-device adaptation of a shared base model to one user, one body, one machine, using that individual's own unlabelled or lightly-labelled stream and never sending the raw signal off the device. Unlike the new-class problem of Section 62.4, the label set usually stays fixed; what shifts is the conditional that maps signal to label for this person. This section explains why per-user shift is a covariate-and-calibration problem, how to personalize with a handful of trainable parameters while the backbone stays frozen, and how to do it without the model collapsing onto one user's bad day.

This section assumes you are comfortable with the frozen-backbone, train-the-head split of Section 62.4 and the forgetting dynamics of Section 62.2. Personalization reuses that machinery but points it inward: instead of adding a class the base never saw, it re-fits the decision surface to a person the base saw only as one anonymous sample among many. The privacy argument that makes on-device the only acceptable option connects directly to Chapter 34, and the fleet-wide version, where personal updates are aggregated without sharing data, is Chapter 64.

Personalization is a per-user distribution shift

State it formally so the fix is obvious. The base model is trained on a population distribution \(p_{\text{pop}}(x, y)\). User \(u\) generates samples from \(p_u(x, y)\), and the two differ. Decompose the joint: \(p_u(x,y) = p_u(y \mid x)\, p_u(x)\). Two flavours of gap appear. Covariate shift moves \(p_u(x)\): a runner's accelerometer occupies a different region of feature space than a desk worker's, though the activity labels mean the same thing. Concept shift moves \(p_u(y \mid x)\): the same photoplethysmography waveform amplitude means "resting" for an athlete and "elevated" for a sedentary user, so the label boundary itself relocates. The population model minimises expected loss over \(p_{\text{pop}}\), which is exactly the wrong average when one user sits far in the tail. Personalization is the act of re-estimating the part of the model that carries this per-user gap, using data drawn from \(p_u\) that only the device ever sees.

Key Insight

You almost never need to move the whole model to close a personal gap; you need to move the small subspace where users differ. A backbone trained on the population already extracts good general features, so personalization becomes a low-dimensional correction: a per-user shift and scale on the features, a re-centred decision threshold, or a tiny adapter. This is what makes it fit on-device. Fitting \(10^2\) to \(10^4\) personal parameters against a few minutes of a user's data is tractable in the energy budget of Section 62.5; fine-tuning \(10^7\) backbone weights is neither affordable nor statistically justified from one person's stream.

Three mechanisms, cheapest first

Calibration and threshold shift. The lightest personalization touches no weights at all. It re-centres feature statistics or moves the operating threshold to match the user's base rate and signal amplitude. Recomputing the running mean and variance that a normalization layer applies, using this user's samples instead of the population's, is often enough to absorb pure covariate shift; it is unsupervised, needs no labels, and is the on-device sibling of the test-time adaptation of Chapter 66. Pairing a personalized threshold with the calibrated probabilities and conformal sets of Chapter 18 keeps error rates honest per user rather than only on average.

Adapter or head fine-tuning. When the boundary itself has moved (concept shift), re-fit a small trainable module: a personalized classifier head, or a bottleneck adapter of width \(r\) inserted after the frozen features. With embedding dimension \(d\), a low-rank adapter costs \(2dr\) parameters against the \(d^2\) of a full layer; for \(d=256, r=8\) that is 4096 numbers, a few kilobytes, trainable from minutes of labelled interaction. The backbone stays frozen, so the population knowledge is preserved by construction, the same firewall against forgetting used in Section 62.4.

Regularized personal fine-tuning. The heaviest option unfreezes a few upper layers but anchors them to the shipped weights with a proximal penalty, minimizing

$$ \mathcal{L}_u(\theta) = \frac{1}{|D_u|}\sum_{(x,y)\in D_u} \ell(f_\theta(x), y) \;+\; \frac{\lambda}{2}\lVert \theta - \theta_0 \rVert_2^2, $$

where \(\theta_0\) is the base model and \(\lambda\) sets how far the personal model may drift. Large \(\lambda\) keeps the user close to the robust population prior when their data is scarce or noisy; small \(\lambda\) lets a data-rich user specialise. This single knob is the practical heart of personalization: it trades personal fit against the safety of the shared model, and choosing it badly is how personalization goes wrong.

Real-World Application: a wrist wearable that learns one runner's cadence

A running watch ships with a population activity classifier (Chapter 26) that keeps confusing one user's high-cadence shuffle-jog with brisk walking, because her stride is short and her arm swing is unusually damped. Nothing is wrong with the sensor; her \(p_u(x)\) simply sits between the population's walk and run clusters. The watch runs a frozen inertial encoder and a per-user adapter. Over three runs it collects windows the user confirms as "run" with a single tap, fits the 4 KB adapter with the proximal penalty above, and stores it in the personal profile. Cadence detection sharpens for her while the shipped model, and every other wearer's, stays untouched. No accelerometer trace ever leaves the wrist, satisfying the biometric-data constraints of Chapter 34.

What breaks, and how to hold the line

Personalization has a signature failure mode: collapse onto a bad sample. A user tags noisy data, wears the device wrong for a day, or feeds the online learner a burst of one class, and the adapter over-fits to that transient. Because on-device labels are scarce and often self-generated, there is no held-out set to catch it. Three defences carry most of the weight. First, keep an escape hatch: retain the frozen base and blend, so a prediction is a gated mixture \(g\cdot f_{\text{personal}} + (1-g)\cdot f_{\text{base}}\) with the gate \(g\) rising only as personal confidence and data volume rise; if the personal model degrades you fall back to the shipped one. Second, anchor with the prior: the \(\lambda\) penalty above, sized to the amount of personal data, prevents a five-sample day from rewriting the model. Third, gate the trigger: only accept personal updates when an on-device drift or confidence check says the population model is genuinely mismatched, not merely noisy. These are the same self-update guardrails formalised in Section 62.7; personalization is the most common reason a deployed model ever asks to change itself.

The snippet below implements the cheapest and most robust mechanism together: an unsupervised per-user feature recalibration plus a confidence-gated blend against the frozen base. It runs in a few lines of NumPy and is faithful to what a microcontroller does in fixed point.

import numpy as np

class PersonalHead:
    """Per-user feature recalibration + confidence-gated blend with a frozen base."""
    def __init__(self, base_mu, base_sigma, W_base, b_base, lam=8.0):
        self.base_mu, self.base_sigma = base_mu, base_sigma      # population stats
        self.mu, self.sigma = base_mu.copy(), base_sigma.copy()  # personal, start = base
        self.n = 0.0                                             # personal samples seen
        self.W, self.b = W_base, b_base                          # shared linear head
        self.lam = lam                                           # prior strength

    def observe(self, z):
        """Unsupervised: update this user's running feature mean/var (no labels)."""
        self.n += 1.0
        d = z - self.mu
        self.mu += d / self.n
        self.sigma += (d * (z - self.mu) - self.sigma) / self.n

    def _logits(self, z, mu, sigma):
        return self.W @ ((z - mu) / (np.sqrt(sigma) + 1e-6)) + self.b

    def predict(self, z):
        # Anchor personal stats to the population prior; gate by evidence n.
        g = self.n / (self.n + self.lam)                        # 0 -> base, 1 -> personal
        mu  = g * self.mu    + (1 - g) * self.base_mu
        sig = g * self.sigma + (1 - g) * self.base_sigma
        logits = self._logits(z, mu, sig)
        p = np.exp(logits - logits.max()); p /= p.sum()
        return int(p.argmax()), float(p.max())                  # label, confidence
A minimal on-device personalizer. observe absorbs covariate shift from unlabelled features; predict blends personal and population statistics with a gate \(g=n/(n+\lambda)\) so a device with few personal samples behaves like the shipped model and only specialises as evidence accumulates. No raw signal or label leaves the device, and the frozen head \((W, b)\) is never overwritten.

Right Tool: adapters instead of hand-rolled fine-tuning

The regularized head-and-adapter path, written by hand, is roughly 120 lines: parameter freezing, low-rank module insertion, the proximal penalty, and the training loop. The Hugging Face peft library reduces it to about 6: wrap the base with get_peft_model(model, LoraConfig(r=8, target_modules=[...])) and every non-adapter tensor is frozen, only the \(2dr\) low-rank weights train, and merge-back for deployment is one call. peft handles the freezing bookkeeping, the low-rank factorization, and checkpointing the tiny personal delta; you supply the per-user data and the \(\lambda\) schedule. On a Cortex-M target you keep the concept but hand-port the merged head, since peft itself targets full frameworks.

Research Frontier

The current frontier is personalized federated learning: fleets that learn a shared representation while each device keeps a private personal head. FedPer and Ditto (Li et al., 2021) split parameters into globally-aggregated and locally-retained sets, and pFedHN generates per-client heads from a shared hypernetwork; these give the population-prior-plus-personal-delta decomposition of this section a fleet-scale training procedure without pooling raw data, the subject of Chapter 64. A second active thread is label-free personalization, adapting from a user's unlabelled stream alone via self-supervised proxy tasks and test-time normalization (Chapter 66), which matters because most wearable users never label anything.

Exercise

Take the PersonalHead above and a two-class sensor stream where one simulated user's feature mean is shifted by \(+1.5\sigma\) from the population. (a) Plot per-user accuracy as a function of samples observed for \(\lambda \in \{1, 8, 64\}\) and explain the ordering early versus late in the stream. (b) Inject a 20-sample burst of mislabelled data and show which \(\lambda\) best resists collapse. (c) Replace the linear gate \(g=n/(n+\lambda)\) with a confidence-driven gate and describe when each is safer.

Self-Check

  1. Why does personalization usually re-fit only a low-dimensional correction rather than the whole backbone, and what statistical fact about one user's data forces that choice?
  2. Distinguish covariate shift from concept shift for a wearable, and name which of the three mechanisms addresses each.
  3. What does the anchor term \(\tfrac{\lambda}{2}\lVert\theta-\theta_0\rVert_2^2\) protect against, and what goes wrong if \(\lambda\) is set far too small on a device that sees only a few labels per week?

What's Next

In Section 62.7, we confront the uncomfortable consequence of everything in this chapter: a model that rewrites itself in the field is a model that can break itself in the field. We turn personalization's guardrails, the escape hatch, the prior anchor, and the gated trigger, into a formal safety discipline for self-updating models, covering rollback, update validation without a held-out set, and the monitoring that keeps an on-device learner from silently drifting past the point of trust.