Original Repository: This work is based on the official BeliefPPG implementation from ETH Zürich's Sensing, Interaction & Perception Lab. Original code and paper available at: https://github.com/eth-siplab/BeliefPPG
BeliefPPG: Uncertainty-aware Heart Rate Estimation from PPG signals via Belief Propagation (UAI 2023, Official Code)
Valentin Bieri*2, Paul Streli*1, Berken Utku Demirel1, Christian Holz1
1 Sensing, Interaction & Perception Lab, Department of Computer Science, ETH Zürich, Switzerland
2 MSc Student, Department of Computer Science, ETH Zürich, Switzerland
* These authors contributed equally to this work
This repository contains code to run experiments on multiple supported datasets. Taking multi-channel PPG and Accelerometer signals as input, BeliefPPG predicts the instantaneous heart rate and provides an uncertainty estimate for the prediction.
- Quick Start
- Datasets
- Training and Evaluation
- Quantization
- Evaluation
- Inference
- Results Organization
- Pruning
- Run the Whole Pipeline
- Todo
Create environments
conda create -n beliefppg python=3.10
conda activate beliefppg
cd BeliefPPG
pip install -r requirements.txt
For heart rate inference from PPG and accelerometer data, please refer to:
beliefppg/inference/inference.py: Direct inference scriptinference.ipynb: notebook for inference
We provide a shell script which downloads the datasets DaLiA, WESAD, BAMI-1 and BAMI-2 from their original hosts. Run the following line in your terminal:
sh download_data.sh
- Note that WESAD does not natively include ground-truth heart rate. Labels can be generated from the provided ECG recordings instead.
- Support for the IEEE datasets is implemented, but the original data format seems to be no longer available. You can download it in the new format under https://zenodo.org/record/3902710#.ZGM9l3ZBy3C and restructure/convert the files or implement your own file reader.
The training scripts are located in the scripts/ directory. You can create a single script to run multiple training tasks, for example scripts/test_all_comb.sh.
Customize your training script:
python -m train_eval \
--dataset dalia \
--out_dir my_models \
--use_wandb
Run the following in your terminal:
bash scripts/train_with_timebackbone_with_beliefpropagation_without_imu.sh
This will run LoSo cross-validation on the DaLiA dataset.
Results, that is the MAEs, predictions and models, are saved in the output directory, which can be specified with the --out_dir argument. Note that you may have to reinstall h5py in order for the models to be saved correctly.
You can customize the model architecture and training process with additional arguments:
Command Line Arguments:
--use_imu: Includes IMU/accelerometer data as additional input channels for improved robustness--no_time_backbone: Disables the time-domain CNN-LSTM backbone, using only frequency domain processing--no_belief_propagation: Disables belief propagation inference, using standard neural network predictions--dataset dalia: Specify dataset (custom datasets require rewriting the reading method inbeliefppg/file_reader.py)--use_wandb: Enable Weights&Biases logging for training monitoring--out_dir: Specify custom output directory for results and models
We highly recommend that you use Weights&Biases to monitor model training. Make sure to log into W&B in the console and then simply add the argument --use_wandb to save additional plots and logging information.
Training Results
Each training session creates a folder with a name pattern like:
dalia_DaLia_S5_2025-07-02_10-40-08_with_timebackbone_with_beliefpropagation_with_imu
The folder contains:
- Model weights: Check
inference_model(keras/npz/json) if using belief propagation, orraw_model(keras/npz/json) if not - Model metrics:
final_results.txt
Training Function (train_eval.py)
The training process includes:
- Neural network training on training data
- CPU-based testing
- Fitting belief propagation state transition matrix on training data (if belief propagation is enabled)
FP16 Quantization:
- Modify the model path in
scripts/quantization_FP16.sh - Run:
bash scripts/quantization_FP16.shFP16 quantization generates the same format as the original model (keras, npz, json files).
INT8 Quantization:
- Modify the model path in
scripts/quantization_INT8.sh - Run:
bash scripts/quantization_INT8.shINT8 quantization generates tflite files.
- Without belief propagation:
raw_model - With belief propagation:
inference_model(FP16) orraw_model+prior_layer.npz/prior_layer_int8.npz(INT8, for storing fitted state transition matrix)
- Modify the model path in
scripts/evaluation.sh - Run:
bash scripts/evaluation.sh
The code will test all model files in the specified directory (e.g., my_models) and calculate relevant metrics. It generates quantized_results.txt in each folder for the model (fp32, fp16, int8) results. Finally, it creates an overall_quantized_results.csv file in the specified directory.
Use inference.ipynb and modify the data/model paths as needed. Currently supports original models (fp32), fp16, Int8.
| Parameter | Type | Description |
|---|---|---|
ppg |
np.array |
PPG signal data with shape (n_samples, n_channels) |
ppg_freq |
int |
Sampling frequency of the PPG signal in Hz |
acc |
Optional[np.ndarray] |
Accelerometer signal data with shape (n_samples, n_channels) |
acc_freq |
Optional[int] |
Sampling frequency of the accelerometer signal in Hz |
decoding |
str |
Decoding method to use, either "sumproduct" or "viterbi" |
uncertainty |
str |
Metric for predictive uncertainty, either "entropy" or "std" |
batch_size |
int |
Batch size for inference |
filter_lowcut |
float |
Lowcut frequency for filtering |
filter_highcut |
float |
Highcut frequency for filtering |
use_gpu |
bool |
Whether to use GPU for inference or not |
model_path |
str |
Path to the model |
Modify the data and paths in the notebook:
Note the path:
- without_belief_propagation:raw_model, raw_model_fp16, raw_model_int8
- with_belief_propagation:inference_model, inference_model_fp16, raw_model_int8
base_path="my_models_trial"
folder1="dalia_DaLia_S5_2025-08-01_18-01-48_with_timebackbone_with_beliefpropagation_with_imu"
folder2="dalia_DaLia_S5_2025-08-01_19-09-08_with_timebackbone_without_beliefpropagation_with_imu"
keras_path=".keras"
tflite_path=".tflite"
path1=f"{base_path}/{folder1}/inference_model{keras_path}"
path2=f"{base_path}/{folder1}/inference_model_fp16{keras_path}"
path3=f"{base_path}/{folder1}/raw_model_int8{tflite_path}"
path4=f"{base_path}/{folder2}/raw_model{keras_path}"
path5=f"{base_path}/{folder2}/raw_model_fp16{keras_path}"
path6=f"{base_path}/{folder2}/raw_model_int8{tflite_path}"
model_paths = [
path1,
path2,
path3,
path4,
path5,
path6
]- Modify the model path in
scripts/org_results.sh:
python analyze_tools/extract_quantized_model_results.py --model_dir /home/sw-sh/BeliefPPG/my_models_trial- Run:
bash scripts/org_results.shThe code reads the overall_quantized_results.csv generated during evaluation and extracts/organizes results according to desired metrics, creating an xlsx file in the analyze_tools folder.
5 different pruning strategies are included in prune_strategy.txt
You can replace your beliefppg.py with beliefppg0/1/2.py and change timedomain_backbone.py.
Available Pruning Schemes:
- prune1: 2×SeparableConv, 1D Conv in Unet, 2×Conv, 2×GRU in time backbone with hidden size 64
- prune2: 2×SeparableConv, 1D Conv in Unet, 2×Conv, 2×GRU in time backbone with hidden size 64, only frequency attention
- prune3: 2×SeparableConv, 1D Conv in Unet, 2×Conv, 2×GRU in time backbone with hidden size 48, only frequency attention
- prune4: 2×SeparableConv, 1D Conv in Unet, 2×Conv, 1×GRU in time backbone with hidden size 64, only frequency attention
- prune5: 2×SeparableConv, 1D Conv in Unet, 1×Conv, 2×GRU in time backbone with hidden size 64, only frequency attention
beliefppg File Naming:
belief_ppg0: originalbelief_ppg1(prune1): 2×SeparableConv, 1D Conv in Unetbelief_ppg2(prune2,3,4,5): 2×SeparableConv, 1D Conv in Unet, only frequency attention
To use a specific pruning strategy, rename the corresponding file to belief_ppg.py.
Timedomain Backbone Modifications:
- prune1: 2×Conv, 2×GRU in time backbone with hidden size 64
- prune2: 2×Conv, 2×GRU in time backbone with hidden size 64
- prune3: 2×Conv, 2×GRU in time backbone with hidden size 48
- prune4: 2×Conv, 1×GRU in time backbone with hidden size 64
- prune5: 1×Conv, 2×GRU in time backbone with hidden size 64
train_eval -> quantization -> evaluation -> organize_results
bash scripts/train_eval_quantization_evaluation_pipeline.sh
- Add customized arguments of whether to use imu/timebackbone/beliefpropagation
- Add evaluation for memory/cpu usage/#parameters
- Prune strategy
- Organize results
- Quantization FP16
- Quantization Int8
- Modify extract_model_resutls.py to get both original and quantized models' performance
- Create a script for the whole pipeline
- Modify inference script to support int8 models
- Customized data loader
This project is released under the MIT license.
