深度学习系列32:GAN入门

Posted IE06

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度学习系列32:GAN入门相关的知识,希望对你有一定的参考价值。

1. 概念

模型通过框架中(至少)两个模块:生成模型(Generative Model)和判别模型(Discriminative Model)的互相博弈学习产生相当好的输出。原始 GAN理论中,并不要求 G 和 D 都是神经网络,只需要是能拟合相应生成和判别的函数即可。但实用中一般均使用深度神经网络作为 G 和 D 。
图例:

其目标函数:

简单来说,就是分每一轮训练分两步,首先optimize CE(D(x),1)+CE(D(G(z)),0),然后optimize CE(G(z),1)。其中CE表示cross entrophy函数,后面的1表示判断为真实,0表示判断为虚假。

2. 简单的GAN代码分析

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
import torchvision
from torchvision import transforms
 
transform = transforms.Compose([
    transforms.ToTensor(),  
    transforms.Normalize(0.5, 0.5)  
])
# 加载内置数据  做生成只需要图片就行,不需要标签 也不需要测试数据集
train_ds = torchvision.datasets.MNIST('data',   # 当前目录下的data文件夹
                                     train=True,  # train数据
                                     transform=transform,
                                     download=True)
 
dataloader = torch.utils.data.DataLoader(train_ds, batch_size=64, shuffle=True)
 
 
# 定义生成器
# 输入是长度为100的噪声
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.gen = nn.Sequential(nn.Linear(100, 256),  # 输入长度为100
                                 nn.ReLU(),
                                 nn.Linear(256, 512),
                                 nn.ReLU(),
                                 nn.Linear(512, 28 * 28),
                                 nn.Tanh()
                                 )
 
    def forward(self, x):  # 定义前向传播 x表示长度为100的noise输入
        img = self.gen(x)
        img = img.view(-1, 28, 28)  
        return img
 
# 定义判别器
# 输入为(1,28,28)的图片 输出为二分类的概率值,使用sigmoid激活
# BCEloss 计算交叉熵损失
# 判别器中推荐使用LeakyReLU
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator,self).__init__()
        self.disc = 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.disc(x)
        return x
 
# 初始化模型
device = 'cuda' if torch.cuda.is_available() 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_function = torch.nn.BCELoss()
 
# 绘图函数
def gen_img_plot(model, test_input):
    prediction = np.squeeze(model(test_input).detach().cpu().numpy())
    fig = plt.figure(figsize=(4,4))
    for i in range(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)
 
# 开始训练
D_loss = []
G_loss = []
# 训练循环
for epoch in range(50):
    d_epoch_loss = 0
    g_epoch_loss = 0
    batch_count = len(dataloader.dataset)
    # 对全部的数据集做一次迭代
    for step, (img, _) in enumerate(dataloader):
        img = img.to(device)  # 上传到设备上
        size = img.size(0)    # 返回img的第一维的大小
 
        random_noise = torch.randn(size, 100, device=device)
 
        d_optim.zero_grad()  # 将上述步骤的梯度归零
        real_output = dis(img)  # 对判别器输入真实的图片,real_output是对真实图片的预测结果
        d_real_loss = loss_function(real_output,
                                    torch.ones_like(real_output)
                                    )
        d_real_loss.backward() #求解梯度
 
        # 得到判别器在生成图像上的损失
        gen_img = gen(random_noise)
        fake_output = dis(gen_img.detach())  # 判别器输入生成的图片,对生成图片的预测结果
        d_fake_loss = loss_function(fake_output,
                                    torch.zeros_like(fake_output))
        d_fake_loss.backward()
 
        d_loss = d_real_loss + d_fake_loss
        d_optim.step()  # 优化
 
        # 得到生成器的损失
        g_optim.zero_grad()
        fake_output = dis(gen_img)
        g_loss = loss_function(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 /= batch_count
        g_epoch_loss /= batch_count
        D_loss.append(d_epoch_loss)
        G_loss.append(g_epoch_loss)
        print('Epoch:', epoch)

3. DCGAN:将全连接用卷积替代

DCGAN的生成器和鉴别器都舍弃了CNN的pooling层(池化层),鉴别器保留CNN的整体架构,生成器则是将卷积层替换成了反卷积层(ConvTranspose2d)
在鉴别器和生成器中使用了BN(Batch Normalization)层,加速模型训练,提升了训练的稳定性。但是在生成器的输出层和鉴别器的输入层不使用BN层【直接应用batchnorm到所有层会导致样本振荡和模型不稳定】
生成器网络中使用ReLU作为激活函数,最后一层使用Tanh()【使用有界激活(a bounded activation)可以让模型更快地学习,以饱和和覆盖训练分布的颜色空间】
鉴别器网络中使用LeakyReLU作为激活函数
使用Adam优化器,一阶矩估计的指数衰减率的值设置为0.5
代码变化的部分如下:

# 定义生成器
class Generator(nn.Module):
    def __init__(self):
        super(Generator,self).__init__()
        self.linear1 = nn.Linear(100, 256*7*7)  # 希望生成1*28*28的图片 7反卷积后14,再反卷积28 pytorch中channel在前
        self.bn1 = nn.BatchNorm1d(256*7*7)
        self.deconv1 = nn.ConvTranspose2d(256, 128,
                                          kernel_size=(3,3),
                                          stride=1,  
                                          padding=1 
                                          )   # 得到128*7*7的图像
        self.bn2 = nn.BatchNorm2d(128)
        self.deconv2 = nn.ConvTranspose2d(128, 64,
                                          kernel_size=(4,4),
                                          stride=2,
                                          padding=1  # 64*14*14
                                          )
        self.bn3 = nn.BatchNorm2d(64)
        self.deconv3 = nn.ConvTranspose2d(64, 1,
                                          kernel_size=(4, 4),
                                          stride=2,
                                          padding=1  # 1*28*28
                                          )
    def forward(self, x):
        x = F.relu(self.linear1(x))
        x = self.bn1(x)
        x = x.view(-1, 256, 7, 7)
        x = F.relu(self.deconv1(x))
        x = self.bn2(x)
        x = F.relu(self.deconv2(x))
        x = self.bn3(x)
        x = torch.tanh(self.deconv3(x))
        return x

# 定义判别器
# input:1,28,28
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.conv1 = nn.Conv2d(1, 64, kernel_size=3, stride=2) # 第一层不适用bn  64,13,13
        self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=2) #128,6,6
        self.bn = nn.BatchNorm2d(128)
        self.fc = nn.Linear(128*6*6, 1) # 输出一个概率值
    def forward(self, x):
        x = F.dropout2d(F.leaky_relu(self.conv1(x)))
        x = F.dropout2d(F.leaky_relu(self.conv2(x)))  # (batch, 128,6,6)
        x = self.bn(x)
        x = x.view(-1, 128*6*6)   # (batch, 128,6,6)--->  (batch, 128*6*6)
        x = torch.sigmoid(self.fc(x))
        return x

4. DCGAN应用

这里分析一下DCGAN生成动漫头像的步骤,数据集见:https://download.csdn.net/download/m0_62128864/85072972
生成器把上面的77改成1616,并且最后输出的黑白64,1改成彩色64,3。同理,判别器也照样修改,66改成1515。

以上是关于深度学习系列32:GAN入门的主要内容,如果未能解决你的问题,请参考以下文章

[GAN学习系列3]采用深度学习和 TensorFlow 实现图片修复(下)

深度学习--GAN从入门到初始

深度学习系列33:有标签的GAN:CGAN

深度学习-Keras-层及损失函数

深度学习-Keras-层及损失函数

深度学习系列46:人脸图像超分GFP-GAN