Human activity recognition from smartphone inertial sensors
Classifying six daily activities from waist-mounted accelerometer and gyroscope signals — five classical models under grid search, a deep-learning pass, and what the subject split teaches about validation.
Problem
Given a window of inertial sensor readings from a phone on a person’s waist, say which of six activities they’re doing: walking, walking upstairs, walking downstairs, sitting, standing, or lying down. It’s the canonical “what is the phone’s owner doing” problem behind step counters, fall detection and context-aware UI — and a clean test of whether a classifier can separate postures that look nearly identical to an accelerometer.
Data
The UCI Human Activity Recognition dataset: 30 subjects aged 19–48, a Samsung Galaxy S II at the waist, 3-axis linear acceleration and 3-axis angular velocity at 50 Hz, video-labelled. Signals were noise-filtered, split into 2.56-second windows with 50 % overlap (128 readings each), and the acceleration separated into body and gravity components with a 0.3 Hz Butterworth low-pass. Each window carries 561 engineered features — time-domain and frequency-domain statistics over the body, gravity, jerk and magnitude signals. 10,299 windows in total; no missing values.
Exploration
- Feature families. The 561 columns break down into 17 signal families (body acceleration, gravity, jerk, gyroscope, their magnitudes, and FFT versions), which made it possible to reason about the feature space by family rather than column by column.
- Per-subject signal traces for the gyroscope across all six activities showed the obvious split: the three walking classes have periodic structure; the three static classes don’t.
- Box plots by activity on acceleration and gyroscope features confirmed where the difficulty is: the walking variants separate on jerk and gyro magnitude, while sitting and standing overlap heavily on almost every body-motion feature — gravity orientation is the only signal that distinguishes them.
- t-SNE across perplexities 2–50 and PCA in two and three components both show the same picture: two well-separated super-clusters (dynamic vs. static), with the walking classes separable inside one and sitting/standing entangled inside the other. Lying down sits apart from both on gravity alone.
That exploration set the expectation for the models: anything reasonable will get the dynamic/static split; the score is decided by sitting vs. standing.
Models
Five classifiers, each in a scaler-plus-model pipeline, tuned with 5-fold grid search on a 70/30 split:
| Model | Best configuration | CV accuracy | Train accuracy | Note |
|---|---|---|---|---|
| Logistic regression | L2, C = 1, multinomial | 98.3 % | 99.7 % | Best overall; the features are close to linearly separable |
| SVM (RBF) | C / γ grid | high | — | Competitive with logistic regression |
| kNN | k = 1 | 95.6 % | 100 % | k = 1 chosen by the grid — a memoriser, not a model |
| Random Forest | depth 6, 6 leaves, 50 trees | 88.5 % | 88.4 % | Grid was deliberately shallow; under-fit, not over-fit |
| Gaussian NB | — | 65.1 % | 65.8 % | Feature independence assumption fails badly on 561 correlated features |
Comparison plots covered train vs. test score, fit time, precision/recall/F1, and per-class ROC. A Keras dense network was also fitted as a deep-learning baseline.
What the results actually say
- Engineered features did the work. With 561 well-designed time- and frequency-domain features, a regularised linear model reaches 98 % — the deep-learning pass had nothing to add on top of hand-built features. Deep models earn their keep on the raw 50 Hz signals, where the feature engineering isn’t done for you.
- The grid search is only as honest as its grid. kNN at k = 1 and a Random Forest capped at depth 6 are both artefacts of the search space, not verdicts on the algorithms. The forest in particular would compete with logistic regression given a sensible depth range.
- Fit time matters at this scale. Logistic regression took ~30 minutes of CPU to grid-search; the forest nearly an hour. For a feature set this clean, a linear model is both the most accurate and the cheapest to run on a phone.
The validation lesson
The dataset ships with a split by subject — 70 % of the volunteers train, 30 % test — precisely so a model is scored on people it has never seen. The notebook re-split the pooled data randomly by window, which puts every subject in both train and test and lets the model learn individual gait signatures. The scores above are therefore optimistic. The right protocol is group-aware: hold out subjects (GroupKFold or leave-one-subject-out), which is also the protocol that predicts how the model behaves on a new phone owner. This is the same lesson as the taxi project in a different costume: the split has to match the deployment.
What I’d do differently
- Split by subject from the first fit, and report the gap between the row-split and subject-split scores — the gap is the real finding.
- Open up the tree grid so Random Forest and gradient boosting get a fair comparison.
- Go to the raw signals for the deep-learning pass. A 1D-CNN or LSTM on the 128×6 windows is where a neural model can beat the engineered features; applied to the pre-engineered 561 features it’s redundant.
- Target the confusion, not the accuracy. Sitting vs. standing is the whole problem; the metric to optimise is that pair’s F1, and the features to add are orientation-specific.