整体思路:
1:使用噪音,通过一系列的转秩卷积(逆卷积)操作,生成一张图片;
2:使用正常的卷积神经网络判断图片的真假。
训练细节:
1:在训练判决器网络时,对真实图像,加上正标签,对假图像加上假标签,使得判别器能够完美的发现假图像;
2:训练生成器时,给假图像打上正标签,使得判别器能够返回假图像在变成真图像所需要拟合的变化。
模型细节:
1:整个网络是两个模型的组合。首先是生成器,然后是判别器;
2:在训练判别器时,使用的网络仅仅是判别器网络,训练的参数也仅仅是判别器网络的参数;
3:训练生成器时,使用的是组合的模型,即先用生成器网络生成图像,再用判别器网络判断优化方向。在优化生成器网络参数时,需要关闭判别器网络。也就是说,尽管反向传播回来的梯度是从判别器开始的,但是判别器的参数不参与优化工作,这也是为什么训练生成器时需要先将判别器网络的梯度更新关闭的原因。
代码重点讲解:
生成器网络搭建:
def generator_model():
model = tf.keras.Sequential()
#将100维的噪音升维到7*7*256,方便后面转换为图像矩阵
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) # Note: None is the batch size
#使用逆卷积操作,将图片进行尺寸的放大(逆卷积讲解:https://blog.csdn.net/nima1994/article/details/83959495;逆卷积和向上采样的区别:https://www.zhihu.com/question/290376931)
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())
#一维卷积,通道合并,生成28*28*1的图像
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
![252bf13a104bc935483cc9590de1ae16.png](https://i-blog.csdnimg.cn/blog_migrate/5990b001ae2e78f7be2ec5769dd50101.jpeg)
判别器网络搭建:
def 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))
#将特征图像展平,然后做一个汇总输出,输出维度为1,即判别图片为真假图片的概率
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):
#对真图像,定义标签为1,ones_lisk为输出real_output维度的1,然后使用交叉熵损失,表示真图像距离被判断为真图像有多大的差距
real_loss = cross_entropy(tf.ones_like(real_output), real_output)
#对假图片,定义标签为0,然后使用交叉熵损失,表示假图像距离被判断为假图像有多大的差距
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):
#将假图像的标签定为1,衡量假图像距离真图像还有多大的差距,这也是生成器网络需要优化的方向
return cross_entropy(tf.ones_like(fake_output), fake_output)
生成对抗网络(自此开始引自github,与上文不是一个作者,可能代码有出入,主要是看整体的思路):
![23687b4e267c90c25d78035ba5378bc5.png](https://i-blog.csdnimg.cn/blog_migrate/9e8092d707a8b5f5b24ef1db90a5109a.png)
将生成器网络和判别器网络加入,生成器网络在前,判别器网络在后,同时将判别器网络的参数更新关闭。因为这个网络的主要作用就是优化生成器,因此判别器不参与优化。
进行训练:
![a77511c9343e7daa15e43919b617161f.png](https://i-blog.csdnimg.cn/blog_migrate/8cb50f6b6f088129eeed6e57a4c52f30.jpeg)
首先,在83,84行初始化两个模型,然后构建生成对抗网络d_on_g。
上图中的88,89,91行是设置损失函数的,作用等同于上面写的损失函数内容。(截图和代码并不是来自同一个作者,因此这里只表达的了整体逻辑,而非真正没有错误的)
然后设置判别器网络可优化参数,对每一批次,首先先生成噪音数据,然后用噪音数据生成假图像。然后将假图像与真图像组合,并合并标签,假图像为0,真图像为1,,在106行,开始训练判别器网络,注意,此时训练的仅仅是判别器网络d。训练使用的是Keras中的train_on_batch,解释:https://blog.csdn.net/weixin_42886817/article/details/99855287
判别器网络训练完毕后,在使用噪音生成假图像,此时将其标签设置为1(在110行,[1]*BATCH_SIZE即设置y),然后关闭判别器网络的优化,开始训练d_on_g网络,此网络由生成器和判别器组成,由于前面将判别器网络的参数优化给关闭了,因此在这里只优化生成器网络。
代码来自:https://github.com/jacobgil/keras-dcgan/blob/master/dcgan.py