Skip to content
sleepKIT
Tasks
HELIA

Detection diagnostics and historical comparison

The first experiment now has read-only error diagnostics and a separate, explicit SD-2 comparison recipe. These steps consume the same saved run evidence. They do not retrain a model or tune a threshold.

Terminal
python -m sleepkit.recipes.detection.error_analysis \
--run /path/to/membership-run \
--output /path/to/new-error-analysis.json

The evaluator first verifies the run, then reports confusion, class errors, and coverage by series, position within a model context, and distance to an observed target transition. Only aggregate results print to stdout; series identifiers remain in the local report outside the artifact bundle.

An observed transition is a change between known 0/1 targets on adjacent native feature frames. Its position is the later frame’s endpoint, not the original raw event timestamp. Unknown-target frames break segments. A nearest transition must be in the same known segment and series. Excluded complete contexts still provide target observations; incomplete tails do not. The distance bins are disjoint.

For the first experiment, the verified index contains 1,370 observed transitions:

Distance to observed transitionEligible framesErrorsAccuracy
At most 5 minutes28,7708,71369.7150%
Over 5 through 30 minutes137,0009,73892.8920%
Over 30 minutes1,870,63067,70696.3806%
No transition in the known segment23,2801,05695.4639%

The near-transition error rate is higher, but most errors occur in the much larger over-30-minute group. These are descriptive observations, not evidence of an error cause or a selected boundary threshold. The no-transition group contains only inside-period targets in this run.

Context-position diagnostics use disjoint first-eight, interior, and last-eight frame bins. Their respective accuracies were 94.6035%, 95.8320%, and 95.0725%, on 68,656, 1,922,368, and 68,656 frames. Class mixtures and temporal correlation limit interpretation; a future experiment should investigate hypotheses on validation data rather than select changes from these test metrics.

historical.py supports the pinned SD-2-TCN-SM int8 release. It reads existing historical features and masks without reading their legacy labels, reconstructs every feature from verified raw sensors, and requires exact float32 equality (with matching NaN positions). It preserves:

  • The original cos(2*pi*nanmean(TS/86400)) arithmetic, including midnight behavior.
  • The legacy duration and additional feature-tail truncation.
  • Whole-record float32 normalization, including unknown-target periods and feature tails that do not enter model contexts. This is an offline transductive policy.
  • The int8 graph, I/O scale and zero point, and fixed batch-one contract.

The stored mask must be all ones, matching this feature generator. Residual nonfinite features exclude their entire model context. Input quantization rounds to nearest with ties to even, clips to the int8 range, and records clipping counts. Outputs are used directly for argmax; ties select index zero and are counted. No additional softmax, cross-entropy, or calibration comparison is applied to the historical graph with its two final softmax operators.

Matching stored features to this source implementation establishes reproducible coordinates for this comparison. It does not recover the historical training inventory or prove that today’s feature store was used to train the released model. Local logs/configurations contain inconsistent seeds and no explicit subject inventory. The historical loader partitions filesystem discovery order, so replaying one seed against today’s files cannot resolve exposure. The historical configuration’s top-level sampling-rate discrepancy also remains unsuitable as a deployment clock. This comparison derives timestamps from the verified source grid instead.

On 2026-09-20, source revision 8d83b23 verified all 3,470,018 historical feature rows across the 43 frozen test series against raw sensors with exact float32 equality. Both pipelines had 14,433 complete native contexts, of which 8,582 were eligible and common. All 5,851 exclusions were due to unknown targets; none were due to nonfinite features. Historical truncation left 6,098 unused feature-tail rows, versus 6,227 for the new pipeline, but removed no complete context here.

Both models were scored on exactly 2,059,680 common frames, with class support 1,251,206 outside-period and 808,474 inside-period targets:

Metric on common framesHistorical SD-2 int8New membership Keras
Accuracy92.2334%95.7657%
Macro-F10.9162140.955611
Outside-period recall98.1601%96.4934%
Inside-period recall83.0611%94.6395%
Unweighted series mean accuracy90.0772%95.8477%
Unweighted series mean macro-F10.8656320.933468

The historical confusion matrix, with actual rows and predicted columns in outside/inside order, was [[1228185, 23021], [136947, 671527]]. The new matrix remained [[1207331, 43875], [43338, 765136]]. Both native eligible denominators equaled the intersection in this run. All 43 series contributed; the same two single-class series and fixed two-class F1 convention described in the first experiment apply to both models.

Historical inference clipped 926 of 10,298,400 input values and produced 4,386 argmax ties; ties chose class zero. The preserved historical model is 39,632 bytes. Verification, inference, and report generation took 35.5 seconds locally, not a hardware inference benchmark. Full local evidence is retained in sleepkit-evaluation-evidence/experiments/sd2-comparison-20260920/ alongside the repository. The pinned release files are retained under historical/sd2-v1/ in the same evidence root.

The new pipeline had higher agreement with this derived target in the observed comparison. Differences in annotation target, historical exposure, preprocessing, normalization, training, and quantization prevent an isolated model-quality claim. No model was selected or retrained from these results, and no Hub upload occurred.

Independent review reproduced every pooled and per-series confusion matrix, checked all common coordinates and targets, and recomputed normalization and input clipping for all 43 series. A separate literal feature calculation and direct LiteRT replay of the first eligible context per series covered 10,320 frames; all replayed int8 outputs matched exactly. Declaration, implementation, model, source, and output hashes also matched. No material finding remained.

Python example
from sleepkit.recipes.detection.comparison import compare
from sleepkit.recipes.detection.target_dataset import AnnotatedDataset
source = AnnotatedDataset(
root="/path/to/cmidss",
events_path="/path/to/train_events.csv",
alignment_path="/path/to/source-event-alignment.json",
coverage_path="/path/to/candidate-coverage.json",
frozen_dir="/path/to/frozen-split",
)
report = compare(
run_path="/path/to/membership-run",
source=source,
feature_root="/path/to/fs-w-a-5-60/cmidss",
baseline_source="/path/to/pinned-sd2-source",
output_path="/path/to/new-comparison",
)

The comparison verifies the new run and source bindings, writes a declaration before historical inference, and preserves each pipeline’s native complete contexts. The common index requires matching subject, raw-source hash, context start/end, every feature endpoint, and target, with both contexts eligible. No context is shifted or stitched to enlarge the intersection.

The historical WAKE/SLEEP argmax is explicitly mapped to outside/inside supported annotated periods as a descriptive proxy. historical_native means historical contexts scored under this candidate-target eligibility policy, not reproduction of the release’s original metrics. Reports retain both native denominators and the common denominator, pooled and per-series results, class support, exclusions, ties, and input clipping. This compares complete pipelines, including training, preprocessing, normalization, and quantization differences. It cannot establish historical held-out performance, clinical accuracy, or architecture superiority.

Local evidence includes declaration.json, new-evaluation.json, historical-index.jsonl, common-index.jsonl, historical-predictions.npz, and comparison.json. Index rows represent complete contexts and explicitly record their frame grid. Saved historical outputs are int8; common start offsets address both flattened prediction arrays. These files contain identifiers and remain outside public bundles. Existing output directories are refused. Failed runs retain partial evidence without a completed comparison report.

The next fixed int8 conversion preserves the new model’s preprocessing and measures quantization separately from this historical comparison.