PPG Heart Rate Estimator β€” CNN/LSTM with TFLite Deployment

Lightweight CNN/LSTM for heart rate estimation from raw PPG signals. Deployed as TensorFlow Lite for mobile and embedded targets.

Results

Model Size MAE Within Β±5 BPM
Baseline FP32 Keras 99.6 KB 0.54 BPM 100%
FP16 TFLite 82.5 KB ~0.55 BPM ~100%
Dynamic Quant TFLite 63.5 KB ~0.57 BPM ~100%
  • Median error: 0.34 BPM
  • P95 error: 1.86 BPM
  • Max error: 4.36 BPM

Architecture

Input (1000, 1) β€” 8 sec @ 125Hz
β†’ Conv1D(16,k=7) β†’ BN β†’ MaxPool(2)
β†’ Conv1D(32,k=5) β†’ BN β†’ MaxPool(2)
β†’ Conv1D(64,k=3) β†’ BN β†’ MaxPool(2)
β†’ LSTM(32, return_sequences=True) β†’ Dropout
β†’ LSTM(16)
β†’ Dense(32) β†’ Dropout
β†’ Dense(1)  β€” BPM regression

Total params: 25,505 (~100KB FP32)

Predicted vs True

Scatter

Error Distribution

Histogram

MAE by HR Range

MAE by Range

Training Curves

Training

Usage

import tensorflow as tf
import numpy as np

interpreter = tf.lite.Interpreter(
    model_path="ppg_hr_dynamic.tflite",
    experimental_delegates=[tf.lite.load_delegate('tensorflowlite_flex')]
)
interpreter.allocate_tensors()
inp = interpreter.get_input_details()
out = interpreter.get_output_details()

# sample: (1, 1000, 1) float32 β€” normalized PPG window
sample = np.random.randn(1, 1000, 1).astype(np.float32)
interpreter.set_tensor(inp[0]['index'], sample)
interpreter.invoke()
hr_bpm = interpreter.get_tensor(out[0]['index'])[0][0]
print(f"Estimated HR: {hr_bpm:.1f} BPM")

Notes

LSTM layers require the Flex delegate for TFLite inference. On Android: add tensorflow-lite-select-tf-ops dependency.

Links

Downloads last month
32
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support