Machine learning2025Published
Classifying gym exercises from wrist-worn IMU data
A CNN-LSTM that reads windows of wrist motion-sensor data and names the gym exercise, with 90% accuracy on sets it never saw in training. The cleaning and the leakage-aware split around the model are what make that number worth trusting.
- Role
- Independent ML project
- Date
- October 2025
- Data
- Gym Workout IMU, a public Kaggle dataset
- Stack
- Python, PyTorch, scikit-learn, Pandas, NumPy
- Scope
- Preprocessing, split design, model, evaluation
- Python
- PyTorch
- CNN-LSTM
- Time series
- IMU
- Wearables
- scikit-learn
- Pandas
- +1
- Problem
- Turn raw recordings from a wrist-worn IMU, one CSV file per gym set, into the name of the exercise being performed.
- Approach
- Clean and window every recording, split by whole set, fit the scaler on training data only, weight the loss by class and train a 1D CNN followed by LSTM layers.
- Outcome
- 90% accuracy on held-out sets. The confusion matrix shows errors between biomechanically similar exercises, pointing to where more data is needed.
Each set is one multichannel recording with one label
The data comes from Gym Workout IMU, a public dataset on Kaggle. Each CSV file holds one set recorded by a wrist-worn device. Its motion channels include rotation rate, gravity, linear acceleration and quaternion orientation, aligned by timestamp, and the label is the exercise.
That makes it multivariate time-series classification: given a window of wrist motion, say which exercise it comes from. It is the building block for fitness tracking and form analysis.
A wrist sensor only measures the wrist. Exercises that move it along similar paths produce similar signals, and that is where the hard cases are.
Four steps turn a raw recording into model-ready windows
Every CSV goes through the same preprocessing. Smoothing and downsampling reduce noise and compute; fixed-length windows give the network inputs of a single shape.
01
Trim
- Drop the noisy first and last seconds of the set
02
Repair
- Fix or drop missing values (NaNs)
03
Smooth and downsample
- Rolling averages
- Stride-based downsampling
04
Window
- Fixed length, e.g. 300 timesteps
- Each window keeps its set's label
The scaler is left out of this stage on purpose: it learns means and variances, so it is fit after the split, on training data only.
Whole sets, not windows, are the unit of the split
Windows cut from the same set share the same person, the same session and the same sensor placement. Shuffle all the windows and split them at random, and almost every test window has near-identical neighbors in the training data. The score then rewards remembering a set rather than recognizing a new one.
So the split happens at file level and is stratified: each set goes whole to training, validation or test, and no set contributes windows to more than one of them.
- Train
- Test
Random window split
- Set 1 · Exercise A
- Set 2 · Exercise A
- Set 3 · Exercise A
- Set 4 · Exercise B
- Set 5 · Exercise B
- Set 6 · Exercise B
Almost every test window has siblings from the same set in training, so the score partly rewards memory.
Split by set (used here)
- Set 1 · Exercise A
- Set 2 · Exercise A
- Set 3 · Exercise A
- Set 4 · Exercise B
- Set 5 · Exercise B
- Set 6 · Exercise B
Test sets are never seen in training or by the scaler, so the score measures recognition of a new set.
Three smaller decisions keep the number honest
01
Fit the scaler on training data only
A
StandardScalerlearns means and variances. Fit on all the data, it would let test statistics shape the training inputs, so it is fit on the training split and applied unchanged to validation and test.02
Weight the loss by class
Some exercises are under-represented. Class weights in the cross-entropy loss make mistakes on rare classes cost more, so the model cannot score well by neglecting them.
With weighting on, training curves were stable and precision and recall were balanced.
03
Judge per class, not by one number
A classification report and a confusion matrix show which exercises fail and what they are mistaken for. Overall accuracy hides both.
Convolutions find local patterns, the LSTM follows them over time
The architecture is a standard hybrid and a solid default for wearable IMU data.
01
Input
- One window
- Timesteps × channels
02
1D CNN blocks
- Local features in short stretches of signal
03
LSTM layers
- How those features evolve across the window
04
Dense head
- One score per exercise class
Built in PyTorch and trained with class-weighted cross-entropy.
90% on unseen sets, with errors where the movements look alike
- accuracy on held-out sets
- 90%
- With class weighting and full preprocessing
- sets shared between training and test
- 0
- The split is by file, not by window
Held-out data is whole CSV files, one set each, kept out of training and out of the scaler fit.
On held-out sets the model separates the common exercises well. The confusion matrix shows where it still fails: between biomechanically similar movements, which leave similar traces at the wrist. Those errors are useful, because they point to the exercises that need more data.
One limit to state plainly: holding out sets is not the same as holding out people. The 90% estimates performance on new sets. Performance on a brand-new user still has to be measured, with more subjects and a split by person.
Next: more people, rep counting and a lighter model
- Collect more users and exercise classes, and test on people the model has never seen.
- Add rep counting on top of the class predictions for richer workout analytics.
- Prototype a lighter on-device model for real-time feedback.
- Compare GRU and Transformer variants, and add augmentation such as jitter and time-warping.
Next case study
Improving a Grey Wolf Optimizer for job shop scheduling
Related
Backtest realism
The backtest that lies, and the rules that stop it
The rules that keep short-horizon backtests honest: stop, take-profit, entry inside every bar, reaction latency, slippage, and payout and leakage audits.
Latency and infrastructure
HFT on Polymarket: Rust, timestamps and the 98% hit rate that wasn't
Second-scale Polymarket research built recorder-first, with Rust on the hot path and LightGBM on top, and a 98% hit rate that fell to about 55% once latency was simulated.

Market systems2025–2026Stopped
Polymarket trading systems
Prediction-market research: a dataset builder, a 15-minute bot and a Rust + Python HFT recorder.
Get in touch
Working on something similar? I am happy to compare notes.