1.生成模型原理
1)CGAN的原理
传统的GAN或者其他的GAN都是通过一堆的训练数据,最后训练出了G网络,随机输入噪声最后产生的数据是这些训练数据类别中之一,我们提前无法预测是那哪一个?
因此,我们有的时候需要定向指定生成某些数据,比如我们想让G生成飞机,数字9,等等的图片数据。
假设现在要做一个项目:输入一段文字,输出一张图片,要让这张图片足够清晰并且符合这段文字的描述。我们搭建一个传统的NeuralNetwork(下称NN)去训练。
考虑我们输入的文字是“train”,希望NN能输出清晰的火车照片,那在数据集中,下面左图是正面的火车,它们统统都是正确的火车图片;下面右图是侧面的火车,它们也统统都是正确的火车。
那在训练这个NN的时候,network会觉得说,火车既要长得像左边的图片,也要长得像右边的图片,那最终network的output就会变成这一大堆images的平均,可想而知那会是一张非常模糊并且错误的照片。
我们需要引入GANs技术来保证NN产生清晰准确的照片。
我们把原始的NN叫做G(Generator),现在它吃两个输入,一个是条件word:c,另外一个是从原始图片中sample出的分布z,它的输出是一个image:x,它希望这个x尽可能地符合条件c的描述,同时足够清晰,如下图。
在GANs中为了保证输出image的质量会引入一个D(Discriminator),这个D用来判断输入的x是真实图片还是伪造图片,如下图。
但是传统GANs只能保证让x尽可能地像真实图片,它忽略了让x符合条件描述c的要求。于是,为了解决这一问题,CGAN便被提出了。
我们的目的是,既要让输出的图片真实,也要让输出的图片符合条件c的描述。Discriminator输入便被改成了同时输入c和x,输出要做两件事情,一个是判断x是否是真实图片,另一个是x和c是否是匹配的。
比如说,在下面这个情况中,条件c是train,图片x也是一张清晰的火车照片,那么D的输出就会是1。
而在下面两个情况中,左边虽然输出图片清晰,但不符合条件c;右边输出图片不真实。因此两种情况中D的输出都会是0。
那CGAN的基本思路就是这样,下面我们具体看一下CGAN的算法实现。
因为CGAN是supervised学习,采样的每一项都是文字和图片的pair。CGAN的核心就是判断什么样的pair给高分,什么样的pair给低分。
2)做法
1:就是给网络的输入噪声数据增加一些类别上的信息,就是说给定某些类别条件下,生成指定的数据,所以输入数据会有一些变化;
2:然后在损失函数那里,我们目标不再是输出1/0,也就是不再是简单的输出真实和构造。当判定是真实数据的时候,还需要判定出是哪一类别的图片。一般使用one-hot表示。
上图表示,改变输入噪声数据,给z增加类别y信息,怎么增加呢,就是简单的维度拼接,y可以是一个one-hot向量,或者其他表达形式(此处采用词向量来表示,详情见代码部分)。对于真实数据x不做变化,只用y来获取D的输出结果。
判别器D最后也应该输出是哪个类别,并且按照类别最小化来训练,也就是希望D(X)尽可能接近y。
2.生成模型训练参考代码
train.py
import torch from torch import nn from torchvision import datasets from torchvision import transforms from torch.utils.data import DataLoader from torchvision.utils import save_image from torch import optim import os import numpy as np # 设置超参数 batch_size = 100 learning_rate = 0.0002 epochsize = 90 sample_dir = "images3" # 创建生成图像的目录 if not os.path.exists(sample_dir): os.makedirs(sample_dir) # 生成器结构 class Generator(nn.Module): def __init__(self): super(Generator, self).__init__() self.label_emb = nn.Embedding(10, 10) self.model = nn.Sequential( nn.Linear(110, 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, label): out = torch.cat((noise, self.label_emb(label)), -1) img = self.model(out) # torch.Size([64, 784]) img = img.view(img.size(0), 1, 28, 28) # torch.Size([64, 1, 32, 32]) return img # 鉴别器结构 class Discriminator(nn.Module): def __init__(self): super(Discriminator, self).__init__() self.label_emb = nn.Embedding(10, 10) self.model = nn.Sequential( nn.Linear(794, 512), nn.LeakyReLU(0.2, inplace=True), nn.Linear(512, 512), nn.Dropout(0.4), nn.LeakyReLU(0.2, inplace=True), nn.Linear(512, 512), nn.Dropout(0.4), nn.LeakyReLU(0.2, inplace=True), nn.Linear(512, 1), nn.Sigmoid() ) def forward(self, img, label): img = img.view(img.size(0), -1) # torch.Size([100, 784]) x = torch.cat((img, self.label_emb(label)), -1) # torch.Size([100, 794]) x = self.model(x) # torch.Size([100, 1]) return x # 训练集下载 mnist_traindata = datasets.MNIST('E:/学习/机器学习/数据集/MNIST', train=True, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=[0.5], std=[0.5]) ]), download=False) mnist_train = DataLoader(mnist_traindata, batch_size=batch_size, shuffle=True, pin_memory=True) # GPU加速 # device = torch.device('cuda') # torch.cuda.set_device(0) G = Generator() D = Discriminator() # 导入之前的训练模型 G.load_state_dict(torch.load('G_plus.ckpt')) D.load_state_dict(torch.load('D_plus.ckpt')) # 设置优化器与损失函数,二分类的时候使用BCELoss较好,BCEWithLogitsLoss是自带一层Sigmoid # criteon = nn.BCEWithLogitsLoss() criteon = nn.BCELoss() G_optimizer = optim.Adam(G.parameters(), lr=learning_rate) D_optimizer = optim.Adam(D.parameters(), lr=learning_rate) # 开始训练 print("start training") for epoch in range(epochsize): D_loss_total = 0 G_loss_total = 0 total_num = 0 # 这里的RealImageLabel是没有用上的 for batchidx, (realimage, realimage_label) in enumerate(mnist_train): # realimage = realimage.to(device) realscore = torch.ones(realimage.size(0), 1) # value:1 torch.Size([128, 1]) fakescore = torch.zeros(realimage.size(0), 1) # value:0 torch.Size([128, 1]) # 随机sample出噪声与标签,生成假图像 z = torch.randn(realimage.size(0), 100) fakeimage_label = torch.LongTensor(np.random.randint(0, 10, realimage.size(0))) fakeimage = G(z, fakeimage_label) # 训练鉴别器————总的损失为两者相加 d_realimage_loss = criteon(D(realimage, realimage_label), realscore) d_fakeimage_loss = criteon(D(fakeimage, fakeimage_label), fakescore) D_loss = d_realimage_loss + d_fakeimage_loss # 参数训练三个步骤 D_optimizer.zero_grad() D_loss.backward() D_optimizer.step() # 计算一次epoch的总损失 D_loss_total += D_loss # 训练生成器————损失只有一个 # 上一次的梯度信息以消除,重新生成假图像 fakeimage = G(z, fakeimage_label) G_loss = criteon(D(fakeimage, fakeimage_label), realscore) # 参数训练三个步骤 G_optimizer.zero_grad() G_loss.backward() G_optimizer.step() # 计算一次epoch的总损失 G_loss_total += G_loss # 打印相关的loss值 if batchidx % 200 == 0: print("batchidx:{}/{}, D_loss:{}, G_loss:{}".format(batchidx, len(mnist_train), D_loss, G_loss)) # 打印一次训练的loss值 print('Epoch:{}/{}, D_loss:{}, G_loss:{}'.format(epoch, epochsize, D_loss_total / len(mnist_train), G_loss_total / len(mnist_train))) # 保存生成图像 z = torch.randn(batch_size, 100) label = torch.LongTensor(np.array([num for _ in range(10) for num in range(10)])) save_image(G(z, label).data, os.path.join(sample_dir, 'images-{}.png'.format(epoch + 61)), nrow=10, normalize=True) # 保存网络结构 torch.save(G.state_dict(), 'G_plus.ckpt') torch.save(D.state_dict(), 'D_plus.ckpt')
test.py
import torch from torch import nn from torchvision.utils import save_image import os import numpy as np # 设置超参数 batch_size = 100 # learning_rate = 0.0002 # epochsize = 80 sample_dir = "test_images" # 创建生成图像的目录 if not os.path.exists(sample_dir): os.makedirs(sample_dir) # 生成器结构 class Generator(nn.Module): def __init__(self): super(Generator, self).__init__() self.label_emb = nn.Embedding(10, 10) self.model = nn.Sequential( nn.Linear(110, 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, label): out = torch.cat((noise, self.label_emb(label)), -1) img = self.model(out) # torch.Size([64, 784]) img = img.view(img.size(0), 1, 28, 28) # torch.Size([64, 1, 32, 32]) return img # 导入训练好的模型 G = Generator() G.load_state_dict(torch.load('G_plus.ckpt')) # 保存图像 z = torch.randn(batch_size, 100) # label = torch.LongTensor(np.array([num for _ in range(10) for num in range(10)])) label = torch.tensor([7,8,1,3,4,2,6,5,9,0]*10) # label = torch.full([100], 9) # label = [] # for i in range(10): # for j in range(10): # label.append(i) # # label = torch.tensor(label) print(label) print("label.shape:", label.size()) save_image(G(z, label).data, os.path.join(sample_dir, 'images.png'), nrow=10, normalize=True)
3.生成模型结果展示
由于电脑配置不行,只能用cpu跑了,跑得比较慢,下面是结果展示:
epoch10 生成的图像
epoch50 生成的图像
epoch200 生成的图像
但是存一个问题,CGAN只能全部条件的图像,不能生成单一条件的图像。也就是无论数字的顺序如何排列,cgan都能准确的生成出来,如图所示:
但是想让其生成单一的数字,比如全部生成数字“1”,就无法正常生成图像。如图所示
参考资料:
李宏毅老师的b站视频
https://blog.csdn.net/a312863063/article/details/83573968
https://blog.csdn.net/qq_29367075/article/details/109149211