A conditional GAN generates realistic data for a requested label instead of generating any plausible example. The generator G receives latent noise z and label y; the discriminator D receives the example and the same label. This turns an unconstrained GAN into a supervised, one-to-many mapping. section 9.5
The implementation adds conditioning with a small wrapper:
- Embed each class label into a vector with the same shape as z. section 9.5.1
- Concatenate the embedded label and z, then pass the result through a small combiner network. section 9.5.1
- Wrap G and D independently, preserving the original networks while giving both access to y. section 9.5.1
- Add class_label to every generator and discriminator call during training. section 9.5.2
Generating controlled digits
Reuse one latent vector across labels 0 through 9 and compute G(z | y) for each label. Each row can preserve style—such as line thickness or slant—while changing the digit class, because the label controls content and z carries variation. section 9.5.3
class ConditionalWrapper(nn.Module):
def __init__(self, input_shape, neurons, classes, main_network,
leak=0.2):
"""
input_shape: the shape that the latent variable z
should take.
neurons: neurons to use in hidden layers
classes: number of classes in labels y
main_network: either the generator G or discriminator D
"""
super().__init__() self.input_shape = input_shape
self.classes = classes
input_size = abs(np.prod(input_shape)) #1
self.label_embedding = nn.Embedding(classes, input_size) #2
self.combiner = nn.Sequential( #3
nn.Flatten(),
fcLayer(input_size*2, input_size, leak=leak), #4
nn.Linear(input_size, input_size), #5
nn.LeakyReLU(leak),
View(input_shape), #6
nn.LayerNorm(input_shape[1:]),
)
self.net = main_network
def forward(self, x, condition=None): #7
if condition is None: #8
condition = torch.randint(0, self.classes, size=(x.size(0),),
device=x.get_device())
embd = self.label_embedding(condition) #9
embd = embd.view(self.input_shape) #10
x = x.view(self.input_shape)
x_comb = torch.cat([x, embd], dim=1) #11
return self.net(self.combiner(x_comb)) #12
#1 Figures out the number of latent parameters from the latent shape
#2 Creates an embedding layer to convert labels to vectors
#3 In the forward function, we concatenate the label and original date into one vector. Then this combiner takes that extra-large tensor and creates a new tensor the size of just the original input_shape. This does the work of merging the conditional information (from label_embedding) into the latent vector.
#4 One FC layer
#5 A second FC layer, but first the linear layer and activation are applied
#6 So we can reshape the output and apply normalizing based on the target output shape. This makes the conditional wrapper useful for linear and convolutional models.
#7 The forward function takes an input and produces an output.
#8 If no label was given, let’s pick one at random.
#9 Embeds the label and reshapes it as desired
#10 Makes sure the label embd and data x have the same shape so we can concatenate them
#11 Concatenates the latent input with the embedded label
#12 Returns the result of the network on the combined inputs
Inspect the ConditionalWrapper listing to connect label embedding, concatenation, and the combiner network to PyTorch module code. section 9.5.1
with torch.no_grad():
noise = torch.randn(10, latent_d, device=device). #1
repeat((1,10)).view(-1, latent_d) labels =
torch.fmod(torch.arange(0, noise.size(0), #2
device=device), classes)
fake_digits = G(noise, labels) #3
scores = D(fake_digits, labels)
fake_digits = fake_digits.cpu()
scores = scores.cpu().numpy().flatten()
plot_gen_imgs(fake_digits) #4
#1 Generates 10 latent noise vectors, and repeats them 10 times. We reuse the same latent codes.
#2 Counts from 0 to 9 and then wraps back around to 0. This is done 10 times.
#3 The same latent in noise is used to generate 10 images, but we change the label each time.
#4 When we plot the results, we should see a grid of digits from 0 to 9, where each row uses the same latent vector and shares similar visual properties.
Inspect the generation listing to see repeated latent vectors paired with labels and passed to G and D. section 9.5.3

