File size: 3,674 Bytes
437c134 f53d7e4 437c134 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | ---
license: other
tags:
- eating-detection
- wearable
- ppg
- accelerometer
- time-series
---
# EatingDetection checkpoint
Ten leave-one-participant-out checkpoints for binary eating detection from the
smart earring — green PPG + tri-axial accelerometer, no gyroscope, no
microphone.
Trained on the 8-second windowed split of a 10-participant earring study. The
study data is not public yet, so these weights are released ahead of it: the
model, the evaluation protocol and the per-fold scores are all here, but the
numbers below cannot be re-derived from raw data until the dataset is released.
Code: https://github.com/FengYe476/Eating_Detection/tree/Feng/eating_detection
## Result
| | macro-F1 |
|---|---:|
| **Pooled over all held-out decisions** | **0.9535** |
| Mean over the 10 participants | 0.9486 ± 0.0253 |
Same ten models, two reductions: pooling concatenates every held-out decision
and scores once (each window counts equally); the per-participant mean scores
each fold then averages (each person counts equally).
| P2 | P3 | P4 | P5 | P7 | P8 | P9 | P10 | P11 | P12 |
|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| .944 | .947 | .947 | .947 | .960 | .996 | .913 | .901 | .964 | .966 |
`checkpoint_scores.json` holds the per-fold table, including the score stored
inside each checkpoint at training time — re-scoring reproduces all ten to four
decimals.
## Files
`fold_P<user>.pt` — one per held-out participant. **`fold_P<u>.pt` never saw
participant `<u>`** during training, standardisation or checkpoint selection;
that is what makes its score subject-independent. For a participant outside this
cohort, any fold is equally valid.
Each file is a dict: `state_dict`, `mu`, `sd` (per-channel standardisation fitted
on that fold's training subjects), `channels`, `window_samples`, `in_channels`,
`test_user`, `val_f1`, `test_macro_f1`.
## Model
`CoincidenceTCN`, **9,433 parameters**. Input `(N, 4, 400)` — 8 s at 50 Hz,
channel order **`[ppg_green, acc_x, acc_y, acc_z]`**. Output: one logit per
window; eating if `logit > 0`.
```python
import torch
from eating_detection.models.coincidence_tcn import CoincidenceTCN
d = torch.load("fold_P2.pt", map_location="cpu", weights_only=False)
m = CoincidenceTCN(d["in_channels"]); m.load_state_dict(d["state_dict"]); m.eval()
X = ... # (N, 4, 400) float32, raw window values
X = (X - d["mu"]) / d["sd"] # the fold's own statistics — do not refit
eating = m(torch.from_numpy(X.astype("float32"))) > 0
```
## Evaluation protocol
10-fold leave-one-participant-out over P2, P3, P4, P5, P7, P8, P9, P10, P11,
P12; both earrings merged as independent windows; 8 s windows on a 1 s stride.
Negatives follow "Strategy-2": every eating window, every idle window within two
minutes of eating, and far background idle subsampled to 1:1 with eating — so
majority-class accuracy would be 0.62, not 0.9.
Reported as macro-F1 with the decision fixed at `logit > 0`; no per-participant
threshold selection and no per-participant calibration.
## Reading these numbers honestly
- Negatives are not uniformly hard: false-positive rate is 0.138 on pauses
*inside* a food block against 0.039 on rest *between* blocks. Hard-negative
balanced accuracy is **0.90** — quote that for a deployment claim.
- P9 and P10 fail in opposite directions (under-detection and over-alarming) and
do so reproducibly; the decision rule has no threshold selection.
- Run-to-run spread for this configuration is about 0.8 pp, and CUDA vs MPS
alone moves it 0.3–0.4 pp. A single run does not separate two configurations
that differ by ~1 pp.
|