Deep Learning with Python
这篇文章是我学习《Deep Learning with Python》(第二版,François Chollet 著) 时写的系列笔记之一。文章的内容是从 Jupyter notebooks 转成 Markdown 的,你可以去 GitHub 或 Gitee 找到原始的 .ipynb
笔记本。
你可以去这个网站在线阅读这本书的正版原文(英文)。这本书的作者也给出了配套的 Jupyter notebooks。
本文为 第8章 生成式深度学习 (Chapter 8. Generative deep learning) 的笔记之一。
文章目录
8.5 Introduction to generative adversarial networks
生成式对抗网络简介
GAN,中文是淦,错了,生成式对抗网络(Generative Adversarial Network)。和 VAE 一样,是用来学习图像的潜在空间的。这东西可以使生成图像与真实图像“在统计上”别无二致,说人话就是,生成的图像相当逼真。但与 VAE 不同,GAN 的潜在空间无法保证具有有意义的结构,并且是不连续的。
GAN 由两部分组成:
- 生成器网络(generator network):输入一个随机向量(潜在空间中的一个随机点),并将其解码为图像。
- 判别器网络(discriminator network):输入一张图像(真实的或生成器画的),预测该图像是真实的还是由生成器网络创建的。(判别器网络也叫「对手」,adversary)
训练 GAN 的目的是使「生成器网络」能够欺骗「判别器网络」。
直观理解 GAN 是一个很励志的故事:就是说有两个人,一个伪造者,一个鉴定师。伪造者仿造大师的画,然后把自己的仿制品混在真迹里交给鉴定师鉴定,鉴定师评估每一幅画的真伪,一样看穿了哪些是伪造的。好心的鉴定师反馈了伪造者,告诉他真迹有哪些特征。伪造者根据鉴定师的意见,一步步提升自己的仿造能力。两人不厌其烦地重复这个过程,伪造者变得越来越擅长复制大师的画,鉴定师也越来越擅长找出假画。到最后,伪造者造出了一批鉴定师也无可挑剔的“仿制正品”。
GAN 的训练方式很特殊,它的优化最小值是不固定的。我们通常的「梯度下降」是沿着静态的损失地形滚下山,但 GAN 训练时每下山一步都会对整个地形造成改变。它是一个动态系统,其最优化过程是两股力量之间的平衡。所以,GAN 很难训练。想要让 GAN 正常运行,需要进行大量的模型构建、超参数调节工作。
深度卷积生成式对抗网络
我们来尝试用 Keras 实现最最最简单的 GAN。具体来说,我们会做一个深度卷积生成式对抗网络(deep convolutional GAN,DCGAN),这种东西的生成器和判别器都是深度卷积神经网络。
我们将用 CIFAR10 数据集中“frog”类别的图像训练 DCGAN。这个数据集包含 50000 张 32×32 的 RGB 图像,这些图像属于 10 个类别(每个类别 5000 张图像)。
实现流程
generator
网络将形状为(latent_dim,)
的向量映射到形状为(32, 32, 3)
的图像。discriminator
网络将形状为(32, 32, 3)
的图像映射到一个二进制得分(a binary score),用于评估图像为真的概率。gan
网络将generator
和discriminator
连接在一起:gan(x) = discriminator(generator(x))
。这个网络是将潜在向量映射到判别器的评估结果。- 使用带有
"real"
或"fake"
标签的真假图像样本来训练判别器,用常规训练普通的图像分类模型的方法。 - 为了训练生成器,使用
gan
模型的损失相对于生成器权重的梯度。在每一步都要向「让判别器更有可能将生成器解码的图像划分为“真”」移动生成器的权重,即训练生成器来欺骗判别器。
实用技巧
训练和调节 GAN 的过程非常困难。所以我们需要记住一些前人总结出的实用技巧。这些技巧一般很有用,但并不能适用于所有情况。这些东西都没有理论依据,都是玄学,所以不解释直接写结论:
- 使用 tanh 作为生成器最后一层的激活,而不用 sigmoid。
- 使用正态分布(高斯分布)对潜在空间中的点进行采样,而不用均匀分布。
- 随机性能够提高稳健性。GAN 训练时可能以各种方式“卡住”(达到错误的动态平衡),在训练过程中引入随机性有助于防止出现这种情况,引入随机性的方式有两种:
- 在判别器中使用 dropout;
- 向判别器的标签添加随机噪声;
- 稀疏的梯度会妨碍 GAN 的训练。「最大池化运算」和 「ReLU 激活」可能导致梯度稀疏,所以推荐:
- 使用「步进卷积」代替「最大池化」来进行下采样;
- 使用 LeakyReLU 层来代替 ReLU 激活;
- 在生成的图像中,常会见到棋盘状伪影,这是由于生成器中像素空间覆盖不均匀。解决这个问题的办法是:每当在生成器和判别器中都使用步进 Conv2DTranpose 或 Conv2D 时,内核大小要能够被步幅整除。
由于步幅大小和内核大小不匹配而导致的棋盘状伪影示例图:
生成器的实现
开始构建年轻人的第一个 GAN 了!!
首先开发 generator 模型:将来自潜在空间的向量转换为一张候选图像。
为了避免训练时“卡住”,在判别器和生成器中都使用 dropout。
# GAN 生成器网络
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np
height = 32
width = 32
channels = 3
latent_dim = 32
generator_input = keras.Input(shape=(latent_dim,))
# 将输入转换为大小为 16×16 的 128 个通道的特征图
x = layers.Dense(128 * 16 * 16)(generator_input)
x = layers.LeakyReLU()(x)
x = layers.Reshape((16, 16, 128))(x)
x = layers.Conv2D(256, 5, padding='same')(x)
x = layers.LeakyReLU()(x)
# 上采样为 32×32
x = layers.Conv2DTranspose(256, 4