robertavrammd's picture
Add strict five-fold CVD inference and full-precision risk equations
12be54e verified
|
Raw History Blame Contribute Delete
7.03 kB
---
license: cc-by-nc-4.0
tags: [electrocardiogram, ecg, cardiovascular-risk, foundation-model, self-supervised, deepecg-ssl]
library_name: pytorch
pipeline_tag: tabular-classification
---
# DeepECG-SSL-CLSA-CVD
Fine-tuned **DeepECG-SSL** (self-supervised 12-lead ECG transformer; Nolin-Lapalme et al., *Eur Heart J* 2026, doi:10.1093/eurheartj/ehaf1119) to predict **incident cardiovascular disease** over a median 5.8 years in the Canadian Longitudinal Study on Aging (CLSA). The model accompanies the manuscript *"A Lab-Free ECG Foundation Model Versus Guideline Risk Scores for Incident Cardiovascular Disease: Development in CLSA and External Validation at the Montreal Heart Institute"*.
## What is in this repository
- `weights/deepecg_ssl_clsa_cvd_fold{0-4}.safetensors`: the five cross-validation fold models (full fine-tuning; ECG only).
- `model_params.json`:
- per-fold Platt calibration parameters;
- the **Model A+** logistic equation (DeepECG-SSL score + age + sex + hypertension; no blood test).
- `inference.py`: a loader and prediction functions. It needs `fairseq-signals` and the matching DeepECG-SSL v2 (amplitude-preserved) encoder checkpoint for the architecture.
## Input
- 12-lead resting ECG, 10 s, resampled to 250 Hz: an array of shape (12, 2500).
- Units are millivolts, with no per-lead normalisation.
- Standard lead order: I, II, III, aVR, aVL, aVF, V1–V6.
## Performance (from the manuscript)
**CLSA held-out test set** (n = 3,906; 394 events). The guideline scores need a blood test; Model A+ does not.
| Model | AUROC |
|---|---|
| **Model A+** | **0.733** (95% CI 0.707–0.759) |
| PREVENT | 0.715 |
| PCE | 0.709 |
| SCORE2/OP | 0.708 |
| FRS | 0.689 |
- **Where the gain comes from:** it is driven by the non-specific self-reported "heart disease" outcome item.
- **Endpoints the gain does not cover:** for hard atherosclerotic endpoints, PREVENT and SCORE2 are as good or better.
**External validation at the Montreal Heart Institute** (n = 21,647; 6-year hard MACE; DeepECG-SSL + age + sex with CLSA coefficients):
| Model | AUROC |
|---|---|
| DeepECG-SSL + age + sex | 0.761 |
| Age + sex | 0.728 |
- **By component:** the gain is in heart failure and stroke, not in myocardial infarction.
- **Calibration:** absolute risks need local recalibration before use.
## Intended use and limitations
- **Research use only.** This is not a medical device and has not been prospectively validated.
- **Training data:** CLSA, a mostly White community cohort aged 45–85, with GE CardioSoft ECGs.
- **Other populations and devices:** performance may differ.
- **No CLSA participant data** are included in this repository.
## Citation
Please cite the manuscript (citation to be added on publication) and DeepECG-SSL (Nolin-Lapalme A, et al. *Eur Heart J* 2026;47:2174–2186).
## Strict inference package
The `clsa_cvd` package adds strict five-fold checkpoint loading, explicit raw-source
mV conversion, full-precision logistic coefficients and the external age/sex variant.
It uses the same five fold weights already staged in this repository. The existing
root `inference.py` API remains present for compatibility; use this package for the
external variant and strict input/checkpoint validation.
```python
from clsa_cvd.model import CLSACVDModel
model = CLSACVDModel(
weights_dir="weights",
base_checkpoint="/approved/models/deepecg-ssl-v2-wcr-amp-preserved-encoder.pt",
params_path="clsa_cvd/model_params.json",
device="cuda:0",
precision="float32",
)
# ecg_mv: finite float32 (N, 12, 2500), 10 seconds at 250 Hz in mV.
# age/female/hypertension: one numeric value per ECG; female/HTN are binary.
pred = model.predict(ecg_mv, age, female, hypertension, batch_size=32)
# p_ecg, risk_Aplus, ecg_logit_uncal, risk_ecg_age_sex
```
Lead order: I, II, III, aVR, aVL, aVF, V1–V6. For raw NPY inputs use
`clsa_cvd.preprocessing.prepare_waveform` or `load_waveform` with an explicit
format: `clsa` = 500-Hz CardioSoft ADC ×0.005 then polyphase resampling;
`muse` = 250-Hz MUSE ADC ×0.00488; `mv250` = already in mV at 250 Hz.
No spectral-power normalization or per-lead z-scoring is applied.
`risk_ecg_age_sex` uses the logit of the mean uncalibrated fold probabilities.
It is the transported MHI variant, not A+ with hypertension set to zero, and its
absolute risks require local recalibration. A+ uses the mean of Platt-calibrated
fold probabilities with age, female and physician-diagnosed hypertension.
The new package defaults to IEEE float32 with TF32 disabled. Historical test
predictions used CUDA bfloat16, batch 128. Select `precision="bfloat16"` and
batch size 128 to reproduce that arithmetic. On all 3,906 CLSA test participants,
historical-mode predictions matched the archive within 9e-8. Float32 is stable
under the tested single/batch ordering but differs from the historical ECG
probabilities by up to 0.001780 and A+ by 0.000998. Full-precision equation
coefficients reproduce archived A+ outputs within 2.3e-16 when supplied stored
ECG probabilities. Do not assume bit-identical results across precision modes.
Validated Python environment: PyTorch NVIDIA 26.08, CUDA 13.4, Python 3.12;
fairseq-signals `Jwoo5/fairseq-signals` commit
`f8f0ff1c788a82c2059cb452cd5462898867489e`. `requirements-clsa.txt` records
the observed direct dependency versions. The matching encoder checkpoint is still
required to define the architecture; its SHA-256 and fold artifact hashes are in
`clsa_cvd/artifacts.json`. Container build/run has not yet been verified.
`synthetic_validation.json` describes non-participant sinusoidal fixtures and
expected outputs. No participant-level data are included in this update.
## Pretraining overlap and subgroup reporting
No patient crosswalk between the encoder pretraining sources and the MHI validation
cohort is available. Patient-level pretraining/validation overlap has therefore not
been independently verified. This repository does not claim a reproduced
zero-overlap audit.
Exploratory racial/cultural subgroup reporting is limited by small event counts
and disclosure restrictions; suppressed results do not establish fairness in those
groups. Disclosure and fairness review were confirmed complete by the project
owner on 23 October 2026.
## Environment setup
The reference environment uses the NVIDIA PyTorch 26.08 container. In that
environment, install the direct dependencies and use the pinned fairseq source:
```bash
python -m pip install -r requirements-clsa.txt
git clone https://github.com/Jwoo5/fairseq-signals.git
git -C fairseq-signals checkout f8f0ff1c788a82c2059cb452cd5462898867489e
export PYTHONPATH="$PWD/fairseq-signals${PYTHONPATH:+:$PYTHONPATH}"
```
Download this repository (including `weights/`) using the Hugging Face client.
Supply the matching encoder checkpoint separately; its public download location
has not been verified. Verify its SHA-256 against `clsa_cvd/artifacts.json`.
The fine-tuned fold weights are included here; the separate base encoder is not.