生成对抗网络(GAN)是一种用于训练生成模型(例如用于生成图像的深度卷积神经网络)的架构。
开发用于生成图像的 GAN 需要一个判别器卷积神经网络模型,用于分类给定图像是真实的还是生成的,以及一个生成器模型,该模型使用反卷积层将输入转换为像素值的完整二维图像。
理解 GAN 的工作原理以及如何在 GAN 架构中训练深度卷积神经网络模型以进行图像生成可能具有挑战性。对于初学者来说,一个好的起点是在计算机视觉领域使用的标准图像数据集(例如 CIFAR 小物体照片数据集)上练习开发和使用 GAN。使用小而易懂的数据集意味着可以更快地开发和训练较小的模型,从而可以将重点放在模型架构和图像生成过程本身。
在本教程中,您将学习如何使用深度卷积网络开发生成对抗网络,以生成小物体照片。
完成本教程后,您将了解:
- 如何定义和训练独立的判别器模型以学习真实图像和虚假图像之间的差异。
- 如何定义独立的生成器模型并训练复合生成器和判别器模型。
- 如何评估 GAN 的性能并使用最终的独立生成器模型生成新图像。
通过我的新书《Python 生成对抗网络》**启动您的项目**,包括分步教程和所有示例的Python 源代码文件。
让我们开始吧。
- 2019 年 7 月:更新了使用最终模型时的函数名称和注释(感谢 Antonio)。

如何从头开始开发用于 CIFAR-10 小物体照片的生成对抗网络
照片由 hiGorgeous 拍摄,保留部分权利。
教程概述
本教程分为七个部分,它们是:
- CIFAR-10 小物体照片数据集
- 如何定义和训练判别器模型
- 如何定义和使用生成器模型
- 如何训练生成器模型
- 如何评估 GAN 模型性能
- CIFAR-10 GAN 的完整示例
- 如何使用最终的生成器模型生成图像
CIFAR-10 小物体照片数据集
CIFAR 是加拿大高级研究所(Canadian Institute For Advanced Research)的缩写,CIFAR-10 数据集与 CIFAR-100 数据集(在下一节中介绍)由 CIFAR 研究所的研究人员共同开发。
该数据集包含 60,000 张 32×32 像素的彩色物体照片,分为 10 个类别,例如青蛙、鸟类、猫、船只、飞机等。
这些图像非常小,远小于典型的照片,该数据集旨在用于计算机视觉研究。
Keras 通过 cifar10.load_dataset() 函数提供对 CIFAR10 数据集的访问。它返回两个元组,一个包含标准训练数据集的输入和输出元素,另一个包含标准测试数据集的输入和输出元素。
下面的示例加载数据集并总结了加载数据集的形状。
注意:第一次加载数据集时,Keras 将自动下载图像的压缩版本并将其保存到您的主目录下的 _~/.keras/datasets/_ 中。下载速度很快,因为压缩后的数据集只有大约 163 兆字节。
1 2 3 4 5 6 7 |
# 加载 cifar10 数据集的示例 from keras.datasets.cifar10 import load_data # 将图像加载到内存中 (trainX, trainy), (testX, testy) = load_data() # 总结数据集的形状 print('训练集', trainX.形状, trainy.形状) print('测试集', testX.形状, testy.形状) |
运行示例会加载数据集并打印图像训练和测试分割的输入和输出组件的形状。
我们可以看到训练集中有 50K 个示例,测试集中有 10K 个示例,并且每个图像都是 32 乘 32 像素的正方形。
1 2 |
训练集 (50000, 32, 32, 3) (50000, 1) 测试集 (10000, 32, 32, 3) (10000, 1) |
图像是彩色的,物体位于框架的中心。
我们可以使用 matplotlib 库的 imshow() 函数绘制训练数据集中的一些图像。
1 2 |
# 绘制原始像素数据 pyplot.imshow(trainX[i]) |
下面的示例绘制了训练数据集中的前 49 张图像,呈 7 乘 7 的正方形。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 加载和绘制 cifar10 数据集的示例 from keras.datasets.cifar10 import load_data from matplotlib import pyplot # 将图像加载到内存中 (trainX, trainy), (testX, testy) = load_data() # 绘制训练数据集中的图像 for i in range(49): # 定义子图 pyplot.subplot(7, 7, 1 + i) # 关闭轴线 pyplot.axis('off') # 绘制原始像素数据 pyplot.imshow(trainX[i]) pyplot.show() |
运行示例会创建一个图形,其中包含来自 CIFAR10 训练数据集的 49 张图像的图,排列成 7×7 的正方形。
在图中,您可以看到飞机、卡车、马、汽车、青蛙等小物体照片。

CIFAR10 数据集中前 49 张小物体照片的图。
我们将使用训练数据集中的图像作为训练生成对抗网络的基础。
具体来说,生成器模型将学习如何使用判别器生成新的、可信的物体照片,判别器将尝试区分来自 CIFAR10 训练数据集的真实图像和生成器模型输出的新图像。
这是一个非平凡的问题,需要适度的生成器和判别器模型,这些模型可能最有效地在 GPU 硬件上训练。
有关使用廉价 Amazon EC2 实例训练深度学习模型的帮助,请参阅帖子
想从零开始开发GAN吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
如何定义和训练判别器模型
第一步是定义判别器模型。
该模型必须将数据集中的样本图像作为输入,并输出分类预测,以判断样本是真实的还是伪造的。这是一个二元分类问题。
- **输入**:具有三个颜色通道和 32×32 像素大小的图像。
- **输出**:二元分类,样本为真实的(或伪造的)可能性。
判别器模型有一个正常的卷积层,后面是三个卷积层,使用 2×2 的步长对输入图像进行下采样。该模型没有 池化层,输出层中有一个带有 sigmoid 激活函数的节点,用于预测输入样本是真实的还是伪造的。该模型经过训练,旨在最小化 二元交叉熵损失函数,适用于二元分类。
我们将在定义判别器模型时采用一些最佳实践,例如使用 LeakyReLU 而不是 ReLU、使用 Dropout,以及使用 Adam 版本的随机梯度下降,学习率为 0.0002,动量为 0.5。
下面的 _define_discriminator()_ 函数定义了判别器模型并对输入图像的大小进行了参数化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 定义独立的判别器模型 def define_discriminator(in_shape=(32,32,3)): model = Sequential() # 正常 model.add(Conv2D(64, (3,3), padding='same', input_shape=in_shape)) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(128, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(128, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(256, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 分类器 model.add(Flatten()) model.add(Dropout(0.4)) model.add(Dense(1, activation='sigmoid')) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy']) return model |
我们可以使用此函数定义判别器模型并对其进行总结。
完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# 定义判别器模型的示例 from keras.models import Sequential from keras.optimizers import Adam from keras.layers import Dense 从 keras.layers 导入 Conv2D from keras.layers import Flatten 从 keras.layers 导入 Dropout from keras.layers import LeakyReLU from keras.utils.vis_utils import plot_model # 定义独立的判别器模型 def define_discriminator(in_shape=(32,32,3)): model = Sequential() # 正常 model.add(Conv2D(64, (3,3), padding='same', input_shape=in_shape)) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(128, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(128, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(256, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 分类器 model.add(Flatten()) model.add(Dropout(0.4)) model.add(Dense(1, activation='sigmoid')) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy']) return model # 定义模型 model = define_discriminator() # 总结模型 model.summary() # 绘制模型 plot_model(model, to_file='discriminator_plot.png', show_shapes=True, show_layer_names=True) |
运行示例首先总结了模型架构,显示了每个层的输入和输出。
我们可以看到,激进的 2×2 步长用于对输入图像进行下采样,首先从 32×32 到 16×16,然后到 8×8,在模型进行输出预测之前更多。
这种模式是刻意设计的,因为我们不使用池化层,而是使用大步长来实现类似的下采样效果。我们将在下一节的生成器模型中看到类似的模式,但方向相反。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
_________________________________________________________________ 层(类型) 输出形状 参数数量 ================================================================= conv2d_1 (Conv2D) (None, 32, 32, 64) 1792 _________________________________________________________________ leaky_re_lu_1 (LeakyReLU) (None, 32, 32, 64) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 16, 16, 128) 73856 _________________________________________________________________ leaky_re_lu_2 (LeakyReLU) (None, 16, 16, 128) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 8, 8, 128) 147584 _________________________________________________________________ leaky_re_lu_3 (LeakyReLU) (None, 8, 8, 128) 0 _________________________________________________________________ conv2d_4 (Conv2D) (None, 4, 4, 256) 295168 _________________________________________________________________ leaky_re_lu_4 (LeakyReLU) (None, 4, 4, 256) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 4096) 0 _________________________________________________________________ dropout_1 (Dropout) (None, 4096) 0 _________________________________________________________________ dense_1 (Dense) (None, 1) 4097 ================================================================= 总参数:522,497 可训练参数:522,497 不可训练参数: 0 _________________________________________________________________ |
还创建了模型的图,我们可以看到模型期望矢量输入并预测单个输出。
**注意**:创建此图假定已安装 pydot 和 graphviz 库。如果存在问题,您可以注释掉导入语句和对 _plot_model()_ 函数的调用。

CIFAR10 生成对抗网络中判别器模型的图
我们现在可以开始使用带有类别标签为 1 的真实示例和随机生成的带有类别标签为 0 的样本来训练这个模型。
这些元素的开发将在以后有用,并且有助于理解判别器只是一个用于二元分类的普通神经网络模型。
首先,我们需要一个函数来加载和准备真实图像数据集。
我们将使用 _cifar.load_data()_ 函数加载 CIFAR-10 数据集,并只使用训练数据集的输入部分作为真实图像。
1 2 3 |
... # 加载 cifar10 数据集 (trainX, _), (_, _) = load_data() |
我们必须将 像素值 从 [0,255] 的无符号整数范围缩放到 [-1,1] 的标准化范围。
生成器模型将生成像素值在 [-1,1] 范围内的图像,因为它将使用 tanh 激活函数,这是一种最佳实践。
将真实图像缩放到相同范围也是一个好习惯。
1 2 3 4 5 |
... # 将无符号整数转换为浮点数 X = trainX.astype('float32') # 将 [0,255] 缩放到 [-1,1] X = (X - 127.5) / 127.5 |
下面的 _load_real_samples()_ 函数实现了真实 CIFAR-10 照片的加载和缩放。
1 2 3 4 5 6 7 8 9 |
# 加载并准备 cifar10 训练图像 def load_real_samples(): # 加载 cifar10 数据集 (trainX, _), (_, _) = load_data() # 将无符号整数转换为浮点数 X = trainX.astype('float32') # 从 [0,255] 缩放到 [-1,1] X = (X - 127.5) / 127.5 return X |
模型将分批更新,特别是使用一组真实样本和一组生成样本。在训练时,一个 epoch 定义为通过整个训练数据集的一次遍历。
我们可以系统地列举训练数据集中的所有样本,这是一个好方法,但通过随机梯度下降进行良好训练需要每个 epoch 之前对训练数据集进行洗牌。一个更简单的方法是从训练数据集中选择随机图像样本。
下面的 _generate_real_samples()_ 函数将以训练数据集作为参数,并选择图像的随机子样本;它还将返回样本的类别标签,具体来说是类别标签 1,以表示真实图像。
1 2 3 4 5 6 7 8 9 |
# 选择真实样本 def generate_real_samples(dataset, n_samples): # 选择随机实例 ix = randint(0, dataset.shape[0], n_samples) # 检索选定的图像 X = dataset[ix] # 生成“真实”类别标签 (1) y = ones((n_samples, 1)) return X, y |
现在,我们需要虚假图像的来源。
我们还没有生成器模型,因此,我们可以生成由随机像素值组成的图像,具体来说是 [0,1] 范围内的随机像素值,然后像我们的缩放真实图像一样缩放到 [-1,1] 范围。
下面的 _generate_fake_samples()_ 函数实现了这种行为,并生成了随机像素值的图像及其关联的类别标签 0(表示伪造)。
1 2 3 4 5 6 7 8 9 10 11 |
# 生成带有类别标签的 n 个假样本 def generate_fake_samples(n_samples): # 在 [0,1] 中生成均匀随机数 X = rand(latent_dim * 32 * 32 * 3 * n_samples) # 更新以使其范围为 [-1, 1] X = -1 + X * 2 # 重塑为一批彩色图像 X = X.reshape((n_samples, 32, 32, 3)) # 生成“假”类别标签 (0) y = zeros((n_samples, 1)) return X, y |
最后,我们需要训练判别器模型。
这涉及重复检索真实图像样本和生成图像样本,并更新模型固定迭代次数。
我们暂时忽略 epoch 的概念(例如,完整遍历训练数据集),并对固定数量的批次拟合判别器模型。模型将迅速学会区分真实图像和随机生成的(虚假)图像,因此在它学会完美区分之前不需要太多批次。
_train_discriminator()_ 函数实现了这一点,使用 128 张图像的批次大小,其中每次迭代有 64 张真实图像和 64 张虚假图像。
我们分别对真实和虚假示例更新判别器,以便在更新之前计算模型在每个样本上的准确性。这提供了对判别器模型随时间表现的洞察。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 训练判别器模型 def train_discriminator(model, dataset, n_iter=20, n_batch=128): half_batch = int(n_batch / 2) # 手动枚举 epoch for i in range(n_iter): # 获取随机选择的“真实”样本 X_real, y_real = generate_real_samples(dataset, half_batch) # 在真实样本上更新判别器 _, real_acc = model.train_on_batch(X_real, y_real) # 生成“假”示例 X_fake, y_fake = generate_fake_samples(half_batch) # 在假样本上更新判别器 _, fake_acc = model.train_on_batch(X_fake, y_fake) # 总结性能 print('>%d real=%.0f%% fake=%.0f%%' % (i+1, real_acc*100, fake_acc*100)) |
综合以上所有内容,下面列出了在真实图像和随机生成的(虚假)图像上训练判别器模型实例的完整示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# 在真实和随机 cifar10 图像上训练判别器模型的示例 from numpy import expand_dims from numpy import ones from numpy import zeros from numpy.random import rand from numpy.random import randint from keras.datasets.cifar10 import load_data from keras.optimizers import Adam from keras.models import Sequential from keras.layers import Dense 从 keras.layers 导入 Conv2D from keras.layers import Flatten 从 keras.layers 导入 Dropout from keras.layers import LeakyReLU # 定义独立的判别器模型 def define_discriminator(in_shape=(32,32,3)): model = Sequential() # 正常 model.add(Conv2D(64, (3,3), padding='same', input_shape=in_shape)) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(128, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(128, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(256, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 分类器 model.add(Flatten()) model.add(Dropout(0.4)) model.add(Dense(1, activation='sigmoid')) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy']) return model # 加载并准备 cifar10 训练图像 def load_real_samples(): # 加载 cifar10 数据集 (trainX, _), (_, _) = load_data() # 将无符号整数转换为浮点数 X = trainX.astype('float32') # 从 [0,255] 缩放到 [-1,1] X = (X - 127.5) / 127.5 return X # 选择真实样本 def generate_real_samples(dataset, n_samples): # 选择随机实例 ix = randint(0, dataset.shape[0], n_samples) # 检索选定的图像 X = dataset[ix] # 生成“真实”类别标签 (1) y = ones((n_samples, 1)) 返回 X, y # 生成带有类别标签的 n 个假样本 def generate_fake_samples(n_samples): # 在 [0,1] 中生成均匀随机数 X = rand(latent_dim * 32 * 32 * 3 * n_samples) # 更新以使其范围为 [-1, 1] X = -1 + X * 2 # 重塑为一批彩色图像 X = X.reshape((n_samples, 32, 32, 3)) # 生成“假”类别标签 (0) y = zeros((n_samples, 1)) 返回 X, y # 训练判别器模型 def train_discriminator(model, dataset, n_iter=20, n_batch=128): half_batch = int(n_batch / 2) # 手动枚举 epoch for i in range(n_iter): # 获取随机选择的“真实”样本 X_real, y_real = generate_real_samples(dataset, half_batch) # 在真实样本上更新判别器 _, real_acc = model.train_on_batch(X_real, y_real) # 生成“假”示例 X_fake, y_fake = generate_fake_samples(half_batch) # 在假样本上更新判别器 _, fake_acc = model.train_on_batch(X_fake, y_fake) # 总结性能 print('>%d real=%.0f%% fake=%.0f%%' % (i+1, real_acc*100, fake_acc*100)) # 定义判别器模型 model = define_discriminator() # 加载图像数据 dataset = load_real_samples() # 拟合模型 train_discriminator(model, dataset) |
运行示例首先定义模型,加载 CIFAR-10 数据集,然后训练判别器模型。
**注意**:由于算法或评估过程的随机性,或数值精度差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
在这种情况下,判别器模型很快学会区分真实和随机生成的 CIFAR-10 图像,大约在 20 个批次内。
1 2 3 4 5 6 |
... >16 真实=100% 虚假=100% >17 真实=100% 虚假=100% >18 真实=98% 虚假=100% >19 真实=100% 虚假=100% >20 真实=100% 虚假=100% |
现在我们知道如何定义和训练判别器模型,下一步是开发生成器模型。
如何定义和使用生成器模型
生成器模型负责创建新的、伪造的但可信的物体小照片。
它通过将潜在空间中的一个点作为输入并输出一个正方形彩色图像来完成此操作。
潜在空间是一个任意定义的、高斯分布值的向量空间,例如 100 维。它本身没有意义,但是通过从该空间中随机抽取点并在训练期间将其提供给生成器模型,生成器模型将为这些潜在点赋值,进而为潜在空间赋值,直到训练结束时,潜在向量空间代表输出空间(CIFAR-10 图像)的压缩表示,只有生成器知道如何将其转换为可信的 CIFAR-10 图像。
- **输入**:潜在空间中的点,例如 100 个高斯随机数元素的向量。
- **输出**:32 x 32 像素的二维方形彩色图像(3 个通道),像素值范围为 [-1,1]。
**注意**:我们不必使用 100 个元素的向量作为输入;它是一个整数且被广泛使用,但我预计 10、50 或 500 个元素也会同样有效。
开发生成器模型需要我们将一个来自潜在空间的 100 维向量转换为一个 32 x 32 x 3 的二维数组,即 3,072 个值。
有多种方法可以实现此目标,但有一种方法已证明在深度卷积生成对抗网络上有效。它涉及两个主要元素。
首先是作为第一个隐藏层的 Dense 层,它具有足够的节点来表示输出图像的低分辨率版本。具体来说,输出图像一半大小(四分之一面积)的图像将是 16x16x3,即 768 个节点;而四分之一大小(八分之一面积)的图像将是 8 x 8 x 3,即 192 个节点。
通过一些实验,我发现图像的较小低分辨率版本效果更好。因此,我们将使用 4 x 4 x 3,即 48 个节点。
我们不只想要一个低分辨率版本的图像;我们想要许多并行版本或对输入的解释。这是卷积神经网络中的一种模式,我们有许多并行滤波器,产生多个并行激活图,称为特征图,对输入有不同的解释。我们希望反过来:我们的输出有许多并行版本,具有不同的学习特征,可以在输出层中折叠成最终图像。模型需要空间来发明、创建或生成。
因此,第一个隐藏层,即 Dense 层,需要足够的节点来容纳我们输出图像的多个版本,例如 256 个。
1 2 3 4 |
# 4x4 图像的基础 n_nodes = 256 * 4 * 4 model.add(Dense(n_nodes, input_dim=latent_dim)) model.add(LeakyReLU(alpha=0.2)) |
这些节点的激活可以被重塑成图像状的东西,以传递到卷积层,例如 256 个不同的 4 x 4 特征图。
1 |
model.add(Reshape((4, 4, 256))) |
下一个主要的架构创新涉及将低分辨率图像上采样到更高分辨率的图像版本。
有两种常见的方法来执行此上采样过程,有时称为反卷积。
一种方法是使用 _UpSampling2D_ 层(类似于反向 池化层),然后是正常的 _Conv2D_ 层。另一种(可能更现代的)方法是将这两个操作组合到一个层中,称为 _Conv2DTranspose_。我们将使用后一种方法作为我们的生成器。
_Conv2DTranspose_ 层可以配置为 步长为 (2×2),这将使输入特征图的面积翻两番(宽度和高度维度加倍)。使用与步长成比例(例如两倍)的内核大小也是一个很好的实践,以 避免棋盘格图案,这种图案有时在向上采样时会观察到。
1 2 3 |
# 上采样到 8x8 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) |
这可以重复两次,以达到我们所需的 32 x 32 输出图像。
同样,我们将使用默认斜率为 0.2 的 LeakyReLU,据报道这是训练 GAN 模型时的最佳实践。
模型的输出层是一个 Conv2D,带有三个用于三个所需通道的过滤器和一个 3×3 的内核大小和“_same_”填充,旨在创建一个特征图并将其维度保留在 32 x 32 x 3 像素。使用 tanh 激活以确保输出值在所需的 [-1,1] 范围内,这是当前的最佳实践。
下面的 _define_generator()_ 函数实现了这一点并定义了生成器模型。
**注意**:生成器模型未编译,未指定损失函数或优化算法。这是因为生成器不是直接训练的。我们将在下一节中了解更多信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# 定义独立的生成器模型 def define_generator(latent_dim): model = Sequential() # 4x4 图像的基础 n_nodes = 256 * 4 * 4 model.add(Dense(n_nodes, input_dim=latent_dim)) model.add(LeakyReLU(alpha=0.2)) model.add(Reshape((4, 4, 256))) # 上采样到 8x8 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 上采样到 16x16 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 上采样到 32x32 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 输出层 model.add(Conv2D(3, (3,3), activation='tanh', padding='same')) return model |
我们可以总结模型以更好地理解输入和输出形状。
完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# 定义生成器模型的示例 from keras.models import Sequential from keras.layers import Dense from keras.layers import Reshape 从 keras.layers 导入 Conv2D from keras.layers import Conv2DTranspose from keras.layers import LeakyReLU from keras.utils.vis_utils import plot_model # 定义独立的生成器模型 def define_generator(latent_dim): model = Sequential() # 4x4 图像的基础 n_nodes = 256 * 4 * 4 model.add(Dense(n_nodes, input_dim=latent_dim)) model.add(LeakyReLU(alpha=0.2)) model.add(Reshape((4, 4, 256))) # 上采样到 8x8 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 上采样到 16x16 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 上采样到 32x32 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 输出层 model.add(Conv2D(3, (3,3), activation='tanh', padding='same')) return model # 定义潜在空间的大小 latent_dim = 100 # 定义生成器模型 model = define_generator(latent_dim) # 总结模型 model.summary() # 绘制模型 plot_model(model, to_file='generator_plot.png', show_shapes=True, show_layer_names=True) |
运行示例总结了模型的层及其输出形状。
我们可以看到,正如设计的那样,第一个隐藏层有 4,096 个参数或 256 x 4 x 4,其激活被重塑为 256 个 4 x 4 的特征图。然后,特征图通过三个 _Conv2DTranspose_ 层向上缩放到所需的 32 x 32 输出形状,直到输出层创建三个滤波器图(通道)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
_________________________________________________________________ 层(类型) 输出形状 参数数量 ================================================================= dense_1 (Dense) (None, 4096) 413696 _________________________________________________________________ leaky_re_lu_1 (LeakyReLU) (None, 4096) 0 _________________________________________________________________ reshape_1 (Reshape) (None, 4, 4, 256) 0 _________________________________________________________________ conv2d_transpose_1 (Conv2DTr (None, 8, 8, 128) 524416 _________________________________________________________________ leaky_re_lu_2 (LeakyReLU) (None, 8, 8, 128) 0 _________________________________________________________________ conv2d_transpose_2 (Conv2DTr (None, 16, 16, 128) 262272 _________________________________________________________________ leaky_re_lu_3 (LeakyReLU) (None, 16, 16, 128) 0 _________________________________________________________________ conv2d_transpose_3 (Conv2DTr (None, 32, 32, 128) 262272 _________________________________________________________________ leaky_re_lu_4 (LeakyReLU) (None, 32, 32, 128) 0 _________________________________________________________________ conv2d_1 (Conv2D) (None, 32, 32, 3) 3459 ================================================================= 总参数:1,466,115 可训练参数:1,466,115 不可训练参数: 0 _________________________________________________________________ |
还创建了模型的图,我们可以看到模型期望来自潜在空间的 100 元素点作为输入,并将预测一个两元素向量作为输出。
**注意**:创建此图假定已安装 pydot 和 graphviz 库。如果存在问题,您可以注释掉导入语句和对 _plot_model()_ 函数的调用。

CIFAR-10 生成对抗网络中生成器模型的图
这个模型目前能做的不多。
尽管如此,我们可以演示如何使用它来生成样本。这是一个有益的演示,可以帮助理解生成器只是另一个模型,其中一些元素在以后会很有用。
第一步是在潜在空间中生成新的点。我们可以通过调用 randn() NumPy 函数来实现这一点,该函数用于生成从标准高斯分布中提取的随机数数组。
然后可以将随机数数组重塑为样本,即 n 行,每行 100 个元素。下面的 _generate_latent_points()_ 函数实现了这一点,并生成了所需数量的潜在空间点,这些点可以用作生成器模型的输入。
1 2 3 4 5 6 7 |
# 在潜在空间中生成点作为生成器的输入 def generate_latent_points(latent_dim, n_samples): # 在潜在空间中生成点 x_input = randn(latent_dim * n_samples) # 重塑为网络的输入批次 x_input = x_input.reshape(n_samples, latent_dim) return x_input |
接下来,我们可以使用生成的点作为生成器模型的输入来生成新样本,然后绘制这些样本。
我们可以更新前一节的 _generate_fake_samples()_ 函数,使其将生成器模型作为参数,并使用它生成所需数量的样本,方法是首先调用 _generate_latent_points()_ 函数生成所需数量的潜在空间点作为模型的输入。
更新后的 _generate_fake_samples()_ 函数列在下面,并返回生成的样本和相关的类别标签。
1 2 3 4 5 6 7 8 9 |
# 使用生成器生成 n 个假示例,并带有类别标签 def generate_fake_samples(g_model, latent_dim, n_samples): # 在潜在空间中生成点 x_input = generate_latent_points(latent_dim, n_samples) # 预测输出 X = g_model.predict(x_input) # 创建“假”类别标签 (0) y = zeros((n_samples, 1)) return X, y |
然后,我们可以像在第一节中绘制真实 CIFAR-10 示例一样,通过调用 imshow() 函数来绘制生成的样本。
下面列出了使用未经训练的生成器模型生成新的 CIFAR-10 图像的完整示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# 定义和使用生成器模型的示例 from numpy import zeros from numpy.random import randn from keras.models import Sequential from keras.layers import Dense from keras.layers import Reshape 从 keras.layers 导入 Conv2D from keras.layers import Conv2DTranspose from keras.layers import LeakyReLU from matplotlib import pyplot # 定义独立的生成器模型 def define_generator(latent_dim): model = Sequential() # 4x4 图像的基础 n_nodes = 256 * 4 * 4 model.add(Dense(n_nodes, input_dim=latent_dim)) model.add(LeakyReLU(alpha=0.2)) model.add(Reshape((4, 4, 256))) # 上采样到 8x8 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 上采样到 16x16 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 上采样到 32x32 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 输出层 model.add(Conv2D(3, (3,3), activation='tanh', padding='same')) return model # 在潜在空间中生成点作为生成器的输入 def generate_latent_points(latent_dim, n_samples): # 在潜在空间中生成点 x_input = randn(latent_dim * n_samples) # 重塑为网络的输入批次 x_input = x_input.reshape(n_samples, latent_dim) return x_input # 使用生成器生成 n 个假示例,并带有类别标签 def generate_fake_samples(g_model, latent_dim, n_samples): # 在潜在空间中生成点 x_input = generate_latent_points(latent_dim, n_samples) # 预测输出 X = g_model.predict(x_input) # 创建“假”类别标签 (0) y = zeros((n_samples, 1)) 返回 X, y # 潜在空间的大小 latent_dim = 100 # 定义判别器模型 model = define_generator(latent_dim) # 生成样本 n_samples = 49 X, _ = generate_fake_samples(model, latent_dim, n_samples) # 将像素值从 [-1,1] 缩放到 [0,1] X = (X + 1) / 2.0 # 绘制生成的样本 for i in range(n_samples): # 定义子图 pyplot.subplot(7, 7, 1 + i) # 关闭轴标签 pyplot.axis('off') # 绘制单张图像 pyplot.imshow(X[i]) # 显示图 pyplot.show() |
运行示例生成 49 个虚假的 CIFAR-10 图像,并将其显示在一个 7 乘 7 的图像网格中。
由于模型未经训练,生成的图像是完全随机的像素值,范围在 [-1, 1] 之间,并重新缩放到 [0, 1]。正如我们可能预期的那样,这些图像看起来像一团灰色。

未经训练的生成器模型输出的 49 张 CIFAR-10 图像示例
现在我们知道如何定义和使用生成器模型,下一步是训练模型。
如何训练生成器模型
生成器模型中的权重根据判别器模型的性能进行更新。
当判别器善于检测假样本时,生成器更新更多;当判别器模型在检测假样本时相对较差或感到困惑时,生成器模型更新较少。
这定义了这两个模型之间的零和或对抗关系。
使用 Keras API 实现此功能可能有很多方法,但最简单的方法可能是创建一个结合生成器和判别器模型的新模型。
具体来说,可以定义一个新的 GAN 模型,该模型将生成器和判别器堆叠起来,使生成器接收潜在空间中的随机点作为输入,并生成直接馈送到判别器模型的样本,进行分类,并且这个较大模型的输出可以用于更新生成器的模型权重。
需要明确的是,我们不是在谈论一个新的第三个模型,而是一个使用已经定义的独立生成器和判别器模型中的层和权重的新逻辑模型。
只有判别器关注区分真实和虚假示例,因此判别器模型可以像我们上面在判别器模型部分中所做的那样,在每种类型的示例上以独立方式进行训练。
生成器模型只关注判别器对虚假示例的性能。因此,当判别器作为 GAN 模型的一部分时,我们将判别器中的所有层标记为不可训练,以便它们不能被更新并过度训练虚假示例。
当通过这个逻辑 GAN 模型训练生成器时,还有一个重要的变化。我们希望判别器认为生成器输出的样本是真实的,而不是虚假的。因此,当生成器作为 GAN 模型的一部分进行训练时,我们将生成的样本标记为真实的(类别 1)。
我们为什么要这样做?
我们可以想象判别器会将生成的样本分类为不真实的(类别 0)或真实可能性较低的(0.3 或 0.5)。用于更新模型权重的反向传播过程会将此视为一个大错误,并将更新模型权重(即,仅更新生成器中的权重)以纠正此错误,从而使生成器更好地生成好的虚假样本。
让我们具体化。
- **输入**:潜在空间中的点,例如 100 个高斯随机数元素的向量。
- **输出**:二元分类,样本为真实的(或伪造的)可能性。
下面的 _define_gan()_ 函数将已经定义的生成器和判别器模型作为参数,并创建了一个新的、逻辑上的第三个模型,将这两个模型包含在内。判别器中的权重被标记为不可训练,这只影响 GAN 模型看到的权重,而不影响独立的判别器模型。
GAN 模型然后使用与判别器相同的二元交叉熵损失函数和高效的 Adam 版本的随机梯度下降,学习率为 0.0002,动量为 0.5,建议在训练深度卷积 GAN 时使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 定义组合的生成器和判别器模型,用于更新生成器 def define_gan(g_model, d_model): # 使判别器中的权重不可训练 d_model.trainable = False # 连接它们 model = Sequential() # 添加生成器 model.add(g_model) # 添加判别器 model.add(d_model) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss='binary_crossentropy', optimizer=opt) return model |
使判别器不可训练是 Keras API 中的一个巧妙技巧。
“trainable”属性在模型编译后起作用。判别器模型是用可训练层编译的,因此当通过调用 _train_on_batch()_ 函数更新独立模型时,这些层中的模型权重将被更新。
判别器模型被标记为不可训练,然后添加到GAN模型中并进行编译。在此模型中,判别器模型的模型权重不可训练,在通过调用“train_on_batch()”函数更新GAN模型时无法更改。此可训练属性的更改不影响独立判别器模型的训练。
Keras API文档中对此行为进行了描述
创建判别器、生成器和复合模型的完整示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# 演示在GAN中创建三个模型 from keras.optimizers import Adam from keras.models import Sequential from keras.layers import Dense from keras.layers import Reshape from keras.layers import Flatten 从 keras.layers 导入 Conv2D from keras.layers import Conv2DTranspose from keras.layers import LeakyReLU 从 keras.layers 导入 Dropout from keras.utils.vis_utils import plot_model # 定义独立的判别器模型 def define_discriminator(in_shape=(32,32,3)): model = Sequential() # 正常 model.add(Conv2D(64, (3,3), padding='same', input_shape=in_shape)) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(128, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(128, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(256, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 分类器 model.add(Flatten()) model.add(Dropout(0.4)) model.add(Dense(1, activation='sigmoid')) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy']) return model # 定义独立的生成器模型 def define_generator(latent_dim): model = Sequential() # 4x4 图像的基础 n_nodes = 256 * 4 * 4 model.add(Dense(n_nodes, input_dim=latent_dim)) model.add(LeakyReLU(alpha=0.2)) model.add(Reshape((4, 4, 256))) # 上采样到 8x8 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 上采样到 16x16 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 上采样到 32x32 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 输出层 model.add(Conv2D(3, (3,3), activation='tanh', padding='same')) return model # 定义组合的生成器和判别器模型,用于更新生成器 def define_gan(g_model, d_model): # 使判别器中的权重不可训练 d_model.trainable = False # 连接它们 model = Sequential() # 添加生成器 model.add(g_model) # 添加判别器 model.add(d_model) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss='binary_crossentropy', optimizer=opt) return model # 潜在空间的大小 latent_dim = 100 # 创建判别器 d_model = define_discriminator() # 创建生成器 g_model = define_generator(latent_dim) # 创建GAN gan_model = define_gan(g_model, d_model) # 总结GAN模型 gan_model.summary() # 绘制GAN模型 plot_model(gan_model, to_file='gan_plot.png', show_shapes=True, show_layer_names=True) |
运行该示例首先创建复合模型的摘要,这非常无趣。
我们可以看到模型期望将CIFAR-10图像作为输入,并预测单个值作为输出。
1 2 3 4 5 6 7 8 9 10 11 |
_________________________________________________________________ 层(类型) 输出形状 参数数量 ================================================================= sequential_2 (Sequential) (None, 32, 32, 3) 1466115 _________________________________________________________________ sequential_1 (Sequential) (None, 1) 522497 ================================================================= 总参数:1,988,612 可训练参数:1,466,115 不可训练参数:522,497 _________________________________________________________________ |
还创建了一个模型图,我们可以看到该模型期望将潜在空间中的100元素点作为输入,并预测单个输出分类标签。
**注意**:创建此图假定已安装 pydot 和 graphviz 库。如果存在问题,您可以注释掉导入语句和对 _plot_model()_ 函数的调用。

CIFAR-10生成对抗网络中复合生成器和判别器模型的图
训练复合模型涉及通过上一节中的“generate_latent_points()”函数在潜在空间中生成一个批量的点,以及类=1标签,并调用“train_on_batch()”函数。
下面的“train_gan()”函数演示了这一点,尽管它非常简单,因为每个 epoch 只会更新生成器,而判别器会保留默认模型权重。
1 2 3 4 5 6 7 8 9 10 |
# 训练复合模型 def train_gan(gan_model, latent_dim, n_epochs=200, n_batch=128): # 手动枚举 epoch for i in range(n_epochs): # 准备潜在空间中的点作为生成器的输入 x_gan = generate_latent_points(latent_dim, n_batch) # 为伪样本创建反转标签 y_gan = ones((n_batch, 1)) # 通过判别器的误差更新生成器 gan_model.train_on_batch(x_gan, y_gan) |
相反,我们需要首先使用真实和伪样本更新判别器模型,然后通过复合模型更新生成器。
这需要结合上面判别器部分中定义的“train_discriminator()”函数和上面定义的“train_gan()”函数的元素。它还需要我们枚举每个 epoch 中的 epoch 和批次。
用于更新判别器模型和生成器(通过复合模型)的完整训练函数如下所示。
此模型训练函数中有几点需要注意。
首先,一个 epoch 中的批次数由批量大小除以训练数据集的次数决定。我们有一个5万个样本的数据集,因此向下取整后,每个 epoch 有390个批次。
判别器模型每个批次更新两次,一次使用真实样本,一次使用伪样本,这是一种最佳实践,而不是将样本组合并执行一次更新。
最后,我们报告每个批次的损失。密切关注批次之间的损失至关重要。原因在于判别器损失的崩溃表明生成器模型已开始生成判别器可以轻松区分的垃圾示例。
监控判别器损失,并期望它在每个批次中徘徊在0.5到0.8之间。生成器损失不那么关键,可能徘徊在0.5到2或更高之间。聪明的程序员甚至可以尝试检测判别器的崩溃损失,停止,然后重新启动训练过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# 训练生成器和判别器 def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=200, n_batch=128): bat_per_epo = int(dataset.shape[0] / n_batch) half_batch = int(n_batch / 2) # 手动枚举 epoch for i in range(n_epochs): # 枚举训练集中的批次 for j in range(bat_per_epo): # 获取随机选择的“真实”样本 X_real, y_real = generate_real_samples(dataset, half_batch) # 更新判别器模型权重 d_loss1, _ = d_model.train_on_batch(X_real, y_real) # 生成“假”示例 X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch) # 更新判别器模型权重 d_loss2, _ = d_model.train_on_batch(X_fake, y_fake) # 准备潜在空间中的点作为生成器的输入 X_gan = generate_latent_points(latent_dim, n_batch) # 为伪样本创建反转标签 y_gan = ones((n_batch, 1)) # 通过判别器的误差更新生成器 g_loss = gan_model.train_on_batch(X_gan, y_gan) # 总结此批次的损失 print('>%d, %d/%d, d1=%.3f, d2=%.3f g=%.3f' % (i+1, j+1, bat_per_epo, d_loss1, d_loss2, g_loss)) |
我们几乎拥有了开发用于CIFAR-10物体照片数据集的GAN所需的一切。
剩余的一个方面是模型的评估。
如何评估 GAN 模型性能
通常,没有客观的方法来评估GAN模型的性能。
我们无法计算生成图像的此客观误差分数。
相反,图像必须由人类操作员主观评估质量。这意味着我们不知道何时停止训练,而无需查看生成的图像示例。反过来,训练过程的对抗性性质意味着生成器在每个批次后都在变化,这意味着一旦可以生成“足够好”的图像,图像的主观质量可能会随后的更新而变化、改进甚至退化。
有三种方法可以处理这种复杂的训练情况。
- 定期评估判别器在真实和伪图像上的分类准确性。
- 定期生成大量图像并将其保存到文件中以供主观审查。
- 定期保存生成器模型。
所有这三个操作可以在给定训练周期同时执行,例如每10个训练周期。结果将是一个保存的生成器模型,我们可以主观评估其输出质量,并客观了解模型保存时判别器被欺骗的程度。
对GAN进行多个周期(例如数百或数千个周期)的训练,将产生许多模型快照,可以检查这些快照,并从中挑选出特定的输出和模型以供以后使用。
首先,我们可以定义一个名为“summarize_performance()”的函数,该函数将总结判别器模型的性能。它通过检索真实CIFAR-10图像的样本,以及使用生成器模型生成相同数量的伪CIFAR-10图像,然后评估判别器模型在每个样本上的分类准确性,并报告这些分数来完成此操作。
1 2 3 4 5 6 7 8 9 10 11 12 |
# 评估判别器,绘制生成的图像,保存生成器模型 def summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=150): # 准备真实样本 X_real, y_real = generate_real_samples(dataset, n_samples) # 评估判别器在真实示例上的表现 _, acc_real = d_model.evaluate(X_real, y_real, verbose=0) # 准备伪示例 x_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples) # 评估判别器在伪示例上的表现 _, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0) # 总结判别器性能 print('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100)) |
此函数可以根据当前时期数从“train()”函数中调用,例如每10个时期。
1 2 3 4 5 6 7 8 9 10 |
# 训练生成器和判别器 def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=200, n_batch=128): bat_per_epo = int(dataset.shape[0] / n_batch) half_batch = int(n_batch / 2) # 手动枚举 epoch for i in range(n_epochs): ... # 有时评估模型性能 if (i+1) % 10 == 0: summarize_performance(i, g_model, d_model, dataset, latent_dim) |
接下来,我们可以更新“summarize_performance()”函数,以保存模型并创建和保存生成的示例图。
可以通过调用生成器模型上的“save()”函数并提供基于训练时期号的唯一文件名来保存生成器模型。
1 2 3 4 |
... # 保存生成器模型到文件 filename = 'generator_model_%03d.h5' % (epoch+1) g_model.save(filename) |
我们可以开发一个函数来创建生成样本的图。
由于我们正在评估判别器对100个生成的CIFAR-10图像的性能,我们可以绘制大约一半,即49个,作为7x7网格。下面的“save_plot()”函数实现了这一点,同样使用基于时期号的唯一文件名保存结果图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 创建并保存生成的图像图 def save_plot(examples, epoch, n=7): # 将范围从[-1,1]缩放到[0,1] examples = (examples + 1) / 2.0 # 绘制图像 for i in range(n * n): # 定义子图 pyplot.subplot(n, n, 1 + i) # 关闭轴线 pyplot.axis('off') # 绘制原始像素数据 pyplot.imshow(examples[i]) # 保存图到文件 filename = 'generated_plot_e%03d.png' % (epoch+1) pyplot.savefig(filename) pyplot.close() |
更新后的“summarize_performance()”函数及其新增内容如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 评估判别器,绘制生成的图像,保存生成器模型 def summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=150): # 准备真实样本 X_real, y_real = generate_real_samples(dataset, n_samples) # 评估判别器在真实示例上的表现 _, acc_real = d_model.evaluate(X_real, y_real, verbose=0) # 准备伪示例 x_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples) # 评估判别器在伪示例上的表现 _, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0) # 总结判别器性能 print('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100)) # 保存图 save_plot(x_fake, epoch) # 保存生成器模型到文件 filename = 'generator_model_%03d.h5' % (epoch+1) g_model.save(filename) |
CIFAR-10 GAN 的完整示例
我们现在拥有一切所需,可以在CIFAR-10小物体照片数据集上训练和评估GAN。
完整的示例如下所示。
注意:此示例可在CPU上运行,但可能需要数小时。该示例可在GPU上运行,例如Amazon EC2 p3实例,并在几分钟内完成。
有关设置AWS EC2实例以运行此代码的帮助,请参阅教程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# cifar10上的dcgan示例 from numpy import expand_dims from numpy import zeros from numpy import ones from numpy import vstack from numpy.random import randn from numpy.random import randint from keras.datasets.cifar10 import load_data from keras.optimizers import Adam from keras.models import Sequential from keras.layers import Dense from keras.layers import Reshape from keras.layers import Flatten 从 keras.layers 导入 Conv2D from keras.layers import Conv2DTranspose from keras.layers import LeakyReLU 从 keras.layers 导入 Dropout from matplotlib import pyplot # 定义独立的判别器模型 def define_discriminator(in_shape=(32,32,3)): model = Sequential() # 正常 model.add(Conv2D(64, (3,3), padding='same', input_shape=in_shape)) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(128, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(128, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 下采样 model.add(Conv2D(256, (3,3), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 分类器 model.add(Flatten()) model.add(Dropout(0.4)) model.add(Dense(1, activation='sigmoid')) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy']) return model # 定义独立的生成器模型 def define_generator(latent_dim): model = Sequential() # 4x4 图像的基础 n_nodes = 256 * 4 * 4 model.add(Dense(n_nodes, input_dim=latent_dim)) model.add(LeakyReLU(alpha=0.2)) model.add(Reshape((4, 4, 256))) # 上采样到 8x8 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 上采样到 16x16 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 上采样到 32x32 model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')) model.add(LeakyReLU(alpha=0.2)) # 输出层 model.add(Conv2D(3, (3,3), activation='tanh', padding='same')) return model # 定义组合的生成器和判别器模型,用于更新生成器 def define_gan(g_model, d_model): # 使判别器中的权重不可训练 d_model.trainable = False # 连接它们 model = Sequential() # 添加生成器 model.add(g_model) # 添加判别器 model.add(d_model) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss='binary_crossentropy', optimizer=opt) return model # 加载并准备 cifar10 训练图像 def load_real_samples(): # 加载 cifar10 数据集 (trainX, _), (_, _) = load_data() # 将无符号整数转换为浮点数 X = trainX.astype('float32') # 从 [0,255] 缩放到 [-1,1] X = (X - 127.5) / 127.5 return X # 选择真实样本 def generate_real_samples(dataset, n_samples): # 选择随机实例 ix = randint(0, dataset.shape[0], n_samples) # 检索选定的图像 X = dataset[ix] # 生成“真实”类别标签 (1) y = ones((n_samples, 1)) 返回 X, y # 在潜在空间中生成点作为生成器的输入 def generate_latent_points(latent_dim, n_samples): # 在潜在空间中生成点 x_input = randn(latent_dim * n_samples) # 重塑为网络的输入批次 x_input = x_input.reshape(n_samples, latent_dim) return x_input # 使用生成器生成 n 个假示例,并带有类别标签 def generate_fake_samples(g_model, latent_dim, n_samples): # 在潜在空间中生成点 x_input = generate_latent_points(latent_dim, n_samples) # 预测输出 X = g_model.predict(x_input) # 创建“假”类别标签 (0) y = zeros((n_samples, 1)) 返回 X, y # 创建并保存生成的图像图 def save_plot(examples, epoch, n=7): # 将范围从[-1,1]缩放到[0,1] examples = (examples + 1) / 2.0 # 绘制图像 for i in range(n * n): # 定义子图 pyplot.subplot(n, n, 1 + i) # 关闭轴线 pyplot.axis('off') # 绘制原始像素数据 pyplot.imshow(examples[i]) # 保存图到文件 filename = 'generated_plot_e%03d.png' % (epoch+1) pyplot.savefig(filename) pyplot.close() # 评估判别器,绘制生成的图像,保存生成器模型 def summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=150): # 准备真实样本 X_real, y_real = generate_real_samples(dataset, n_samples) # 评估判别器在真实示例上的表现 _, acc_real = d_model.evaluate(X_real, y_real, verbose=0) # 准备伪示例 x_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples) # 评估判别器在伪示例上的表现 _, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0) # 总结判别器性能 print('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100)) # 保存图 save_plot(x_fake, epoch) # 保存生成器模型到文件 filename = 'generator_model_%03d.h5' % (epoch+1) g_model.save(filename) # 训练生成器和判别器 def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=200, n_batch=128): bat_per_epo = int(dataset.shape[0] / n_batch) half_batch = int(n_batch / 2) # 手动枚举 epoch for i in range(n_epochs): # 枚举训练集中的批次 for j in range(bat_per_epo): # 获取随机选择的“真实”样本 X_real, y_real = generate_real_samples(dataset, half_batch) # 更新判别器模型权重 d_loss1, _ = d_model.train_on_batch(X_real, y_real) # 生成“假”示例 X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch) # 更新判别器模型权重 d_loss2, _ = d_model.train_on_batch(X_fake, y_fake) # 准备潜在空间中的点作为生成器的输入 X_gan = generate_latent_points(latent_dim, n_batch) # 为伪样本创建反转标签 y_gan = ones((n_batch, 1)) # 通过判别器的误差更新生成器 g_loss = gan_model.train_on_batch(X_gan, y_gan) # 总结此批次的损失 print('>%d, %d/%d, d1=%.3f, d2=%.3f g=%.3f' % (i+1, j+1, bat_per_epo, d_loss1, d_loss2, g_loss)) # 有时评估模型性能 if (i+1) % 10 == 0: summarize_performance(i, g_model, d_model, dataset, latent_dim) # 潜在空间的大小 latent_dim = 100 # 创建判别器 d_model = define_discriminator() # 创建生成器 g_model = define_generator(latent_dim) # 创建GAN gan_model = define_gan(g_model, d_model) # 加载图像数据 dataset = load_real_samples() # 训练模型 train(g_model, d_model, gan_model, dataset, latent_dim) |
选择的配置导致生成模型和判别模型稳定训练。
每批报告模型性能,包括判别(d)和生成(g)模型的损失。
**注意**:由于算法或评估过程的随机性,或数值精度差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
在这种情况下,损失在训练过程中保持稳定。判别器在真实和生成示例上的损失约为0.5,而通过判别器训练的生成器的损失在训练过程的大部分时间里约为1.5。
1 2 3 4 5 6 7 8 9 10 11 |
>1, 1/390, d1=0.720, d2=0.695 g=0.692 >1, 2/390, d1=0.658, d2=0.697 g=0.691 >1, 3/390, d1=0.604, d2=0.700 g=0.687 >1, 4/390, d1=0.522, d2=0.709 g=0.680 >1, 5/390, d1=0.417, d2=0.731 g=0.662 ... >200, 386/390, d1=0.499, d2=0.401 g=1.565 >200, 387/390, d1=0.459, d2=0.623 g=1.481 >200, 388/390, d1=0.588, d2=0.556 g=1.700 >200, 389/390, d1=0.579, d2=0.288 g=1.555 >200, 390/390, d1=0.620, d2=0.453 g=1.466 |
生成器每10个时期进行一次评估,从而产生20次评估、20幅生成图像图和20个保存的模型。
在这种情况下,我们可以看到准确性在训练过程中波动。当结合生成的图像查看判别器模型的准确性分数时,我们可以看到伪示例的准确性与图像的主观质量相关性不佳,但真实示例的准确性可能相关。
这是衡量GAN性能的一个粗略且可能不可靠的指标,连同损失。
1 2 3 4 5 6 |
>真实准确率:55%,伪造准确率:89% >真实准确率:50%,伪造准确率:75% >真实准确率:49%,伪造准确率:86% >真实准确率:60%,伪造准确率:79% >真实准确率:49%,伪造准确率:87% ... |
在某个点之后进行更多训练并不意味着生成图像的质量会更好。
在这种情况下,10个 epoch 后的结果质量较低,尽管我们可以看到背景和前景之间存在一些差异,每个图像中间都有一个斑点。

10个时期后49幅GAN生成的CIFAR-10照片图
经过90或100个时期后,我们开始看到一些看似合理的照片,其中有一些看起来像鸟、狗、猫和马的斑点。
这些物体很熟悉,类似于CIFAR-10,但其中许多并不清楚是10个指定类别中的哪一个。

90个时期后49幅GAN生成的CIFAR-10照片图

100个时期后49幅GAN生成的CIFAR-10照片图
该模型在接下来的100个时期内保持稳定,生成的图像几乎没有重大改进。
这些小照片仍然模糊地像CIFAR-10,并专注于狗、猫和鸟等动物。

200个时期后49幅GAN生成的CIFAR-10照片图
如何使用最终的生成器模型生成图像
一旦选择了最终生成器模型,它就可以以独立的方式用于您的应用程序。
这涉及首先从文件加载模型,然后使用它生成图像。每个图像的生成都需要潜在空间中的一个点作为输入。
下面列出了加载保存的模型和生成图像的完整示例。在这种情况下,我们将使用经过200个训练周期后保存的模型,但经过100个周期后保存的模型同样适用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# 加载生成器模型并生成图像的示例 from keras.models import load_model from numpy.random import randn from matplotlib import pyplot # 在潜在空间中生成点作为生成器的输入 def generate_latent_points(latent_dim, n_samples): # 在潜在空间中生成点 x_input = randn(latent_dim * n_samples) # 重塑为网络的输入批次 x_input = x_input.reshape(n_samples, latent_dim) return x_input # 绘制生成的图像 def create_plot(examples, n): # 绘制图像 for i in range(n * n): # 定义子图 pyplot.subplot(n, n, 1 + i) # 关闭轴线 pyplot.axis('off') # 绘制原始像素数据 pyplot.imshow(examples[i, :, :]) pyplot.show() # 加载模型 model = load_model('generator_model_200.h5') # 生成图像 latent_points = generate_latent_points(100, 100) # 生成图像 X = model.predict(latent_points) # 将范围从[-1,1]缩放到[0,1] X = (X + 1) / 2.0 # 绘制结果 create_plot(X, 10) |
运行示例首先加载模型,在潜在空间中采样100个随机点,生成100张图像,然后将结果绘制成一张图像。
**注意**:由于算法或评估过程的随机性,或数值精度差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
我们可以看到,大多数图像都是合理的,或者是小物体合理的片段。
我能看到狗、猫、马、鸟、青蛙,可能还有飞机。

100张GAN生成的CIFAR-10小物体照片示例
潜在空间现在定义了CIFAR-10照片的压缩表示。
您可以尝试在此空间中生成不同的点,并查看它们生成何种类型的图像。
下面的示例使用全为0.75值的向量生成单个图像。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 为潜在空间中的特定点生成图像的示例 from keras.models import load_model from numpy import asarray from matplotlib import pyplot # 加载模型 model = load_model('generator_model_200.h5') # 全为0 vector = asarray([[0.75 for _ in range(100)]]) # 生成图像 X = model.predict(vector) # 将范围从[-1,1]缩放到[0,1] X = (X + 1) / 2.0 # 绘制结果 pyplot.imshow(X[0, :, :]) pyplot.show() |
**注意**:由于算法或评估过程的随机性,或数值精度差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
在这种情况下,一个所有值为0.75的向量会生成一只鹿,或者是一只鹿-马状的动物,置身于一片绿色的田野中。

GAN为潜在空间中的特定点生成CIFAR小物体照片的示例
扩展
本节列出了一些您可能希望探索的扩展本教程的想法。
- 改变潜在空间。更新示例以使用更大或更小的潜在空间,并比较结果质量和训练速度。
- 批归一化。更新判别器和/或生成器以使用批归一化,建议用于DCGAN模型。
- 标签平滑。更新示例以在训练判别器时使用单侧标签平滑,具体地,将真实示例的目标标签从1.0更改为0.9并添加随机噪声,然后查看对图像质量和训练速度的影响。
- 模型配置。更新模型配置以使用更深或更浅的判别器和/或生成器模型,也许可以尝试生成器中的UpSampling2D层。
如果您探索了这些扩展中的任何一个,我很想知道。
请在下面的评论中发布您的发现。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
文章
- 第20章。深度生成模型,深度学习,2016年。
- 第8章。生成深度学习,Python深度学习,2017年。
论文
- 生成对抗网络, 2014.
- 教程:生成对抗网络,NIPS, 2016.
- 使用深度卷积生成对抗网络的无监督表征学习, 2015.
API
- Keras数据集API。
- Keras 序列模型 API
- Keras卷积层API
- 我如何“冻结”Keras层?
- MatplotLib API
- NumPy 随机抽样 (numpy.random) API
- NumPy 数组操作例程
文章
- CIFAR-10,维基百科.
- CIFAR-10数据集和CIFAR-100数据集.
- DCGAN与Cifar10的冒险,2018年.
- DCGAN-CIFAR10项目,DCGAN用于CIFAR10图像的实现.
- TensorFlow-GAN (TF-GAN) 项目.
总结
在本教程中,您学习了如何使用深度卷积网络开发生成对抗网络,用于生成小物体照片。
具体来说,你学到了:
- 如何定义和训练独立的判别器模型以学习真实图像和虚假图像之间的差异。
- 如何定义独立的生成器模型并训练复合生成器和判别器模型。
- 如何评估 GAN 的性能并使用最终的独立生成器模型生成新图像。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
你好。谢谢你的帖子。我可以使用GAN进行分类吗?
您可以使用判别器进行分类,或作为迁移学习的起点。
你好!感谢您的工作。我在pytorch和tensorflow中做了GAN,但它们在GAN方面表现不佳。但是您的工作让我接触到了Keras。我将您的代码用于我的AI,我将对其进行改进。非常感谢。
嗨 GosSamer…谢谢您的反馈!请告诉我们您的模型未来的表现如何。
迄今为止最好的GAN教程。非常感谢!
谢谢!
Jason,非常感谢您的所有教程。非常感谢您为解释概念所做的努力,这确实帮助我理解了一些非常复杂的事情。
关于这个的快速问题,上面的代码是否也可以用于更高分辨率的图像,只需简单地将输入和输出维度从32x32x3更改为更高的值,并更改您上采样/下采样的方式,或者基本上是否涉及其他技巧。
以及这是否与深度伪造的实现方式基本相同。
是的,正是如此。
尽管您可能需要更花哨的技巧才能使模型稳定
https://machinelearning.org.cn/how-to-code-generative-adversarial-network-hacks/
你好,
我使用本教程学习如何实现GANS。我修改了代码,并编程了Robbie Barrat艺术DCGAN(在佳士得拍卖会上以432,500美元售出的肖像生成器)的相同拓扑结构
它允许您加载自己的128×128 RGB图像数据集
在此处查看
https://github.com/jordan-bird/art-DCGAN-Keras
PS:感谢精彩的教程,Jason
非常酷,Jordan,谢谢分享!
非常感谢这篇文章。我在哪里可以下载数据集?非常感谢!
当您调用时下载
Jason,在显示判别器模型后,您引用它说:“还创建了一个模型图,我们可以看到该模型期望两个输入并预测一个输出。”我认为这段文字可能被其他文字污染了,您实际上想写“一个输入”。我只是告诉您,因为我知道您是一个完美主义者,所以也许您想纠正它(我喜欢您写的任何东西)。
谢谢,已修正。
感谢您分享关于GAN的详细讨论。我有一个问题,是否可以通过代码获取生成图像的类别标签?
我们该怎么做。
再次感谢。
是的,也许可以使用监督学习模型对其进行分类,例如预训练的vgg16或更好的模型。
Jason,我非常非常喜欢你的教学方法。你不害怕重复代码片段,即使读者可以推断出细微的改动。你再次展示它们,让读者的注意力集中在内容上;这样他或她就不会因为需要填补空白而分心。你所写的内容没有空白,读者可以流畅地跟随推理流程(我喜欢你写“完整的例子是……”,“让我们把所有东西都整合起来……”)。是的,我们可以自己编译代码片段,但现在我们专注于故事的结局,而你让我们保持在正轨上。此外,通常“编译”对于新读者来说并不明显,而离线验证单个组件则是一个有趣的练习。多次看到相同代码,并保持你在不同教程中确保的命名约定和描述的一致性,这对于用户来说是一种监督学习,它提供了增强的示例;它有效。这太棒了!
您选择避免将所有内容嵌入类中,这也使得阅读变得轻松。构建对象可能更“优雅”,但这会要求用户在分配给每个不同元素的属性和方法中找到自己的方式。您避免不必要的努力,偏离目标,因此您将所有所需功能压缩到清晰的函数中;这些函数将由需要它们的任何代码部分使用。在您的教程中,您不保护或隐藏,您明确偏向于可见性。
我在互联网上看到了许多有效的示例和教程,但很少有像你那样,我发现了你为确保读者真正学习所付出的非凡努力,以及不断强调关键项目(从不同角度看)以巩固学习。这是对教师技能的认可。我相信这代表了你的读者的观点。
回到工作。
在“# 加载生成器模型并生成图像的示例”中
您自然地重用了“# 创建并保存生成的图像图”
覆盖了之前的函数。但是,这次您没有保存图像,因为您实际上正在用 pyplot.show() 显示它。在不需要更改 save_plot() 的名称的情况下,也许您可以将注释“# 创建并保存生成的图像图”更改为
# 创建并显示生成的图像图。除非您想将保存解释为保存到屏幕。
谢谢你,安东尼奥,你真好。
经过这么多年写作,您可能是我第一个真正深入注意到我的“风格”的人。感谢您的注意和欣赏。是的,有意使用一致的命名约定,重复,将文章设计成逐步完整的示例,并且不使用类和更花哨的API。我很高兴它有所帮助。
感谢您对代码的说明,我已经更新了示例以使其更清晰。
你好 Jason,
我非常同意安东尼奥的观点。你是一位才华横溢的老师(我是巴黎大学的教授,对你的才华深感谦卑)。我在我的课程中使用了你的许多例子(并附上了你博客的链接),我坚持你的教学方法。
再次非常感谢
谢谢,您这么说真是太好了。我很高兴我的例子有帮助!
非常感谢您的精彩教程。
我大致明白我们首先独立训练判别器,然后使其权重不可训练。然后设计生成器模型,将其输出输入到训练好的判别器中。然后我们可以训练这个组合模型来只更新生成器的权重并完成任务。然后我们就可以只使用生成器部分来创建新的照片。
在这种情况下(如上所述的组合模型),我无法编写代码来获取中间层输出。即生成器部分生成32x32x3维度的输出。
这最终会被输入到判别器中,但我想在它发生之前捕获它并查看照片的外观。如何实现这一点?Keras的“模型”定义也没有帮助。
在教程中,我们将策略更改为将生成器和判别器结合起来,并循环训练它们。这是为什么?
谢谢
拉吉夫
谢谢,很高兴它帮到了你!
也许您可以将复合模型设计为具有两个输出,一个来自生成器,一个来自判别器。使用函数式API应该很容易。
我怎么用我电脑里的图片数据集替换CIFAR 10呢?能详细解释一下吗?
提前感谢.. 🙂
是的,您可以加载图像并使用它们来训练gan,我在这里展示了如何加载图像
https://machinelearning.org.cn/how-to-load-and-manipulate-images-for-deep-learning-in-python-with-pil-pillow/
谢谢你的回复,但是当我以列表形式加载图像(trainX[])时,它显示错误“list object has noattribute astype”。
很抱歉听到这个消息,我在这里有一些建议。
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
我解决了这个问题。现在如何将所有生成的图像保存到一个文件夹中?你能帮我一下吗?
是的,我在此教程中展示了如何操作,请参阅save_plot()函数。
但是如何将每张图片保存到一个文件夹中呢?
如果您愿意,可以更改代码以单独保存每个图像。
我可以保存多少张图片?有什么限制吗?
没有,只有您拥有的硬盘空间量。
谢谢 Jason。
有史以来最好的教程……解释真的非常清晰,而且循序渐进
现在我对GAN有了更深入的理解。
非常感谢。
谢谢,很高兴它帮到了你!
非常感谢您的本教程。解释得非常好
不客气。我很高兴它有所帮助。
嗨,Jason,感谢您的精彩帖子!我关注您的文章已经有一段时间了。它们真的非常具体且易于理解。
所以,一个问题,我仍然对生成器如何根据gan的损失更新其权重感到困惑。为了更好地理解,让我在此处放置您的定义gan部分代码
def define_gan(g_model, d_model)
# 使判别器中的权重不可训练
d_model.trainable = False
# 连接它们
model = Sequential()
# 添加生成器
model.add(g_model)
# 添加判别器
model.add(d_model)
# 编译模型
opt = Adam(lr=0.0002, beta_1=0.5)
model.compile(loss=’binary_crossentropy’, optimizer=opt)
return model
所以,在这一部分中,您说我们将冻结d_model的权重以防止更新,并让d_model认为g_model的输出是真实的(类1)。假设g_model生成一个图像,将其传递给d_model,d_model输出一个数字,例如0.6,但真实值应该是1.0,这会产生损失(也许我把这想得太简单了),但生成器模型到底是如何更新其权重以朝“真实”方向发展的,它如何知道什么是“真实”方向?
好问题。
它被更新以更好地欺骗判别器。
嗨!首先要感谢您制作了这个(以及许多其他关于机器学习的教程)!无论如何,我想知道在保存判别模型和生成模型之后,是否有办法同时生成新图像及其10类类别标签?我相信在您的示例中,只生成了图像而没有10类标签。(在您的生成伪样本函数中,y都是零)。再次非常感谢!
谢谢!
此模型不了解标签。您可能需要研究条件GAN模型。
或者您可以尝试在CIFAR数据集上使用预训练模型来对图像进行分类。
嗨,Jason,
感谢您的教程。
你能解释一下你是怎么得出
n_nodes = 256 * 4 * 4 的吗?
如何决定节点数?
此致
没有可靠的启发式方法来配置模型。我可能使用了试错法,或者从相关模型中复制了配置。
你好,
非常感谢您的教程!!
我尝试为CIFAR10数据集中的第一个类别,即飞机,实现一个GAN。我在每个时期之后显示生成的图像,运行大约100个时期后,生成的图像仍然是黑白的。我是否做错了什么,或者理解有误?
真心希望您能有时间回答这个问题。
GAN很难训练,也许可以尝试不同的架构或这里的一些建议
https://machinelearning.org.cn/how-to-code-generative-adversarial-network-hacks/
我在哪里可以找到generator_model_200.h5文件
你运行代码,它就被创建了。
我可以使用这个算法在小数据集上生成更多图像吗?我每个类别只有30个样本。所以我想找到一种方法来扩展我的数据集。
是的,不过如果您想扩展数据集,我强烈推荐使用数据增强。
https://machinelearning.org.cn/how-to-configure-image-data-augmentation-when-training-deep-learning-neural-networks/
这是最好的GAN教程!非常感谢!说实话,感觉我通过这个教程从零基础变成了高手!
谢谢!
为你的进步喝彩!
我有一个问题。根据本教程,我们事先在随机噪声数据上训练了判别器模型,然后使用该模型将生成器模型分类为伪造,但我的问题是,在生成器模型也同时学习的情况下,用生成器生成的伪造输出进行即时训练判别器模型不是更有利吗?这样判别器模型将很难区分伪造图像和真实图像,从而两者都变得更好,从而获得更好的结果,或者我的理论有缺陷吗?
谢谢你
不,我们不对随机噪声训练模型,我们使用随机噪声作为输入来合成新图像。
您可以在此处了解有关GAN如何工作的一般信息
https://machinelearning.org.cn/start-here/#gans
好的,现在我明白了,我的理解中存在一些空白。
谢谢你 🙂
我很高兴听到这个消息,不客气。
Jason,很棒的教程!有一个问题
问:在训练函数中,我们已经传递了gan_model,因此模型不会改变。在gan_model中,我们有未训练的初始判别器函数,并且根据define_gan()不可训练。如果gan模型已经传递并锁定在判别器上,那么训练函数中的判别器训练将如何改变判别器函数的权重?
因此,我们不应该先训练判别器,然后再定义gan模型来完全训练它吗?
不,它们是同时训练的。这就是GANs的全部思想。
https://machinelearning.org.cn/what-are-generative-adversarial-networks-gans/
生成器通过复合模型中的判别器进行训练
https://machinelearning.org.cn/how-to-code-the-generative-adversarial-network-training-algorithm-and-loss-functions/
所以当判别器在该批次中训练时,它确实更新了gan_model,但当gan模型训练时,判别器保持不变。我这样理解正确吗?
随着训练的进行,用于生成伪图像的生成器也会变得更好吗?
是的。
是的。
所以随着GAN的训练进行,生成器将更好地创建图像。因此,当我们把生成器生成的伪样本输入到判别器时,它们将非常接近真实图像。如果我们用0来标记它们,那么判别器将被训练来检测这些样本为伪样本,这与我们想要的相反。它将被训练来将生成器生成的这些“类似真实图像”分类为伪样本。
一般来说是的,尽管我们并不真正关心判别器最终的表现如何,只关心它是否对生成器构成了很好的挑战。
我们丢弃判别器并保留生成器。
你也能上传训练好的模型吗,因为训练需要GPU资源,我目前没有。
抱歉,我不上传/分享最终模型。我专注于教读者如何构建自己的模型。
您可以在这里获得便宜的GPU
https://machinelearning.org.cn/develop-evaluate-large-deep-learning-models-keras-amazon-web-services/
你好,Jason。
我按照教程进行了操作并实现了代码,但我的d_loss_1和d_loss_2在第一次训练批次后变得非常小(大约0.0023),我的gan_loss也是如此(大约0.002)。这没有产生预期的结果。我检查了整个代码10次,没有发现任何差异或不一致。您能帮我一下吗?
或许可以尝试多运行几次这个例子?
也许可以尝试在长时间运行期间检查生成的图像,而不是观察损失?
感谢 Jason 的回复。我弄明白了。是批归一化层导致了问题。但我想知道它为什么会引起问题?使用批归一化,权重学习得更快,因此误差非常低,但我不太确定为什么生成的图像达不到预期效果。
很高兴听到这个消息。
GANs非常挑剔。
你好,我正在使用批量归一化层,无论我训练多久,我的所有图像都只是随机噪声?如果我删除批量归一化,它就能很好地工作。我搞不清楚为什么它在有批量归一化的情况下无法训练。非常感谢您的帮助。
也许批处理归一化层不适合该模型。可能就这么简单。
嗨,这个教程非常有用。我是这个领域的新手。我尝试运行它,但训练后没有出现任何图像。它只显示了伪造和真实的准确性。我想知道为什么?
我只是把 Epochs 改成了 10,因为在 CPU 上运行很耗时,我想看看输出结果。我只需要创建 20 张图片。
谢谢!
你必须从命令行运行示例,而不是从 notebook 或 IDE
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
我选择了这种架构来使用 WGAN 模型训练 CIFAR-10。我想知道为什么我无法生成好的图像?我只得到了一些嘈杂的图像。请看损失值。它应该相应地生成高质量的图像。
(50000, 32, 32, 3)
0 [判别器损失:8.213928] [生成器损失:0.000266]
1 [判别器损失:7.631218] [生成器损失:-0.009719]
2 [判别器损失:6.827905] [生成器损失:-0.043795]
3 [判别器损失:5.744817] [生成器损失:-0.128916]
4 [判别器损失:4.664616] [生成器损失:-0.302539]
100 [判别器损失:-34.882454] [生成器损失:-1.986118]
1016 [判别器损失:-13.983415] [生成器损失:-18.977585]
1170 [判别器损失:-9.365166] [生成器损失:-10.809624]
1171 [判别器损失:-7.520091] [生成器损失:-6.204955]
1172 [判别器损失:-8.415616] [生成器损失:-8.243237]
1195 [判别器损失:-9.166229] [生成器损失:-8.273212]
1196 [判别器损失:-8.356554] [生成器损失:-8.563138]
1231 [判别器损失:-8.578263] [生成器损失:-5.937617]
1232 [判别器损失:-9.183654] [生成器损失:-9.121166]
1352 [判别器损失:-8.893832] [生成器损失:-9.738533]
1353 [判别器损失:-9.843869] [生成器损失:-14.416454]
也许可以从本教程中的架构开始,并根据您的需求进行调整
https://machinelearning.org.cn/how-to-develop-a-generative-adversarial-network-for-a-cifar-10-small-object-photographs-from-scratch/
嘿
我正在尝试加载自己的数据集来生成图像。这是为了执行某种语音克隆应用程序,因此我的数据集包含一个标签和 3001 张图像。
当我尝试运行此模型时,它会报错
TypeError: 找不到必需参数 'mean' (pos 2)
在 #
def generate_latent_points(latent_dim, n_samples)
....
x_input = randn(latent_dim * n_samples)
.....
你能帮忙吗?谢谢!
没关系,现在可以用了。很抱歉打扰了。非常感谢这些教程。
没问题,很高兴听到这个消息。
听到这个消息我很难过。
也许可以从工作示例开始,然后慢慢将其调整为使用您自己的数据集。
嘿。感谢这篇精彩的文章。我这里有一个小疑问。为什么生成器中要进行下采样?
我们不在生成器中进行下采样,我们进行上采样。
嗨,布朗利,
您的代码帮助我清楚地理解了整体流程。我对此表示赞赏。但我有一个问题,希望您能给我答案。
我的问题是
1. 我训练了我的模型,保存了判别器和 GAN 的权重,对吗?现在我想加载模型,输入训练好的权重,并从我的输入图像生成图像。这怎么实现呢?因为在示例模型中,我们只是加载模型,提供权重,然后调用 model.predict(input)->。
您能解释一下如何在 GAN 或 DCGAN 中处理这个问题吗?
谢谢你
很高兴听到这个消息。
上面的教程向您展示了如何加载模型并生成图像。
嗨,这是一个非常有用的教程,谢谢!我想知道 Python 中是否有任何基准测试库可以用于训练和评估我的 GAN。我找到了许多其他概念的库,例如通过 cleverhans 生成对抗样本。但是我还没有找到任何与 GANs 特别相关的库?有什么想法吗?再次感谢您的帮助。
谢谢!
这可以帮助您评估 GAN 模型
https://machinelearning.org.cn/how-to-evaluate-generative-adversarial-networks/
为什么在“define_gan”中你说“d_model.trainable = false”?是因为我们已经训练过它了吗?如果我们不把它设为 false 会发生什么?
不是的,因为我们不想在更新 g 时更新 d 的权重。
考虑训练有素的判别器的性能作为生成器性能的客观衡量标准,而不是 DCGAN 的判别器的性能,这样不是更有意义吗?
不,它与高质量图像没有关联。
你可以在这里了解更多
https://machinelearning.org.cn/how-to-evaluate-generative-adversarial-networks/
我将得到一些图像,如果它们是输入大小,它将自动提供一个额外的类似图像,例如,如果我提供 10 张图片,输出将显示 11 张图片。
我该如何挽救它??
抱歉,我不明白。您能重新措辞或详细说明您的问题吗?
我将输入一些图像。我的模型将根据输入的图像自动给我一个图像输出。我如何使用深度学习实现这一点??
如果您想要图像作为输出,这听起来像是一个图像转换问题,也许您可以使用像 pix2pix 这样的 GAN
https://machinelearning.org.cn/a-gentle-introduction-to-pix2pix-generative-adversarial-network/
嗨,Jason,
感谢您抽出时间撰写本教程。
我可以问您是否使用多个 GPU 训练 GAN?我发现很难实现,因为 GAN 实际上由多个在不同函数中启动的模型组成。任何建议都将不胜感激。
此致,
汉克
我没有,抱歉。
您好,我想问一下在判别模型和生成模型的两个图中,输入密集层之前出现的随机数。它是一个随机值吗?它有什么意义吗?
我在绘制模型时收到了不同的值,但我不太明白。
非常感谢。
可能是我写教程时绘图生成代码中的一个 bug。现在似乎没有再发生过。
嗨,Jason,我想问一下,如果我想生成 256x256 维度的糖尿病视网膜病变眼底图像,并且我有 3 个类别要生成,它们的数量分别是 20、70 和 50。我想把它们平均成 140。这个 GAN 能生成吗?我应该设置什么参数?
你尝试了什么?
嗨,Jason,我在攻读博士期间和现在在工业界都使用您的书籍和教程。
我阅读了:https://machinelearning.org.cn/how-to-develop-a-generative-adversarial-network-for-an-mnist-handwritten-digits-from-scratch-in-keras/
来生成黑白图像。
当前的讲座是关于 CIFAR 彩色图像的。我们需要对黑白图像和彩色图像进行哪些代码更改。
最简单的区别是通道数。在黑白图像中,我们假设输入/输出是一个浮点网格(例如,0 到 1),但对于彩色图像,您需要三个浮点网格用于 RGB 通道。
嗨,Jason,
在上面的示例中,您使用了 X = (X – 127.5) / 127.5。但在
https://machinelearning.org.cn/how-to-develop-a-generative-adversarial-network-for-an-mnist-handwritten-digits-from-scratch-in-keras/
# 加载并准备 MNIST 训练图像
def load_real_samples()
# 加载 MNIST 数据集
(trainX, _), (_, _) = load_data()
# 扩展到 3D,例如添加通道维度
X = expand_dims(trainX, axis=-1)
# 将无符号整数转换为浮点数
X = X.astype(‘float32’)
# 从 [0,255] 缩放到 [0,1]
X = X / 255.0
return X
您已将其缩放为 0 到 1。
您能说明原因吗?
只是不同的归一化方式。训练后应该没有太大区别。
感谢您提供出色的文章和模型。
我在 Colab 中运行了模型,但收到消息
“警告:tensorflow:已编译加载的模型,但编译的指标尚未构建。在训练或评估模型之前,`model.compile metrics` 将为空。”
请指教。
这似乎不是一个致命错误。它只是一个警告。是哪一行引起的?
嗨,Adrian……以下讨论可能对特定错误有所帮助。
https://stackoverflow.com/questions/67970389/warningtensorflowcompiled-the-loaded-model-but-the-compiled-metrics-have-yet
在您的机器上建立本地 Python 环境也可能很有益。请参阅以下教程
https://machinelearning.org.cn/setup-python-environment-machine-learning-deep-learning-anaconda/
我试过了,但仍然显示相同的错误
警告:tensorflow:已编译加载的模型,但编译的指标尚未构建。
2/2 [==============================] - 0s 10ms/步 - 损失:0.4915 - 准确度:0.7344
2/2 [==============================] - 0s 10ms/步 - 损失:0.4705 - 准确度:0.8438
真实准确率:73.4375% 虚假准确率:84.375%
对此有什么建议吗
嗨,Visvas……这不是一个“错误”,而是一个警告。您的代码似乎正在正常执行。以下内容可能有助于澄清情况
https://stackoverflow.com/questions/67970389/warningtensorflowcompiled-the-loaded-model-but-the-compiled-metrics-have-yet
嗨,Jason,
早上好,
在运行“CIFAR-10 GAN 完整示例”之前,我们需要独立训练判别器吗,还是“CIFAR-10 GAN 完整示例”会处理好?
嗨,Jason,
感谢您精彩的教程。
请问我运行了您精确的代码,没有任何更改,但我收到了这个错误
—————————————————————————
TypeError Traceback (most recent call last)
in
20 dataset = load_real_samples()
21 # 拟合模型
—> 22 train_discriminator(model, dataset)
在 train_discriminator(model, dataset, n_iter, n_batch) 中
5 for i in range(n_iter)
6 # 获取随机选择的“真实”样本
—-> 7 X_real, y_real = generate_real_samples(dataset, half_batch)
8 # 更新判别器在真实样本上的权重
9 _, real_acc = model.train_on_batch(X_real, y_real)
在 generate_real_samples(dataset, n_samples) 中
2 def generate_real_samples(dataset, n_samples)
3 # 选择随机实例
—-> 4 ix = randint(0, dataset.shape[0], n_samples)
5 # 检索选定的图像
6 X = dataset[ix]
TypeError: randint() 接受 3 个位置参数,但给出了 4 个
嗨,Iffy……请提供以下详细信息,以便我们更好地协助您
1. 您是如何输入代码的……复制粘贴还是手动输入?
2. Python 环境是什么?Google Colab 还是 Anaconda 或其他什么?
我想制作一个 GAN 模型。但我有一个问题。我如何改变 GAN 输入向量(潜在空间)。我想制作不同调色板的图像。如果我选择红色调色板,我想要红色和相同颜色的输出。
嗨,fatih……以下内容可能对您有帮助
https://machinelearning.org.cn/introduction-to-progressive-growing-generative-adversarial-networks/
我的数据集有限(50 个样本),只有 CIFAR10 的 2 个类别(总共 100 张图像)。每个类别不能使用超过 50 个样本,我想生成特定 2 个类别的假图像,以扩展我的数据集。我该怎么做?谢谢。
嗨,Andrei……请详细说明您在模型中遇到的问题,以便我们更好地协助您。
如何在 GPU 上运行此代码??
我拥有所有库并已测试它们,它们运行良好。
嗨,Jitesh……以下资源可能对您有帮助
https://machinelearning.org.cn/develop-evaluate-large-deep-learning-models-keras-amazon-web-services/
此外,您可能想尝试使用 GPU 选项的 Google Colab
https://machinelearning.org.cn/google-colab-for-machine-learning-projects/