Generative AI · DCGAN
The DCGAN architecture guidelines (strided convolutions, batch norm, ReLU/LeakyReLU, no pooling or fully connected hidden layers) built in PyTorch, with the tensor shapes traced from noise to a 32×32 image.
DCGAN (Radford, Metz and Chintala, 2015) made GANs work with convolutional networks, using a short list of architecture guidelines from the paper:
- replace pooling with strided convolutions (discriminator) and fractionally-strided/transposed convolutions (generator)
- use batch normalisation in both networks
- no fully connected hidden layers in deeper architectures
- ReLU in the generator (Tanh at the output), LeakyReLU in the discriminator
In March I reproduced the paper in my Modern-DCGAN-Reproducibility repo, keeping this all-convolutional architecture but changing the training engine (LSGAN, spectral norm, label smoothing). Here I rebuilt the architecture at 32×32 (CIFAR-10 size) and traced the shapes layer by layer. Following the shapes is the clearest way to understand how 100 numbers become an image.
import torch, torch.nn as nn
torch.manual_seed(0)
nz, ngf, ndf = 100, 64, 64
G = nn.Sequential(
nn.ConvTranspose2d(nz, ngf * 4, 4, 1, 0, bias=False), nn.BatchNorm2d(ngf * 4), nn.ReLU(True), # 1 -> 4
nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf * 2), nn.ReLU(True), # 4 -> 8
nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf), nn.ReLU(True), # 8 -> 16
nn.ConvTranspose2d(ngf, 3, 4, 2, 1, bias=False), nn.Tanh()) # 16 -> 32
D = nn.Sequential(
nn.Conv2d(3, ndf, 4, 2, 1, bias=False), nn.LeakyReLU(0.2, True), # 32 -> 16
nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 2), nn.LeakyReLU(0.2, True), # 16 -> 8
nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 4), nn.LeakyReLU(0.2, True), # 8 -> 4
nn.Conv2d(ndf * 4, 1, 4, 1, 0, bias=False)) # 4 -> 1 (logit)
def trace(net, x, label):
print(label)
for layer in net:
x = layer(x)
if isinstance(layer, (nn.ConvTranspose2d, nn.Conv2d)):
print(f" {type(layer).__name__:<16} -> {tuple(x.shape)}")
return x
img = trace(G, torch.randn(2, nz, 1, 1), "generator: noise -> image")
print(" output range:", round(img.min().item(), 3), "to", round(img.max().item(), 3), "(Tanh)")
trace(D, img, "discriminator: image -> real/fake logit")
count = lambda m: sum(p.numel() for p in m.parameters())
print(f"parameters: G {count(G):,} | D {count(D):,}")
Output:
generator: noise -> image
ConvTranspose2d -> (2, 256, 4, 4)
ConvTranspose2d -> (2, 128, 8, 8)
ConvTranspose2d -> (2, 64, 16, 16)
ConvTranspose2d -> (2, 3, 32, 32)
output range: -0.996 to 0.999 (Tanh)
discriminator: image -> real/fake logit
Conv2d -> (2, 64, 16, 16)
Conv2d -> (2, 128, 8, 8)
Conv2d -> (2, 256, 4, 4)
Conv2d -> (2, 1, 1, 1)
parameters: G 1,068,928 | D 663,296