一、GAN的基本概念
GAN是由Ian Goodfellow于2014年首次提出,学习GAN的初衷,即生成不存在于真实世界的数据。类似于AI具有创造力和想象力。
GAN有两大护法G和D:
G是generator,生成器: 负责凭空捏造数据出来
D是discriminator,判别器: 负责判断数据是不是真数据
这样可以简单看作是两个网络的博弈过程。在原始的GAN论文里面,G和D都是两个多层感知机网络。GAN操作的数据不一定非是图像数据,在此用图像数据为例解释以下GAN:
上图中,z是随机噪声(随机生成的一些数,也是GAN生成图像的源头)。D通过真图和假图的数据,进行一个二分类神经网络训练。G根据一串随机数就可以捏造出一个"假图像"出来,用这些假图去欺骗D,D负责辨别这是真图还是假图,会给出一个score。比如,G生成了一张图,在D这里评分很高,说明G生成能力是很成功的;若D给出的评分不高,可以有效区分真假图,则G的效果还不太好,需要调整参数。
二、GAN的基本原理
GAN的训练在同一轮梯度反转的过程中可以细分为2步:(1)先训练D;(2)再训练G。注意,不是等所有的D训练好了才开始训练G,因为D的训练也需要上一轮梯度反转中的G的输出值作为输入。
当训练D的时候:上一轮G产生的图片和真实图片,直接拼接在一起作为x。然后按顺序摆放成0和1,假图对应0,真图对应1。然后就可以通过D,x输入生成一个score(从0到1之间的数),通过score和y组成的损失函数,就可以进行梯度反传了。(我在图片上举的例子是batch = 1,len(y)=2*batch,训练时通常可以取较大的batch)
当训练G的时候:需要把G和D当作一个整体,这里取名叫做’D_on_G’。这个整体(简称DG系统)的输出仍然是score。输入一组随机向量z,就可以在G生成一张图,通过D对生成的这张图进行打分得到score,这就是DG系统的前向过程。score=1就是DG系统需要优化的目标,score和y=1之间的差异可以组成损失函数,然后可以采用反向传播梯度。注意,这里的D的参数是不可训练的。这样就能保证G的训练是符合D的打分标准的。这就好比:如果你参加考试,你别指望能改变老师的评分标准。
GAN模型的目标函数如下:
在这里,训练网络D使得最大概率地分对训练样本的标签(最大化log D(x)和log(1—D(G(z)))),训练网络G最小化log(1-D(G(z))),即最大化D的损失。而训练过程中固定一方,更新另一个网络的参数,交替迭代,使得对方的错误最大化,最终,G 能估测出样本数据的分布,也就是生成的样本更加的真实。
三、案例、用GAN对输入的随机数生成手写字体的图片
程序如下:
- import torch
- import torch.nn as nn
- import torch.optim as optim
- import torch.nn.functional as F
- import numpy as np
- import matplotlib.pyplot as plt
- import torchvision
- from torchvision import transforms
- import os
- import pandas
- os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
- #对数据做归一化(-1 --- 1)
- tranform = transforms.Compose([
- transforms.ToTensor(), # 0-1归一化:channel,high,width
- transforms.Normalize(0.5,0.5), #均值,方差均为0.5
- ])
-
- train_ds = torchvision.datasets.MNIST("data",train = True,transform = tranform,download=True)
-
- dataloader = torch.utils.data.DataLoader(train_ds,batch_size = 64,shuffle = True)
-
- imgs, _ =next(iter(dataloader))
- print(imgs.shape) #torch.Size([64, 1, 28, 28])
-
- # batch_size=64 图片大小:1,28,28
- # 定义生成器
- # 输入是长度为100的噪声(正态分布随机数)
- # 输出为(1,28,28)的图片
-
- # linear1:100---256
- # linear2:256---512
- # linear1:512---28*28
- # linear2:28*28---(1,28,28)
-
- class Generator(nn.Module):
- def __init__(self):
- super(Generator,self).__init__()
- self.main = nn.Sequential(
- nn.Linear(100,256),
- nn.ReLU(),
- nn.Linear(256,512),
- nn.ReLU(),
- nn.Linear(512, 28*28),
- nn.Tanh()
- )
-
- def forward(self,x): # x表示长度为100的噪声输入
- img = self.main(x)
- img = img.view(-1,28,28)
- return img
-
- # 定义判别器
- # 输入为(1,28,28)的图片,输出为二分类的概率值,输出使用sigmoid激活
- # BCELose计算交叉熵损失
- # nn.LeakyReLU f(x):x>0,输出x,如果x<0,输出a*x,a表示一个很小的斜率,比如0.1
- #判别器中一般推荐使用nn.LeakyReLU
- class Discriminator(nn.Module):
- def __init__(self):
- super(Discriminator, self).__init__()
- self.main = nn.Sequential(
- nn.Linear(28*28,512),
- nn.LeakyReLU(),
- nn.Linear(512,256),
- nn.LeakyReLU(),
- nn.Linear(256, 1),
- nn.Sigmoid()
- )
-
- def forward(self,x):
- x = x.view(-1,28*28)
- x = self.main(x)
- return x
-
- # device = 'cuda' if torch.cuda.is_available() else 'cpu'
- device = 'cuda' if 0 else 'cpu'
- gen = Generator().to(device)
- dis = Discriminator().to(device)
- d_optim = torch.optim.Adam(dis.parameters(),lr=0.0001)
- g_optim = torch.optim.Adam(gen.parameters(),lr=0.0001)
-
- loss_fn = torch.nn.BCELoss()
-
- def gen_img_plot(model,test_input):
- prediction = np.squeeze(model(test_input).detach().cpu().numpy())
- # detach()截断梯度,np.squeeze可以去掉维度为1
- fig = plt.figure(figsize=(4,4))
- for i in range(16): # prediction.size(0)=16
- plt.subplot(4,4,i+1)
- plt.imshow((prediction[i]+1)/2) # tanh 得到的是-1 - 1之间,-》0-1之间
- plt.axis("off")
- plt.show()
-
- test_input = torch.randn(16,100,device=device) # 16个长度为100的正态随机数
-
- # print(test_input)
-
- D_loss = []
- G_loss = []
-
- # 训练循环
- for epoch in range(20):
- d_epoch_loss = 0
- g_epoch_loss = 0
- count = len(dataloader) # len(dataloader)返回批次数
- # len(dataset)返回样本数
- for step,(img,_) in enumerate(dataloader):
- img = img.to(device)
- size = img.size(0)
- random_noise = torch.randn(size,100,device=device)
-
- d_optim.zero_grad()
- real_output = dis(img) # 对判别器输入真实图片 real_output对真实图片的预测结果
- #得到判别器在真实图像上面的损失
- d_real_loss = loss_fn(real_output,torch.ones_like(real_output))
-
- d_real_loss.backward()
-
- gen_img = gen(random_noise)
- #detach()截断生成器梯度,更新判别器梯度
- fake_output = dis(gen_img.detach()) # 判别器输入生成图片。fake_output对生成图片的预测
- # 得到判别器在生成图像上面的损失
- d_fake_loss = loss_fn(fake_output,torch.zeros_like(fake_output))
-
- d_fake_loss.backward()
-
- d_loss = d_fake_loss + d_real_loss
- d_optim.step()
-
- g_optim.zero_grad()
- fake_output = dis(gen_img)
- # 得到生成器的损失
- g_loss = loss_fn(fake_output,torch.ones_like(fake_output))
-
- g_loss.backward()
- g_optim.step()
-
- with torch.no_grad():
- d_epoch_loss +=d_loss
- g_epoch_loss +=g_loss
-
- with torch.no_grad():
- d_epoch_loss /=count
- g_epoch_loss /=count
- D_loss.append(d_epoch_loss)
- G_loss.append(g_epoch_loss)
- print("Epoch",epoch)
-
- gen_img_plot(gen,test_input)