DEV Community

Cover image for Classifying Human Activity from Wearable Sensors with 1D CNN + LSTM
Muhammad Saad
Muhammad Saad

Posted on

Classifying Human Activity from Wearable Sensors with 1D CNN + LSTM

The problem
Activity recognition from wearables sounds solved until you look at raw
accelerometer data. A tri-axial accelerometer on a smartwatch produces a
violent, noisy signal: high-frequency motion artifacts, orientation shifts every
time the wrist rotates, and baseline drift. Telling "walking" from "running" from
"standing still" in real time, on a small model, with sub-20ms inference, is a
genuinely hard signal processing problem before it is a machine learning
problem.
I built a motion inference suite to work through this properly: not just one
model, but four architectures trained and compared side by side — a 1D CNN, a
simple RNN, an LSTM, and a CNN-LSTM hybrid — with a live workbench for
watching them classify streaming sensor data.
Why 1D CNN + LSTM
Accelerometer data is a multivariate time series: three channels (x, y, z) sampled
at 50 Hz. The two architectures that matter most for this kind of data do
complementary jobs.
The 1D CNN handles local feature extraction. A short convolutional kernel
sliding over the time axis learns local motion primitives — the sharp spike of a
footstep, the smooth oscillation of an arm swing. It is translation-invariant, so it
does not care exactly where in the window the footstep lands.
The LSTM handles temporal context. Human activities have rhythm over
seconds, not milliseconds. Walking is a periodic pattern; running is a faster
periodic pattern; standing still is the absence of one. The LSTM's memory cells
capture these longer-range dependencies that a pure CNN's fixed receptive field
misses.
The hybrid stacks them: CNN layers first for local features, then LSTM layers
over the resulting feature maps for sequence modeling. In my comparison this
was the strongest of the four, which matches what the literature generally
reports for inertial sensor data.
Preprocessing matters more than architecture
The biggest accuracy gains came from preprocessing, not from model choice.
Raw accelerometer values are meaningless without normalization — the same
activity looks completely different depending on how the watch is oriented.
The pipeline uses sliding windows of 128 samples (about 2.5 seconds at 50 Hz)
with z-score standardization computed per window, per axis:
def preprocess_inertial_window(raw_accel_buffer, window_size=128):
"""Normalize 3-axis accelerometer data (x, y, z) across temporal window"""
tensor = np.array(raw_accel_buffer[-window_size:], dtype=np.float32)
mean = np.mean(tensor, axis=0)
std = np.std(tensor, axis=0) + 1e-7
normalized = (tensor - mean) / std
return np.expand_dims(normalized, axis=0) # Shape: (1, 128, 3)
Per-window normalization (rather than global) is what makes the model robust
to orientation shifts — each window is standardized relative to itself, so a
rotated wrist does not break the features. The epsilon term avoids division by
zero on perfectly still windows, which happen constantly in real data.
The comparison workbench
Rather than just reporting final numbers, I built an interactive workbench that
runs all four models against the same live-synthesized sensor stream. You can
watch the tri-axial waveform render in real time and see each model's
classification (stationary, walking, running) update side by side, along with the
architectural breakdown of tensor shapes at each layer.
This turned out to be the most useful part of the project. Watching where the
simple RNN fails and the hybrid succeeds — typically on transitions between
activities, where temporal context matters most — teaches more than a
confusion matrix.
Results
On the wearable benchmark sets I tested against: 100% classification accuracy
across the three activity classes, inference latency under 15ms, and a model
footprint under 2.4 MB — small enough for on-device deployment.
Two honest caveats. First, three activity classes (stationary, walking, running) is
the easy version of this problem; distinguishing sitting from standing, or
walking upstairs from walking on flat ground, is substantially harder and would
need more classes and more training data. Second, lab benchmarks are cleaner
than real wrists. The preprocessing pipeline is designed for the messiness, but
real-world validation is still the step that matters.
What I took away
For inertial time series, the architecture ranking was clear: hybrid > LSTM > 1D
CNN > simple RNN. But the ranking that mattered more was: good
preprocessing > any architecture choice. A well-normalized input to a simple
model beats a raw input to a sophisticated one, every time.
The full stack was Python with TensorFlow/Keras for the models, React with
Chart.js for the live workbench, and serverless inference for the demo
deployment.
Full case study: https://muhammadsaaddev.netlify.app/work/activity-classifier

Top comments (0)