深度学习(PyTorch)——生成对抗网络(GAN)

一、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)

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>