人类的创作过程,可以在某种意义上理解为“从无到有”(Creating)的过程或者“举一反三”(Translation)的过程。那幺对于机器而言,我们可不可以使用某种模型来模拟这种过程呢?答案是肯定的。生成式对抗网络(Generative Adversarial Networks)是近年来计算机科学当中比较有趣的想法之一,本文将以创作数字图像为应用场景来对该模型进行介绍。
在生成式对抗网络模型中,两个独立的神经网络模型会以一种对抗式的方式进行训练,它们通常分别称为生成器(Generator)和判别器(Discriminator)。以生成数字图像的应用场景为例,生成器学习如何创造栩栩如生的图片,它的工作类似于“艺术家”;同时,判别器学习鉴别图像的真伪,它的工作类似于“艺术评论家”。
在训练模型的过程中, 生成器能够创造越来越接近真实场景的图片;而在另一方面,判别器也越来越善于分辨图片的真伪。 当判别器无法分辨生成图像与真实图像的区别后,这个过程就达到了动态平衡,具体表现为两个网络的损失函数值从此增彼减到不再发生明显的变化。以下是一个在MNIST数据集当中,使用生成式对抗网络模型创作数字图像的代码示例。
# 引用相关的工具库 import tensorflow as tf import matplotlib.pyplot as plt import numpy as np import time from tensorflow.keras import layers, losses from tensorflow.keras.datasets import mnist # 设置常量参数 BUFFER_SIZE = 60000 BATCH_SIZE = 256 EPOCHS = 50 NOISE_DIM = 100 NUM_EXAMPLES_PER_ROW = 10 NUM_EXAMPLES_TO_GENERATE = NUM_EXAMPLES_PER_ROW ** 2 # 读取MNIST数据集并归一化至[0, 1]区间 (x_train, y_train), (_, _) = mnist.load_data() x_train = x_train.astype('float32') / 255. # 将图像矩阵由(60000, 28, 28)转换为(60000, 28, 28, 1) x_train = x_train[..., None] # 创建训练数据集 train_dataset = tf.data.Dataset.from_tensor_slices(x_train).shuffle(BUFFER_SIZE).batch(BATCH_SIZE) # 定义图像生成器 generator = tf.keras.Sequential([ layers.Dense(7*7*64, input_shape=(NOISE_DIM,)), layers.BatchNormalization(), layers.ReLU(), layers.Reshape((7, 7, 64)), layers.Conv2DTranspose(32, (3, 3), strides=(1, 1), padding='same'), layers.BatchNormalization(), layers.ReLU(), layers.Conv2DTranspose(16, (3, 3), strides=(2, 2), padding='same'), layers.BatchNormalization(), layers.ReLU(), layers.Conv2DTranspose(1, (3, 3), strides=(2, 2), padding='same', activation='sigmoid')]) print(generator.output_shape) # 定义图像判别器 discriminator = tf.keras.Sequential([ layers.Conv2D(16, (3, 3), strides=(2, 2), padding='same', input_shape=[28, 28, 1]), layers.BatchNormalization(), layers.ReLU(), layers.Conv2D(32, (3, 3), strides=(2, 2), padding='same'), layers.BatchNormalization(), layers.ReLU(), layers.Flatten(), layers.Dense(1, activation='sigmoid')]) print(discriminator.output_shape) # 定义图像生成器与判别器损失函数 cross_entropy = losses.BinaryCrossentropy(from_logits=True) def generator_loss(fake_output): return cross_entropy(tf.ones_like(fake_output), fake_output) def discriminator_loss(real_output, fake_output): real_loss = cross_entropy(tf.ones_like(real_output), real_output) fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output) total_loss = real_loss + fake_loss return total_loss # 使用Adam算法进行优化 generator_optimizer = tf.keras.optimizers.Adam(1e-4) discriminator_optimizer = tf.keras.optimizers.Adam(1e-4) # 定义在每个批次(Batch)训练时的步骤 @tf.function def train_step(images): noise = tf.random.normal([BATCH_SIZE, NOISE_DIM]) with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape: generated_images = generator(noise, training=True) real_output = discriminator(images, training=True) fake_output = discriminator(generated_images, training=True) gen_loss = generator_loss(fake_output) disc_loss = discriminator_loss(real_output, fake_output) gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables) gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables) generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables)) discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables)) return (tf.reduce_mean(gen_loss), tf.reduce_mean(disc_loss)) # 定义生成及保存图像的函数 def generate_and_save_images(model, epoch, test_input): predictions = model(test_input, training=False) fig = plt.figure(figsize=(NUM_EXAMPLES_PER_ROW, NUM_EXAMPLES_PER_ROW)) for i in range(predictions.shape[0]): plt.subplot(NUM_EXAMPLES_PER_ROW, NUM_EXAMPLES_PER_ROW, i+1) plt.imshow(predictions[i, :, :, 0]) plt.gray() plt.axis('off') plt.savefig('image_at_epoch_{:04d}.png'.format(epoch)) # 生成固定的高斯噪声作为生成器输入,以便于比较不同训练时间点(Epoch)生成的图像 seed = tf.random.normal([NUM_EXAMPLES_TO_GENERATE, NOISE_DIM]) # 定义整个训练过程的函数 def train(dataset, epochs): for epoch in range(epochs): start = time.time() for image_batch in dataset: (gen_loss, disc_loss) = train_step(image_batch) print ('Time for epoch {} is {} sec, gen_loss:{}, disc_loss:{}'.format(epoch+1, time.time()-start, gen_loss, disc_loss)) generate_and_save_images(generator, epoch + 1, seed) # 开始进行训练 train(train_dataset, EPOCHS)
在测试时,生成器仅以随机生成高斯噪声作为输入,便可以创造出不同的数字图像。 下面的图像显示了在前50个训练周期(Epoch)中,生成器输出的图像变化的过程。这些图像开始表现为随机的图像块,而后越来越逼近于手写的数字图像。
实验结果从整体上看还是比较令人满意的,达到了模型预期训练目的。虽然部分图像并不像生活当中常见的手写数字,但这也不失为一种艺术创作的过程。而造成上述现象的原因在于使用的网络结构较为简单,层数也相对较浅,这也是因为在自己笔记本上训练效率太低才不得已而为之;如果使用更为现代的深层神经网络结构,该模型也可以应用于自然彩色图像的创作场景中,同时生成的图像也会更加逼真。另一方面,除了“无中生有”的、创造式的创作过程,生成式对抗网络模型也能模拟“举一反三”的、迁移式的创作过程,其中 StyleGAN 及其改进版本 StyleGAN2 是近年来比较优秀的图像风格转移的研究成果,其相应的代码和论文可以在对应的链接中看到。
Be First to Comment