Part VI: Motion, Location, and Inertial Intelligence
Chapter 26: Human Activity and Behavior Recognition

Fairness across users, bodies, and demographics

"My aggregate accuracy was ninety-four percent, which everyone celebrated. Nobody asked which six percent, so I quietly kept it all in one corner of the population."

A Diplomatically Averaged AI Agent

Prerequisites

This section assumes you can train and evaluate a subject-independent activity model, the through-line of this chapter, and that you understand leakage-safe subject splits from Chapter 5 and Chapter 65. The mitigation by adaptation builds directly on Section 26.4 (personalization). The governance framing connects to Chapter 70, and the skin-tone example draws on optical sensing physics from Chapter 30.

The Big Picture

An activity model reports one accuracy number, and that number is a population average. Averages hide their tails. A step counter that is excellent on a lab full of graduate students, who tend to be young, able-bodied, and moving at a brisk deliberate pace, can silently fail for a frail eighty-year-old shuffling with a walker, precisely the person a fall-risk product most needs to serve. The failure is not random noise; it is structured along the axes of body size, sex, age, gait pathology, and skin tone, because human bodies produce systematically different signals and our training sets systematically over-sample some bodies over others. This section is about seeing those structured gaps, measuring them honestly instead of hiding them under an average, and closing them. Fairness in activity recognition is not a compliance checkbox bolted on at the end; it is a direct measure of whether your model works for the people who will actually wear it.

Where the gaps come from

Unfairness in human activity recognition is rarely malice and almost always physics plus sampling. Three mechanisms dominate. First, dataset composition: public benchmarks and internal collections skew toward convenient subjects, typically younger, fitter, and disproportionately male, so the model simply sees fewer examples of everyone else and fits them worse. Second, physiological signal differences: the same activity produces genuinely different inertial signatures across bodies. Stride length scales with leg length, so a person of height \(1.55\ \mathrm{m}\) and one of \(1.90\ \mathrm{m}\) walking at the same speed generate different step cadences and peak accelerations; a threshold or feature tuned to the mean stride under-counts the short-legged walker. Cadence, ground-contact time, and vertical impact all shift with age, weight, footwear, and gait disorders such as Parkinsonian shuffling or a post-stroke hemiparetic swing. Third, sensor coupling: a wrist device sits and moves differently on a thin wrist than a large one, and a hip clip rides at a different angle on different body shapes, so even identical motion reaches the accelerometer transformed.

A fourth mechanism is modality-specific and easy to forget in an inertial chapter: any activity pipeline that also ingests optical heart rate inherits the skin-tone bias of photoplethysmography. Green light is absorbed more by higher-melanin skin, lowering the signal-to-noise ratio of the pulse and degrading any activity or energy-expenditure estimate that leans on heart rate, an effect examined in depth in Chapter 30. The point is that a single fairness gap can be caused by data, by biomechanics, by mounting, and by sensor physics at once, so there is no single lever that fixes all of it.

Key Insight

Fairness is a claim about conditional performance, not average performance, so it is invisible to any metric computed over the pooled test set. The right unit of analysis is the subgroup, and the right headline number is not the mean but the worst-group score. If your model reports \(94\%\) overall while quietly delivering \(71\%\) to older women with shorter strides, the honest description of your model's quality is closer to \(71\%\) than to \(94\%\), because that is the experience your most vulnerable users receive. Optimizing the average can actively widen the gap: gradient descent spends its capacity where the data is dense, which is exactly the majority, so a lower average loss and a larger disparity often move together.

Measuring fairness without hiding the tail

Start by defining the groups explicitly. Let \(g \in \mathcal{G}\) index a protected or body-related attribute (sex, age band, height quartile, BMI band, reported mobility aid). For a per-subject or per-window accuracy \(\mathrm{acc}(g)\), two summaries matter more than the pooled mean. The gap is \(\Delta = \max_g \mathrm{acc}(g) - \min_g \mathrm{acc}(g)\), and the worst-group accuracy is \(\min_g \mathrm{acc}(g)\). For classification you usually want error-rate parity rather than raw accuracy parity, because base rates of activities differ across groups; report per-group false-negative rate on the safety-critical class (a missed fall, a missed seizure-like event) since a group with more misses is the group being failed. Crucially, none of this works unless the test set is split by subject and stratified so every group is actually represented on the held-out side, the leakage discipline of Chapter 5 applied group by group.

The listing below computes per-group accuracy, the disparity gap, and the worst-group number from arrays of true labels, predictions, and a group tag per sample. It is deliberately small because the audit itself should be trivial to run on every model version.

import numpy as np

def fairness_report(y_true, y_pred, groups):
    y_true, y_pred, groups = map(np.asarray, (y_true, y_pred, groups))
    per_group = {}
    for g in np.unique(groups):
        m = groups == g
        per_group[g] = (y_true[m] == y_pred[m]).mean()
    overall = (y_true == y_pred).mean()
    worst = min(per_group.values())
    gap = max(per_group.values()) - worst
    return overall, worst, gap, per_group

# Toy data: majority group "M" is easy, minority "F_short" is under-served.
rng = np.random.default_rng(0)
groups = np.array(["M"] * 800 + ["F_short"] * 200)
y_true = rng.integers(0, 5, size=1000)
y_pred = y_true.copy()
flip_M = (groups == "M") & (rng.random(1000) < 0.05)      # 5% error
flip_F = (groups == "F_short") & (rng.random(1000) < 0.30) # 30% error
y_pred[flip_M | flip_F] = rng.integers(0, 5, size=(flip_M | flip_F).sum())

overall, worst, gap, pg = fairness_report(y_true, y_pred, groups)
print(f"overall={overall:.3f}  worst-group={worst:.3f}  gap={gap:.3f}")
print({k: round(v, 3) for k, v in pg.items()})
Listing 26.6. A minimal subgroup fairness audit. The pooled overall accuracy looks healthy because the majority group dominates the mean, yet worst-group and gap expose that the short-stride minority is served far worse. Run this alongside your standard metrics on every model version, not once at the end.

As Listing 26.6 makes plain, the pooled average and the worst-group number tell different stories about the same model, and only the second one describes the user you are most likely to harm. The same skeleton extends to false-negative rate per group by swapping the accuracy line for a per-class miss rate.

The Right Tool

Rolling your own subgroup audit across several metrics, several attributes, and their intersections (sex crossed with age band) quickly balloons past 60 lines of bookkeeping. Fairlearn's MetricFrame does it in three: pass a dict of metrics, the true and predicted labels, and one or more sensitive_features, then call .by_group, .difference(), and .group_min() for the per-group table, the disparity gap, and the worst-group score. It also handles intersectional grouping and control features for free. Reach for it before hand-coding, and keep the hand-rolled version only as the on-device smoke test.

In Practice: a fall detector that missed the people it was built for

A consumer fall-detection wristband was validated on simulated falls performed by paid actors, who skewed young, athletic, and male because those are the people willing to repeatedly throw themselves onto crash mats. The reported sensitivity was excellent. In the field, falls among the actual target population, older adults and especially older women, were detected far less reliably. The mechanism was biomechanical: a genuine geriatric fall is often a slow crumple or a slide down a wall, with lower peak acceleration and a longer, softer impact than an actor's staged tumble, and the model's impact-magnitude features had learned the energetic young-male signature as the template for "fall." Lower-mass bodies simply did not clear the acceleration threshold. The remedy combined three moves from this section: collect and label real-world falls across body sizes and ages, re-evaluate with sensitivity reported per age-and-sex subgroup instead of pooled, and retrain to raise worst-group sensitivity rather than the average. The pooled sensitivity barely moved; the worst-group sensitivity, the number that actually mattered for the product's purpose, rose sharply.

Closing the gap

Once you can see a gap, several levers narrow it, roughly in order of cost. The cheapest is evaluation discipline: making worst-group score a first-class release gate so a regression that helps the majority but hurts a minority cannot ship unnoticed. Next is data: targeted collection to raise the density of under-served bodies, and group-balanced or reweighted sampling so training loss stops being dominated by the majority. A principled version of reweighting is group distributionally robust optimization (Group DRO), which minimizes the loss of the worst group rather than the average, optimizing \(\min_\theta \max_{g} \mathbb{E}_{(x,y)\sim g}\,[\ell_\theta(x,y)]\); it directly targets \(\min_g \mathrm{acc}(g)\) instead of the mean and is a small change to the training loop. A complementary, powerful lever is personalization: the adaptation methods of Section 26.4 bend a general model toward the individual wearing it, which dissolves many between-group gaps because a model adapted to one short-stride older user no longer needs the population to have represented her. Personalization is often the most effective fairness intervention available, since the ultimate minority group is a single person.

Two cautions keep this from becoming naive. First, mitigation can trade average for worst-group; that trade is usually the right one for a safety product, but state it explicitly rather than reporting only the number that improved. Second, fairness fixes must survive the same leakage scrutiny as everything else, and they interact with governance: many jurisdictions and standards now expect documented subgroup performance for health-adjacent wearables, the responsible-deployment terrain of Chapter 70.

Auditing without fooling yourself

Two failure modes turn a fairness audit into theater. The first is underpowered groups: a per-group accuracy computed on eight subjects has a confidence interval so wide that the reported gap may be pure sampling noise. Report the number of subjects per group and a confidence interval; if a group is too small to measure, that is itself a finding that you have not collected enough of that population to make any claim. The second is intersectionality: a model can look fair on sex alone and fair on age alone yet fail specifically for older women, because the harm lives at the crossing of two axes that each look fine in isolation. Audit the intersections you have the power to measure, and treat missing intersections as unmeasured risk, not as evidence of fairness. Finally, remember that collecting demographic labels is itself a privacy-sensitive act governed by the biometric-data constraints of Chapter 30: you need enough attribute information to detect gaps without over-collecting sensitive data, a genuine tension to design around rather than ignore.

Exercise

Take a subject-annotated activity dataset that carries demographic metadata (for example, one with subject height, sex, and age, or construct height quartiles from reported values). Train a subject-independent classifier, then use Listing 26.6 (or Fairlearn's MetricFrame) to report overall accuracy, worst-group accuracy, and the disparity gap across height quartiles and across sex, plus the intersection of the two. Then retrain with group-balanced sampling and with a Group-DRO-style worst-group objective, and quantify how much each moves the worst-group score, the gap, and the pooled average. Report the number of subjects behind every group number and flag any group too small for a trustworthy estimate.

Self-Check

1. Why can a model that maximizes average accuracy simultaneously increase the disparity between groups? What does gradient descent "spend its capacity" on?

2. Name three distinct mechanisms that make an inertial activity model less accurate for a short, older user, and say which one data rebalancing alone cannot fix.

3. A model is fair when audited on sex alone and on age alone but fails for older women. What is this phenomenon called, and what must your audit include to catch it?

What's Next

In Section 26.7, we bring the whole chapter to bear on real deployments across fitness, healthcare, safety, and accessibility, the domains where taxonomy choices, sensor placement, segmentation, personalization, and the fairness gaps of this section stop being abstractions and start determining whether a product helps or fails the person wearing it.