Press "Enter" to skip to content

生成对抗网络(DCGAN)理论与实战分析

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

生成对抗网络常用于图像风格迁移,超分辨率图像生成,序列生成,文本风格迁移等应用场景

 

在使用AI Studio完成手写数字生成任务的过程中,对GAN的原理和训练技巧有了更深入的认识。

 

GAN的原理

 

GAN 的思想启发自博弈论中的零和游戏,包含一个生成网络G和一个判别网络D

G是一个生成式的网络,它接收一个随机的噪声Z,通过Generator生成假数据
D是一个判别网络,判别输入数据的真实性。它的输入是X,输出D(X)代表X为真实数据的概率
训练过程中,生成网络G的目标是尽量生成真实的数据去欺骗判别网络D。而D的目标就是尽量辨别出G生成的假数据和真数据。 这个博弈过程最终的平衡点是纳什均衡点

 

GAN ZOO

 

 

GAN的损失函数和训练过程

 

GAN的核心公式:

 

从两方面来说:

判别器的目标是最大化它的奖励V(D,G)
生成器的目标是最小化判别器的奖励(最大化其损失)V(D,G)

x表示真实图片,z表示输入G网络的噪声,而G(z)表示G网络生成的数据。

D(x)表示D网络判断真实数据是否真实的概率,而D(G(z))是D网络判断G生成的数据是否真实的概率。
G的目的:G希望自己生成的数据“越接近真实越好”。也就是说,G希望D(G(z))尽可能得大,这时V(D, G)会变小。因此我们看到式子的最前面的记号是min G。
D的目的:D的能力越强,D(x)应该越大,D(G(x))应该越小。这时V(D,G)会变大。因此式子对于D来说是求最大(max D)

训练过程伪代码

 

 

GAN与VAE比较

 

相比于变分自编码器, GANs没有引入任何决定性偏置( deterministic bias),变分方法引入决定性偏置,因为它们优化对数似然的下界,而不是似然度本身,这看起来导致了VAEs生成的实例比GANs更模糊

 

决定性偏置: 指VAE对输入数据的概率分布预先设定假设,然后学习该分布下的参数;而GAN通过梯度下降逼近该分布;有点类似于RL中的model based和model free

 

GAN存在的问题

 

1. Non-Convergence(不收敛)

 

与监督学习不同,损失函数基本是单调下降的;训练GAN由于需要达到纳什均衡,有时候可以用梯度下降法做到,有时候做不到.我们还没有找到很好的达到纳什均衡的方法,所以训练GAN相比VAE是不稳定的

 

下面是典型的D和G网络损失函数的学习率曲线:

 

2.Mode-Collapse(模式坍塌)

 

可以理解为生成的内容没有多样性,比如手写数字生成的结果都是某一个数字

 

一般出现在GAN训练不稳定的时候,具体表现为生成出来的结果非常差,但是即使加长训练时间后也无法得到很好的改善

 

 

原因分析:

GAN采用的是对抗训练的方式,G的梯度更新来自D,所以G生成的好不好,依赖于D的评价。
如果某一次G生成的样本可能并不是很好,但是D给出了很好的评价,或者是G生成的结果中一些特征得到了D的认可,这时候G就会认为我输出的正确的,那幺接下来我就这样输出肯定D还会给出比较高的评价(实际上G生成的并不好)
进入一种“死循环”,最终生成结果缺失一些信息,特征不全。

DCGAN的训练技巧

 

核心思想

 

 

使用卷积层替换全连接层

 

在每层后使用BatchNormalization。将特征层的输出归一化到一起,加速了训练,提升了训练的稳定性。(生成器的最后一层和判别器的第一层不加batchnorm)

 

G的隐藏层使用ReLU;G的输出层使用Tanh;D使用leakrelu激活函数,而不是RELU,防止梯度稀

 

 

训练时网络结构需要精心设计,下面的踩坑心得心得:

relu激活要放在BatchNorm后面,从结果看,深层网络,普遍使用BN取得更好的训练效果:

缓解了梯度传递问题,使模型适应更大的学习率,加速了训练;
改善了饱和非线性模型不易训练的问题;
还起到了正则化的作用。

conv层使用比较大的filter_size(5)
G风格的FC层不使用激活

代码分析:

 

# 通过上采样扩大特征图
class G(fluid.dygraph.Layer):
    def __init__(self, name_scope):
        super(G, self).__init__(name_scope)
        name_scope = self.full_name()
        img_dim = 28
        # FC层没有激活函数, 但是使用了批归一化
        self.fc1 = Linear(100, 1024)
        self.batch_norm1 = fluid.BatchNorm(1024)
        self.fc2 = Linear(1024, 128*img_dim//4*img_dim//4)
        self.batch_norm2 = fluid.BatchNorm(6272)
        # 使用反卷积对图像2倍上采样,并且使用了激活做非线性变换
        self.deconv1=Conv2DTranspose(128,64,filter_size=5, stride=2, padding=2, output_size=[14,14], act='relu')
        # 2倍上采样,并且使用了激活做非线性变换
        self.deconv2=Conv2DTranspose(64,1,filter_size=5, stride=2, padding=2, output_size=[28,28], act='tanh')

    def forward(self, z):
        #
        # My_G forward的代码
        #
        z= fluid.layers.reshape(z,shape=(-1,100))
        fc1=self.batch_norm1(self.fc1(z))
        fc2=self.batch_norm2(self.fc2(fc1))
        img=fluid.layers.reshape(fc2, shape=(-1,128,7,7))
        conv1=self.deconv1(img)
        assert conv1.shape[-1]==14, conv1.shape
        conv2=self.deconv2(conv1)
        assert conv2.shape[-1]==28
        return conv2
class D(fluid.dygraph.Layer):
    def __init__(self, name_scope):
        super(D, self).__init__(name_scope)
        name_scope = self.full_name()
        #
        # My_D的代码
        #
        self.conv1 = Conv2D(1, 64, 3,act='leaky_relu')
        self.pool2d1 = fluid.dygraph.Pool2D(pool_size=2,
                  pool_type='max',
                  pool_stride=2,
                  global_pooling=False)
        self.conv2 = Conv2D(64, 128, 3)
        # 把BN变换放在非线性激活函数前, BN使得输入数据重新回到非饱和区
        self.batch_norm1 = fluid.BatchNorm(128,act='leaky_relu')
        self.pool2d2 = fluid.dygraph.Pool2D(pool_size=2,
                  pool_type='max',
                  pool_stride=2,
                  global_pooling=False)
        self.fc1 = Linear(3200, 1024)
        self.batch_norm2 = fluid.BatchNorm(1024,act='leaky_relu')
        self.fc2 = Linear(1024, 1)

    def forward(self, img):
        #
        # My_G forward的代码
        #
        conv1 = self.pool2d1(self.conv1(img))
        conv2 = self.conv2(conv1)
        conv2 = self.batch_norm1(self.pool2d2(conv2))
        latent = fluid.layers.reshape(conv2, shape=(-1,3200))
        fc1 = self.batch_norm2(self.fc1(latent))
        fc2 = self.fc2(fc1)
        return fc2

 

参考资源

从0到1:批量规范化Batch Normalization(原理篇)
百度顶会论文复现营>GAN入门-理论(下

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注