DCGAN的原理及应用

本文研究基于GAN基础上,结合卷积神经网络(CNN)的DCGAN模型。

相关论文:Unsupervised Representations Learning With Deep Convolutional Generative Adversarial Networks

DCGAN简介

DCGAN是将CNN与GAN的一种结合。
其将卷积网络引入到生成式模型当中来做无监督的训练,利用卷积网络强大的特征提取能力来提高生成网络的学习效果。
DCGAN的原理和GAN对抗生成是一样的。它只是把GAN的G和D换成了两个卷积神经网络(CNN)。但不是直接换就可以了,
DCGAN对卷积神经网络的结构做了一些改变,以提高样本的质量和收敛的速度,这些改变有:

  1. 取消所有pooling层。G网络中使用转置卷积(transposed convolutional layer)进行上采样,D网络中用加入stride的卷积代替pooling。
  2. 除了生成器模型的输出层和判别器模型的输入层,在网络其它层上都使用了Batch Normalization,使用BN可以稳定学习,有助于处理初始化不良导致的训练问题。
  3. 去掉全连接层,使网络变为全卷积网络
  4. G网络中使用ReLU作为激活函数,最后一层使用tanh
  5. D网络中使用LeakyReLU作为激活函数

其中,转置卷积(也称反卷积)transposed conv或者deconv。
动图一:卷积
动图二:转置卷积
上图为卷积与转置卷积,注意图中蓝色(下面)是输入,绿色(上面)是输出,卷积和反卷积在 p、s、k 等参数一样时,是相当于 i 和 o 调了个位。 这里说明了反卷积的时候,是有补0的,即使人家管这叫no padding。图中反卷积应该从蓝色 2×2 扩展成绿色 4×4。
转置并不是指这个 3×3 的核 w 变为 wT,但如果将卷积计算写成矩阵乘法(在程序中,为了提高卷积操作的效率,就可以这么干,比如tensorflow中就是这种实现),而这样的矩阵乘法,恰恰等于 w 左右翻转再上下翻转后与补0的 Y 卷积的情况。

其中,batch normalization相关:
传统的神经网络,只是在将样本xx输入输入层之前对xx进行标准化处理(减均值,除标准差),以降低样本间的差异性。BN是在此基础上,不仅仅只对输入层的输入数据x进行标准化,还对每个隐藏层的输入进行标准化。

标准化后的x乘以权值矩阵Wh1加上偏置bh1得到第一层的输入wh1x+bh1,经过激活函数得到h1=ReLU(wh1x+bh1),然而加入BN后, h1的计算流程如虚线框所示:

再其中,求平均数mean, variance = tf.nn.moments(x, axes, name=None, keep_dims=False)这个函数的输入参数x表示样本,形如[batchsize, height, width, kernels]
axes表示在哪个维度上求解 。
函数输出均值和方差。移动平均就是滑动平均的方法,详见另一篇文章:使用tensorflow实现cnn。

生成网络模型示意图:

生成网络

通过TensorFlow实现DCGAN

DCGAN源码解析:参考博客
源码地址
先看main.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
with tf.Session(config=run_config) as sess:
if FLAGS.dataset == 'mnist':
dcgan = DCGAN(
sess,
input_width=FLAGS.input_width,
input_height=FLAGS.input_height,
output_width=FLAGS.output_width,
output_height=FLAGS.output_height,
batch_size=FLAGS.batch_size,
y_dim=10,
c_dim=1,
dataset_name=FLAGS.dataset,
input_fname_pattern=FLAGS.input_fname_pattern,
is_crop=FLAGS.is_crop,
checkpoint_dir=FLAGS.checkpoint_dir,
sample_dir=FLAGS.sample_dir)

因为我们使用DCGAN来生成MNIST数字手写体图像,注意这里的y_dim=10,表示0到9这10个类别,c_dim=1,表示灰度图像。
再看model.py:
DCGAN-tensorflow核心是model.py
model.py定义了生成器和判别器,其中生成器使用deconv2d,判别器使用conv2d,这里conv2d是通过调用tensorflown的conv2d实现和权重相乘,再用bias_add实现偏置项相加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def discriminator(self, image, y=None, reuse=False):
with tf.variable_scope("discriminator") as scope:
if reuse:
scope.reuse_variables()

yb = tf.reshape(y, [self.batch_size, 1, 1, self.y_dim])
x = conv_cond_concat(image, yb)

h0 = lrelu(conv2d(x, self.c_dim + self.y_dim, name='d_h0_conv'))
h0 = conv_cond_concat(h0, yb)

h1 = lrelu(self.d_bn1(conv2d(h0, self.df_dim + self.y_dim, name='d_h1_conv')))
h1 = tf.reshape(h1, [self.batch_size, -1])
h1 = tf.concat_v2([h1, y], 1)

h2 = lrelu(self.d_bn2(linear(h1, self.dfc_dim, 'd_h2_lin')))
h2 = tf.concat_v2([h2, y], 1)

h3 = linear(h2, 1, 'd_h3_lin')

return tf.nn.sigmoid(h3), h3

1
2
3
4
5
6
7
8
9
10
11
12
def conv2d(input_, output_dim, 
k_h=5, k_w=5, d_h=2, d_w=2, stddev=0.02,
name="conv2d"):
with tf.variable_scope(name):
w = tf.get_variable('w', [k_h, k_w, input_.get_shape()[-1], output_dim],
initializer=tf.truncated_normal_initializer(stddev=stddev))
conv = tf.nn.conv2d(input_, w, strides=[1, d_h, d_w, 1], padding='SAME')

biases = tf.get_variable('biases', [output_dim], initializer=tf.constant_initializer(0.0))
conv = tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape())

return conv

这里batch_size=64,image的维度为[64 28 28 1],y的维度是[64 10],yb的维度[64 1 1 10],x将image和yb连接起来,这相当于是使用了Conditional GAN,为图像提供标签作为条件信息,于是x的维度是[64 28 28 11],将x输入到卷积层conv2d,conv2d的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
def conv2d(input_, output_dim, 
k_h=5, k_w=5, d_h=2, d_w=2, stddev=0.02,
name="conv2d"):
with tf.variable_scope(name):
w = tf.get_variable('w', [k_h, k_w, input_.get_shape()[-1], output_dim],
initializer=tf.truncated_normal_initializer(stddev=stddev))
conv = tf.nn.conv2d(input_, w, strides=[1, d_h, d_w, 1], padding='SAME')

biases = tf.get_variable('biases', [output_dim], initializer=tf.constant_initializer(0.0))
conv = tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape())

return conv

卷积核的大小为55,stride为[1 2 2 1],通过2的卷积步长可以替代pooling进行降维,padding=‘SAME’,则卷积的输出维度为[64 14 14 11]。然后使用batch normalization及leaky ReLU的激活层,输出与yb再进行concat,得到h0,维度为[64 14 14 21]。同理,h1的维度为[64 77*74+10],h2的维度为[64 1024+10],然后连接一个线性输出,得到h3,维度为[64 1],由于我们希望判别器的输出代表概率,所以最终使用一个sigmoid的激活。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def generator(self, z, y=None):
with tf.variable_scope("generator") as scope:

s_h, s_w = self.output_height, self.output_width
s_h2, s_h4 = int(s_h/2), int(s_h/4)
s_w2, s_w4 = int(s_w/2), int(s_w/4)

# yb = tf.expand_dims(tf.expand_dims(y, 1),2)
yb = tf.reshape(y, [self.batch_size, 1, 1, self.y_dim])
z = tf.concat_v2([z, y], 1)

h0 = tf.nn.relu(
self.g_bn0(linear(z, self.gfc_dim, 'g_h0_lin')))
h0 = tf.concat_v2([h0, y], 1)

h1 = tf.nn.relu(self.g_bn1(
linear(h0, self.gf_dim*2*s_h4*s_w4, 'g_h1_lin')))
h1 = tf.reshape(h1, [self.batch_size, s_h4, s_w4, self.gf_dim * 2])

h1 = conv_cond_concat(h1, yb)

h2 = tf.nn.relu(self.g_bn2(deconv2d(h1,
[self.batch_size, s_h2, s_w2, self.gf_dim * 2], name='g_h2')))
h2 = conv_cond_concat(h2, yb)

return tf.nn.sigmoid(
deconv2d(h2, [self.batch_size, s_h, s_w, self.c_dim], name='g_h3'))

output_height和output_width为28,因此s_h和s_w为28,s_h2和s_w2为14,s_h4和s_w4为7。在这里z为平均分布的随机分布数,维度为[64 100],y的维度为[64 10],yb的维度是[64 1 1 10],z与y进行一个concat得到[64 110]的tensor,输入到一个线性层,输出维度是[64 1024],再经过batch normalization以及ReLU激活,并与y进行concat,输出h0的维度是[64 1034],同样的再经过一个线性层输出维度为[64 12877],再进行reshape并与yb进行concat,得到h1,维度为[64 7 7 138],然后输入到一个deconv2d,做一个反卷积,也就是文中说的fractional strided convolutions,再经过batch normalization以及ReLU激活,并与yb进行concat,输出h2的维度是[64 14 14 138],最后再输入到deconv2d层以及sigmoid激活,得到生成器的输出,维度为[64 28 28 1]。
生成器以及判别器的输出:

1
2
3
4
5
6
self.G = self.generator(self.z, self.y)
self.D, self.D_logits = \
self.discriminator(inputs, self.y, reuse=False)

self.D_, self.D_logits_ = \
self.discriminator(self.G, self.y, reuse=True)

其中D表示真实数据的判别器输出,D_表示生成数据的判别器输出。
再看损失函数:

1
2
3
4
5
6
7
8
9
self.d_loss_real = tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(
logits=self.D_logits, targets=tf.ones_like(self.D)))
self.d_loss_fake = tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(
logits=self.D_logits_, targets=tf.zeros_like(self.D_)))
self.g_loss = tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(
logits=self.D_logits_, targets=tf.ones_like(self.D_)))

即对于真实数据,判别器的损失函数d_loss_real为判别器输出与1的交叉熵,而对于生成数据,判别器的损失函数d_loss_fake为输出与0的交叉熵,因此判别器的损失函数d_loss=d_loss_real+d_loss_fake;生成器的损失函数是g_loss判别器对于生成数据的输出与1的交叉熵。
优化器:

1
2
3
4
d_optim = tf.train.AdamOptimizer(config.learning_rate, beta1=config.beta1) \
.minimize(self.d_loss, var_list=self.d_vars)
g_optim = tf.train.AdamOptimizer(config.learning_rate, beta1=config.beta1) \
.minimize(self.g_loss, var_list=self.g_vars)

训练:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
for epoch in xrange(config.epoch):
batch_idxs = min(len(data_X), config.train_size) // config.batch_size

for idx in xrange(0, batch_idxs):

batch_images = data_X[idx*config.batch_size:(idx+1)*config.batch_size]
batch_labels = data_y[idx*config.batch_size:(idx+1)*config.batch_size]

batch_images = np.array(batch).astype(np.float32)[:, :, :, None]

batch_z = np.random.uniform(-1, 1, [config.batch_size, self.z_dim]) \
.astype(np.float32)

# Update D network
_, summary_str = self.sess.run([d_optim, self.d_sum],
feed_dict={
self.inputs: batch_images,
self.z: batch_z,
self.y:batch_labels,
})
self.writer.add_summary(summary_str, counter)

# Update G network
_, summary_str = self.sess.run([g_optim, self.g_sum],
feed_dict={
self.z: batch_z,
self.y:batch_labels,
})
self.writer.add_summary(summary_str, counter)

# Run g_optim twice to make sure that d_loss does not go to zero (different from paper)
_, summary_str = self.sess.run([g_optim, self.g_sum],
feed_dict={ self.z: batch_z, self.y:batch_labels })
self.writer.add_summary(summary_str, counter)

errD_fake = self.d_loss_fake.eval({
self.z: batch_z,
self.y:batch_labels
})
errD_real = self.d_loss_real.eval({
self.inputs: batch_images,
self.y:batch_labels
})
errG = self.g_loss.eval({
self.z: batch_z,
self.y: batch_labels
})

counter += 1

文章目录
  1. 1. DCGAN简介
  2. 2. 通过TensorFlow实现DCGAN
,