ye476's picture
Prepare model card for public release
f53d7e4 verified
|
Raw History Blame Contribute Delete
3.67 kB
---
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.