在本章中,您将:
了解标准化流动模型如何利用变量方程的变化
了解 Jacobian 如何在我们计算显式密度函数的能力中发挥重要作用
了解我们如何使用耦合层来限制雅可比行列式的形式。
查看神经网络如何设计为可逆的
构建 RealNVP 模型 - 规范化流程生成 2D 点的特定示例
使用 RealNVP 模型生成新的点,这些点似乎是从数据分布中提取的
了解 RealNVP 模型的两个关键扩展——GLOW 和 FFJORD
本节前面的章节介绍了生成模型的三个家族——变分自动编码器、生成对抗网络和自回归模型。每一种方法都提出了一种不同的方法来解决分布建模的挑战p(x),要么通过引入一个可以很容易地从中采样的潜在变量z(VAEs,GANs),要么通过基于前面元素的值预测序列的下一个元素(自回归楷模)。在本章中,我们将介绍生成建模家族的另一个成员——归一化流。
Normalizing Flows 是另一种明确和易处理地对数据生成分布进行建模的方法p(x),因此与自回归模型有一些相似之处。归一化流学习一种双向映射p(x)和一种更简单的分布,例如高斯分布,因此要生成新的数据点,我们可以简单地从高斯分布中采样并应用学习到的前向映射函数。
我们将首先从一个小故事开始,以说明规范化流程背后的关键概念:
Jacob和 流动机器
在参观一个小村庄时,您会注意到一家看起来很神秘的商店,门上方有一个标志,上面写着JACOB'S。出于好奇,你小心翼翼地走进去,问站在柜台后面的老人卖什么。
他回答说,他提供了一项与众不同的数字化绘画服务。在商店后面翻找了片刻后,他拿出一个银盒子,上面压印着字母 FLO W。他告诉你,这代表寻找水彩画的相似之处,大致描述了机器的功能。你决定试试这台机器。
你递给店主一套你最喜欢的画作,他将它们通过机器。FLOW 机器开始发出嗡嗡声和口哨声,一段时间后输出一组看起来几乎是随机生成的数字。店主将清单递给您,然后走向收银台,计算您欠他多少数字化流程和 FLOW 框。你显然不为所动,问店主你应该如何处理这一长串数字,以及如何才能取回你最喜欢的画作。
店主翻了个白眼,好像答案应该是显而易见的。他走回机器前,把一长串数字递进来,这次是从对面。你再次听到机器的嗡嗡声,随着机器的声音向后移动,直到最后,你原来的画从它们最初进入的地方掉了下来。
终于拿回您的画作后松了一口气,您决定最好将它们存放在阁楼中。然而,在你有机会离开之前,店主会把你带到商店的另一个角落,那里的椽子上挂着一个巨大的钟。他用一根巨大的棍子敲击钟形曲线,在商店周围发出振动。
顿时,你腋下的 FLOW 机器开始发出嘶嘶声和反向旋转声,好像刚刚输入了一组新的数字。片刻之后,更多美丽的水彩画开始从 FLOW 机器中掉出来,但它们是与您最初数字化的不同。它们类似于您原始画作的风格和形式,但每一幅都是独一无二的!
你问店主这个不可思议的装置是如何工作的。他解释说,神奇之处在于他开发了一种特殊的工艺,确保转换过程非常快速且计算简单,同时仍然足够复杂,可以将钟声产生的振动转化为复杂的图案和形状。画。
意识到这个装置的潜力后,您匆忙为设备付款并离开商店,很高兴您现在有办法生成您喜欢的风格的新画作,只需访问商店、按铃并等待您的 FLOW 机器启动发挥它的魔力!
标准化流程
归一化流是一个可逆过程,它将复杂的分布(例如一组水彩画)转换为更简单的分布(例如钟形高斯分布),反之亦然。正如我们在 Jacob 和 FLOW 机器的故事中听到的那样,这个过程的秘诀在于转换过程被限制为快速且易于计算,同时保持能够在非常简单的模型之间进行转换的基本属性。分布和非常复杂的分布。
对于本节关于规范化流的其余部分,我们将在二维中使用一个简单的示例,以便您可以准确地了解规范化流的工作细节。更复杂的示例只是我们将在本节中看到的基本技术的扩展。首先,我们需要了解一种称为变量变化的技术。
变量的变化
假设我们在二维的p_X(x)矩形上定义了X一个概率分布(x = (x_1, x_2)),如图5.1 所示。
图5.1 在两个维度上定义的概率分布p_X(x),以 2D(左)和 3D(右)显示。
此函数在分布域(即x_1范围[1,4]和x_2范围内[0,2])上积分为 1,因此它表示定义明确的概率分布。我们可以这样写:
假设我们想要移动和缩放此分布,以便将其定义在一个单位正方形上Z。我们可以通过定义一个新变量z = (z_1, z_2)和一个将每个点映射到X恰好一个点的函数f来实现这一点,Z如下所示:
注意这个函数是可逆的。也就是说,有一个函数g将 every 映射z回其对应的x. 这对于变量的变化是必不可少的,否则我们无法在两个空间之间前后一致地映射。我们可以g简单地通过重新排列定义 f的方程式来找到,如图5.2 所示。
图5.2 X在到之间更改变量Z。
我们现在需要了解变量从X到Z 的变化如何影响概率分布p_X(x)。我们可以通过代入定义为的方程式g将其转换为根据定义的p_X(x)函数p_Z(z)来做到这一点z。
但是,如果我们现在p_Z(z)对单位正方形进行积分,我们就会发现我们遇到了问题!
变换后的函数p_Z(z)现在不再是有效的概率分布,因为它仅积分到 1/6。这是一个问题,因为如果我们想将数据上的复杂概率分布转换为我们可以从中采样的更简单的分布,我们必须确保它积分为 1,否则它不再是有效的概率分布。
缺失因子 6 是由于我们变换后的概率分布的域比原始域小六倍——原始矩形的面积为 6,X而这已被压缩为Z只有面积 1 的单位正方形。因此我们需要将新的概率分布乘以归一化因子,该归一化因子等于面积(或更高维度的体积)的相对变化。
幸运的是,有一种方法可以计算给定变换的体积变化——它是变换的雅可比行列式的绝对值。让我们打开包装!
雅可比行列式
函数的雅可比矩阵z=f(x)是其一阶偏导数的矩阵,如下所示。
解释这一点的最好方法是用我们的例子。如果我们对z_1取偏导数x_1,我们得到 1/3。z_1如果我们对z_1 取偏导数z_2,我们得到 0。同样,如果我们对x_2的偏导数x_1,我们得到 0。最后,如果我们对z_2的偏导数x_2,我们得到 1/2 .
因此,我们函数的雅可比矩阵f(x)如下:
行列式仅针对方阵定义,等于通过将矩阵表示的变换应用于单位(超)立方体而创建的平行六面体的有符号体积。因此,在二维中,这只是通过将矩阵表示的变换应用于单位正方形而创建的平行四边形的带符号区域。
有一个计算矩阵行列式的通用公式n,它按O(n^3)时间运行。对于我们的例子,我们只需要二维的公式,简单如下:
因此,对于我们的示例,雅可比行列式的行列式是1/3 x 1/2 - 0 x 0 = 1/6。这就是我们需要保证变换后的概率分布仍然积分为1的1/6的比例因子!
请注意,根据定义,行列式是有符号的——也就是说,它可以是负数。因此我们需要取雅可比行列式的绝对值以获得体积的相对变化。
X我们现在可以写下一个方程来描述在和之间改变变量的过程Z。这被称为变量方程的变化,如下所示。
这如何帮助我们建立生成模型?关键是要看到如果 p_Z(z) 是我们可以很容易地从中采样的简单分布(例如高斯分布),那么理论上,我们需要做的就是找到一个合适的可逆函数,它可以从数据映射f(x)到X对应Z的g(z)可用于将采样映射z回原始域中的点的反函数, x。我们可以使用上面涉及雅可比行列式的等式来找到数据分布的精确、易处理的公式p(x)。
然而,我们首先需要解决将这一理论应用于实践的两个主要问题!
直接应用变量变化的挑战
首先,计算高维矩阵的行列式在计算上非常昂贵 - 具体来说,它是O(n^3)。这在实践中完全不切实际,因为即使是小的32 x 32灰度像素图像也有超过 1024 维。
其次,我们应该如何计算可逆函数并不是很明显f(x)。我们可以使用神经网络来寻找一些功能f(x),但我们不一定要反转这个网络——神经网络只能在一个方向上工作!
为了解决这两个问题,我们需要使用一种特殊的神经网络架构,以确保变量函数的变化f是可逆的,并且具有易于计算的行列式。
我们将在下一节中看到如何使用称为实值非体积保持转换 (RealNVP) 的技术来实现这一点。
RealNVP
RealNVP 由 Dinh 等人于 2017 年首次引入。在这篇论文中,作者展示了如何构建一个神经网络,该网络可以将复杂的数据分布转换为简单的高斯分布,同时还具有所需的可逆特性和易于计算的雅可比行列式。
要了解这是如何工作的,我们需要首先介绍一种新型层,称为耦合层。
耦合层
耦合层只是为其输入的每个元素生成比例和平移因子。换句话说,它产生两个与输入张量大小完全相同的张量——一个用于比例因子,一个用于平移因子,如图 5.3 所示。
图5.3 耦合层输出两个与输入形状相同的张量:缩放因子 (s) 和平移因子 (t)。
对于我们只有两个维度的简单示例,我们可以通过两种方式发送输入并堆叠Dense层以将输入转换为所需的格式,如示例 5-1所示。对于图像,耦合层块使用Conv2D层而不是Dense层。请注意通道数是如何暂时增加的,以便在折叠回与输入相同的通道数之前学习更复杂的表示。在原始论文中,作者还在每一层上使用正则化器来惩罚大权重。
示例 5-1 Keras 中的耦合层
def Coupling():
input_layer = keras.layers.Input(shape=2)
s_layer_1 = keras.layers.Dense(
256, activation="relu", kernel_regularizer=keras.regularizers.l2(0.01)
)(input_layer)
s_layer_2 = keras.layers.Dense(
256, activation="relu", kernel_regularizer=keras.regularizers.l2(0.01)
)(s_layer_1)
s_layer_3 = keras.layers.Dense(
256, activation="relu", kernel_regularizer=keras.regularizers.l2(0.01)
)(s_layer_2)
s_layer_4 = keras.layers.Dense(
256, activation="relu", kernel_regularizer=keras.regularizers.l2(0.01)
)(s_layer_3)
s_layer_5 = keras.layers.Dense(
2, activation="tanh", kernel_regularizer=keras.regularizers.l2(0.01)
)(s_layer_4)
t_layer_1 = keras.layers.Dense(
256, activation="relu", kernel_regularizer=keras.regularizers.l2(0.01)
)(input_layer)
t_layer_2 = keras.layers.Dense(
256, activation="relu", kernel_regularizer=keras.regularizers.l2(0.01)
)(t_layer_1)
t_layer_3 = keras.layers.Dense(
256, activation="relu", kernel_regularizer=keras.regularizers.l2(0.01)
)(t_layer_2)
t_layer_4 = keras.layers.Dense(
256, activation="relu", kernel_regularizer=keras.regularizers.l2(0.01)
)(t_layer_3)
t_layer_5 = keras.layers.Dense(
2, activation="linear", kernel_regularizer=keras.regularizers.l2(0.01)
)(t_layer_4)
return keras.Model(inputs=input_layer, outputs=[s_layer_5, t_layer_5])
1.Input我们示例中的耦合层块有两个维度
2.缩放流是一堆Dense大小为 256 的层
3.最终缩放层的大小为 2 并具有tanh激活
4.翻译流是一堆Dense大小为 256 的层
5.最终翻译层的大小为 2 并具有linear激活
6.该Coupling层被构造Keras Model为具有两个输出(缩放和平移因子)的Coupling。
耦合层本身并不是特别特殊或复杂 - 关键区别在于输入数据在通过该层之前被屏蔽的方式,从而为具有易于计算的行列式的雅可比行列式创建特定格式。
具体来说,只有数据的第一d维被馈送到第一耦合层——其余的D-d被完全屏蔽(即设置为零)。在我们的简单示例中D=2,选择d=1意味着而不是耦合层看到两个值(x_1,x_2)。层见(x_1, 0)。
该层的输出是比例和平移因子。这些再次被屏蔽,但这次使用与之前相反的屏蔽,因此只有后半部分被允许通过 - 即在我们的示例中,我们获得(0,s_2)和(0, t_2)。然后将这些按元素应用于输入的后半部分x_2,输入的前半部分x_1直接通过,根本不更新。综上所述,对于维度为 的向量D,d < D更新方程如下,如图5.4 所示。
图5.4 x通过 Coupling 层进行转换的过程。
您可能想知道为什么我们要费心构建一个屏蔽如此多信息的层更新过程!如果我们研究一下这个函数的雅可比矩阵的结构,答案就很清楚了:
左上角的d x d子矩阵只是单位矩阵,因为z_{1:d} = x_{1:d}- 这些元素直接通过而不更新。因此右上角的子矩阵为 0,因为z_{1:d}不依赖于x_{d+1:D}。
左下角的子矩阵很复杂,我们不寻求简化它。右下角的子矩阵只是一个对角矩阵,由 的元素填充\mathrm{exp}[s(x_{1:d})],因为z_{d+1:D}线性依赖于x_{d+1:D}并且梯度仅依赖于缩放因子(不依赖于平移因子)。
图5.5 显示了这种矩阵形式的示意图,其中只有非零元素用颜色填充。请注意对角线上方没有非零元素 - 因此,这种矩阵形式称为下三角矩阵。现在我们看到了以这种方式构造矩阵的好处——下三角矩阵的行列式正好等于对角线元素的乘积。换句话说,行列式不依赖于左下子矩阵中的任何复数导数。
图5.5 变换的雅可比矩阵 - 下三角矩阵,其行列式等于沿对角线的元素的乘积。
因此,我们可以将这个矩阵的行列式写成如下:
这很容易计算,这是构建规范化流模型的两个最初目标之一。另一个目标是该函数必须易于反转。
我们可以看到这是真的,因为我们可以通过重新排列正向方程来写下可逆函数,如下所示。等效图如图5.6 所示。
图5.6 反函数x = g(z)。
我们现在几乎拥有构建 RealNVP 模型所需的一切。但是,仍然存在一个问题——我们应该如何更新d输入的第一个元素?目前,模型完全没有改变它们!
堆叠耦合层
要解决这个问题,我们可以使用一个非常简单的技巧。如果我们将Coupling层堆叠在彼此之上但交替使用掩蔽模式,则一层保持不变的层将在下一层中更新。这种架构的额外好处是能够学习更复杂的数据表示,因为它是一个更深层次的神经网络。
这种耦合层组合的雅可比行列式仍然很容易计算,因为线性代数告诉我们矩阵乘积的行列式是行列式的乘积。类似地,两个函数复合的逆函数就是逆函数的复合,如下面的等式所示。
因此,如果我们堆叠耦合层,每次翻转掩码,我们就可以构建一个能够转换整个输入张量的神经网络,同时保留具有简单雅可比行列式和可逆的基本属性。总体结构如图5.7 所示。
图5.7 堆叠耦合层,将掩蔽层与每一层交替。
现在让我们通过一个具体的例子将我们所学的一切付诸实践。
下载数据
make_moons我们将用于此示例的数据集是由 Python 库中的函数创建的sklearn。这会在 2D 中创建一个嘈杂的点数据集,类似于两个新月,如图5.8 所示。
图5.8 两个维度的两个卫星数据集。
示例 5-2中给出了创建此数据集的代码。
示例 5-2 创建卫星数据集
data = datasets.make_moons(3000, noise=0.05)[0].astype("float32")
norm = keras.layers.Normalization()
norm.adapt(data)
normalized_data = norm(data)
1.制作一个包含 3,000 个点的嘈杂的、非标准化的卫星数据集
2.将数据集标准化,使其均值为 0,标准差为 1。
训练 RealNVP 模型
我们希望正向过程 ( f) 的最终输出是标准高斯分布,因为我们可以很容易地从这个分布中采样。然后,我们可以通过应用逆过程 (g ) 将从高斯采样的点转换回原始图像域,如图5.9 所示。
图5.9 在一维(中间行)和二维(底行)中复杂分布p_X(x)和p_Z(z)简单高斯之间的转换。
示例 5-3展示了如何将 RealNVP 网络构建为自定义 Keras 模型。
示例 5-3 在 Keras 中构建 RealNVP 模型
class RealNVP(keras.Model):
def __init__(self, input_dim, coupling_layers, coupling_dim, regularization):
super(RealNVP, self).__init__()
self.coupling_layers = coupling_layers
self.distribution = tfp.distributions.MultivariateNormalDiag(
loc=[0.0, 0.0], scale_diag=[1.0, 1.0]
)
self.masks = np.array(
[[0, 1], [1, 0]] * (coupling_layers // 2), dtype="float32"
)
self.loss_tracker = keras.metrics.Mean(name="loss")
self.layers_list = [Coupling(input_dim, coupling_dim, regularization) for i in range(coupling_layers)]
@property
def metrics(self):
return [self.loss_tracker]
def call(self, x, training=True):
log_det_inv = 0
direction = 1
if training:
direction = -1
for i in range(self.coupling_layers)[::direction]:
x_masked = x * self.masks[i]
reversed_mask = 1 - self.masks[i]
s, t = self.layers_list[i](x_masked)
s *= reversed_mask
t *= reversed_mask
gate = (direction - 1) / 2
x = (
reversed_mask
* (x * tf.exp(direction * s) + direction * t * tf.exp(gate * s))
+ x_masked
)
log_det_inv += gate * tf.reduce_sum(s, axis = 1)
return x, log_det_inv
def log_loss(self, x):
y, logdet = self(x)
log_likelihood = self.distribution.log_prob(y) + logdet
return -tf.reduce_mean(log_likelihood)
def train_step(self, data):
with tf.GradientTape() as tape:
loss = self.log_loss(data)
g = tape.gradient(loss, self.trainable_variables)
self.optimizer.apply_gradients(zip(g, self.trainable_variables))
self.loss_tracker.update_state(loss)
return {"loss": self.loss_tracker.result()}
def test_step(self, data):
loss = self.log_loss(data)
self.loss_tracker.update_state(loss)
return {"loss": self.loss_tracker.result()}
model = RealNVP(
input_dim = 2
, coupling_layers= 6
, coupling_dim = 256
, regularization = 0.01
)
model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.0001))
model.fit(
normalized_data
, batch_size=256
, epochs=300
)
1.目标分布是标准的二维高斯分布
2.在这里,我们创建交替掩码模式
3.Coupling`定义 RealNVP 网络的层列表
4.call在网络的主要功能中,我们在Coupling层上循环 - 如果training=True,那么我们通过层向前移动(即从数据到潜在空间)。如果training=False,那么我们通过层向后移动(即从潜在空间到数据)
5.这一行描述了依赖于direction(尝试插入direction = -1并向direction = 1自己证明这一点!)
6.我们需要计算损失函数的雅可比行列式的对数行列式只是因子的s总和
7.在我们的目标高斯分布和雅可比行列式的对数行列式下,损失函数是转换数据的对数概率的负和
分析 RealNVP 模型
一旦模型被训练好,我们就可以使用它将训练集转换到潜在空间(使用前向,f),更重要的是,将潜在空间中的采样点转换成看起来像从原始数据分布中采样(使用向后方向,g)。
图5.10 显示了在进行任何学习之前网络的输出——前向和后向只是直接传递信息,几乎没有任何转换。
图5.10 训练前的 RealNVP 模型输入(左)和输出(右),用于正向过程(顶部)和反向过程(底部)。
训练后(图5.11),前向过程能够将训练集中的点转换为类似于高斯分布的分布。同样,后向过程可以从高斯分布中抽取点并将它们映射回类似于原始数据的分布。
图5.11 训练后的 RealNVP 模型输入(左)和输出(右),用于正向过程(顶部)和反向过程(底部)。
训练过程的损失曲线如图 5.12 所示。
图5.12 RealNVP 训练过程的损失曲线。
这完成了我们关于 RealNVP 的部分——规范化流生成模型的一个特定案例。在下一节中,我们将介绍一些现代规范化流模型,它们扩展了 RealNVP 论文中介绍的思想。
其他归一化流模型
另外两个成功且重要的规范化流模型是 GLOW ( https://arxiv.org/abs/1807.03039 ) 和 FFJORD ( https://arxiv.org/abs/1810.01367 ) - 下面,我们描述了这些论文中取得的关键进展.
GLOW
在 NeurIPS 2018 上展示的 GLOW 是首批展示标准化流生成高质量样本并生成有意义的潜在空间的能力的模型之一,可以遍历该空间来操作样本。关键步骤是用可逆的 1x1 卷积层替换反向掩蔽设置。例如,将 RealNVP 应用于图像后,通道的顺序会在每一步后翻转,以确保网络有机会转换所有输入。在 GLOW 中,应用了 1x1 卷积,它有效地充当了生成模型所需通道的任何排列的通用方法。作者表明,即使有这种添加,整个分布仍然易于处理,行列式和逆矩阵易于大规模计算。
图5.13 来自 GLOW 模型的随机样本(来源:https ://arxiv.org/pdf/1807.03039.pdf )
FFJORD
RealNVP 和 GLOW 是离散时间归一化流——也就是说,它们通过一组离散的耦合层来转换输入。FFJORD(可扩展可逆生成模型的自由形式连续动力学)在 ICLR 2019 上展示,并展示了如何将转换建模为连续时间过程(即,随着流程中的步骤数趋于无穷大,采取限制) ,步长趋于零)。在这种情况下,动力学是使用常微分方程 (ODE) 建模的,其参数由神经网络 ( f_\theta) 生成。黑盒求解器用于求解时间 t_1 处的 ODE - 即找到z_1() 给定从t_0 处的高斯分布采样的一些初始点z_1,如以下方程式所述。
图5.14 FFJORD 通过常微分方程对数据分布和标准高斯之间的转换进行建模,由神经网络参数化(来源:https ://arxiv.org/pdf/1810.01367.pdf )
概括
在本章中,我们探索了标准化流模型,例如 RealNVP、GLOW 和 FFJORD。
归一化流模型是由神经网络定义的可逆函数,它允许我们通过变量的变化直接对数据密度进行建模。在一般情况下,变量方程的变化要求我们计算一个高度复杂的雅可比行列式,这对于除了最简单的例子之外的所有例子都是不切实际的。
为了回避这个问题,RealNVP 模型限制了神经网络的形式,使其遵循两个基本标准——它是可逆的并且具有易于计算的雅可比行列式。
它通过堆叠Coupling层来实现这一点,堆叠层在每一步都会产生比例和平移因子。重要的是,该Coupling层在数据流经网络时屏蔽数据,以确保雅可比行列式是下三角的,因此具有易于计算的行列式。通过翻转每一层的掩码,可以实现输入数据的完全可见性。
通过设计,缩放和平移操作可以很容易地反转,因此一旦模型被训练,就可以通过网络反向运行数据。这意味着我们可以将前向转换过程定位为标准高斯,我们可以轻松地从中采样,然后通过网络向后运行采样点以生成新的观察结果。
RealNVP 论文还展示了如何通过在耦合层内使用卷积而不是密集连接层将这种技术应用于图像。GLOW 论文扩展了这一想法,消除了掩码的任何硬编码排列的必要性。FFJORD 模型通过将转换过程建模为由神经网络定义的 ODE,引入了连续时间归一化流的概念。
总的来说,我们已经看到归一化流如何成为一个强大的生成建模系列,它可以产生高质量的样本,同时保持明确描述数据密度函数的能力。