Learning a Basis from Heartbeats

Learn a basis from thousands of recorded heartbeats with the SVD, then use it to compress beats and to flag the ones that do not fit.
Published

October 4, 2026

Key takeaway

The earlier articles chose a basis in advance. Given enough examples of a signal, the singular value decomposition can learn one instead: the directions along which a collection of heartbeats actually varies.

On normal beats from patients it never saw, with the same eight basis vectors for every beat, the learned basis gives a median reconstruction error of 21%, compared with 34% for the DCT and 35% for wavelets. Wavelets that pick their largest coefficients beat by beat close much of the gap. Beats the basis reconstructs poorly are often abnormal, which turns a compression method into a simple anomaly detector, but only for abnormalities that change a beat’s shape.

Introduction

Signals as Vectors described a flute note in the Fourier basis, and Compression via Sparsity described a photograph in the DCT and in wavelets. In both, the basis came first. It was designed from a general belief about signals, that they oscillate or that they are smooth with occasional edges, and it worked well or badly depending on how closely the signal matched that belief.

What if we learned the basis from examples instead? Here we have tens of thousands of examples of one kind of signal: the heartbeat, as recorded by an electrocardiogram (ECG). Can these examples tell us which shapes make a useful basis?

They can. The singular value decomposition (SVD) of a collection of heartbeats produces basis vectors ordered by how much of the collection’s variation each one explains. A few of them describe a typical beat much better than the same number of cosines or wavelets. And because the basis is learned from normal beats, the beats it describes badly are worth a second look.

A heartbeat is a vector

The data is the MIT-BIH Arrhythmia Database, 48 half-hour two-lead ECG recordings from 47 patients, sampled 360 times a second.1 Cardiologists annotated every beat in it: where the beat’s R-peak falls, and what kind of beat it is. Here is six seconds of one recording.

Figure 1: Six seconds of record 233, lead MLII, with its baseline wander removed. Each annotated beat is marked at its R-peak and labelled N (normal) or V (premature ventricular contraction).

A normal beat has three parts: a small P wave as the atria contract, a sharp QRS complex as the ventricles contract, and a broad T wave as they recover. The beats labelled V are premature ventricular contractions (PVCs). Each arrives early, starts in the ventricles rather than through the normal conduction path, and has a wide, distorted QRS complex as a result; in this patient they point downward.

To make the beats comparable, we cut a window of \(n = 256\) samples around each annotated R-peak: 250 ms before it and 458 ms after, enough for the P wave, the QRS complex and most of the T wave. Every window is aligned on its R-peak, so each beat becomes a vector \(\mathbf{x} \in \mathbb{R}^{256}\) whose 91st coordinate is the peak.

Load MIT-BIH and cut it into aligned beats
from pathlib import Path

import numpy as np
import pywt
import wfdb
from scipy.fft import dct
from scipy.ndimage import median_filter
from scipy.stats import mannwhitneyu

# The raw database (90 MB) is downloaded once into a git-ignored cache. CI never
# executes this cell: it renders from the committed frozen output.
DB = Path("../../.cache/mitdb")
if not (DB / "234.atr").exists():
    wfdb.dl_database("mitdb", str(DB))

FS = 360            # samples per second
PRE, N = 90, 256    # 250 ms before the R-peak, 711 ms window
# de Chazal et al. (2004), minus record 202: it comes from the same patient as
# 201, which is in the training set. The paced records 102, 104, 107 and 217
# are excluded, as is standard.
DS1 = [101, 106, 108, 109, 112, 114, 115, 116, 118, 119, 122,
       124, 201, 203, 205, 207, 208, 209, 215, 220, 223, 230]
DS2 = [100, 103, 105, 111, 113, 117, 121, 123, 200, 210,
       212, 213, 214, 219, 221, 222, 228, 231, 232, 233, 234]


def mlii(rec):
    """Lead MLII in millivolts, with baseline wander removed."""
    r = wfdb.rdrecord(str(DB / str(rec)))
    x = r.p_signal[:, r.sig_name.index("MLII")]
    # Two median filters, 200 ms then 600 ms, estimate the baseline.
    return x - median_filter(median_filter(x, 71), 215)


def beats(rec):
    """One N-sample window per annotated beat, aligned on its R-peak."""
    x = mlii(rec)
    ann = wfdb.rdann(str(DB / str(rec)), "atr")
    ok = [(s, c) for s, c in zip(ann.sample, ann.symbol)
          if s >= PRE and s - PRE + N <= len(x)]
    return (np.stack([x[s - PRE:s - PRE + N] for s, _ in ok]),
            np.array([c for _, c in ok]))


def load(recs):
    X, y = zip(*map(beats, recs))
    return np.concatenate(X), np.concatenate(y)


X1, y1 = load(DS1)
X2, y2 = load(DS2)
train = X1[y1 == "N"]   # learn from normal beats only
test = X2[y2 == "N"]

Thirty normal beats from different patients, drawn on top of each other, look remarkably alike:

Figure 2: Thirty normal beats, aligned on their R-peaks (thin lines), and the mean of all normal training beats (thick line).

They are not identical. The height of the QRS complex, the depth of the dip after it and the size and timing of the T wave vary from patient to patient and from beat to beat. But they do not vary in arbitrary ways. Out of all the vectors in \(\mathbb{R}^{256}\), heartbeats occupy a small, structured region. A basis designed for that region should need far fewer coordinates than one designed for signals in general.

Learning the basis

We split the patients in two. Beats from 22 records are for learning the basis and beats from 21 others are for testing it, and no patient contributes to both.2 We learn only from beats annotated as normal, 38,088 of them, so the basis describes what a normal heartbeat looks like.

First, subtract the mean beat \(\boldsymbol{\mu}\), the thick line above. What remains of each beat is how it differs from the average, and that difference is what the basis needs to describe. Stack the \(m\) centred beats as the columns of a matrix, and the SVD writes it as a product of three matrices:

\[ \tilde{X} = \begin{bmatrix} \mathbf{x}_1 - \boldsymbol{\mu} & \cdots & \mathbf{x}_m - \boldsymbol{\mu} \end{bmatrix} = U \Sigma V^{\mathsf T}. \]

The columns \(\mathbf{u}_1, \mathbf{u}_2, \ldots\) of \(U\) are orthonormal vectors in \(\mathbb{R}^{256}\), the same shape as a beat. They are the learned basis. \(\Sigma\) is diagonal, with the singular values \(\sigma_1 \ge \sigma_2 \ge \cdots \ge 0\) on its diagonal; they measure how much of the collection lies along each basis vector. Together, \(\Sigma\) and \(V^{\mathsf T}\) give the coordinates of the training beats in this basis; we will not need them.

Learn the basis with the SVD
mu = train.mean(axis=0)                     # the mean beat
_, S, Vt = np.linalg.svd(train - mu, full_matrices=False)
U = Vt.T                                    # columns: the learned basis vectors
# Singular vectors are defined up to sign; make each one's largest entry positive.
U *= np.sign(U[np.abs(U).argmax(axis=0), np.arange(N)])
energy = S**2 / (S**2).sum()

Here are the first six basis vectors. Each is a shape in time, like a beat:

Figure 3: The first six learned basis vectors, in the same time window as a beat. The thin grey line is the mean beat, rescaled, for reference.

Nobody chose these shapes; they come from the data, and some have a useful interpretation. The first follows the mean beat closely. Adding a multiple of it to the mean makes the QRS complex taller, and subtracting it makes it shorter:

Figure 4: The mean beat μ (thick line), and μ ± c·u₁ (thin lines), where c is twice the standard deviation of the training beats’ first coordinate.

The early basis vectors concentrate around the QRS complex and the T wave, the parts of the beat whose height and timing vary most, while the flat stretch before the P wave contributes almost nothing. Several have alternating lobes around the QRS complex; adding them moves it slightly earlier or later, or makes it wider or narrower. Individual basis vectors should not be over-interpreted, though. They are forced to be orthogonal, so each is shaped partly by the ones before it, and they describe the collection rather than any single beat.

How much does each one account for? In Compression via Sparsity we called the squared length of a vector its energy. The total energy of the centred beats splits exactly among the basis vectors, \(\sigma_k^2\) to the \(k\)-th:

\[ \sum_{j=1}^{m} \|\mathbf{x}_j - \boldsymbol{\mu}\|_2^2 = \sum_{k} \sigma_k^2. \]

So \(\sigma_k^2 / \sum_i \sigma_i^2\) is the share of the collection’s variation that the \(k\)-th basis vector accounts for.

Figure 5: Share of the training beats’ variation (energy after subtracting the mean) captured by each learned basis vector alone (bars) and by the first r together (line).

The first basis vector alone accounts for 42% of the variation, the first four for 76%, and the first eight for 89%. A beat has 256 samples, but most of the ways normal beats differ from one another fit into a handful of directions.

Reconstructing a beat

The recipe is the one from Compression via Sparsity, with the mean taken out first. A beat’s coordinates in the learned basis are \(\mathbf{s} = U^{\mathsf T}(\mathbf{x} - \boldsymbol{\mu})\), and keeping the first \(r\) of them gives the reconstruction

\[ \hat{\mathbf{x}} = \boldsymbol{\mu} + \sum_{k=1}^{r} s_k \mathbf{u}_k. \]

We measure the error with the percentage root-mean-square difference, the usual figure of merit in ECG compression:3

\[ \mathrm{PRD} = 100 \cdot \frac{\|\mathbf{x} - \hat{\mathbf{x}}\|_2}{\|\mathbf{x}\|_2}. \]

The beats below come from the test patients; the basis has never seen them. Start at \(r = 0\), where the reconstruction is just the mean beat, and add basis vectors. Then switch to the ventricular premature beat and compare how quickly its reconstruction improves.

Figure 6: A held-out beat (thin line) and its reconstruction from the mean beat plus r learned basis vectors (thick line). Examples were selected by median unnormalized reconstruction error at r = 8.

A few basis vectors adjust the mean’s height and shape toward the beat in question, and by \(r = 32\) only small differences remain for the normal beat, a PRD of 6%. The PVC’s wider QRS complex is poorly captured by the first few learned shapes, which come from normal beats. Its reconstruction improves more slowly, and at \(r = 32\) its PRD is still 17%.

Learned versus fixed bases

Learning these directions from centred data is principal component analysis (PCA); the plots use that name for the learned basis.4 How much does learning help? We can run the same experiment with the DCT and with db4 wavelets, the bases from Compression via Sparsity, on the 34,368 normal beats from the test patients. To make the comparison fair, every basis gets the learned mean beat subtracted first; without it, the fixed bases spend their first coefficients rebuilding the average shape and do much worse.5

There are two ways to choose which \(r\) coefficients to keep:

  • The same \(r\) for every beat. PCA keeps its first \(r\) basis vectors, the DCT its \(r\) lowest frequencies, and the wavelets their first \(r\) coefficients in coarse-to-fine order.
  • The \(r\) largest per beat. Each beat keeps whichever \(r\) of its coefficients are largest in absolute value, as Compression via Sparsity did. Every beat can choose differently.

For the first rule, there is a reason to expect the learned basis to do well. The Eckart–Young theorem says that among all \(r\)-dimensional subspaces of \(\mathbb{R}^{256}\), the one spanned by \(\mathbf{u}_1, \ldots, \mathbf{u}_r\) minimizes the total squared error of projecting the training beats onto it.6 The minimum is \(\sum_{k > r} \sigma_k^2\), the energy left out. That is the precise sense in which the learned basis is optimal: on average, over the beats it learned from, with the same \(r\) basis vectors for every beat. It promises nothing about any single beat, about beats from other patients, or about the second rule. Those have to be measured.

Figure 7: Median PRD over the normal beats of the 21 test records, by number of coefficients kept. The dashed line is the learned basis on its own training beats.

With the same \(r\) for every beat, the learned basis has the lowest median error at every \(r\). At eight coefficients its median PRD is 21%, compared with 34% for the DCT and 35% for db4. It spends its few coordinates on the variations that are common in these beats.

The dashed line shows the learned basis on the beats it was trained on. At \(r = 8\), median PRD rises from 17% on the training beats to 21% on the test beats, where the learned basis still outperforms both fixed transforms. The basis learned from one group of patients transfers to another.

Switch to \(r\) largest per beat and every basis improves, db4 most of all, because a wavelet coefficient is local in time. A beat that needs detail around its QRS complex can spend its budget there and nowhere else. The learned basis still leads under this rule, 15% against 19% for db4 at \(r = 8\), but the gap is much smaller. Adaptive wavelets even beat the learned basis under the fixed rule: 10% against 12% at \(r = 16\).

That comparison is not quite even. A beat that keeps its largest coefficients must also record which ones it kept, about 8 bits of position for each of 256 possible coefficients, while the fixed rule needs no positions at all. And db4 still relied on the learned mean beat. Learning the basis from the data and letting each signal choose its own coefficients are two different ways to improve on a generic transform, and they combine: the learned basis with its largest coefficients per beat is the lowest solid curve of all.

Finding beats that do not fit

The learned coordinates are useful beyond compression. The first two, \(s_1\) and \(s_2\), place every beat in a plane. Here are 250 test beats of each of five types: normal beats, beats from patients with left or right bundle branch block (in which one branch of the heart’s conduction system is blocked), and premature atrial and ventricular contractions.

Figure 8: Test beats by their first two coordinates in the learned basis, 250 of each type.

Although the basis was fitted only to normal beats, its first two coordinates also reveal differences among beat types. Many PVCs and right bundle branch block beats sit away from the normal beats. The overlap is large too: atrial premature beats fall almost entirely among the normal ones, and left bundle branch block beats and some PVCs overlap them as well. Two coordinates are not enough to tell the types apart.

A more direct signal is the reconstruction error itself. The basis describes normal beats, so a beat it reconstructs badly is unlike the beats it learned from. We call the size of the residual, \(\|(\mathbf{x} - \boldsymbol{\mu}) - U_r U_r^{\mathsf T}(\mathbf{x} - \boldsymbol{\mu})\|_2\) in mV, the beat’s anomaly score, where \(U_r\) holds the first \(r\) basis vectors. Unlike the PRD, it is not divided by the size of the beat: for anomaly detection we want to know how much of the beat the basis misses, not what fraction.

Figure 9: Anomaly scores of the sampled test beats by type. The number at the right of each row is the AUC over all the test beats of that type: the probability that such a beat scores higher than a random normal beat.

At \(r = 8\), a PVC scores higher than a normal beat with probability 0.88, and a right bundle branch block beat with probability 0.78. For a detector fitted only to normal beats, that is a useful signal.

The atrial premature beats are the exception. Their AUC is 0.40, no better than chance; they look slightly more normal than normal beats do. An atrial premature beat starts in the wrong place in the atria, but it reaches the ventricles through the normal conduction path, so its QRS complex has the normal shape. What makes it abnormal is that it arrives early. Aligning and isolating the beats keeps the timing within a beat, such as where its T wave falls, but removes the intervals between beats that would tell us whether a beat arrived early. Arrhythmia classifiers combine beat shape with those intervals for exactly this reason.

Conclusion

Learning a basis gives us a compact description of the variation in normal heartbeat shapes. With the same few basis vectors for every beat, it describes normal beats from new patients better than cosines or wavelets do, and wavelets that choose their coefficients beat by beat close much of that gap.

The reconstruction error can then flag beats whose shape departs from the normal ones, such as PVCs, but it misses abnormalities expressed mainly in the intervals between beats. What the model can detect depends on what we put into the vector.

This article uses the SVD as a tool. A later article will look at how it works, and a playground will apply a learned basis to images.

Footnotes

  1. George B. Moody and Roger G. Mark, The impact of the MIT-BIH Arrhythmia Database, IEEE Engineering in Medicine and Biology 20(3), 45–50, 2001. The data is distributed by PhysioNet under the Open Data Commons Attribution License v1.0: Ary L. Goldberger et al., PhysioBank, PhysioToolkit, and PhysioNet, Circulation 101(23), e215–e220, 2000. This article uses lead MLII, with baseline wander removed by two median filters.↩︎

  2. The split follows Philip de Chazal, Maria O’Dwyer, and Richard B. Reilly, Automatic classification of heartbeats using ECG morphology and heartbeat interval features, IEEE Transactions on Biomedical Engineering 51(7), 1196–1206, 2004, with one change: record 202 is left out of the test set, because it comes from the same patient as record 201, which is in the training set. The four records with paced beats are excluded.↩︎

  3. PRD figures in the ECG compression literature are often computed against the raw recording, which carries a constant offset that makes the denominator large and the PRD small. Here the denominator is the beat after baseline removal, which is the stricter measure. The numbers are not comparable with published codec results.↩︎

  4. The covariance matrix of the beats is \(\tilde{X}\tilde{X}^{\mathsf T}/(m-1) = U \Sigma^2 U^{\mathsf T}/(m-1)\), so the \(\mathbf{u}_k\) are its eigenvectors, the principal components.↩︎

  5. Without the mean beat, the median PRD at \(r = 8\) is 77% for the DCT and 82% for db4 with the same \(r\) for every beat.↩︎

  6. Carl Eckart and Gale Young, The approximation of one matrix by another of lower rank, Psychometrika 1(3), 211–218, 1936.↩︎