Python 与 TensorFlow2 生成式 AI(二)(2)https://developer.aliyun.com/article/1512035
要允许额外的输出,我们在输出中包括了第三个变量,如果我们将 IAF 选项设置为True
,它将被设置为输入的线性变换,如果为False
,则为none
,因此我们可以在具有和不具有 IAF 的网络中使用 GaussianMLP 作为编码器。
现在我们已经定义了我们的两个子网络,让我们看看如何使用它们来构建一个完整的 VAE 网络。像子网络一样,我们可以使用 Keras API 定义 VAE:
class VAE(tf.keras.Model): def __init__(self, input_shape, name='variational_autoencoder', latent_dim=10, hidden_dim=10, encoder='GaussianMLP', decoder='BernoulliMLP', iaf_model=None, number_iaf_networks=0, iaf_params={}, num_samples=100, **kwargs): super().__init__(name=name, **kwargs) self._latent_dim = latent_dim self._num_samples = num_samples self._iaf = [] if encoder == 'GaussianMLP': self._encoder = GaussianMLP(input_shape=input_shape, latent_dim=latent_dim, iaf=(iaf_model is not None), hidden_dim=hidden_dim) else: raise ValueError("Unknown encoder type: {}".format(encoder)) if decoder == 'BernoulliMLP': self._decoder = BernoulliMLP(input_shape=(1,latent_dim), latent_dim=input_shape[1], hidden_dim=hidden_dim) elif decoder == 'GaussianMLP': self._encoder = GaussianMLP(input_shape=(1,latent_dim), latent_dim=input_shape[1], iaf=(iaf_model is not None), hidden_dim=hidden_dim) else: raise ValueError("Unknown decoder type: {}".format(decoder)) if iaf_model: self._iaf = [] for t in range(number_iaf_networks): self._iaf.append( iaf_model(input_shape==(1,latent_dim*2), **iaf_params))
如您所见,此模型被定义为包含编码器和解码器网络。此外,我们允许用户指定我们是否在模型中实现 IAF,如果是的话,我们需要一个由iaf_params
变量指定的自回归变换的堆栈。因为这个 IAF 网络需要将z和h作为输入,输入形状是latent_dim
(z)的两倍。我们允许解码器是 GaussianMLP 或 BernoulliMLP 网络,而编码器是 GaussianMLP。
此模型类还有一些其他函数需要讨论;首先是 VAE 模型类的编码和解码函数:
def encode(self, x): return self._encoder.call(x) def decode(self, z, apply_sigmoid=False): logits, _, _ = self._decoder.call(z) if apply_sigmoid: probs = tf.sigmoid(logits) return probs return logits
对于编码器,我们只需调用(运行前向传递)编码器网络。解码时,您会注意到我们指定了三个输出。介绍了 VAE 模型的文章《自编码变分贝叶斯》提供了解码器的例子,指定为高斯多层感知器(MLP)或 Benoulli 输出。如果我们使用了高斯 MLP,解码器将为输出值、均值和标准差向量,我们需要使用 sigmoidal 变换将该输出转换为概率(0 到 1)。在伯努利情况下,输出已经在 0 到 1 的范围内,我们不需要这种转换(apply_sigmoid
=False
)。
一旦我们训练好了 VAE 网络,我们就需要使用抽样来产生随机潜在向量(z)并运行解码器来生成新图像。虽然我们可以将其作为 Python 运行时类的正常函数运行,但我们将使用@tf.function
注释装饰这个函数,这样它就可以在 TensorFlow 图形运行时执行(就像任何 tf 函数一样,比如 reduce_sum
和 multiply
),如果可用的话可以使用 GPU 和 TPU 等设备。我们从一个随机正态分布中抽取一个值,对于指定数量的样本,然后应用解码器来生成新图像:
@tf.function def sample(self, eps=None): if eps is None: eps = tf.random.normal(shape=(self._num_samples, self.latent_dim)) return self._decoder.call(eps, apply_sigmoid=False)
最后,回想一下,“重新参数化技巧”是用来使我们能够反向传播 z 的值,并减少 z 的似然的方差。我们需要实现这个变换,它的给定形式如下:
def reparameterize(self, mean, logvar): eps = tf.random.normal(shape=mean.shape) return eps * tf.exp(logvar * .5) + mean
在原始论文《Autoencoding Variational Bayes》中,给出了:
其中i是x中的一个数据点,l 是从随机分布中抽样的一个样本,这里是一个正常分布。在我们的代码中,我们乘以 0.5,因为我们在计算log variance(或标准差的平方),log(s²)=log(s)2,所以 0.5 取消了 2,给我们留下了exp(log(s))=s,正如我们在公式中需要的那样。
我们还将包括一个类属性(使用@property
装饰器),这样我们就可以访问归一化变换的数组,如果我们实现了 IAF:
@property def iaf(self): return self._iaf
现在,我们将需要一些额外的函数来实际运行我们的 VAE 算法。第一个计算 lognormal 概率密度函数(pdf),用于计算变分下限或 ELBO:
def log_normal_pdf(sample, mean, logvar, raxis=1): log2pi = tf.math.log(2\. * np.pi) return tf.reduce_sum( -.5 * ((sample - mean) ** 2\. * tf.exp(-logvar) + \ logvar + log2pi), axis=raxis)
现在我们需要利用这个函数作为在训练 VAE 的过程中每个小批量梯度下降传递的一部分来计算损失。和样本方法一样,我们将使用@tf.function
注释装饰这个函数,以便它在图形运行时执行:
@tf.function def compute_loss(model, x): mean, logvar, h = model.encode(x) z = model.reparameterize(mean, logvar) logqz_x = log_normal_pdf(z, mean, logvar) for iaf_model in model.iaf: mean, logvar, _ = iaf_model.call(tf.concat([z, h], 2)) s = tf.sigmoid(logvar) z = tf.add(tf.math.multiply(z,s), tf.math.multiply(mean,(1-s))) logqz_x -= tf.reduce_sum(tf.math.log(s)) x_logit = model.decode(z) cross_ent = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x) logpx_z = -tf.reduce_sum(cross_ent, axis=[2]) logpz = log_normal_pdf(z, 0., 0.) return -tf.reduce_mean(logpx_z + logpz - logqz_x)
让我们来解开这里发生的一些事情。首先,我们可以看到我们在输入上调用编码器网络(在我们的情况下是扁平图像的小批量),生成所需的均值、logvariance
,以及如果我们在网络中使用 IAF,我们将在归一化流变换的每一步传递的辅助输入h
。
我们对输入应用“重新参数化技巧”,以生成潜在向量 z
,并应用对得到的* logq*(z|x)的 lognormal pdf。
如果我们使用 IAF,我们需要通过每个网络迭代地变换z
,并在每一步从解码器传入h
(辅助输入)。然后我们将这个变换的损失应用到我们计算的初始损失上,就像在 IAF 论文中给出的算法中一样:¹⁴
一旦我们有了转换或未转换的z,我们使用解码器网络解码它,得到重构数据x,然后我们计算交叉熵损失。我们对小批量求和,并计算在标准正态分布(先验)处评估的z的对数正态 pdf,然后计算期望的下界。
请记住,变分下界或 ELBO 的表达式是:
因此,我们的小批量估计器是这个值的样本:
现在我们有了这些要素,我们可以使用GradientTape
API 运行随机梯度下降,就像我们在第四章,教授网络生成数字中为 DBN 所做的那样,传入一个优化器、模型和数据的小批量(x):
@tf.function def compute_apply_gradients(model, x, optimizer): with tf.GradientTape() as tape: loss = compute_loss(model, x) gradients = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(gradients, model.trainable_variables))
要运行训练,首先我们需要指定一个使用我们构建的类的模型。如果我们不想使用 IAF,我们可以这样做:
model = VAE(input_shape=(1,3072), hidden_dim=500, latent_dim=500)
如果我们想要使用 IAF 变换,我们需要包含一些额外的参数:
model = VAE(input_shape=(1,3072), hidden_dim=500, latent_dim=500, iaf_model=GaussianMLP, number_iaf_networks=3, iaf_params={'latent_dim': 500, 'hidden_dim': 500, 'iaf': False})
创建好模型后,我们需要指定一定数量的 epochs,一个优化器(在这个例子中,是 Adam,正如我们在第三章,深度神经网络的构建基块中描述的那样)。我们将数据分成 32 个元素的小批量,并在每个小批量后应用梯度更新,更新的次数为我们指定的 epochs 数。定期输出 ELBO 的估计值以验证我们的模型是否在改善:
import time as time epochs = 100 optimizer = tf.keras.optimizers.Adam(1e-4) for epoch in range(1, epochs + 1): start_time = time.time() for train_x in cifar10_train.map( lambda x: flatten_image(x, label=False)).batch(32): compute_apply_gradients(model, train_x, optimizer) end_time = time.time() if epoch % 1 == 0: loss = tf.keras.metrics.Mean() for test_x in cifar10_test.map( lambda x: flatten_image(x, label=False)).batch(32): loss(compute_loss(model, test_x)) elbo = -loss.result() print('Epoch: {}, Test set ELBO: {}, ' 'time elapse for current epoch {}'.format(epoch, elbo, end_time - start_time))
我们可以通过查看更新来验证模型是否在改善,这些更新应该显示 ELBO 正在增加:
要检查模型的输出,我们可以首先查看重构误差;网络对输入图像的编码是否大致捕捉到了输入图像中的主要模式,从而使其能够从其向量z的编码中重构出来?我们可以将原始图像与通过编码器传递图像、应用 IAF,然后解码得到的重构进行比较:
for sample in cifar10_train.map(lambda x: flatten_image(x, label=False)).batch(1).take(10): mean, logvar, h = model.encode(sample) z = model.reparameterize(mean, logvar) for iaf_model in model.iaf: mean, logvar, _ = iaf_model.call(tf.concat([z, h], 2)) s = tf.sigmoid(logvar) z = tf.add(tf.math.multiply(z,s), tf.math.multiply(mean,(1-s))) plt.figure(0) plt.imshow((sample.numpy().reshape(32,32,3)).astype(np.float32), cmap=plt.get_cmap("gray")) plt.figure(1) plt.imshow((model.decode(z).numpy().reshape(32,32,3)).astype(np.float32), cmap=plt.get_cmap("gray"))
对于前几个 CIFAR-10 图像,我们得到以下输出,显示我们已经捕捉到了图像的总体模式(尽管它是模糊的,这是 VAE 的一个普遍缺点,我们将在将来章节中讨论的**生成对抗网络(GANs)**中解决):
图 5.10:CIFAR-10 图像的输出
如果我们想要创建全新的图像怎么办?在这里,我们可以使用我们之前在从 TensorFlow 2 创建网络中定义的“sample”函数,从随机生成的z向量而不是 CIFAR 图像的编码产品中创建新图像的批次:
plt.imshow((model.sample(10)).numpy().reshape(32,32,3)).astype(np.float32), cmap=plt.get_cmap("gray"))
此代码将生成类似于以下内容的输出,显示了从随机数向量生成的一组图像:
图 5.11:从随机数向量生成的图像
诚然,这些图像可能有些模糊,但您可以欣赏到它们显示的结构,并且看起来与您之前看到的一些“重建”CIFAR-10 图像相当。在这里的部分挑战,就像我们将在随后的章节中讨论的那样,是损失函数本身:交叉熵函数本质上对每个像素惩罚,以衡量其与输入像素的相似程度。虽然这在数学上可能是正确的,但它并不能捕捉到我们所认为的输入和重建图像之间的“相似性”的概念。例如,输入图像可能有一个像素设为无穷大,这将导致它与将该像素设为 0 的重建图像之间存在很大差异;然而,一个人观看这个图像时,会认为它们两者完全相同。GANs 所使用的目标函数,如第六章中描述的用 GAN 生成图像,更准确地捕捉到了这种微妙之处。
总结
在本章中,您看到了如何使用深度神经网络来创建复杂数据的表示,例如图像,捕捉到比传统的降维技术如 PCA 更多的变化。这是通过 MNIST 数字进行演示的,其中神经网络可以在二维网格上更干净地分离不同数字,而不像这些图像的主成分那样。本章展示了如何使用深度神经网络来近似复杂的后验分布,例如图像,使用变分方法从不可约分布的近似中进行采样,形成了一种基于最小化真实和近似后验之间的变分下界的 VAE 算法。
您还学会了如何重新参数化这个算法生成的潜在向量,以降低方差,从而使随机小批量梯度下降收敛更好。您还看到了这些模型中编码器生成的潜在向量通常是相互独立的,可以使用 IAF 将其转换为更真实的相关分布。最后,我们在 CIFAR-10 数据集上实现了这些模型,并展示了它们如何用于重建图像和从随机向量生成新图像。
下一章将介绍 GANs,并展示我们如何使用它们为输入图像添加风格滤镜,使用 StyleGAN 模型。
参考文献
- Eckersley P., Nasser Y. 衡量 AI 研究的进展。EFF。检索日期 2021 年 4 月 26 日,
www.eff.org/ai/metrics#Measuring-the-Progress-of-AI-Research
和 CIFAR-10 数据集,www.cs.toronto.edu/~kriz/
- Malhotra P. (2018). 自编码器实现。GitHub 仓库。
www.piyushmalhotra.in/Autoencoder-Implementations/VAE/
- Kingma, D P., Welling, M. (2014). 自动编码变分贝叶斯. arXiv:1312.6114.
arxiv.org/pdf/1312.6114.pdf
- Hinton G. E., Salakhutdinov R. R. (2006). 使用神经网络降低数据维度. ScienceMag.
www.cs.toronto.edu/~hinton/science.pdf
- Hinton G. E., Salakhutdinov R. R. (2006). 使用神经网络降低数据维度. ScienceMag.
www.cs.toronto.edu/~hinton/science.pdf
- Kingma, D P., Welling, M. (2014). 自动编码变分贝叶斯. arXiv:1312.6114.
arxiv.org/pdf/1312.6114.pdf
- Doersch, C. (2016). 变分自动编码器教程. arXiv:1606.05908.
arxiv.org/pdf/1606.05908.pdf
- Paisley, J., Blei, D., Jordan, M. (2012). 随机搜索的变分贝叶斯推断.
icml.cc/2012/papers/687.pdf
- Doersch, C. (2016). 变分自动编码器教程. arXiv:1606.05908.
arxiv.org/pdf/1606.05908.pdf
- Angelov, Plamen; Gegov, Alexander; Jayne, Chrisina; Shen, Qiang (2016-09-06). 计算智能系统的进展: 2016 年 9 月 7-9 日英国兰开斯特举办的第 16 届英国计算智能研讨会的贡献. Springer International Publishing. pp. 441–. ISBN 9783319465623. 检索于 2018 年 1 月 22 日。
- TinyImages: 麻省理工学院小图像
- Krizhevsky A. (2009). 从小图像中学习多层特征.
citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.222.9220&rep=rep1&type=pdf
- Kingma, D P., Welling, M. (2014). 自动编码变分贝叶斯. arXiv:1312.6114.
arxiv.org/pdf/1312.6114.pdf
- Kingma, D P., Salimans, T., Jozefowicz, R., Chen, X., Sutskever, I., Welling, M. (2016). 用逆自回归流改进变分推断. arXiv:1606.04934.
arxiv.org/pdf/1606.04934.pdf
第六章:使用 GAN 生成图像
生成建模是一个强大的概念,它为我们提供了巨大的潜力来逼近或建模生成数据的基本过程。在前几章中,我们涵盖了与深度学习一般以及更具体地与受限玻尔兹曼机(RBMs)和变分自编码器(VAEs)相关的概念。本章将介绍另一类生成模型家族,称为生成对抗网络(GANs)。
在受博弈理论概念的强烈启发并吸取了先前讨论的一些最佳组件的基础上,GANs 为在生成建模空间中工作提供了一个强大的框架。自从它们于 2014 年由 Goodfellow 等人发明以来,GANs 受益于巨大的研究,并且现在被用于探索艺术、时尚和摄影等创造性领域。
以下是 GANs 的一个变体(StyleGAN)的两个惊人高质量样本(图 6.1)。儿童的照片实际上是一个虚构的不存在的人物。艺术样本也是由类似的网络生成的。通过使用渐进增长的概念,StyleGANs 能够生成高质量清晰的图像(我们将在后面的部分中详细介绍)。这些输出是使用在数据集上训练的 StyleGAN2 模型生成的,如Flickr-Faces-HQ或FFHQ数据集。
图 6.1:GAN(StyleGAN2)想象出的图像(2019 年 12 月)- Karras 等人和 Nvidia²
本章将涵盖:
- 生成模型的分类
- 一些改进的 GAN,如 DCGAN、条件-GAN 等。
- 渐进式 GAN 设定及其各个组件
- 与 GANs 相关的一些挑战
- 实例操作
生成模型的分类
生成模型是无监督机器学习领域的一类模型。它们帮助我们对生成数据集的潜在分布进行建模。有不同的方法/框架可以用来处理生成模型。第一组方法对应于用显式密度函数表示数据的模型。在这里,我们明确定义一个概率密度函数,,并开发一个增加从这个分布中采样的最大似然的模型。
在显式密度方法中,还有两种进一步的类型,即可计算和近似密度方法。 PixelRNNs 是可计算密度方法的一个活跃研究领域。当我们试图对复杂的现实世界数据分布建模时,例如自然图像或语音信号,定义参数函数变得具有挑战性。为了克服这一点,你在第四章“教网络生成数字”和第五章“使用 VAEs 绘制图像”中分别学习了关于 RBMs 和 VAEs。这些技术通过明确地逼近概率密度函数来工作。VAEs 通过最大化下界的似然估计来工作,而 RBMs 则使用马尔可夫链来估计分布。生成模型的整体景观可以使用图 6.2来描述:
图 6.2:生成模型的分类³
GANs 属于隐式密度建模方法。隐式密度函数放弃了明确定义潜在分布的属性,但通过定义方法来从这些分布中抽样的方法来工作。 GAN 框架是一类可以直接从潜在分布中抽样的方法。这减轻了到目前为止我们所涵盖的方法的某些复杂性,例如定义底层概率分布函数和输出的质量。现在你已经对生成模型有了高层次的理解,让我们深入了解 GAN 的细节。
生成对抗网络
GANs 有一个非常有趣的起源故事。一切始于在酒吧里讨论/争论与伊恩·古德费洛和朋友们讨论使用神经网络生成数据相关的工作。争论以每个人都贬低彼此的方法而告终。古德费洛回家编写了现在我们称之为 GAN 的第一个版本的代码。令他惊讶的是,代码第一次尝试就成功了。古德费洛本人在接受《连线》杂志采访时分享了一份更详细的事件链描述。
如前所述,GANs 是隐式密度函数,直接从底层分布中抽样。它们通过定义一个双方对抗的两人游戏来实现这一点。对抗者在定义良好的奖励函数下相互竞争,每个玩家都试图最大化其奖励。不涉及博弈论的细节,该框架可以解释如下。
判别器模型
这个模型代表了一个可微分函数,试图最大化从训练分布中抽样得到样本的概率为 1。这可以是任何分类模型,但我们通常偏爱使用深度神经网络。这是一种一次性模型(类似于自动编码器的解码器部分)。
鉴别器也用于分类生成器输出是真实的还是假的。这个模型的主要作用是帮助开发一个强大的生成器。我们将鉴别器模型表示为D,其输出为D(x)。当它用于分类生成器模型的输出时,鉴别器模型被表示为D(G(z)),其中G(z)是生成器模型的输出。
图 6.3:鉴别器模型
生成器模型
这是整个游戏中主要关注的模型。这个模型生成样本,意图是与我们的训练集中的样本相似。该模型将随机的非结构化噪声作为输入(通常表示为z),并尝试创建各种输出。生成器模型通常是一个可微分函数;它通常由深度神经网络表示,但不局限于此。
我们将生成器表示为G,其输出为G(z)。通常相对于原始数据x的维度,我们使用一个较低维度的z,即。这是对真实世界信息进行压缩或编码的一种方式。
图 6.4:生成器模型
简单来说,生成器的训练是为了生成足够好的样本以欺骗鉴别器,而鉴别器的训练是为了正确地区分真实(训练样本)与假的(输出自生成器)。因此,这种对抗游戏使用一个生成器模型G,试图使D(G(z))尽可能接近 1。而鉴别器被激励让D(G(z))接近 0,其中 1 表示真实样本,0 表示假样本。当生成器开始轻松地欺骗鉴别器时,生成对抗网络模型达到均衡,即鉴别器达到鞍点。虽然从理论上讲,生成对抗网络相对于之前描述的其他方法有几个优点,但它们也存在自己的一系列问题。我们将在接下来的章节中讨论其中一些问题。
训练生成对抗网络
训练生成对抗网络就像玩这个两个对手的游戏。生成器正在学习生成足够好的假样本,而鉴别器正在努力区分真实和假的。更正式地说,这被称为极小极大游戏,其中价值函数V(G, D)描述如下:
这也被称为零和游戏,其均衡点与纳什均衡相同。我们可以通过分离每个玩家的目标函数来更好地理解价值函数V(G, D)。以下方程描述了各自的目标函数:
其中是传统意义上的鉴别器目标函数,是生成器目标等于鉴别器的负值,是训练数据的分布。其余项具有其通常的含义。这是定义博弈或相应目标函数的最简单方式之一。多年来,已经研究了不同的方式,其中一些我们将在本章中讨论。
目标函数帮助我们理解每个参与者的目标。如果我们假设两个概率密度在任何地方都非零,我们可以得到D(x)的最优值为:
我们将在本章后面重新讨论这个方程。现在,下一步是提出一个训练算法,其中鉴别器和生成器模型分别朝着各自的目标进行训练。训练 GAN 的最简单但也是最广泛使用的方法(迄今为止最成功的方法)如下。
重复以下步骤N次。N是总迭代次数:
- 重复k次步骤:
- 从生成器中抽取大小为m的小批量:{z[1], z[2], … z[m]} = p[model](z)
- 从实际数据中抽取大小为m的小批量:{x[1], x[2], … x[m]} = p[data](x)
- 更新鉴别器损失,
- 将鉴别器设为不可训练
- 从生成器中抽取大小为m的小批量:{z[1], z[2], … z[m]} = p[model](z)
- 更新生成器损失,
在他们的原始论文中,Goodfellow 等人使用了k=1,也就是说,他们交替训练鉴别器和生成器模型。有一些变种和技巧,观察到更频繁地训练鉴别器比生成器有助于更好地收敛。
下图(图 6.5)展示了生成器和鉴别器模型的训练阶段。较小的虚线是鉴别器模型,实线是生成器模型,较大的虚线是实际训练数据。底部的垂直线示意从z的分布中抽取数据点,即x=p[model](z)。线指出了生成器在高密度区域收缩而在低密度区域扩张的事实。部分**(a)展示了训练阶段的初始阶段,此时鉴别器(D)是部分正确的分类器。部分(b)和©展示了D的改进如何引导G的变化。最后,在部分(d)中,你可以看到p[model] = p[data],鉴别器不再能区分假样本和真样本,即:
图 6.5:GAN 的训练过程⁴
非饱和生成器成本
在实践中,我们不训练生成器最小化log(1 – D(G(z))),因为该函数不能提供足够的梯度用于学习。在G表现较差的初始学习阶段,鉴别器能够以高置信度区分假的和真实的。这导致log(1 – D(G(z)))饱和,阻碍了生成模型的改进。因此,我们调整生成器改为最大化log(D(G(z))):
这为生成器提供了更强的梯度进行学习。这在图 6.6中显示。x轴表示D(G(z))。顶部线显示目标,即最小化鉴别器正确的可能性。底部线(更新的目标)通过最大化鉴别器错误的可能性来工作。
图 6.6:生成器目标函数⁵
图 6.6说明了在训练的初始阶段,轻微的变化如何帮助实现更好的梯度。
最大似然游戏
极小极大游戏可以转化为最大似然游戏,其目的是最大化生成器概率密度的可能性。这是为了确保生成器的概率密度类似于真实/训练数据的概率密度。换句话说,游戏可以转化为最小化p[z]和p[data]之间的离散度。为此,我们利用Kullback-Leibler 散度(KL 散度)来计算两个感兴趣的分布之间的相似性。总的价值函数可以表示为:
生成器的成本函数转化为:
一个重要的要点是 KL 散度不是对称度量,也就是说,。模型通常使用来获得更好的结果。
迄今为止讨论过的三种不同成本函数具有略有不同的轨迹,因此在训练的不同阶段具有不同的特性。这三个函数可以如图 6.7所示可视化:
图 6.7:生成器成本函数⁶
基本 GAN
我们已经在理解 GAN 的基础知识方面取得了相当大的进展。在本节中,我们将应用这些理解,从头开始构建一个 GAN。这个生成模型将由一个重复的块结构组成,类似于原始论文中提出的模型。我们将尝试使用我们的网络复制生成 MNIST 数字的任务。
整体的 GAN 设置可以在图 6.8中看到。图中概述了一个以噪声向量z作为输入的生成器模型,并且利用重复块来转换和扩展向量以达到所需的尺寸。每个块由一个密集层后跟一个 Leaky ReLU 激活和一个批量归一化层组成。我们简单地将最终块的输出重新塑造以将其转换为所需的输出图像大小。
另一方面,判别器是一个简单的前馈网络。该模型以图像作为输入(真实图像或来自生成器的伪造输出)并将其分类为真实或伪造。这两个竞争模型的简单设置有助于我们训练整体的 GAN。
图 6.8:Vanilla GAN 架构
我们将依赖于 TensorFlow 2 并尽可能使用高级 Keras API。第一步是定义判别器模型。在此实现中,我们将使用一个非常基本的多层感知器(MLP)作为判别器模型:
def build_discriminator(input_shape=(28, 28,), verbose=True): """ Utility method to build a MLP discriminator Parameters: input_shape: type:tuple. Shape of input image for classification. Default shape is (28,28)->MNIST verbose: type:boolean. Print model summary if set to true. Default is True Returns: tensorflow.keras.model object """ model = Sequential() model.add(Input(shape=input_shape)) model.add(Flatten()) model.add(Dense(512)) model.add(LeakyReLU(alpha=0.2)) model.add(Dense(256)) model.add(LeakyReLU(alpha=0.2)) model.add(Dense(1, activation='sigmoid')) if verbose: model.summary() return model
我们将使用顺序 API 来准备这个简单的模型,仅含有四层和具有 sigmoid 激活的最终输出层。由于我们有一个二元分类任务,因此最终层中只有一个单元。我们将使用二元交叉熵损失来训练判别器模型。
生成器模型也是一个多层感知器,其中含有多层将噪声向量z扩展到所需的尺寸。由于我们的任务是生成类似于 MNIST 的输出样本,最终的重塑层将把平面向量转换成 28x28 的输出形状。请注意,我们将利用批次归一化来稳定模型训练。以下代码片段显示了构建生成器模型的实用方法:
def build_generator(z_dim=100, output_shape=(28, 28), verbose=True): """ Utility method to build a MLP generator Parameters: z_dim: type:int(positive). Size of input noise vector to be used as model input. Default value is 100 output_shape: type:tuple. Shape of output image . Default shape is (28,28)->MNIST verbose: type:boolean. Print model summary if set to true. Default is True Returns: tensorflow.keras.model object """ model = Sequential() model.add(Input(shape=(z_dim,))) model.add(Dense(256, input_dim=z_dim)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(512)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(1024)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(np.prod(output_shape), activation='tanh')) model.add(Reshape(output_shape)) if verbose: model.summary() return model
Python 与 TensorFlow2 生成式 AI(二)(4)https://developer.aliyun.com/article/1512037