13.3 GAN

我们先以一个非常简单的例子来介绍GAN的基本实现。假设我们的真实样本就是满足正态分布大小为200的向量,我们希望通过GAN,让我们的生成器可以生成与真实样本分布类似的样本。我们定义真实样本的生成函数,可以根据batch_size生成指定个数的真实样本,样本分布满足正态分布,均值为mu,标准差为sigma。相关代码在GitHub的code/keras-gan.py。

代码如下:


def x_sample(size=200,batch_size=32):
    x=[]
    for _ in range(batch_size):
        x.append(np.random.normal(mu, sigma, size))
    return np.array(x)

定义噪声生成函数,生成满足-1~1均匀分布的噪声:


def z_sample(size=200,batch_size=32):
    z=[]
    for _ in range(batch_size):
        z.append(np.random.uniform(-1, 1, size))
    return np.array(z)

1.Generator

我们创建Generator,架构如图13-7所示。主要参数包括:

·输入层大小为200。

·两个全连接层,结点数分别为256和200,激活函数均为relu。

·输出层大小为200。

图13-7 GAN的Generator

代码如下:


def generator_model():
    model = Sequential()
    model.add(Dense(input_dim=200, units=256))
    model.add(Activation('relu'))
    model.add(Dense(200))
    model.add(Activation('sigmoid'))
    plot_model(model, show_shapes=True, to_file='gan/keras-gan-generator_model.png')
    return model

2.Discriminator

我们创建Discriminator,架构如图13-8所示。主要参数包括:

·输入层大小为200。

·结点数分别为256和1的全连接。

·使用激活函数sigmoid输出一维的分类概率。

图13-8 GAN的Discriminator

代码如下:


def discriminator_model():
    model = Sequential()
    model.add(Reshape((200,), input_shape=(200,)))
    model.add(Dense(units=256))
    model.add(Activation('relu'))
    model.add(Dense(1))
    model.add(Activation('sigmoid'))
    plot_model(model, show_shapes=True, to_file='gan/keras-gan-discriminator_model.png')
    return model

3.对抗模型

GAN的对抗模型把Generator和Discriminator连接即可,不过需要将Discriminator参数设置为只允许手工更新,只有当设置trainable为True时才根据训练结果自动更新参数。完整的对抗模型结构如图13-9所示。由于Generator和Discriminator相连,且Discriminator参数不可更新,所以在使用对抗模型训练时,Generator被迫更新自己的参数适应Discriminator,以使整体的损失函数下降,正是这一过程让Generator可以从Discriminator得到反馈,生成更逼真的图像,代码如下:

图13-9 GAN的对抗模型


def generator_containing_discriminator(g, d):
    model = Sequential()
    model.add(g)
    d.trainable = False
    model.add(d)
    return model

4.训练过程

GAN的训练过程分为两步:第一步,生成一个大小为(batch_size,200)的在-1~1之间平均分布的噪声,使用Generator生成样本,然后和同样大小的真实样本合并,分别标记为0和1,对Discriminator进行训练。这个过程中Discriminator的trainable状态为True,训练过程会更新其参数。

代码如下:


noise=z_sample(batch_size=batch_size)
image_batch=x_sample(batch_size=batch_size)
generated_images = g.predict(noise, verbose=0)
x= np.concatenate((image_batch, generated_images))
y=[1]*batch_size+[0]*batch_size
d_loss = d.train_on_batch(x, y)
print("d_loss : %f" % (d_loss))

第二步,生成一个大小为(batch_size,200)的在-1~1之间平均分布的噪声,使用Generator生成图像样本,标记为1,欺骗Discriminator,这个过程针对对抗模型进行训练。这个过程中Discriminator的trainable状态为False,训练过程不会更新其参数。训练完成后将重新将Discriminator的trainable状态设置为True,代码如下:


noise = z_sample(batch_size=batch_size)
d.trainable = False
g_loss = d_on_g.train_on_batch(noise, [1]*batch_size)
d.trainable = True
print("g_loss : %f" % (g_loss))

5.训练结果

我们的真实样本分布如图13-10所示,是典型的正态分布;我们的噪声分布如图13-11所示,满足-1~1的均匀分布。

图13-10 GAN示例中真实样本的分布

图13-11 GAN示例中噪声的样本分布

经过500轮训练后,如图13-12所示,生成的样本分布基本接近我们的真实样本了。

Ian Goodfellow在他的论文 [1] 中也形象了描绘了这一过程,如图13-13所示。

图13-12 GAN示例中经过500轮训练后生成的样本分布

图13-13 Ian Goodfellow论文中提到的GAN样本生成过程

[1] https://arxiv.org/pdf/1406.2661.pdf