生成对抗网络(GAN)是一种用于训练深度卷积模型以生成合成图像的架构。
尽管效果显著,但默认的 GAN 无法控制所生成图像的类型。信息最大化生成对抗网络(简称 InfoGAN)是对 GAN 架构的扩展,它引入了由架构自动学习的控制变量,可以控制生成图像的属性,例如手写数字图像的样式、粗细和类型。
在本教程中,您将了解如何从头开始实现信息最大化生成对抗网络模型。
完成本教程后,您将了解:
- InfoGAN 的动机是解耦和控制生成图像中的属性。
- InfoGAN 包含添加控制变量以生成辅助模型,该模型通过互信息损失函数预测控制变量。
- 如何从头开始开发和训练 InfoGAN 模型,并使用控制变量来控制模型生成的数字。
通过我的新书《Python 生成对抗网络》开始您的项目,其中包括分步教程和所有示例的Python 源代码文件。
让我们开始吧。
- 2019 年 10 月更新:修正了互信息损失解释中的拼写错误。
- 2021 年 1 月更新:更新以使层冻结与批标准化一起工作。

如何在 Keras 中开发信息最大化生成对抗网络 (InfoGAN)
照片由 Irol Trasmonte 拍摄,部分权利保留。
教程概述
本教程分为四个部分;它们是
- 什么是信息最大化生成对抗网络
- 如何实现 InfoGAN 损失函数
- 如何为 MNIST 开发 InfoGAN
- 如何使用训练好的 InfoGAN 模型进行控制代码
什么是信息最大化生成对抗网络
生成对抗网络(简称 GAN)是一种用于训练生成模型(如生成合成图像的模型)的架构。
它涉及同时训练用于生成图像的生成器模型和学习将图像分类为真实(来自训练数据集)或虚假(生成)的判别器模型。这两种模型在一个零和博弈中竞争,使得训练过程的收敛涉及在生成器生成逼真图像的能力与判别器检测它们的能力之间找到平衡。
生成器模型以潜在空间中的一个随机点作为输入,通常是 50 到 100 个随机高斯变量。生成器通过训练为潜在空间中的点赋予独特的含义,并将点映射到特定的输出合成图像。这意味着,尽管潜在空间由生成器模型构建,但无法控制生成的图像。
GAN 公式使用简单的因子化连续输入噪声向量 z,同时不对生成器可能使用此噪声的方式施加任何限制。因此,噪声可能被生成器以高度纠缠的方式使用,导致 z 的各个维度不对应数据的语义特征。
— InfoGAN:通过信息最大化生成对抗网络学习可解释的表示,2016。
可以探索潜在空间并比较生成的图像,以尝试理解生成器模型已学习的映射。或者,可以通过类标签等条件来控制生成过程,以便按需创建特定类型的图像。这是条件生成对抗网络(CGAN 或 cGAN)的基础。
另一种方法是将控制变量作为输入与潜在空间中的点(噪声)一起提供给生成器。生成器可以被训练为使用控制变量来影响生成图像的特定属性。这就是信息最大化生成对抗网络(简称 InfoGAN)所采用的方法。
InfoGAN,是对生成对抗网络的一种信息论扩展,能够以完全无监督的方式学习解耦的表示。
— InfoGAN:通过信息最大化生成对抗网络学习可解释的表示,2016。
生成器在训练过程中学习到的结构化映射在某种程度上是随机的。尽管生成器模型学会了在潜在空间中空间上分离生成图像的属性,但无法进行控制。这些属性是纠缠的。InfoGAN 的动机是解耦生成图像的属性。
例如,在人脸生成的情况下,生成人脸的属性可以解耦和控制,例如脸型、发色、发型等。
例如,对于包含人脸的数据集,一个有用的解耦表示可能为以下每个属性分配一组单独的维度:面部表情、眼睛颜色、发型、是否存在眼镜以及相应的人的身份。
— InfoGAN:通过信息最大化生成对抗网络学习可解释的表示,2016。
控制变量与噪声一起作为输入提供给生成器,并通过互信息损失函数训练模型。
……我们提出了一种对生成对抗网络目标函数的简单修改,该修改可以鼓励它学习可解释且有意义的表示。我们通过最大化 GAN 的一小部分固定噪声变量与观测值之间的互信息来实现这一点,这相对来说很直接。
— InfoGAN:通过信息最大化生成对抗网络学习可解释的表示,2016。
互信息指的是通过了解另一个变量来了解一个变量的信息量。在这种情况下,我们感兴趣的是通过使用噪声和控制变量生成的图像来了解控制变量的信息。
在信息论中,X 和 Y 之间的互信息 I(X; Y) 衡量了从对随机变量 Y 的了解中获得的关于另一个随机变量 X 的“信息量”。
互信息 (MI) 的计算方式为:由生成器 (G) 从噪声 (z) 和控制变量 (c) 生成的图像(创建的图像)给定控制变量 (c) 的条件熵,减去控制变量 (c) 的边际熵;例如
- MI = Entropy(c) – Entropy(c ; G(z,c))
在实践中,计算真实互信息通常是不可行的,尽管论文中采用了简化方法,称为变分信息最大化,并且控制代码的熵保持恒定。有关互信息的更多信息,请参阅教程
通过互信息训练生成器是通过使用一种称为 Q 或辅助模型的新模型来实现的。新模型与判别器模型共享所有相同的权重,用于解释输入图像,但与预测图像是真实还是虚假的判别器模型不同,辅助模型预测了用于生成图像的控制代码。
这两种模型都用于更新生成器模型,首先是为了提高生成欺骗判别器模型的图像的可能性,其次是为了提高用于生成图像的控制代码与辅助模型对控制代码的预测之间的互信息。
结果是生成器模型通过互信息损失进行正则化,使得控制代码捕获生成图像的关键属性,反过来,这些属性可用于控制图像生成过程。
……每当我们有兴趣学习一个从给定输入 X 到保留原始输入信息的更高层表示 Y 的参数化映射时,就可以利用互信息。 […] 表明最大化互信息的任务本质上等同于训练一个自编码器来最小化重构误差。
— 理解互信息及其在 InfoGAN 中的应用, 2016。
想从零开始开发GAN吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
如何实现 InfoGAN 损失函数
一旦熟悉了模型的输入和输出,InfoGAN 的实现就相当直接。
可能唯一的障碍是互信息损失函数,尤其是如果您没有像大多数开发人员那样扎实的数学背景。
InfoGan 使用的控制变量主要有两种:分类变量和连续变量,连续变量可能有不同的数据分布,这会影响互损失的计算方式。可以根据变量类型计算并对所有控制变量求和互损失,这是 OpenAI 为 TensorFlow 发布的原版 InfoGAN 实现所采用的方法。
在 Keras 中,简化控制变量为分类变量和高斯或均匀连续变量可能更容易,并在辅助模型上为每种控制变量类型设置单独的输出。这样就可以使用不同的损失函数,大大简化了实现。
有关此部分建议的更多背景信息,请参阅“进一步阅读”部分中的论文和文章。
分类控制变量
分类变量可用于控制生成图像的类型或类别。
这被实现为一个独热编码向量。也就是说,如果类别有 10 个值,那么控制代码将是一个类别,例如 6,并且输入到生成器模型的分类控制向量将是一个包含所有零值的 10 个元素的向量,其中类别 6 的值为 1,例如 [0, 0, 0, 0, 0, 0, 1, 0, 0]。
我们在训练模型时不需要选择分类控制变量;相反,它们是随机生成的,例如,每个样本以均匀概率选择。
……在潜在代码 c ∼ Cat(K = 10, p = 0.1) 上进行均匀分类分布
— InfoGAN:通过信息最大化生成对抗网络学习可解释的表示,2016。
在辅助模型中,分类变量的输出层也将是独热编码向量,以匹配输入控制代码,并使用 softmax 激活函数。
对于分类潜在代码 ci,我们使用自然选择的 softmax 非线性来表示 Q(ci |x)。
— InfoGAN:通过信息最大化生成对抗网络学习可解释的表示,2016。
回想一下,互信息是通过控制变量的条件熵和辅助模型的输出减去输入变量提供的控制变量的熵来计算的。我们可以直接实现这一点,但没有必要。
控制变量的熵是一个常数,并且非常接近零;因此,我们可以从计算中将其删除。条件熵可以直接计算为控制变量输入与辅助模型输出之间的交叉熵。因此,可以使用分类交叉熵损失函数,就像我们在任何多类分类问题上所做的那样。
使用超参数 lambda 来缩放互信息损失函数,并设置为 1,因此可以忽略。
尽管 InfoGAN 引入了一个额外的超参数 λ,但它很容易调整,并且对于离散潜在代码,将其设置为 1 就足够了。
— InfoGAN:通过信息最大化生成对抗网络学习可解释的表示,2016。
连续控制变量
连续控制变量可用于控制图像的样式。
连续变量从均匀分布(例如 -1 到 1 之间)采样,并作为输入提供给生成器模型。
……连续代码可以捕获本质上连续的变化:c2, c3 ∼ Unif(−1, 1)
— InfoGAN:通过信息最大化生成对抗网络学习可解释的表示,2016。
辅助模型可以通过高斯分布实现连续控制变量的预测,其中输出层配置为具有一个节点(均值)和一个节点(高斯标准差),例如,每个连续控制变量需要两个输出。
对于连续潜在代码 cj,根据真实的后验 P(cj |x) 有更多选项。在我们的实验中,我们发现将 Q(cj |x) 简单地视为因子化高斯就足够了。
— InfoGAN:通过信息最大化生成对抗网络学习可解释的表示,2016。
输出均值的节点可以使用线性激活函数,而输出标准差的节点必须产生正值,因此可以使用 sigmoid 等激活函数来创建 0 到 1 之间的值。
对于连续潜在代码,我们通过对角高斯分布来参数化近似后验,并且识别网络输出其均值和标准差,其中标准差通过网络输出的指数变换进行参数化以确保正性。
— InfoGAN:通过信息最大化生成对抗网络学习可解释的表示,2016。
损失函数必须计算为高斯控制变量的互信息,这意味着它们必须在计算损失之前从均值和标准差重构。计算高斯分布变量的熵和条件熵可以直接实现,但不是必需的。相反,可以使用均方误差损失。
或者,可以将输出分布简化为每个控制变量的均匀分布,辅助模型中每个变量使用一个输出节点,并具有线性激活,模型可以使用均方误差损失函数。
如何为 MNIST 开发 InfoGAN
在本节中,我们将仔细研究生成器 (g)、判别器 (d) 和辅助模型 (q),以及如何在 Keras 中实现它们。
我们将开发一个适用于 MNIST 数据集的 InfoGAN 实现,这与 InfoGAN 论文中的做法相同。
该论文探讨了两个版本;第一个版本仅使用分类控制代码,并允许模型将一个分类变量映射到大约一个数字(尽管数字没有按分类变量排序)。

通过分类控制代码的值改变生成数字的示例。
取自 InfoGan 论文。
该论文还探讨了 InfoGAN 架构的一个版本,其中包含独热编码的分类变量 (c1) 和两个连续控制变量 (c2 和 c3)。
第一个连续变量被发现控制数字的旋转,第二个控制数字的粗细。

使用连续控制代码改变生成数字倾斜和粗细的示例。
取自 InfoGan 论文。
我们将重点关注使用具有 10 个值的分类控制变量的更简单情况,并鼓励模型学会让此变量控制生成的数字。您可能希望通过更改分类控制变量的基数或添加连续控制变量来扩展此示例。
用于在 MNIST 数据集上训练的 GAN 模型配置已在论文的附录中提供,并在下面转载。我们将使用列出的配置作为开发我们自己的生成器 (g)、判别器 (d) 和辅助 (q) 模型的一个起点。

InfoGAN 训练 MNIST 的生成器、判别器和辅助模型配置摘要。
取自 InfoGan 论文。
让我们从开发生成器模型作为深度卷积神经网络(例如 DCGAN)开始。
模型可以接受噪声向量 (z) 和控制向量 (c) 作为单独的输入,并将它们连接起来,然后将它们用作生成图像的基础。或者,可以将向量预先连接起来并提供给模型中的单个输入层。这两种方法是等效的,在本例中我们将使用后者来简化模型。
下面的 `define_generator()` 函数定义了生成器模型,并接受输入向量的大小作为参数。
一个全连接层接收输入向量并生成足够的激活量,以创建 512 个 7x7 的特征图,然后从中重塑激活。然后,这些激活通过具有 1x1 步长的常规卷积层,然后通过两个后续的上采样转置卷积层,其步长为 2x2,首先生成 14x14 的特征图,然后通过 tanh 激活函数生成所需的 1 通道 28x28 的特征图输出,像素值在 [-1,-1] 范围内。
良好的生成器配置启发式方法包括:随机高斯权重初始化、隐藏层使用ReLU 激活以及使用批标准化。
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 |
# 定义独立的生成器模型 def define_generator(gen_input_size): # 权重初始化 init = RandomNormal(stddev=0.02) # 图像生成器输入 in_lat = Input(shape=(gen_input_size,)) # 7x7 图像的基础 n_nodes = 512 * 7 * 7 gen = Dense(n_nodes, kernel_initializer=init)(in_lat) gen = Activation('relu')(gen) gen = BatchNormalization()(gen) gen = Reshape((7, 7, 512))(gen) # 正常 gen = Conv2D(128, (4,4), padding='same', kernel_initializer=init)(gen) gen = Activation('relu')(gen) gen = BatchNormalization()(gen) # 上采样到 14x14 gen = Conv2DTranspose(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen) gen = Activation('relu')(gen) gen = BatchNormalization()(gen) # 上采样到 28x28 gen = Conv2DTranspose(1, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen) # tanh 输出 out_layer = Activation('tanh')(gen) # 定义模型 model = Model(in_lat, out_layer) return model |
接下来,我们可以定义判别器和辅助模型。
判别器模型按照正常 GAN 的方式独立训练,用于真实和虚假的图像。生成器和辅助模型都不会被直接拟合;相反,它们作为复合模型的一部分被拟合。
判别器和辅助模型共享相同的输入和特征提取层,但输出层不同。因此,同时定义它们是有意义的。
同样,这个架构可以通过多种方式实现,但首先将判别器和辅助模型定义为独立的模型,可以让我们稍后通过函数式 API 将它们直接组合成一个更大的 GAN 模型。
下面的 `define_discriminator()` 函数定义了判别器和辅助模型,并接受分类变量的基数(例如,值的数量,如 10)作为输入。输入图像的形状也作为函数参数进行参数化,并设置为 MNIST 图像大小的默认值。
特征提取层包括两个下采样层,作为最佳实践,用于替代池化层。同样遵循 DCGAN 模型的最佳实践,我们使用 LeakyReLU 激活和批标准化。
判别器模型 (d) 有一个单一的输出节点,并通过 sigmoid 激活函数预测输入图像为真实的概率。该模型被编译,因为它将以独立方式使用,通过随机梯度下降的Adam 版本和最佳实践学习率和动量来优化二元交叉熵函数。
辅助模型 (q) 为分类变量的每个值有一个节点输出,并使用 softmax 激活函数。在特征提取层和输出层之间添加了一个全连接层,这与 InfoGAN 论文中的使用相同。该模型未被编译,因为它不是独立使用。
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 |
# 定义独立的判别器模型 def define_discriminator(n_cat, in_shape=(28,28,1)): # 权重初始化 init = RandomNormal(stddev=0.02) # 图像输入 in_image = Input(shape=in_shape) # 下采样到 14x14 d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(in_image) d = LeakyReLU(alpha=0.1)(d) # 下采样到 7x7 d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d) d = LeakyReLU(alpha=0.1)(d) d = BatchNormalization()(d) # 正常 d = Conv2D(256, (4,4), padding='same', kernel_initializer=init)(d) d = LeakyReLU(alpha=0.1)(d) d = BatchNormalization()(d) # 展平特征图 d = Flatten()(d) # 真实/虚假输出 out_classifier = Dense(1, activation='sigmoid')(d) # 定义 d 模型 d_model = Model(in_image, out_classifier) # 编译 d 模型 d_model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.0002, beta_1=0.5)) # 创建 q 模型层 q = Dense(128)(d) q = BatchNormalization()(q) q = LeakyReLU(alpha=0.1)(q) # q 模型输出 out_codes = Dense(n_cat, activation='softmax')(q) # 定义 q 模型 q_model = Model(in_image, out_codes) return d_model, q_model |
接下来,我们可以定义复合 GAN 模型。
此模型使用了所有子模型,并且是训练生成器模型权重的基石。
下面的 `define_gan()` 函数实现了这一点,它接收三个子模型作为输入,然后定义并返回该模型。
如前所述,判别器以独立方式进行训练,因此判别器的所有权重都被设置为不可训练(仅在此上下文中)。生成器模型的输出连接到判别器模型的输入,以及辅助模型的输入。
这将创建一个新的复合模型,该模型以 [噪声 + 控制] 向量作为输入,然后通过生成器生成图像。图像随后通过判别器模型进行分类,并通过辅助模型预测控制变量。
该模型有两个输出层,需要使用不同的损失函数进行训练。判别器输出使用二元交叉熵损失,正如我们在为独立使用编译判别器时所做的那样;辅助模型使用互信息损失,在本例中,可以直接实现为分类交叉熵,并达到预期结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 定义组合的判别器、生成器和 q 网络模型 def define_gan(g_model, d_model, q_model): # 使判别器(部分与 q 模型共享)中的权重不可训练 for layer in d_model.layers: if not isinstance(layer, BatchNormalization): layer.trainable = False # 连接 g 的输出到 d 的输入 d_output = d_model(g_model.output) # 连接 g 的输出到 q 的输入 q_output = q_model(g_model.output) # 定义复合模型 model = Model(g_model.input, [d_output, q_output]) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss=['binary_crossentropy', 'categorical_crossentropy'], optimizer=opt) return model |
为了使 GAN 模型架构更清晰,我们可以创建模型和复合模型的图。
完整的示例如下所示。
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 |
# 为 mnist 创建并绘制 infogan 模型 from keras.optimizers import Adam from keras.models import Model from keras.layers import Input 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.层 导入 BatchNormalization from keras.layers import Activation from keras.initializers import RandomNormal from keras.utils.vis_utils import plot_model # 定义独立的判别器模型 def define_discriminator(n_cat, in_shape=(28,28,1)): # 权重初始化 init = RandomNormal(stddev=0.02) # 图像输入 in_image = Input(shape=in_shape) # 下采样到 14x14 d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(in_image) d = LeakyReLU(alpha=0.1)(d) # 下采样到 7x7 d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d) d = LeakyReLU(alpha=0.1)(d) d = BatchNormalization()(d) # 正常 d = Conv2D(256, (4,4), padding='same', kernel_initializer=init)(d) d = LeakyReLU(alpha=0.1)(d) d = BatchNormalization()(d) # 展平特征图 d = Flatten()(d) # 真实/虚假输出 out_classifier = Dense(1, activation='sigmoid')(d) # 定义 d 模型 d_model = Model(in_image, out_classifier) # 编译 d 模型 d_model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.0002, beta_1=0.5)) # 创建 q 模型层 q = Dense(128)(d) q = BatchNormalization()(q) q = LeakyReLU(alpha=0.1)(q) # q 模型输出 out_codes = Dense(n_cat, activation='softmax')(q) # 定义 q 模型 q_model = Model(in_image, out_codes) return d_model, q_model # 定义独立的生成器模型 def define_generator(gen_input_size): # 权重初始化 init = RandomNormal(stddev=0.02) # 图像生成器输入 in_lat = Input(shape=(gen_input_size,)) # 7x7 图像的基础 n_nodes = 512 * 7 * 7 gen = Dense(n_nodes, kernel_initializer=init)(in_lat) gen = Activation('relu')(gen) gen = BatchNormalization()(gen) gen = Reshape((7, 7, 512))(gen) # 正常 gen = Conv2D(128, (4,4), padding='same', kernel_initializer=init)(gen) gen = Activation('relu')(gen) gen = BatchNormalization()(gen) # 上采样到 14x14 gen = Conv2DTranspose(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen) gen = Activation('relu')(gen) gen = BatchNormalization()(gen) # 上采样到 28x28 gen = Conv2DTranspose(1, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen) # tanh 输出 out_layer = Activation('tanh')(gen) # 定义模型 model = Model(in_lat, out_layer) return model # 定义组合的判别器、生成器和 q 网络模型 def define_gan(g_model, d_model, q_model): # 使判别器(部分与 q 模型共享)中的权重不可训练 for layer in d_model.layers: if not isinstance(layer, BatchNormalization): layer.trainable = False # 连接 g 的输出到 d 的输入 d_output = d_model(g_model.output) # 连接 g 的输出到 q 的输入 q_output = q_model(g_model.output) # 定义复合模型 model = Model(g_model.input, [d_output, q_output]) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss=['binary_crossentropy', 'categorical_crossentropy'], optimizer=opt) return model # 分类控制代码的值的数量 n_cat = 10 # 潜在空间的大小 latent_dim = 62 # 创建判别器 d_model, q_model = define_discriminator(n_cat) # 创建生成器 gen_input_size = latent_dim + n_cat g_model = define_generator(gen_input_size) # 创建GAN gan_model = define_gan(g_model, d_model, q_model) # 绘制模型 plot_model(gan_model, to_file='gan_plot.png', show_shapes=True, show_layer_names=True) |
运行示例会创建所有三个模型,然后创建复合 GAN 模型并保存模型架构的图。
注意:创建此图假定已安装 pydot 和 graphviz 库。如果这是一个问题,您可以注释掉 import 语句和 `plot_model()` 函数的调用。
该图显示了生成器模型的所有细节以及判别器和辅助模型的压缩描述。重要的是,请注意判别器的输出形状,它是用于预测图像真实性或伪造性的单个节点,以及用于辅助模型预测分类控制码的 10 个节点。
请记住,此复合模型仅用于更新生成器和辅助模型的模型权重,并且判别器模型中的所有权重将保持不可训练,即仅在更新独立判别器模型时更新。

用于训练生成器和辅助模型的复合 InfoGAN 模型的图
接下来,我们将为生成器开发输入。
每个输入将是一个由噪声和控制代码组成的向量。具体来说,是一个 [ 高斯随机数 ] 向量和一个一次热编码的随机选择的分类值。
下面的 `generate_latent_points()` 函数实现了这一点,它将潜在空间的大小、分类值的数量和要生成的样本数量作为参数。该函数返回连接的输入向量作为生成器模型的输入,以及独立的控制代码。在通过复合 GAN 模型更新生成器和辅助模型时,将需要独立的控制代码,特别是用于计算辅助模型的互信息损失。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 在潜在空间中生成点作为生成器的输入 def generate_latent_points(latent_dim, n_cat, n_samples): # 在潜在空间中生成点 z_latent = randn(latent_dim * n_samples) # 重塑为网络的输入批次 z_latent = z_latent.reshape(n_samples, latent_dim) # 生成分类代码 cat_codes = randint(0, n_cat, n_samples) # 独热编码 cat_codes = to_categorical(cat_codes, num_classes=n_cat) # 连接潜在点和控制代码 z_input = hstack((z_latent, cat_codes)) return [z_input, cat_codes] |
接下来,我们可以生成真实和虚假的样本。
可以加载 MNIST 数据集,将其转换为 3D 输入(通过为灰度图像添加额外的维度),并 将所有像素值缩放到 [-1,1] 范围,以匹配生成器模型的输出。这在下面的 `load_real_samples()` 函数中实现。
通过随机选择数据集的一个子集,我们可以检索训练判别器所需的真实样本批次。这在下面的 `generate_real_samples()` 函数中实现,该函数返回图像和类别标签 1,表示判别器它们是真实的图像。
判别器还需要通过生成器生成的虚假样本批次,使用 `generate_latent_points()` 函数的向量作为输入。下面的 `generate_fake_samples()` 函数实现了这一点,返回生成的图像以及类别标签 0,表示判别器它们是虚假的图像。
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 |
# 加载图像 def load_real_samples(): # 加载数据集 (trainX, _), (_, _) = load_data() # 扩展到3D,例如添加通道 X = expand_dims(trainX, axis=-1) # 从整数转换为浮点数 X = X.astype('float32') # 从 [0,255] 缩放到 [-1,1] X = (X - 127.5) / 127.5 print(X.shape) return X # 选择真实样本 def generate_real_samples(dataset, n_samples): # 选择随机实例 ix = randint(0, dataset.shape[0], n_samples) # 选择图像和标签 X = dataset[ix] # 生成类别标签 y = ones((n_samples, 1)) 返回 X, y # 使用生成器生成 n 个假示例,并带有类别标签 def generate_fake_samples(generator, latent_dim, n_cat, n_samples): # 生成潜在空间中的点和控制代码 z_input, _ = generate_latent_points(latent_dim, n_cat, n_samples) # 预测输出 images = generator.predict(z_input) # 创建类别标签 y = zeros((n_samples, 1)) return images, y |
接下来,我们需要跟踪生成图像的质量。
我们将定期使用生成器生成图像样本,并将生成器和复合模型保存到文件。然后,我们可以在训练结束时查看生成的图像,以选择最终的生成器模型,并加载模型开始使用它来生成图像。
下面的 `summarize_performance()` 函数实现了这一点,首先生成 100 张图像,将它们的像素值缩放回 [0,1] 范围,并将它们保存为 10x10 方形图像的图。
生成器和复合 GAN 模型也会被保存到文件,文件名基于训练迭代次数而唯一。
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 summarize_performance(step, g_model, gan_model, latent_dim, n_cat, n_samples=100): # 准备伪示例 X, _ = generate_fake_samples(g_model, latent_dim, n_cat, n_samples) # 将范围从[-1,1]缩放到[0,1] X = (X + 1) / 2.0 # 绘制图像 for i in range(100): # 定义子图 pyplot.subplot(10, 10, 1 + i) # 关闭轴线 pyplot.axis('off') # 绘制原始像素数据 pyplot.imshow(X[i, :, :, 0], cmap='gray_r') # 保存图到文件 filename1 = 'generated_plot_%04d.png' % (step+1) pyplot.savefig(filename1) pyplot.close() # 保存生成器模型 filename2 = 'model_%04d.h5' % (step+1) g_model.save(filename2) # 保存 gan 模型 filename3 = 'gan_model_%04d.h5' % (step+1) gan_model.save(filename3) print('>Saved: %s, %s, and %s' % (filename1, filename2, filename3)) |
最后,我们可以训练 InfoGAN。
这在下面的 `train()` 函数中实现,该函数接收定义的模型和配置作为参数,并运行训练过程。
模型训练 100 个 epoch,每个 batch 使用 64 个样本。MNIST 训练数据集有 60,000 张图像,因此一个 epoch 涉及 60,000/64,即 937 个 batches 或训练迭代。将其乘以 epoch 数量(100),意味着总共将有 93,700 次总训练迭代。
每次训练迭代包括首先用一半真实样本和一半虚假样本更新判别器,形成一个 batch 的权重更新,或每次迭代 64 个。接下来,复合 GAN 模型会根据一个 batch 的噪声和控制代码输入进行更新。每次训练迭代都会报告判别器在真实和虚假图像上的损失,以及生成器和辅助模型的损失。
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 |
# 训练生成器和判别器 def train(g_model, d_model, gan_model, dataset, latent_dim, n_cat, n_epochs=100, n_batch=64): # 计算每个训练 epoch 的批次数量 bat_per_epo = int(dataset.shape[0] / n_batch) # 计算训练迭代次数 n_steps = bat_per_epo * n_epochs # 计算半批样本的大小 half_batch = int(n_batch / 2) # 手动枚举 epoch for i in range(n_steps): # 获取随机选择的“真实”样本 X_real, y_real = generate_real_samples(dataset, half_batch) # 更新判别器和 q 模型的权重 d_loss1 = d_model.train_on_batch(X_real, y_real) # 生成“假”示例 X_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_cat, half_batch) # 更新判别器模型权重 d_loss2 = d_model.train_on_batch(X_fake, y_fake) # 准备潜在空间中的点作为生成器的输入 z_input, cat_codes = generate_latent_points(latent_dim, n_cat, n_batch) # 为伪样本创建反转标签 y_gan = ones((n_batch, 1)) # 通过 d 和 q 误差更新 g _,g_1,g_2 = gan_model.train_on_batch(z_input, [y_gan, cat_codes]) # 总结此批次的损失 print('>%d, d[%.3f,%.3f], g[%.3f] q[%.3f]' % (i+1, d_loss1, d_loss2, g_1, g_2)) # 每“个周期”评估模型性能 if (i+1) % (bat_per_epo * 10) == 0: summarize_performance(i, g_model, gan_model, latent_dim, n_cat) |
然后,我们可以配置并创建模型,然后运行训练过程。
我们将为单个分类变量使用 10 个值,以匹配 MNIST 数据集中的 10 个已知类别。我们将使用具有 64 个维度的潜在空间,以匹配 InfoGAN 论文,这意味着在此情况下,生成器模型的每个输入向量将是 64 个(随机高斯变量)+ 10 个(一次热编码的控制变量),即长度为 72 个元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 分类控制代码的值的数量 n_cat = 10 # 潜在空间的大小 latent_dim = 62 # 创建判别器 d_model, q_model = define_discriminator(n_cat) # 创建生成器 gen_input_size = latent_dim + n_cat g_model = define_generator(gen_input_size) # 创建GAN gan_model = define_gan(g_model, d_model, q_model) # 加载图像数据 dataset = load_real_samples() # 训练模型 train(g_model, d_model, gan_model, dataset, latent_dim, n_cat) |
总而言之,在 MNIST 数据集上使用单个分类变量训练 InfoGAN 模型的完整示例列在下面。
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 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# 在 mnist 上训练 infogan 的示例 from numpy import zeros from numpy import ones from numpy import expand_dims from numpy import hstack from numpy.random import randn from numpy.random import randint from keras.datasets.mnist import load_data from keras.optimizers import Adam from keras.initializers import RandomNormal from keras.utils import to_categorical from keras.models import Model from keras.layers import Input 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.层 导入 BatchNormalization from keras.layers import Activation from matplotlib import pyplot # 定义独立的判别器模型 def define_discriminator(n_cat, in_shape=(28,28,1)): # 权重初始化 init = RandomNormal(stddev=0.02) # 图像输入 in_image = Input(shape=in_shape) # 下采样到 14x14 d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(in_image) d = LeakyReLU(alpha=0.1)(d) # 下采样到 7x7 d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d) d = LeakyReLU(alpha=0.1)(d) d = BatchNormalization()(d) # 正常 d = Conv2D(256, (4,4), padding='same', kernel_initializer=init)(d) d = LeakyReLU(alpha=0.1)(d) d = BatchNormalization()(d) # 展平特征图 d = Flatten()(d) # 真实/虚假输出 out_classifier = Dense(1, activation='sigmoid')(d) # 定义 d 模型 d_model = Model(in_image, out_classifier) # 编译 d 模型 d_model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.0002, beta_1=0.5)) # 创建 q 模型层 q = Dense(128)(d) q = BatchNormalization()(q) q = LeakyReLU(alpha=0.1)(q) # q 模型输出 out_codes = Dense(n_cat, activation='softmax')(q) # 定义 q 模型 q_model = Model(in_image, out_codes) return d_model, q_model # 定义独立的生成器模型 def define_generator(gen_input_size): # 权重初始化 init = RandomNormal(stddev=0.02) # 图像生成器输入 in_lat = Input(shape=(gen_input_size,)) # 7x7 图像的基础 n_nodes = 512 * 7 * 7 gen = Dense(n_nodes, kernel_initializer=init)(in_lat) gen = Activation('relu')(gen) gen = BatchNormalization()(gen) gen = Reshape((7, 7, 512))(gen) # 正常 gen = Conv2D(128, (4,4), padding='same', kernel_initializer=init)(gen) gen = Activation('relu')(gen) gen = BatchNormalization()(gen) # 上采样到 14x14 gen = Conv2DTranspose(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen) gen = Activation('relu')(gen) gen = BatchNormalization()(gen) # 上采样到 28x28 gen = Conv2DTranspose(1, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen) # tanh 输出 out_layer = Activation('tanh')(gen) # 定义模型 model = Model(in_lat, out_layer) return model # 定义组合的判别器、生成器和 q 网络模型 def define_gan(g_model, d_model, q_model): # 使判别器(部分与 q 模型共享)中的权重不可训练 for layer in d_model.layers: if not isinstance(layer, BatchNormalization): layer.trainable = False # 连接 g 的输出到 d 的输入 d_output = d_model(g_model.output) # 连接 g 的输出到 q 的输入 q_output = q_model(g_model.output) # 定义复合模型 model = Model(g_model.input, [d_output, q_output]) # 编译模型 opt = Adam(lr=0.0002, beta_1=0.5) model.compile(loss=['binary_crossentropy', 'categorical_crossentropy'], optimizer=opt) return model # 加载图像 def load_real_samples(): # 加载数据集 (trainX, _), (_, _) = load_data() # 扩展到3D,例如添加通道 X = expand_dims(trainX, axis=-1) # 从整数转换为浮点数 X = X.astype('float32') # 从 [0,255] 缩放到 [-1,1] X = (X - 127.5) / 127.5 print(X.shape) return X # 选择真实样本 def generate_real_samples(dataset, n_samples): # 选择随机实例 ix = randint(0, dataset.shape[0], n_samples) # 选择图像和标签 X = dataset[ix] # 生成类别标签 y = ones((n_samples, 1)) 返回 X, y # 在潜在空间中生成点作为生成器的输入 def generate_latent_points(latent_dim, n_cat, n_samples): # 在潜在空间中生成点 z_latent = randn(latent_dim * n_samples) # 重塑为网络的输入批次 z_latent = z_latent.reshape(n_samples, latent_dim) # 生成分类代码 cat_codes = randint(0, n_cat, n_samples) # 独热编码 cat_codes = to_categorical(cat_codes, num_classes=n_cat) # 连接潜在点和控制代码 z_input = hstack((z_latent, cat_codes)) return [z_input, cat_codes] # 使用生成器生成 n 个假示例,并带有类别标签 def generate_fake_samples(generator, latent_dim, n_cat, n_samples): # 生成潜在空间中的点和控制代码 z_input, _ = generate_latent_points(latent_dim, n_cat, n_samples) # 预测输出 images = generator.predict(z_input) # 创建类别标签 y = zeros((n_samples, 1)) return images, y # 生成样本并保存为图,并保存模型 def summarize_performance(step, g_model, gan_model, latent_dim, n_cat, n_samples=100): # 准备伪示例 X, _ = generate_fake_samples(g_model, latent_dim, n_cat, n_samples) # 将范围从[-1,1]缩放到[0,1] X = (X + 1) / 2.0 # 绘制图像 for i in range(100): # 定义子图 pyplot.subplot(10, 10, 1 + i) # 关闭轴线 pyplot.axis('off') # 绘制原始像素数据 pyplot.imshow(X[i, :, :, 0], cmap='gray_r') # 保存图到文件 filename1 = 'generated_plot_%04d.png' % (step+1) pyplot.savefig(filename1) pyplot.close() # 保存生成器模型 filename2 = 'model_%04d.h5' % (step+1) g_model.save(filename2) # 保存 gan 模型 filename3 = 'gan_model_%04d.h5' % (step+1) gan_model.save(filename3) print('>Saved: %s, %s, and %s' % (filename1, filename2, filename3)) # 训练生成器和判别器 def train(g_model, d_model, gan_model, dataset, latent_dim, n_cat, n_epochs=100, n_batch=64): # 计算每个训练 epoch 的批次数量 bat_per_epo = int(dataset.shape[0] / n_batch) # 计算训练迭代次数 n_steps = bat_per_epo * n_epochs # 计算半批样本的大小 half_batch = int(n_batch / 2) # 手动枚举 epoch for i in range(n_steps): # 获取随机选择的“真实”样本 X_real, y_real = generate_real_samples(dataset, half_batch) # 更新判别器和 q 模型的权重 d_loss1 = d_model.train_on_batch(X_real, y_real) # 生成“假”示例 X_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_cat, half_batch) # 更新判别器模型权重 d_loss2 = d_model.train_on_batch(X_fake, y_fake) # 准备潜在空间中的点作为生成器的输入 z_input, cat_codes = generate_latent_points(latent_dim, n_cat, n_batch) # 为伪样本创建反转标签 y_gan = ones((n_batch, 1)) # 通过 d 和 q 误差更新 g _,g_1,g_2 = gan_model.train_on_batch(z_input, [y_gan, cat_codes]) # 总结此批次的损失 print('>%d, d[%.3f,%.3f], g[%.3f] q[%.3f]' % (i+1, d_loss1, d_loss2, g_1, g_2)) # 每“个周期”评估模型性能 if (i+1) % (bat_per_epo * 10) == 0: summarize_performance(i, g_model, gan_model, latent_dim, n_cat) # 分类控制代码的值的数量 n_cat = 10 # 潜在空间的大小 latent_dim = 62 # 创建判别器 d_model, q_model = define_discriminator(n_cat) # 创建生成器 gen_input_size = latent_dim + n_cat g_model = define_generator(gen_input_size) # 创建GAN gan_model = define_gan(g_model, d_model, q_model) # 加载图像数据 dataset = load_real_samples() # 训练模型 train(g_model, d_model, gan_model, dataset, latent_dim, n_cat) |
运行示例可能需要一些时间,建议使用GPU硬件,但并非必需。
注意:由于算法或评估程序的随机性质,或者数值精度的差异,您的 结果可能会有所不同。考虑运行该示例几次并比较平均结果。
每次训练迭代都会报告模型之间的损失。如果判别器的损失保持在 0.0 或长时间停留在 0.0,这可能是一个训练失败的迹象,您可能需要重新启动训练过程。判别器损失可能从 0.0 开始,但很可能会上升,正如在本例中那样。
辅助模型的损失很可能会趋于零,因为它完美地预测了分类变量。生成器和判别器模型的损失最终可能会在 1.0 附近徘徊,以展示稳定的训练过程或两个模型训练之间的平衡。
1 2 3 4 5 6 7 8 9 10 11 12 |
>1, d[0.924,0.758], g[0.448] q[2.909] >2, d[0.000,2.699], g[0.547] q[2.704] >3, d[0.000,1.557], g[1.175] q[2.820] >4, d[0.000,0.941], g[1.466] q[2.813] >5, d[0.000,1.013], g[1.908] q[2.715] ... >93696, d[0.814,1.212], g[1.283] q[0.000] >93697, d[1.063,0.920], g[1.132] q[0.000] >93698, d[0.999,1.188], g[1.128] q[0.000] >93699, d[0.935,0.985], g[1.229] q[0.000] >93700, d[0.968,1.016], g[1.200] q[0.001] >Saved: generated_plot_93700.png, model_93700.h5, and gan_model_93700.h5 |
图和模型每 10 个 epoch 或每 9,370 次训练迭代保存一次。
查看图表应显示早期 epoch 的图像质量较差,后期 epoch 的图像质量有所提高且稳定。
例如,下面显示了前 10 个 epoch 后保存的图像图,显示了低质量的生成图像。

10 个训练 epoch 后 InfoGAN 生成的 100 张随机图像的图
更多的 epoch 并不意味着更好的质量,这意味着最高质量的图像可能不是来自训练结束时保存的最终模型。
查看图表并选择具有最佳图像质量的最终模型。在此案例中,我们将使用在 100 个 epoch 或 93,700 次训练迭代后保存的模型。

100 个训练 epoch 后 InfoGAN 生成的 100 张随机图像的图
如何使用训练好的 InfoGAN 模型进行控制代码
现在我们已经训练了 InfoGAN 模型,我们可以探索如何使用它。
首先,我们可以加载模型并像在训练期间那样使用它来生成随机图像。
完整的示例如下所示。
将模型文件名更改为与您训练期间生成最佳图像的模型文件名匹配。
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 |
# 加载生成器模型并生成图像的示例 from math import sqrt from numpy import hstack from numpy.random import randn from numpy.random import randint from keras.models import load_model from keras.utils import to_categorical from matplotlib import pyplot # 在潜在空间中生成点作为生成器的输入 def generate_latent_points(latent_dim, n_cat, n_samples): # 在潜在空间中生成点 z_latent = randn(latent_dim * n_samples) # 重塑为网络的输入批次 z_latent = z_latent.reshape(n_samples, latent_dim) # 生成分类代码 cat_codes = randint(0, n_cat, n_samples) # 独热编码 cat_codes = to_categorical(cat_codes, num_classes=n_cat) # 连接潜在点和控制代码 z_input = hstack((z_latent, cat_codes)) return [z_input, cat_codes] # 创建生成图像的图 def create_plot(examples, n_examples): # 绘制图像 for i in range(n_examples): # 定义子图 pyplot.subplot(sqrt(n_examples), sqrt(n_examples), 1 + i) # 关闭轴线 pyplot.axis('off') # 绘制原始像素数据 pyplot.imshow(examples[i, :, :, 0], cmap='gray_r') pyplot.show() # 加载模型 model = load_model('model_93700.h5') # 分类控制代码的值的数量 n_cat = 10 # 潜在空间的大小 latent_dim = 62 # 要生成的示例数量 n_samples = 100 # 在潜在空间和控制代码中生成点 z_input, _ = generate_latent_points(latent_dim, n_cat, n_samples) # 预测输出 X = model.predict(z_input) # 将范围从[-1,1]缩放到[0,1] X = (X + 1) / 2.0 # 绘制结果 create_plot(X, n_samples) |
运行此示例将加载已保存的生成器模型,并使用它生成 100 张随机图像,并将这些图像绘制在 10×10 的网格上。

加载已保存的 InfoGAN 生成器模型创建的 100 张随机图像图
接下来,我们可以更新示例,测试我们的控制变量能提供多大的控制力。
我们可以更新 `generate_latent_points()` 函数,使其接受一个类别值参数(范围在 [0,9] 之间),对其进行编码,并将其与噪声向量一起用作输入。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 在潜在空间中生成点作为生成器的输入 def generate_latent_points(latent_dim, n_cat, n_samples, digit): # 在潜在空间中生成点 z_latent = randn(latent_dim * n_samples) # 重塑为网络的输入批次 z_latent = z_latent.reshape(n_samples, latent_dim) # 定义类别编码 cat_codes = asarray([digit for _ in range(n_samples)]) # 独热编码 cat_codes = to_categorical(cat_codes, num_classes=n_cat) # 连接潜在点和控制代码 z_input = hstack((z_latent, cat_codes)) return [z_input, cat_codes] |
我们可以通过生成一个 25 张图像的网格,并将类别值设置为 1 来测试这一点。
完整的示例如下所示。
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 |
# 测试类别控制变量不同值的示例 from math import sqrt from numpy import asarray from numpy import hstack from numpy.random import randn from numpy.random import randint from keras.models import load_model from keras.utils import to_categorical from matplotlib import pyplot # 在潜在空间中生成点作为生成器的输入 def generate_latent_points(latent_dim, n_cat, n_samples, digit): # 在潜在空间中生成点 z_latent = randn(latent_dim * n_samples) # 重塑为网络的输入批次 z_latent = z_latent.reshape(n_samples, latent_dim) # 定义类别编码 cat_codes = asarray([digit for _ in range(n_samples)]) # 独热编码 cat_codes = to_categorical(cat_codes, num_classes=n_cat) # 连接潜在点和控制代码 z_input = hstack((z_latent, cat_codes)) return [z_input, cat_codes] # 创建并保存生成的图像图 def save_plot(examples, n_examples): # 绘制图像 for i in range(n_examples): # 定义子图 pyplot.subplot(sqrt(n_examples), sqrt(n_examples), 1 + i) # 关闭轴线 pyplot.axis('off') # 绘制原始像素数据 pyplot.imshow(examples[i, :, :, 0], cmap='gray_r') pyplot.show() # 加载模型 model = load_model('model_93700.h5') # 类别控制代码的数量 n_cat = 10 # 潜在空间的大小 latent_dim = 62 # 要生成的示例数量 n_samples = 25 # 定义数字 digit = 1 # 在潜在空间和控制代码中生成点 z_input, _ = generate_latent_points(latent_dim, n_cat, n_samples, digit) # 预测输出 X = model.predict(z_input) # 将范围从[-1,1]缩放到[0,1] X = (X + 1) / 2.0 # 绘制结果 save_plot(X, n_samples) |
结果是一个由 25 张生成的图像组成的网格,这些图像是在将类别代码设置为 1 的情况下生成的。
注意:由于算法或评估程序的随机性质,或者数值精度的差异,您的 结果可能会有所不同。考虑运行该示例几次并比较平均结果。
控制代码的值预计会影响生成的图像;特别是,它们预计会影响数字的类型。但是,它们不一定是排序的,例如,控制代码为 1、2 和 3 来创建这些数字。
尽管如此,在本例中,值为 1 的控制代码已生成看起来像 1 的图像。

InfoGAN 模型使用类别控制代码设置为 1 生成的 25 张图像图
尝试不同的数字,并回顾一下该值具体控制图像的哪个部分。
例如,在这种情况下将值设置为 5(digit = 5)会生成看起来像数字“*8*”的图像。

InfoGAN 模型使用类别控制代码设置为 5 生成的 25 张图像图
扩展
本节列出了一些您可能希望探索的扩展本教程的想法。
- 更改基数。更新示例以使用不同基数的类别控制变量(例如,更多或更少的值),并回顾其对训练过程和生成图像控制的影响。
- 均匀控制变量。更新示例并将两个均匀连续控制变量添加到辅助模型中,并回顾其对训练过程和生成图像控制的影响。
- 高斯控制变量。更新示例并将两个高斯连续控制变量添加到辅助模型中,并回顾其对训练过程和生成图像控制的影响。
如果您探索了这些扩展中的任何一个,我很想知道。
请在下面的评论中发布您的发现。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
论文
- InfoGAN:通过信息最大化生成对抗网络进行可解释表示学习, 2016.
- 理解互信息及其在 InfoGAN 中的应用, 2016.
- IM 算法:一种信息最大化的变分方法, 2004.
API
- Keras 数据集 API.
- Keras 序列模型 API
- Keras卷积层API
- 我如何“冻结”Keras层?
- MatplotLib API
- NumPy 随机抽样 (numpy.random) API
- NumPy 数组操作例程
项目
- InfoGAN (官方),OpenAI,GitHub.
- Keras Infogan:InfoGAN 的 Keras 实现,GitHub.
- Keras-GAN:生成对抗网络的 Keras 实现,GitHub.
- Advanced-Deep-Learning-with-Keras,PacktPublishing,GitHub.
- DeepLearningImplementations:近期深度学习论文的实现,GitHub.
文章
- 互信息,维基百科.
- 条件互信息,维基百科.
- InfoGAN:使用互信息的变分界(两次), 2016.
- GANs,互信息,以及可能的算法选择?, 2016.
- 实现 InfoGAN:比看起来容易?, 2016.
- 成本函数问题,DeepLearningImplementations 项目,GitHub.
总结
在本教程中,您将学习如何从头开始实现信息最大化生成对抗网络模型。
具体来说,你学到了:
- InfoGAN 的动机是解耦和控制生成图像中的属性。
- InfoGAN 包含添加控制变量以生成辅助模型,该模型通过互信息损失函数预测控制变量。
- 如何从头开始开发和训练 InfoGAN 模型,并使用控制变量来控制模型生成的数字。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
不错。这和 AC-GAN 一样对吧?(辅助分类 GAN)
我有这个实现:https://raw.githubusercontent.com/rjpg/bftensor/master/Autoencoder/src/ac-gan2.py
为了控制要生成的类别,我将噪声(潜在)乘以类的编号。也称为“给噪声上色”。效果很好。
但您的实现非常优雅,干得好。
不,InfoGAN 与此处介绍的 AC-GAN 不同
https://machinelearning.org.cn/how-to-develop-an-auxiliary-classifier-gan-ac-gan-from-scratch-with-keras/
嗨,Jason,
我能得到一些关于如何实现连续控制变量的代码片段吗?
谢谢,
Darren
是的,我尝试了大约 10 分钟,效果很差。我建议使用论文中的方法,而不是我糟糕的方法。
尽管如此,为了完整起见,这里仍然提供。
如果您能找到实现此目标并获得良好结果的方法,请告诉我。
你好 Darren,
你成功了吗?
我正在尝试,并且我的模型与 Jason 在下面发布的模型类似……但无法使其正常工作。
我实际上正在尝试将连续变量实现到 ACGAN 中。
提前感谢,
Caterina
Jason,非常感谢您的工作。
是否可以构建 InfoGAN 以及一个分类器?我明白 InfoGAN 可以使用控制变量生成所需类别的图像,但同时,我想输入一个未见的 MNIST 数据,看看模型是否能正确地对它们进行分类。
谢谢
通常 GAN 是为图像合成设计的,而不是分类。
如果您需要一个分类模型,请参阅这篇帖子
https://machinelearning.org.cn/how-to-develop-a-convolutional-neural-network-from-scratch-for-mnist-handwritten-digit-classification/
是的,我使用了您的代码构建了一个 InfoGAN 模型。但现在我想做的是将一个未见的 MNIST 图像反向输入生成器,看看它在学习到的潜在空间中的位置。基本上,我希望可视化模型学习到的潜在空间,以便在新数据输入时,它首先被分类,然后在潜在空间中显示。这可能吗?
听起来很有趣。
抱歉,我没有这方面的例子,需要做一些实验/原型设计才能弄清楚。如果您有进展,请告诉我。
您能否保存模型的权重,构建一个“反向”模型,然后逐层加载权重?
David,神经网络是连续操作的应用,例如仿射变换 Wx + b 和激活函数。如果所有这些都是可逆的(权重矩阵 W 通常是),那么整个操作就是可逆的。
在此特定代码中,使用了“relu”激活函数,它不是可逆的,因为它有一半的时间将某些东西乘以 0。LeakyReLU 是可逆的,以及一些其他激活函数。
但是,您可以采用不同的路径,尝试构建一个自动编码器网络,其中解码器部分是您已经训练过的生成器函数。您可以通过冻结解码器并为输入和输出提供相同的图像来训练它,训练数据可以来自预训练的生成器网络。
结果应该是一个编码器+解码器的组合,当输入图像时,编码器会生成潜在变量,而这些潜在变量在输入到 GAN 生成器(解码器)时会生成相同的图像。
你好 David,我也在研究您提出的问题,您是否找到了对未见过的 MNIST 数据进行分类的方法?
试试这个
https://machinelearning.org.cn/how-to-develop-a-convolutional-neural-network-from-scratch-for-mnist-handwritten-digit-classification/
非常感谢您的回复。然而,我的疑虑与 David 上面的疑虑类似,即如何实现 InfoGAN 训练的分类器的无监督分类。到目前为止,我还没有找到满足要求的 InfoGAN 实现。
无论如何,我从您精彩的教程中学到了很多,非常感谢。
听起来您对“聚类”感兴趣,这是无监督的,而不是“分类”的,这是有监督的。
上面的示例介于两者之间,称为“半监督”,它进行一些聚类和一些分类。
Jason,感谢您的文章。
但实际上,无论运行 100 个 epoch 还是 1000 个 epoch,我生成的图像都不好。
我认为是 q_net 的训练过程导致了这个问题。
也许我们可以尝试将 q_net 的独立部分从 Discriminator 中分离出来,然后设置共享部分 q_model.trainable=False。这样可以生成出色的图像。
说得有理。
你好 lei!
我有点困惑。你的意思是
– 像本教程中那样定义 d_net 和 q_net。
– 使 q_net 的 d_net 部分不可训练。
– 使用真实数据(带有相应标签的真实图像)训练 q_net。
我的理解正确吗?
提前感谢你
Jason,感谢 InfoGAN 代码。
info gen 的损失函数如下:
min max V(D, G)= V (D, G) − λI(c; G(z, c))
G D
您考虑了 λ=1,但是 q_model 上的“交叉熵损失”如何等于 c 和 G(z,c) 之间的互信息????
我相信我们在“如何实现 InfoGAN 损失函数”部分对此进行了讨论。
是的,解释得很清楚。非常感谢。
不客气。
你好!很棒的教程。
请问,为什么您要将 Q 模型与 G 模型一起训练?
为什么不使用 CNN 作为 Q 模型,并像 D 模型一样单独训练它?
我的实现是为了匹配论文的意图。
我明白了,但您认为进行这些更改是否合理,并且可能会奏效?
我已经在尝试应用此模型,但我正在尝试检查我的逻辑和思维方式是否正确。
以下是我正在进行的更改列表
– 将 Q 模型更改为 CNN 模型。
– 单独训练 Q 模型。
谢谢你的帮助。
很难说,也许可以尝试一下看看。
当然,非常感谢!
不客气。
你好!
我用你在 Google Colab 上发布的模型进行了训练,我看到了这个
> 49943, d[0.000,0.000], g[0.000] q[0.000]
这是第40或50个 epoch,损失是0!?
除此之外,生成的图像也很糟糕。有什么原因吗?
抱歉,我的意思是,我在 Google Colab 上运行了您在这篇文章中分享的代码。
损失为零表明模式失败。
https://machinelearning.org.cn/practical-guide-to-gan-failure-modes/
也许可以尝试重新训练模型。
我尝试了几个改动,不知何故,删除批量归一化解决了我的问题。
我尝试将批量归一化添加到 vanilla DCGAN (用于 MNIST),结果也一样。
看起来批量归一化不是 GAN 的好选择,但我总是看到有人建议添加它。我感到很困惑。
您遇到过同样的情况吗?
干得好!
这确实取决于具体的数据和模型。对于简单的 dcgan,通常不需要批量归一化。
我同意。非常感谢您,先生!
不客气。
大家好!
经过一些实验,连续控制变量模型奏效了。
我使用了两个控制变量模型,就像您一样,我也删除了批量归一化。
但我注意到,带有额外变量(分类或连续)的模型更容易生成错误的数字。另外,两个连续变量具有厚度和宽度效果。不知何故,宽度较小的数字会变成“1”,所以也许他并没有生成错误的数字,很难说……
抱歉语言不通顺,我还是太激动了。
以下是保存模型的链接:https://drive.google.com/drive/folders/1Gv3fyAi8oypC3JhTdCmc6CxOIdqHJKeA?usp=sharing
这是我的 Colab 游乐场链接
https://colab.research.google.com/drive/13B4COwR-UktpQ7pTQntORri2jwn13BuQ?usp=sharing
此外,我将相同的概念应用到了 CelebFaces 数据集上,我选择了一个具有两个类别的分类变量(男性和女性),我认为模型会学习这些特征,而随机噪声会塑造面部的细节。不幸的是,这没有奏效,我每次都得到了两种类型的面孔,呈绿色,但不是我想要的,这是模型和结果
https://drive.google.com/drive/folders/1GMom1tIltHbph-Rr4Qdt1Vg9wal4rWJ3?usp=sharing
所以我的问题是,您会如何制作一个用于人脸的人脸 InfoGAN,您会使用哪些变量?
谢谢你。
你的实验做得很好!
是的,像 InfoGAN 这样的条件 GAN 是生成男性/女性面孔的不错方法。可以使用分类变量,并且生成器必须足够好/大才能生成逼真的面孔。
也许可以借鉴一个已经擅长生成人脸的模型中的生成器架构。
嗨,Jason,
如果我错了,请纠正我。
i) 在实现中,您是否生成了两个假批次,一个用于更新判别器,一个用于更新辅助网络?
ii) 在原始 InfoGAN 实现中,似乎判别器在更新辅助网络时没有被冻结?
谢谢!
判别器使用真实和虚假图像进行更新。
判别器权重未被冻结,只有在更新生成器时才不进行更新——例如,对于 GAN 更新来说是典型的。
如果您不熟悉 GAN 训练实现,这可能会有所帮助。
https://machinelearning.org.cn/how-to-code-the-generative-adversarial-network-training-algorithm-and-loss-functions/
谢谢你。
我知道 GAN 中的生成器和判别器是如何更新的。
i) 在您的代码中,您首先通过真实和虚假图像更新了判别器和 q 模型
# 获取随机选择的“真实”样本
X_real, y_real = generate_real_samples(dataset, half_batch)
# 更新判别器和 q 模型权重
d_loss1 = d_model.train_on_batch(X_real, y_real)
# 生成“虚假”示例
X_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_cat, n_con, half_batch)
# 更新判别器模型权重
d_loss2 = d_model.train_on_batch(X_fake, y_fake)
然后,您似乎使用另一批新生成的虚假数据更新了您的复合 GAN 模型(生成器和 q 模型)(如果我错了请纠正我)
z_input, cat_codes, con_codes = generate_latent_points(latent_dim, n_cat, n_con, n_batch)
# 为虚假样本创建反转标签
y_gan = ones((n_batch, 1))
# 通过 d 和 q 误差更新 g
_,g_1,g_2,g3 = gan_model.train_on_batch(z_input, [y_gan, cat_codes, con_codes])
ii) 我不熟悉 Keras,所以我不知道当您使判别器参数不可训练时,与 Q 模型共享的层是否也变得不可训练(换句话说,判别器和 Q 模型是否保留了共享层的单独副本)。我认为原始 InfoGAN 的意图是共享层可由 D 和 Q 更新(只有一份副本被 D 和 Q 访问和更新)。当您更新生成器(肯定会固定判别器)并更新 Q 模型时,会发生两种情况
i. 如果我猜对了,共享层在更新 Q 模型时不会被更新,那么这与原始论文的意图不符。
ii. 如果我猜错了,共享层在更新 Q 模型时实际上会被更新,那么您的实现是正确的。
请告知,谢谢!
祝好
组合模型是我们通过判别器模型更新 GAN 模型的方式。在这种情况下,判别器保持不变——共享层不会被更新。
你可以在这里了解更多信息
https://machinelearning.org.cn/how-to-code-the-generative-adversarial-network-training-algorithm-and-loss-functions/
还有这里
https://keras.org.cn/getting_started/faq/#how-can-i-freeze-layers-and-do-finetuning
谢谢你,Jason。
如果共享层未被更新,那么我担心这并非原始实现的目的:https://github.com/openai/InfoGAN/blob/master/infogan/algos/infogan_trainer.py
我相信实现是正确的,也许我们彼此误解了。
为了更清楚地说明我的观点:共享层应该从 Q 模型的角度进行更新(当您更新复合 GAN 模型时),即使判别器被固定。
此教程提供的代码使用了哪个版本的 TensorFlow?我无法在 2.8 或 2.6 版本中使其正常工作。
你好 Daniel……你遇到了什么错误信息或问题?
我最终在 TensorFlow 2.5.0 版本中成功了。现在我正在尝试添加更多的控制变量——例如宽度/角度的变量。有什么资源/技巧可以参考吗?
你好 Daniel……以下内容可能对您有帮助
https://theailearner.com/tag/control-variables-gan/