GlucoChinook: MetaboNet glucose forecasting ensemble
Weights for a three-model ensemble that forecasts continuous glucose monitor (CGM) readings 30, 60, 90 and 120 minutes ahead for people with type 1 diabetes. It was trained on the MetaboNet corpus for the MetaboNet glucose prediction challenge (annual competition track). Training and inference code: https://github.com/Harri200191/Glucose_Competition (MIT).
Research artefact. Not a medical device and not validated for insulin dosing or any treatment decision.
Members
| Folder | Backbone | Parameters | Predictive law |
|---|---|---|---|
c96a_transformerpp |
Transformer++ | 1.76M | Student-t mixture, k = 3 |
c96a_mambaformer |
MambaFormer | 1.44M | Student-t, k = 1 |
c96a_mamba2 |
Mamba-2 | 1.05M | Student-t, k = 1 |
All three members share the rest of the architecture: modality encoders, CGM-centred FiLM fusion, then the backbone, then a horizon cross-attention head that passes each horizon's predictive distribution on to the next.
Inputs: 96 hours of history at 5-minute resolution (1,152 steps) of CGM, insulin and carbohydrates, plus their observation masks, time-of-day features, patient statics, and insulin delivered after the forecast time. The challenge provides future insulin and masks future CGM, so the models use it. Do not compare them with models that see history only.
Output: the log-ratio log(y / cgm_now) per horizon, which the code converts to mg/dL.
Training:
- A from-scratch run on the full training corpus (
scripts/run_c96t.sh), using a loss that is the exact expected DTS error-grid zone A under the Student-t law. - Three epochs of warm-started fine-tuning on a mix targeting 55% IOBP2 patients
(
scripts/run_c96a.sh). IOBP2 is the only source that matches the annual test population (Dexcom G6 + iLet, mostly adolescents).
The checkpoint kept is the one with the best validation zone A.
Files
<member>/best.pt state_dict + training metadata
<member>/config.resolved.yaml full resolved config the model is rebuilt from
<member>/standardizer.json normalisation fitted on training anchors only
ensemble_recipe.json per-horizon member weights + per-glucose-bin affine correction
The weights in ensemble_recipe.json apply to the members in this order:
transformerpp, mambaformer, mamba2. The correction was fitted on the live leaderboard's
public answer key, then transferred unchanged to the annual set.
Inference
The code reads each model's category codes from the training store, so you also need the MetaboNet corpus. Request access from its maintainers.
git clone https://github.com/Harri200191/Glucose_Competition && cd Glucose_Competition
pip install -r requirements.txt
git clone https://github.com/replicahealth/glucose-prediction-submission-toolkit.git toolkit
# corpus -> data/metabonet_public.parquet
# annual holdout inputs -> data/annual_competition_secret_holdout_set_public.parquet
STAGE=store scripts/run_ctx1152.sh # training store at artifacts/store/ctx1152
hf download Hari93/GlucoChinook --local-dir runs/ # puts the member folders in runs/
python -m metabonet_forecasting.data.prepare --corpus data/annual_competition_secret_holdout_set_public.parquet \
--out artifacts/store/submission_annual \
--anchors-from toolkit/data/annual_competition/template.parquet --min-segment-min 30
for m in c96a_transformerpp c96a_mambaformer c96a_mamba2; do
python -m metabonet_forecasting.submission.predict_annual --run runs/$m \
--store artifacts/store/submission_annual \
--template toolkit/data/annual_competition/template.parquet \
--out annual_$m.parquet
done
predict_annual sets each patient's statics to the pattern IOBP2 patients had in training:
gender is set to unknown, and diabetes duration is imputed from age. Without this step, annual
zone A drops by 4 to 12 points (see the module docstring).
Combine the three member forecasts with the recipe:
import json, numpy as np, pandas as pd
from metabonet_forecasting.submission.recalibrate import ensemble, apply_multipliers
r = json.load(open("runs/ensemble_recipe.json"))
files = [f"annual_c96a_{b}.parquet" for b in ("transformerpp", "mambaformer", "mamba2")]
frames = [pd.read_parquet(f) for f in files]
out = ensemble(files, None)
for h, mult in r["mult"].items():
col = f"pred_{h}"
stack = np.stack([f[col].to_numpy(float) for f in frames])
out[col] = apply_multipliers(np.asarray(r["horizon_weights"][h]) @ stack, mult)
out.to_parquet("annual_ensemble.parquet", index=False)
Run on the competition submission's own member forecasts, this reproduces the submitted annual file to within 1e-13 mg/dL.
License
MIT. The MetaboNet data is covered by its own data-use terms and is not included here.