DCGAN (Deep Convolutional GAN, Radford, Metz & Chintala, 2015) was the first set of architecture guidelines that made GAN training reliably work with convolutional networks. Before it, convolutional GANs were notoriously hard to train; after it, a fairly fixed recipe produced recognizable 64×64 bedrooms, faces and objects. It remains the standard teaching baseline and the skeleton many later GANs extend.
The generator projects a 100-dimensional noise vector into a small 4×4 feature map with many channels, then repeatedly doubles spatial resolution using transposed convolutions (sometimes called fractionally-strided convolutions). Each stage halves the channel count, so compute is spread fairly evenly across resolutions.
The discriminator is essentially a CNN image classifier with the pooling layers replaced by stride-2 convolutions. Mirroring the generator keeps the two networks roughly matched in capacity, which helps prevent either from overpowering the other.
β1 = 0.5; weights initialized from N(0, 0.02).These are heuristics, not theorems; they were found empirically. Later work replaced BatchNorm in D with spectral normalization and showed the recipe is still sensitive to dataset and resolution. DCGAN does not scale well beyond about 128×128 without progressive growing or other techniques.
import torch, torch.nn as nn
nz, ngf, ndf, nc = 100, 64, 64, 3
def init_weights(m):
if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
nn.init.normal_(m.weight, 0.0, 0.02)
elif isinstance(m, nn.BatchNorm2d):
nn.init.normal_(m.weight, 1.0, 0.02); nn.init.zeros_(m.bias)
G = nn.Sequential( # input: (B, nz, 1, 1)
nn.ConvTranspose2d(nz, ngf*8, 4, 1, 0, bias=False), nn.BatchNorm2d(ngf*8), nn.ReLU(True), # 4x4
nn.ConvTranspose2d(ngf*8, ngf*4, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf*4), nn.ReLU(True), # 8x8
nn.ConvTranspose2d(ngf*4, ngf*2, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf*2), nn.ReLU(True), # 16x16
nn.ConvTranspose2d(ngf*2, ngf, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf), nn.ReLU(True), # 32x32
nn.ConvTranspose2d(ngf, nc, 4, 2, 1, bias=False), nn.Tanh()) # 64x64
D = nn.Sequential( # input: (B, 3, 64, 64)
nn.Conv2d(nc, ndf, 4, 2, 1, bias=False), nn.LeakyReLU(0.2, True),
nn.Conv2d(ndf, ndf*2, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf*2), nn.LeakyReLU(0.2, True),
nn.Conv2d(ndf*2, ndf*4, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf*4), nn.LeakyReLU(0.2, True),
nn.Conv2d(ndf*4, ndf*8, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf*8), nn.LeakyReLU(0.2, True),
nn.Conv2d(ndf*8, 1, 4, 1, 0, bias=False), nn.Flatten()) # (B, 1) logits
G.apply(init_weights); D.apply(init_weights)
opt_g = torch.optim.Adam(G.parameters(), lr=2e-4, betas=(0.5, 0.999))
opt_d = torch.optim.Adam(D.parameters(), lr=2e-4, betas=(0.5, 0.999))
bce = nn.BCEWithLogitsLoss()
def train_step(real): # real: (B,3,64,64) in [-1,1]
b = real.size(0)
z = torch.randn(b, nz, 1, 1, device=real.device)
fake = G(z)
loss_d = bce(D(real), torch.ones(b, 1, device=real.device)) + \
bce(D(fake.detach()), torch.zeros(b, 1, device=real.device))
opt_d.zero_grad(); loss_d.backward(); opt_d.step()
loss_g = bce(D(fake), torch.ones(b, 1, device=real.device))
opt_g.zero_grad(); loss_g.backward(); opt_g.step()
return loss_d.item(), loss_g.item()
Plug train_step into a DataLoader over CelebA or LSUN resized to 64×64 and normalized to [-1, 1]. A few epochs on CelebA are typically enough to see faces emerge.
The DCGAN paper showed that linear interpolation between two noise vectors produces smooth transitions (no memorization jumps), and that averaged latent vectors support semantic arithmetic. It also used the trained discriminator's features as a frozen feature extractor for classification, an early demonstration of GANs as unsupervised representation learners.
Use DCGAN to learn GANs, to prototype on small images, or as a quick baseline. For production image synthesis prefer StyleGAN-family models or diffusion models.
DCGAN keeps the original GAN game from Goodfellow et al. (2014). The discriminator D is a binary classifier trained with binary cross-entropy to output 1 on real images and 0 on generated ones. The generator G maps noise z to images and is trained to make D wrong. In the original minimax form, G minimises log(1 − D(G(z))), but early in training D rejects fakes confidently and that term saturates, giving G almost no gradient. Practically every implementation, DCGAN included, uses the non-saturating loss instead: G maximises log D(G(z)), which in code means computing BCE on fake images with target label 1.
Each iteration alternates: one D update on a real batch and a fake batch (with the fake batch detach()ed so G is not updated), then one G update on a fresh or the same fake batch passed through the updated D. At the theoretical optimum, D outputs 0.5 everywhere and the generator distribution matches the data distribution; in practice the game never converges cleanly, which is why monitoring samples matters more than loss values.
For the standard 64×64 RGB configuration with feature width ngf = ndf = 64, the generator shapes are: z as 100×1×1 → 512×4×4 → 256×8×8 → 128×16×16 → 64×32×32 → 3×64×64. Each transposed convolution uses kernel 4, stride 2, padding 1, which exactly doubles height and width; the first layer uses stride 1, padding 0 to turn the 1×1 input into 4×4. The discriminator reverses this with stride-2 convolutions: 3×64×64 → 64×32×32 → 128×16×16 → 256×8×8 → 512×4×4 → a single logit from a final 4×4 convolution.
The output size of a transposed convolution is (in − 1)·stride − 2·padding + kernel; for in = 4, stride 2, padding 1, kernel 4 that gives 3·2 − 2 + 4 = 8. The resulting generator has a few million parameters, small by modern standards, which is part of why it trains on a single consumer GPU in under an hour on CelebA at 64×64. To go to 128×128, add one more block to each network; beyond that, instability grows quickly.
GAN losses are not a progress bar: the generator loss can rise while samples get better, because D is also improving. Save a fixed grid of samples from the same set of noise vectors every few hundred steps and look at it; that is the primary signal. For quantitative tracking, compute FID (Fréchet Inception Distance, Heusel et al. 2017) on a few thousand samples every epoch. FID compares the mean and covariance of Inception features of real versus generated images; lower is better, and it is sensitive to both quality and diversity. Use the same number of samples and the same implementation when comparing runs, since FID is biased by sample count.
When D wins too easily, common fixes are one-sided label smoothing (real target 0.9 instead of 1.0), adding small instance noise to D's inputs, lowering D's learning rate, or using spectral normalization in D. For mode collapse, try minibatch standard-deviation features (from Progressive GAN) or switch to a different loss such as hinge loss or WGAN-GP (Gulrajani et al., 2017). These changes move you away from strict DCGAN, but the architecture skeleton stays the same.
| Model | Main idea over DCGAN | Typical use |
|---|---|---|
| DCGAN (2015) | Baseline all-convolutional recipe | Teaching, small datasets, 64×64 |
| WGAN / WGAN-GP (2017) | Wasserstein critic with Lipschitz constraint for stabler gradients | More reliable training on hard data |
| Progressive GAN (2018) | Grow resolution during training | 1024×1024 faces |
| StyleGAN (2019+) | Mapping network and style modulation | High-fidelity faces, editing |
| Diffusion models | Iterative denoising, no adversary | Current default for text-to-image |
DCGAN still earns its place: it is small enough to read in one sitting, it exposes every classic GAN failure mode, and its generator-discriminator pairing is the template for conditional GANs, CycleGAN-style image translation, and GAN-based data augmentation for small image datasets. For new production image generation, diffusion models usually win on diversity and controllability, while GANs remain attractive where single-pass, low-latency generation matters.
Further reading: Radford, Metz & Chintala, "Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks" (2015, arXiv:1511.06434); Goodfellow et al., "Generative Adversarial Nets" (2014); Salimans et al., "Improved Techniques for Training GANs" (2016); Odena, Dumoulin & Olah, "Deconvolution and Checkerboard Artifacts" (Distill, 2016); the PyTorch DCGAN tutorial.