Patient Unified Longitudinal Signal Engine for longitudinal multimodal clinical data with missing visits and modalities.
pip install -e .Requires Python 3.8+, PyTorch 1.9+, NumPy, and optionally tqdm.
To install the base dependencies from the repository:
pip install -r requirements.txtBenchmark-specific dependencies are documented in each benchmark folder README.
import torch
from torch.utils.data import DataLoader
from pulse.data.dataset import LongitudinalDataset, collate_visits, create_synthetic_data
from pulse.model import PulseModel
from pulse.training.trainer import Trainer
device = "cuda:0" if torch.cuda.is_available() else "cpu"
modality_dims = {"modality_a": 61, "modality_b": 251}
model = PulseModel(
modality_dims=modality_dims,
latent_dim=16,
hidden_dim=32,
temporal_model="recurrent", # or "transformer"
).to(device)
patient_ids, visits_data, missing_masks = create_synthetic_data(
n_patients=1000,
n_visits=3,
modality_dims=modality_dims,
missing_rate=0.3,
)
dataset = LongitudinalDataset(
patient_ids=patient_ids,
visits_data=visits_data,
missing_masks=missing_masks,
train_mask_rate=0.6,
)
dataloader = DataLoader(dataset, batch_size=4, shuffle=True, collate_fn=collate_visits)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
trainer = Trainer(model, optimizer, device=device)
history = trainer.train(dataloader, n_epochs=50)scripts/Ablation/PULSE ablations on the two-visit metabolomics imputation task:- historical-state ablation:
scripts/Ablation/run_metabolomics_ablations.py - alignment variants (stop-gradient, point-wise):
scripts/Ablation/run_metabolomics_ablations.py - loss ablations (remove
L_align, removeL_adv):scripts/Ablation/run_metabolomics_ablations.py - active-masking sensitivity:
scripts/Ablation/run_mask_probability_sensitivity.py
- historical-state ablation:
scripts/Benchmark-single-cell-Method/Four models for metabolomics imputation:- MIDAS:
scripts/Benchmark-single-cell-Method/run_midas.py - scVAEIT:
scripts/Benchmark-single-cell-Method/run_scvaeit.py - StabMap:
scripts/Benchmark-single-cell-Method/run_stabmap_from_csv.py - PULSE:
scripts/Benchmark-single-cell-Method/run_pulse.py
- MIDAS:
scripts/Bencmark-EHR-Modeling/Four models for EHR/proteomics-generation:- PULSE:
scripts/Bencmark-EHR-Modeling/pulse_benchmark.py - Transformer:
scripts/Bencmark-EHR-Modeling/transformer_benchmark.py - Llama 3.1:
scripts/Bencmark-EHR-Modeling/llama31_benchmark.py
- PULSE:
scripts/PULSE_MIMIC/Four models for MIMIC-III:- note-only Llama:
scripts/PULSE_MIMIC/run_notes_only.py - lab-only Transformer:
scripts/PULSE_MIMIC/run_lab_transformer.py - late-fusion (Llama + labs):
scripts/PULSE_MIMIC/run_simply_combined.py - PULSE:
scripts/PULSE_MIMIC/run_pulse.py
- note-only Llama:
visits_data:List[Dict[str, Tensor]], one dictionary per visit.missing_masks:List[Dict[str, int]], one dictionary per visit.- Mask convention:
0: currently available1: actively masked during training2: naturally missing
Example:
visits_data = [
{"modality_a": modality_a_t1, "modality_b": modality_b_t1},
{"modality_a": modality_a_t2, "modality_b": None},
{"modality_a": modality_a_t3, "modality_b": modality_b_t3},
]
missing_masks = [
{"modality_a": 0, "modality_b": 0},
{"modality_a": 0, "modality_b": 2},
{"modality_a": 0, "modality_b": 0},
]Train with active masking:
CUDA_VISIBLE_DEVICES=0 python scripts/train.py --device cuda:0 --n_patients 100 --n_visits 3 --epochs 10 --train_mask_rate 0.6 --modality_a_name modality_a --modality_a_dim 61 --modality_b_name modality_b --modality_b_dim 251 --save_dir ./ckptsTrain with transformer-based temporal modeling:
CUDA_VISIBLE_DEVICES=0 python scripts/train.py --device cuda:0 --temporal_model transformer --temporal_num_heads 4 --temporal_num_layers 1Inference from a checkpoint:
CUDA_VISIBLE_DEVICES=0 python scripts/inference.py --device cuda:0 --checkpoint ./ckpts/checkpoint_epoch_10.ptFor visit N, PULSE:
- Encodes only the currently available modalities.
- Builds the historical state with a temporal autoregressive module.
- Aligns available modality distributions with the historical latent state.
- Computes the visit state with equal-weight fusion:
Z^N = (Z_P + sum(Z_M)) / (M_total + 1)
Here M_total counts only the currently observed modalities and does not
include the historical state Z_P.
- Decodes every modality from the fused visit state.
The autoregressive component is not restricted to an RNN formulation.
temporal_model="recurrent"uses a GRU-style hidden-state update.temporal_model="transformer"uses causal self-attention over previous visit states and is better suited to longer visit sequences.
Both options plug into the same PULSE fusion and decoding pipeline.
| Component | Description |
|---|---|
PulseModel(..., temporal_model="recurrent") |
Main model with recurrent or transformer temporal dynamics |
model.forward(visits_data, missing_masks, kl_annealing_weight=1.0, recon_targets=None) |
Returns reconstructions and losses |
model.impute_missing(visits_data, missing_masks) |
Returns per-visit imputations |
LongitudinalDataset(..., train_mask_rate=0.6) |
Dataset with single-modality active masking |
Trainer(model, optimizer).train(...) |
Training loop with KL annealing and checkpointing |
MIT