第3章 你的第一个GAN模型:生成手写数字
一、GAN的基础:对抗训练
生成器和鉴别器由可微函数表示如神经网络,它们都有自己的代价函数。这两个网络是利用鉴别器的损失进行反向传播训练。
鉴别器努力使真实样本输入和伪样本输入带来的损失最小化,而生成器努力使它生成的伪样本造成的鉴别器损失最大化。
生成器的目标是生成符合训练数据集数据分布的样本。训练数据集决定了生成器要学习模拟的样本类型,例如:目标是生成猫的逼真图像,我们就会给GAN一组猫的图像。
生成器和鉴别器只能调整自己的参数而不能相互调整对方的参数,所以GAN的训练过程可以用一个博弈过程来更好地描述,而非优化。
二、生成器和鉴别器
现在通过引入更多的表示概括所学的内容。生成器(G)接收随机噪声向量z并生成一个伪样本。数学上来说,G(z)=。鉴别器(D)的输入要么是真实样本x,要么是伪样本;对于每个输入,它输出一个介于0和1之间的值,表示输入是真实样本的概率。
生成器网络G将随机向量z转换为伪样本:G(z) = 。
鉴别器网络D对输入样本是否是真实进行分类并输出。
- 对于真实样本x,鉴别器力求输出尽可能接近1的值;
- 对于伪样本 ,鉴别器力求输出尽可能接近0的值。
相反:生成器希望D()尽可能接近1,这表明鉴别器被欺骗,将伪样本分类为真实样本。
1、对抗的目标 -鉴真与造假
鉴真:鉴别器的目标是尽可能精确。对于真是样本x,D(x)力求尽可能接近1(正的标签);对于伪样本,D()力求尽可能接近0(负的标签)。
造假:生成器的目标正好相反,它试图通过生成与训练数据集中的真实数据别无二致的伪样本来欺骗鉴别器。从数学角度讲,即生成器试图生成假样本,使得D()尽可能接近1。
2、混淆矩阵
鉴别器的分类可以用混淆矩阵来表示,混淆矩阵是二元分类中所有可能结果的表格表示。鉴别器的分类结果:真阳性(True Positive)、假阴性(False Negative)、真阴性(True Negative)、假阴性(False Negative)。
三、GAN训练算法
GAN训练算法:对每次训练迭代,执行:
(1)训练鉴别器。
a.取随机的小批量的真实样本x。
b.去随机的小批量的随机噪声z,并生成一小批量伪样本:G(z)=。
c.计算D(x)和D()的分类损失,并反向传播总误差以更新D模型参数来最小化分类损失。
(2)训练生成器。
a.取随机的小批量的随机噪声z生成一小批量伪样本:G(z)=。
b.用鉴别器网络对进行分类。
c.计算D()的分类损失,并反向传播总误差以更新生成器的模型参数来最大化分类损失。
注意:在步骤1中训练鉴别器时,生成器的参数保持不变;同样,在步骤2中,在训练生成器时保持鉴别器的参数不变。
之所以只允许更新被训练网络的权重和偏置,是因为要将所有更改隔离到仅受到该网络控制的参数中。这可以确保每个网络都能获得如何进行更新的相关信号,而不受其他网络更新的干扰。
四、、教程:生成手写数字
1、导入依赖包输入超参数
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from keras.datasets import mnist
from keras.layers import Dense, Flatten, Reshape
from keras.layers.advanced_activations import LeakyReLU
from keras.models import Sequential
from keras.optimizers import Adam
img_rows = 28
img_cols = 28
channels = 1
# Input image dimensions
img_shape = (img_rows, img_cols, channels)
# Size of the noise vector, used as input to the Generator
z_dim = 100
2、构造生成器
生成器是一个只有一格隐藏层的神经网络。
生成器以z为输入,生成28*28*1的图像。在隐藏层中使用LeakyReLU激活函数。与将任何负输入映射到0的常规ReLU函数不同,LeakyRelu函数允许存在一个小的正梯度,这样可以防止梯度在训练过程中消失,从而产生更好的训练效果。
在输出层使用tanh激活函数,它将输出值缩放到范围[-1,1]。之所以使用tanh(与sigmoid不同,sigmoid会输出更为典型的0到1范围内的值),是因为它有助于生成更清晰的图像。
使用贯序模式,无分支。
def build_generator(img_shape, z_dim):
model = Sequential()
# Fully connected layer
model.add(Dense(128, input_dim=z_dim))
# Leaky ReLU activation
model.add(LeakyReLU(alpha=0.01))
# Output layer with tanh activation
model.add(Dense(28 * 28 * 1, activation='tanh'))
# Reshape the Generator output to image dimensions
model.add(Reshape(img_shape))
return model
3、构造鉴别器
鉴别器接受28*28*1的图像,并输出表示输入是否被视为真而不是假的概率。(为真概率)
鉴别器由一个两层神经网络表示,器隐藏层由128个隐藏单元及激活函数为LeakyReLU。
鉴别器的输出层应用了sigmoid激活函数。这确保了输出值将介于0和1之间,可以将其解释为生成器将输入认定为真的概率。
def build_discriminator(img_shape):
model = Sequential()
# Flatten the input image
model.add(Flatten(input_shape=img_shape))
# Fully connected layer
model.add(Dense(128))
# Leaky ReLU activation
model.add(LeakyReLU(alpha=0.01))
# Output layer with sigmoid activation
model.add(Dense(1, activation='sigmoid'))
return model
4、搭建整个模型
def build_gan(generator, discriminator):
model = Sequential()
# Combined Generator -> Discriminator model
model.add(generator)
model.add(discriminator)
return model
组合模型(其中鉴别器设置为不可训练)仅用于训练生成器。鉴别器将用单独编译的模型训练。
二元交叉熵用于度量二分类预测计算的概率和实际概率之间的差异;交叉熵损失越大,预测离真值就越远。
# Build and compile the Discriminator
discriminator = build_discriminator(img_shape)
discriminator.compile(loss='binary_crossentropy',
optimizer=Adam(),
metrics=['accuracy'])
# Build the Generator
generator = build_generator(img_shape, z_dim)
# Keep Discriminator’s parameters constant for Generator training
discriminator.trainable = False
# Build and compile GAN model with fixed Discriminator to train the Generator
gan = build_gan(generator, discriminator)
gan.compile(loss='binary_crossentropy', optimizer=Adam())
优化每个网络使用的是Adam优化算法。
5、训练
首先,取随机小批量 的MNIST图像为真实样本,从随机噪声向量z中生成小批量伪样本,然后在保持生成器参数不变的情况下,利用这些伪样本训练鉴别器网络。
losses = []
accuracies = []
iteration_checkpoints = []
def train(iterations, batch_size, sample_interval):
# Load the MNIST dataset
(X_train, _), (_, _) = mnist.load_data()
# Rescale [0, 255] grayscale pixel values to [-1, 1]
X_train = X_train / 127.5 - 1.0
X_train = np.expand_dims(X_train, axis=3)
# Labels for real images: all ones
real = np.ones((batch_size, 1))
# Labels for fake images: all zeros
fake = np.zeros((batch_size, 1))
for iteration in range(iterations):
# -------------------------
# Train the Discriminator
# -------------------------
# Get a random batch of real images
idx = np.random.randint(0, X_train.shape[0], batch_size)
imgs = X_train[idx]
# Generate a batch of fake images
z = np.random.normal(0, 1, (batch_size, 100))
gen_imgs = generator.predict(z)
# Train Discriminator
d_loss_real = discriminator.train_on_batch(imgs, real)
d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
d_loss, accuracy = 0.5 * np.add(d_loss_real, d_loss_fake)
# ---------------------
# Train the Generator
# ---------------------
# Generate a batch of fake images
z = np.random.normal(0, 1, (batch_size, 100))
gen_imgs = generator.predict(z)
# Train Generator
g_loss = gan.train_on_batch(z, real)
if (iteration + 1) % sample_interval == 0:
# Save losses and accuracies so they can be plotted after training
losses.append((d_loss, g_loss))
accuracies.append(100.0 * accuracy)
iteration_checkpoints.append(iteration + 1)
# Output training progress
print("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" %
(iteration + 1, d_loss, 100.0 * accuracy, g_loss))
# Output a sample of generated image
sample_images(generator)
训练K次判别器、再训练一次生成器。交替轮循进行。当前代码中k=1。
6、输出样本图像
def sample_images(generator, image_grid_rows=4, image_grid_columns=4):
# Sample random noise
z = np.random.normal(0, 1, (image_grid_rows * image_grid_columns, z_dim))
# Generate images from random noise
gen_imgs = generator.predict(z)
# Rescale image pixel values to [0, 1]
gen_imgs = 0.5 * gen_imgs + 0.5
# Set image grid
fig, axs = plt.subplots(image_grid_rows,
image_grid_columns,
figsize=(4, 4),
sharey=True,
sharex=True)
cnt = 0
for i in range(image_grid_rows):
for j in range(image_grid_columns):
# Output a grid of images
axs[i, j].imshow(gen_imgs[cnt, :, :, 0], cmap='gray')
axs[i, j].axis('off')
cnt += 1
7、运行模型
设置训练超参数——迭代次数和批量大小,训练模型。
# Set hyperparameters
iterations = 20000
batch_size = 128
sample_interval = 1000
# Train the GAN for the specified number of iterations
train(iterations, batch_size, sample_interval)
1000 [D loss: 0.000703, acc.: 100.00%] [G loss: 6.611647]
2000 [D loss: 0.000129, acc.: 100.00%] [G loss: 8.342966]
3000 [D loss: 0.048652, acc.: 99.61%] [G loss: 5.102392]
4000 [D loss: 0.290520, acc.: 87.11%] [G loss: 5.049376]
5000 [D loss: 0.272809, acc.: 92.19%] [G loss: 5.135170]
6000 [D loss: 0.241676, acc.: 91.41%] [G loss: 4.293948]
7000 [D loss: 0.578245, acc.: 73.83%] [G loss: 2.877602]
8000 [D loss: 0.253521, acc.: 89.45%] [G loss: 4.035530]
9000 [D loss: 0.260670, acc.: 89.06%] [G loss: 3.997754]
10000 [D loss: 0.366024, acc.: 81.64%] [G loss: 3.163330]
11000 [D loss: 0.224645, acc.: 91.02%] [G loss: 4.301305]
12000 [D loss: 0.609185, acc.: 76.56%] [G loss: 2.731848]
13000 [D loss: 0.374400, acc.: 83.98%] [G loss: 2.878426]
14000 [D loss: 0.447661, acc.: 82.42%] [G loss: 2.822231]
15000 [D loss: 0.130454, acc.: 95.31%] [G loss: 4.019130]
16000 [D loss: 0.395809, acc.: 81.25%] [G loss: 3.178937]
17000 [D loss: 0.318862, acc.: 86.33%] [G loss: 3.722922]
18000 [D loss: 0.326413, acc.: 86.72%] [G loss: 2.938485]
19000 [D loss: 0.475047, acc.: 75.78%] [G loss: 2.513085]
20000 [D loss: 0.450170, acc.: 78.91%] [G loss: 2.463450]
8、Loss与Acc
losses = np.array(losses)
# Plot training losses for Discriminator and Generator
plt.figure(figsize=(15, 5))
plt.plot(iteration_checkpoints, losses.T[0], label="Discriminator loss")
plt.plot(iteration_checkpoints, losses.T[1], label="Generator loss")
plt.xticks(iteration_checkpoints, rotation=90)
plt.title("Training Loss")
plt.xlabel("Iteration")
plt.ylabel("Loss")
plt.legend()
accuracies = np.array(accuracies)
# Plot Discriminator accuracy
plt.figure(figsize=(15, 5))
plt.plot(iteration_checkpoints, accuracies, label="Discriminator accuracy")
plt.xticks(iteration_checkpoints, rotation=90)
plt.yticks(range(0, 100, 5))
plt.title("Discriminator Accuracy")
plt.xlabel("Iteration")
plt.ylabel("Accuracy (%)")
plt.legend()
五、结论
考虑到生成器和鉴别器只用了简单的双层网络结构,后续将使用更加复杂和强大的神经网络结构——卷积神经网络,以提高生成图像的质量。