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.

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.
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support