"I learned that humans walk at 1.4 metres per second. Then I met one who ambles, one who marches, and one on crutches, and discovered that the average human does not exist."
A Chastened AI Agent
Prerequisites
This section assumes the windowed feature pipeline and label hierarchy built earlier in this chapter (Section 26.1) and the raw inertial signals of Chapter 23. The adaptation methods reuse feature standardization from Chapter 8, the self-supervised pretraining idea from Chapter 17, and connect closely to test-time adaptation under distribution shift in Chapter 66. Leakage-safe, subject-wise evaluation from Chapter 65 is the yardstick throughout.
The Big Picture
A model trained on a hundred people and tested on those same hundred people looks brilliant. Ship it to a new wrist and it can lose ten to twenty accuracy points overnight. The reason is that human motion is not a single distribution but a family of individual ones: your walking cadence, your arm swing, your dominant hand, your particular way of climbing stairs, and even how tightly you fasten the strap all shift the signal in ways the population mean never captures. This is the user-independent versus user-dependent gap, and closing it is what personalization is for. The engineering problem is sharp: a user-dependent model is far more accurate but demands labelled data from every individual, which no consumer will provide. This section is about how to bend a general model toward the person actually wearing it, using little or no labelled data, quickly, and often on the device itself.
Why one model does not fit all users
Formally, personalization is a domain shift problem in which each user \(u\) is a domain. The population model learns \(p_\text{train}(y \mid x)\) from a pooled dataset, but at deployment the wearer draws windows from their own \(p_u(x)\), and both the marginal (how your walking accelerometer signature looks) and the class-conditional (how your particular stair-climb differs from your walk) can differ from the pool. The gap is not subtle. Across standard human-activity benchmarks, evaluating with a leave-one-subject-out protocol, where the test user never appears in training, routinely costs a large fraction of the accuracy you would see under a naive random split that scatters a single person's windows across both train and test. That inflated random-split number is a leakage artefact of exactly the kind flagged in Chapter 65: adjacent windows from one person are near-duplicates, so any per-window split silently trains and tests on the same individual. The honest baseline for personalization work is always the leave-one-subject-out score, and personalization is judged by how much of that lost ground it recovers.
Key Insight
Personalization spends one scarce resource, labelled data from the target user, to buy accuracy. The whole design space is organized by how much of that resource you are willing to demand. At one extreme, supervised fine-tuning asks the user to perform and label each activity (a "calibration" ritual) and adapts strongly. At the other, unsupervised adaptation touches only the statistics of unlabelled data the device already collects and asks the user for nothing. Between them sit few-shot methods that need a handful of examples per class. The right choice is not the most powerful method; it is the most powerful method whose data cost the product can actually pay. A frailty monitor for hospital patients can run a two-minute supervised calibration; a fitness band shipped to millions cannot ask anyone for anything.
A ladder of adaptation methods
The cheapest and most underrated technique is per-user feature normalization. A large part of the cross-user gap is just an affine shift: different resting orientations, strap tightness, and body mass move the mean and scale of every feature. Standardizing each user's features by their own running mean and variance, rather than by the global training statistics, removes that shift before the classifier ever sees the data, costs no labels, and often recovers a meaningful slice of the gap for a few lines of code. It is the first thing to try and the last thing people remember.
One rung up, supervised fine-tuning takes the pretrained population network and continues training its last layers (or a small adapter) on a short labelled calibration set from the user. It is the strongest adapter when labels exist, but it risks catastrophic forgetting of the general model if you fine-tune too aggressively on a tiny set, so freeze the backbone and adapt only a light head. Few-shot prototype methods avoid gradient updates entirely: embed the user's few labelled examples with the frozen population encoder, average them into one prototype per class, and classify new windows by nearest prototype. This is cheap, stable, and pairs naturally with the contrastive encoders of Chapter 17, whose embeddings are built precisely so that same-activity windows cluster. At the top of the ladder, unsupervised test-time adaptation adjusts the model from unlabelled target data alone, for instance by recomputing batch-normalization statistics on the new user's stream or minimizing prediction entropy; this is the machinery of Chapter 66 applied with the user as the shifting domain.
The listing below implements the two label-free rungs that every practitioner should reach for first: per-user standardization and a nearest-prototype adapter over a frozen embedding. It reports the population baseline and the personalized score so the recovered gap is explicit.
import numpy as np
def personalize(z_train, y_train, z_user_few, y_user_few, z_user_test):
# z_* are frozen-encoder embeddings; *_few is the tiny calibration set.
# 1) Per-user standardization: remove this user's mean/scale shift.
mu, sd = z_user_few.mean(0), z_user_few.std(0) + 1e-6
zf = (z_user_few - mu) / sd
zt = (z_user_test - mu) / sd
# 2) One prototype per class from the few labelled user windows.
classes = np.unique(y_user_few)
protos = np.stack([zf[y_user_few == c].mean(0) for c in classes])
# 3) Classify test windows by nearest prototype (cosine).
protos /= np.linalg.norm(protos, axis=1, keepdims=True) + 1e-9
ztn = zt / (np.linalg.norm(zt, axis=1, keepdims=True) + 1e-9)
pred = classes[np.argmax(ztn @ protos.T, axis=1)]
return pred
As Listing 26.4 shows, the entire personalized classifier is a normalization and a set of class means; there is no optimization loop to diverge and nothing to overfit beyond a mean vector. That stability is exactly why prototype adaptation is the default for on-device personalization where you cannot supervise a training run.
The Right Tool
Hand-writing a robust few-shot episodic trainer (episode sampling, support/query splits, a prototypical or matching-network loss, and metric-learning bookkeeping) runs to a few hundred lines. learn2learn and higher collapse the meta-learning inner loop to a handful of calls, and for the frozen-encoder case scikit-learn's NearestCentroid is the prototype classifier of Listing 26.4 in one line. For the unsupervised rung, adapting batch-norm statistics to a new user is a single flag: put the model in train() mode with the affine weights frozen so only the running stats update on the target stream. Reach for these before rolling your own; hand-code only when you are on a microcontroller where none of them fit.
In Practice: a running-form coach that never asked its users to calibrate
A sports-wearable company built a foot-pod classifier that flagged overstriding, a running-form fault, from a shoe-mounted inertial unit. In-house it was excellent, because the developers ran on treadmills where they could label their own strides. In the field it degraded sharply, and the culprit was inter-user variation: stride length, foot-strike angle, and cadence differ enough between a 1.6-metre recreational jogger and a 1.9-metre sprinter that the population decision boundary sat in the wrong place for most individuals. Asking runners to perform a labelled calibration run was a non-starter; nobody would do it. The team instead shipped the label-free bottom of the ladder. First, per-user feature standardization computed over each runner's first kilometre removed the bulk of the affine shift. Then a short unsupervised batch-norm adaptation on the unlabelled ongoing stream nudged the model the rest of the way, treating each runner as a new domain exactly as in Chapter 66. False-positive overstride alerts fell by more than half with no user effort and no labels collected. The lesson: the most valuable personalization is often the kind the user never notices.
Cold start, drift, and evaluating honestly
Personalization has a cold-start problem: on the very first day, before any user data exists, you must fall back to the population model, so ship a strong user-independent baseline and let personalization improve it, never depend on it. Adaptation should also be reversible and monitored. An unsupervised adapter that quietly minimizes entropy can lock onto a confident wrong answer and drift away from truth, so cap how far the personalized model may move from the population prior and keep the original weights so you can reset. Because a person's behaviour itself changes over weeks (injury, ageing, a new gym routine), personalization shades into the continual-learning problem taken up next; the distinction is that personalization adapts to who the user is, while continual learning tracks how they change.
Evaluate with the same discipline the rest of the book insists on. The headline number must come from a leave-one-subject-out split, and the calibration data used to personalize a test user must come from a disjoint time block of that user, never from windows interleaved with the test windows, or you leak the answer through temporal correlation and report a fantasy. Report the population baseline and the personalized score side by side so the recovered gap is visible, and record how many labelled windows the personalization consumed, because a method that needs fifty labels per class is a different product from one that needs two.
Exercise
Using a multi-subject activity dataset (for example PAMAP2 or MHEALTH), train a user-independent classifier under a leave-one-subject-out protocol and record its per-user accuracy. Now personalize each held-out user three ways: (a) per-user feature standardization only, (b) the nearest-prototype adapter of Listing 26.4 with \(k \in \{1, 2, 5\}\) labelled windows per class drawn from an early time block, and (c) fine-tuning a linear head on the same few windows. Plot recovered accuracy against the number of labels consumed, and identify at what label budget the gradient method overtakes the label-free ones. Confirm that drawing the calibration windows from an interleaved (rather than disjoint) time block inflates every score, and quantify by how much.
Self-Check
1. Why does a model's accuracy under a random per-window split systematically overstate how it will perform on a genuinely new user, and which split gives the honest number?
2. Order per-user normalization, few-shot fine-tuning, and unsupervised batch-norm adaptation by the amount of labelled user data each demands, and give a product for which each is the right pick.
3. What is the cold-start problem in personalization, and what two safeguards keep an unsupervised adapter from drifting to a confident wrong answer?
What's Next
In Section 26.5, we hold the user fixed and let time move. Personalization adapts to who a person is on day one; but the same person's gait after an injury, their routine after a move, and their activity mix as they age all drift the target out from under a fixed model. We take up continual learning for changing behavior: how to keep updating a deployed model over months without forgetting what it already knew.