← Praruj Thapa
/
Project · Time-series ML · 2026

Detecting stress from wearable sensors — in people the model has never seen

A chest sensor records heartbeat (ECG), sweat response (EDA), muscle activity, temperature and breathing. I built a pipeline that turns 60 seconds of these noisy signals into a prediction of the wearer's state, and evaluated it the strict way: every result is on a person whose data was never used in training.

–
accuracy detecting stress vs. non-stress (best model)
–
macro-F1 across 4 states: baseline, stress, amusement, meditation
19% → –
4-state accuracy, first version vs. now
Results

Leave-one-subject-out evaluation on all 15 people of the WESAD study: hold one person out, train on the rest, test on that person, repeat 15 times. Deep models are 5-seed ensembles.

Show as a table
How it works

Clean the signals

Anti-aliased resampling from 700 Hz to 100 Hz, then per-person normalisation — skin conductance and body temperature differ more between people than between moods.

Two views of each 60 s window

Physiological features (heart rate, HRV, sweat-response count, breathing rate) and the raw waveform, compressed 64× by a 1-D CNN before a bidirectional LSTM with attention.

Fuse and classify

The hybrid model concatenates both views. Features are standardised using training people only, so nothing about the test person leaks in.

What I learned
  • Evaluation design matters more than the model. My first LSTM scored 19% — worse than guessing. The fixes were per-person normalisation and a CNN front-end, not a bigger network.
  • More regularisation did not help. Lower learning rate, dropout, label smoothing, crop and channel-drop augmentation all scored at or below the baseline: the gap is person-to-person variation, not classic overfitting.
  • Single runs lie. The same CNN-LSTM varied by ±0.05 macro-F1 from the random seed alone, so the final numbers are 5-seed ensembles. Validating on one person per fold was too noisy to tune on: it would have picked a setting that did worse on test, so I kept the defaults and publish both scores.
  • Simple features win on average; deep models fail more gracefully. The Random Forest has the best 4-state score (0.76 macro-F1) but collapses on one person (0.22). The hybrid's worst person still scores 0.52, and adding features lifted the CNN-LSTM from 0.68 to 0.70.
Stack
PyTorchscikit-learnSciPy signal processingNumPypytest + GitHub ActionsGoogle Colab (T4)