Medical AI · Deep learning & imaging · Lesson 4 of 5
ECG and physiological time-series models
Reviewed by Dr. Ram Kumar G, MBBS, MD (Internal Medicine), MPH (USA), on · Last updated
- Intermediate
- Technical focus
- About 30 minutes
- Includes exercise
Before this lesson: CNNs for chest X-ray and retinal images
In short
Physiological signals such as ECGs, oxygen saturation and continuous glucose readings are time series: measurements sampled many times per second or minute. They can be modelled with features designed by experts, such as the variability of the interval between heartbeats, or with neural networks such as 1D CNNs that learn directly from the raw signal. On clean signals both can work well; on noisy signals hand-made features that depend on detecting every beat break down faster. Signal quality, device type and sampling rate shape what any model can do.
You will learn to
- Describe the properties of physiological time series: sampling rate, noise, artefacts and length
- Build a feature-based rhythm classifier from detected beats
- Train a 1D CNN on raw signals in PyTorch
- Explain how signal quality affects each approach, and how to handle it
Signals as data
Deep learning has reached cardiologist-level performance at classifying rhythms from single-lead ECGs in a research setting (Hannun et al., 2019), and has even detected signs of past or future atrial fibrillation from ECGs recorded in sinus rhythm (Attia et al., 2019). Wearable rhythm notifications have been studied in very large populations (Perez et al., 2019).
| Signal | Typical sampling | AI examples |
|---|---|---|
| Electrocardiogram (ECG) | 250–500 samples per second, 1 to 12 leads | Arrhythmia detection; predicting low ejection fraction or atrial fibrillation risk from sinus-rhythm ECGs |
| Photoplethysmography (PPG) | 25–100 per second (wearables, pulse oximeters) | Irregular-rhythm notifications on watches |
| Continuous glucose monitoring (CGM) | Every 1–15 minutes | Glucose forecasting and hypoglycaemia alerts (see the applied track) |
| ICU monitoring | Seconds to minutes, many channels | Deterioration and sepsis prediction |
Synthetic rhythm strips
The generator below makes crude 10-second single-lead strips. Half have an irregularly irregular rhythm like atrial fibrillation; the regular strips include occasional ectopic beats, which make the task realistic rather than trivial.
import numpy as np
FS, SECONDS = 100, 10 # 100 samples per second (real ECGs are often 250-500 Hz), 10-second strips
def synthetic_strip(irregular, rng, noise):
"""A crude single-lead ECG-like strip: a spike per beat, baseline wander and noise.
irregular=True mimics atrial fibrillation; regular strips include occasional ectopic beats."""
t = np.arange(FS * SECONDS) / FS
signal = 0.15 * np.sin(2 * np.pi * 0.3 * t) + rng.normal(0, noise, t.size)
mean_rr, beat = rng.uniform(0.65, 1.0), rng.uniform(0, 0.5) # 60-90 beats per minute
while beat < SECONDS:
signal += rng.uniform(0.6, 1.2) * np.exp(-((t - beat) ** 2) / (2 * 0.012 ** 2))
if irregular:
beat += mean_rr * rng.uniform(0.6, 1.4)
elif rng.random() < 0.08:
beat += mean_rr * 0.6 # occasional ectopic beat
else:
beat += mean_rr * rng.normal(1, 0.03)
return signal
def make_dataset(n, noise, seed=0):
rng = np.random.default_rng(seed)
labels = rng.integers(0, 2, n) # 1 = irregular rhythm
return np.stack([synthetic_strip(bool(l), rng, noise) for l in labels]), labels
signals, labels = make_dataset(2000, noise=0.08)
print(f"{signals.shape[0]} strips of {signals.shape[1]} samples ({SECONDS} s at {FS} Hz); "
f"{labels.mean():.0%} irregular")
# 2000 strips of 1000 samples (10 s at 100 Hz); 52% irregularApproach 1: expert features
Clinicians recognise atrial fibrillation partly by irregular intervals between beats. The feature approach detects each beat, measures the RR intervals and summarises their variability.
from scipy.signal import find_peaks
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
def rr_features(signal):
peaks, _ = find_peaks(signal, height=0.4, distance=int(0.3 * FS)) # detect beats
rr = np.diff(peaks) / FS
return [np.std(rr) / np.mean(rr), len(peaks)] # RR variability, beat count
def feature_model_auroc(signals, labels):
feats = np.array([rr_features(s) for s in signals])
tr, te = train_test_split(np.arange(len(labels)), test_size=0.3, random_state=0, stratify=labels)
p = LogisticRegression().fit(feats[tr], labels[tr]).predict_proba(feats[te])[:, 1]
return roc_auc_score(labels[te], p)
print(f"RR-variability features + logistic regression: AUROC {feature_model_auroc(signals, labels):.3f}")
# RR-variability features + logistic regression: AUROC 0.917- Two interpretable numbers give an AUROC of 0.917. Ectopic beats in regular strips also raise RR variability, which is why it is not perfect, a familiar problem for real rhythm algorithms.
Approach 2: a 1D CNN on the raw signal
import torch
from torch import nn
def cnn_auroc(signals, labels, epochs=15):
torch.manual_seed(0)
model = nn.Sequential(
nn.Conv1d(1, 8, kernel_size=7, padding=3), nn.ReLU(), nn.MaxPool1d(4),
nn.Conv1d(8, 16, kernel_size=7, padding=3), nn.ReLU(), nn.MaxPool1d(4),
nn.Conv1d(16, 16, kernel_size=7, padding=3), nn.ReLU(), nn.AdaptiveAvgPool1d(1),
nn.Flatten(), nn.Linear(16, 2))
X, y = torch.tensor(signals, dtype=torch.float32).unsqueeze(1), torch.tensor(labels)
tr, te = train_test_split(np.arange(len(labels)), test_size=0.3, random_state=0, stratify=labels)
tr = torch.tensor(tr)
opt = torch.optim.Adam(model.parameters(), lr=3e-3)
for _ in range(epochs):
for batch in tr[torch.randperm(len(tr))].split(64):
opt.zero_grad()
nn.functional.cross_entropy(model(X[batch]), y[batch]).backward()
opt.step()
with torch.no_grad():
p = model(X[te]).softmax(1)[:, 1].numpy()
return roc_auc_score(labels[te], p)
print(f"1D CNN on the raw signal: AUROC {cnn_auroc(signals, labels):.3f}")
# 1D CNN on the raw signal: AUROC 0.945- The CNN learns its own features from the waveform and does slightly better (0.945). It needs more data and is harder to interpret than two RR statistics.
When the signal is noisy
noisy_signals, noisy_labels = make_dataset(2000, noise=0.25, seed=1)
print(f"Noisy strips, RR features: AUROC {feature_model_auroc(noisy_signals, noisy_labels):.3f}")
print(f"Noisy strips, 1D CNN: AUROC {cnn_auroc(noisy_signals, noisy_labels):.3f}")
# Noisy strips, RR features: AUROC 0.601
# Noisy strips, 1D CNN: AUROC 0.812- With more noise, the beat detector finds false beats and misses real ones, so the RR features become almost useless (0.601). The CNN degrades too, but less (0.812).
- Neither is acceptable for clinical use on such signals. Real systems assess signal quality first and decline to classify unreadable recordings, as wearable rhythm features do.
Practical points for physiological signals
- Record the device, lead placement and sampling rate; models trained on one device often transfer poorly to another.
- Handle artefacts explicitly (movement, poor contact, pacing spikes) and report how many recordings were excluded as unreadable.
- Split data by patient, not by recording or segment, to avoid leakage.
Exercise
Compare feature-based and deep-learning rhythm classifiers on synthetic and real ECGs.
Data: Synthetic strips in the notebook; PTB-XL 12-lead ECG dataset from PhysioNet (optional) (Generated in the notebook; PTB-XL under CC BY 4.0)
- Reproduce both approaches on clean and noisy strips, and plot an example of each class.
- Add a signal-quality score (for example, the fraction of detected beats with plausible RR intervals) and exclude low-quality strips; report AUROC and the share excluded.
- Add a third feature, the proportion of successive RR differences above 50 ms, and see whether the feature model improves.
- Optional: train the 1D CNN on PTB-XL lead II to detect atrial fibrillation, splitting by patient as the dataset recommends.
Open the notebook in ColabView on GitHubWorked solution coming soon
Key takeaways
- Physiological signals are time series whose quality depends on device, sampling and artefacts.
- Expert features are interpretable; 1D CNNs learn their own features from raw signals.
- Features that depend on detecting every beat fail quickly on noisy signals.
- Assess signal quality, decline unreadable recordings and split data by patient.
Terms used: Convolutional neural network (CNN), Data leakage, Dataset shift
Related: Applied: forecasting glucose from CGM data, Continuous glucose monitoring (CGM), Healthcare data types
FAQ
Can AI detect atrial fibrillation from an ECG?
Yes. Research models classify rhythms from ECGs with high accuracy, and some can even identify patients likely to have atrial fibrillation from ECGs recorded in normal rhythm. Clinical use needs validation for the specific device and population.
What is a 1D CNN?
A convolutional neural network whose filters slide along one dimension, time, instead of across an image. It learns patterns such as beat shapes and rhythms directly from the raw signal.
Should I use hand-made features or deep learning for signals?
Features are interpretable and work with less data; deep learning can capture subtler patterns and cope better with noise, but needs more data. Many systems combine both.
Why does sampling rate matter?
It limits which details can be captured. A model trained on 500 Hz ECGs may not work on 100 Hz wearable data without resampling and revalidation.
What public ECG datasets exist?
PTB-XL (21,837 12-lead ECGs from 18,885 patients, CC BY 4.0) and other datasets on PhysioNet are widely used for research and teaching.
Sources
- Hannun AY, et al. Cardiologist-level arrhythmia detection and classification in ambulatory electrocardiograms using a deep neural network. Nature Medicine 2019;25:65–69. doi:10.1038/s41591-018-0268-3
- Attia ZI, et al. An artificial intelligence-enabled ECG algorithm for the identification of patients with atrial fibrillation during sinus rhythm. Lancet 2019;394:861–867. doi:10.1016/S0140-6736(19)31721-0
- Perez MV, et al. Large-scale assessment of a smartwatch to identify atrial fibrillation. N Engl J Med 2019;381:1909–1917. doi:10.1056/NEJMoa1901183
- Wagner P, et al. PTB-XL, a large publicly available electrocardiography dataset. Scientific Data 2020;7:154. doi:10.1038/s41597-020-0495-6
Educational content only. This material teaches methods; it is not clinical advice. Code, models and examples here are not validated medical devices and must not be used to make decisions about real patients without proper clinical validation, governance and any required regulatory approval. Examples use synthetic or openly licensed de-identified data only. Never paste identifiable patient information into public AI tools. Mentions of models, tools or vendors are for identification, not endorsement.