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技术支持
defload_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 optimizerdefget_optimizer():return Adam(lr=0.0002, beta_1=0.5)defget_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
defget_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
技术支持
终于到了将生成器和判别器组合在一起的时候!
defget_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
技术支持
deftrain(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 inxrange(1, epochs+1):print'-'*15,'Epoch %d'% e,'-'*15for _ 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 ==1or e %20==0:
plot_generated_images(e, generator)if __name__ =='__main__':
train(400,128)技术支持
在本教程中,您将了解什么是生成对抗网络(GAN),而无需深入数学细节。随后,您将学习如何编写一个可以生成数字的简单 GAN!
类比
理解 GAN 最简单的方法是通过一个类比:
假设有一家商店从顾客那里收购某些种类的葡萄酒,然后再转售。
然而,也有图谋不轨的顾客为了赚钱而出售假酒。在这种情况下,店主必须能够分辨假酒与真酒。
您可以想象,最初伪造者在试图出售假酒时会犯很多错误,店主很容易识别出酒不是真品。由于这些失败,伪造者会不断尝试不同的技巧来模拟真酒,其中一些最终会成功。既然伪造者知道某些技巧能躲过店主的检查,他就可以基于这些技巧进一步改进假酒。
与此同时,店主可能也会从其他店主或葡萄酒专家那里得到一些反馈,说她手头的一些酒并非原装。这意味着店主要改进她判断葡萄酒真假的方法。伪造者的目标是酿出与真酒难以区分的酒,而店主的目标是准确判断酒是真是假。
这种你来我往的对抗就是 GAN 背后的主要思想。
生成对抗网络的组成
基于上面的例子,我们可以构建 GAN 的架构。
GAN 包含两个主要组件:生成器和判别器。示例中的店主对应判别器网络,通常是一个卷积神经网络(因为 GAN 主要用于图像任务),用于给出图像为真(真实)的概率。
伪造者对应生成网络,通常也是卷积神经网络(带有反卷积层)。该网络接收某个噪声向量并输出一张图像。在训练生成网络时,它会学习该如何改进/改变图像的哪些区域,从而让判别器更难将其生成的图像与真实图像区分开来。
生成网络不断产出更接近真实图像的结果,而判别网络则努力找出真伪图像之间的差异。最终目标是得到一个能够生成与真实图像难以区分的图像的生成网络。
用 Keras 构建一个简单的生成对抗网络
现在您已经理解了 GAN 及其主要组件,我们可以开始编写一个非常简单的实现。您将使用
Keras,如果您对这个 Python 库不熟悉,建议先阅读这篇教程再继续。本文基于这里开发的 GAN。首先,您需要通过
pip安装以下软件包kerasmatplotlibtensorflowtqdm您将使用
matplotlib来绘图,tensorflow作为 Keras 的后端库,tqdm用于在每个 epoch(迭代)中显示精美的进度条。下一步是创建一个 Python 脚本。在该脚本中,您需要先导入所有将会用到的模块和函数。每一项都会在使用时进行解释。
现在设置一些变量:
在开始构建判别器和生成器之前,您应先收集并预处理数据。我们将使用流行的 MNIST 数据集,其中包含从 0 到 9 的单个数字图像。
注意,
mnist.load_data()是 Keras 的一部分,可让您轻松将 MNIST 数据集导入工作空间。现在,您可以创建生成器和判别器网络。两个网络都将使用 Adam 优化器。对于生成器和判别器,您将创建一个具有三层隐藏层的神经网络,激活函数采用 Leaky ReLU。您还应为判别器添加dropout 层,以提升其在未见图像上的鲁棒性。
终于到了将生成器和判别器组合在一起的时候!
为了完整起见,您可以创建一个函数,每 20 个 epoch 保存一次生成的图像。由于这不是本课的核心,您无需完全理解该函数。
至此,您已经编写了网络的大部分代码。剩下的就是训练该网络,并查看您生成的图像。
在训练 400 个 epoch 之后,您可以查看生成的图像。观察第一个 epoch 后生成的图像,您会发现它还没有任何真正的结构;到第 40 个 epoch,数字开始成形;最后,第 400 个 epoch 生成的图像已经有清晰的数字,尽管仍有少数难以辨认。
在 CPU 上,这段代码每个 epoch 大约需要 2 分钟,这也是选择该代码的主要原因。您可以通过增加训练轮数,并为生成器和判别器添加更多(或不同)的层来进行实验。不过,如果您只使用 CPU,在采用更复杂、更深的架构时,运行时间也会增加。但别因此止步,尽情尝试吧!
总结
恭喜您读到了本教程的末尾!您已经以直观的方式掌握了生成对抗网络(GAN)的基础知识,并借助 Keras 库实现了您的第一个模型。如果您想进一步学习用 Python 进行深度学习,欢迎考虑参加 DataCamp 的 Deep Learning in Python 课程。