Condition GAN model in MNIST
你能想像一個 AI 像畫家一樣,根據你的要求創作出指定內容的圖像嗎?」生成對抗網絡(GAN)的誕生帶來了人工智慧的創作革命。然而,GAN 的生成過程是無條件的 — —…
Condition GAN model in MNIST
你能想像一個 AI 像畫家一樣,根據你的要求創作出指定內容的圖像嗎?」生成對抗網絡(GAN)的誕生帶來了人工智慧的創作革命。然而,GAN 的生成過程是無條件的 — — 它只關注生成看似真實的數據,但無法控制數據的類型。這就像一位畫家,只會隨機畫出一些藝術作品,但無法接受具體的命題。而條件生成對抗網絡(Condition GAN, CGAN)正是為了解決這一問題,讓生成的過程更加可控。
What is CGAN
CGAN 是 GAN 的一個變體,為生成器和判別器加入了「條件」信息,讓我們能夠生成特定類型的數據。舉個例子,如果我們讓 CGAN 學習 MNIST 數據集,我們可以指定「數字 3」,讓生成器產生看起來像數字 3 的圖像。
在 CGAN 中,條件(例如數字標籤)會被作為輸入提供給生成器和判別器,並引導生成器生成符合條件的數據。CGAN 數學式可以用以下的損失函數表示。(y是條件 Mnist 中是數字的Label)

Condition GAN Loss function
GAN vs. CGAN
GAN 是無條件的:給它一個隨機噪聲 z ,它會生成某些「真實感」的數據,卻無法控制這些數據的類型。相比之下,CGAN 則加入了條件 y ,讓生成器學會在生成數據時考慮條件信息,從而生成特定類型的數據。
Example:
• GAN:輸入隨機噪聲,可能生成任何數字。
• CGAN:輸入條件「數字 7」,生成器只生成數字 7 的圖像。
MNIST with CGAN
MNIST 數據集是展示 CGAN 應用的最佳選擇之一。它包含 0 到 9 的手寫數字圖像,我們的目標是讓 CGAN 學會根據指定的數字標籤生成對應的數字圖像。
- importing python modules
import time
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import utils, datasets, transforms
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML
- Setting the Hyperparameter
# Root directory for dataset
dataroot = "dataset/"
# Number of workers for dataloader
workers = 10
# Batch size during training
batch_size = 100
# Spatial size of training images. All images will be resized to this size using a transformer.
image_size = 32
# Number of channels in the training images. For color images this is 3
nc = 1
# Number of classes in the training images. For mnist dataset this is 10
num_classes = 10
# Size of z latent vector (i.e. size of generator input)
nz = 100
# Size of feature maps in generator
ngf = 64
# Size of feature maps in discriminator
ndf = 64
# Number of training epochs
num_epochs = 10
# Learning rate for optimizers
lr = 0.0002
# Beta1 hyperparam for Adam optimizers
beta1 = 0.5
# Number of GPUs available. Use 0 for CPU mode.
ngpu = 1
- Loading the Dataset
train_data = datasets.MNIST(
root=dataroot,
train=True,
transform=transforms.Compose([
transforms.Resize(image_size),
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
]),
download=True
)
test_data = datasets.MNIST(
root=dataroot,
train=False,
transform=transforms.Compose([
transforms.Resize(image_size),
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
)
dataset = train_data+test_data
print(f'Total Size of Dataset: {len(dataset)}')
dataloader = DataLoader (
dataset=dataset,
batch_size=batch_size,
shuffle=True,
num_workers=workers
)
Result : Total Size of Dataset: 70000.
- Select the device
device = torch.device('cuda:1' if (torch.cuda.is_available() and ngpu > 0) else 'cpu')
# training images Visualization
imgs = {}
for x, y in dataset:
if y not in imgs:
imgs[y] = []
elif len(imgs[y])!=10:
imgs[y].append(x)
elif sum(len(imgs[key]) for key in imgs)==100:
break
else:
continue
imgs = sorted(imgs.items(), key=lambda x:x[0])
imgs = [torch.stack(item[1], dim=0) for item in imgs]
imgs = torch.cat(imgs, dim=0)
plt.figure(figsize=(10,10))
plt.title("Training Images")
plt.axis('off')
imgs = utils.make_grid(imgs, nrow=10)
plt.imshow(imgs.permute(1, 2, 0)*0.5+0.5)
- Weight Initalizat
def weights_init(m):
classname = m.__class__.__name__
if classname.find('Conv') != -1:
nn.init.normal_(m.weight.data, 0.0, 0.02)
elif classname.find('BatchNorm') != -1:
nn.init.normal_(m.weight.data, 1.0, 0.02)
nn.init.constant_(m.bias.data, 0)
- Define the Generator
class Generator(nn.Module):
def __init__(self, ngpu):
super(Generator, self).__init__()
self.ngpu = ngpu
self.image = nn.Sequential(
# state size. (nz) x 1 x 1
nn.ConvTranspose2d(nz, ngf * 4, 4, 1, 0, bias=False),
nn.BatchNorm2d(ngf * 4),
nn.ReLU(True)
# state size. (ngf*4) x 4 x 4
)
self.label = nn.Sequential(
# state size. (num_classes) x 1 x 1
nn.ConvTranspose2d(num_classes, ngf * 4, 4, 1, 0, bias=False),
nn.BatchNorm2d(ngf * 4),
nn.ReLU(True)
# state size. (ngf*4) x 4 x 4
)
self.main = nn.Sequential(
# state size. (ngf*8) x 4 x 4
nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 4),
nn.ReLU(True),
# state size. (ngf*4) x 8 x 8
nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(ngf * 2),
nn.ReLU(True),
# state size. (ngf*2) x 16 x 16
nn.ConvTranspose2d(ngf*2, nc, 4, 2, 1, bias=False),
nn.Tanh()
# state size. (nc) x 32 x 32
)
def forward(self, image, label):
image = self.image(image)
label = self.label(label)
incat = torch.cat((image, label), dim=1)
return self.main(incat)
- Instantiation of Generator
# Create the generator
netG = Generator(ngpu).to(device)
# Handle multi-gpu if desired
if device.type == 'cuda' and ngpu > 1:
netG = nn.DataParallel(netG, list(range(ngpu)))
# Apply the weights_init function to randomly initialize all weights to mean=0, stdev=0.2.
netG.apply(weights_init)
- Define the Discriminator
class Discriminator(nn.Module):
def __init__(self, ngpu):
super(Discriminator, self).__init__()
self.ngpu = ngpu
self.image = nn.Sequential(
# input is (nc) x 32 x 32
nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True)
# state size. (ndf) x 16 x 16
)
self.label = nn.Sequential(
# input is (num_classes) x 32 x 32
nn.Conv2d(num_classes, ndf, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True)
# state size. (ndf) x 16 x 16
)
self.main = nn.Sequential(
# state size. (ndf*2) x 16 x 16
nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 4),
nn.LeakyReLU(0.2, inplace=True),
# state size. (ndf*4) x 8 x 8
nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(ndf * 8),
nn.LeakyReLU(0.2, inplace=True),
# state size. (ndf*8) x 4 x 4
nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
# state size. (1) x 1 x 1
nn.Sigmoid()
)
def forward(self, image, label):
image = self.image(image)
label = self.label(label)
incat = torch.cat((image, label), dim=1)
return self.main(incat)
- Instantiation of Discriminator
# Create the Discriminator
netD = Discriminator(ngpu).to(device)
# Handle multi-gpu if desired
if device.type == 'cuda' and ngpu > 1:
netD = nn.DataParallel(netD, list(range(ngpu)))
# Apply the weights_init function to randomly initialize all weights to mean=0, stdev=0.2.
netD.apply(weights_init)
- Optimizers and Loss Functions
# Initialize BCELoss function
criterion = nn.BCELoss()
# Establish convention for real and fake labels during training
real_label_num = 1.
fake_label_num = 0.
# Setup Adam optimizers for both G and D
optimizerD = torch.optim.Adam(netD.parameters(), lr=lr, betas=(beta1, 0.999))
optimizerG = torch.optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))
# Label one-hot for G
label_1hots = torch.zeros(10,10)
for i in range(10):
label_1hots[i,i] = 1
label_1hots = label_1hots.view(10,10,1,1).to(device)
# Label one-hot for D
label_fills = torch.zeros(10, 10, image_size, image_size)
ones = torch.ones(image_size, image_size)
for i in range(10):
label_fills[i][i] = ones
label_fills = label_fills.to(device)
# Create batch of latent vectors and laebls that we will use to visualize the progression of the generator
fixed_noise = torch.randn(100, nz, 1, 1).to(device)
fixed_label = label_1hots[torch.arange(10).repeat(10).sort().values]
- Training the Networks
# Lists to keep track of progress
img_list = []
G_losses = []
D_losses = []
D_x_list = []
D_z_list = []
loss_tep = 10
print("Starting Training Loop...")
# For each epoch
for epoch in range(num_epochs):
beg_time = time.time()
# For each batch in the dataloader
for i, data in enumerate(dataloader):
############################
# (1) Update D network: maximize log(D(x)) + log(1 - D(G(z)))
###########################
## Train with all-real batch
netD.zero_grad()
# Format batch
real_image = data[0].to(device)
b_size = real_image.size(0)
real_label = torch.full((b_size,), real_label_num).to(device)
fake_label = torch.full((b_size,), fake_label_num).to(device)
G_label = label_1hots[data[1]]
D_label = label_fills[data[1]]
# Forward pass real batch through D
output = netD(real_image, D_label).view(-1)
# Calculate loss on all-real batch
errD_real = criterion(output, real_label)
# Calculate gradients for D in backward pass
errD_real.backward()
D_x = output.mean().item()
## Train with all-fake batch
# Generate batch of latent vectors
noise = torch.randn(b_size, nz, 1, 1).to(device)
# Generate fake image batch with G
fake = netG(noise, G_label)
# Classify all fake batch with D
output = netD(fake.detach(), D_label).view(-1)
# Calculate D's loss on the all-fake batch
errD_fake = criterion(output, fake_label)
# Calculate the gradients for this batch
errD_fake.backward()
D_G_z1 = output.mean().item()
# Add the gradients from the all-real and all-fake batches
errD = errD_real + errD_fake
# Update D
optimizerD.step()
############################
# (2) Update G network: maximize log(D(G(z)))
###########################
netG.zero_grad()
# Since we just updated D, perform another forward pass of all-fake batch through D
output = netD(fake, D_label).view(-1)
# Calculate G's loss based on this output
errG = criterion(output, real_label)
# Calculate gradients for G
errG.backward()
D_G_z2 = output.mean().item()
# Update G
optimizerG.step()
# Output training stats
end_time = time.time()
run_time = round(end_time-beg_time)
print(
f'Epoch: [{epoch+1:0>{len(str(num_epochs))}}/{num_epochs}]',
f'Step: [{i+1:0>{len(str(len(dataloader)))}}/{len(dataloader)}]',
f'Loss-D: {errD.item():.4f}',
f'Loss-G: {errG.item():.4f}',
f'D(x): {D_x:.4f}',
f'D(G(z)): [{D_G_z1:.4f}/{D_G_z2:.4f}]',
f'Time: {run_time}s',
end='\r'
)
# Save Losses for plotting later
G_losses.append(errG.item())
D_losses.append(errD.item())
# Save D(X) and D(G(z)) for plotting later
D_x_list.append(D_x)
D_z_list.append(D_G_z2)
# Save the Best Model
if errG < loss_tep:
torch.save(netG.state_dict(), 'model.pt')
loss_tep = errG
# Check how the generator is doing by saving G's output on fixed_noise and fixed_label
with torch.no_grad():
fake = netG(fixed_noise, fixed_label).detach().cpu()
img_list.append(utils.make_grid(fake, nrow=10))
# Next line
print()
- Loss versus training interation
plt.figure(figsize=(20, 10))
plt.title("Generator and Discriminator Loss During Training")
plt.plot(G_losses[::100], label="G")
plt.plot(D_losses[::100], label="D")
plt.xlabel("iterations")
plt.ylabel("Loss")
plt.axhline(y=0, label="0", c='g') # asymptote
plt.legend()

Loss versus training iteration
- D(x) and D(G(z)) versus training iteration

D(x) and D(G(z)) versus training iteration
- Visualization of G’s

- Real vs Fake data
# Size of the Figure
plt.figure(figsize=(20,10))
# Plot the real images
plt.subplot(1,2,1)
plt.axis('off')
plt.title("Real Images")
imgs = utils.make_grid(imgs, nrow=10)
plt.imshow(imgs.permute(1, 2, 0)*0.5+0.5)
# Load the Best Generative Model
netG = Generator(0)
netG.load_state_dict(torch.load('model.pt', map_location=torch.device('cpu')))
netG.eval()
# Generate the Fake Images
with torch.no_grad():
fake = netG(fixed_noise.cpu(), fixed_label.cpu())
# Plot the fake images
plt.subplot(1,2,2)
plt.axis("off")
plt.title("Fake Images")
fake = utils.make_grid(fake, nrow=10)
plt.imshow(fake.permute(1, 2, 0)*0.5+0.5)
# Save the comparation result
plt.savefig('result.jpg', bbox_inches='tight')

Real vs Fake
- Generate the specific number images
def make_img(num, label, netG, nz, num_classes, device):
"""
生成指定數量的帶有指定標籤的假圖像。
參數:
- num: int, 要生成的圖像數量。
- label: int, 指定的標籤 (0 ~ num_classes-1)。
- netG: torch.nn.Module, 生成器模型。
- nz: int, 噪聲向量的維度。
- num_classes: int, 標籤總數量。
- device: torch.device, 設備 (CPU 或 GPU)。
返回:
- fake_images: torch.Tensor, 生成的圖像張量。
"""
# 生成噪聲向量
noise = torch.randn(num, nz, 1, 1).to(device)
# 生成對應標籤的 one-hot 向量
label_onehot = torch.zeros(num, num_classes).to(device)
label_onehot[range(num), label] = 1 # 將對應標籤置為 1
# Step 2: 擴展維度到 4D
label_onehot = label_onehot.view(num, num_classes, 1, 1).to(device)
netG.to(device)
# 通過生成器生成假圖像
with torch.no_grad():
fake_images = netG(noise, label_onehot)
# 可視化圖像
fake_grid = utils.make_grid(fake_images, nrow=min(num, 10), normalize=True)
plt.figure(figsize=(10, 5))
plt.axis("off")
plt.title(f"Fake Images with Label {label}")
plt.imshow(fake_grid.permute(1, 2, 0).cpu())
plt.show()
return 0
# this one
img = make_img(100, 0, netG, nz, 10, device)

100 張 Fake 0 的圖片

100 張的 Fake 9的圖片
메타데이터
- post_id
- f1d4743f2bfa
- slug
- condition-gan-model-in-mnist-f1d4743f2bfa
- url
- https://medium.com/@smpss91341/condition-gan-model-in-mnist-f1d4743f2bfa
- canonical_url
- https://medium.com/@smpss91341/condition-gan-model-in-mnist-f1d4743f2bfa
- author_url
- https://medium.com/@smpss91341
- status
- ok
- fetched_at
- 2026-06-27 07:40:21