引言
本篇文论文所介绍的DCGAN网络就是在GAN网络的基础上进行改进并引入了图像的卷积操作,即把CNN与GAN网络很好的结合起来,使得网络的生成器最终可以从随机噪声中生成一张以假乱真的图片。该论文作者Alec Radford & Luke Metz。作者在文章中并没有对DCGAN网络的具体原理进行公式性的阐述,而是直接呈现了作者自己设计的网络架构和一些参数调整的细节。相关摘要,介绍和相关工作可以直接查看原论文
方法和模型架构
具体来说本篇论文就是把GAN原始论文中的生成器G和判别器D用两个CNN网络来替代。对于这两个CNN网络,做出了一些调整。
首先就是使用了一个全卷积网络,用步长卷积(即步长大于1的卷积)来替换掉池化层,这样的目的在于希望网络可以自己学习到下采样的方式,相比于固定的池化层更加灵活。这样的方法同时运用在生成器和判别器中(在生成器中主要是要使用转职卷积进行上采样)。
第二就是当时的趋势是取消全连接层,最近的做法是使用全局平均池化层代替全连接层,但是这样做虽然提高了稳定性却降低了收敛速度。对于生成器本,GAN的输入是采用均匀分布初始化的一维噪声,之后还是会使用一个全连接层,得到的结果reshape成一个4D张量,就可以进行一层层的卷积操作。对于判别器,最后的卷积层则是先flatten然后再送入sigmoid分类器进行输出。
第三就是每层中都加入Batch Normalization层,这有助于模型的稳定和收敛,也有助防止过拟合。但是通过实验表明,对网络所有层使用BN层会使得样本不稳定,所以只对生成器的输出和判别器的输入层加入BN层。
生成器中,除了输出层用Tanh,其余层都是用ReLU。而对于判别器,使用LeakyReLU则要更好点。
综上所述,本网络的一些改进的点在于:
- 将所有的池化层都用步长卷积(判别器)和转置步长卷积(生成器)代替。
- 在生成器和判别器中加入BN层。
- 对于深层网络架构去除全连接层。
- 对生成器的除了输出层的其它层使用ReLU,输出层用Tanh。
- 对判别器所有曾都是用LeakyReLU。
训练细节
接下来作者提及了一些他训练模型中运用的一些参数调整。
- 首先图片并没有进行一些预处理,只是利用tanh将它的输出映射在了[-1,1]的区间。
- 使用mini-batch SGD,batch大小为128。
- 所有的参数都采用0均值,标准差为0.02的初始化方式。
- LeakyReLU的斜率设置为0.2。
- 使用Adam优化器,由于推荐学习率0.001过大,作者修改到了使用0.0002。此外将的值从默认的0.9调整到了0.5保证模型的稳定。
下图是作者给出的生成器的架构图。第一层就是一个均匀分布的一维噪声,然后经过一层后映射成的特征图。其中的上采样过程使用了转置卷积,最后的输出就是本次模型生成的图片大小,接下来它将会交给判别器进行判别。
实验
DCGAN的网络实现在github上已经有很多现成的实现了,这里使用了tensorflow官网教程中关于DCGAN的实现。在具体应用中可以发现,其实许多的参数和网络结构设置都是可以根据实际实验需要进行调整的,像在tensorflow的官网教程中,对生成器和判别器中激活函数都有些许调整并且使用了全连接层以及加入了dropout层。
下面就是生成器的代码架构:
def make_generator_model():
model = tf.keras.Sequential()
model.add(layers.Dense(7*7*256, use_bias=False, input_shape=(100,)))
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add(layers.Reshape((7, 7, 256)))
assert model.output_shape == (None, 7, 7, 256)
model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding ='same', use_bias=False))
assert model.output_shape == (None, 7, 7, 128)
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))
assert model.output_shape == (None, 14, 14, 64)
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))
assert model.output_shape == (None, 28, 28, 1)
return model
def make_discriminator_model():
model = tf.keras.Sequential()
model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',
input_shape=[28, 28, 1]))
model.add(layers.LeakyReLU())
model.add(layers.Dropout(0.3))
model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
model.add(layers.LeakyReLU())
model.add(layers.Dropout(0.3))
model.add(layers.Flatten())
model.add(layers.Dense(1))
return model
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(real_output, fake_output):
real_loss = cross_entropy(tf.ones_like(real_output), real_output)
fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
total_loss = real_loss + fake_loss
return total_loss
def generator_loss(fake_output):
return cross_entropy(tf.ones_like(fake_output), fake_output)
该篇论文给出CNN和GAN结合的做法并进行了改进,在实际使用中,也可以根据实际不同的需求进行调整以达到最优的情况。
实际实验中,使用反卷积的时候,当步长不能整除卷积核大小的时候,上采样出来的图像会很容易出现棋盘效应,棋盘效应的直观原因可以参考这里,应该说转置卷积不可避免的就会带来棋盘效应,就算步长能整除卷积核大小,也可能因为权重分学习的不均匀导致棋盘效应。所以推荐的做法是使用插值先进行上采样再用same卷积也能达到同样的效果且没有棋盘效应。
头像数据集的获取可以点击查看这篇博客
具体的教程可以去tensorflow官网教程进行查阅,它实现了一个自动生成手写数据集的网络,在这个框架的基础上进行改动也可以生成各种其它尺寸的不同图片。我对网络结构修改后在动漫人物头像的数据集上训练后,网络便能生成动漫人物头像,大概训练了200步,可以看到还是有不少瑕疵。
损失函数的定义也是根据原始GAN网络来定义的
加入了dropout层。
而判别器的代码则是:
可以看到第一层用了全连接,并且中间层也并没有使用原论文中所说的ReLU而是也用了LeakyReLU。