CycleGAN: Unpaired Image-to-Image Translation

CycleGAN (Zhu, Park, Isola & Efros, 2017) performs unpaired image-to-image translation: it learns to turn horses into zebras, summer into winter, or photos into Monet-style paintings from two unrelated collections of images, with no pixel-aligned pairs. It extends the GAN framework with a second generator and a cycle-consistency constraint.


1. Architecture

CycleGAN: Two Generators, Two Discriminators Domain Xe.g. horses Domain Ye.g. zebras G : X → Y F : Y → X DYreal zebra? DXreal horse? cycle: F(G(x)) ≈ x cycle: G(F(y)) ≈ y No paired examples needed: only two unaligned image collections.

Two generators learn opposite mappings, G: X → Y and F: Y → X. Two discriminators judge realism in each domain. Adversarial losses alone only ensure G's outputs look like domain Y; they say nothing about whether the output corresponds to the input. That is the job of cycle consistency.

2. Loss Function

Cycle-Consistency Loss xreal horse G G(x)fake zebra F F(G(x))reconstruction ‖F(G(x)) − x‖₁cycle loss Without this term, G could map every horse to the same convincing zebra (mode collapse); requiring invertibility forces G to preserve the content of x.

The full objective is

L = LGAN(G, DY) + LGAN(F, DX) + λcyc Lcyc + λid Lid

The original also uses an image buffer of 50 previously generated images when updating discriminators, which reduces oscillation.

3. Network Design

Generator and PatchGAN Discriminator c7s1-64conv in 2× downstride 2 9× ResBlock256 ch 2× upConvT c7s1-3Tanh PatchGANN×N grid70×70 fields InstanceNorm throughout; the discriminator classifies overlapping patches, which focuses it on local texture and style rather than global layout.

Generators follow Johnson et al.'s style-transfer network: downsample, a stack of residual blocks, upsample. The discriminator is a 70×70 PatchGAN from Pix2Pix that outputs a grid of real/fake scores. Instance normalization is used rather than batch normalization since batch size is typically 1.

4. Core Training Step in PyTorch

import torch, torch.nn as nn, itertools

# G_XY, G_YX: ResNet generators; D_X, D_Y: PatchGAN discriminators (defined elsewhere)
mse, l1 = nn.MSELoss(), nn.L1Loss()
lam_cyc, lam_id = 10.0, 5.0
opt_G = torch.optim.Adam(itertools.chain(G_XY.parameters(), G_YX.parameters()), lr=2e-4, betas=(0.5, 0.999))
opt_D = torch.optim.Adam(itertools.chain(D_X.parameters(), D_Y.parameters()), lr=2e-4, betas=(0.5, 0.999))

def step(x, y):
    # ---- generators ----
    fake_y, fake_x = G_XY(x), G_YX(y)
    pred_fy, pred_fx = D_Y(fake_y), D_X(fake_x)
    loss_gan = mse(pred_fy, torch.ones_like(pred_fy)) + mse(pred_fx, torch.ones_like(pred_fx))
    loss_cyc = l1(G_YX(fake_y), x) + l1(G_XY(fake_x), y)
    loss_id  = l1(G_XY(y), y) + l1(G_YX(x), x)
    loss_G = loss_gan + lam_cyc * loss_cyc + lam_id * loss_id
    opt_G.zero_grad(); loss_G.backward(); opt_G.step()

    # ---- discriminators (LSGAN; 0.5 halves D's learning speed) ----
    def d_loss(D, real, fake):
        pr, pf = D(real), D(fake.detach())
        return 0.5 * (mse(pr, torch.ones_like(pr)) + mse(pf, torch.zeros_like(pf)))
    loss_D = d_loss(D_Y, y, fake_y) + d_loss(D_X, x, fake_x)
    opt_D.zero_grad(); loss_D.backward(); opt_D.step()
    return loss_G.item(), loss_D.item()

Production code adds the 50-image history buffer, linear learning-rate decay over the second half of training, and random crop / flip augmentation.

5. When to Use It, and Limits

CycleGAN shines for appearance changes where geometry stays mostly the same: seasons, weather, artistic style, day/night, synthetic-to-real domain adaptation for training perception models, and some medical modality translation (with caution). If you have paired data, Pix2Pix or a conditional diffusion model will be more faithful.

6. Walking Through One Training Iteration

One Generator Update, Step by Step real x domain X G(x) fake y F(G(x)) reconstructed x D_Y(G(x)) adversarial L1 vs x cycle loss x10 Sum + backprop update G, F

Each iteration samples one image from each domain, usually random-cropped to 256×256 with batch size 1. For the generator step, G turns the horse photo x into a fake zebra G(x); D_Y scores that fake patch by patch, and the least-squares adversarial loss pushes those scores toward 1 ("real"). F then maps the fake zebra back, and the L1 distance between F(G(x)) and x is multiplied by λcyc = 10. The same happens in the other direction for a zebra y. All generator terms are summed and both generators are updated together with one Adam step.

For the discriminator step, D_Y sees a real zebra (target 1) and a fake drawn from the 50-image history buffer (target 0). The LSGAN loss for each discriminator is halved so it learns slower than the generators. The paper's schedule is Adam with learning rate 0.0002 and β1 = 0.5, constant for 100 epochs and then decayed linearly to zero over another 100.

Why the weights look like that: with λcyc too low, outputs drift toward any plausible zebra and lose the input's pose; too high, and G learns a near-identity mapping that barely changes texture. The identity loss is a nudge, not a requirement; it mostly helps when the two domains share color palettes and the generator would otherwise tint everything.

7. Alternatives and Successors

Choosing an Image Translation Method Paired data? aligned pairs Yes: Pix2Pix or conditional diffusion No: CycleGAN geometry preserved No: CUT one-sided, faster Diffusion edit SDEdit, img2img
MethodData neededKey ideaTrade-off
Pix2Pix (Isola et al., 2017)Paired imagesConditional GAN + L1 to the targetMost faithful, but pairs are expensive
CycleGANTwo unpaired setsTwo generators + cycle consistencyDoubles model size; favors minimal edits
UNIT / MUNITUnpairedShared latent space; MUNIT splits content and styleMultimodal outputs, more complex training
CUT (Park et al., 2020)UnpairedPatchwise contrastive loss, one direction onlyFaster and lighter; fewer guarantees on inversion
Diffusion-based editingPretrained diffusion modelNoise the source partway, then denoise with a target prompt or modelHigher quality and flexible, but slower and harder to keep exact structure

For new projects, diffusion-based translation often gives better visual quality, while CycleGAN remains attractive when you need a small, fast, feed-forward model that runs in real time, or when the domains are narrow (two sensor types, two scanners) and you have a few thousand images of each.

8. Evaluating Translation Quality

There is no ground truth for unpaired translation, so evaluation combines several imperfect signals:

9. Practical Training Tips

10. Further Reading

See also Diffusion Models and StyleGAN.