README.md 31.5 KB
Newer Older
Y
Yang Wang 已提交
1 2 3
# 对抗式生成网络

## 背景介绍
Y
Yang Wang 已提交
4

5
之前的几章中,我们主要介绍的深度学习模型都是在有监督学习(supervised learning)条件下的判别式模型(discriminative models)。在这些例子里,训练数据 X 都是带有标签 y 的,如图像识别中的类别标号,或是语音识别中对应的真实文本。模型的输入是 X,输出是 y,训练得到的模型表示从X到y的映射函数 y=f(X)。
Y
Yang Wang 已提交
6

W
wangyang59 已提交
7
和判别式网络模型相对的一类模型是生成式模型(generative models)。它们通常是通过非监督训练(unsupervised learning)来得到的。这类模型的训练数据里只有 X,没有y。训练的目标是希望模型能挖掘到训练数据的统计分布信息以及内部结构\[[11](#参考文献)\]
Y
Yang Wang 已提交
8

W
wangyang59 已提交
9
生成模型在很多方向都有着广泛的应用。在图像处理方向,生成模型可以用来做图像自动生成、图像去噪、和缺失图像补全等应用。研究生成模型的另一个动机是,人们认为如果能够生成很好的数据,那么很可能这个生成模型就学习到了这组数据的一个很好的通用表示(representation),随后就可以用这个学到的表示来完成其他的一些任务。例如在半监督(semi-supervised)学习的条件下,把生成模型生成的数据加入分类器训练当中,能够降低分类器训练对于标记数据数量的要求\[[6](#参考文献)\]。真实世界中大量数据都是没有标注的,人为标注数据会耗费大量人力财力,这就使生成模型有了它的用武之地。
Y
Yang Wang 已提交
10

W
wangyang59 已提交
11
之前出现的生成模型,一般是直接构造模型$P_{model}(x; \theta)$来模拟真实数据分布$P_{data}(x)$。而这个模拟的过程,通常是由最大似然(Maximum Likelihood)的办法来调节模型参数,使得观测到的真实数据在该模型下概率最大\[[6](#参考文献)\]。常用的生成模型有以下几种:
W
wangyang59 已提交
12

W
wangyang59 已提交
13
1. 深度玻尔兹曼机(Deep Boltzmann Machine)\[[4](#参考文献)\]: 深度玻尔兹曼机是在概率图模型(probabilistc graphical model)的框架下由多个隐层(hidden layer)搭建的无向图模型(Markov random field)。具体的模型结构可参见图1,图中各层之间的是通过受限玻尔兹曼机(restricted Boltzmann machine)的结构来连接的。这类模型参数的学习一般需要通过马尔可夫链-蒙地卡罗(Markov-Chain-Monte-Carlo)\[[12](#参考文献)\]的方法来取样本,所以计算量会比较大。
14
2. 变分自编码器(variational autoencoder)\[[3](#参考文献)\]:它是在概率图模型的框架下搭建的有向图模型,并结合了深度学习和统计推断方法,希望将数据表示在一个隐层空间$z$中,而其中仍用神经网络做非线性映射,具体模型结构可参加图1。这类模型的参数学习用的近似方法是构造一个似然的下限(Likelihood lower-bound),然后用变分的办法来提高这个下限的值,从而达到提高数据似然的目的。这种方法的问题是产生的图片看起来会比较模糊。
W
wangyang59 已提交
15
3. 像素循环神经网络(Pixel Recurrent Neural Network)\[[2](#参考文献)\]:它是对每个像素相对于周围像素的条件概率进行建模,也就是说根据周围的像素来一个像素一个像素的生成图片。例如图1中红色像素$x_i$的值就是依赖于之前生成的所有蓝色像素。这种方法的问题是对于一个n维的数据,需要n步才能生成,速度较慢。
W
wangyang59 已提交
16 17

<p align="center">
W
wangyang59 已提交
18 19
    <img src="./image/background_intro.png" width="800" height="300"><br/>
    图1. 生成模型概览
W
wangyang59 已提交
20
</p>
Y
Yang Wang 已提交
21

W
wangyang59 已提交
22
为了解决上面这些模型的问题,人们又提出了本章所要介绍的另一种生成模型,对抗式生成网络(Generative Adversarial Network - GAN)\[[1](#参考文献)\]。它相比于前面提到的方法,具有生成网络结构灵活,产生样本快,生成图像看起来更真实的优点。GAN的核心思想是,为了能更好地训练一个生成式神经网络,引入一个与它“对抗”的判别式神经网络来辅助学习,不断激励生成神经网络生成更优质的数据。
W
wangyang59 已提交
23

24 25
## 效果展示

26
一个训练好的对抗式生成网络,它的输入是一个随机生成的向量(相当于不需要任何有意义的输入),而输出是一个和训练数据相类似的数据样本。如果训练数据是二维单位均匀分布的数据,那么输出的也是二维单位均匀分布的数据(参见图2左);如果训练数据是MNIST手写数字图片(详见[第二章](https://github.com/PaddlePaddle/book/blob/develop/recognize_digits/README.md)),那么输出也是类似MNIST的数字图片(参见图2中,其中每个数字是由一个随机向量产生);如果训练数据是CIFAR物体图片(详见[第三章](https://github.com/PaddlePaddle/book/blob/develop/image_classification/README.md)),那么输出也是类似物体的图片(参见图2右)。图2中的三幅图都是生成模型根据随机噪声生成的图片,不同之处仅在于输入的训练数据不同。
Y
Yang Wang 已提交
27 28

<p align="center">
W
wangyang59 已提交
29 30
    <img src="./image/gan_samples.jpg" width="800" height="300"><br/>
    图2. GAN生成效果展示图
Y
Yang Wang 已提交
31 32 33
</p>


34
## 模型概览
W
wangyang59 已提交
35

W
wangyang59 已提交
36
### 对抗式网络结构
W
wangyang59 已提交
37
对抗式生成网络的基本结构是将一个已知概率分布(如高斯分布)的随机变量$z$输入一个生成模型$G$,得到其生成的概率分布,并用一个分类器$D$判断该样本为真实数据还是$G$生成的数据。训练生成模型的过程就是调节生成模型的参数,使得生成的概率分布趋向于真实数据概率分布的过程。
Y
Yang Wang 已提交
38

39
对抗式生成网络和之前的生成模型最大的创新就在于,用一个判别式神经网络来描述生成的概率分布和真实数据概率分布之间的差别。也就是说,我们用一个判别式模型$D$辅助构造优化目标函数,来训练一个生成式模型$G$。$G$和$D$在训练时是处在相互对抗的角色下,$G$的目标是尽量生成和真实数据看起来相似的伪数据,从而使得$D$无法分别数据的真伪;而$D$的目标是能尽量分别出哪些是真实数据,哪些是$G$生成的伪数据。两者在竞争的条件下,能够相互提高各自的能力,最后收敛到一个均衡点:生成器生成的数据分布和真实数据分布完全一样,而判别器完全无法区分数据的真伪。
Y
Yang Wang 已提交
40

W
wangyang59 已提交
41 42
### 对抗式训练方法
对抗式训练里,具体训练流程是不断交替执行如下两步(参见图3):
43

44 45
1. 训练判别器$D$(如图3左):
   1. 固定$G$的参数不变,输入一组随机噪声,得到一组(产生式)输出,$X_f$,并将其标号(label)设置为"假"。
W
wangyang59 已提交
46
   2. 从训练数据 X 采样一组 $X_r$,并将其标号设置为"真"。
47
   3. 用这两组数据更新模型$D$,从而使$D$能够分辨$G$产生的数据和真实训练数据。
Y
Yi Wang 已提交
48

49 50 51 52
2. 训练生成器$G$(如图3右):
   1. 把$G$的输出和$D$的输入连接起来,得到一个网路。
   2. 给$G$一组随机噪声作为输入,输出生成数据。
   3. 将$G$生成的数据输入$D$。在$D$的输出端,优化目标是通过更新$G$的参数来最小化$D$对生成数据的判别结果和“真”的差别。
Y
Yang Wang 已提交
53

54 55
<p align="center">
    <img src="./image/gan_ig.png" width="500" height="400"><br/>
W
wangyang59 已提交
56
    图3. GAN模型训练流程图 [6]
57 58
</p>

Y
Yi Wang 已提交
59 60 61 62
上述方法实际上在优化如下目标:

$$\min_G \max_D \frac{1}{N}\sum_{i=1}^N[\log D(x^i) + \log(1-D(G(z^i)))]$$

63
其中$x$是真实数据,$z$是随机产生的输入,$N$是训练数据的数量。这个目标函数的意思是:真实数据被分类为真的概率加上伪数据被分类为假的概率。因为上述两步交替优化$G$生成结果的仿真程度(看起来像x),和$D$分辨真伪数据的能力,所以这个方法被称为对抗(adversarial)方法。
Y
Yang Wang 已提交
64

W
wangyang59 已提交
65
### 基本GAN模型
66
参照\[[1](#参考文献)\]中的实现,我们将$G$和$D$都构造为三层全连接组成的网络,并且在某些全联接层后面加入了批标准化层(batch normalization)。所用网络结构在图4中给出。
W
wangyang59 已提交
67 68

<p align="center">
W
wangyang59 已提交
69
    <img src="./image/gan_conf_graph.png" width="700" height="400"><br/>
W
wangyang59 已提交
70
    图4. GAN模型结构图
W
wangyang59 已提交
71 72
</p>

W
wangyang59 已提交
73
### DCGAN模型
74
由于上面的这种网络都是由全连接层组成,所以没有办法很好的生成图片数据,也没有办法做的很深。所以在随后的论文中,人们提出了深度卷积对抗式生成网络(deep convolutional generative adversarial network or DCGAN)\[[5](#参考文献)\]。在DCGAN中,生成器$G$是由多个卷积转置层(transposed convolution)组成的,这样可以用更少的参数来生成质量更高的图片,而判别器是由多个卷积层组成。卷积转置层和卷积层的关系是,卷积转置层的向前传递(feedforward)操作类似于卷积层的向后传递(back-propagation)操作。也就是说,卷积转着层向前传递时,输入图片的每个像素都和整个卷积核(kernel)相乘,然后把结果叠加到输出图片的相应位置\[[7](#参考文献)\]。 具体网络结构可参见图5。
Y
Yang Wang 已提交
75 76

<p align="center">
W
wangyang59 已提交
77
    <img src="./image/dcgan_conf_graph.png" width="700" height="400"><br/>
W
wangyang59 已提交
78
    图5. DCGAN生成器模型结构
Y
Yang Wang 已提交
79 80
</p>

Y
Yang Wang 已提交
81

W
wangyang59 已提交
82 83 84
## 数据准备

这章会用到三种数据,一种是二维均匀分布随机数(后面会用基本GAN模型训练),一种是MNIST手写数字图片(后面会用DCGAN模型训练),一种是CIFAR-10物体图片(后面会用DCGAN模型训练)。
Y
Yang Wang 已提交
85

W
wangyang59 已提交
86
二维均匀分布随机数的生成方式如下:
Y
Yang Wang 已提交
87 88

```python
W
wangyang59 已提交
89 90 91 92
# 合成二维均匀分布随机数 gan_trainer.py:114
# numpy.random.rand会生成0-1之间的均匀分布随机数
# 后面的参数是让其生成两百万个这样的随机数,然后排成1000000*2的矩阵,
# 这样可以当做1000000个二维均匀分布随机数来使用
Y
Yang Wang 已提交
93 94 95 96 97
def load_uniform_data():
    data = numpy.random.rand(1000000, 2).astype('float32')
    return data
```

W
wangyang59 已提交
98
MNIST/CIFAR数据可以分别通过执行下面的命令来下载:
Y
Yang Wang 已提交
99 100 101 102

```bash
$cd data/
$./get_mnist_data.sh
W
wangyang59 已提交
103
$./get_cifar_data.sh
Y
Yang Wang 已提交
104 105
```

W
wangyang59 已提交
106
## 模型配置说明
Y
Yi Wang 已提交
107 108

由于对抗式生产网络涉及到多个神经网络,所以必须用PaddlePaddle Python API来训练。下面的介绍也可以部分的拿来当作PaddlePaddle Python API的使用说明。
Y
Yang Wang 已提交
109

W
wangyang59 已提交
110
### 数据定义
Y
Yi Wang 已提交
111 112

这里数据没有通过data provider提供,而是在`gan_trainer.py`里面直接产生`data_batch`并以`Arguments`的形式提供给trainer。
W
wangyang59 已提交
113 114 115

```python
def prepare_generator_data_batch(batch_size, noise):
116 117
    # generator训练标签。根据前文的介绍,generator是为了让自己的生成的数据
    # 被标记为真,所以这里的标签都统一生成1,也就是真
W
wangyang59 已提交
118
    label = numpy.ones(batch_size, dtype='int32')
119
    # 数据是Arguments的类型,这里创建的一个有两个位置的Arguments
W
wangyang59 已提交
120
    inputs = api.Arguments.createArguments(2)
121
    # 第一个Argument位置放noise
W
wangyang59 已提交
122
    inputs.setSlotValue(0, api.Matrix.createDenseFromNumpy(noise))
123
    # 第二个Argument位置放label
W
wangyang59 已提交
124 125 126
    inputs.setSlotIds(1, api.IVector.createVectorFromNumpy(label))
    return inputs

127
# 为generator训练创造数据
W
wangyang59 已提交
128 129 130 131 132 133 134
data_batch_gen = prepare_generator_data_batch(batch_size, noise)
# 把数据data_batch_gen传递给generator trainer
gen_trainer.trainOneDataBatch(batch_size, data_batch_gen)
```

### 算法配置

W
wangyang59 已提交
135
在这里,我们指定了模型的训练参数, 选择学习率(learning rate)和batch size。这里用到的优化方法是AdamOptimizer\[[8](#参考文献)\],它的`beta1`参数比默认值0.9小很多是为了使学习的过程更稳定。
W
wangyang59 已提交
136 137 138 139 140 141 142

```python
settings(
    batch_size=128,
    learning_rate=1e-4,
    learning_method=AdamOptimizer(beta1=0.5))
```
143
    
Y
Yang Wang 已提交
144
### 模型结构
145
本章里我们主要用到两种模型。一种是基本的GAN模型,主要由全连接层搭建,在`gan_conf.py`里面定义。另一种是DCGAN模型,主要由卷积层搭建,在`gan_conf_image.py`里面定义。
W
wangyang59 已提交
146 147

#### 对抗式网络基本框架
148
在文件`gan_conf.py``gan_conf_image.py`当中我们都定义了三个网络, **generator_training**, **discriminator_training****generator**. 和前文提到的模型结构的关系是:**discriminator_training** 是判别器,**generator** 是生成器,**generator_training** 是生成器加上判别器,这样构造的原因是因为训练生成器时需要用到判别器提供目标函数。这个对应关系在下面这段代码中定义:
W
wangyang59 已提交
149 150

```python
W
wangyang59 已提交
151 152 153 154 155 156
if is_generator_training:
    noise = data_layer(name="noise", size=noise_dim)
    # 函数generator定义了生成器的结构,生成器输入噪音z,输出伪数据。
    sample = generator(noise)

if is_discriminator_training:
157
    # 判别器输入为真实数据或者伪数据
W
wangyang59 已提交
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
    sample = data_layer(name="sample", size=sample_dim)

if is_generator_training or is_discriminator_training:
    label = data_layer(name="label", size=1)
    # 函数discriminator定义了判别器的结构,判别器输入样本数据,
    # 输出为该样本为真的概率
    prob = discriminator(sample)
    cost = cross_entropy(input=prob, label=label)
    classification_error_evaluator(
        input=prob, label=label, name=mode + '_error')
    outputs(cost)

if is_generator:
    noise = data_layer(name="noise", size=noise_dim)
    outputs(generator(noise))
```
不同的generator和discriminator函数就决定了不同的GAN模型的实现。下面我们就分别介绍基本GAN模型和DCGAN模型里面这两个函数的定义。

#### 基本GAN模型生成器和判别器定义
```python
178
# 下面这个函数定义了基本GAN模型里面生成器的结构,可参见图4
W
wangyang59 已提交
179 180 181 182 183 184
def generator(noise):
    # 这里is_static=is_discriminator_training的意思是在训练判别器时,
    # 生成器的参数保持不变。
    param_attr = ParamAttr(is_static=is_discriminator_training)
    # 这里bias的初始值设成1.0是为了让神经元都尽量处于开启(activated)状态
    # 这样便于Relu层向回传递导数信号
W
wangyang59 已提交
185
    bias_attr = ParamAttr(
W
wangyang59 已提交
186
        is_static=is_discriminator_training, initial_mean=1.0, initial_std=0)
W
wangyang59 已提交
187 188

    hidden = fc_layer(
W
wangyang59 已提交
189 190
        input=noise,
        name="gen_layer_hidden",
W
wangyang59 已提交
191 192 193 194 195 196 197
        size=hidden_dim,
        bias_attr=bias_attr,
        param_attr=param_attr,
        act=ReluActivation())

    hidden2 = fc_layer(
        input=hidden,
W
wangyang59 已提交
198
        name="gen_hidden2",
W
wangyang59 已提交
199 200 201 202 203 204 205 206
        size=hidden_dim,
        bias_attr=bias_attr,
        param_attr=param_attr,
        act=LinearActivation())

    hidden_bn = batch_norm_layer(
        hidden2,
        act=ReluActivation(),
W
wangyang59 已提交
207
        name="gen_layer_hidden_bn",
W
wangyang59 已提交
208 209
        bias_attr=bias_attr,
        param_attr=ParamAttr(
W
wangyang59 已提交
210 211
            is_static=is_discriminator_training,
            initial_mean=1.0,
W
wangyang59 已提交
212 213 214 215 216
            initial_std=0.02),
        use_global_stats=False)

    return fc_layer(
        input=hidden_bn,
W
wangyang59 已提交
217 218
        name="gen_layer1",
        size=sample_dim,
W
wangyang59 已提交
219 220
        bias_attr=bias_attr,
        param_attr=param_attr,
W
wangyang59 已提交
221 222
        act=LinearActivation())
        
223
# 下面这个函数定义了基本GAN模型里面的判别器结构,可参见图4
W
wangyang59 已提交
224 225 226 227
def discriminator(sample):
    # 这里is_static=is_generator_training的意思是在训练生成器时,
    # 判别器的参数保持不变。
    param_attr = ParamAttr(is_static=is_generator_training)
W
wangyang59 已提交
228
    bias_attr = ParamAttr(
W
wangyang59 已提交
229
        is_static=is_generator_training, initial_mean=1.0, initial_std=0)
W
wangyang59 已提交
230 231

    hidden = fc_layer(
W
wangyang59 已提交
232 233
        input=sample,
        name="dis_hidden",
W
wangyang59 已提交
234 235 236 237 238 239 240
        size=hidden_dim,
        bias_attr=bias_attr,
        param_attr=param_attr,
        act=ReluActivation())

    hidden2 = fc_layer(
        input=hidden,
W
wangyang59 已提交
241
        name="dis_hidden2",
W
wangyang59 已提交
242 243 244 245 246 247 248 249
        size=hidden_dim,
        bias_attr=bias_attr,
        param_attr=param_attr,
        act=LinearActivation())

    hidden_bn = batch_norm_layer(
        hidden2,
        act=ReluActivation(),
W
wangyang59 已提交
250
        name="dis_hidden_bn",
W
wangyang59 已提交
251 252
        bias_attr=bias_attr,
        param_attr=ParamAttr(
W
wangyang59 已提交
253
            is_static=is_generator_training, initial_mean=1.0,
W
wangyang59 已提交
254 255 256 257 258
            initial_std=0.02),
        use_global_stats=False)

    return fc_layer(
        input=hidden_bn,
W
wangyang59 已提交
259 260
        name="dis_prob",
        size=2,
W
wangyang59 已提交
261 262
        bias_attr=bias_attr,
        param_attr=param_attr,
W
wangyang59 已提交
263
        act=SoftmaxActivation())
W
wangyang59 已提交
264 265
```

W
wangyang59 已提交
266
#### DCGAN模型生成器和判别器定义
W
wangyang59 已提交
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
```python
# 一个卷积/卷积转置层和一个批标准化层打包在一起
def conv_bn(input,
            channels,
            imgSize,
            num_filters,
            output_x,
            stride,
            name,
            param_attr,
            bias_attr,
            param_attr_bn,
            bn,
            trans=False,
            act=ReluActivation()):
W
wangyang59 已提交
282 283
    # 根据输入图片的大小(imgSize)和输出图片的大小(output_x),
    # 来计算所需的卷积核大小(filter_size)和边界补全大小(padding)
W
wangyang59 已提交
284 285 286 287 288 289 290 291 292 293 294 295 296 297
    tmp = imgSize - (output_x - 1) * stride
    if tmp <= 1 or tmp > 5:
        raise ValueError("conv input-output dimension does not fit")
    elif tmp <= 3:
        filter_size = tmp + 2
        padding = 1
    else:
        filter_size = tmp
        padding = 0

    if trans:
        nameApx = "_conv"
    else:
        nameApx = "_convt"
W
wangyang59 已提交
298 299
    
    # 如果conv层后面跟batchNorm层,那么conv层的activation必须是线性激发
W
wangyang59 已提交
300
    if bn:
W
wangyang59 已提交
301 302 303 304 305
        conv_act = LinearActivation()
    else:
        conv_act = act

    conv = img_conv_layer(
W
wangyang59 已提交
306 307 308 309 310
            input,
            filter_size=filter_size,
            num_filters=num_filters,
            name=name + nameApx,
            num_channels=channels,
W
wangyang59 已提交
311
            act=conv_act,
W
wangyang59 已提交
312 313 314 315 316 317 318 319 320 321 322 323
            groups=1,
            stride=stride,
            padding=padding,
            bias_attr=bias_attr,
            param_attr=param_attr,
            shared_biases=True,
            layer_attr=None,
            filter_size_y=None,
            stride_y=None,
            padding_y=None,
            trans=trans)

W
wangyang59 已提交
324
    if bn:
W
wangyang59 已提交
325 326 327 328 329 330 331 332 333 334 335
        conv_bn = batch_norm_layer(
            conv,
            act=act,
            name=name + nameApx + "_bn",
            bias_attr=bias_attr,
            param_attr=param_attr_bn,
            use_global_stats=False)
        return conv_bn
    else:
        return conv

336
# 下面这个函数定义了DCGAN模型里面的生成器的结构,可参见图5
W
wangyang59 已提交
337
def generator(noise):
W
wangyang59 已提交
338
    # 这里的参数初始化设置参考了DCGAN论文里的建议和实际调试的结果
W
wangyang59 已提交
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
    param_attr = ParamAttr(
        is_static=is_discriminator_training, initial_mean=0.0, initial_std=0.02)
    bias_attr = ParamAttr(
        is_static=is_discriminator_training, initial_mean=0.0, initial_std=0.0)
    param_attr_bn = ParamAttr(
        is_static=is_discriminator_training, initial_mean=1.0, initial_std=0.02)

    h1 = fc_layer(
        input=noise,
        name="gen_layer_h1",
        size=s8 * s8 * gf_dim * 4,
        bias_attr=bias_attr,
        param_attr=param_attr,
        act=LinearActivation())

    h1_bn = batch_norm_layer(
        h1,
        act=ReluActivation(),
        name="gen_layer_h1_bn",
        bias_attr=bias_attr,
        param_attr=param_attr_bn,
        use_global_stats=False)

    h2_bn = conv_bn(
        h1_bn,
        channels=gf_dim * 4,
        output_x=s8,
        num_filters=gf_dim * 2,
        imgSize=s4,
        stride=2,
        name="gen_layer_h2",
        param_attr=param_attr,
        bias_attr=bias_attr,
        param_attr_bn=param_attr_bn,
        bn=True,
        trans=True)

    h3_bn = conv_bn(
        h2_bn,
        channels=gf_dim * 2,
        output_x=s4,
        num_filters=gf_dim,
        imgSize=s2,
        stride=2,
        name="gen_layer_h3",
        param_attr=param_attr,
        bias_attr=bias_attr,
        param_attr_bn=param_attr_bn,
        bn=True,
        trans=True)

    return conv_bn(
        h3_bn,
        channels=gf_dim,
        output_x=s2,
        num_filters=c_dim,
        imgSize=sample_dim,
        stride=2,
        name="gen_layer_h4",
        param_attr=param_attr,
        bias_attr=bias_attr,
        param_attr_bn=param_attr_bn,
        bn=False,
        trans=True,
        act=TanhActivation())

405
# 下面这个函数定义了DCGAN模型里面的判别器结构,可参见图5
W
wangyang59 已提交
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
def discriminator(sample):
    param_attr = ParamAttr(
        is_static=is_generator_training, initial_mean=0.0, initial_std=0.02)
    bias_attr = ParamAttr(
        is_static=is_generator_training, initial_mean=0.0, initial_std=0.0)
    param_attr_bn = ParamAttr(
        is_static=is_generator_training, initial_mean=1.0, initial_std=0.02)

    h0 = conv_bn(
        sample,
        channels=c_dim,
        imgSize=sample_dim,
        num_filters=df_dim,
        output_x=s2,
        stride=2,
        name="dis_h0",
        param_attr=param_attr,
        bias_attr=bias_attr,
        param_attr_bn=param_attr_bn,
        bn=False)

    h1_bn = conv_bn(
        h0,
        channels=df_dim,
        imgSize=s2,
        num_filters=df_dim * 2,
        output_x=s4,
        stride=2,
        name="dis_h1",
        param_attr=param_attr,
        bias_attr=bias_attr,
        param_attr_bn=param_attr_bn,
        bn=True)

    h2_bn = conv_bn(
        h1_bn,
        channels=df_dim * 2,
        imgSize=s4,
        num_filters=df_dim * 4,
        output_x=s8,
        stride=2,
        name="dis_h2",
        param_attr=param_attr,
        bias_attr=bias_attr,
        param_attr_bn=param_attr_bn,
        bn=True)

    return fc_layer(
        input=h2_bn,
        name="dis_prob",
        size=2,
        bias_attr=bias_attr,
        param_attr=param_attr,
        act=SoftmaxActivation())
```

Y
Yi Wang 已提交
462
## 训练模型
463

W
wangyang59 已提交
464 465
### 用Paddle API解析模型设置并创建trainer
为了能够训练在上面的模型配置文件中定义的网络,我们首先需要用Paddle API完成如下几个步骤:
Y
Yi Wang 已提交
466

W
wangyang59 已提交
467 468 469
1. 初始化Paddle环境
2. 解析设置
3. 由设置创造GradientMachine以及由GradientMachine创造trainer
470
4. 初始化并同步不同trainer里的参数
Y
Yi Wang 已提交
471 472

这几步分别由下面几段代码实现:
Y
Yang Wang 已提交
473 474 475

```python
import py_paddle.swig_paddle as api
W
wangyang59 已提交
476
# 初始化Paddle环境
Y
Yang Wang 已提交
477 478 479 480
api.initPaddle('--use_gpu=' + use_gpu, '--dot_period=10',
               '--log_period=100', '--gpu_id=' + args.gpu_id,
               '--save_dir=' + "./%s_params/" % data_source)

481
# 解析设置:像上个小节提到的那样,gan的模型训练需要三个神经网络
W
wangyang59 已提交
482
# 这里分别解析出三种模型设置
Y
Yang Wang 已提交
483 484 485 486
gen_conf = parse_config(conf, "mode=generator_training,data=" + data_source)
dis_conf = parse_config(conf, "mode=discriminator_training,data=" + data_source)
generator_conf = parse_config(conf, "mode=generator,data=" + data_source)

W
wangyang59 已提交
487
# 由模型设置创造GradientMachine
Y
Yang Wang 已提交
488 489 490 491 492 493 494
dis_training_machine = api.GradientMachine.createFromConfigProto(
dis_conf.model_config)
gen_training_machine = api.GradientMachine.createFromConfigProto(
gen_conf.model_config)
generator_machine = api.GradientMachine.createFromConfigProto(
generator_conf.model_config)

W
wangyang59 已提交
495
# 由GradientMachine创造trainer
Y
Yang Wang 已提交
496 497
dis_trainer = api.Trainer.create(dis_conf, dis_training_machine)
gen_trainer = api.Trainer.create(gen_conf, gen_training_machine)
W
wangyang59 已提交
498

499 500 501
# trainer.startTrain()会初始化该trainer所对应的网络的参数
dis_trainer.startTrain()
gen_trainer.startTrain()
W
wangyang59 已提交
502

503 504 505
# 由于初始化的参数是随机的,所以需要同步不同网络之间的参数
copy_shared_parameters(gen_training_machine, dis_training_machine)
copy_shared_parameters(gen_training_machine, generator_machine)
Y
Yang Wang 已提交
506 507
```

508 509
### 用trainer来训练模型
根据前面模型概览里面的介绍,对抗式生成网络需要轮流训练生成器和判别器。下面的代码描述了具体的训练流程:
Y
Yang Wang 已提交
510 511

```python
512 513 514 515 516 517 518 519 520 521 522 523 524
# 定义训练100个pass
for train_pass in xrange(100):
    dis_trainer.startTrainPass()
    gen_trainer.startTrainPass()
    for i in xrange(num_iter):
        noise = get_noise(batch_size, noise_dim)
        # 准备真实数据样本
        data_batch_dis_pos = prepare_discriminator_data_batch_pos(
            batch_size, data_np)
        # 计算D关于真实数据样本的损失函数
        # 损失函数的值可以通过调用`GradientMachine`的`forward`方法来计算。
        dis_loss_pos = get_training_loss(dis_training_machine,
                                         data_batch_dis_pos)
525
        # 准备生成(伪)数据样本
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
        data_batch_dis_neg = prepare_discriminator_data_batch_neg(
            generator_machine, batch_size, noise)
        # 计算D关于生成数据样本的损失函数
        dis_loss_neg = get_training_loss(dis_training_machine,
                                         data_batch_dis_neg)

        dis_loss = (dis_loss_pos + dis_loss_neg) / 2.0

        data_batch_gen = prepare_generator_data_batch(batch_size, noise)
        # 计算G关于生成数据样本的损失函数
        gen_loss = get_training_loss(gen_training_machine, data_batch_gen)

        if i % 100 == 0:
            print "d_pos_loss is %s     d_neg_loss is %s" % (dis_loss_pos,
                                                             dis_loss_neg)
            print "d_loss is %s    g_loss is %s" % (dis_loss, gen_loss)

        # 为了能够平衡生成器和判别器之间的能力
        # 我们依据它们各自的损失函数的大小来决定训练对象
        # 即我们选择训练那个损失函数更大的网络   
        # 但同时我们也限制不去连续训练一个网络太多次
        if (not (curr_train == "dis" and curr_strike == MAX_strike)) and \
           ((curr_train == "gen" and curr_strike == MAX_strike) or dis_loss > gen_loss):
            if curr_train == "dis":
                curr_strike += 1
            else:
                curr_train = "dis"
                curr_strike = 1
            dis_trainer.trainOneDataBatch(batch_size, data_batch_dis_neg)
            dis_trainer.trainOneDataBatch(batch_size, data_batch_dis_pos)
            # 每当训练完一个网络,我们需要和其他几个网络同步互相分享的参数值。
            copy_shared_parameters(dis_training_machine,
                                   gen_training_machine)

        else:
            if curr_train == "gen":
                curr_strike += 1
            else:
                curr_train = "gen"
                curr_strike = 1
            gen_trainer.trainOneDataBatch(batch_size, data_batch_gen)
            # 每当训练完一个网络,我们需要和其他几个网络同步互相分享的参数值。
            copy_shared_parameters(gen_training_machine,
                                   dis_training_machine)
            copy_shared_parameters(gen_training_machine, generator_machine)

    dis_trainer.finishTrainPass()
    gen_trainer.finishTrainPass()
    # 在每个pass结束之后,保存生成数据
    fake_samples = get_fake_samples(generator_machine, batch_size, noise)
    save_results(fake_samples, "./%s_samples/train_pass%s.png" %
                 (data_source, train_pass), data_source)
Y
Yang Wang 已提交
578 579
```

580
### 训练脚本及结果
W
wangyang59 已提交
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
用MNIST手写数字图片训练对抗式生成网络可以用如下的命令。如果想用其他训练数据可以将参数`-d`改为uniform或者cifar。

```bash
$python gan_trainer.py -d mnist --use_gpu 1
```

训练中打印的日志信息大致如下:

```
d_pos_loss is 0.681067     d_neg_loss is 0.704936
d_loss is 0.693001151085    g_loss is 0.681496
...........d_pos_loss is 0.64475     d_neg_loss is 0.667874
d_loss is 0.656311988831    g_loss is 0.719081
...
I0105 17:15:48.346783 20517 TrainerInternal.cpp:165]  Batch=100 samples=12800 AvgCost=0.701575 CurrentCost=0.701575 Eval: generator_training_error=0.679219  CurrentEval: generator_training_error=0.679219 
.........d_pos_loss is 0.644203     d_neg_loss is 0.71601
d_loss is 0.680106401443    g_loss is 0.671118
....
I0105 17:16:37.172737 20517 TrainerInternal.cpp:165]  Batch=100 samples=12800 AvgCost=0.687359 CurrentCost=0.687359 Eval: discriminator_training_error=0.438359  CurrentEval: discriminator_training_error=0.438359 
```

602
其中`d_pos_loss`是判别器对于真实数据判别真的负对数概率,`d_neg_loss`是判别器对于伪数据判别为假的负对数概率,`d_loss`是这两者的平均值。`g_loss`是伪数据被判别器判别为真的负对数概率。对于对抗式生成网络来说,最好的训练情况是$D$和$G$的能力比较相近,也就是`d_loss``g_loss`在训练的前几个pass中数值比较接近(-log(0.5) = 0.693)。由于$G$和$D$是轮流训练,所以它们各自每过100个batch,都会打印各自的训练信息。
W
wangyang59 已提交
603

Y
Yang Wang 已提交
604
## 应用模型
Y
Yi Wang 已提交
605

606
以MNIST为例,在训练完成后,模型会保存在路径 mnist_params/pass-%05d 下,例如第100个pass的模型会保存在路径 output/pass-00099。可以用下面的命令,加载训练好的参数,来生成MNIST图片。生成的图片会保存在文件`generated_mnist_samples.png`里面。
Y
Yang Wang 已提交
607

608 609
```bash
$python gan_trainer.py -d mnist --use_gpu 1 --model_dir mnist_params/pass-00059
Y
Yang Wang 已提交
610 611
```

Y
Yang Wang 已提交
612
## 总结
Y
Yi Wang 已提交
613

614
本章中,我们首先介绍了生成模型的概念,并简单回顾了几种常见的生成模型。对抗式生成网络是近期出现的一种全新的生成模型,它是由一个生成器和一个分类器通过相互对抗的方法来训练。我们着重介绍和用PaddlePaddle实现了两种常见的GAN模型:基本GAN模型和DCGAN模型。GAN模型还有许多相关的变形和扩展,其中一种conditional GAN模型就是把之前GAN模型的随机输入变量z变成有意义的输入,那么输出的图片就可以根据输入的信号来调节。这个方法有许多实际的应用,例如模型可以根据输入的低分辨率的图片生成相应的高分辨率图片,达到图片超分辨的效果\[[9](#参考文献)\];另一个应用场景是根据输入的文字来生成相对应的图片\[[10](#参考文献)\]。读者可以在本章搭建的GAN模型框架下,尝试实现各种最新研究中的扩展模型。
Y
Yang Wang 已提交
615 616 617


## 参考文献
Y
Yi Wang 已提交
618

Y
Yang Wang 已提交
619
1. Goodfellow I, Pouget-Abadie J, Mirza M, et al. [Generative adversarial nets](https://arxiv.org/pdf/1406.2661v1.pdf)[C] Advances in Neural Information Processing Systems. 2014
620
2. van den Oord A, Kalchbrenner N and Kavukcuoglu K. [Pixel Recurrent Neural Networks](https://arxiv.org/pdf/1601.06759v3.pdf) arXiv preprint arXiv:1601.06759 (2016).
W
wangyang59 已提交
621
3. Kingma D.P. and Welling M. [Auto-encoding variational bayes](https://arxiv.org/pdf/1312.6114v10.pdf)[C] arXiv preprint arXiv:1312.6114. 2013
622 623
4. Salakhutdinov R and Hinton G. [Deep Boltzmann Machines](http://www.jmlr.org/proceedings/papers/v5/salakhutdinov09a/salakhutdinov09a.pdf)[J] AISTATS. Vol. 1. 2009
5. Radford A, Metz L, Chintala S. [Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks](https://arxiv.org/pdf/1511.06434v2.pdf)[C] arXiv preprint arXiv:1511.06434. 2015
W
wangyang59 已提交
624 625 626
6. Goodfellow I. [NIPS 2016 Tutorial: Generative Adversarial Networks] (https://arxiv.org/pdf/1701.00160v1.pdf) [C] arXiv preprint arXiv:1701.00160. 2016
7. Dumoulin V. and Visin F. [A guide to convolution arithmetic for deep learning] (https://arxiv.org/pdf/1603.07285v1.pdf). arXiv preprint arXiv:1603.07285. 2016
8. Kingma D., Ba J. [Adam: A method for stochastic optimization] (https://arxiv.org/pdf/1412.6980v8.pdf) arXiv preprint arXiv:1412.6980. 2014
627 628
9. Ledig C, Theis L, Huszár F, et al. [Photo-realistic single image super-resolution using a generative adversarial network] (https://arxiv.org/pdf/1609.04802.pdf) arXiv preprint arXiv:1609.04802. 2016
10. Reed S, Akata Z, Yan X, et al. [Generative adversarial text to image synthesis] (https://arxiv.org/pdf/1605.05396v2.pdf) arXiv preprint arXiv:1605.05396. 2016
W
wangyang59 已提交
629 630
11. Bengio Y, Courville A and Vincent P. [Representation learning: A review and new perspectives] (https://arxiv.org/pdf/1206.5538.pdf) [J] IEEE transactions on pattern analysis and machine intelligence, 35(8), pp.1798-1828. 2013
12. Andrieu C, De Freitas N, Doucet A and Jordan M.I. [An introduction to MCMC for machine learning] (http://www.cs.princeton.edu/courses/archive/spr06/cos598C/papers/AndrieuFreitasDoucetJordan2003.pdf) [J] Machine learning, 50(1-2), pp.5-43. 2003