13.5 ACGAN

基于分类优化的GAN(Auxiliary Classifier Generative Adversarial Network,ACGAN) [1] 它的主要改进如图13-21所示,是在生成图像和进行图像分类的环节引入了图像内容的标签,所谓图像内容的标签,在MNIST里面表现为标记数字0~9。正是由于加入了图像内容的分类标签,可以让图像的生成和训练更加有针对性。下面我们将介绍如何基于ACGAN实现生成MNIST数据集的功能,相关代码在GitHub的code/keras-acgan.py。

1.Generator

我们创建Generator,由于需要支持多输入,所以需要使用函数式生成模型的方法,架构如图13-22所示。需要指出的是使用Sequential创建的CNN结构以Sequential_2表示,参数如下:

图13-21 ACGAN原理图 [2]

·输入层一共有两个,其中一个是大小100的噪音。

·第二个输入层是大小为1标记MNIST图片内容的标记。

·将第二个输入层映射成大小为100的向量并压平。

·将两个大小均为100的输入层相加,大小依然为100。

·构建两个全连接层,结点数分别为1024和128×7×7即6272,激活函数均为relu。

·改变形状为(7,7,128)。

·使用(2,2)进行上采样。

·使用64个大小为(5,5)进行卷积处理。

·使用(2,2)进行上采样。

·使用1个(5,5)进行卷积处理,生成一个(28,28,1)的图像数据。

代码如下:


def build_generator(latent_size):
    cnn = Sequential()
    cnn.add(Dense(1024, input_dim=latent_size, activation='relu'))
    cnn.add(Dense(128 * 7 * 7, activation='relu'))
    cnn.add(Reshape((128, 7, 7)))
    cnn.add(UpSampling2D(size=(2, 2)))
    cnn.add(Conv2D(256, (5, 5), padding="same", kernel_initializer="glorot_normal", activation="relu"))
    cnn.add(UpSampling2D(size=(2, 2)))
    cnn.add(Conv2D(128, (5, 5), padding="same", kernel_initializer="glorot_normal", activation="relu"))
    cnn.add(Conv2D(1, (2, 2), padding="same", kernel_initializer="glorot_normal", activation="tanh"))
    latent = Input(shape=(latent_size, ))
    image_class = Input(shape=(1,), dtype='int32')
    cls = Flatten()(Embedding(10, 100, embeddings_initializer="glorot_normal")(image_class))
    h=add([latent, cls])
    fake_image = cnn(h)
    return Model(inputs=[latent, image_class], outputs=[fake_image])

图13-22 ACGAN的Generator

2.Discriminator

我们创建Discriminator,架构如图13-23所示,需要指出的是使用Sequential创建的CNN结构以Sequential_1表示,参数如下:

·输入层大小为(1,28,28)。

·32个大小为(3,3),采样步长大小为(2,2)的卷积处理。

·64个大小为(3,3),采样步长大小为(1,1)的卷积处理。

·128个大小为(3,3),采样步长大小为(2,2)的卷积处理。

·256个大小为(3,3),采样步长大小为(1,1)的卷积处理。

·压平为一维向量。

·输出有两个,其中一个连接结点数为1全连接,使用激活函数sigmoid输出大小为1的分类概率。

·另外一个连接结点数为10全连接,使用激活函数softmax输出大小为10的0~9数字分类概率。

代码如下:


def build_discriminator():
    cnn = Sequential()
    cnn.add(Conv2D(32, (3, 3), padding="same", strides=(2, 2), input_shape=(1, 28, 28) ))
    cnn.add(LeakyReLU())
    cnn.add(Dropout(0.3))
    cnn.add(Conv2D(64, (3, 3), padding="same", strides=(1, 1)))
    cnn.add(LeakyReLU())
    cnn.add(Dropout(0.3))
    cnn.add(Conv2D(128, (3, 3), padding="same", strides=(2, 2)))
    cnn.add(LeakyReLU())
    cnn.add(Dropout(0.3))
    cnn.add(Conv2D(256, (3, 3), padding="same", strides=(1, 1)))
    cnn.add(LeakyReLU())
    cnn.add(Dropout(0.3))
    cnn.add(Flatten())
    image = Input(shape=(1, 28, 28))
    features = cnn(image)
    fake = Dense(1, activation='sigmoid', name='generation')(features)
    aux = Dense(10, activation='softmax', name='auxiliary')(features)
    return Model(inputs=[image], outputs=[fake, aux])

图13-23 ACGAN的Discriminator

3.对抗模型

ACGAN的对抗模型实现非常简单,把Generator和Discriminator连接即可,不过需要将Discriminator参数设置为只允许手工更新,只有当设置trainable为Ture时才根据训练结果自动更新参数。

代码如下:


discriminator.trainable = False
fake, aux = discriminator(fake)
combined = Model(inputs=[latent, image_class], outputs=[fake, aux])
combined.compile(
    optimizer=Adam(lr=adam_lr, beta_1=adam_beta_1),
    loss=['binary_crossentropy', 'sparse_categorical_crossentropy']
)

4.训练过程

ACGAN的训练过程也分为两步:第一步,生成一个大小为(batch_size,latent_size)的在-1~1之间平均分布的噪声,使用Generator生成图像样本,对应的图像内容标记sampled_labels随机生成,然后和同样大小的真实MNIST图像样本合并,分别标记为0和1,对Discriminator进行训练。这个过程中Discriminator的trainable状态为True,训练过程会更新其参数,代码如下:


noise = np.random.uniform(-1, 1, (batch_size, latent_size))
image_batch = X_train[index * batch_size:(index + 1) * batch_size]
label_batch = y_train[index * batch_size:(index + 1) * batch_size]
sampled_labels = np.random.randint(0, 10, batch_size)
generated_images = generator.predict(
    [noise, sampled_labels.reshape((-1, 1))], verbose=0)
X = np.concatenate((image_batch, generated_images))
y = np.array([1] * batch_size + [0] * batch_size)
aux_y = np.concatenate((label_batch, sampled_labels), axis=0)
discriminator.train_on_batch(X, [y, aux_y])

第二步,生成一个大小为(2×batch_size,latent_size)的在-1~1之间平均分布的噪声,对应的图像内容标记sampled_labels随机生成,使用Generator生成图像样本,标记为1,欺骗Discriminator,这个过程针对对抗模型进行训练,代码如下:


noise = np.random.uniform(-1, 1, (2 * batch_size, latent_size))
sampled_labels = np.random.randint(0, 10, 2 * batch_size)
trick = np.ones(2 * batch_size)
combined.train_on_batch(
    [noise, sampled_labels.reshape((-1, 1))], [trick, sampled_labels])

5.训练结果

Mehdi Mirza在他的论文 [3] 中提到了训练的效果,如图13-24所示。

图13-24 ACGAN运行结果

整个训练过程在Mac本上运行非常缓慢,一个训练周期超过1小时,所以我们使用GPU服务器。由于GPU服务器安装的字符界面的操作系统,Python的基于图形界面的图像生成库无法使用,所以我们把模型的训练和图像的生成分开,在GPU服务器上完成模型的训练,在我的Mac本上加载模型生成的图像,对应的文件在GitHub的code/acgan文件夹下。在GPU Tesla M60下,完成一轮训练花费约300秒,代码如下:


name: Tesla M60
major: 5 minor: 2 memoryClockRate (GHz) 1.1775
pciBusID 0000:00:15.0
Total memory: 7.43GiB
Free memory: 7.36GiB
I tensorflow/core/common_runtime/gpu/gpu_device.cc:906] DMA: 0
I tensorflow/core/common_runtime/gpu/gpu_device.cc:916] 0:   Y

进行了1轮学习后,生成的图像如图13-25所示。

图13-25 ACGAN进行了1轮学习后生成的图像

进行了2轮学习后,生成的图像如图13-26所示。

进行了5轮学习后,生成的图像如图13-27所示,可见训练次数越多生成的图像越逼真。

图13-26 ACGAN进行了2轮学习后生成的图像

图13-27 ACGAN进行了5轮学习后生成的图像

[1] https://arxiv.org/abs/1610.09585

[2] https://arxiv.org/pdf/1411.1784.pdf

[3] https://arxiv.org/abs/1610.09585