Skip to content

Pulse2Pulse GAN — Visual Guide

Pulse2Pulse is a WaveGAN-style GAN. It turns noise with the same shape as an ECG into an ECG, using a 1-D U-Net generator. A large convolutional critic scores real and generated ECGs, and training uses the WGAN-GP objective. Code: src/ecgen/families/gan/pulse2pulse.py. Written reference: Pulse2Pulse GAN.

Adversarial setup

z ~ U(−1,1) (B,8,5000) Generator GU-Net · tanh fake G(z) real x (first 8 leads) Critic Dscore ∈ ℝ D(x) D(G(z)) The critic learns to push D(x) up and D(G(z)) down; the generator learns to push D(G(z)) up.
Hover over a block for details.

Generator: a 1-D U-Net

Six strided convolutions (kernel 25) compress the noise from length 5000 down to 2. The decoder upsamples it back, and at four resolutions it concatenates the encoder features of the same length (skip connections). Hover over a layer:

Defaults: model_size = 50, num_channels = 8. The total stride is 2·5·2·5·5·5 = 2500, so seq_length must be a multiple of 2500.

Critic: WaveGANDiscriminator

Hover over a layer. The final Linear layer is created on the first forward pass, so its size follows the input length.

Phase shuffle

After each of the first six critic convolutions, PhaseShuffle(2) shifts every example in the batch by a random k ∈ {−2, …, 2} samples and fills the gap with reflection padding. This stops the critic from judging ECGs by the exact sample-level phase of upsampling artefacts:

One feature channel, 36 time steps. Each example in a batch gets its own k.

How the losses are computed

Training uses manual optimisation with two Adam optimisers (lr = 1e-4, betas = (0.5, 0.9)).

\[ \mathcal{L}_D = \underbrace{D(G(z)) - D(x)}_{\text{negative Wasserstein estimate}} + \lambda\, \mathbb{E}\big[(\lVert \nabla_{\hat{x}} D(\hat{x}) \rVert_2 - 1)^2\big], \quad \hat{x} = \alpha x + (1-\alpha)\,G(z),\; \alpha \sim U(0,1),\; \lambda = 10 \]
\[ \mathcal{L}_G = -\,D(G(z)) \]
Critic stepevery batch · G(z) is detached x sg[G(z)] x̂ = mix(α) D − mean D(x) + mean D(G(z)) + λ (‖∇ₓ̂ D(x̂)‖₂ − 1)² + ℒ_D → opt_d updates D only Generator stepwhen (batch_idx + 1) % n_critic == 0 new z G D − mean D(G(z)) ℒ_G → opt_g through D (not stepped) into G
Hover over the loss terms.

The gradient-penalty interpolation

The penalty is evaluated at random points on straight lines between real and generated ECGs:

α = 1 gives the real ECG, α = 0 the generated one. During training every example in the batch gets its own α.

Update schedule

Validation does not update anything. It logs val/d_wasserstein = D(x) − D(G(z)) and val_loss = −val/d_wasserstein, which is used for checkpointing.