1. Pytorch和MNIST数据集简介
Pytorch是一个基于Torch的开源机器学习库,提供两个主要功能:一是用于张量计算的自动微分系统,二是支持构建基于反向传播的神经网络。而MNIST数据集则是一个手写数字识别数据集,包含60,000个训练数据和10,000个测试数据,每张图像的大小都是28×28像素。
2. Conditional Generative Adversarial Networks(CGAN)简介
条件生成式对抗网络(CGAN)是一种生成式对抗网络(GAN)的改进版,其输入是目标图像和相应的条件信息,可以生成具有指定条件的新图像。在MNIST数据集上,我们可以通过向CGAN输入目标数字的标签来生成具有指定数字的新图像。
3. 数据预处理
在运行代码前,我们需要将MNIST数据集下载并进行预处理。预处理过程包括数据的下载、归一化和转换为Tensor:
import torch
import torchvision
from torchvision import transforms
# 数据预处理
transform = transforms.Compose([transforms.Resize((28, 28)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5], std=[0.5])])
# 下载并加载MNIST数据集
trainset = torchvision.datasets.MNIST(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,
shuffle=True, num_workers=2)
需要注意的是,由于GAN是一个无监督的学习过程,我们在训练时,只需要将图像作为输入,无需传入其对应的标签。
4. 搭建CGAN模型
CGAN模型由两个部分组成:生成器和判别器。在生成器中,我们将输入的条件信息和一个随机噪声向量拼接在一起,将其作为输入,通过反向传播不断调整参数,从而生成具有指定条件的新图像。而在判别器中,我们输入一张图像并通过反向传播不断调整参数,从而判断这张图像是真实的MNIST图像还是生成器生成的图像。两个模型相互对抗,不断优化,最终使得生成器能够生成越来越逼真的图像。
4.1 生成器
生成器的输入包括随机噪声和条件信息,其中随机噪声是为了让生成器能够生成多个不同的图像,而条件信息则是为了让生成器能够生成指定条件的图像。在本例中,条件信息是一个长度为10的one-hot向量,其中包括0-9的数字标签。生成器的输出为一张28×28像素的图像,其代码实现如下:
import torch.nn as nn
# 定义生成器
class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__()
self.label_emb = nn.Embedding(10, 10)
self.model = nn.Sequential(
nn.Linear(100 + 10, 128),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(128, 256),
nn.BatchNorm1d(256, 0.8),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(256, 512),
nn.BatchNorm1d(512, 0.8),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(512, 1024),
nn.BatchNorm1d(1024, 0.8),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(1024, 784),
nn.Tanh()
)
def forward(self, noise, labels):
gen_input = torch.cat((self.label_emb(labels), noise), -1)
img = self.model(gen_input)
img = img.view(img.size(0), *self.output_shape)
return img
在定义生成器时,我们使用了Embedding层来将条件信息转化为一个10维向量。而后面的模型则由四个全连接层和四个BatchNoramlization层以及四个LeakyReLU激活函数层组成。最后,我们使用一个Tanh激活函数层来输出生成的图像,并将输出的图像大小重新调整为28×28像素。
4.2 判别器
判别器需要判断输入的图像是真实的MNIST图像还是生成器生成的图像。其定义方式与生成器类似,包括了一个Embedding层和四个全连接层以及四个LeakyReLU激活函数层。输出的结果仅为一个数值,分别表示这张图像是真实图像的概率和是假图像的概率。其代码实现如下:
# 定义判别器
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator, self).__init__()
self.label_embedding = nn.Embedding(10, 10)
self.model = nn.Sequential(
nn.Linear(784 + 10, 512),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(512, 256),
nn.BatchNorm1d(256, 0.8),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(256, 1),
nn.Sigmoid(),
)
def forward(self, img, labels):
d_in = torch.cat((img.view(img.size(0), -1), self.label_embedding(labels)), -1)
validity = self.model(d_in)
return validity
在定义判别器时,我们也使用了Embedding层将条件信息转化为一个10维向量。其后面的模型由两个全连接层和一个Sigmoid激活函数层组成,输出结果为一个数值,范围在0到1之间,表示输入图像是真实图像的概率和是假图像的概率。
5. 模型训练
在训练前,我们需要定义损失函数和优化器。损失函数选择二分类的交叉熵,优化器选择Adam优化器。在训练过程中,我们需要对生成器和判别器分别进行训练,并针对不同的模型使用不同的损失函数(生成器损失函数和判别器损失函数)和优化器。
5.1 生成器损失函数和优化器
生成器的目标是使自己生成的图像越来越逼真,并且能够通过判别器的判断。因此,生成器的损失函数可以定义为生成器生成的图像通过判别器判断为真实图像的概率与1之差(也就是生成器生成的图像越逼真,损失函数越小)。其代码实现如下:
# 定义生成器的损失函数和优化器
adversarial_loss = torch.nn.BCELoss()
optimizer_G = torch.optim.Adam(generator.parameters(), lr=0.0002, betas=(0.5, 0.999))
# 生成器训练
optimizer_G.zero_grad()
# 生成一个batch的随机噪声和标签
z = torch.randn(batch_size, 100).to(device)
labels = torch.randint(0, 10, (batch_size,)).to(device)
# 通过生成器生成图像
fake_imgs = generator(z, labels)
# 判别器对真实图像的判断结果
valid = torch.ones(batch_size, 1).to(device)
# 计算生成器损失函数
g_loss = adversarial_loss(discriminator(fake_imgs, labels), valid)
# 反向传播和优化器更新
g_loss.backward()
optimizer_G.step()
其中,我们使用了二分类的交叉熵作为损失函数,并使用Adam优化器来更新生成器的参数。具体来说,我们首先生成一个batch的随机噪声和标签,通过生成器生成图像,随后计算生成器损失函数并进行反向传播和优化器更新。
5.2 判别器损失函数和优化器
判别器的目标是能够准确地判断输入图像是真实图像还是生成器生成的图像。因此,判别器的损失函数可以定义为真实图像通过判别器判断为真实图像的概率和生成器生成的图像通过判别器判断为假图像的概率、或真实图像通过判别器判断为假图像的概率和生成器生成的图像通过判别器判断为真实图像的概率(也就是判别器越准确,损失函数越小)。其代码实现如下:
# 定义判别器的损失函数和优化器
adversarial_loss = torch.nn.BCELoss()
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=0.0002, betas=(0.5, 0.999))
# 判别器训练
optimizer_D.zero_grad()
# 生成一个batch的随机噪声和标签
z = torch.randn(batch_size, 100).to(device)
labels = torch.randint(0, 10, (batch_size,)).to(device)
# 通过生成器生成图像
fake_imgs = generator(z, labels).detach()
# 获取一个batch的真实图像
real_imgs, real_labels = next(iter(trainloader))
real_imgs, real_labels = real_imgs.to(device), real_labels.to(device)
# 判别器对真实图像的判断结果
valid = torch.ones(batch_size, 1).to(device)
# 判别器对生成器生成的图像的判断结果
fake = torch.zeros(batch_size, 1).to(device)
# 计算判别器损失函数
d_loss_real = adversarial_loss(discriminator(real_imgs, real_labels), valid)
d_loss_fake = adversarial_loss(discriminator(fake_imgs, labels), fake)
d_loss = (d_loss_real + d_loss_fake) / 2
# 反向传播和优化器更新
d_loss.backward()
optimizer_D.step()
其中,我们使用了二分类的交叉熵作为损失函数,并使用Adam优化器来更新判别器的参数。具体来说,我们首先生成一批随机标签以及随机噪声,通过生成器生成图像,并获取一个batch的真实图像。随后我们分别计算判别器对真实图像和生成器生成的图像的判断结果,并计算判别器的损失函数。最后进行反向传播和优化器更新。
6. 生成指定的数字
现在我们可以尝试生成指定数字的图像。生成过程很简单,我们只需要在生成器输入随机噪声时,传入特定的标签即可。具体来说,当我们需要生成数字5时,可以将标签设置为5,将随机噪声设置为一个固定的数值,随后通过生成器生成一张新的数字5的图像。其代码实现如下:
# 生成数字5的图像
z = torch.randn(1, 100).to(device)
label = torch.LongTensor([5]).to(device)
img = generator(z, label)
# 转换为numpy数组并显示
img = img.view(28,28).data.cpu().numpy()
plt.imshow(img, cmap='gray')
plt.show()
生成的数字5的图像如下所示:
![generated_digit_5](https://i.imgur.com/6yk98Qp.png)
同样的,我们可以通过调整Temperature参数(如将Temperature设置为0.6),生成更加多样化的图像。
7. 结语
本文简要介绍了Pytorch和MNIST数据集的相关知识,并详细讲解了如何使用CGAN在MNIST数据集上实现生成数字,并生成指定数字的图像。对于初学者来说,CGAN是一个非常好的入门案例,可以帮助大家深入理解神经网络的本质以及GAN的应用实现。