跳至主要内容

走近生成对抗网络(GAN)

在不涉及数学细节的情况下,了解什么是生成对抗网络,并编写一个能够生成数字的简单 GAN!
· 9分钟 阅读

使用 AI 探索

在本教程中,您将了解什么是生成对抗网络(GAN),而无需深入数学细节。随后,您将学习如何编写一个可以生成数字的简单 GAN!

类比

理解 GAN 最简单的方法是通过一个类比:

假设有一家商店从顾客那里收购某些种类的葡萄酒,然后再转售。

analogy

然而,也有图谋不轨的顾客为了赚钱而出售假酒。在这种情况下,店主必须能够分辨假酒与真酒。

analogy

您可以想象,最初伪造者在试图出售假酒时会犯很多错误,店主很容易识别出酒不是真品。由于这些失败,伪造者会不断尝试不同的技巧来模拟真酒,其中一些最终会成功。既然伪造者知道某些技巧能躲过店主的检查,他就可以基于这些技巧进一步改进假酒。

与此同时,店主可能也会从其他店主或葡萄酒专家那里得到一些反馈,说她手头的一些酒并非原装。这意味着店主要改进她判断葡萄酒真假的方法。伪造者的目标是酿出与真酒难以区分的酒,而店主的目标是准确判断酒是真是假。

这种你来我往的对抗就是 GAN 背后的主要思想。

生成对抗网络的组成

基于上面的例子,我们可以构建 GAN 的架构。

gan

GAN 包含两个主要组件:生成器和判别器。示例中的店主对应判别器网络,通常是一个卷积神经网络(因为 GAN 主要用于图像任务),用于给出图像为真(真实)的概率。

伪造者对应生成网络,通常也是卷积神经网络(带有反卷积层)。该网络接收某个噪声向量并输出一张图像。在训练生成网络时,它会学习该如何改进/改变图像的哪些区域,从而让判别器更难将其生成的图像与真实图像区分开来。

生成网络不断产出更接近真实图像的结果,而判别网络则努力找出真伪图像之间的差异。最终目标是得到一个能够生成与真实图像难以区分的图像的生成网络。

用 Keras 构建一个简单的生成对抗网络

现在您已经理解了 GAN 及其主要组件,我们可以开始编写一个非常简单的实现。您将使用 Keras,如果您对这个 Python 库不熟悉,建议先阅读这篇教程再继续。本文基于这里开发的 GAN。

首先,您需要通过 pip 安装以下软件包

  • keras
  • matplotlib
  • tensorflow
  • tqdm

您将使用 matplotlib 来绘图,tensorflow 作为 Keras 的后端库,tqdm 用于在每个 epoch(迭代)中显示精美的进度条。

下一步是创建一个 Python 脚本。在该脚本中,您需要先导入所有将会用到的模块和函数。每一项都会在使用时进行解释。

import os
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm


from keras.layers import Input
from keras.models import Model, Sequential
from keras.layers.core import Dense, Dropout
from keras.layers.advanced_activations import LeakyReLU
from keras.datasets import mnist
from keras.optimizers import Adam
from keras import initializers

现在设置一些变量:

# Let Keras know that we are using tensorflow as our backend engine
os.environ["KERAS_BACKEND"] = "tensorflow"

# To make sure that we can reproduce the experiment and get the same results
np.random.seed(10)

# The dimension of our random noise vector.
random_dim = 100

在开始构建判别器和生成器之前,您应先收集并预处理数据。我们将使用流行的 MNIST 数据集,其中包含从 0 到 9 的单个数字图像。

mnist dataset

def load_minst_data():
    # load the data
    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    # normalize our inputs to be in the range[-1, 1]
    x_train = (x_train.astype(np.float32) - 127.5)/127.5
    # convert x_train with a shape of (60000, 28, 28) to (60000, 784) so we have
    # 784 columns per row
    x_train = x_train.reshape(60000, 784)
    return (x_train, y_train, x_test, y_test)

注意,mnist.load_data() 是 Keras 的一部分,可让您轻松将 MNIST 数据集导入工作空间。

现在,您可以创建生成器和判别器网络。两个网络都将使用 Adam 优化器。对于生成器和判别器,您将创建一个具有三层隐藏层的神经网络,激活函数采用 Leaky ReLU。您还应为判别器添加dropout 层,以提升其在未见图像上的鲁棒性。

# You will use the Adam optimizer
def get_optimizer():
    return Adam(lr=0.0002, beta_1=0.5)

def get_generator(optimizer):
    generator = Sequential()
    generator.add(Dense(256, input_dim=random_dim, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
    generator.add(LeakyReLU(0.2))

    generator.add(Dense(512))
    generator.add(LeakyReLU(0.2))

    generator.add(Dense(1024))
    generator.add(LeakyReLU(0.2))

    generator.add(Dense(784, activation='tanh'))
    generator.compile(loss='binary_crossentropy', optimizer=optimizer)
    return generator

def get_discriminator(optimizer):
    discriminator = Sequential()
    discriminator.add(Dense(1024, input_dim=784, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
    discriminator.add(LeakyReLU(0.2))
    discriminator.add(Dropout(0.3))

    discriminator.add(Dense(512))
    discriminator.add(LeakyReLU(0.2))
    discriminator.add(Dropout(0.3))

    discriminator.add(Dense(256))
    discriminator.add(LeakyReLU(0.2))
    discriminator.add(Dropout(0.3))

    discriminator.add(Dense(1, activation='sigmoid'))
    discriminator.compile(loss='binary_crossentropy', optimizer=optimizer)
    return discriminator

终于到了将生成器和判别器组合在一起的时候!

def get_gan_network(discriminator, random_dim, generator, optimizer):
    # We initially set trainable to False since we only want to train either the
    # generator or discriminator at a time
    discriminator.trainable = False
    # gan input (noise) will be 100-dimensional vectors
    gan_input = Input(shape=(random_dim,))
    # the output of the generator (an image)
    x = generator(gan_input)
    # get the output of the discriminator (probability if the image is real or not)
    gan_output = discriminator(x)
    gan = Model(inputs=gan_input, outputs=gan_output)
    gan.compile(loss='binary_crossentropy', optimizer=optimizer)
    return gan

为了完整起见,您可以创建一个函数,每 20 个 epoch 保存一次生成的图像。由于这不是本课的核心,您无需完全理解该函数。

# Create a wall of generated MNIST images
def plot_generated_images(epoch, generator, examples=100, dim=(10, 10), figsize=(10, 10)):
    noise = np.random.normal(0, 1, size=[examples, random_dim])
    generated_images = generator.predict(noise)
    generated_images = generated_images.reshape(examples, 28, 28)

    plt.figure(figsize=figsize)
    for i in range(generated_images.shape[0]):
        plt.subplot(dim[0], dim[1], i+1)
        plt.imshow(generated_images[i], interpolation='nearest', cmap='gray_r')
        plt.axis('off')
    plt.tight_layout()
    plt.savefig('gan_generated_image_epoch_%d.png' % epoch)

至此,您已经编写了网络的大部分代码。剩下的就是训练该网络,并查看您生成的图像。

def train(epochs=1, batch_size=128):
    # Get the training and testing data
    x_train, y_train, x_test, y_test = load_minst_data()
    # Split the training data into batches of size 128
    batch_count = x_train.shape[0] / batch_size

    # Build our GAN netowrk
    adam = get_optimizer()
    generator = get_generator(adam)
    discriminator = get_discriminator(adam)
    gan = get_gan_network(discriminator, random_dim, generator, adam)

    for e in xrange(1, epochs+1):
        print '-'*15, 'Epoch %d' % e, '-'*15
        for _ in tqdm(xrange(batch_count)):
            # Get a random set of input noise and images
            noise = np.random.normal(0, 1, size=[batch_size, random_dim])
            image_batch = x_train[np.random.randint(0, x_train.shape[0], size=batch_size)]

            # Generate fake MNIST images
            generated_images = generator.predict(noise)
            X = np.concatenate([image_batch, generated_images])

            # Labels for generated and real data
            y_dis = np.zeros(2*batch_size)
            # One-sided label smoothing
            y_dis[:batch_size] = 0.9

            # Train discriminator
            discriminator.trainable = True
            discriminator.train_on_batch(X, y_dis)

            # Train generator
            noise = np.random.normal(0, 1, size=[batch_size, random_dim])
            y_gen = np.ones(batch_size)
            discriminator.trainable = False
            gan.train_on_batch(noise, y_gen)

        if e == 1 or e % 20 == 0:
            plot_generated_images(e, generator)

if __name__ == '__main__':
    train(400, 128)

在训练 400 个 epoch 之后,您可以查看生成的图像。观察第一个 epoch 后生成的图像,您会发现它还没有任何真正的结构;到第 40 个 epoch,数字开始成形;最后,第 400 个 epoch 生成的图像已经有清晰的数字,尽管仍有少数难以辨认。

1 个 epoch 后的结果40 个 epoch 后的结果400 个 epoch 后的结果
!images1images2images3

在 CPU 上,这段代码每个 epoch 大约需要 2 分钟,这也是选择该代码的主要原因。您可以通过增加训练轮数,并为生成器和判别器添加更多(或不同)的层来进行实验。不过,如果您只使用 CPU,在采用更复杂、更深的架构时,运行时间也会增加。但别因此止步,尽情尝试吧!

总结

恭喜您读到了本教程的末尾!您已经以直观的方式掌握了生成对抗网络(GAN)的基础知识,并借助 Keras 库实现了您的第一个模型。如果您想进一步学习用 Python 进行深度学习,欢迎考虑参加 DataCamp 的 Deep Learning in Python 课程。

主题