DCGAN原理分析与pytorch实现

本文介绍的是DCGAN及其pytorch实现。DCGAN论文地址https://arxiv.org/pdf/1511.06434.pdf

DCGAN论文全称为“Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks”,DCGAN将深度卷积神经网络CNN与生成对抗网络GAN结合用于无监督学习领域。

尽管今天(2019年)CNN网络已经被GAN的生成器和判别器普遍的使用,但是在几年前GAN缺乏一种强大网络架构设计作为通用的生成器和判别器对研究人员来说是非常不利的。此外,DCGAN包含的思想对于今天GAN网络设计仍然具有很大的启发意义。毫无疑问,DCGAN的出现极大的推动了GAN的研究,据谷歌学术统计DCGAN的引用量已经达到“4487”。

DCGAN的出发点并不是更改损失函数或者是对GAN的原理进行剖析,而是直接的对网络结构施加限制从而实现更为强大的生成模型,所以我们可以直接将DCGAN的设计方案嵌入到自己的GAN网络中。

本文包含以下3个部分:

(1)DCGAN原理分析

(2)网络训练细节与pytorch实现DCGAN

(3)生成图像的视觉结果分析与个人总结

笔记主要是自己的一些理解,如有错误请指出。

1、DCGAN原理分析

DCGAN是对Goodfellow在2014年提出的原始GAN【1】的一种改进,原始GAN的原理可以参考我之前的文章:原始GAN原理与pytorch实现动漫人脸生成 - 知乎

DCGAN网络设计中采用了当时对CNN比较流行的改进方案:

(1)将空间池化层用卷积层替代,这种替代只需要将卷积的步长stride设置为大于1的数值。改进的意义是下采样过程不再是固定的抛弃某些位置的像素值,而是可以让网络自己去学习下采样方式。

(2)将全连接层去除,(我前面关于VGG的文章分析了全连接层是如何大幅度增加网络参数数量的);作者通过实验发现了全局均值池化有助于模型的稳定性但是降低了模型的收敛速度;作者在这里说明了他是通过将生成器输入的噪声reshape成4D的张量,来实现不用全连接而是用卷积的。

(3)采用BN层,BN的全称是Batch Normalization,是一种用于常用于卷积层后面的归一化方法,起到帮助网络的收敛等作用。作者实验中发现对所有的层都使用BN会造成采样的震荡(我也不理解什么是采样的震荡,我猜是生成图像趋于同样的模式或者生成图像质量忽高忽低)和网络不稳定。

DCGAN的改进不包含严格数学证明,主要是理论分析和实验验证,其对生成器的判别的修改核心如下:

(1)使用指定步长的卷积层代替池化层

(2)生成器和判别器中都使用BN

(3)移除全连接层

(4)生成器除去输出层采用Tanh外,全部使用ReLU作为激活函数

(5)判别器所有层都使用LeakyReLU作为激活函数

DCAGN通过以上的改进得到的生成器结构如下:

2、网络训练细节与pytorch实现DCGAN

DCGAN中提到了网络的训练细节:

(1)使用Adam算法更新参数,betas=(0.5, 0.999);

(2)batch size选为128;

(3)权重使用正太分布,均值为0,标准差为0.02;

(4)学习率0.0002。

实现的过程中借鉴了一些代码的写法【2】,发现大家在生成器第一层上的写法并不统一:一种是生成器第一层不包含任何的全连接层,另一种是生成器的第一层为全连接,也就是论文中给出的生成器结构。我采用生成器不包含任何全连接层的写法,因为在图像尺寸较大时想要在生成器的第一层使用全连接是非常消耗内存的(我的做法是生成的噪声维度是4D,只不过后面两个维度都是1,也就是生成噪声为(batch_size, z_dim, 1, 1))。

实现生成器时需要注意最后一层的激活函数是Tanh,代码如下:

class Generator(nn.Module):
    def __init__(self, z_dim, ):

        super().__init__()
        self.z_dim = z_dim
        net = []

        # 1:设定每次反卷积的输入和输出通道数等
        #   卷积核尺寸固定为4,反卷积输出为“SAME”模式
        channels_in = [self.z_dim, 512, 256, 128, 64]
        channels_out = [512, 256, 128, 64, 3]
        active = ["R", "R", "R", "R", "tanh"]
        stride = [1, 2, 2, 2, 2]
        padding = [0, 1, 1, 1, 1]
        for i in range(len(channels_in)):
            net.append(nn.ConvTranspose2d(in_channels=channels_in[i], out_channels=channels_out[i],
                                          kernel_size=4, stride=stride[i], padding=padding[i], bias=False))
            if active[i] == "R":
                net.append(nn.BatchNorm2d(num_features=channels_out[i]))
                net.append(nn.ReLU())
            elif active[i] == "tanh":
                net.append(nn.Tanh())

        self.generator = nn.Sequential(*net)
        self.weight_init()

    def forward(self, x):
        out = self.generator(x)
        return out

其中的self.weight_init()就是按照论文中所说进行权重初始化,代码如下:

def weight_init(self):
    for m in self.generator.modules():
        if isinstance(m, nn.ConvTranspose2d):
            nn.init.normal_(m.weight.data, 0, 0.02)

        elif isinstance(m, nn.BatchNorm2d):
            nn.init.normal_(m.weight.data, 0, 0.02)
            nn.init.constant_(m.bias.data, 0)

虽然DCGAN论文中没有给出判别器的结构图,但是仍然可以从文中推理出判别器应该同样是5层的网络,每一层使用卷积和BN以及LeakyReLU,仍然需要注意最后一层的激活函数是sigmoid,代码如下:

class Discriminator(nn.Module):
    def __init__(self):
        """
        initialize
        
        :param image_size: tuple (3, h, w)
        """
        super().__init__()

        net = []
        # 1:预先定义
        channels_in = [3, 64, 128, 256, 512]
        channels_out = [64, 128, 256, 512, 1]
        padding = [1, 1, 1, 1, 0]
        active = ["LR", "LR", "LR", "LR", "sigmoid"]
        for i in range(len(channels_in)):
            net.append(nn.Conv2d(in_channels=channels_in[i], out_channels=channels_out[i],
                                 kernel_size=4, stride=2, padding=padding[i], bias=False))
            if i == 0:
                net.append(nn.LeakyReLU(0.2))
            elif active[i] == "LR":
                net.append(nn.BatchNorm2d(num_features=channels_out[i]))
                net.append(nn.LeakyReLU(0.2))
            elif active[i] == "sigmoid":
                net.append(nn.Sigmoid())

        self.discriminator = nn.Sequential(*net)
        self.weight_init()

    def weight_init(self):
        for m in self.discriminator.modules():
            if isinstance(m, nn.ConvTranspose2d):
                nn.init.normal_(m.weight.data, 0, 0.02)

            elif isinstance(m, nn.BatchNorm2d):
                nn.init.normal_(m.weight.data, 0, 0.02)
                nn.init.constant_(m.bias.data, 0)

    def forward(self, x):
        out = self.discriminator(x)
        out = out.view(x.size(0), -1)
        return out

其它的代码和我之前的文章原始GAN原始GAN原理与pytorch实现动漫人脸生成 - 知乎中给出的相同,这里不再重复给出。

3、生成图像的视觉结果分析与个人总结

3.1 结果分析

自己试验中采用的是欧美人脸作为训练数据,训练了9500次,得到了下面的结果:

GAN的生成器和判别器损失并不会和普通的CNN一样平滑的收敛,GAN网络的论文一般也很少给出网络的损失函数情况,自己记录的网络损失收敛情况如下:

可以看出生判别器的损失一直都很小,而生成器损失在震荡中不断的增加。

3.2 个人总结

GAN非常难以训练,尽管DCGAN本身已经被证明是有效的了,但是实验中发现如果对某些层改变BN或者改变激活函数,都可能导致网络生成的图像为噪声。

复现GAN网络时不仅要考虑到网络结构是否正确,还要考虑损失函数优化过程是否正确。所以最好一开始搭建一个简单的网络并使用小尺寸的图像,来测试损失函数优化过程正确性;然后再更改网络结构,实现生成大尺寸图像的功能。

参考:

【1】Goodfellow I, Pouget-Abadie J, Mirza M, et al. Generative adversarial nets[C]//Advances in neural information processing systems. 2014: 2672-2680.

【2】https://pytorch.org/tutorials/b

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值