Python 与 TensorFlow2 生成式 AI(二)(2)

简介: Python 与 TensorFlow2 生成式 AI(二)

Python 与 TensorFlow2 生成式 AI(二)(1)https://developer.aliyun.com/article/1512033

与此同时请注意,我们还初始化了一组带有 softmax 的 sigmoid 密集层,我们可以在使用之前概述的生成过程训练模型后通过反向传播进行微调。要训练 DBN,我们开始一个新的代码块来启动 RBM 堆栈的生成学习过程:

# pretraining:
        inputs_layers = []
        for num in range(len(self._rbm_layers)):
            if num == 0:
                inputs_layers.append(inputs)
                self._rbm_layers[num] = \
                    self.train_rbm(self._rbm_layers[num],
                                   inputs)
            else:  # pass all data through previous layer
                inputs_layers.append(inputs_layers[num-1].map(
                    self._rbm_layers[num-1].forward))
                self._rbm_layers[num] = \
                    self.train_rbm(self._rbm_layers[num],
                                   inputs_layers[num]) 

为了计算效率,我们通过使用 Dataset API 中的 map() 函数,在前向传递中将每个数据点通过前一层传递以生成除第一层以外每一层的输入,而不是反复生成这些前向样本。尽管这需要更多的内存,但大大减少了所需的计算量。预训练循环中的每一层都会回调到你之前看到的 CD 循环,它现在是 DBN 类的成员函数:

def train_rbm(self, rbm, inputs,
              num_epochs, tolerance, batch_size, shuffle_buffer):
    last_cost = None
    for epoch in range(num_epochs):
        cost = 0.0
        count = 0.0
        for datapoints in inputs.shuffle(shuffle_buffer).batch(batch_size).take(1):
            cost += rbm.cd_update(datapoints)
            count += 1.0
        cost /= count
        print("epoch: {}, cost: {}".format(epoch, cost))
        if last_cost and abs(last_cost-cost) <= tolerance:
            break
        last_cost = cost
    return rbm 

一旦我们以贪婪的方式进行了预训练,我们就可以进行wake-sleep步骤。我们从向上传递开始:

# wake-sleep:
    for epoch in range(self._num_epochs):
        # wake pass
        inputs_layers = []
        for num, rbm in enumerate(self._rbm_layers):
            if num == 0:
                inputs_layers.append(inputs)
            else:
                inputs_layers.append(inputs_layers[num-1].map(self._rbm_layers[num-1].forward))
        for num, rbm in enumerate(self._rbm_layers[:-1]):
            cost = 0.0
            count = 0.0
            for datapoints in inputs_layers[num].shuffle(
                self._shuffle_buffer).batch(self._batch_size):
                cost += self._rbm_layers[num].wake_update(datapoints)
                count += 1.0
            cost /= count
            print("epoch: {}, wake_cost: {}".format(epoch, cost)) 

再次注意,我们收集了在每个阶段转换的前向传递的列表,以便我们具有更新公式所需的必要输入。我们现在已经向 RBM 类添加了一个函数,wake_update,它将仅为生成(向下)权重计算更新,即除了最后一层(关联的,无向连接)之外的每一层:

def wake_update(self, x):
    with tf.GradientTape(watch_accessed_variables=False) as g:
        h_sample = self.sample_h(x)
        for step in range(self.cd_steps):
            v_sample = self.sample_v(h_sample)
            h_sample = self.sample_h(v_sample)
        g.watch(self.w_gen)
        g.watch(self.vb)
        cost = tf.reduce_mean(self.free_energy(x)) - tf.reduce_mean(self.free_energy_reverse(h_sample))
    w_grad, vb_grad = g.gradient(cost, [self.w_gen, self.vb])
    self.w_gen.assign_sub(self.learning_rate * w_grad)
    self.vb.assign_sub(self.learning_rate * vb_grad)
    return self.reconstruction_cost(x).numpy() 

这与 CD 更新几乎相同,不同之处在于我们仅更新生成权重和可见单元偏置项。一旦我们计算了前向传递,然后对顶层的关联内存执行对比更新:

# top-level associative:
        self._rbm_layers[-1] = self.train_rbm(self._rbm_layers[-1],
            inputs_layers[-2].map(self._rbm_layers[-2].forward), 
            num_epochs=self._num_epochs, 
            tolerance=self._tolerance, batch_size=self._batch_size, 
            shuffle_buffer=self._shuffle_buffer) 

然后我们需要计算wake-sleep算法的逆向传递数据;我们通过再次对最后一层的输入应用映射来实现这一点:

reverse_inputs = inputs_layers[-1].map(self._rbm_layers[-1].forward) 

对于睡眠传递,我们需要反向遍历 RBM,仅更新非关联(无向)连接。我们首先需要逆向映射每一层所需的输入:

reverse_inputs_layers = []
        for num, rbm in enumerate(self._rbm_layers[::-1]):
            if num == 0:
                reverse_inputs_layers.append(reverse_inputs)
            else:
                reverse_inputs_layers.append(
                    reverse_inputs_layers[num-1].map(
                    self._rbm_layers[len(self._rbm_layers)-num].reverse)) 

然后我们对层进行反向遍历,仅更新非关联连接:

for num, rbm in enumerate(self._rbm_layers[::-1]):
            if num > 0:
                cost = 0.0
                count = 0.0
                for datapoints in reverse_inputs_layers[num].shuffle(
                    self._shuffle_buffer).batch(self._batch_size):
                    cost += self._rbm_layers[len(self._rbm_layers)-1-num].sleep_update(datapoints)
                    count += 1.0
                cost /= count
                print("epoch: {}, sleep_cost: {}".format(epoch, cost)) 

一旦我们对训练进展满意,我们就可以使用常规反向传播进一步调整模型。wake-sleep过程中的最后一步是将所有稠密层设置为来自 RBM 层的训练权重的结果:

for dense_layer, rbm_layer in zip(dbn._dense_layers, dbn._rbm_layers):
    dense_layer.set_weights([rbm_layer.w_rec.numpy(), rbm_layer.hb.numpy()] 

我们已经在 DBN 类中使用 function() 调用包含了神经网络的前向传递:

def call(self, x, training):
    for dense_layer in self._dense_layers:
        x = dense_layer(x)
    return x 

这可以在 TensorFlow API 中的 fit() 调用中使用:

dbn.compile(loss=tf.keras.losses.CategoricalCrossentropy())
dbn.fit(x=mnist_train.map(lambda x: flatten_image(x, label=True)).batch(32), ) 

这开始使用反向传播来训练现在预先训练过的权重,以微调模型的判别能力。概念上理解这种微调的一种方式是,预训练过程引导权重到一个合理的配置,以捕捉数据的“形状”,然后反向传播可以调整这些权重以适应特定的分类任务。否则,从完全随机的权重配置开始,参数距离捕捉数据中的变化太远,无法通过单独的反向传播有效地导航到最佳配置。

您已经了解了如何将多个 RBM 结合在层中创建 DBN,并如何使用 TensorFlow 2 API 在端到端模型上运行生成式学习过程;特别是,我们利用梯度磁带允许我们记录和重放梯度使用非标准优化算法(例如,不是 TensorFlow API 中的默认优化器之一),使我们能够将自定义梯度更新插入 TensorFlow 框架中。

摘要

在本章中,您了解了深度学习革命开始时最重要的模型之一,即 DBN。您看到 DBN 是通过堆叠 RBM 构建的,以及如何使用 CD 训练这些无向模型。

该章节随后描述了一种贪婪的逐层过程,通过逐个训练一堆 RBM 来启动 DBN,然后可以使用唤醒-睡眠算法或反向传播进行微调。然后,我们探讨了使用 TensorFlow 2 API 创建 RBM 层和 DBN 模型的实际示例,说明了使用GradientTape类计算使用 CD 更新的方法。

您还学习了如何根据唤醒-睡眠算法,将 DBN 编译为正常的深度神经网络,并对其进行监督训练的反向传播。我们将这些模型应用于 MNIST 数据,并看到 RBM 在训练收敛后如何生成数字,并具有类似于第三章深度神经网络的构建基块中描述的卷积滤波器的特征。

虽然本章中的示例显著扩展了 TensorFlow Keras API 的基本层和模型类,但它们应该能够让你了解如何实现自己的低级替代训练过程。未来,我们将主要使用标准的fit()和predict()方法,从我们的下一个主题开始,变分自动编码器,这是一种复杂且计算效率高的生成图像数据的方式。

参考文献

  1. LeCun, Yann; Léon Bottou; Yoshua Bengio; Patrick Haffner (1998). 基于梯度的学习应用于文档识别。IEEE 会议录。86 (11): 2278–2324
  2. LeCun, Yann; Corinna Cortes; Christopher J.C. Burges。 MNIST 手写数字数据库,Yann LeCun,Corinna Cortes 和 Chris Burges
  3. NIST 的原始数据集:www.nist.gov/system/files/documents/srd/nistsd19.pdf
  4. upload.wikimedia.org/wikipedia/commons/thumb/2/27/MnistExamples.png/440px-MnistExamples.png
  5. LeCun, Yann; Léon Bottou; Yoshua Bengio; Patrick Haffner (1998). 基于梯度的学习应用于文档识别。IEEE 会议录。86 (11): 2278–2324
  6. D. Ciregan, U. Meier 和 J. Schmidhuber, (2012) 用于图像分类的多列深度神经网络,2012 年 IEEE 计算机视觉和模式识别会议,pp. 3642-3649. ieeexplore.ieee.org/document/6248110
  7. Hinton GE, Osindero S, Teh YW (2006) 深度信念网络的快速学习算法。神经计算。18(7):1527-54. www.cs.toronto.edu/~hinton/absps/fastnc.pdf
  8. Hebb, D. O. (1949). 行为的组织:一个神经心理学理论。纽约:Wiley and Sons
  9. Gurney, Kevin (2002). 神经网络简介. Routledge
  10. Sathasivam, Saratha (2008). 霍普菲尔德网络中的逻辑学习.
  11. Hebb, D. O.。行为的组织:一个神经心理学理论。劳伦斯埃尔巴姆出版,2002 年
  12. Suzuki, Wendy A. (2005). 联想学习与海马体. 心理科学日程。美国心理协会。www.apa.org/science/about/psa/2005/02/suzuki
  13. Hammersley, J. M.; Clifford, P. (1971),有限图和晶格上的马尔可夫场;Clifford, P. (1990),统计学中的马尔可夫随机场,在 Grimmett, G. R.; Welsh, D. J. A. (eds.),物理系统中的无序:纪念约翰 M. Hammersley 专著,牛津大学出版社,pp. 19-32
  14. Ackley, David H; Hinton, Geoffrey E; Sejnowski, Terrence J (1985),玻尔兹曼机的学习算法 (PDF),认知科学,9(1):147–169
  15. 玻尔兹曼机. 维基百科. 检索日期:2021 年 4 月 26 日,来自en.wikipedia.org/wiki/Boltzmann_machine
  16. Smolensky, Paul (1986). 第六章:动力系统中的信息处理:谐和理论的基础 (PDF). 在 Rumelhart,David E.; McLelland, James L. (eds.) 平行分布式处理:认知微结构探索,第 1 卷:基础。麻省理工学院出版社。pp.194–281
  17. Woodford O. 对比散度笔记. www.robots.ox.ac.uk/~ojw/files/NotesOnCD.pdf
  18. Hinton, G E. (2000). 通过最小化对比散度来训练专家乘积. www.cs.utoronto.ca/~hinton/absps/nccd.pdf
  19. Roux, N L.,Bengio, Y. (2008). 受限玻尔兹曼机和深度信念网络的表示能力. 在神经计算,卷 20,第 6 期,pp. 1631-1649. www.microsoft.com/en-us/research/wp-content/uploads/2016/02/representational_power.pdf
  20. Hinton, G E. (2000). 通过最小化对比散度来训练专家乘积. www.cs.utoronto.ca/~hinton/absps/nccd.pdf
  21. Pearl J., Russell S. (2000). 贝叶斯网络. ftp.cs.ucla.edu/pub/stat_ser/r277.pdf
  22. Hinton GE, Osindero S, Teh YW. (2006) 深信念网络的快速学习算法. Neural Comput. 18(7):1527-54. www.cs.toronto.edu/~hinton/absps/fastnc.pdf
  23. Hinton GE, Osindero S, Teh YW. (2006) 深信念网络的快速学习算法. Neural Comput. 18(7):1527-54. www.cs.toronto.edu/~hinton/absps/fastnc.pdf
  24. Hinton GE, Osindero S, Teh YW. (2006) 深信念网络的快速学习算法. Neural Comput. 18(7):1527-54. www.cs.toronto.edu/~hinton/absps/fastnc.pdf

第五章:使用 VAEs 用神经网络绘制图片

正如您在 第四章 中所看到的,教网络生成数字,深度神经网络是创建复杂数据的生成模型的强大工具,允许我们开发一个网络,该网络可以从 MNIST 手写数字数据库生成图像。在那个例子中,数据相对简单;图像只能来自一组有限的类别(数字 0 到 9),并且是低分辨率灰度数据。

更复杂的数据呢,比如来自现实世界的彩色图像呢?这类“现实世界”的数据的一个例子是加拿大高级研究所 10 类数据集,简称为 CIFAR-10。¹ 它是来自 8000 万张图像更大数据集的 60,000 个样本的子集,分为十个类别——飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船和卡车。虽然在真实世界中我们可能会遇到的图像多样性方面仍然是一个极为有限的集合,但是这些类别具有一些特性,使它们比 MNIST 更复杂。例如,MNIST 数字可以在宽度、曲率和其他几个属性上变化;而 CIFAR-10 类别的动物或车辆照片有着更广泛的潜在变化范围,这意味着我们可能需要更复杂的模型来捕捉这种变化。

在本章中,我们将讨论一类称为变分自动编码器(VAEs)的生成模型,这些模型旨在使生成这些复杂的现实世界图像更易于处理和调节。它们通过使用许多巧妙的简化方法来使得在复杂的概率分布上进行采样成为可能,从而可扩展。

我们将探讨以下主题以揭示 VAEs 的工作原理:

  • 神经网络如何创建数据的低维表示,以及这些表示的一些理想属性
  • 变分方法如何允许我们使用这些表示从复杂数据中进行采样
  • 如何使用重新参数化技巧来稳定基于变分采样的神经网络的方差 —— 一个 VAE
  • 我们如何使用逆自回归流(IAF)来调整 VAE 的输出
  • 如何在 TensorFlow 中实现 VAE/IAF

创建图像的可分离编码

在 图 5.1 中,您可以看到 CIFAR-10 数据集的图像示例,以及一个可以根据随机数输入生成这些图像模糊版本的早期 VAE 算法示例:


图 5.1:CIFAR-10 样本(左),VAE(右)²

对 VAE 网络的最新工作已经使得这些模型能够生成更好的图像,正如您将在本章后面看到的那样。首先,让我们重新审视生成 MNIST 数字的问题以及我们如何将这种方法扩展到更复杂的数据。

从第一章,生成式人工智能简介:“从模型中”绘制数据和第四章,教网络生成数字中回想起,RBM(或 DBN)模型本质上涉及学习给定一些潜在“代码”(z)的图像(x)的后验概率分布,由网络的隐藏层表示,x的“边际可能性”:³


我们可以将z视为图像x的“编码”(例如,RBM 中二进制隐藏单元的激活),可以解码(例如,反向运行 RBM 以对图像进行采样)以获得x的重构。如果编码“好”,重构将接近原始图像。因为这些网络对其输入数据的表示进行编码和解码,它们也被称为“自编码器”。

深度神经网络捕捉复杂数据的基本结构的能力是它们最吸引人的特征之一;正如我们在第四章,教网络生成数字中所看到的 DBN 模型一样,它使我们能够通过为数据的分布创建更好的基础模型来提高分类器的性能。它还可以用于简单地创建一种更好的方法来“压缩”数据的复杂性,类似于经典统计学中的主成分分析(PCA)。在图 5.2中,您可以看到堆叠的 RBM 模型如何用作编码面部分布的一种方式,例如。

我们从“预训练”阶段开始,创建一个 30 单位的编码向量,然后通过强制它重构输入图像来校准它,然后使用标准反向传播进行微调:


图 5.2:使用 DBN 作为自编码器⁴

作为堆叠的 RBM 模型如何更有效地表示图像分布的示例,从图 5.2派生的论文用神经网络减少数据的维数的作者演示了使用两个单位代码来对比 MNIST 数字的 PCA:


图 5.3:MNIST 数字的 PCA 与 RBM 自编码器对比⁵

在左边,我们看到使用二维 PCA 编码的数字 0-9(由不同的阴影和形状表示)。回想一下,PCA 是使用数据的协方差矩阵的低维分解生成的:


Cov(X) 的高度/宽度与数据相同(例如,在 MNIST 中为 28 x 28 像素),而 U 和 V 都是较低维度的(M x k 和 k x M),其中 k 远小于 M。由于它们在一个维度上具有较少的行/列数 k,U 和 V 是数据的低维表示,我们可以通过将其投影到这些 k 向量上来获得对单个图像的编码,从而给出了数据的 k 单位编码。由于分解(和投影)是线性变换(两个矩阵的乘积),PCA 组件有效区分数据的能力取决于数据是否线性可分(我们可以通过组之间的空间绘制一个超平面—该空间可以是二维的或 N 维的,例如 MNIST 图像中的 784 个像素)。

如 图 5.3 所示,PCA 为图像生成了重叠的代码,表明使用二分量线性分解来表示数字是具有挑战性的,其中表示相同数字的向量彼此靠近,而表示不同数字的向量明显分开。从概念上讲,神经网络能够捕捉更多表示不同数字的图像之间的变化,如其在二维空间中更清晰地分离这些数字的表示所示。

为了理解这一现象,可以将其类比为一个非常简单的二维数据集,由平行双曲线(二次多项式)组成(图 5.4):


图 5.4:平行双曲线和可分离性

在顶部,即使我们有两个不同的类别,我们也无法在二维空间中画一条直线将两个组分开;在神经网络中,单个层中的权重矩阵在 sigmoid 或 tanh 的非线性转换之前本质上是这种类型的线性边界。然而,如果我们对我们的 2D 坐标应用非线性变换,比如取超半径的平方根,我们可以创建两个可分离的平面(图 5.4,底部)。

在 MNIST 数据中存在类似的现象:我们需要一个神经网络来将这些 784 位数的图像放置到不同的、可分离的空间区域中。这个目标通过对原始的、重叠的数据执行非线性变换来实现,其中的目标函数奖励增加编码不同数字图像的向量之间的空间分离。因此,可分离的表示增加了神经网络利用这些表示区分图像类别的能力。因此,在 图 5.3 中,我们可以看到右侧应用 DBN 模型创建所需的非线性变换以分离不同的图像。

现在我们已经讨论了神经网络如何将数据压缩为数值向量以及这些向量表示的一些理想特性,我们将探讨如何在这些向量中最佳压缩信息。为此,向量的每个元素应该从其他元素中编码出不同的信息,我们可以使用变分目标这一属性来实现。这个变分目标是创建 VAE 网络的基础。

变分目标

我们之前讨论了几个例子,展示了如何使用神经网络将图像压缩成数值向量。这一部分将介绍能够让我们从随机数值向量空间中采样新图像的要素,主要是有效的推理算法和适当的目标函数。让我们更加严谨地量化什么样的编码才能让其“好”,并且能够很好地重现图像。我们需要最大化后验概率:


当x的概率非常高维时会出现问题,如你所见,在甚至是简单的数据中,如二元 MNIST 数字中,我们有2^(像素数)可能的配置,我们需要对其进行积分(在概率分布意义上进行积分)以得到对单个图像概率的度量;换句话说,密度p(x)是棘手的,导致了依赖于p(x)的后验p(z|x)也同样不容易处理。

在某些情况下,正如你在第四章中看到的,训练网络生成数字,我们可以使用简单的二元单元,例如对比散度来计算近似,这使我们可以计算梯度,即使我们无法计算封闭形式。然而,在处理非常大的数据集时也可能具有挑战性,我们需要对数据进行多次传递以计算使用对比散度(CD)计算平均梯度,就像之前在第四章中看到的那样。

如果我们无法直接计算编码器p(z|x)的分布,也许我们可以优化一个足够“接近”的近似——让我们称之为q(z|x)。然后,我们可以使用一种度量来确定这两个分布是否足够接近。一个有用的接近度量是这两个分布是否编码了类似的信息;我们可以使用香农信息方程来量化信息:


考虑一下这为什么是一个很好的度量:随着p(x)的减少,事件变得更加罕见,因此事件的观察向系统或数据集传达更多信息,导致log(p(x))的正值。相反,当事件的概率接近 1 时,该事件对数据集的编码信息变少,而log(p(x))的值变为 0(图 5.5):


图 5.5:香农信息

因此,如果我们想要衡量两个分布p和q中编码的信息之间的差异,我们可以使用它们的信息之差:


最后,如果我们想要找到分布在x的所有元素上的信息差异的期望值,我们可以对p(x)取平均:


这个量被称为Kullback Leibler (KL) 散度。它有一些有趣的特性:

  1. 它不是对称的:KL(p(x), q(x))一般来说不等于KL(q(x), p(x)),所以“接近程度”是通过将一个分布映射到另一个分布的特定方向来衡量的。
  2. 每当q(x)和p(x)匹配时,这个项就是 0,意味着它们彼此之间的距离是最小的。同样,只有当p和q是相同的时候,KL(p(x), q(x))才为 0。
  3. 如果q(x)为 0 或者p(x)为 0,那么KL是未定义的;按照定义,它仅计算两个分布在x的范围内匹配的相对信息。
  4. KL始终大于 0。

如果我们要使用KL散度来计算近似值q(z,x)对于我们不可计算的p(z|x)的拟合程度,我们可以写成:


和:



现在我们也可以写出我们不可计算的p(x)的表达式了:由于log(p(x))不依赖于q(z|x),对p(x)的期望值简单地是log(p(x))。因此,我们可以用KL散度表示 VAE 的目标,学习p(x)的边际分布:


第二项也被称为变分下限,也被称为证据下界(ELBO);由于KL(q,p)严格大于 0,log(p(x))严格大于或者(如果KL(q,p)为 0)等于这个值。

要解释这个目标在做什么,注意到期望引入了q(z|x)(编码 x)和p(x|z)p(z)(数据和编码的联合概率)之间的差异;因此,我们想要最小化一个下界,它实质上是编码的概率和编码与数据的联合概率之间的差距,误差项由KL(q,p)给出,这是一个可计算的近似和不可计算的编码器p(z|x)形式之间的差异。我们可以想象函数Q(z|x)和P(x|z)由两个深度神经网络表示;一个生成潜在代码z(Q),另一个从这个代码重建x(P)。我们可以把这看作是一个自动编码器设置,就像上面的堆叠 RBM 模型一样,有一个编码器和解码器:


图 5.6:无再参数化 VAE 的自动编码器/解码器⁷

我们希望优化编码器 Q 和解码器 P 的参数,以最小化重构成本。其中一种方法是构造蒙特卡洛样本来使用梯度下降优化 Q 的参数:


我们从哪里抽样 z:


然而,在实践中发现,可能需要大量的样本才能使这些梯度更新的方差稳定下来。

我们在这里也遇到了一个实际问题:即使我们可以选择足够的样本来获得对编码器的梯度的良好近似,但我们的网络包含一个随机的、不可微分的步骤(抽样 z),我们无法通过反向传播来处理,就像我们无法通过反向传播来处理 第四章 中 RBN 中的随机单元一样。因此,我们的重构误差取决于 z 的样本,但我们无法通过生成这些样本的步骤进行端到端的网络调整。有没有办法我们可以创建一个可微分的解码器/编码器架构,同时减少样本估计的方差?VAE 的主要见解之一就是通过 “重新参数化技巧” 实现这一点。

重新参数化技巧

为了使我们能够通过我们的自编码器进行反向传播,我们需要将 z 的随机样本转换为一个确定性的、可微分的变换。我们可以通过将 z 重新参数化为一个噪声变量的函数来实现这一点:


一旦我们从  中抽样,z 中的随机性就不再取决于变分分布 Q(编码器)的参数,我们可以进行端到端的反向传播。我们的网络现在看起来像 图 5.7,我们可以使用  的随机样本(例如,标准正态分布)来优化我们的目标。这种重新参数化将 “随机” 节点移出了编码器/解码器框架,使我们能够通过整个系统进行反向传播,但它还有一个更微妙的优点;它减少了这些梯度的方差。请注意,在未重新参数化的网络中,z 的分布取决于编码器分布 Q 的参数;因此,当我们改变 Q 的参数时,我们也在改变 z 的分布,并且我们可能需要使用大量样本才能得到一个合理的估计。

通过重新参数化,z 现在仅取决于我们更简单的函数 g,通过从标准正态分布中进行抽样引入随机性(这不依赖于 Q);因此,我们已经消除了一个有些循环的依赖,并使我们正在估计的梯度更加稳定:


图 5.7:重新参数化 VAE 的自编码器/解码器

现在你已经看到 VAE 网络是如何构建的,让我们讨论一种进一步改进这一算法的方法,使得 VAE 能够从复杂分布中取样:逆自回归流(IAF)。

逆自回归流

在我们之前的讨论中,我们指出希望使用q(z|x)来近似“真实”的p(z|x),这会允许我们生成数据的理想编码,并从中取样生成新的图像。到目前为止,我们假设q(z|x)有一个相对简单的分布,比如独立的高斯分布随机变量的向量(对角协方差矩阵上的非对角元素为 0)。这种分布有许多好处;因为它很简单,我们可以轻松地从随机正态分布中进行抽样生成新数据,并且因为它是独立的,我们可以分别调整潜在向量z的各个元素,以影响输出图像的各个部分。

然而,这样一个简单的分布可能不能很好地适应数据的期望输出分布,增加了p(z|x)和q(z|x)之间的KL散度。我们能不能以某种方式保留q(z|x)的良好特性,但“变换”z,以便它更多地捕捉表示x所需的复杂性呢?

一种方法是对z应用一系列自回归变换,将其从一个简单分布转变为一个复杂分布;通过“自回归”,我们指的是每个变换利用了前一次变换和当前数据来计算z的更新版本。相比之下,我们上面介绍的基本 VAE 形式只有一个“变换”:从z到输出(虽然z可能经过多层,但没有循环网络链接来进一步完善输出)。我们之前已经见过这样的变换,比如第三章中的 LSTM 网络,其中网络的输出是当前输入和先前时间步加权版本的组合。

我们之前讨论过的独立q(z|x)分布的吸引人之处是,例如独立的正态分布,它们在对数似然函数上有一个非常简单的表达式。这一特性对于 VAE 模型非常重要,因为其目标函数取决于对整个似然函数进行积分,而对于更复杂的对数似然函数来说,这可能是繁琐的。然而,通过约束一个经过一系列自回归变换的z,我们得到了一个很好的特性,即第t步的对数似然仅取决于t-1,因此雅可比矩阵(t和t-1之间的偏导数的梯度矩阵)是下三角的,可以计算为一个和:


可以使用哪些种类的变换 f?记住,在参数化技巧之后,z 是编码器 Q 输出的均值和标准差以及一个噪声元素 e 的函数:


如果我们应用连续的转换层,步骤 t 就变成了和前一层 z 与 sigmoid 输出  的逐元素乘积之和:


在实践中,我们使用神经网络转换来稳定每一步的均值估计:


图 5.8:IAF 网络⁶

再次注意,这种转换与第三章,深度神经网络的基本构件中讨论的 LSTM 网络的相似性。在图 5.8中,除了均值和标准差之外,编码器 Q 还有另一个输出 (h),用于对 z 进行采样。H 本质上是“辅助数据”,它被传递到每一个连续的转换中,并且与在每一步计算的加权和一起,以一种类似 LSTM 的方式,表示网络的“持久记忆”。

导入 CIFAR

现在我们已经讨论了 VAE 算法的基本理论,让我们开始使用真实世界的数据集构建一个实际的例子。正如我们在介绍中讨论的,对于本章的实验,我们将使用加拿大高级研究所(CIFAR)10 数据集。¹⁰ 这个数据集中的图像是 8000 万个“小图像”数据集¹¹的一部分,其中大多数没有像 CIFAR-10 这样的类标签。对于 CIFAR-10,标签最初是由学生志愿者创建的¹²,而更大的小图像数据集允许研究人员为数据的部分提交标签。

像 MNIST 数据集一样,可以使用 TensorFlow 数据集的 API 下载 CIFAR-10:

import tensorflow.compat.v2 as tf
import tensorflow_datasets as tfds
cifar10_builder = tfds.builder("cifar10")
cifar10_builder.download_and_prepare() 

这将把数据集下载到磁盘并使其可用于我们的实验。要将其拆分为训练集和测试集,我们可以使用以下命令:

cifar10_train = cifar10_builder.as_dataset(split="train")
cifar10_test = cifar10_builder.as_dataset(split="test") 

让我们检查一下其中一幅图像,看看它是什么格式:

cifar10_train.take(1) 

输出告诉我们数据集中每个图像的格式是 , types: {image: tf.uint8, label: tf.int64}>: 不像我们在第四章,教网络生成数字中使用的 MNIST 数据集,CIFAR 图像有三个颜色通道,每个通道都有 32 x 32 个像素,而标签是一个从 0 到 9 的整数(代表 10 个类别中的一个)。我们也可以绘制图像来进行视觉检查:

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
for sample in cifar10_train.map(lambda x: flatten_image(x, label=True)).take(1):
    plt.imshow(sample[0].numpy().reshape(32,32,3).astype(np.float32), 
               cmap=plt.get_cmap("gray")
              )
    print("Label: %d" % sample[1].numpy()) 

这给出了以下输出:


图 5.9:输出

像 RBM 模型一样,在这个示例中我们将构建的 VAE 模型的输出被缩放在 1 到 0 之间,并且接受图像的扁平版本,因此我们需要将每个图像转换为一个向量,并将其缩放到最大为 1:

def flatten_image(x, label=False):
    if label:
        return (tf.divide(
            tf.dtypes.cast(
                tf.reshape(x["image"], (1, 32*32*3)), tf.float32), 
                    256.0),
                x["label"])
    else:
        return (
            tf.divide(tf.dtypes.cast(
                tf.reshape(x["image"], (1, 32*32*3)), tf.float32), 
                    256.0)) 

这导致每个图像都是长度为 3072 (32323) 的向量,在运行模型后,我们可以重塑它们以检查生成的图像。

从 TensorFlow 2 创建网络

现在我们已经下载了 CIFAR-10 数据集,将其拆分为测试和训练数据,并对其进行了重塑和重新缩放,我们已经准备好开始构建我们的 VAE 模型了。我们将使用 TensorFlow 2 中的 Keras 模块的相同 Model API。TensorFlow 文档中包含了使用卷积网络实现 VAE 的示例(www.tensorflow.org/tutorials/generative/cvae),我们将在此代码示例的基础上构建;然而,出于我们的目的,我们将使用基于原始 VAE 论文《自编码变分贝叶斯》(Auto-Encoding Variational Bayes)¹³ 的 MLP 层实现更简单的 VAE 网络,并展示如何将 TensorFlow 示例改进为也允许解码中的 IAF 模块。

在原始文章中,作者提出了两种用于 VAE 的模型,都是 MLP 前馈网络:高斯和伯努利,这些名称反映了 MLP 网络输出中使用的概率分布函数在它们的最终层中。伯努利 MLP 可以用作网络的解码器,从潜在向量 z 生成模拟图像 x。伯努利 MLP 的公式如下:


第一行是我们用于确定网络是否生成原始图像近似重建的交叉熵函数,而 y 是一个前馈网络,有两层:一个双曲正切变换,然后是一个 sigmoid 函数将输出缩放到 0 到 1 之间。回想一下,这种缩放是我们不得不将 CIFAR-10 像素从其原始值归一化的原因。

我们可以很容易地使用 Keras API 创建这个伯努利 MLP 网络:

class BernoulliMLP(tf.keras.Model):
    def __init__(self, input_shape, name='BernoulliMLP', hidden_dim=10, latent_dim=10, **kwargs):
        super().__init__(name=name, **kwargs)
        self._h = tf.keras.layers.Dense(hidden_dim, 
                                        activation='tanh')
        self._y = tf.keras.layers.Dense(latent_dim, 
                                        activation='sigmoid')
    def call(self, x):
        return self._y(self._h(x)), None, None 

我们只需要指定单隐藏层和潜在输出 (z) 的维度。然后,我们将前向传递指定为这两个层的组合。请注意,在输出中,我们返回了三个值,第二和第三个值均设置为 None。这是因为在我们的最终模型中,我们可以使用 BernoulliMLP 或 GaussianMLP 作为解码器。如果我们使用 GaussianMLP,则返回三个值,正如我们将在下文中看到的;本章中的示例利用了二进制输出和交叉熵损失,因此我们可以只使用单个输出,但我们希望两个解码器的返回签名匹配。

原始 VAE 论文中作者提出的第二种网络类型是高斯 MLP,其公式为:


此网络可以在网络中作为编码器(生成潜在向量z)或解码器(生成模拟图像x)使用。上述方程假定它用作解码器,对于编码器,我们只需交换x和z变量。如您所见,这个网络有两种类型的层,一个隐藏层由输入的 tanh 变换给出,并且两个输出层,每个输出层由隐藏层的线性变换给出,这些输出层被用作对数正态似然函数的输入。像 Bernoulli MLP 一样,我们可以轻松使用 TensorFlow Keras API 实现这个简单网络:

class GaussianMLP(tf.keras.Model):
    def __init__(self, input_shape, name='GaussianMLP', hidden_dim=10, latent_dim=10, iaf=False, **kwargs):
        super().__init__(name=name, **kwargs)
        self._h = tf.keras.layers.Dense(hidden_dim, 
                                        activation='tanh')
        self._mean = tf.keras.layers.Dense(latent_dim)
        self._logvar = tf.keras.layers.Dense(latent_dim)
        self._iaf_output = None
        if iaf:
            self._iaf_output = tf.keras.layers.Dense(latent_dim)
    def call(self, x):
        if self._iaf_output:
            return self._mean(self._h(x)), self._logvar(self._h(x)), 
                self._iaf_output(self._h(x))
        else:
            return self._mean(self._h(x)), self._logvar(self._h(x)), 
                None 

如您所见,要实现call函数,我们必须返回模型的两个输出(我们将用来计算z或x的正态分布的均值和对数方差)。然而,请注意,对于 IAE 模型,编码器必须具有额外的输出h,它被馈送到每一步的正规流中:

Python 与 TensorFlow2 生成式 AI(二)(3)https://developer.aliyun.com/article/1512036

相关文章
|
1月前
|
存储 人工智能 开发工具
AI助理化繁为简,速取代码参数——使用python SDK 处理OSS存储的图片
只需要通过向AI助理提问的方式输入您的需求,即可瞬间获得核心流程代码及参数,缩短学习路径、提升开发效率。
1432 4
AI助理化繁为简,速取代码参数——使用python SDK 处理OSS存储的图片
|
6天前
|
人工智能 IDE 开发工具
Python AI 编程助手
Python AI 编程助手。
23 5
|
4天前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
垃圾识别分类系统。本系统采用Python作为主要编程语言,通过收集了5种常见的垃圾数据集('塑料', '玻璃', '纸张', '纸板', '金属'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对图像数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。然后使用Django搭建Web网页端可视化操作界面,实现用户在网页端上传一张垃圾图片识别其名称。
21 0
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
|
4天前
|
机器学习/深度学习 人工智能 算法
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
手写数字识别系统,使用Python作为主要开发语言,基于深度学习TensorFlow框架,搭建卷积神经网络算法。并通过对数据集进行训练,最后得到一个识别精度较高的模型。并基于Flask框架,开发网页端操作平台,实现用户上传一张图片识别其名称。
16 0
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
|
4天前
|
机器学习/深度学习 人工智能 算法
基于深度学习的【蔬菜识别】系统实现~Python+人工智能+TensorFlow+算法模型
蔬菜识别系统,本系统使用Python作为主要编程语言,通过收集了8种常见的蔬菜图像数据集('土豆', '大白菜', '大葱', '莲藕', '菠菜', '西红柿', '韭菜', '黄瓜'),然后基于TensorFlow搭建卷积神经网络算法模型,通过多轮迭代训练最后得到一个识别精度较高的模型文件。在使用Django开发web网页端操作界面,实现用户上传一张蔬菜图片识别其名称。
17 0
基于深度学习的【蔬菜识别】系统实现~Python+人工智能+TensorFlow+算法模型
|
8天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
利用Python和TensorFlow构建简单神经网络进行图像分类
利用Python和TensorFlow构建简单神经网络进行图像分类
27 3
|
15天前
|
人工智能 C语言 Python
AI师傅+通义灵码=零基础小白上手python真·不是梦
作为一名不懂编程的设计师,我一直渴望掌握AI辅助设计。在快刀青衣的推荐下,我尝试了AI师傅和通义灵码,成功写出了第一个Python程序,并理解了编程的基本概念。通过AI师傅的引导和通义灵码的帮助,我顺利完成了Coursera上的Python课程,获得了两张证书。这种学习方式让编程变得不再遥不可及,为我的未来学习打开了新大门。
|
20天前
|
机器学习/深度学习 人工智能 算法
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
车辆车型识别,使用Python作为主要编程语言,通过收集多种车辆车型图像数据集,然后基于TensorFlow搭建卷积网络算法模型,并对数据集进行训练,最后得到一个识别精度较高的模型文件。再基于Django搭建web网页端操作界面,实现用户上传一张车辆图片识别其类型。
65 0
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
|
2月前
|
机器学习/深度学习 人工智能 算法
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
鸟类识别系统。本系统采用Python作为主要开发语言,通过使用加利福利亚大学开源的200种鸟类图像作为数据集。使用TensorFlow搭建ResNet50卷积神经网络算法模型,然后进行模型的迭代训练,得到一个识别精度较高的模型,然后在保存为本地的H5格式文件。在使用Django开发Web网页端操作界面,实现用户上传一张鸟类图像,识别其名称。
108 12
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
|
30天前
|
人工智能 文字识别 Java
SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?
尼恩,一位拥有20年架构经验的老架构师,通过其深厚的架构功力,成功指导了一位9年经验的网易工程师转型为大模型架构师,薪资逆涨50%,年薪近80W。尼恩的指导不仅帮助这位工程师在一年内成为大模型架构师,还让他管理起了10人团队,产品成功应用于多家大中型企业。尼恩因此决定编写《LLM大模型学习圣经》系列,帮助更多人掌握大模型架构,实现职业跃迁。该系列包括《从0到1吃透Transformer技术底座》、《从0到1精通RAG架构》等,旨在系统化、体系化地讲解大模型技术,助力读者实现“offer直提”。此外,尼恩还分享了多个技术圣经,如《NIO圣经》、《Docker圣经》等,帮助读者深入理解核心技术。
SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?