nptel-dlcv-merge-1

NPTEL 深度学习计算机视觉笔记(二)

如果你有一个多对多RNN,你只会稍微改变这一点,因为现在你将在每个时间步都有输出,而我们在前面看到的RNN中没有。你现在可以有一个从多对多RNN出来的输出 L1,这最终是作为输出给用户的。你也可以考虑组合所有这些 L1, L2, L3, ..., Lt 来给出你的最终 L 作为输出。

为什么这很重要?如果你看,比如说,视频描述生成,这将是这种多对多架构的一个实例。你可以将 L1, L2, L3, ..., Lt 等视为保存描述每个单词的变量,而 L 是作为RNN输出给出的最终描述。

如果你考虑多对一架构,计算图将如下所示,初始部分大致保持不变,但输出 y 现在只在最后一个时间步。这完整吗?在计算图方面并不完全,你还需要连接,其中 y 可以是一个输出,它也接收 h1, h2, h3 等作为输入来计算最终输出值。

在一对多设置的情况下,你可以有这种计算图,其中输入仅在第一个时间步是 x,但你现在在每个时间步都有输出 y1, y2, y3, ..., yt。

这里的一个问题是,在中间时间步你会给出什么作为输入?为什么我们需要讨论这个?记住,F_UW 在每个时间步是相同的函数。我们已经知道在第一个时间步 F_UW 需要两个输入 x 和 H0,这意味着在每个时间步它确实需要两个输入。我们该怎么办?一个选择是,我们可以在每个时间步输入零,这可能是我们可以提供的一个选项。另一个有时也遵循的选项是,前一个时间步的输出也可以通过调整维度作为下一个时间步的输入。你可以使用前一个时间步的输出作为下一个时间步的输入。

为什么这相关?为了生成描述中的下一个单词,你可以提供描述中的前一个单词来帮助模型生成适当的下一个单词。


一个具体示例

为了给出一个更具体的例子,让我们考虑一个RNN模型。让我们假设输入和输出来自我们的词汇表 {H, E, L, O}。你的输入字符,假设是 ‘H’,表示为 1000。这样的表示也称为独热向量。独热向量是一个向量,在给定输入的位置(从词汇表中的位置)为1,其他地方为0。由于 H 是这里的第一个字符,如果你的输入字符是 H,对应的独热向量将是 1000。

给定那个作为输入,让我们假设你有一个隐藏层,一个输出层,并假设输出层有 d 组输出,在进行 softmax 后,你最终得到一组概率,其中第二个概率最高,这意味着你将预测下一个字符为 E。

现在,这个字符作为输入被提供给下一个时间步(记住我们刚刚说过 y1 作为输入被提供给下一个时间步,作为 x2)。所以 E,由于其在词汇表中第二个索引的位置,将由独热向量 0100 表示,然后这通过相同的网络传递,你现在得到一个 softmax 输出为 0.25, 0.2, 0.5, 0.05,最高的是第三个,即 L,这是RNN在该时间步的输出。

那个 L 然后再次作为输入被提供给下一个时间步,L 表示为 0010(由于 L 在词汇表中的位置)。你再次执行这个过程,它再次给出 L 作为输出。那个 L 被提供给下一个时间步,然后输出是 O。在某个时刻,整个RNN模型可以给出一个结束标记作为输出,以知道这组输出在哪里结束。所以结束标记将是词汇表中的一个默认字符,当RNN模型决定结束该特定单词的生成时,就会预测它。


总结与作业

在本节课中,我们一起学习了循环神经网络的基本概念。我们了解了RNN为何适用于序列学习问题,探索了其一对多、多对一和多对多等不同架构变体。我们深入探讨了RNN的核心循环操作,其中相同的权重和函数在每一步被重复使用以处理序列数据。此外,我们还从计算图的角度理解了RNN的前向和反向传播机制,这对于实际实现和训练至关重要。

作为作业,你的阅读材料将是《深度学习》书的第10章,Andrej Karpathy关于RNN的优秀博客文章,此外,如果你愿意,可以浏览斯坦福CS231N课程或IIT Madras的CS 7015课程。

在下一讲中,我们将讨论如何使用反向传播来训练这些RNN。在此之前,有几个问题供你思考:

  1. RNN能否有多个隐藏层?(我们已经回答了这个问题,但请思考以确保可以。)

  2. RNN的状态 H 记录了来自所有先前时间步的信息。在每个新的时间步,从某种意义上说,旧信息被当前输入稍微修改(你有一个 ht-1 到来,你有一个 Xt 到来,先前步骤的状态现在由于这个时间步到来的输入而被修改)。如果我们过多地修改状态会发生什么?或者,如果你只非常关注当前输入,而较少关注先前时间步的状态,会发生什么?思考一下,我们很快就会讨论这个问题。

053:RNN中的反向传播

在本节课中,我们将学习循环神经网络中的反向传播过程。我们将从回顾RNN的前向传播开始,然后详细推导计算权重梯度的方法,并探讨在此过程中可能遇到的梯度消失与梯度爆炸问题。

回顾RNN结构与前向传播

上一节我们介绍了RNN的基本结构。本节中,我们来看看如何计算其梯度。

一个展开的RNN可以可视化为一系列按时间步连接的模块。其前向传播公式如下:

隐藏状态更新公式:

h_t = tanh(U * x_t + W * h_{t-1})

输出计算公式:

ŷ_t = softmax(V * h_t)

其中,U、W、V 分别是输入层到隐藏层、隐藏层到隐藏层、隐藏层到输出层的权重矩阵。对于分类问题,我们通常使用交叉熵损失函数。

计算梯度:以输出权重V为例

首先,我们计算误差 E 对权重 V 的梯度。这相对简单,因为 V 直接连接到输出。

假设在时间步 t=3,我们有中间变量 z_3 = V * h_3。根据链式法则,梯度计算如下:

∂E_3/∂V = (∂E_3/∂ŷ_3) * (∂ŷ_3/∂z_3) * (∂z_3/∂V)

其中:

  • ∂E_3/∂ŷ_3 取决于损失函数(如交叉熵损失,可简化为 ŷ_3 - y_3)。

  • ∂ŷ_3/∂z_3 是softmax函数对其输入的导数。

  • ∂z_3/∂V = h_3。

要得到总误差 E 对 V 的梯度,我们需要对所有时间步的梯度求和:

∂E/∂V = Σ_t (∂E_t/∂V)

计算梯度:以循环权重W为例

接下来,我们计算误差对循环权重 W 的梯度。这更为复杂,因为 W 的影响通过时间传播。

以计算 ∂E_3/∂W 为例。W 不仅直接影响 h_3,还通过影响 h_2、h_1 等间接影响 h_3。因此,必须考虑所有路径。

根据链式法则,∂E_3/∂W 需要展开为对所有先前时间步 k 的求和:

∂E_3/∂W = Σ_{k=0}^{3} (∂E_3/∂ŷ_3) * (∂ŷ_3/∂h_3) * (∂h_3/∂h_k) * (∂h_k/∂W)

其中,∂h_3/∂h_k 项本身可以进一步展开为一系列雅可比矩阵的乘积,例如:

∂h_3/∂h_1 = (∂h_3/∂h_2) * (∂h_2/∂h_1)

因此,∂E_3/∂W 最终涉及一连串 ∂h_j/∂h_{j-1} 的乘积。总误差 E 对 W 的梯度,则是所有时间步 t 的 ∂E_t/∂W 之和。

梯度消失与梯度爆炸问题

上述计算揭示了一个关键问题:在计算 ∂E_t/∂W 时,我们需要连乘多个 ∂h_j/∂h_{j-1} 项。

以下是可能出现的两种情况:

  1. 梯度消失:如果每个 ∂h_j/∂h_{j-1} 的值都小于1(例如使用sigmoid或tanh激活函数时,其导数的绝对值通常小于1),那么多个小于1的数连乘会迅速趋近于零。这意味着,较早时间步的输入对后面时间步输出的影响梯度会变得极小甚至消失,网络难以学习长期依赖关系。

  2. 梯度爆炸:反之,如果这些梯度值都大于1,连乘会导致梯度值指数级增长,变得异常巨大,这被称为梯度爆炸。

应对策略

对于梯度爆炸问题,实践中有一个简单有效的解决方法:

  • 梯度裁剪:设定一个梯度阈值(例如10),在更新权重前,如果梯度的范数超过这个阈值,就将其按比例缩放到阈值范围内。这能有效防止参数更新步长过大。

然而,梯度消失问题无法通过简单的裁剪解决,因为它导致梯度变为零,而非过大。这需要更根本的架构改进。

课后任务与思考

本节课我们一起学习了RNN中反向传播的原理,并认识了梯度消失和爆炸这两个核心挑战。

以下是本讲的课后阅读建议:

  • 继续阅读《深度学习》第10章。

  • 学习Denny Britz的WildML RNN教程,其中对反向传播有非常清晰的解释。

留给你的思考题是:在不改变RNN整体架构的前提下,能否通过某些选择(如激活函数、权重初始化等)来缓解梯度消失问题? 我们将在下一讲讨论解决方案。


下节预告:在下一讲中,我们将探讨如何通过改进RNN架构(例如使用LSTM或GRU单元)来有效解决梯度消失问题,从而让网络能够学习长序列中的长期依赖关系。

054:LSTM与GRU 🧠

在本节课中,我们将学习如何通过改进循环神经网络(RNN)的架构来解决梯度消失问题。我们将重点介绍两种重要的变体:长短期记忆网络(LSTM)和门控循环单元(GRU)。这两种结构通过引入门控机制,有效地捕捉序列数据中的长期依赖关系。

回顾RNN的梯度问题

上一节我们介绍了RNN及其训练方法。本节中,我们来看看RNN面临的核心挑战:梯度消失问题。

RNN通过时间反向传播进行训练。这种方法的局限性在于梯度消失和梯度爆炸。梯度爆炸可以通过梯度裁剪来处理。然而,梯度消失问题则需要通过改变RNN的架构来解决。

一个关键问题是长期依赖。在某些任务中,当前输出可能依赖于很久之前的输入。例如,在句子“I grew up in France. I speak fluent French.”中,单词“French”依赖于前面出现的“France”。由于梯度在反向传播过程中会逐渐减弱,标准RNN难以捕捉这种长期依赖关系。

LSTM:长短期记忆网络

为了解决长期依赖问题,研究者提出了长短期记忆网络。LSTM在1997年被首次提出,其核心思想是引入一个“细胞状态”来充当信息传输的高速公路。

LSTM的每个时间步包含一个模块,该模块比标准RNN单元更复杂。它包含四个交互的层和三个门控机制,用于控制信息的流动。

LSTM的核心组件

以下是LSTM单元内部的主要组成部分及其功能:

  • 细胞状态:这是LSTM的关键,它像一条传送带,贯穿整个时间序列。信息可以相对不变地流过它。

  • 遗忘门:决定从上一个细胞状态中丢弃哪些信息。它查看当前输入 x_t 和上一个隐藏状态 h_{t-1},并通过一个Sigmoid层输出一个介于0和1之间的向量,该向量与上一个细胞状态 C_{t-1} 逐元素相乘。

  • 输入门:决定将哪些新信息存入细胞状态。它包含两部分:一个Sigmoid层决定更新哪些值,一个Tanh层创建新的候选值向量 \tilde{C}_t。

  • 输出门:基于细胞状态,决定下一个隐藏状态(即输出)是什么。隐藏状态包含了用于下一时间步和当前输出的信息。

LSTM的工作流程

LSTM在每个时间步的计算可以通过以下公式清晰描述:

  1. 遗忘门:f_t = \sigma(W_f \cdot [h_{t-1}, x_t] + b_f)

  2. 输入门:i_t = \sigma(W_i \cdot [h_{t-1}, x_t] + b_i)

  3. 候选细胞状态:\tilde{C}_t = \tanh(W_C \cdot [h_{t-1}, x_t] + b_C)

  4. 更新细胞状态:C_t = f_t * C_{t-1} + i_t * \tilde{C}_t

  5. 输出门:o_t = \sigma(W_o \cdot [h_{t-1}, x_t] + b_o)

  6. 最终隐藏状态:h_t = o_t * \tanh(C_t)

LSTM如何解决梯度消失?

LSTM解决梯度消失问题的关键在于细胞状态 C_t 的更新公式。从 C_{t-1} 到 C_t 的路径上,主要的操作是逐元素相乘(*)和相加(+)。梯度在通过这条路径反向传播时,主要受遗忘门 f_t 的控制,而 f_t 本身是一个向量,不涉及会导致梯度指数级衰减的权重矩阵连乘。这使得梯度能够在更长时间步上保持流动,从而学习长期依赖关系。

GRU:门控循环单元

在了解了LSTM之后,我们来看看一个更简洁的变体——门控循环单元。GRU在2014年被提出,它将LSTM中的三个门简化为两个,并合并了细胞状态和隐藏状态。

GRU的核心组件

GRU单元包含以下两个门:

  • 更新门:它决定了有多少过去的信息需要传递到未来。它融合了LSTM中遗忘门和输入门的功能。

  • 重置门:它决定了有多少过去的信息需要被忽略,用于计算新的候选隐藏状态。

GRU的工作流程

GRU的计算过程可以用以下公式表示:

  1. 更新门:z_t = \sigma(W_z \cdot [h_{t-1}, x_t] + b_z)

  2. 重置门:r_t = \sigma(W_r \cdot [h_{t-1}, x_t] + b_r)

  3. 候选隐藏状态:\tilde{h}_t = \tanh(W \cdot [r_t * h_{t-1}, x_t] + b)

  4. 最终隐藏状态:h_t = (1 - z_t) * h_{t-1} + z_t * \tilde{h}_t

LSTM与GRU的对比

以下是LSTM和GRU的主要区别:

  • 门数量:LSTM有三个门(遗忘、输入、输出),GRU有两个门(更新、重置)。GRU参数更少,训练可能更快,在数据量较少时可能表现更好。

  • 内部状态:LSTM有独立的细胞状态 C_t 和隐藏状态 h_t。GRU则将二者合并,只有一个隐藏状态 h_t。

  • 输出控制:LSTM通过输出门控制从细胞状态到隐藏状态的暴露程度。GRU的隐藏状态则是全部暴露。

  • 应用选择:通常,在处理具有明显长期依赖关系或拥有大量训练数据时,LSTM是更常见的选择。而当追求更快的训练速度、更少的参数或面对较小数据集时,GRU是一个值得尝试的优秀替代方案。

总结与展望

本节课中,我们一起学习了如何通过改进RNN架构来解决梯度消失问题。我们详细探讨了LSTM和GRU的结构、门控机制以及它们的工作流程。LSTM通过引入细胞状态和三个门控,有效地建立了梯度传播的“高速公路”。GRU则提供了一个更简洁的架构,通过两个门实现了类似的功能。

尽管LSTM和GRU在序列建模领域取得了巨大成功并成为过去几年的默认选择,但当前(课程录制时)最热门的趋势是基于“自注意力”机制的Transformer模型。它在机器翻译等任务上已经显著超越了基于RNN的模型。

为了巩固理解,请思考:GRU是如何解决梯度消失问题的?(提示:观察其隐藏状态的更新公式中,从 h_{t-1} 到 h_t 的路径。)


参考文献与扩展阅读

  • Deep Learning Book, Chapter 10

  • Understanding LSTM Networks (colah‘s blog)

  • Alex Graves’s book on RNN

  • LSTM Code Tutorial

055:使用CNN和RNN进行视频理解 🎥

在本节课中,我们将学习如何利用卷积神经网络和循环神经网络来处理和理解视频数据。视频是包含时间维度的视觉序列,理解视频需要模型能够捕捉帧与帧之间的动态变化和时序关系。

上一节我们介绍了处理时序数据的RNN,本节中我们来看看如何将CNN和RNN应用于视频理解。

为什么需要理解视频?🤔

以下是几个需要理解视频而非单帧图像的场景示例:

  • 棒球击球:要预测棒球被击中后的飞行方向,需要观察击球动作的完整序列,单帧图像无法提供足够信息。

  • 拧螺丝:要判断螺丝的旋转方向,必须观察多个连续帧才能确定其运动趋势。

因此,视频理解的核心在于结合多帧信息进行决策。

视频理解的深度学习方法 🧠

考虑到本课程聚焦于深度学习,我们将探讨基于深度学习的视频理解方法。总体而言,主要有两种思路:

  1. 仅使用CNN:通过扩展卷积操作或特殊设计来处理视频。

  2. 结合CNN与RNN:利用CNN提取空间特征,再用RNN处理时序关系。

即使在仅使用CNN的方法中,也存在两种主要方式。

3D卷积神经网络 📦

一种直观的思路是将时间视为另一个维度。图像是二维信号,视频则是三维信号(宽、高、时间)。因此,我们可以将卷积操作扩展到三维。

3D卷积:卷积核在三个维度(宽、高、时间)上滑动。这与之前处理RGB图像的2D卷积不同,在2D卷积中,卷积核的深度(通道数)是固定的,我们只在宽和高两个维度上滑动。

以下是3D CNN的一个早期经典架构示例(用于人体动作识别):

  • 输入:7帧60x40分辨率的灰度图像。

  • 第一层(硬编码滤波器):使用5种非学习的滤波器(如灰度、x方向梯度、x/y方向光流)生成33个特征图。

  • 后续层(3D卷积):执行7x7x3的3D卷积。注意,卷积操作在每种滤波器生成的特征图组内独立进行(如图中不同颜色所示),以保持特征的一致性,同时也引入不同权重的卷积核以增加多样性。

  • 其他操作:包含子采样(2x2, 3x3)、全连接层等标准CNN组件。

  • 学习方式:使用交叉熵损失,通过反向传播训练所有可学习参数。

使用这种方法,模型能够识别如“打电话”、“遗留物品”、“指向他人”等动作。

双流卷积网络 🌊

另一种思路是使用标准的2D卷积,但将时间信息作为独立的实体进行处理。双流网络是这一思路的经典代表。

该方法包含两个并行的“流”:

  1. 空间流:处理单个RGB帧,通过一个标准的2D CNN(如AlexNet)提取空间外观特征,并输出动作分类分数。

  2. 时间流:处理多帧之间的光流(optical flow)图像。光流描述了像素在连续帧间的运动(x和y方向分量)。将多个光流帧堆叠成一个“体积”,然后送入另一个2D CNN中提取时序运动特征,并输出动作分类分数。

最后,将两个流的分类分数进行融合(如平均或投票),得到最终的动作预测结果。

核心要点:此方法中的所有卷积操作仍是2D卷积。

结合CNN与RNN 🧬

我们还可以将擅长空间特征提取的CNN与擅长时序建模的RNN(如LSTM)结合起来。

以下是结合方式:

  1. 特征提取:将视频的每一帧输入到同一个CNN中(例如使用AlexNet的倒数第二层),得到每一帧的视觉特征向量。

  2. 序列建模:将这些按时间顺序排列的特征向量序列,输入到一个RNN(如堆叠的LSTM)中。

  3. 输出预测:RNN可以在每个时间步输出预测(如每帧的标签),也可以只在最后一个时间步输出整个视频的预测(如整体动作分类)。

学习方式:由于CNN和RNN都是可微分的,我们可以定义一个损失函数(例如,对整个序列或最终输出的交叉熵损失),并通过反向传播算法同时训练CNN和RNN的所有参数。

视频理解的应用场景 🚀

利用上述方法,我们可以解决多种视频理解任务:

  • 动作识别:识别视频中人物或物体的行为,如“做俯卧撑”、“攀岩”、“跳蹦床”(UCF101数据集)。

  • 活动理解:识别更复杂的日常活动,如“对着书微笑”(Hollywood in Homes数据集)。

  • 视频描述生成:为整个视频生成文字描述(字幕)。

  • 动作预测:根据已观察到的视频序列,预测接下来可能发生的动作(例如,在篮球比赛中预测传球目标)。

  • 目标跟踪:在视频中持续定位特定物体。

  • 动态场景理解:对包含动态变化的视频场景进行分类(而不仅仅是静态图像)。

总结与思考 💡

本节课中我们一起学习了视频理解的几种核心深度学习方法:

  1. 3D CNN:通过扩展卷积维度,直接在时空体积上进行特征学习。

  2. 双流网络:分别用2D CNN处理外观(空间流)和运动(时间流)信息,再进行融合。

  3. CNN+RNN:用CNN提取每帧特征,再用RNN建模帧间的时序依赖关系。

这些方法为动作识别、视频描述等任务提供了强大的解决方案。

课后思考:如果一个模型在正常顺序的视频上训练,但在测试时输入一个倒放的视频,模型还能正确工作吗?为什么?我们将在下次讨论这个问题。

扩展阅读:建议阅读关于大规模整体视频理解的教程,以了解更多任务和架构的适配方法。相关链接也提供了该领域的重要论文列表。

057:视觉与语言 - 图像字幕

在本节课中,我们将要学习图像字幕任务。我们将首先解答上一节课留下的关于自编码器与PCA关系的问题,然后深入探讨如何让计算机为图像生成描述性文字,并介绍几种先进的图像字幕方法。

自编码器与PCA的联系

上一节我们介绍了自编码器作为一种降维技术。本节中我们来看看它与主成分分析(PCA)之间的具体联系。

答案是肯定的,自编码器在特定条件下等价于PCA。让我们来理解这个联系。如果你对数据进行特定的归一化处理,使得归一化后的数据 x̂ 满足以下公式:

x̂ = (1/√m) * (x - μ)

这里的归一化操作使得每个维度的均值为零,这是一个标准的归一化步骤。进行这种归一化后,协方差矩阵由 x̂ᵀx̂ 给出,即 Cov = (1/m) * x̂ᵀx̂。

给定这个预处理,我们来看自编码器的损失函数。自编码器的损失(参数为 θ)是重建输出与原始输入之间的均方误差。我们也可以将其写为:

Loss = ||x - hW*||²_F

其中,||·||²_F 表示Frobenius范数的平方(即矩阵所有元素的平方和)。h 是自编码器中间层(瓶颈层或上下文向量)的表示,W* 是解码器的权重,因此 W h* 是从中间表示重建的输出,x 是原始输入。

为什么这样写呢?我们进一步分析。我们知道,对于这样写的损失函数,hW* 可以通过奇异值分解(SVD)表示为 UΣVᵀ,其中我们只考虑 U 和 Vᵀ 中前 k 个具有非零奇异值的列。这来自于 x 的SVD分解。

如果这确实是解,那么一种可能的因子分解是:令 h = UΣ(仅取前k列),W* = V(仅取前k列)。基于这个假设,我们可以推导出 h 是 x 的一个线性变换,变换矩阵 W 在这里是 V 的前k列。

从SVD中我们知道,给定矩阵 x 及其分解 x = UΣVᵀ,V 是 xᵀx 的特征向量矩阵。这意味着我们自编码器的编码器权重 V 正是协方差矩阵的特征向量。这应该让你联想到PCA,因为PCA的投影矩阵就是协方差矩阵的特征向量。

那么,自编码器总是PCA的变体吗?并非如此。只有当你的编码器和解码器都是线性的(即没有使用任何激活函数),并且按照上述方式对输入进行归一化,并使用均方误差作为损失函数时,自编码器学习到的编码映射才确实等同于主成分分析的投影矩阵。

图像字幕任务简介

解答了上述问题后,让我们回到本节课的主题:图像字幕。假设我们想描述一张图像,例如你在这里看到的图片。作为人类,我们能够用文字描述这些图像。我们如何确保计算机或机器学习模型也能做到同样的事情?

理想情况下,我们需要一种能够处理图像并输出描述的方法。因此,给定一些图像及其对应的描述字幕,我们将它们作为输入提供给某个方法。这个方法现在可以接收一张新图像并输出描述,例如“狗正在望向窗外”。

我们如何构建这种方法?我们需要结合视觉和语言模型:

  • 处理图像部分,使用基于视觉的深度学习模型(如CNN)。

  • 处理文本部分,使用基于自然语言处理的深度学习模型(如RNN)。

然后将它们结合起来,得到最终的输出字幕。

基础图像字幕模型

让我们详细看看这些组件。假设我们处于此类模型的训练阶段。我们有一张图像,并且知道对应的字幕是“草帽”。

以下是训练相关模型的步骤:

  1. 图像处理:将图像输入到一个卷积神经网络(CNN)中。

  2. 文本处理:使用对应的文本来训练一个循环神经网络(RNN)。

  3. 模型结合:为了学习CNN和RNN之间(即图像和文本之间)的交互,我们丢弃CNN最后的分类层,将CNN的输出表示(例如FC7层特征)也作为输入提供给RNN。

对于RNN,其隐藏状态 h_n 的更新公式(如果使用线性层加ReLU激活)通常是:

h_n = max(0, W_xh * x_n + W_hh * h_{n-1})

但现在,我们将其修改为同时接收图像特征 v:

h_n = max(0, W_xh * x_n + W_hh * h_{n-1} + W_ih * v)

其中 v 向量来自CNN的输出。

在测试时,给定一张新图像,我们将其输入CNN,并将CNN的FC7表示与一个“开始标记”一起作为输入给RNN的第一个时间步。RNN接收开始标记和图像特征,产生一个输出 y_0。y_0 然后被用作下一个时间步的输入 x_1,以生成新词 y_1,如此循环,直到生成“结束标记”,表示字幕生成完成,例如“草帽”。

在实践中,RNN并不直接“生成”单词。我们有一个词汇表(例如包含1000个词)。RNN预测的是词汇表上的一个softmax分布,我们选择概率最高的词作为输出。开始标记和结束标记是词汇表中额外添加的两个特殊词。当输入开始标记时,我们使用一个独热向量来表示它,即在该标记对应的位置设为1,其他位置设为0。

以下是这种基础方法的一些结果示例:

  • 成功案例:模型能够生成“一群人站在房间里拿着遥控器”、“一个小男孩拿着棒球棒”、“一头牛站在街道中央”等相当准确的描述。

  • 失败案例:模型可能会生成“一个男人站在墙上的钟旁边”(实际图片可能没有钟),或“一只猫坐在沙发上拿着遥控器”(实际图片可能没有猫或遥控器)。有些错误可以理解(被图像中某些元素误导),有些则更难以解释。

引入注意力机制

那么,我们如何超越简单的CNN+RNN组合来提升图像字幕的性能呢?答案我们已经知道:使用注意力机制。

现在让我们更详细地了解注意力如何用于改进这类方法。这种方法基于一篇名为《Show, Attend and Tell》的2015年ICML论文,是一个非常流行的图像字幕方法。

在该方法中:

  1. 输入图像,提取特定的特征图(保留空间信息,例如一个14x14的特征图,而不是FC7层)。

  2. 使用一个带有注意力机制的RNN来处理这个特征图。

  3. 最终进行逐词生成。

具体流程如下:

  • 给定输入图像(尺寸 H x W x 3),通过CNN得到特征图,其维度为 L x D,其中 L = H x W,代表空间位置数。

  • RNN的第一个隐藏状态 h_0 接收开始标记和该特征图作为输入,并输出一个在 L 个可能位置上的分布 a₁(一个softmax向量)。

  • 使用 a₁ 对特征图中每个位置的特征进行加权,得到加权组合特征 z₁ = Σ (a₁_i * v_i),其中 v_i 是每个网格位置的特征。

  • z₁ 和第一个词(或开始标记)一起作为输入,得到下一个隐藏状态 h₁。

  • h₁ 产生两个输出:新的位置分布 a₂ 和词汇表分布 d₁(预测的词)。

  • a₂ 用于计算新的加权特征 z₂,d₁ 预测的词作为下一个输入(或在训练时使用真实的下一个词)。

  • 此过程重复进行,生成整个字幕。

注意力模型有两种主要类型:

  • 软注意力:权重向量 a 是一个softmax向量,用于对所有网格位置的特征进行软加权求和。这是可微的,可以通过反向传播学习。

  • 硬注意力:根据softmax向量 a,选择一个得分最高的位置,仅使用该网格位置的特征。这涉及argmax操作,不可微,通常需要使用强化学习方法来训练。

注意力机制的可视化显示,在生成“鸟”这个词时,模型注意力集中在鸟的区域;而在生成“水体”时,注意力则转移到图像的其他部分。

基于《Show, Attend and Tell》论文的注意力模型结果示例如下:

  • 图像描述为“一个女人在公园里扔飞盘”,生成“飞盘”时注意力完全集中在飞盘上。

  • 图像描述为“一只狗站在硬木地板上”,生成“狗”时注意力集中在狗身上。

  • 图像描述为“一个停车标志在带有山景背景的路上”,生成“停车标志”时注意力集中在标志上。

近期图像字幕方法进展

《Show, Attend and Tell》发表于2015年,后续研究从不同角度探索了图像字幕。我们来看几个例子。

1. 结合属性的LSTM(2017年工作)

该研究尝试用属性提升图像字幕性能。给定图像 I 和字幕词序列 W。

  • 基础方法(Vanilla):图像经过变换 T_v 后输入LSTM第一步,每个时间步输入对应字幕词的变换。

  • 变体A1:不使用原始图像,而是使用从图像中提取的属性向量(如“船、水、人、骑、狗、小个子、河”及其概率)作为LSTM的初始输入。

  • 变体A2:将图像表示和属性向量依次输入LSTM(先图像,后属性)。

  • 变体A3:交换A2的输入顺序(先属性,后图像)。

  • 变体A4:初始输入为属性,每个时间步同时输入图像和字幕词。

  • 变体A5:初始输入为图像,每个时间步同时输入属性和字幕词。

研究结论表明,使用高级属性(A1)优于基础LSTM。结合图像和属性(A2)能进一步提升。先属性后图像(A3)优于A2。但将图像在每个时间步输入(A4)效果不佳,可能因为图像噪声在RNN中累积。而交换后的A5(图像初始输入,属性每个时间步输入)效果最好,说明图像表示和属性具有互补性。

2. StyleNet:改变字幕风格

该方法旨在改变字幕的语调。例如,将事实性描述“一个男人在石墙旁的岩石山坡上”改为浪漫版“一个男人用攀岩来征服陡坡”,或幽默版“一个男人像蜥蜴一样爬岩石”。

其核心是在LSTM的权重矩阵 W_x 上使用因子分解,类似于SVD分解:W_x = U S_x V。

  • 训练时,对于不同风格(事实、浪漫、幽默)的字幕,学习不同的风格矩阵 S_F, S_R, S_H,而 U 和 V 共享。

  • 测试时,给定图像,通过切换 S_x 矩阵(如使用 S_R)即可生成相应风格的字幕。

3. Neural Baby Talk(2018年CVPR)

该方法尝试结合目标检测来提升字幕生成。思路介于传统计算机视觉方法(如可变形部件模型、条件随机场)和纯深度神经网络方法之间。

流程如下:

  1. 给定图像,使用目标检测器(如Faster R-CNN)获取一系列区域提议及其特征。

  2. 将区域特征和字幕的词嵌入一起输入一个带注意力的RNN。

  3. RNN一方面生成文本输出(字幕),另一方面通过注意力决定当前关注字幕中的哪个词,并结合所有区域特征,预测该词对应的图像区域的标签。

最终生成的字幕可能包含如“一个[黄色框]戴着[红色框]坐在[蓝色框]旁,上面有[绿色框]”的占位符,然后根据检测结果将这些占位符替换为具体的物体标签(如“小狗”、“领带”、“桌子”、“蛋糕”),形成最终字幕“一只戴领带的小狗坐在放有蛋糕的桌子旁”。

总结与思考

本节课中我们一起学习了图像字幕任务。我们从自编码器与PCA的关系入手,然后介绍了基础的CNN+RNN图像字幕模型。为了提升性能,我们深入探讨了引入注意力机制的《Show, Attend and Tell》方法。最后,我们还了解了近期的一些进展,包括结合属性、改变字幕风格以及融合目标检测的先进方法。

本讲的课后作业是阅读幻灯片中提到的相关论文。如果你希望获得一些实践经验,除了课程作业外,还可以参考佐治亚理工学院发布的一个关于图像字幕的优秀Notebook。

本节课留下的思考问题是:我们现在能否做相反的事情?能否从文字描述生成图像?与我们本节课所学的内容相比,这该如何实现?请思考一下,我们下次课将继续讨论。

058:超越字幕 - 视觉问答与视觉对话

在本节课中,我们将要学习计算机视觉与语言结合领域中的两个重要任务:视觉问答和视觉对话。我们将探讨循环神经网络和注意力模型在这些任务中的关键作用,了解相关的数据集,并学习几种解决这些问题的经典模型架构。

RNN与注意力模型在视觉语言任务中的重要性

当我们关注视觉与语言相结合的问题时,RNN模型和注意力模型在计算机视觉中的相关性就变得尤为突出。

这种结合产生了许多序列学习问题。我们在上一讲中看到了其中一个例子:图像字幕生成。

现在,我们将更进一步,讨论诸如视觉问答和视觉对话这样的任务。

我们之前遗留的一个问题是:我们能否做与图像字幕生成相反的事情?即,能否从字幕生成图像?

使用我们目前见过的任何模型,我们可以做到,但并非使用我们迄今为止见过的模型。我们很快将看到生成模型,并了解如何通过它们实现从字幕到图像的生成。

视觉问答概述

在视觉问答任务中,你可以将其视为图像字幕生成任务的扩展。

任务概述如下:给定一张图像(例如你在此处看到的图像)和一个问题(例如“胡子是由什么做的?”),我们的系统(一个深度学习模型)的目标是给出答案“香蕉”。

因此,你需要理解问题,也需要理解图像,以及该问题与图像特定部分的相关性,然后才能给出合理的答案。

这是一个演示视觉问答任务如何工作的例子:给定一张图像和问题“孩子坐在哪里?”,如果你有一个针对此任务训练良好的模型,你会得到“水槽”这个答案,置信度为99%,如果你观察图像,这实际上是正确的。

这个演示来自一个名为Cloud CV的网站,由佐治亚理工学院的Dr. Bara和David Para开发。欢迎你访问此链接,在你自己的图像上尝试视觉问答。

视觉问答数据集

在我们继续讨论可以使用哪些模型来解决视觉问答问题之前,显然,我们将关注句子、循环神经网络和注意力的组合。当我们说循环神经网络时,我们也将其中的LSTM和GRU包含在同一术语中。

在讨论这些模型之前,让我们先讨论解决视觉问答问题所需的数据集类型。

到目前为止,我们需要图像分类数据集、目标检测数据集、图像分割数据集,以及某种程度上我们在上一讲中看到的图像字幕数据集。对于其中每一项,训练模型所需的数据集是显而易见的。

对于视觉问答任务,情况则更为复杂,因此让我们看看为解决此问题而开发的几个数据集。

VQA数据集

这是第一个数据集,称为VQA数据集,于2015年开发。该数据集包含图像和问题,例如你在此处图像上看到的:给定一张图像,有两个问题:“她的眼睛是什么颜色?”、“胡子是由什么做的?”;对于第二张图像:“有多少片披萨?”、“这是素食披萨吗?”;第三张图像:“这个人是在等人吗?”、“树下有什么?”;最后一张:“看起来在下雨吗?”、“这个人视力是2.0吗?”。

如你所见,对于每个问题,模型都需要理解问题以及图像,以及图像与问题之间的关系。

在这个VQA数据集中,答案是开放式的,同时也是多项选择题。这意味着模型的任务再次变成了分类。给定一组选项,模型必须输出一个在选项集上的softmax概率向量,概率最高的选项是模型的预测输出。

这个VQA数据集大约有25万张图像,这些图像来自一个名为MS COCO的数据集。MS COCO代表Microsoft Common Objects in Context。该数据集是为图像字幕任务开发的。VQA数据集扩展了该数据,外加约5万张抽象图像。总共有大约75万个问题,以及大约1000万个答案,因为每个问题都需要从多个答案中选择,每个问题由10个人工标注者回答。

COCO-QA数据集

另一个在2015年开发的数据集称为COCO-QA数据集。它同样使用了Microsoft COCO数据。Microsoft COCO数据集是一个图像字幕数据集。因此,COCO-QA数据使用这些字幕来自动生成问答对。

例如,给定这张图像,如果有一个字幕,该字幕现在被转换成一个问题和答案。问题可能是:“红色自行车上还剩下多少个甜甜圈?”,答案是“三个”。“这件T恤是什么颜色?”,答案是“蓝色”。“灰色的猫坐在哪里?”,答案是“窗户”。

你可以看到,可以从一个字幕中获得问答对。这里唯一的限制是答案是单个单词,这在许多VQA数据集中都是如此。

该数据集在12.3万张图像上有11.8万个问答对。有四种类型的问题:什么物体、多少、什么颜色和在哪里。

Visual 7W数据集

随后在2016年开发了另一个流行的数据集,称为Visual 7W。该数据集包含图像和问题,例如你在此处看到的:“这张图片中卡车上展示的濒危动物是什么?”,答案是“白头鹰”,其他选项是“麻雀”、“蜂鸟”和“乌鸦”。同样,“如果右转,司机会去哪里?”。

标题中的“7W”代表:什么、哪里、何时、谁、为什么、如何,以及最后的“哪一个”。与其他数据集相比,这是一种不同类型的问题。“哪一个”问题如下:给定左下角的图像,问题可能是“哪个枕头离窗户更远?”,答案将是此处黄色框中的内容。“哪一级台阶通向浴缸?”,答案同样是此处的黄色框。“哪个是角落里的小电脑?”,答案同样是此处的黄色框。

这就是它被称为Visual 7W的原因。该数据集在约4.7万张图像上有大约32.8万个问答对。正如我们刚才看到的,有两种任务:描述任务(我们在顶行看到的)和指向任务(我们必须指向图像的特定部分来回答问题)。

CLEVR数据集

另一个近年来更受欢迎的数据集称为CLEVR,于2017年开发。这是一个半合成的数据集,有助于回答推理问题,因此问题可能有点复杂。例如,给定左上角的图像,问题是:“在紫色球左侧的大金属物体后面的大闪亮物体后面的灰色橡胶物体有多大?”

这听起来像一个复杂的问题,但经过推理,我们谈论的是后面这个灰色物体。理想情况下,需要理解问题的几个子部分才能回答此类推理问题。

因此,CLEVR数据有大约10万张渲染图像和100万个自动生成的问题。这些问题的答案是单个单词,但要得到答案,需要非常仔细地解析问题。问题确实很复杂,需要推理能力。

视觉问答模型

在了解了用于视觉问答的几个数据集之后,现在让我们看看为解决视觉问答问题而开发的几个模型。

基线模型:LSTM + CNN特征

一个显而易见的用于视觉问答的基线模型是LSTM和从CNN获得的图像特征的组合。

给定一张图像,你有一个CNN(在此案例中是VGG网络)。在CNN的末端,你得到一个全连接层表示,在此案例中是1024维。类似地,对于问题,你通过LSTM的不同时间步传递每个单词。LSTM的输出是一个向量,同样是1024维。

你可以将这两个向量连接起来,并通过几个全连接层发送,以对答案应该是什么做出最终预测。如果你的答案在一组选项中,你将使用交叉熵损失以及你在此处看到的所有内容。

你可以通过反向传播该交叉熵损失来学习你在此处看到的网络每个部分的所有参数。因此,希望你能理解如何训练这些架构。尽管我们正在添加我们迄今为止学到的抽象概念(CNN、RNN、注意力),并尝试混合它们来解决这些问题,但希望你能理解,这些组件中没有一个违反了你如何使用反向传播来学习网络权重的方法。使用反向传播和梯度下降在所有这类架构中保持不变。

2015年的简单基线模型

另一个在2015年为视觉问答开发的基线模型是一个简单的方法,其中给定一张图像,从CNN某一层的输出中提取图像特征。对于问题,使用简单的词袋频率直方图作为文本输入。然后,将这两者连接起来,并使用一个简单的神经网络,模型预测在一组选项中哪个是正确答案。

堆叠注意力网络

在了解了这些简单的基线模型之后,现在让我们尝试看看是否可以通过更复杂的模型来改进这些基线模型。

这方面的一个努力是2016年CVPR上发表的“用于图像问答的堆叠注意力网络”。

在这个特定模型中,给定一张图像,你从CNN获得一个表示。与我们在CNN之上讨论的注意力模型非常相似,你取某个卷积层的特征图,然后能够将其映射到原始图像中的空间位置。现在,你拥有对应于图像每个部分的特征向量。

现在,你获取问题并通过LSTM传递它(如果你愿意,也可以进行卷积,我们将在几页幻灯片后看到)。LSTM的输出为你提供了问题的表示。

这个问题的表示被传递给图像不同部分的特征向量。在图像的特征向量和问题表示之间,模型在图像的不同部分上给我们一个特定的注意力图。

基于这个注意力图和问题的表示,在图像不同部分的特征向量上再次执行另一层注意力。这导致了一个不同的表示,我们期望这是最终的解决方案,该表示最终被送到输出层,在答案选项上的softmax给出最终结果。

让我们看一个可视化示例。在这种情况下,如果你有原始图像,堆叠注意力网络所做的第一层注意力层专注于问题中提到的所有概念。

因此,我们在上一张幻灯片中看到,第一层注意力层决定关注图像的哪些部分,它关注的是问题中存在的概念所在的任何部分。所以,在这种情况下,可能是“自行车”、“篮子”和“篮子里的物体”。这里的问题是“自行车篮子里坐着什么?”

第二层注意力层,基于你在第一层注意力层中获得的注意力,选择只关注包含答案的图像部分。这最终帮助我们预测结果。因此,在第二步中,你将注意力的焦点缩小到仅包含答案的图像部分,因此,模型可以在选项集中将答案预测为“狗”。

分层协同注意力模型

这引出了一个后续问题:在我们刚刚看到的模型中,我们只对图像执行了注意力。我们将问题表示视为既定,并尝试基于注意力使用问题表示与图像不同部分之间的关系。

因此,即使在我们刚刚在上一张幻灯片中使用的注意力中,它也是软注意力,通过学习的某些权重对不同部分进行加权,这可以促进反向传播。

但现在我们问一个问题:我们是否也可以在问题中引入注意力?这在一个称为“分层协同注意力模型”的模型中完成,该模型于2016年在ECCV上发表。

在这个特定模型中,我们具有协同注意力,这意味着我们同时关注图像的部分以及问题的部分。分层部分来自于我们首先获得问题在单词级别的嵌入,然后在短语级别,最后在整个问题本身。

让我们更详细地看看这一点。给定一个问题,我们提取其单词级别嵌入、短语级别嵌入和问题级别嵌入。此时,你可以假设这是通过某些神经网络传递得到的。

那么,这些情况下的输入是什么?你真的将文本作为输入吗?不完全是。请记住,对于所有涉及文本的问题,在很大程度上,你有一个词汇表,问题中的每个单词对应于词汇表上的一个独热向量,你在该单词所属的词汇表索引处放置一个1,其他地方为0。这将是特定单词的输入。

因此,你提取单词级别、短语级别和问题级别的嵌入。在每个级别,你应用协同注意力。所以,对于一个单词级别的嵌入,你对问题以及相应的图像应用注意力。类似地,对于短语级别和问题级别。

最终答案基于所有协同注意的图像和问题特征。

因此,如果你仔细看这里,当我们说“红绿灯上哪个灯亮着?”时,你可以看到这里是问题,你期望的理想答案是“绿色”。你首先取单词级别的嵌入,并关注其中的一部分,在这种情况下,你可以看到注意力集中在“stop”上。同时,图像上的注意力也似乎集中在交通灯周围的某个地方。

在层次结构的下一个更高级别,注意力集中在问题中的不同短语上,在这种情况下,你可以看到最大权重给了短语“the stop light”。再一次,你可以看到图像现在更有信心,完全集中在红绿灯上。

然后,在问题层次结构的最高级别,注意力图告诉我们我们关注的是“light”,在那个阶段,图像上的注意力只集中在交通信号灯内的灯上,从而得出答案“绿色”。

让我们更详细地理解一下问题层次结构。在单词级别,你获得单词级别的嵌入,正如我们刚才看到的。我们如何获得短语级别和问题级别的嵌入?为了获得短语级别的嵌入,你可以取字幕每个单词的嵌入,用不同宽度的多个滤波器对这些输入执行卷积,你会得到向量表示,这就是我们所说的短语级别嵌入。然后,我们在卷积层上执行最大池化,然后通过LSTM将最大池化输出传递给我们,从而获得问题级别的嵌入。这为我们提供了问题级别的表示。

这里有一些结果。你在这里看到的图像和问题是:“那个拿着滑雪板站在雪覆盖的什么上面的男人?”,答案是“山”。另一个例子是:“鸟是什么颜色?”,答案是“白色”。

这里是单词级别的协同注意力图。在这种情况下,问题中被关注的部分是“snowboard”、“top”、“snow covered”,而图像焦点在那一刻有点在滑雪板上,有点在人身上。在这个例子中,焦点是“colour of bird”,模型建议关注图像中鸟的部分。

对于短语级别的协同注意力,在这种情况下,焦点是“holding snowboard”、“snowboard on top of snow covered”,图像中的焦点(红色表示高,蓝色表示低,类似于我们看到的解释方法中的热图),所以你可以看到模型关注的是人物以外的图像其他部分。同样,对于“鸟是什么颜色?”,模型似乎关注鸟周围的区域,以便能够分离颜色。

最后,当模型达到问题级别的注意力图时,“那个拿着滑雪板站在雪覆盖的什么上面的男人?”,模型观察人物以外的一切,以便得到答案“山”。在另一个例子中,“鸟是什么颜色?”,模型准确地观察鸟以给出答案“白色”。

视觉对话

现在我们将继续讨论本讲中计划讨论的第二个任务,你可以将其视为视觉问答的扩展,即视觉对话。

任务概述在此处的插图中给出,这首次在2017年完成。给定这张图像和给定的字幕“一只猫从咖啡杯中喝水”,视觉对话就像一个聊天机器人,但这是一个根据图像提问和回答问题的聊天机器人,而不是简单的自由文本。

让我们看一个例子:“杯子是什么颜色?”机器人回答“白色和红色”,所以你可以看到那里的杯子。“上面有图案吗?”“没有,有东西,但看不清是什么。”“杯子和猫在桌子上吗?”“是的,它们在。”“桌子上还有其他物品吗?”“是的,有杂志、书、烤面包机和篮子和一个盘子。”如你所见,背景中有一个烤面包机,有一个篮子,这对于一台机器来说是一个相当全面的答案列表。

这是一个演示视觉对话如何工作的例子:给定这张图像,机器人说“一个男人和一个女人拿着伞”。人类可以问的问题:“他的伞是什么颜色?”机器人回答“他的伞是黑色的。”“她的呢?”这是一个棘手的问题,因为模型必须理解问题是关于伞的,其次问题是关于她的伞,这意味着模型需要知道有两个人,一个是男性,一个是女性,然后才能回答这个问题,机器人确实说“她的伞是彩色的。”“图像中还有多少人?”“我想是三个,他们被遮挡了。”再次说明,这个演示来自Cloud CV,由佐治亚理工学院的Dr. Bara和David Para开发。欢迎你访问此链接,尝试视觉对话,并亲自看看它是如何工作的。

视觉对话任务定义与评估

现在让我们正式定义如何解决这个任务。这不像传统的分类问题,甚至不像传统的序列学习问题,在那里你为图像输出一个字幕。那么,你将如何形式化并解决它?

给定一张图像 I,一个对话历史,包含问答对 (Q1, A1), (Q2, A2), ..., (Q_{t-1}, A_{t-1}),这些是你之前的问答对。你有一个后续问题,即当前时间的问题 Q_t(在这个例子中,问题是“另一个拿着什么东西吗?”)。这里的任务是产生一个自由形式的自然语言答案 A_t 来回答这个当前问题 Q_t。

那么,这里的一个问题是:你如何评估答案?这里的评估可能很棘手。自然语言处理中常用的分数,如BLEU(我们之前见过)、METEOR、ROUGE等(我们现在不深入讨论),但众所周知,这些分数通常与人类判断相关性较差。例如,你可以有以不同方式表达的多个答案来回答一个问题,但并非所有答案都能与一个单一的真实答案的分数相关联。

那么,如何研究这个问题?你可能需要进行所谓的“人类图灵测试”,即给出一个生成的句子,然后让一群人阅读句子的质量。这可能有效,因为基于人类的判断,这被称为人类图灵测试,以检查人类是否能分辨出答案是由机器生成的还是由人类生成的。因此,除了评估答案的质量外,你还可以将其作为询问人类的问题之一引入。

这里的挑战在于,这种评估可能变得昂贵,因为你可能需要补偿人类参与评估。而且,根据评估系统的人类不同,它也可能是主观的,不同的人类可能对不同的答案有不同的评价。

那么,我们如何评估这样的系统呢?让我们现在来看看。给定一张图像、对话历史和当前问题,视觉对话数据集所做的是给出大约100个答案选项。这些答案选项可能是:50个最相似问题的答案、30个热门答案,以及可能20个随机答案。你挑选这100个答案,并要求你的模型对所有答案进行排序。因此,模型的任务是给定这100个答案,你可以再次拥有一个softmax,它在这100个选项上给你一个概率向量。但使用该概率向量,你现在可以对选项进行排序:最高概率将是第一排名,第二高概率将是第二排名,依此类推。

但你如何判断这种排序的好坏呢?你可以做几件事来衡量性能:

  1. 你可以检查相对于真实答案的平均排名。你期望模型将第一个答案作为真实答案,但这可能并不总是发生。在一种情况下,你可能得到排名第三的答案是真实答案;在另一种情况下,你可能得到排名第七的答案是真实答案;在一种情况下,你排名的第41个答案可能是真实答案,这将是一个糟糕的结果。因此,在所有测试样本中,相对于真实答案的平均排名可能是你可以使用的一个性能指标。

  2. 另一个也使用的指标称为平均倒数排名。平均倒数排名可以这样计算:对于一个特定的测试问题,如果你预测了一组选项的排名,并且你预测的第三个位置是正确答案,这意味着你的正确排名是3。假设对于另一个问题,你的第二个排名选项是正确答案;对于另一个问题,你的第一个排名选项是正确答案。那么你的平均倒数排名将由 (1 + 1/2 + 1/3) / 3 给出,因为这是你拥有的总问题数,计算结果约为0.61。当你的第一个排名选项总是真实答案时,平均倒数排名将为1。当你的最后一个排名选项是你的真实答案时,它将为0。因此,平均倒数排名是另一个你可以实际用来研究性能的性能指标。

视觉对话模型

现在让我们来看看可以用来执行视觉对话任务的模型类型。它再次是编码器-解码器框架,其中编码器可以是晚期融合编码器、分层循环编码器或记忆网络编码器,解码器可以是生成式的或判别式的。我们将在接下来的几张幻灯片中看到其中一些例子。

晚期融合编码器

这是一个晚期融合编码器的例子。在这种情况下,图像是你在此处看到的这张。你有先前的问题和答案历史:“那个男人正在人行道上骑自行车。”“那个男人戴着头盔吗?”“不,他没有戴头盔。”“附近有人吗?”“是的,有一个女人走在他后面。”这是对话历史。当前问题 Q_t 是:“你认为那个女人是和他一起的吗?”所以我们现在必须给出这个问题的答案 A_t。

因此,你将图像提供给CNN,将当前问题提供给LSTM,并将整个历史连接起来作为单个文本输入提供给另一个LSTM。你融合或连接这些模型(CNN、当前问题LSTM和历史LSTM)的输出。然后,你可以在它们之后添加层以减少该最终表示的维度,并将其提供给解码器以获得最终答案。我们稍后将讨论解码器,但此刻我们正在讨论用于视觉对话的模型的编码器。这是一种称为晚期融合编码器的模型。

分层循环编码器

另一种编码器是分层循环编码器。你再次将图像通过CNN获得表示,将当前问题传递给LSTM。但现在这个LSTM也将图像表示作为输入。然后,对于对话历史的每一轮,你通过LSTM的一个实例发送它,并为历史中的每个步骤获得不同的表示。现在你有另一个LSTM,它结合了来自先前对话历史、当前问题和图像的这些输入。该下一层LSTM的输出被传递给解码器以获得输出。因此,这被称为分层循环编码器,因为你有一个LSTM来获取图像、当前问题和对话的输入,并且你有另一个LSTM在传递给解码器之前结合这些输入。

记忆网络编码器

第三种编码器是记忆网络编码器,它使用注意力思想。再次将图像通过CNN,问题通过LSTM,连接表示,压缩表示,这是第一部分。再次,历史中的每个对话都通过同一LSTM的不同实例,你为对话历史中的每个问答获得不同的表示。现在,你还引入当前图像-问题表示以及历史的表示,你现在在这里查看一个注意力步骤:你看到对历史的注意力,以决定对话的哪个部分与回答当前问题相关。请记住,对于一个长对话,你可能需要引用三个回合前陈述的一个句子来回答当前问题,使用注意力思想给了我们这种方法。因此,你取当前问题和图像的内积,这给了你对齐的概念(还记得我们在讨论注意力时谈到对齐,在编码器-解码器模型中,我们讨论了解码器中隐藏状态与编码器中隐藏状态的对齐)。我们说过,你可以通过对齐的一种方式是通过点积或内积。所以,你在这里看到的是检查当前图像和问题与历史之间对齐的注意力,以获得历史上的注意力图,这是一个加权和。然后,你将其与图像和当前问题的当前特征向量结合,然后将其传递给解码器以获得最终答案。

再次提醒你,你在此处看到的每个组件都是可微分的,你可以通过反向传播步骤学习所有这些。即使是注意力或历史,也仅仅是输入的加权组合,可以通过反向传播。

解码器:生成式与判别式

谈到此类模型的解码器部分,正如我们所说,解码器可以是生成式的或判别式的。

在生成式解码器中,例如,在训练期间,你将最大化真实答案序列的对数似然。你希望你的解码器生成正确的答案序列。在训练期间,你最大化生成该真实答案序列的对数似然。再次说明,当你实际实现它们时,这些将被实现为:如果你有一个解码器LSTM,在每个步骤你将输出一个单词,该单词将在词汇表上给出交叉熵,你将在LSTM中有多个时间步,这些交叉熵的总和将是你的损失。或者在这种情况下,是对数似然。请记住,最小化交叉熵等同于在这种情况下最小化负对数似然。在测试或评估时,你使用模型在词汇表上的对数似然分数对你的候选答案进行排序。

而在判别式解码器中,你获取输入编码,并计算输入编码与每个答案选项的LSTM编码之间的点积相似度(你可以在此处使用任何编码方法,我们不会深入讨论,你可能需要一些NLP知识来理解如何获取文本编码,但让我们现在假设你可以获得一些编码,有诸如Word2Vec、GloVe等嵌入,你可以使用它们来获取答案选项和输入编码的编码)。现在你计算点积,并将该点积输入softmax以计算所有答案选项的后验概率。在训练期间,你将最大化正确选项的对数似然。在测试或评估时,选项仅根据其后验概率(你将作为softmax的输出获得)进行排序。

这就是你如何为编码器-解码器模型建模解码器以解决视觉对话任务。

让我们看几个例子。这里有一张图像。初始字幕是“滑雪者站在山顶上”,然后你有对话:“有多少滑雪者?”“数百人。”“他们准备好了吗?他们准备下山了吗?”“我想是的,我的视角在队伍的末尾。”“在下雪吗?”“不,不过有很多雪。”“你能看到有人下山吗?”“不,我的视角显示人们穿着滑雪板走上小山,我看不到那里发生了什么。”等等,如你所见,这是一个有意义的对话。

这是另一个供你闲暇时观看的视觉对话示例。如果你想了解更多,可以阅读这些论文,其中一些是最近的,我们在本讲中没有机会涵盖,我们涵盖了最基本的方法,但如果你想了解更多,可以关注其中一些最近的论文。

我们将在这里留下一个问题:你能想出更好的方法来评估视觉对话系统吗?思考一下,这可能不是一个简单的答案,思考一下,我们下次再讨论。

总结

在本节课中,我们一起学习了计算机视觉与自然语言处理交叉领域的两个高级任务:视觉问答和视觉对话。我们了解了RNN和注意力模型在这些任务中的核心作用,探讨了VQA、COCO-QA、Visual 7W和CLEVR等关键数据集。我们学习了从简单的LSTM+CNN基线模型到更复杂的堆叠注意力网络和分层协同注意力模型等多种VQA解决方案。对于视觉对话,我们理解了其任务定义、独特的评估挑战(如平均排名和平均倒数排名),并学习了晚期融合、分层循环和记忆网络等编码器架构,以及生成式和判别式解码器。这些内容展示了如何将我们已学的CNN、RNN和注意力机制组合起来,解决需要同时理解视觉内容和自然语言的复杂问题。

以下是一些参考文献。

059:其他注意力模型 🧠

在本节课中,我们将学习注意力机制在深度学习中的其他几种重要应用。我们将探讨三种不同的模型:神经图灵机、深度循环注意力写入器和空间变换网络。这些模型以非常不同的方式使用或实现了注意力机制,并对计算机视觉领域产生了深远影响。


神经图灵机 🧠

上一节我们介绍了视觉问答和视觉对话中的注意力应用。本节中,我们来看看注意力机制如何被建模并用于其他任务,首先从神经图灵机开始。

神经图灵机是一类旨在执行类似于图灵机任务的神经网络。与图灵机类似,NTM 拥有一个内存,并通过读写操作来访问内存以执行后续任务。然而,与图灵机不同,NTM 需要固定大小的内存,以便将其视为神经网络并学习操作。

下图是神经图灵机的高层可视化。它包含一个控制器和一个内存矩阵。控制器通过读写头来访问内存,进行读取和写入操作。

控制器是一个神经网络,它执行对内存的读写操作。内存本身是一个 R x C 的矩阵,包含 R 行,每行有 C 维。

现在,我们提出一个问题:能否通过反向传播端到端地训练一个像图灵机那样通过指定行和列索引(例如,读取第三行第十列)来执行读写操作的 NTM?答案是否定的。因为无法对索引进行求导,这类似于硬注意力机制中的“硬选择”问题。

为了解决这个问题,NTM 采用了与软注意力非常相似的思想。它使用一个软加权函数作用于内存矩阵的所有位置,而不是选择一个特定的索引。这使得 NTM 可以通过反向传播和梯度下降进行训练。

以下是 NTM 核心组件的详细说明:

  • 内存矩阵:在时间步 t 表示为 M_t,维度为 R x C。

  • 归一化注意力向量:用于从内存中读取,由一组权重 w_t(i) 给出,所有权重之和为 1,且每个权重在 0 到 1 之间。

  • 读头操作:读头获取的内容是内存矩阵各行的加权和。如果内存的第 i 行是 M_t(i),对应的注意力权重为 w_t(i),那么读头读取的内容 r_t 为:

    r_t = Σ_i w_t(i) * M_t(i)

  • 写头操作:写操作分为两步。首先,使用擦除向量 e_t 擦除部分旧内存。然后,添加一个由加向量 a_t 加权后的新内容。更新公式为:

    M_t(i) = M_{t-1}(i) * [1 - w_t(i) * e_t] + w_t(i) * a_t

那么,如何获得注意力向量 w_t 呢?NTM 使用了两种寻址方式:基于内容的寻址和基于位置的寻址。

以下是获取注意力向量的步骤:

  1. 基于内容的寻址:每个读写头在时间步 t 有一个关键向量 k_t。通过计算 k_t 与内存矩阵 M_t 每一行的余弦相似度,得到一个相似度分数。最终的注意力权重通过 softmax 函数计算,并引入一个键强度因子 β_t 来控制权重的集中程度。

    w_t^c(i) = exp(β_t * cosine_sim(k_t, M_t(i))) / Σ_j exp(β_t * cosine_sim(k_t, M_t(j)))

  2. 插值:将当前基于内容的注意力权重 w_t^c 与上一时间步的注意力权重 w_{t-1} 进行插值,插值参数为 g_t。

    w_t^g = g_t * w_t^c + (1 - g_t) * w_{t-1}

  3. 卷积移位(基于位置的寻址):对插值后的权重向量 w_t^g 执行一个循环卷积操作,卷积核为 s_t。这允许模型根据控制器的需要,在内存中移动注意力焦点。

    w_t^s = circular_convolution(w_t^g, s_t)

  4. 锐化:最后,使用一个标量 γ_t 对权重进行锐化,使高权重更高,低权重更低,从而得到最终的注意力向量 w_t。

    w_t(i) = (w_t^s(i))^{γ_t} / Σ_j (w_t^s(j))^{γ_t}

总结来说,神经图灵机的工作流程是:首先进行基于内容的寻址,然后与上一步注意力权重插值,接着进行基于位置的卷积移位,最后锐化权重以决定在特定时间步关注内存的哪一部分。这是一种与我们之前所见不同的注意力应用方式。


深度循环注意力写入器 🎨

接下来,我们看看本讲座要讨论的第二种方法:深度循环注意力写入器。这项 2015 年的工作基于一个直观想法:人类生成图像(如绘画和素描)是循序渐进的,先勾勒轮廓,再迭代添加细节。

DRAW 使用了一种称为变分自编码器的模型(我们将在后续课程中详细学习),来模拟这种逐步生成图像的方法。我们在这里讨论它,是因为它使用了一种独特的注意力机制。

下图展示了 DRAW 方法的工作原理。图像是逐步生成的,而不是一次性生成。每一行代表一次迭代生成。初始步骤是一张空白画布。模型通过一个红色方块所示的注意力区域聚焦于画布的特定部分,在该区域绘制内容,然后更新画布。在下一个时间步,模型聚焦于图像的另一个区域并绘制。经过多次迭代,模型最终生成了完整的图像(这里是一个 MNIST 手写数字)。

DRAW 的编码器和解码器都是循环神经网络。其整体架构图类似于自编码器。图像在 T 个时间步内绘制在一个画布矩阵 C 上。DRAW 受 NTM 启发,也包含读写头,用于聚焦图像的特定部分进行读取和写入。

以下是 DRAW 框架的主要组件和工作流程:

  • 编码器 RNN:接收四个输入:输入图像 x、残差图像 x̂、上一时间步编码器的隐藏状态、上一时间步解码器的隐藏状态。其中,x̂ = x - σ(C_{t-1}),σ 是 sigmoid 函数,C_{t-1} 是上一时间步的画布。x̂ 代表了画布尚未关注到的图像部分。

  • 读取模块:读取模块 r_t 接收 x, x̂ 和解码器上一时间步的隐藏状态 h_{t-1}^{dec},决定在当前时间步读取图像的哪一部分。

  • 解码器 RNN 与写入:从编码器产生的瓶颈层采样一个向量,传递给解码器 RNN。解码器 RNN 使用其上一时间步的隐藏状态和采样向量。写入模块的输出被添加到画布上:C_t = C_{t-1} + write_output_t。

现在,让我们深入了解作为该框架注意力机制的读写模块是如何工作的。

DRAW 的目标是聚焦于图像的特定区域。由于不能只聚焦于一个单一区域(会导致不可微分的问题),它采用了两步法来将权重分布在整个图像上。

以下是 DRAW 注意力机制的关键步骤:

  1. 预测网格参数:模型预测一组滤波器参数,这些参数定义了一个覆盖在图像上的网格。网格的中心坐标 (g_x, g_y)、网格点之间的步长 δ 都是通过学习得到的。

  2. 放置高斯滤波器:在每个网格点位置上,放置一个高斯滤波器。高斯滤波器的方差 σ^2 以及一个强度因子 γ 也是通过学习得到的。

具体来说,模型预测的是归一化的值 (g̃_x, g̃_y, δ̃),然后将它们转换到实际图像坐标 (A x B) 上:

  • g_x = (A + 1)/2 * (g̃_x + 1)

  • g_y = (B + 1)/2 * (g̃_y + 1)

  • δ = max(A, B) * δ̃ / (N - 1),其中 N 是网格大小(如 3x3 网格则 N=3)。

网格中第 (i, j) 个点的位置 (μ_x(i), μ_y(j)) 由下式给出:

  • μ_x(i) = g_x + (i - N/2 - 0.5) * δ

  • μ_y(j) = g_y + (j - N/2 - 0.5) * δ

在这些位置放置高斯滤波器 f_x, f_y(方差为 σ^2)。强度因子 γ 控制绘制或读取的浓淡程度。

  • 读取操作:最终,读取函数实现为高斯滤波后的输入图像与残差图像的拼接,并乘以强度因子 γ。

  • 写入操作:写入操作是读取的互补形式,其顺序是转置的,以确保维度匹配和互补性。

在每个时间步,通过学习到的网格位置、步长和高斯滤波器,模型决定了关注图像的哪个区域。解码器的输出被绘制在画布的对应区域上。通过多个时间步的迭代,最终生成完整的图像。


空间变换网络 🔄

本讲座要讨论的第三种方法是空间变换网络。这又是一种非常不同的利用注意力的方法,旨在为 CNN 带来注意力机制的价值。

其核心思想是:CNN 本身可能缺乏空间不变性。如果一个物体以不同方式旋转或位于不同角落,CNN 可能无法很好地处理。最大池化操作在一定程度上有所帮助,但主要是在网络深层处理小邻域内的变化。空间变换网络是一个完全可微分的模块,可以插入到任何 CNN 中,通过注意力机制来解决特定问题。

下图展示了空间变换网络的工作原理。第一列是包含各种变换(缩放、旋转、平移)和噪声的输入图像。第二列显示了空间变换网络学习到的注意力区域(红色框和方向)。第三列是经过空间变换网络校正后的图像,数字被居中并标准化。第四列展示了经过校正后,CNN 能够进行更好的分类。

空间变换网络架构包含三个可微分模块,因此可以集成到任何 CNN 中(例如,插入在 Conv4 和 Conv5 之间)。

以下是三个模块的详细说明:

  1. 定位网络:输入一个特征图 U(维度 H x W x C),输出一组变换参数 θ。例如,对于仿射变换,θ 是一个 6 维向量。定位网络本身可以是一个小型 CNN 或全连接网络。

  2. 网格生成器:利用变换参数 θ,建立输出特征图 V(维度 H' x W' x C)中每个点 (x_i^t, y_i^t) 与输入特征图 U 中对应采样点 (x_i^s, y_i^s) 之间的映射关系。对于仿射变换,关系为:

    (x_i^s, y_i^s)^T = A_θ * (x_i^t, y_i^t, 1)^T

    其中 A_θ 是由 θ 参数化的 2x3 仿射变换矩阵。

  3. 采样器:这是实现可微性的关键步骤。对于输出 V 中的每个位置,根据网格生成器计算出的采样坐标 (x_i^s, y_i^s),从输入 U 中采样值。由于 (x_i^s, y_i^s) 通常是浮点数,需要可微的采样方法。常用的是双线性采样:

    V_i^c = Σ_n^H Σ_m^W U_{nm}^c * max(0, 1 - |x_i^s - m|) * max(0, 1 - |y_i^s - n|)

    这个核函数只在 (m, n) 接近 (x_i^s, y_i^s) 时才有非零值,并且关于 U 和 (x_i^s, y_i^s) 都是可微的,从而允许梯度在整个网络中反向传播。

通过这三个模块,空间变换网络能够学会对输入特征图进行适当的空间变换(如裁剪、旋转、缩放),将感兴趣的区域标准化,从而提升后续 CNN 层的处理效果。


总结 📚

本节课我们一起学习了注意力机制在三种不同模型中的应用:

  1. 神经图灵机:将软注意力机制应用于外部内存的读写,实现了可微分的基于内容与位置的寻址。

  2. 深度循环注意力写入器:使用可学习的高斯滤波网格作为注意力机制,逐步、迭代地生成图像,模仿人类的绘画过程。

  3. 空间变换网络:通过一个完全可微分的模块(定位网络、网格生成器、采样器)实现对输入特征图的空间变换,使 CNN 能够主动“关注”并标准化图像中的关键区域。

这些模型展示了注意力机制的灵活性和强大能力,它不仅能用于序列到序列的任务,还能与内存交互、指导图像生成,以及增强 CNN 的空间不变性。

扩展阅读建议:

  • Lilian Weng 关于注意力的博客。

  • Jonathan Hui 关于 DRAW 的简介和概述。

  • Sik-Ho Tsang 关于空间变换网络的综述。

060:自注意力与Transformer 🧠

在本节课中,我们将学习一个近年来变得极其热门的话题:自注意力机制与Transformer模型。我们将探讨它们如何克服传统循环神经网络(RNN)的局限性,并理解其在序列建模,乃至计算机视觉任务中的核心工作原理。

概述

上一节我们讨论了视觉对话系统的评估方法。本节中,我们将目光转向一种全新的序列建模架构——Transformer。它完全摒弃了循环结构,仅依靠注意力机制,在并行计算和长序列依赖建模方面展现出巨大优势。

为什么需要超越RNN?🤔

在深入Transformer之前,我们首先需要理解其诞生的动机。传统的RNN或LSTM在处理序列时存在两个主要问题:

  1. 顺序计算阻碍并行化:RNN必须按时间步顺序处理输入。在一个由线性层、LSTM层交替组成的深度架构中,这种顺序性使得整个处理流程无法并行化,限制了计算效率。

  2. 有限的长程依赖建模能力:尽管GRU和LSTM通过门控机制缓解了梯度消失问题,但它们仍然难以在非常长的序列(如一整章书)中维持所有关键信息。实际上,在许多任务(如视觉问答)中,我们已经引入了注意力机制来聚焦于序列的相关部分。

既然注意力机制本身就能告诉我们生成特定输出时应关注输入序列的哪个部分,那么我们是否还需要RNN来维护一个压缩的隐藏状态呢?这就是Transformer提出的核心假设:仅用注意力就足够了。

Transformer核心架构 🏗️

Transformer首次在2017年的论文《Attention Is All You Need》中提出。它是一个完全基于注意力机制的编码器-解码器框架,用于序列到序列的建模。其关键组件包括:

  • 自注意力机制

  • 多头注意力

  • 位置编码

  • 编码器-解码器堆叠结构

下图展示了Transformer的整体架构,我们将在后续逐一解析其组成部分。

自注意力机制详解

自注意力是Transformer的独特之处。考虑两个句子:

  1. “The animal didn't cross the street because it was too tired.”(动物没有过马路,因为它太累了。)

  2. “The animal didn't cross the street because it was too wide.”(动物没有过马路,因为它太宽了。)

对人类而言,第一个“it”指代“animal”,第二个“it”指代“street”。但对基于RNN的模型来说,捕捉这种指代关系是困难的。自注意力机制允许模型在处理某个词时,评估序列中所有其他词的重要性。

之所以称为“自”注意力,是因为当处理序列中的一个元素时,模型会关注同一序列中的其他部分。这消除了对RNN那种压缩整个序列历史信息的隐藏状态的需求。

以下是自注意力计算的具体步骤:

第一步:创建查询、键、值向量

给定输入词嵌入向量 x(例如,“thinking”和“machines”分别对应 x1 和 x2),我们通过三个可学习的权重矩阵 WQ、WK、WV 将其线性变换为三组向量:

  • 查询向量 (Query): q = x * WQ

  • 键向量 (Key): k = x * WK

  • 值向量 (Value): v = x * WV

在原始论文中,x 的维度是512,而 q、k、v 被设计为64维,这主要是为了后续多头注意力计算的便利,并非强制要求。

第二步:计算注意力分数

以处理第一个词“thinking”为例,我们计算其查询向量 q1 与序列中所有词(包括自身)的键向量 k 的点积,得到一组分数。这些分数衡量了其他词对当前词的重要性。

分数 = q1 · k_i (i 为序列中词的索引)

第三步:缩放与归一化

将上一步得到的分数除以键向量维度 d_k 的平方根(本例中 sqrt(64)=8),然后应用Softmax函数,将分数转化为概率分布(权重)。这一步称为缩放点积注意力。

缩放后分数 = (q1 · k_i) / sqrt(d_k)

权重_i = Softmax(缩放后分数_i)

缩放操作有助于稳定梯度,防止点积结果过大或过小导致训练不稳定。

第四步:加权求和输出

将每个词的权重与其对应的值向量 v_i 相乘,然后将所有结果求和,得到当前词“thinking”经过自注意力处理后的输出向量 z1。

z1 = Σ (权重_i * v_i)

同理,我们可以计算出序列中每个词的输出向量 z。

多头注意力机制 🎯

上一节我们介绍了单一的自注意力计算。然而,只使用一组 WQ、WK、WV 可能限制模型从不同角度关注信息的能力。Transformer引入了多头注意力来解决这个问题。

其核心思想是:使用多组不同的 WQ、WK、WV 矩阵(即多个“头”),并行地进行多次自注意力计算。每个头都可以学习关注输入序列的不同方面(例如语法结构、语义关联等)。

以下是多头注意力的流程:

  1. 输入序列 X 分别与 h 组(例如8组)不同的 {WQ, WK, WV} 矩阵相乘,得到 h 组 {Q, K, V}。

  2. 对每一组 {Q, K, V},独立进行上一节所述的自注意力计算,得到 h 个输出矩阵 Z_i。

  3. 将这 h 个输出矩阵在特征维度上拼接(Concatenate)起来。

  4. 将拼接后的结果通过一个可学习的线性层进行投影,最终得到多头注意力的输出。

通过这种方式,模型能够同时从多个表示子空间捕获信息,从而获得更丰富、更强大的序列表示。

位置编码 📍

自注意力机制本身是排列不变的,即打乱输入词的顺序,其输出的加权和结果可能不变。但这对于语言理解是致命的,因为词序至关重要。

为了将序列的顺序信息注入模型,Transformer使用了位置编码。具体做法是:为输入序列中的每个位置(第1个词,第2个词...)生成一个唯一的、与词嵌入维度相同的向量。这个位置编码向量会与对应的词嵌入向量相加,作为自注意力层的实际输入。

原始论文使用正弦和余弦函数来生成位置编码:

PE(pos, 2i) = sin(pos / 10000^(2i/d_model))

PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

其中 pos 是位置,i 是维度索引,d_model 是模型维度(如512)。这种周期性函数设计使得模型能够轻松学习到相对位置关系。

编码器与解码器结构 🧩

现在,让我们将这些组件组合起来,看看完整的Transformer架构。

编码器:由 N 个(原文中 N=6)完全相同的层堆叠而成。每一层包含两个子层:

  1. 多头自注意力层:即我们刚讨论的多头注意力机制,其输入是上一层的输出(第一层为词嵌入+位置编码)。

  2. 前馈神经网络层:一个简单的全连接网络(例如两层线性变换加激活函数)。

每个子层周围都应用了残差连接和层归一化。即:输出 = LayerNorm(x + Sublayer(x))。这有助于缓解深度网络中的梯度消失问题,加速训练。

解码器:同样由 N 个相同的层堆叠而成。每层包含三个子层:

  1. 掩码多头自注意力层:与编码器类似,但加入了掩码。在训练时,为了确保解码器在预测第 t 个输出时只能看到 1 到 t-1 时刻的已知输出,需要将未来时刻的注意力分数掩蔽(设为负无穷大),使其Softmax权重为0。

  2. 多头注意力层:这一层的 K 和 V 来自编码器的最终输出,而 Q 来自解码器第一子层的输出。这使得解码器可以关注输入序列的相关部分。

  3. 前馈神经网络层。

解码器的每个子层同样使用了残差连接和层归一化。

最终,解码器最后一个层的输出会通过一个线性层和Softmax函数,转化为目标词汇表上的概率分布,从而生成翻译结果。

Transformer在计算机视觉中的应用 👁️

Transformer最初为NLP设计,但其强大的序列建模能力迅速被引入计算机视觉领域。

目标检测(DETR, ECCV 2020):该工作用Transformer简化了目标检测流程。CNN提取的图像特征图被展平为一组特征向量,并加上空间位置编码。这些向量送入Transformer编码器。解码器接收一组可学习的“对象查询”向量,并通过与编码器输出的交互,并行地预测出一组边界框和类别标签(或“无对象”)。它省去了Faster R-CNN中生成候选区域、非极大值抑制等复杂后处理步骤,实现了端到端训练。

图像识别(ViT, ICLR 2021):该工作将图像分割成固定大小的图像块,每个块被线性投影为向量(类似词嵌入)。加上位置编码后,这些向量序列被直接送入标准的Transformer编码器。同时,在序列开头添加一个可学习的“[CLS]”分类令牌,其编码器输出用于最终的图像分类。ViT表明,在大规模数据上预训练后,纯Transformer架构在图像分类任务上可以超越最先进的CNN模型。

总结

本节课我们一起深入学习了Transformer模型的核心思想与架构。我们从RNN的局限性出发,理解了自注意力机制如何通过计算序列内部元素间的关联来建模依赖关系。我们详细剖析了自注意力的计算步骤、多头注意力如何扩展模型的表示能力、位置编码如何引入序列顺序信息,以及编码器-解码器的堆叠结构与残差连接等细节。最后,我们看到了Transformer如何从NLP领域成功跨界,在目标检测和图像识别等计算机视觉任务中引发新的革命。Transformer以其并行高效和强大的建模能力,正在重塑我们对深度学习模型的设计思路。

课后思考与作业 💡

思考题:你认为Transformer比LSTM更快还是更慢?为什么?(提示:从并行计算和序列长度角度考虑)

作业:

  1. 观看本周课程资料中提供的“Transformers in action”视频,直观理解其工作原理。

  2. 阅读Jay Alammar的经典文章《The Illustrated Transformer》。

  3. 若想深入理解位置编码,可参考相关专题文章。

  4. 阅读原始论文《Attention Is All You Need》。

062:生成对抗网络 - 第一部分

概述

在本节课中,我们将要学习一种非常流行的深度生成模型——生成对抗网络。我们将从回顾上节课的内容开始,然后深入探讨GAN的核心思想、目标函数、训练策略以及其背后的原理。


回顾:生成模型与密度估计

上一节我们介绍了生成模型的基本概念,本节中我们来看看生成对抗网络的具体实现。

生成模型旨在学习给定训练图像数据集 X 上的概率密度函数 P(x)。如果该分布被参数化为高斯分布,那么 P(x) 会根据图像由该分布生成的概率,为每个可能的图像分配一个正数。它告诉我们图像 x 在该分布 P(x) 下的可能性有多大。

生成模型的应用包括:

  • 采样和生成新数据

  • 似然估计:可用于异常值检测。如果你有一个分布,并且一个数据点属于该分布的可能性很低,那么该点很可能是异常值。

  • 特征学习:我们将在本讲座中进一步讨论。

我们还讨论了从显式和隐式角度进行概率分布的密度估计,这也定义了深度生成模型的不同方法。

显式密度估计方法尝试为概率分布写出一个显式函数。例如,你可以说 P(x) = f(x; θ),其中 f 可以是一个高斯函数。在这种情况下,输入是图像 x,输出 P(x) 是图像 x 的似然值,参数是定义该密度函数的权重 θ。显式密度估计方法在异常值检测方面表现很好,但有时在生成高质量图像方面会遇到困难,生成速度也可能较慢。

隐式密度估计方法并不试图为 P(x) 分配一个函数形式。相反,它们只旨在确保给定一个模型,你可以从该模型中采样图像,而无需担心为每个这样的样本分配显式的似然值。这类方法最终为更好的样本生成和更快的采样速度提供了途径。


生成对抗网络的核心思想

带着上述回顾,让我们进入本讲座的重点——生成对抗网络。这些网络由Goodfellow等人于2014年提出。

GAN的目标是构建一个良好的采样器,允许从 P_model(x) 中抽取高质量样本,其中 P_model(x) 定义了通过该算法学习到的模型所生成的样本的分布。正如我们刚才提到的,没有为 P(x) 计算特定的函数形式。我们唯一的目标是确保从模型中采样的任何图像,其样本分布(我们称之为 P_model(x))看起来与原始数据分布相似。我们并不为每个样本分配似然值。

因此,我们理想地希望输出样本与训练数据相似,但又不完全相同。为什么这么说?因为如果我们得到的样本与训练数据完全相同,首先,神经网络可能只是在记忆,而没有真正学到什么。其次,你可能就不再需要这样的模型了。你只需要一个能够生成超出你已有数据的多样化样本的模型。

那么如何实现这个目标呢?生成对抗网络的关键思想是引入一个潜变量 z。这是一个具有简单先验(例如高斯先验)的潜变量。一旦你从该高斯分布中采样出这个随机变量,你就将其通过一个称为生成器的模块(在我们的例子中,生成器可以是一个神经网络)。你向这个生成器神经网络输入 z,网络输出一个图像 x̂。因此,这个模块的输出是一个图像本身,我们将其定义为来自生成器的分布,记作 P_G。

现在,我们想要什么?我们希望确保,如果我们有一个由 P_data 给出的训练数据分布,那么分布 P_G 必须接近 P_data。请注意,我们并不是试图为每个数据点 x̂ 甚至 x 分配似然值。我们希望分布 P_G 接近分布 P_data。

实现这一点的挑战在于,我们没有对 P_G 或 P_data 施加任何特定的参数化。因此,我们必须在不知道它们是什么分布的情况下,找到一种方法来确保 P_G 变得接近 P_data。请记住,这里的高斯分布仅用于输入向量 z;我们并没有假设你的 P_data 和 P_G 分布是高斯分布或任何其他参数化形式。

那么,我们如何确保 P_G 大致接近 P_data 呢?为此,该方法的创始人Goodfellow等人提出了一个有趣的想法:他们引入了一个称为判别器的分类器(在本讲座中记为 D),用于区分真实样本和生成样本。因此,如果数据来自 P_data,它将被归为类别1。如果数据来自生成分布 P_G,则该图像将被归为类别0。

这如何有益?我们的目标现在是训练生成器,以确保判别器将生成的样本 x̂ 错误分类为类别1,并且不再能够区分原始分布 P_data 和新的分布 P_G。因此,我们将通过这个判别器来使 P_G 等同于 P_data。判别器的工作是将生成的(假)样本与真实样本分开。而生成器的工作是欺骗判别器。你也可以把这看作警察和小偷的游戏,其中判别器就像能区分真假的警察,而生成器的工作就是愚弄判别器。这就是总体思路。


GAN的目标函数

如何训练这样的生成器或判别器?GAN的训练目标由以下公式给出:

这是一个极小极大优化问题。你对生成器网络的参数 G 进行最小化,对判别器(分类器)网络的参数 D 进行最大化。

公式的第一部分表示:对于来自真实原始训练分布 P_data 的数据点 x,我们希望判别器最大化这些数据点的对数似然(即 log D(x))。

公式的第二部分表示:如果你现在从高斯分布中采样一个 z,并将其输入你的生成器 G,生成器的输出 G(z) 再输入判别器。我们希望确保判别器对此的输出是0(即判别为假)。至少,判别器必须旨在使这个值(D(G(z)))为0,当整个数量最大化时就会发生这种情况(因为 log(1 - 0) = 0)。另一方面,生成器的工作是确保最后这个数量(D(G(z)))趋向于1,这将最小化整个数量(因为 log(1 - 1) = -∞)。这就是为什么你对 G 最小化而对 D 最大化。

这也符合直觉,因为生成器和判别器正在进行警察和小偷的游戏,每一方都希望胜过对方。因此,当判别器想要最大化目标函数中的项时,生成器则想要最小化目标函数中相应的项。这样的极小极大问题也被称为零和博弈,它起源于博弈论。

我们假设判别器在其输出层具有Sigmoid激活函数。请记住,至少在我们讨论的这个特定例子中,判别器是一个二元分类器,它只需要判断是真实还是生成。因此,二元分类器输出层可以使用的最佳激活函数是Sigmoid激活函数。

让我们尝试以略有不同的方式解析这个目标函数。如果你看第一项,我们称之为目标1。目标1指出,我们希望训练判别器,使得如果样本属于真实训练数据分布 P_data,我们最大化它作为真实样本的对数概率。

第二部分是目标2,它讨论了关于第二项最小化生成器。请记住,第一项与 G 无关,因此在最小化 G 时,第一项无关紧要,可以排除。因此,在最小化 G 时,我们寻求什么?我们寻求训练生成器 G,使得如果样本属于 P_G(即生成器 G 的输出),我们希望最大化它作为真实样本的对数概率;这就是生成器想要做的,尽管判别器也想要最大化这个数量。

在实际实现中,这两项中的“期望”仅仅意味着损失是在一批样本上平均的。


GAN的训练策略

现在来看训练策略。首先想到的是,我们现在有两个网络需要训练:D 和 G。到目前为止,我们已经见过其他几种需要训练两个网络的方法。但在所有这些例子中,两个分支大多是用相同的损失或目标训练的。那么这里我们可以做什么?

一个选择是我们先完全训练 D,得到一个好的判别器。这可以通过仅优化目标1的第一部分来完成。一旦我们有了这个,我们就可以训练 G 来优化目标2。这行得通吗?有什么问题吗?

让我们试着思考一下。如果 D 最初非常自信,这意味着你训练了一个优秀的判别器或分类器,那么它将能够判断任何来自 G 的样本都是假的。这意味着,如果你得到一个对应于分布 P_G 的样本 x(即生成器的输出),那么 D(x) 或 σ(x)(D 中的Sigmoid激活函数)将等于0。这意味着 log(1 - D(G(z))) 在这种情况下将是 log(1 - 0) = 0。如果你查看 log(1 - σ(x)) 的曲线及其梯度,当 log(1 - σ(x)) 为0时,梯度显然也是0。这意味着 G 将得不到任何梯度来训练,因此永远学不到东西。

因此,一开始就完全训练好判别器,并不能帮助我们得到一个好的生成器,而这是GAN的主要目标。那么我们该怎么做?

我们希望交替使用目标1和目标2分别训练判别器和生成器。


GAN的训练算法

让我们看看这在GAN的算法中是如何完成的。

Goodfellow的原始论文建议,我们首先进行 k 步判别器训练:

  1. 从噪声先验 P_g(z)(例如高斯分布)中采样一个小批量的 M 个噪声样本 {z^(1), ..., z^(M)}。

  2. 从原始数据分布 P_data(即你的训练数据)中采样一个小批量的 M 个样本 {x^(1), ..., x^(M)}。

  3. 更新判别器,通过梯度上升其随机梯度。梯度是通过对判别器网络中的每个权重求取损失函数 L_D 的导数得到的。损失函数 L_D 为:

    L_D = (1/M) * Σ [log D(x^(i)) + log(1 - D(G(z^(i))))]

    你对判别器参数进行梯度上升更新。

你进行 k 步这样的操作来更新判别器。它还没有被完全训练好。一旦你完成了 k 步,现在切换到训练生成器。

对于生成器训练:

  1. 从噪声先验 P_g(z) 中采样一个小批量的 M 个噪声样本 {z^(1), ..., z^(M)}。

  2. 更新生成器,通过梯度下降其随机梯度。梯度是通过对生成器网络中的每个权重求取损失函数 L_G 的导数得到的。损失函数 L_G 为(注意,对于生成器,我们最小化目标函数的第二项):

    L_G = (1/M) * Σ [log(1 - D(G(z^(i))))]

    你对生成器参数进行梯度下降更新。

这个过程在训练迭代中重复进行,最终用于设计(得到)生成器模型。

在测试时,你做什么?一旦你训练了完整的模型,你可以丢弃判别器。你从高斯分布中采样一个 z,将其送入生成器网络,得到的图像就是你将假定属于原始数据分布的图像。


关于收敛的思考

这里有一个问题:对于这样的网络,收敛意味着什么?到目前为止,每当我们有一个损失函数时,我们总是希望最小化损失函数,并确保损失趋于零。为了避免过拟合,我们可能不会让它达到零,但至少我们希望看到损失随着迭代而减少。

然而在这里,我们有两个组件,其中一个可能试图增加目标函数值,另一个则试图减少目标函数值。那么对于这样的网络,收敛意味着什么?

我们将在下一部分更详细地探讨这个问题。


总结

本节课中我们一起学习了生成对抗网络的第一部分。我们回顾了生成模型和密度估计的概念,介绍了GAN通过生成器和判别器对抗训练的核心思想,详细解析了其极小极大目标函数,并阐述了交替训练判别器和生成器的具体算法步骤。GAN作为一种隐式密度估计方法,旨在学习一个能够生成与真实数据分布相似的新样本的采样器,而无需显式定义概率密度函数。

063:生成对抗网络 - 第二部分

概述

在本节课中,我们将深入探讨生成对抗网络(GAN)的数学原理、优化过程、经典架构DCGAN以及如何评估GAN模型的性能。我们将从理论推导开始,逐步过渡到实践应用。


优化目标与最优解推导

上一节我们介绍了GAN的基本概念,本节中我们来看看如何从数学上推导其最优解。

首先,重新考虑GAN的训练目标函数:

Min_G Max_D [ E_{x~p_data(x)}[log D(x)] + E_{z~p_z(z)}[log(1 - D(G(z)))] ]

将期望展开为积分形式:

Min_G Max_D ∫ [ p_data(x) log D(x) + p_g(x) log(1 - D(x)) ] dx

为了求解最优判别器D,我们将最大化操作移入积分内部,并分析被积函数。设 y = D(x),a = p_data(x),b = p_g(x),则函数为:

f(y) = a log y + b log(1 - y)

通过求导并令导数为零,可以找到使f(y)最大的y值:

f'(y) = a/y - b/(1 - y) = 0

解得:

y = a / (a + b)*

将变量代回,得到最优判别器的输出为:

D*(x) = p_data(x) / (p_data(x) + p_g(x))


生成器的最优条件

在得到最优判别器D*后,我们将其代入目标函数,来求解最优生成器G。

将D*的表达式代入,目标函数变为:

Min_G ∫ [ p_data(x) log( p_data/(p_data+p_g) ) + p_g(x) log( p_g/(p_data+p_g) ) ] dx

此式可以重写为两个KL散度之和的形式,并进一步与Jensen-Shannon散度(JSD)建立联系。JSD的定义为:

JSD(P||Q) = 1/2 KL(P || M) + 1/2 KL(Q || M), 其中 M = (P+Q)/2

经过推导,最终目标函数可简化为:

Min_G [ 2 * JSD(p_data || p_g) - log 4 ]

由于JSD是非负的,且当且仅当两个分布相等时为零。因此,目标函数在 p_g = p_data 时取得最小值 -log 4。

这意味着,在最优状态下,生成器学到的分布 p_g 必须与真实数据分布 p_data 完全一致。


最优状态下的平衡

将 p_g = p_data 代回最优判别器公式,得到:

D*(x) = p_data(x) / (p_data(x) + p_data(x)) = 1/2

这个结果具有深刻的含义:当GAN训练达到全局最优时,判别器对于任何输入(无论是真实样本还是生成样本)的输出概率都将是0.5。此时,判别器完全无法区分真假,达到了纳什均衡。


DCGAN:深度卷积生成对抗网络

上一节我们从理论上分析了GAN的最优解,本节中我们来看看一个里程碑式的实践架构——DCGAN。

DCGAN于2016年提出,它将CNN在监督学习中的成功经验引入到无监督的生成模型中,显著提升了生成图像的质量。

以下是DCGAN引入的核心训练策略:

  • 用步幅卷积替代池化层:使用带步幅的卷积层来学习空间下采样,取代了确定性的最大池化。

  • 去除全连接层:在深层架构中主要使用卷积层,移除了全连接隐藏层。

  • 引入批量归一化:在生成器和判别器中都使用批量归一化(BN),以稳定训练、防止梯度问题。但注意,生成器的输出层和判别器的输入层不使用BN。

  • 使用ReLU族激活函数:生成器中使用ReLU,判别器中使用Leaky ReLU。

  • 输出层激活函数:生成器最终输出层使用Tanh,将像素值映射到[-1, 1];判别器输出层使用Sigmoid,输出一个概率值。


潜在空间的操作

DCGAN的一个重要贡献是展示了生成器的潜在空间具有有意义的算术性质。

  • 图像插值:对两个潜在向量 z1 和 z2 进行线性插值 αz1 + (1-α)z2,输入生成器后,可以得到从图像G(z1)到G(z2)的平滑过渡。

  • 向量算术:通过对潜在向量进行算术运算,可以控制生成图像的语义属性。例如:

    Z_{女人戴眼镜} ≈ Z_{男人戴眼镜} - Z_{男人不戴眼镜} + Z_{女人不戴眼镜}

    将运算结果输入生成器,可以得到戴眼镜的女性图像。


GAN的特征学习能力

DCGAN还证明了GAN的判别器可以学习到具有判别力的特征,可用于其他任务。

实验方法如下:

  1. 使用ImageNet数据集训练一个DCGAN。

  2. 固定训练好的判别器权重。

  3. 将另一个数据集(如CIFAR-10)的图像输入该判别器。

  4. 提取判别器中间某一层的卷积特征。

  5. 用这些特征训练一个SVM分类器对CIFAR-10图像进行分类。

实验结果表明,用这种方式学到的特征取得了具有竞争力的分类性能,证明了GAN强大的特征学习能力。


GAN的评估方法

评估生成模型比评估分类模型更具挑战性,因为没有明确的“正确”标签。以下是几种常见的评估方法:

1. 人类评估

最直接但主观的方法。好的生成器应能生成:

  • 可识别的物体:生成的图像包含清晰可辨的物体。

  • 语义多样性:生成的样本覆盖了训练集中的多种类别。

2. Inception Score (IS)

这是一个试图量化上述人类评估标准的自动化指标。它使用一个在ImageNet上预训练的Inception-v3网络。

  • 核心思想:对于高质量的生成数据,分类器对每个生成图像应有很高的置信度(条件分布 p(y|x) 的熵低),同时所有生成图像的类别分布应尽可能均衡多样(边际分布 p(y) 的熵高)。

  • 计算公式:

    IS = exp( E_{x~p_g} [ KL( p(y|x) || p(y) ) ] )

    分数越高越好。

3. Fréchet Inception Distance (FID)

IS的一个主要局限是它完全不考虑真实数据分布。FID通过比较生成分布与真实分布在特征空间中的距离来解决这个问题。

  • 计算方法:

    1. 将真实图像和生成图像分别输入Inception-v3网络,提取某一中间层(如pool3层)的特征。

    2. 假设这两组特征均服从多元高斯分布,分别计算它们的均值 m, m_w 和协方差矩阵 C, C_w。

    3. 计算这两个高斯分布之间的Fréchet距离:

      FID = ||m - m_w||^2 + Tr(C + C_w - 2(CC_w)^{1/2})

  • 解读:FID分数越低,表示生成分布与真实分布越接近,模型性能越好。

FID对图像质量的退化(如添加噪声)比IS更敏感,通常被认为是更可靠的指标。


总结

本节课我们一起学习了生成对抗网络的核心理论、经典架构DCGAN以及评估方法。

  1. 我们从数学上推导了GAN的最优解,证明了在全局最优时,生成分布会匹配真实分布,且判别器输出为0.5。

  2. 我们介绍了DCGAN,它通过引入CNN的最佳实践(如使用卷积层、BN、特定激活函数)极大地改善了GAN的训练稳定性和生成质量,并展示了潜在空间的可解释性。

  3. 最后,我们探讨了评估GAN的挑战,介绍了Inception Score和Fréchet Inception Distance这两种重要的自动化评估指标,其中FID因其同时考虑生成质量和多样性,并与真实数据对比,而被广泛使用。

延伸阅读与资源

064:变分自编码器 (VAE) 🧠

在本节课中,我们将要学习一种重要的深度生成模型——变分自编码器。我们将从回顾自编码器开始,逐步理解VAE的核心思想、数学原理、训练方法及其应用。

概述

上一节我们介绍了生成对抗网络,本节中我们来看看另一种深度生成模型,称为变分自编码器,通常简称为VAE。在讨论变分自编码器之前,我们先简要回顾一下上周学过的自编码器。

自编码器是一种用于无监督学习的神经网络,其目标是重构输入本身作为输出。自编码器的架构通常包含一个将输入压缩到瓶颈层的编码器,该层产生输入的某种表示(也称为潜在编码),然后通过解码器重构输出,理想情况下输出应尽可能接近输入。

在本周主题的背景下,我们想问的问题是:如何利用这种方法自动生成图像?回想一下,当我们讨论自编码器时曾提到,训练完成后通常会丢弃解码器,仅使用编码器来获取数据的低维表示。

现在,让我们尝试看看是否能反转这个思路:在训练结束时,我们能否丢弃编码器而非解码器?如果我们使用某个向量作为编码,能否生成接近训练数据分布的图像?正如你所见,这个目标与GANs试图实现的目标相近,但实现视角不同。

变分自编码器的思想大约在同一时间由两组研究人员提出。Kingma和Welling在2014年ICLR上发表的论文《Auto-Encoding Variational Bayes》中提出,以及Rezende、Mohamed和Wierstra在2014年ICML上发表的《Stochastic Backpropagation and Variational Inference in Deep Latent Gaussian Models》中提出。

理解变分自编码器

VAE的核心在于学习所谓的潜变量模型。在潜变量模型中,我们的目标是学习从某个潜在随机变量 Z 到一个可能很复杂的分布 X 的映射,我们假设 X 是我们希望生成相似数据的真实世界训练分布。

从数学上讲,可以写作:

p(x) = ∫ p(x, z) dz

其中 p(x, z) = p(x|z) * p(z)。

我们在这里假设,潜在变量 Z 的先验或边缘分布是简单的。那么如何得到 p(x|z) 呢?我们假设 p(x|z) 是输入向量 z 的某个函数。这就是这里简单的数学构造。

我们提出的问题是:在给定训练数据(这里假设是无监督学习,只有 X)的情况下,我们能否学习解耦数据分布背后真正的解释因素或潜在变量?这些潜在变量可能正在生成数据。例如,在人脸图像中,你可能想假设存在一个称为“身份”的潜在随机变量和一个称为“表情”的潜在随机变量。因此,如果说人物A是快乐的,你应该能够生成人物A处于快乐情绪的人脸图像。在这种情况下,身份和表情就是潜在变量,这些变量可以取不同的值。身份可以取不同人的值,如A、B、C,表情可以取不同的值,如快乐、悲伤、愤怒等,组合起来就产生了特定类型的图像。

所以,我们讨论的是一组潜在变量(在此表示为 z1, z2, ...)和一个函数 G,它将我们从这个潜在空间带到原始数据空间。我们如何学习这样一个从 z 到 x 的函数 G?

我们将假设使用神经网络来学习这样一个从 Z 到 X 的函数,因此这里的条件分布 p(x|z) 可以通过神经网络给出。现在剩下的问题是:Z 从何而来?计算后验概率 p(z|x) 是难以处理的,因为我们不知道 Z 是什么样子,不幸的是,我们需要它来训练 G(z) 模型。没有 Z 的值,我们也无法训练 G(z) 模型。那么,我们如何解决这个问题?

正如你所见,到目前为止,变分自编码器为自编码器提供了贝叶斯视角。因此,如果我们假设数据是通过这个生成过程产生的:Z 是一组我们假设属于某个先验分布的潜在随机变量,我们从真实条件分布中采样,得到 X,即我们看到的训练数据分布。这里的直觉是,X 可以是一幅图像,它由一些潜在属性生成,例如类别标签、方向、属性等。因此,我们可以说类别标签是人脸,方向是他/她看的方向,属性可以是肤色、眼睛颜色、头发颜色等。现在对我们来说的问题是,在不知道这些 z 是什么的情况下,我们需要估计这个条件分布 p(x|z) 的参数 θ。

如何做到这一点是VAE的核心思想。为了启动这个过程,我们现在假设随机变量 Z 上有一个先验分布,为了方便起见,我们假设它是一个单位高斯分布。一旦有了 Z,你现在可以构建一个我们称为解码器网络的神经网络,它具有某些参数 θ(即解码器网络中各层的权重)。解码器输出一组均值和方差,然后可以用来采样并获得类似于 X 的数据。因此,这里的条件分布 p_θ(x|z) 被假设为对角高斯分布(为简单起见,可以假设具有某个协方差矩阵的多元高斯分布,但过程会变得有点复杂,我们将其保持为简单的对角高斯分布,其每个维度的均值和方差由解码器网络预测)。

根据贝叶斯规则,我们知道后验概率 p_θ(z|x) 由 p_θ(x|z) * p_θ(z) / p_θ(x) 给出。现在,如果我们观察右边的项:p_θ(x|z),我们刚刚讨论了如何获得它;p_θ(z),我们刚刚说假设它是高斯分布;分母 p_θ(x) 不幸地是一个难以处理的积分。为了找出 X 来自什么分布及其参数化,我们目前并不知道。

那么,我们该如何解决?我们现在将假设存在另一个分布 q_φ(z|x),我们将其称为近似后验分布。这是使用一个编码器网络获得的。编码器网络的作用是:它接收你的训练数据分布(即神经网络从训练数据中接收所有输入),它有一组由某些权重参数化的层(我们表示为 φ),并且它再次输出一组多元对角高斯分布的均值和方差,该分布由近似后验 q_φ(z|x) 给出。

如果把所有这些部分放在一起,我们现在有了下面的编码器和上面的解码器。编码器接收 x,并为这个近似后验分布 q_φ(z|x) 输出一组均值和方差。一旦有了那个分布,你就可以从中采样一个 z。然后将该样本 z 作为输入给你的解码器网络,解码器网络输出一组对应于 p_θ(x|z) 的均值和方差,给定这些均值和方差,你可以从 p_θ(x|z) 中采样以获得你的输出图像或重构。

我们在这里假设 q_φ 和 p_θ 都是具有特定均值和对角协方差的多元高斯分布,因此每个维度是独立的。

训练VAE网络

我们理想地希望训练解码器网络,就像普通的自编码器一样。因为它将构造并给我们一个输出 x,我们理想地希望使用像重构损失这样的东西,例如均方误差损失,以确保这里的重构尽可能接近 x 本身,这肯定应该是我们损失函数的一项。我们还需要做什么?

我们还需要确保这个近似后验 q_φ(z|x) 应该接近我们假设的 Z 的先验。我们为解码器网络假设 Z 来自一个单位高斯先验。因此,我们理想地希望 q_φ(z|x) 接近分布 p_θ(z),在我们的例子中,我们假设它是一个均值为0、协方差矩阵为单位矩阵的高斯分布。

让我们尝试把这些放在一起,得到训练这种变分自编码器的正式损失函数。记住,这里的目标是进行最大似然估计:我们有一个由 {x_i} 给出的训练数据,我们理想地希望学习参数 θ(即解码器的参数),使得该分布生成训练数据点的可能性最大化,这就是数据的最大似然估计。

我们将把它转换为对数似然最大估计(最大对数似然估计),其中乘积转换为和,然后你有 log p_θ,这在数学上使事情变得简单一些。

现在,p_θ(x) 可以定义为 ∫ p_θ(x, z) dz。这可以展开为 ∫ p_θ(x|z) * p_θ(z) dz。不幸的是,我们再次面临一个难以处理的积分。

因此,让我们尝试看看如何解决这个最大似然估计问题,我们将用一个技巧来解决。让我们重写最大对数似然估计问题。我们有对数似然 log p_θ(x),它可以表示为对从近似后验采样的 Z 向量取期望的 log p_θ(x)。如果我们从你的近似后验中采样 Z 向量并作为输入给解码器,生成类似于训练分布的样本的可能性必须很高。这就是第一句话的意思。

现在,log p_θ(x) 可以使用贝叶斯定理写为 log [p_θ(x|z) * p_θ(z) / p_θ(z|x)]。对于这个表达式,我们将乘以并除以近似后验 q_φ(z|x)。一旦我们有了这个等价于该问题最大似然估计设置的表达式,我们可以以不同的方式分组项来书写它。

第一项 log p_θ(x|z) 保持不变。然后你有另一项 q_φ(z|x) 和 p_θ(z)。理想情况下,你可以将其写为 + E_z[log (p_θ(z) / q_φ(z|x))]。但现在,我们将减去它并这样写。所以我们有 - E_z[log (q_φ(z|x) / p_θ(z))]。然后剩下的项是分母中的 p_θ(z|x) 和分子中的 q_φ(z|x),这就是第三项。

现在,如果你观察这里的第二项和第三项,你会注意到第二项是近似后验 q_φ(z|x) 和先验 p_θ(z) 之间的KL散度。第三项是真实后验 p_θ(z|x) 和近似后验 q_φ(z|x) 之间的KL散度。

现在,观察这里,KL散度是一个非负量,所以第三项将大于或等于0。因为这是一个对数似然估计问题,我们理想地希望最大化这个量。因此,要最大化由这三项组成的量(其中第三项大于等于0),可以通过最大化前两项本身来实现。

所以我们现在说的是,我们有一个下界,由前两项给出。记住第三项是非负的并且是加上的,所以整个对数似然将以下面这两项为下界。因此,如果我们想最大化对数似然,我们可以最大化这个下界。这个下界也被称为证据下界或ELBO。这整个优化过程被称为变分推断的方法,其中我们引入了一个变分分布(在本例中是近似后验 q_φ),并利用它来学习分布的参数化。

所以,我们现在的目标是找到最大化这个证据下界的 θ* 和 φ*。

编码器与解码器结构

让我们把这些放在一起。我们现在说,我们引入了一个推理模型 q_φ(z|x)(近似后验),它试图通过优化变分下界来近似原始后验。我们使用编码器网络对 q_φ(z|x) 进行参数化。

因此,我们现在有了一个编码器和一个解码器网络。让我们在将它们放在一起之前单独看看它们:给定训练数据 X,我们将其作为输入提供给编码器。编码器输出近似后验分布的均值和方差,我们希望它接近 Z 的先验。然后我们从 Z 采样,并使用解码器生成 x,我们希望它接近训练数据分布。

重参数化技巧

如何训练这个网络?如果你看损失本身,这是我们要最大化的总体目标函数,但训练过程中存在一个问题。我们说的是将输入分布给编码器,编码器给出近似后验的参数化,然后我们从先验中采样,再给解码器,最后得到最终输出。这意味着我们现在有一个编码器-解码器模型,一个自编码器,其中在中间步骤或瓶颈层步骤,我们将采样一个向量,而不是前向传播一个向量。为什么这是个问题?那个样本可能不是确定性的,我们不知道如何通过那个瓶颈层将梯度传递回编码器。这是一个挑战。我们该怎么做?

我们理想地必须以某种方式确保我们得到的样本变得独立于编码器的输出,因为如果它变得独立于编码器的输出,你可以简单地通过解码器和编码器反向传播梯度,并假设随机性来自某个外部量。

如何将随机性与编码器输出分离?这是通过一个称为重参数化技巧的诀窍完成的,这是变分自编码器方法的主要贡献之一,一个简单的技巧但解决了通过网络反向传播的问题。

记住,我们将从某个近似后验分布中采样 Z,该分布由具有均值 μ_z(x) 和标准差 σ_z(x) 的高斯分布给出。现在,从这样的分布中抽取的样本可以稍微不同地重写。我们可以将该样本 Z 参数化为:

z = μ_z(x) + σ_z(x) * ε

其中 ε 是从单位标准正态分布中随机采样的向量。

这如何解决了真正的问题?记住,在之前的步骤中,到目前为止,你可以认为这类似于这样:你在神经网络中有一组层。变分自编码器的早期版本中,其中一层变成了随机的,你必须采样一个向量,而不是像我们迄今为止所见的那样通过神经网络前向传播的向量。因为那是一个样本,我们不会知道如何通过该层反向传播到更早的层(在我们的例子中是编码器)。

因此,重参数化技巧建议:让我们将该层固定为前一层的输出,相反,从外部引入这个随机性,而不是该层输出本身。如果你在这里看到,现在新样本由 μ_z(编码器的输出)、σ_z(也是编码器的输出)和 ε(你现在几乎可以将其视为来自噪声的偏置向量)给出。它并不真正影响反向传播到更早的层,它只是在该特定层中给出的外部输入,现在你可以像以前一样使用梯度下降通过整个网络反向传播。

因此,你现在在编码器中给定 x,生成 μ 和 σ,然后使用重参数化技巧采样 z,通过解码器并生成输出的 μ 和 σ,从中你可以采样并获得任何生成。

通过重参数化技巧,整个变分自编码器变得可微分,你可以使用梯度下降和反向传播来更新VAE中的权重并训练网络。

损失函数与训练

如前所述,这里的目标函数由下式给出:

L(θ, φ; x) = -D_KL(q_φ(z|x) || p_θ(z)) + E_{z~q_φ(z|x)}[log p_θ(x|z)]

现在,log p_θ(x|z) 结果是一个简单的重构损失,我们希望它接近你的训练分布,因此你甚至可以使用均方误差来最大化这个似然(第二项)。那么第一项呢?我们如何使其可微分?如何获得其梯度以进行反向传播?关键点在于,我们假设 q_φ 是一个具有编码器提供的特定均值和方差的高斯分布,我们也假设 p_θ(z) 是一个单位高斯分布。高斯分布之间的KL散度是一个定义明确且可微分的公式,这有助于我们确保可以计算梯度并反向传播,从而端到端地训练这个网络。

VAE与自编码器的区别

这完成了我们对VAE如何学习的讨论。总结一下并与自编码器区分:

  • 传统自编码器通过学习重构输入来训练,通常用于学习特征(低维表示),然后可用于训练另一个神经网络、SVM或任何其他监督模型。

  • 变分自编码器在概念上是贝叶斯学习和深度学习的结合。在训练变分自编码器之后,你可以丢弃编码器,只需从你的先验 p_θ(z)(一个单位高斯分布)中采样,现在你的解码器已经学会了以单位高斯分布作为输入来生成看起来接近输入数据分布的图像。这就是使VAE成为生成模型的原因。

VAE的应用与示例

VAE能做什么?以下是几个例子,其中VAE在MNIST数据和Frey Face数据集上进行了训练。你可以看到,当VAE中的潜在变量 z 被微小值修改时(记住我们将采样一些向量),你可以看到解码器的输出(即生成的图像)随着 z 值的变化而逐渐变化。这表明变分自编码器确实学会了能够在其所在的流形上平滑生成数据的潜在变量值。

类似地,就像我们在Frey Face数据集上对GANs所做的那样,你可以学习诸如表情和姿势等潜在变量,当你改变这些对应的潜在随机变量的值时,最终会得到具有不同姿势和不同表情的人脸图像生成。

VAE还能用于什么?它们有很多应用,我们将在下周详细讨论其中一些。但这里有几个例子:图像和视频生成、超分辨率、从静态图像进行预测、图像修复等等。我们将在下周看到其中一些。

VAE的扩展

VAE在过去几年中也以多种方式得到了扩展。虽然我们可能没有范围在本课程中涵盖所有这些,但如果你想知道更多,这里有一些提示:

  • 原始作者扩展了VAE以获得半监督变体,其中可以使用标记和未标记数据来学习VAE。

  • 另一个流行且强大的扩展是条件VAE,你可以在给定特定类别标签的情况下生成数据。

  • 另一种变体称为重要性加权VAE,它对潜在随机变量进行不同的加权以获得更强大的生成。

  • 还有去噪VAE,你可以将其视为去噪自编码器在VAE设置中的扩展。

  • VAE也被用于图形学,一个流行的网络称为逆向图形网络,它使用类似VAE的策略来学习潜在变量,使你能在生成各种对象、3D对象等方面进行控制。

  • 最后,另一种网络称为对抗自编码器,它将GANs和VAEs结合在一起,这将是我们下一讲立即讨论的主题。

总结

本节课中我们一起学习了变分自编码器的核心概念。我们了解到,VAE通过结合编码器、解码器以及重参数化技巧,在潜在空间中学习数据的连续、结构化表示,从而能够生成新的数据样本。其训练目标ELBO平衡了重构精度和潜在空间的正则化。与标准自编码器不同,VAE是一种强大的生成模型,在图像生成、数据压缩和特征学习等领域有广泛应用。

066:超越VAE和GAN - 其他深度生成模型 - 01

概述

在本节课中,我们将学习除了变分自编码器和生成对抗网络之外的其他深度生成模型。这些模型通常被称为基于流的模型,它们与VAE和GAN的一个关键区别在于,它们能够显式地学习真实数据的概率密度函数。

基于流的生成模型简介

虽然VAE和GAN是最流行的深度生成模型,但在过去几年中,其他方法也取得了成功。在本周的最后一讲中,我们将了解其中的几种。这些模型通常被广泛称为基于流的模型。

与GAN和VAE的区别

它们与GAN和VAE之间存在一个非常重要的区别。GAN和VAE都没有显式地学习真实数据的概率密度函数。在GAN的情况下,我们已经看到密度估计是隐式的。你并不显式地分配一个概率密度函数并尝试在GAN中估计它。在VAE的情况下,我们通过使用变分推理优化证据下界来获得近似的密度估计。在这两种情况下,你都无法得到精确的密度函数。

然而,真实数据的确切概率密度函数 p(x) 对于许多任务可能非常有用,例如处理缺失值、采样数据,甚至识别数据分布中的偏差。了解密度函数对于这类任务会很方便。因此,我们将在本讲座中讨论的这类方法,是那些能够估计所提供训练数据的真实密度的方法。

主要类别

广义上讲,这些方法可以分为两种不同的类型:标准化流和自回归方法(或自回归流)。

标准化流

让我们从标准化流开始。回想一下本周的第一讲,我们曾试图提出这个问题:如果我们有一个包含 n 个数据点的数据集,这些数据点来自一个底层分布 P(x)。我们想要找到一个参数 θ,它属于分布族 M,使得通过 M 中的参数化选择,P_θ 和 P_data 之间的距离最小化。我们当时还指出,如果这里的距离被替换为KL散度,那么这就变成了一个最大似然估计问题,或者说通过最小化负对数似然来求解。

最大似然估计的重要性

为什么这一点很重要?到目前为止,我们在训练神经网络时涉及的所有方法,都可以被视为最小化负对数似然的一种方式。无论是使用均方误差训练神经网络,还是使用交叉熵损失为分类问题训练神经网络或LSTM,都可以证明这两者最终都归结为最小化负对数似然。所以我们现在说,即使在无监督学习设置中,我们也可以使用类似的方法。

模型框架

那么,基于流的模型在这里试图做什么呢?我们可以将GAN看作是一个判别器和一个生成器,其中判别器负责区分真实数据和生成数据,而生成器将你从潜在空间 z 带到生成的图像 x',然后 x' 与真实数据 x 一起作为输入提供给判别器。类似地,VAE是一个编码器-解码器模型,其中编码器捕获由参数 φ(编码器的权重)参数化的近似后验 Q;同样,解码器捕获给定 z 的 P_θ(x|z),其中 θ 是解码器的参数。所以在这种情况下,正如我们刚才提到的,它是一种近似的密度估计。

而在基于流的生成模型中,我们做了一件本应显而易见但并非很简单的事情:给定 x,找到函数 f 来获得一个潜在表示 z,如果我们将其反转,就能得到 x' 或重建结果。所以这里的挑战在于,如何找到这些完全可逆的函数 f 来恢复原始数据?显然,这就是挑战所在。

标准化流的核心思想

在第一类方法,即标准化流中,主要目标是识别一个从 z 到 x 的变换 f。如果你还记得,z 再次是潜在变量,与VAE非常相似。VAE和基于流的模型之间的主要区别在于,在VAE中,编码器捕获具有不同参数化的近似后验,解码器捕获 P_θ 本身。我们知道 Q_φ 是真实后验 P_θ(z|x) 的近似,而在基于流的模型中,函数是精确可逆的。

因此,在这里我们识别一个从 z 到 x 的变换 f,其中变换 f 是一系列可微的双射函数 f1 到 fk,使得 x 可以写成 f(z),而 f(z) 又可以写成 f_k ∘ f_{k-1} ∘ ... ∘ f_1(z)。反过来,z 可以写成 f^{-1}(x),而 f^{-1}(x) 又可以写成 f_1^{-1} ∘ f_2^{-1} ∘ ... ∘ f_k^{-1}(x)。

图解说明

从图解上看,给定一个潜在变量 z(可能类似于GAN中的高斯分布),你将这个从 z 中采样的输入向量通过一个神经网络 f1(此时我们只称其为函数),它输出 z1,z1 再通过 f2,依此类推,直到 fk,我们最终期望它输出原始数据分布 x 所来自的分布。

数学推导

让我们更详细地看看这一点。在进入实现之前,对于任何从 z 到 x 的可逆函数 f,利用概率密度函数的变量变换公式,我们可以写出:

p_x(x) = p_z(z) * |det( df^{-1}(x) / dx )|

这并不难证明。如果 z 属于分布 π(z),z 是一个随机变量,且 x = f(z),使得 f 可逆,那么 z = f^{-1}(x)。根据概率分布的定义,我们有 ∫ p(x) dx = 1,也等于 ∫ π(z) dz = 1。从这个等式,我们可以说 p(x) 可以表示为 π(z) * |dz/dx|。但我们已知 z = f^{-1}(x),所以代入得到 π(f^{-1}(x)) * |d f^{-1}(x)/dx|,我们将其写作 π(f^{-1}(x)) * |f^{-1}'(x)|,其中 ' 表示梯度。

在向量形式下,这将是:

p(x) = π(z) * |det( dz/dx )| 或 π(f^{-1}(x)) * |det( d f^{-1}/dx )|,这就是我们一开始写的内容。

如果我们取相同的表达式,将其展开并对两边取对数,那么我们将得到:

log p(x) = log π(z) + Σ_{i=1}^{k} log |det( d f_i^{-1} / d z_i )|

这是如何得到的?这是通过将 f 替换为函数 f1 到 f_K 的组合得到的。在对数运算下,这种组合变成了求和。这就是我们得到这个表达式的方式。

直观理解

在估计概率密度函数的对数似然时,这两项的直观理解是:第一项可以看作是变换将密度 p(z) 塑造成 p_x(x),这就是它想要做的。第二项量化了围绕 z 的小邻域的相对体积变化。记住,这就是梯度度量的内容,行列式也可以看作是捕获你试图测量的空间的体积。

工作原理图示

这里有一个图示来理解标准化流的工作原理。假设你最初从一个高斯分布 Z_n 中采样,应用函数 f1 得到 Z1,然后应用另一个函数,依此类推,直到得到 Z_{i-1}、Z_i,最后你不断应用许多函数,直到得到 Z_k,此时概率密度函数以这种方式变换,这就是我们正在寻找的 x 的密度函数。

为什么需要可微双射函数?

为什么我们说需要一个可微的双射函数?为什么每个 f 都应该是可微的双射函数?这应该相当直接,我们已经看到了逆函数是如何被使用的,但让我们更正式地定义它。

我们使用的这种双射函数被称为微分同胚。微分同胚函数是可组合的。这意味着给定两个变换 f1 和 f2,组合也是可逆且可微的。这在处理神经网络时非常重要。因此,从高斯分布到真实世界数据的复杂概率密度函数的任何复杂变换,都可以通过组合多个简单变换的实例来建模。

标准化流的要求

对于标准化流,我们需要什么?

  1. 我们希望变换函数中的每一个都是可微的。所以,如果每个将你从 z 带到 x 的函数是神经网络的一层、一个LSTM或一组神经网络层,那么它们就是可微的,我们就满足了第一个前提条件。

  2. 函数必须易于求逆。我们稍后会看到如何做到这一点。

  3. 雅可比行列式应该易于计算。为什么?因为这是你损失函数中的一项,你希望它易于计算,以便你可以使用梯度下降训练整个网络。

NICE:非线性独立分量估计

让我们看看实现标准化流的最早努力之一。这被称为NICE(非线性独立分量估计)。这项工作由Laurent Dinh等人于2015年发表。

这里的想法是引入所谓的可逆耦合层。让我们看看这些变换是什么。

耦合层操作

使用的耦合层操作是:

y1 = x1

y2 = g( x2, m(x1) )

所以,如果 x1, x2, ... 是数据 x 的不同维度,y1, y2, ... 是你试图生成的、最终希望匹配 x 的输出的不同维度。那么 y1 等于 x1,y2 由某个函数 g 作用于 x2 和 m(x1) 给出。从图示上看,y2 由函数 g 给出,该函数应用于 x2 以及 m(x1)。注意,在这个特定的公式中,y1 不依赖于 x2,但 y2 依赖于 x1。

在这种情况下,很明显雅可比矩阵将是一个下三角矩阵。为什么这么说?因为 y1 不依赖于 x2,所以雅可比矩阵的所有上三角元素都将变为零。

雅可比矩阵回顾

让我们花点时间回忆一下雅可比矩阵是什么。雅可比矩阵是所有偏导数的矩阵。如果你有一个包含 y1 到 y_k 的输出向量 y,以及一个包含 x1 到 x_d 的输入向量 x,那么所有成对的梯度 ∂y1/∂x1, ∂y2/∂x1, ..., ∂y_k/∂x1,以及类似地 ∂y1/∂x2, ∂y1/∂x3, ... 将构成雅可比矩阵的行和列。所以它是一个向量对向量的所有一阶偏导数矩阵。

在这种情况下,你可以看到,由于操作的构造,y2 将依赖于 x1,但 y1 不依赖于 x2, x3, ..., x_d。类似地,y2 将依赖于 x1 和 x2,但不依赖于 x3 到 x_d。这意味着这里的所有上三角元素都将变为0,你将得到一个下三角矩阵,而下三角矩阵的行列式就是对角线元素的乘积,因此在这种构造中,行列式变得易于计算。

逆映射

逆映射会是什么?逆映射将是:

x1 = y1

x2 = g^{-1}( y2, m(y1) )

这将是这种情况下的逆操作。

考虑所有数据

如果你希望考虑所有数据,在之前的构造中,我们认为 y2 可以依赖于 x1,但 y1 不能依赖于 x2。如果我们希望输出的所有元素都依赖于输入的所有元素,这是可以做到的,但你可能需要在每一层之后翻转输入。这样,在下一个迭代(下一个函数,记住它是一系列层或函数的组合)中,你可以让 y1 依赖于 x2,并且你可以继续这个过程,以确保所有输出变量都依赖于所有输入变量。所以你现在也可以将其写为加法耦合操作。

加法耦合操作

在加法耦合操作中,你可以说:

y1 = x1

y2 = x2 + m(x1)

所以,在这种特定情况下,它不是一个函数 g,你只是有 y2 = x2 + m(x1)。这里的逆操作将是 x1 = y1 本身,x2 = y2 - m(y1)。

在这种加法耦合层的构造中,雅可比行列式将始终为1。为什么这么说?你可以看看这两个方程并给出答案,因为它是加法耦合。∂y1/∂x1 将是1。∂y2/∂x2 也将是1,因为这里的第二项是加法的,并且不依赖于 x2。所以,你雅可比矩阵的所有对角线元素(你的雅可比矩阵的对角线元素是什么?你会有 ∂y1/∂x1, ∂y2/∂x2, ..., ∂y_k/∂x_k,假设两者维度相同)如果你看这些方程,所有这些值都将是1。对角线元素的乘积将是1,雅可比行列式将是1。这被称为体积保持操作。

体积变化与雅可比行列式

记住我们说过,雅可比行列式是体积变化程度的一个估计。例如,如果你有一个随机变量 x,它具有一定的密度,比如在0到1之间是均匀密度。如果你有另一个随机变量 z,它从0到3。如果它的体积像这样,那么你可以看到梯度会给你一个答案为3,所以对于你在 x 中移动的每一个单位,你将在 z 中移动三个单位。所以,这告诉你 x 和 p(z) 之间的体积增加了两倍(译者注:应为变为三倍),梯度 dz/dx 的行列式也给出了答案3,这在某种意义上直观地告诉你在新的PDF中体积变化了多少。

因此,当雅可比行列式始终为1时,就像在加法耦合层中一样,它是一种体积保持操作。所以在这种情况下,对数似然变得非常简单;你只需知道雅可比行列式项始终为1,所以该项会消失,而 p_x 的对数似然就是 y 本身的对数似然。

Real NVP:实值非体积保持标准化流

对NICE的一个改进是另一种流行的标准化流方法,称为Real NVP(实值非体积保持标准化流)。正如其名称所示,它是非体积保持的,所以我们肯定需要做一些超越加法耦合的事情。我们做什么呢?

我们进行仿射耦合操作。仿射耦合操作做什么?我们说:

y1 = x1

y2 = x2 ⊙ exp( s(x1) ) + t(x1)

这里 ⊙ 是哈达玛积,表示它是逐元素乘积。所以 x2 是一个向量,这里的第二项也是一个向量,它是 x2 与 exp(s(x1)) 的逐元素乘积,再加上 t(x1)。所以有一个平移分量和一个缩放分量,它们共同构成一个仿射变换。

这种变换的雅可比矩阵仍然是一个下三角矩阵,因为 y1 不依赖于 x2, x3 等等,类似地 y2 不依赖于 x3, x4 等等。所以雅可比矩阵的上三角项将为0。在下三角项中,你会有 ∂y2/∂x1,而对角线项在这种情况下将是 diag( exp( s(x1) ) ),因为这就是 ∂y2/∂x2 的微分结果。

在这种情况下,雅可比行列式不一定为1,因此变换可能不是体积保持的,这在真实世界数据中更可能发生。例如,如果我们考虑用于标准化流的随机变量 z 是一个单位高斯分布,期望任何真实世界数据分布也具有与该单位高斯分布相同的体积,这可能不是一个非常正确的假设。从这个意义上说,Real NVP更现实。

逆操作

这里的逆操作会是什么?

x1 = y1

x2 = ( y2 - t(y1) ) ⊙ exp( -s(y1) )

记住这个指数函数。当你转到另一边时,它变成了逆指数函数,这就是这个项表示的含义。

图解说明

形象地说,这就是Real NVP仿射耦合的样子:你有你的 x1, x2;y2 依赖于 x1 和 x2;y1 仅依赖于 x1。y2 如何依赖于 x1 和 x2?通过一个平移分量和一个缩放分量。逆操作,正如你在这里看到的,看起来像这样,其中 x2 将依赖于来自 y1 的缩放分量和除了 y2 贡献之外的平移分量。

网络训练

然而,所有这些网络都是被训练的。因此,其余部分应该像任何其他神经网络一样工作。所以,它是关于最大化似然的。你可以使用标准的误差函数,如均方误差或交叉熵,根据你试图重建的内容以及这些网络每一层的输出来学习网络。例如,y1 = x1 将是神经网络的一层,第二层将是这个,其中缩放和平移由神经网络学习,依此类推。这就是网络学习的方式。

总结

在本节课中,我们一起学习了超越VAE和GAN的其他深度生成模型,重点是标准化流方法。我们了解了标准化流的核心思想是通过一系列可逆、可微的变换,将简单的先验分布(如高斯分布)映射到复杂的数据分布,并能够精确计算数据的概率密度。我们介绍了NICE模型中的加法耦合层及其体积保持特性,以及更先进的Real NVP模型中的仿射耦合层,它允许非体积保持的变换以更好地建模真实数据。这些方法通过最大化对数似然进行训练,为显式密度估计提供了一种强大的框架。

067:超越VAE和GAN - 其他深度生成模型 - 02

在本节课中,我们将继续探讨深度生成模型,重点介绍自回归流模型。我们将了解其核心思想、几种具体实现(如NADE和MADE),以及它们在图像生成领域的应用,例如Pixel CNN和Pixel RNN。课程内容旨在让初学者理解这些模型如何通过分解概率密度函数来生成数据。

自回归流模型概述

上一节我们介绍了标准化流模型。本节中,我们来看看另一类重要的深度生成模型——自回归流。

自回归流,顾名思义,源于自回归思想。在标准的机器学习中,自回归模型常用于时间序列数据,即通过观察过去n个时间步的数据来预测下一个值。例如,根据过去10天的股价预测第11天的股价。这类问题可以使用ARMA、ARIMA等经典时间序列模型解决。

我们在此讨论的是该思想的一种扩展。然而,现在的目标不是预测单一结果,而是生成数据。这标志着从监督学习到无监督学习的转变。

自回归用于生成

我们来看看如何将自回归用于数据生成。我们将寻找真实世界数据概率密度函数(PDF)的任务分解。给定数据 x1 到 xn,其联合概率密度函数 p(x1, ..., xn) 可以写为一系列条件概率的乘积:

p(x1, ..., xn) = p(x1) * p(x2|x1) * p(x3|x1, x2) * ... * p(xn|x1, ..., xn-1)

在计算某一层的条件概率时,模型只能看到出现在它之前的输入。具体工作原理如下:如果 x1 到 xn 是输入的不同维度,那么 x1 被用作第二个网络的输入,该网络可以预测 p(x2|x1)。这个概率(取决于 x2 可能取的值)可以通过均方误差、交叉熵等损失函数来学习。接着,x1 和 x2 共同贡献给第三个网络,以学习 p(x3|x1, x2),依此类推,直到 p(xn|x1, ..., xn-1)。

因此,自回归流可以看作是标准化流的一个特例,其中每个中间变换都屏蔽了某些输入——并非每一步都查看所有输入。如果假设网络1到网络n是不同的函数(类似于标准化流),并且每个网络只查看输入的一部分而非全部,就可以这样理解,尽管实际实现方式略有不同。

神经自回归分布估计(NADE)

以下是NADE的工作原理。我们有输入 x1 到 xN,它们是每个输入向量的维度。同样,我们有 p(x1),p(x2|所有小于2的随机变量),p(x3|所有小于3的随机变量),直到 p(xn|所有小于等于n的随机变量)。我们试图理解真实世界数据的概率密度函数,它是所有这些条件概率的乘积。

具体操作是:将所有输入提供给一个共享的神经网络(层数可自定义)。该神经网络的输出然后传递到一个隐藏表示层,这些隐藏表示是在被屏蔽的输入上获得的。也就是说,这里的节点 H_n 依赖于所有小于 n 的 x 值。具体做法是:x1 仅用于计算 H2;x1 和 x2 用于计算 H3;x1、x2、x3 等用于计算 H4,依此类推,所有小于 n 的 x 值用于计算 H_n。因此,必须屏蔽输入,以确保相应的输入到达隐藏层中的相应节点。

接下来,每个 H1 到 Hn 通过不同的网络传递,以得到 p(x1),p(x2|x<2) 等。

推理与生成

在推理或生成时,如何实际生成数据呢?在训练好这样一个网络后(回想一下GANs,我们知道训练后如何生成图像:只需从高斯分布采样一个向量,通过生成器得到图像),NADE的做法类似。现在可以假设 H1 等价于生成样本时的潜在变量 z。可以假设 H1 来自高斯分布,从中采样一个值,通过第一个网络,会得到某个 x1。这个 x1 现在被馈送以得到 H2,用于得到 p(x2);然后 x1 和 x2 一起被馈送以得到 H3,用于得到 p(x3);接着用于得到 x4,依此类推,直到最后一个网络预测 p(xn|xn-1)。可以重复这个过程,直到生成完整的数据样本。

掩码自编码器分布估计(MADE)

MADE是NADE的一种改进变体。在这种情况下,我们使用自编码器来实现与NADE相同的效果。我们有 x1, x2, x3, x4,以及 p(x1),p(x2|所有小于2的随机变量),p(x3|所有小于3的随机变量),直到最后一个随机变量。

我们如何将NADE适配到这种架构呢?有一个有趣的方法。首先,为每个输入节点和输出节点分配一个顺序(这里简单设为1,2,3,4)。同时,为隐藏层的每个节点也分配一个顺序,但每个隐藏层节点的顺序号必须小于 n(例如,这里 n=4,所以每个节点应分配数字1、2或3,必须小于4)。下一个隐藏层也是如此。

这样做的目的是什么呢?我们现在保留那些连接节点 i 到节点 j 的权重,其中对于所有隐藏层,要求 i <= j。例如,标记为1的隐藏节点应只连接到输入节点1(因为 i<=j,且 j 最大为1)。标记为3的隐藏节点可以连接到输入节点1、2和3。标记为2的隐藏节点连接到输入节点1和2,依此类推。我们只保留这些权重,丢弃这些层的所有其他权重。

在最后一层,我们也只保留那些连接输出节点 i(对应 p(xj),其中 j <= i)的权重,使得 i < j。例如,输出节点2只连接到标记为1的隐藏节点;输出节点3连接到标记为1和2的隐藏节点,依此类推。同样,消除所有其他权重。

通过这个过程我们实现了什么?我们确保了 p(xi|所有小于i的随机变量) 只依赖于小于 xi 的输入。例如,p(x2|x<2)(注意图中的蓝色箭头)只连接到标记为1的节点,根本没有连接到标记为2的节点,这正是一开始的目标。这确保了只有输入 x1 影响 p(x2|所有其他小于2的随机变量)。同样,p(x3) 只依赖于标记为1和2的节点,p(x4) 依赖于标记为1、2和3的节点。

最终,自编码器只保留了一部分权重。在实现时,看起来是一个完整的自编码器,但在前向传播或反向传播时使用掩码,只保留特定的一组权重,丢弃另一组权重。这就得到了MADE。通过这个过程,在训练网络时,我们自动学习了这里的每个函数:p(x1),p(x2|x<2) 等,它们共同给出了给定数据的真实密度函数。

自回归图像生成模型:Pixel CNN

另一类自回归模型被称为Pixel CNN,于2016年在NeurIPS会议上提出。其核心思想是:给定一张图像,我们的总体思路是将其通过一个CNN(不改变形状),并得到一个逐像素的Softmax分布来生成相同的数据。

但我们的实现方式是通过引入像素掩码过滤器,指定哪些像素应用于预测输出中特定像素位置的值。例如,要预测图中这个特定的像素,可能不需要所有像素,而只需要预测像素周围邻域中的某些像素集。这就是Pixel CNN的思想。正如所见,它的计算速度非常快,过程并不复杂。

然而,Pixel CNN可能无法利用完整的上下文信息。例如,用于预测特定像素的像素掩码方式,可能只关注出现在它之前的一组像素值。在这种情况下,如果要预测这个红色像素,可能会使用所有这些蓝色像素。这提供了一种局部上下文感,但也缺失了该区域中同样构成局部上下文的其他像素。因此,在生成过程中可能会丢失一些信息,尽管其计算速度极快。

自回归图像生成模型:Pixel RNN

同一作者在ICLR 2016上提出的一个变体被称为Pixel RNN。Pixel RNN有一个非常相似的想法,但生成是使用LSTM而不是CNN完成的。同样,这是一个自回归模型,图像是逐像素生成的。每个像素依赖于之前的像素,基于一个有向图。

因此,整体联合分布 P(x) 由 i=1 到 n^2(假设是 n x n 图像)的 P(xi|所有直到该特定像素的其他像素) 给出。如图所示,任何特定像素都依赖于它之前的所有其他像素,以影响该像素值的生成。这些像素之间的依赖关系使用LSTM建模,因此得名Pixel RNN。

与本节中我们看到的所有模型类似,学习过程再次是通过梯度下降最大化似然。似然是可处理的,因为我们使用的是LSTM,我们知道它的工作原理,只需要相应地构建它,但除此之外,LSTM的训练方式与我们之前看到的相同。

Pixel RNN中的图像生成可能较慢,因为必须使用LSTM生成每个像素的值,而不是像GAN那样一次性生成整个图像。Pixel RNN也可以被视为完全可见模型的一个例子,其中没有潜在变量。Pixel CNN也是这样的例子。事实上,大多数自回归模型结果都是完全可见模型,在方法本身中并不对任何潜在变量进行建模。

Pixel RNN的变体

让我们看看该方法如何工作。Pixel RNN有两种变体:一种称为行LSTM,其中每个LSTM一次生成一整行像素。因此,当必须生成特定行 t 时,假设需要生成该行 t 中的一个特定值,这将是LSTM一个时间步的输出。LSTM应该查看什么,应该如何操作来生成这个特定的像素 i?如果我们考虑该行中该像素之前的像素集为 K_i,以及紧邻该行上方、i 的邻近像素为 K_s,它们两者都成为隐藏状态上下文,类似于LSTM生成当前像素。因此,隐藏状态和细胞状态将由某个权重 W_{hs} * H_{t-1} + W_i * x_i 给出,其Sigmoid函数将给出所有不同的门,其余部分与LSTM的操作非常相似。因此,输入基于 K_i 和 K_s 的值。可以这样看:如果要生成一个特定的像素(比如这个红色像素),可以从同一行的前一个像素以及前一行中这里的上三个像素获取上下文。然而,这三个像素从前一行的这五个像素接收输入,因此可以认为正在生成的这个红色像素在其生成过程中具有一个三角形的上下文。显然,由于这种生成方式,该模型将比Pixel CNN慢得多。

Pixel RNN中提出的另一种变体称为对角线双向LSTM。在对角线双向LSTM中,方法类似于双向RNN。双向RNN是一种RNN,在标准RNN中,x0 影响 y0,x0 和 x1 影响 y1,x0、x1 和 x2 影响 y2。在双向RNN中,还可以反向进行,所有这些箭头也可以反向操作。这意味着可以反转整个RNN,现在可以说 y2 仅基于 x2 生成,y1 基于 x1 和 x2 生成,y0 基于 x0、x1 和 x2 生成。如何学习这样的RNN?学习两个方向的权重,会得到两组不同的输出 y0、y1、y2,可以平均这些输出,这可能更好地指示输出应该是什么,特别是当序列中的方向可能不重要,且可能存在来自两个方向的上下文时。

这就是Pixel RNN中对角线双向LSTM变体所使用的思想。在这种情况下,图像是按对角线填充的。如图所示,每次填充一条对角线,当填充任何一个像素时,在该特定位置,这些值作为上下文来填充该像素 i。反过来,这两个用蓝色方块表示的像素依赖于之前的像素,因此它们最终都会影响这个像素 i 值的生成。我们现在也从另一侧的对角线重复这个过程,非常类似于双向RNN。

现在让我们看看,如果要生成这个特定的像素 i,我们会考虑哪些像素。你会说直接邻居,即上面的像素和右边的像素。不幸的是,不能使用右边的像素,因为根据图像的光栅化顺序(即逐行生成:第一行,然后第二行,第三行等),该像素尚未生成。那么如何克服这一点呢?在对角线双向LSTM方法中,当从另一条对角线过来时,使用这两个顶部像素作为上下文来生成这个特定的像素 i。这是唯一的变化。但现在尝试结合从这两个方向获得的上下文,然后在值 i 处进行生成,这就是我们讨论的双向RNN。因此,现在可以使用来自两个方向的完整上下文来看待这个中间的红色像素。为了生成一个像素(这里的红色像素),考虑从一条对角线填充时得到的所有深蓝色像素,以及从另一个方向填充时得到的浅蓝色像素,这提供了完整的上下文。同样,可以看出这比Pixel CNN慢。

总结与延伸阅读

本节课中,我们一起学习了自回归流模型。我们从基本概念出发,探讨了如何通过分解联合概率密度函数来生成数据。接着,我们详细介绍了神经自回归分布估计(NADE)及其改进版本掩码自编码器分布估计(MADE)的工作原理。最后,我们了解了这些思想在图像生成领域的应用,即Pixel CNN和Pixel RNN(包括行LSTM和对角线双向LSTM变体),并比较了它们的特点和性能。

本讲的课后作业是阅读Lillian Weng关于基于流的深度生成模型的精彩博客。如果你对每种方法感兴趣,请务必阅读此处链接的相应论文。该博客还涵盖了更新的方法,例如用于标准化流的Glow,如果你想知道更多,可以查阅。

以下是参考文献。

068:GAN改进 🚀

在本节课中,我们将学习生成对抗网络(GAN)在过去几年中的多种改进方法。我们将从对基础GAN的改进开始,简要讨论潜在空间的解耦,然后探讨GAN在图像和视频生成中的应用。

从基础GAN的改进开始

上一周我们学习了各种深度生成模型,如GAN、VAE、标准化流和自回归流。现在,我们将转向GAN在过去几年中被改进的各种方式。

首先,我们来介绍几种对基础GAN的改进方法。

StackGAN:分阶段生成高分辨率图像

我们将讨论的第一个方法称为StackGAN。这项工作发表于2017年的ICCV会议。

该工作的目标是生成分辨率达256x256、具有照片级真实感的图像,并且这些图像是基于文本描述生成的。以下是其高级流程。

整个生成器模型以一个文本描述为条件,例如一个图像标题。使用标准的NLP方法(如Word2Vec、GloVe、BERT等)来获取该文本描述的嵌入向量,并将其作为输入提供给生成器。

在第一阶段,生成器以该嵌入向量和标准高斯噪声为输入,生成64x64的图像,这些图像可能包含一些高级或低频细节。这些图像随后被提供给StackGAN的下一阶段,以生成细节更丰富、分辨率达256x256的照片级真实感图像。这两个阶段都以相同的文本输入为条件。

现在,让我们看看整个架构。

  • 文本输入与嵌入:你有一个文本描述(例如:“这只鸟是灰色的,胸部有白色,喙很短”),它被输入到整个StackGAN。通过Word2Vec、GloVe或BERT等方法获得其嵌入向量。

  • 第一阶段生成器:该文本嵌入向量被用来计算一个均值和标准差,从而对高斯噪声进行采样。这个采样结果与一个标准正态分布的样本连接起来,共同作为第一阶段生成器的输入。第一阶段生成器通过一个上采样模块生成一个64x64的图像。

  • 第一阶段判别器:生成的图像和真实图像的小批量样本被提供给判别器。判别器对这些图像进行下采样。在最终将其分类为“真实”或“虚假”之前,文本嵌入向量再次被引入并与下采样后的特征表示结合。此时,判别器的任务是判断这个(图像特征,文本嵌入)组合是真实的还是虚假的。

  • 第二阶段生成器:第一阶段的结果(64x64图像)与文本嵌入向量再次结合,作为第二阶段的输入。图像经过下采样,然后与文本嵌入连接,再通过一组残差块,最后上采样得到最终的256x256图像。

  • 第二阶段判别器:生成的256x256图像和真实的256x256图像被提供给第二个判别器。同样,下采样后的特征表示与文本嵌入向量连接,判别器需要判断每个这样的组合是真实的还是虚假的。

接下来,我们理解判别器如何查看这个组合并进行分类。

在这种情况下,判别器需要提供三种类型的分数:

  1. S_R:当提供真实图像和正确文本时,判别器希望给出高分(接近1)。

  2. S_W:当提供真实图像和错误文本时,判别器应给出低分。

  3. S_F:当提供虚假图像和正确文本时,判别器应给出低分,但生成器会试图提高这个分数。

现在,让我们理解优化过程是如何工作的。

与标准GAN类似,StackGAN交替地最大化判别器损失和最小化生成器损失。

  • 判别器:试图最大化S_R的对数似然,同时最小化S_W和S_F的对数似然。

  • 生成器:试图最大化 log(1 - S_F),同时也最小化从文本嵌入层输出的均值/标准差分布与标准正态分布之间的KL散度。

以下是StackGAN获得的一些结果。顶行显示的是2016年发表的基线方法“GAN-INT-CLS”的结果,底行显示的是StackGAN的结果。可以看到,StackGAN生成的图像质量在照片真实感方面远超早期方法。

Progressive GAN:渐进式生成高分辨率图像

另一个2018年出现的流行方法是Progressive GAN(渐进式GAN)。它旨在生成高达1024x1024分辨率的高分辨率图像。

其核心思想体现在方法名称中:该方法渐进式地增长生成器和判别器。这项工作发表在2018年的ICLR会议上。

它还包括一些其他设计决策,例如使用小批量标准差、均衡学习率以及生成器中的逐像素特征向量归一化。

Progressive GAN的整体思想如下图所示。生成器首先生成一个4x4的图像(如图中最左侧部分)。一个来自标准正态分布的潜在向量通过一个非常简单的网络生成4x4图像,该图像与真实样本一起提供给判别器D,以判断其真伪。

在接下来的训练迭代中,分辨率增加到8x8。与4x4设置相比,8x8的图像仍然模糊,但细节更多。可以注意到,当生成下一个更高分辨率时,生成器和判别器中都会添加新的层。这个过程不断重复,直到最终生成1024x1024的图像。

这种渐进式增长的关键优势在于能够稳定地训练GAN以生成高分辨率图像。

每当Progressive GAN从生成某个分辨率过渡到下一个更高分辨率时,如前所述,会引入新的层。这些层是如何引入的呢?这个过程与残差网络非常相似。例如,当从16x16升级到32x32时,16x16层输出的最近邻插值版本(得到32x32)会通过一个跳跃连接添加到新的32x32层的输出中。最终输出由 alpha * 新层输出 + (1 - alpha) * 插值投影层输出 给出,这允许新层以有机的方式逐渐融入,从而生成更好的图像。

如前所述,Progressive GAN还引入了一些其他设计决策。

  • 小批量标准差:在此步骤中,计算一个小批量中每个空间位置(即每个像素)上每个特征的标准差,然后进行平均。这个计算出的标准差被连接到判别器后续层的所有空间位置。为什么这样做? 这将作为本讲的课后思考题。

  • 均衡学习率:在这种情况下,生成器网络每一层的权重都由一个每层特定的常数 C 进行归一化。这样做可以确保每一层的权重尺度相似,从而有助于在训练期间保持权重的稳定。之所以称为“均衡学习率”,是因为权重的值实际上会影响梯度,从而影响学习率。虽然像Adam这样的方法会自适应调整学习率,但如果权重本身非常小或非常大,它们最终可能仍会得到较低的值。通过常数归一化,可以使所有层的权重在训练期间保持相似的尺度,从而实现更好的学习。

  • 生成器中的逐像素特征向量归一化:对于生成器中的每个卷积层,归一化定义为:b_{x,y}^i = a_{x,y}^i / sqrt( (1/N) * sum_{j=0}^{N-1} (a_{x,y}^j)^2 + epsilon )。其中,a_{x,y}^i 是第 i 个特征图中位置 (x, y) 的像素值,N 是特征图的数量。分母考虑了同一位置在所有特征图中的像素值,并用它来归一化第 i 个特征图的值。epsilon 是为了数值稳定性,避免除以零错误。

凭借这些贡献,Progressive GAN报告了令人印象深刻的结果。与Mao等人和Gulrajani等人的早期工作相比,采用渐进式GAN方法生成的结果具有更高的照片真实感和分辨率,细节也更丰富。

StyleGAN:基于风格控制生成

第三种更近期的GAN改进是2019年CVPR会议上发表的StyleGAN。它着眼于同样能生成高质量图像的Progressive GAN(也称为ProGAN),但ProGAN无法控制生成过程中的特定特征。例如,很难使用ProGAN并指定为图像添加某种颜色或改变特定属性。

StyleGAN的目标是能够使用预定义的特定风格来控制图像的生成。它是如何做到这一点的?它能自动学习高级属性的无监督分离(例如人脸图像中的姿态和身份)、随机变化(如具有大量随机性的发型)以及特定尺度的属性控制。

StyleGAN的直觉是:在图像中,粗略的分辨率(对于人脸图像)可能影响姿态、大致发型、脸型等属性。在中间分辨率(如162到322)会影响更精细的属性,如面部特征、更具体的发型、眼睛开合等。在最高分辨率(如642到10242)则影响色彩方案(眼睛、头发、皮肤)并引入面部的微观特征。

StyleGAN试图在这些分辨率级别上引入特定的风格组件以达到其预期效果。它是如何实现的?

给定一个输入随机向量(与基础GAN相同),StyleGAN首先对该向量进行归一化,然后通过8个全连接层(不改变输入维度)进行处理,得到一个512x1的向量 w。这个变换后的向量 w 被输入到生成器(或合成网络)。

现在,让我们看看这个向量 w 如何影响生成过程中的不同分辨率。这是通过引入一个矩阵 A 来实现的,该矩阵在不同分辨率下学习一种仿射变换。

合成网络(或StyleGAN中的生成器)有一个上采样模块,然后是一个称为自适应实例归一化的层,后面跟着一个卷积层,然后是另一个自适应实例归一化层,这个过程在多个块中重复。

让我们看一下其中一个卷积层和自适应实例归一化层的组合。一个卷积层的特定通道的特征图通过其均值和方差进行归一化,以实现自适应实例归一化。在此之前,输入到每个块的权重向量 w 会经过一个 A 映射,产生一组值 y_s^i 和 y_b^i,用于改变卷积层输出的尺度和偏置。这就是“自适应”归一化的来源。

具体来说,归一化后的值计算为:(x - μ(x_i)) / σ(x_i) * y_s^i + y_b^i。其中,(x - μ)/σ 是减去均值并除以方差的标准归一化操作,然后乘以从 A 变换得到的缩放值 y_s^i,再加上偏置值 y_bi**。注意,**y_si 和 y_b^i 在合成网络的不同块中可能不同,每个这样的变换从而定义了该层级特征的视觉表达,这使得输入潜在向量能够在生成的不同分辨率上产生不同的影响。

SPADE:空间自适应归一化

另一个近期方法是同样发表于2019年CVPR的SPADE。SPADE的核心思想是:先前的方法直接将语义布局图作为网络的输入。而在SPADE中,通过空间自适应归一化,输入布局通过这种空间自适应的学习变换来调制归一化层中的激活。

让我们看看这是如何发生的。在标准的批量归一化层中,你可以将输出视为经过一个仿射变换:缩放一个值并加上一个常数(即 gamma * value + beta)。然而,在SPADE中,一个语义分割图被作为输入。这个语义分割图经过一个卷积层,该卷积层输出一个 gamma 和一个 beta,用于对生成器的前一层进行归一化。

与盲目归一化特定层的输出特征图不同,在这种情况下,归一化是以空间自适应的方式进行的,其中 gamma 和 beta 来自语义分割图中的空间关系。语义分割图具有像素级的类别标签关联(例如,树、天空、山、草、路)。这被用来定义归一化的 gamma 和 beta。

一个随机潜在向量也可以用来操纵生成图像的风格,但语义分割图提供了一种利用每个像素的空间内容进行归一化的方法。这些 gamma 和 beta 是在像素级别定义的,允许每个像素根据语义分割掩码以不同的方式进行归一化。

以下是一些有趣的结果。可以看到,语义分割输出作为SPADE的输入(这些是像素级类别标注的掩码),以及对应的真实图像。与其他方法(第三、四列)相比,SPADE生成了相当逼真的输出,接近真实情况。SPADE中生成器的架构类似于许多其他GAN架构,但区别在于每个层都有语义分割掩码的输入。

BigGAN:规模化GAN用于更好的生成

最后,我们将讨论本讲座中最后一个目前非常流行的方法:BigGAN,发表于2019年的ICLR。BigGAN的重点是扩大GAN的规模以实现更好的高分辨率生成。它专为类别条件图像生成而设计,这意味着输入既包括类似于基础GAN的噪声向量,也包括一些类别信息(例如一个one-hot向量),它们共同提供给生成器以生成对应类别的图像。

BigGAN也引入了一些不同的设计决策。

BigGAN中的一个重要设计决策是使用自注意力GAN 的思想。SAGAN实际上是一项更早的工作(2018年底提出,2019年ICML发表),它引入了自注意力的思想。这与我们一周前在Transformer中看到的自注意力是相同的概念。在生成器中某一层的卷积图输出上,通过多个1x1卷积进行处理。其中一些分支经过变换和转置以获得注意力图。这三个分支类似于Transformer架构中的查询、键和值。注意力图用于聚焦于卷积图的特定部分,以便在下一层中生成图像的该部分细节。引入自注意力是因为通常的GAN在生成清晰图像时可能会遗漏更精细的细节(例如,在狗的图像中可能生成狗的毛发但遗漏了腿)。使用自注意力的思想是聚焦于图像的特定部分,从而更恰当地生成每个局部细节。

在BigGAN中,与SAGAN一起使用的损失函数是铰链损失。这个铰链损失类似于支持向量机中使用的损失函数:max(0, 1 - t*y),其中 t 是目标输出,y 是预测输出。这是因为GAN是类别条件的,所以你希望输出图像属于特定类别,这个类别信息在判别器阶段也会被使用,因为判别器现在不仅判断真假,还可以给出一个类别标签,其损失在这种情况下由铰链损失给出。

此外,BigGAN还以略有不同的方式引入了类别条件潜在向量。不是直接将类别条件潜在向量(如one-hot向量)提供给生成器的输入,而是将其作为单独的输入,在生成的多个阶段与某些输入连接,然后提供给每个残差块。

在每个残差块内部,连接后的类别标签向量通过线性变换(实际上是两个不同的线性变换)传递,并输入到块内的两个批量归一化层。

BigGAN还有其他有助于其性能的设计决策:

  • 谱归一化:对每一层的权重矩阵进行归一化,以确保其谱范数保持不变,并满足Lipschitz约束 σ(W) = 1。其主要思想类似于批量归一化,即用某些属性约束权重,以确保学习更好更快。在这种情况下,我们试图约束权重矩阵的最高奇异值。

  • 正交权重初始化:一种众所周知的权重初始化方法,其中每一层的权重被初始化为确保 W^T W = I,即权重矩阵是正交的。

  • 跳跃连接:我们已经看到,输入潜在向量 Z 连接到网络深处的特定层。

  • 正交正则化:鼓励权重在每次训练迭代中保持正交。这是通过一个正则化项 β * ||W^T W - I||_F^2 来实现的,其中 β 是一个系数。这确保了 W^T W 接近单位矩阵,因此矩阵是正交的。为什么我们需要正交正则化? 这将是本讲的另一个课后思考题。

BigGAN中采用的其他技巧包括:在每次迭代中,在更新生成器模型之前,先更新判别器模型两次;使用移动平均方法对最后几次训练迭代的模型权重进行平均(这在ProGAN中也做过);观察到使用非常大的小批量大小(如256、512、1024和2048)时性能最佳(实际上,他们观察到2048的小批量大小性能最好);将每层的模型参数或通道数量增加了一倍;并采用了称为截断技巧的方法。在训练时,生成器从高斯分布接收潜在向量。在测试时,你从高斯分布中采样一个潜在向量。如果该值小于某个阈值,则丢弃它并重新采样。这被称为截断高斯。其主要思想是,你不会使用这些潜在向量得到异常生成,而是得到属于该高斯分布PDF核心部分的生成结果。

总结

在本节课中,我们一起学习了多种重要的GAN改进方法。我们从StackGAN开始,它通过分阶段生成和文本条件化来创建高分辨率、逼真的图像。接着探讨了Progressive GAN,它通过渐进式增长生成器和判别器来稳定地生成极高分辨率的图像,并引入了小批量标准差、均衡学习率等技巧。然后,我们了解了StyleGAN,它通过自适应实例归一化和风格向量在多个分辨率上的控制,实现了对生成图像风格的高级操控。之后,介绍了SPADE,它利用语义分割图进行空间自适应的归一化,实现了精确的语义图像合成。最后,我们讨论了BigGAN,它通过结合自注意力、谱归一化、正交正则化以及扩大模型规模和小批量大小,在类别条件图像生成上取得了突破性成果。

这些改进展示了GAN研究领域的快速演进,从提升生成质量、分辨率到增强控制能力和训练稳定性。要深入理解这些模型,建议阅读深度学习书籍的第20章(关于GAN的综述)以及相关论文和代码。

课后思考:

  1. 为什么在Progressive GAN中使用小批量标准差?

  2. 为什么在BigGAN中使用正交正则化?

我们将在下次讨论这些问题。

071:深度生成模型 - 图像应用

在本节课中,我们将学习生成对抗网络(GANs)和生成模型在图像与视频领域的几个应用。我们将尝试涵盖GANs在不同领域的简单应用,以及这些生成模型的一些变体。

在开始之前,我们先回顾上一讲遗留的问题:为什么在解耦度量中使用的是互信息间隙,而不仅仅是互信息?希望你已经尝试寻找答案。互信息间隙旨在惩罚未对齐的潜在变量,因为我们关注的是与某个生成因子具有最高互信息的两个潜在变量之间的差距。因此,如果一个生成因子是两个潜在变量的组合,这将在互信息间隙中被惩罚,我们会得到一个0或较低的值,表明解耦程度较差。另一个原因是,如果一个潜在变量已经建模了一个生成因子,我们就不需要其他潜在变量来建模同一个因子。如果发生这种情况,我们希望表明解耦程度较差。这也说明了为什么需要定义“间隙”,而不仅仅是使用与生成因子相关的最高潜在变量的互信息。更多细节请参阅《Isolating Sources of Disentanglement in VAEs》论文,如果你感兴趣或需要更多信息,请阅读它。

现在,让我们来看一些GANs的应用。

🖼️ 图像编辑

简单的图像编辑,如将图像转为灰度或调整亮度/对比度,不需要理解图像内部的语义。然而,如果我们想处理一张人脸图像,例如将头发变黑、添加刘海、添加微笑或改变性别,就需要理解图像中的语义内容。GANs能帮上忙吗?有可能。然而,GANs没有固有的机制将图像映射到其潜在表示。我们之前看到VAEs可以做到,但正如在VAEs课程中讨论的,VAEs有时会存在重建问题。那么,如何使用GANs来实现类似功能呢?

这在一个名为可逆条件GAN的GAN变体中提出,发表于ECCV 2016研讨会,称为IC-GAN。IC-GAN通过以下架构实现目标:

它是一个条件GAN,与我们本周早些时候看到的跨域GANs非常相似。但现在,架构中还有一个编码器,可以将图像编码为其潜在属性。整体架构如下:有两个编码器,一个编码器给出图像的潜在表示z,另一个编码器给出图像的属性y。为了生成具有不同属性的新图像,你将图像通过编码器,得到一个属性向量y,然后改变其属性(例如,将黑发从0改为1,将棕发从1改为0),现在你就得到了具有黑发的同一张图像。

那么,这实际上是如何工作的呢?生成器G从潜在表示z和一些条件信息y中采样生成图像x',即 x' = G(z, y)。编码器执行逆操作:给定图像X,它输出z和y。如何训练这样的模型?其总体目标与我们之前看到的条件GANs相似,类似于跨域GANs的课程内容。

因此,我们有一个图像X和属性Y的对数似然,我们希望最大化训练数据中给定的这个似然。同时,我们也关注GAN的第二个目标:判别器D查看由生成器生成的元组(x_hat, y_hat),并判断其是真实的还是伪造的。这与我们尝试从文本生成图像的条件GAN类似。

这里的编码器有两个部分:E_z和E_y,分别对应潜在表示和属性。为了训练E_z编码器,首先训练生成器,然后使用生成器生成一个图像数据集。现在我们有了图像及其对应潜在表示的数据对,这些数据对通过生成器的性能获得,现在被用来训练编码器。因此,我们有生成图像G(z, y_hat)和用于生成该图像的对应z。现在,通过最小化这两者之间的L2范数或均方误差来训练E_z编码器。另一个编码器E_y则使用数据本身进行训练。我们假设训练数据包含数据和属性。因此,现在将y视为数据集中提供的属性,E_y(x)是预测的属性,通过最小化这两者之间的均方误差来训练E_y。

以下是在一个名为CelebA的流行数据集上的一些结果。你可以看到原始图像,以及通过改变多个属性后得到的相当逼真的多个重建/生成图像,例如秃顶、添加刘海、黑发、金发、眼镜、更多妆容、性别改变、苍白皮肤、微笑等。

🔍 超分辨率

GANs的另一个应用是超分辨率任务。我们之前在CNNs的背景下也讨论过这个问题,现在重新审视一下。图像超分辨率是从低分辨率图像获取高分辨率图像的任务,这可能是一个具有挑战性的任务,因为你必须填充原本不存在的信息。

SRGAN(超分辨率GAN)旨在生成具有高达4倍放大倍率的逼真图像。即使是CNNs也可以通过反卷积或在编码器-解码器架构的第二阶段进行上采样来用于超分辨率。在这些架构中,损失函数最小化生成输出与真实高分辨率地面实况图像之间的均方误差。由于对所有像素取平均的效果,均方误差可能导致输出图像过度平滑。因此,由于尝试计算均方误差,它可能不会尝试使特定像素更锐利,而这对于超分辨率可能很重要。GANs通过使用对抗性损失,将输出推向更接近自然图像流形,从而有助于获得高质量的超分辨率图像。

SRGAN是如何训练的?SRGAN有几个组成部分。其中一个损失被称为内容损失。内容损失最小化生成器输出G_theta_G与真实高分辨率图像之间的均方误差。但它不是测量图像本身之间的均方误差,而是最小化这两个图像特征表示之间的均方误差。是什么特征呢?这两个图像都通过一个VGG19网络。取VGG19网络中某个卷积层输出的特征表示,并考虑这些表示之间的均方误差作为损失函数来训练SRGAN。在这个特定情况下,Phi_{I,j}对应于在第i个最大池化层之前的第j个卷积层获得的特征图,W_{I,j}和H_{I,j}分别是用于归一化的特征图的宽度和高度。

用于训练SRGAN的第二个损失是标准的对抗性损失,它着眼于欺骗判别器,类似于我们迄今为止看到的其他所有GAN。用于训练SRGAN的最终损失是内容损失和对抗性损失的加权和。在这个特定案例中,研究表明,对抗性损失可以通过一个较小的权重(在他们的案例中是10^{-3})来获得良好的超分辨率性能。

使用这种损失,以下是一些示例结果。你可以看到图像的双三次插值结果、使用超分辨率CNN(如SRCNN)的结果,以及使用GAN生成的图像,与原始图像相比,后者清晰得多。更多自然场景的图像对比:双三次插值、使用SRCNN和SRGAN的结果,以及最终的高分辨率原始图片。你可以看到,SRGAN在局部细节上比超分辨率CNN清晰得多,也比双三次插值好得多。

🧊 3D物体生成

第三个应用是用于3D物体生成的GANs。到目前为止,我们已经看到GANs生成2D图像。GANs也能生成3D物体吗?相当直接。近年来,随着3D CNNs和体积卷积网络的进步,学习3D物体表示已成为可能。因此,生成器中的层现在必须生成一个体积,所以只要你能替换这些层,你就可以通过GAN框架生成任何物体。

在这个特定示例中,这项工作发表于NIPS 2016。3D-GAN的工作方式是:生成器G将一个长度为200的潜在向量映射到一个64 x 64 x 64的立方体,该立方体表示3D体素空间中的物体G(z)。判别器D则分类该物体是真实的还是合成的。因此,只有生成器中的架构与标准GAN不同。损失函数是标准GAN损失。这是通过改变生成器架构以处理3D体积,将GANs直接应用于3D物体的一个例子。

使用这种损失,当GAN被训练来生成3D物体时,你会得到相当好的各种3D物体生成结果,正如你在本幻灯片上看到的那样。

🎨 图像修复

我们将看到的最后一个应用是用于图像修复的GANs。图像修复是一项任务,其中图像的一部分缺失,而GAN的工作是填充图像中缺失的部分。GAN能被训练来执行图像修复吗?答案是肯定的。为此提出的一种方法称为PG-GAN,它使用一个标准的GAN框架,其中生成器是一个残差网络,它以部分缺失的图像作为输入,生成相同分辨率的图像。判别器分为两部分:一个全局判别器和一个局部(Patch)判别器。全局判别器在全局或图像级别进行推理,以决定图像是真实的还是伪造的。而局部GAN判别器在局部级别、块级别进行推理,以决定图像是真实的还是伪造的。因此,局部判别器有助于捕捉局部纹理细节并填充图像中那些缺失的间隙。

损失函数是什么?有一个重建损失,它测量生成图像与地面实况之间的逐像素L1距离。还有一个对抗性损失,其中对抗性损失分为两部分:一部分用于全局GAN,一部分用于局部GAN。总体目标是重建损失、全局GAN的对抗性损失和局部GAN的对抗性损失三者的组合。

通过这种训练过程和架构,以下是一些图像修复的结果,当从图像中取出一块时,GAN能够相当真实地重建那些部分。你可以在这里看到许多例子,其中可能有一些伪影,但GAN生成的内容仍然相当逼真。以下是建筑立面的更多结果,同样,GAN以逼真的方式填充信息。

关于这些应用的更多信息,请参阅3D GANs的链接,以及一个名为“GAN Zoo”的非常好的链接,它概述了可用于不同应用的各种GANs。

🎼 参考文献。

072:深度生成模型在视频中的应用 🎬

在本节课中,我们将探讨生成模型在视频理解领域的应用。我们将介绍三种不同的方法,它们分别解决了视频生成、视频预测和舞蹈动作迁移等任务。通过学习这些方法,你将了解如何将生成对抗网络和变分自编码器等模型应用于动态的视觉数据。


生成视频:场景动态生成法 🎥

上一节我们介绍了生成模型的基本概念,本节中我们来看看如何将其应用于视频生成。一个核心问题是:我们能否使用GAN来生成视频?答案是肯定的。视频本质上也是一种三维数据(空间+时间),因此可以借鉴3D物体生成的思路。

一种名为“Generating Videos with Scene Dynamics”的方法对此进行了独特改进。其生成器的输出由以下公式定义:

输出 = M(Z) ⊙ F(Z) + (1 - M(Z)) ⊙ B(Z)

其中:

  • Z 是输入生成器的潜在向量。

  • F(Z) 代表前景(动态部分)。

  • B(Z) 代表背景(静态部分)。

  • M(Z) 是一个掩码,指示每个像素应使用前景还是背景。

以下是该生成器架构的工作流程:

  1. 一个100维的噪声向量被输入到两个分支。

  2. 前景流:生成一个三维体积(视频序列),并在最后分叉为前景视频和对应的像素级掩码。

  3. 背景流:生成一个静态的背景图像。

  4. 最终,通过掩码将前景和背景逐像素融合,生成完整的视频。

判别器部分则采用标准的GAN架构,对生成的视频进行下采样,并通过一个二元分类器判断其真伪。损失函数与标准GAN相同。

通过这种方法,模型能够学习并生成具有动态前景和静态背景的视频。例如,它可以生成海浪退去或物体在固定场景中移动的短视频。


视频预测:基于姿态的预测法 🔮

上一节我们学习了如何生成全新的视频,本节中我们来看看一个相关的任务:视频预测。给定一段视频的前几帧,预测接下来的帧。直接使用GAN在像素空间生成未来帧可能导致结果模糊或结构不合理。

“Pose Nose”方法提出了一种解决方案:先在更高层次的抽象(人体姿态)上进行预测,再利用预测的姿态作为条件信息来生成像素级的视频帧。这相当于将问题分解为两步:

  1. 姿态预测:使用一个变分自编码器来预测未来帧中人物的姿态序列。

  2. 视频生成:将预测的姿态作为条件,输入到一个条件生成对抗网络中,生成具体的未来视频帧。

以下是姿态预测VAE的工作细节:

  • 编码器:接收当前帧的图像特征、历史姿态序列(P1...Pt)以及历史姿态关节点的速度(Y1...Yt)。

  • 解码器:接收编码器的隐藏状态Ht,并尝试以逆序重建姿态速度(Yt...Y1)。

  • 训练完成后,使用未来解码器。它接收过去的隐藏状态Ht,并从先验分布中采样,以预测未来的姿态速度(Yt+1...YT),从而得到未来姿态。

在视频生成阶段,GAN的生成器以历史视频帧和预测的未来姿态为输入,生成未来的视频帧。其损失函数包含两部分:

  1. 对抗损失:使判别器难以区分生成帧和真实未来帧。

  2. L1重建损失:最小化生成帧与真实未来帧在像素上的差异。

这种方法能够生成相对合理的未来帧,例如预测滑雪者的下一个动作位置。


舞蹈动作迁移:人人皆可舞法 💃

前面我们讨论了视频生成和预测,本节中我们来看一个有趣的应用:舞蹈动作迁移。“Everybody Dance Now”方法的目标是:给定一段专业舞者的视频和一段业余者的视频,生成一段业余者跳出专业舞步的视频。

该框架包含几个关键模块,以下是核心部分:

  1. 时序一致性GAN:确保生成的视频帧之间过渡自然。

    • 判别器的任务不再是区分“真/假”,而是判断连续两帧是否“时序连贯”。

    • 损失函数鼓励生成的连续帧(G(Xt), G(Xt+1))与真实的连续帧(Yt, Yt+1)具有相似的时序特性。

  2. 姿态归一化与视频生成:

    • 提取专业舞者的姿态序列(X1‘...XT’)。

    • 将这些姿态归一化到目标人物(业余者)的肢体比例上,得到(X1...XT)。

    • 使用预训练好的生成器(来自模块1),根据归一化后的姿态生成目标人物的视频帧。

  3. 面部细化GAN:由于动作迁移可能导致面部细节失真,此模块专门用于增强生成人脸的清晰度和真实感。

    • 从生成的帧中裁剪出面部区域。

    • 使用另一个GAN来学习一个残差图,将其加到原始生成的面部上,以添加细节。

    • 损失函数同样包含对抗损失和感知损失。

整个训练过程分阶段进行:先训练主生成器(阶段一),冻结其权重后训练面部细化GAN(阶段二),然后交替微调。该方法能成功地将一个人的舞蹈动作转移到不同体型、性别和背景的另一个人身上,实现有趣的“同步舞蹈”效果。


总结与思考 🧠

本节课中我们一起学习了生成模型在视频领域的三种应用:

  1. 场景动态生成法:通过分离前景和背景,使用GAN生成具有动态内容的短视频。

  2. 基于姿态的视频预测法:利用VAE预测高层姿态,再用条件GAN生成像素帧,实现未来帧预测。

  3. 舞蹈动作迁移法:通过时序一致性约束、姿态归一化和面部细化,实现跨人物的舞蹈动作迁移。

这些方法展示了如何将生成模型的强大能力与视频数据的时空特性相结合。最后留给你一个思考题:我们能否从单张图片生成一段视频?这将是生成模型另一个充满挑战和机遇的方向。

073:少样本和零样本学习 - 第一部分

在本系列课程的最后一周,我们将讨论一些与计算机视觉深度学习相关的高级主题。我们将从一个当代主题——零样本和少样本学习——开始。

在继续之前,我们先回顾一下上一讲留下的一个问题。在上一讲关于深度生成模型在视频理解中的应用中,我们看到了使用视频作为输入来生成视频的方法。留下的问题是:能否从单张图像生成视频? 你可能已经猜到了答案,答案是肯定的。我们现在不讨论具体方法,但你可以查看本幻灯片链接的文章及其相关论文。实现这一点的核心思想是通过一种称为单样本学习的方法。我们使用单个实例(在我们的案例中是一张图像),网络学习生成完整的视频。有趣的是,单样本学习是少样本学习的一种特定设置,也是本讲的重点,这为我们即将讨论的内容提供了一个很好的过渡。更多细节,请查阅相关文章以了解如何从单张图像生成视频。

概述

在本节课中,我们将学习少样本和零样本学习的基本概念、问题定义以及解决这些挑战的三大核心思路。深度学习模型在训练时严重依赖大量标注数据,这是它们表现出色的原因之一,但也是其主要局限之一。如果只有少量样本可供学习,深度学习模型可能无法提供良好的模型和预测性能。正则化方法在一定程度上用于避免低数据情况下的过拟合,但它们并不能在数据稀缺时提供良好性能。那么,我们该怎么办?解决方案是训练能够仅凭少量样本甚至没有样本就能快速泛化到新任务的模型,这正是本节课的重点。我们的目标是使模型能够在数据标注不可行、成本高昂或新类别随时间增加的现实场景中有效工作。

问题定义

上一节我们介绍了课程概述,本节中我们来看看少样本和零样本学习的具体问题定义。

这个问题的设置被称为少样本学习或零样本学习。让我们首先回顾一下这两者的形式化定义。

假设 x 是一张图像或一个特征(例如,通过VGG或ResNet网络提取的特征),y 表示该数据点的类别标签。

少样本学习的形式化定义如下:

  • 训练数据 D_train 由 (x_i, y_i) 元组组成,总共有 I 个元组。

  • 对于某些类别,只有很少的训练样本。

  • 少样本学习也常被描述为 N-way K-shot 设置,其中总共有 C 个类别。对于 N 个类别(N < C),每个类别只有 K 个样本,而对于其余类别,则拥有大量样本。这些拥有大量样本的类别也被称为基类。

零样本学习则是当“少样本”变为“零样本”时的情况。这意味着:

  • 训练数据包含 (x, y),但现在我们希望分类器能够对训练集中从未出现过的类别(即零样本类别)的数据点进行分类。

  • 对于这些零样本类别,完全没有标注数据。

  • 因此,训练数据由 x、y 以及每个类别 y 相关的属性 a(y) 组成。这些属性可以是文本描述或一组特征(例如,对于动物,是否有尾巴、四条腿、鬃毛等)。我们将 a(y) 视为即使数据不可用也能提供的元数据。

  • 在测试时,输入 x 来自未见过的类别 x_U,y 是对应的标签,a(y) 是对应的属性。关键点在于,未见类别 Y_U 与已见类别 Y_S 的交集为空。

学习设置分类

在少样本和零样本学习内部,还有两种进一步的分类方式。

1. 传统少样本/零样本学习

  • 目标:学习一个分类器 F。

  • 测试时,待识别的图像或特征 x 仅来自未见类别或少样本类别,而不来自基类或已见类别。

  • 这是该领域早期(约四五年前)方法采用的设置。

2. 广义少样本/零样本学习

  • 目标:学习一个分类器 F。

  • 测试时,待识别的图像或特征 x 可能属于已见/未见类别或基类/少样本类别中的任何一类。

  • 显然,广义设置更具挑战性,也更有用,因为在现实世界中,你无法预测测试图像只会来自未见类别或少样本类别。

理论基础:泛化误差分析

上一节我们明确了问题定义,本节中我们深入探讨其背后的理论基础,以理解为何数据稀缺会带来挑战。

让我们回顾监督机器学习的一个基本原则:经验风险最小化。

如果 P(x, y) 是输入 x 和输出 y 的真实联合概率分布,监督学习的目标是:给定一个假设(模型)h,最小化相对于该联合分布的期望风险或损失。期望风险 R(h) 定义为损失函数 L 在联合分布上的积分(或期望):

R(h) = ∫ L(h(x), y) dP(x, y) = E[L(h(x), y)]

然而,在现实世界中,我们无法估计真实的联合概率分布 P(x, y)。因此,这个期望被近似为相对于我们拥有的训练数据的经验风险:

R_emp(h) = (1/I) * Σ_{i=1}^{I} L(h(x_i), y_i)

现在,假设我们有一个模型族 H(例如,所有线性模型)。我们定义三个函数:

  • h*:在模型族 H 中,能最小化期望风险 R(h) 的最佳函数。

  • h_cap:在所有可能的模型中,能最小化期望风险 R(h) 的全局最优函数(真实函数)。

  • h_I:在模型族 H 中,通过最小化经验风险 R_emp(h) 得到的函数(即我们实际训练出的模型)。

我们模型 h_I 与全局最优模型 h_cap 之间的泛化差距可以分解为两部分:

R(h_I) - R(h_cap) = [R(h_I) - R(h*)] + [R(h*) - R(h_cap)]

  • 第一部分 [R(h_I) - R(h*)]:称为估计误差。它衡量了由于使用有限训练数据(经验风险最小化)而非真实分布(期望风险最小化)而导致的,在模型族 H 内部最优解 h* 附近的偏差。

  • 第二部分 [R(h*) - R(h_cap)]:称为近似误差。它衡量了由于我们选择的模型族 H 的局限性,其内部最佳模型 h* 与全局最优模型 h_cap 之间的差距。

为什么现在讨论这个?在少样本和零样本学习中,我们面临的核心问题是数据不足。这直接影响了估计误差。当你有大量标注数据时,经验风险最小化器 h_I 会非常接近模型族内的最佳期望风险最小化器 h。然而,在少样本/零样本设置下,对于某些类别,我们没有足够的训练数据,因此 h_I 可能远离 h,导致模型过拟合训练数据,泛化性能差。

解决思路

上一节我们分析了数据稀缺导致泛化误差增大的原因,本节中我们来看看解决这一问题的三大核心思路。

我们可以从三个不同角度来解决这个问题:数据、模型和算法。

1. 数据角度:扩充训练数据

  • 思路:简单地增加训练样本数量,从 I 增加到远大于 I 的 Ĩ。

  • 作用:这有助于获得对经验风险最小化器更好的估计。通过利用先验知识(如数据增强、合成数据),我们可以将训练得到的模型 h_I 推向更接近理想模型 h* 的位置。

2. 模型角度:约束假设空间

  • 思路:利用先验知识或领域理解,将模型族 H 约束到一个更小的、更合理的子空间。

  • 作用:限制模型的复杂度,使得即使只有少量训练数据,也足以在这个较小的假设空间内训练出一个可靠的、接近 h* 的模型 h_I。

3. 算法角度:改变搜索策略

  • 思路:利用先验知识来改变在假设空间 H 中搜索最佳参数 θ 的策略。

  • 作用:这可以包括提供更好的参数初始化点,或者改变优化算法本身。一个好的起点可以帮助算法更快、更稳定地收敛到一个更优的模型 h_I。

这三种方法都是解决少样本/零样本学习问题的有效途径。

方法分类

基于上述三大思路,少样本和零样本学习方法可以大致分为三类:

以下是基于不同解决思路的方法分类:

  1. 嵌入学习方法:对应模型角度。通过先验知识约束假设空间,学习将图像和类别(或其属性)映射到共享的嵌入空间,在该空间中进行比较和分类。

  2. 幻觉/特征合成方法:对应数据角度。通过先验知识扩充训练数据,例如,根据类别属性合成少样本或零样本类别的特征或图像。

  3. 参数初始化方法:对应算法角度。利用先验知识改变搜索策略,例如,设计元学习算法,学习如何快速将模型参数初始化为适合新任务的状态。

在接下来的课程中,我们将详细探讨每一类方法。

总结

本节课中,我们一起学习了少样本和零样本学习的基础知识。我们首先明确了问题定义,区分了少样本学习、零样本学习以及传统与广义设置。接着,我们从经验风险最小化的角度分析了数据稀缺导致模型泛化能力下降的根本原因——估计误差增大。最后,我们介绍了解决这一挑战的三大核心思路:从数据角度扩充样本、从模型角度约束假设空间、以及从算法角度优化搜索策略,并对应地概述了嵌入学习、特征合成和参数初始化这三类主流方法。在下一讲中,我们将深入每一类方法的具体实现。

074:少样本与零样本学习 - 第二部分

概述

在本节课中,我们将学习解决少样本和零样本学习问题的三类核心方法:嵌入学习方法、数据生成方法和参数初始化方法。我们将了解每种方法背后的关键思想、工作原理以及它们如何帮助模型在数据稀缺的情况下进行学习。


嵌入学习方法:学习“比较”

上一节我们介绍了少样本和零样本学习的基本概念。本节中,我们来看看第一类方法——嵌入学习方法,也称为基于模型的方法。

这类方法的核心直觉是通过学习“比较”来解决少样本学习问题。如果一个模型能够判断两张图像的相似性,或者判断类标签语义的相似性(这在零样本学习中需要),那么它就可以通过与训练时见过的标记实例进行比较或关联,来对未见过的输入进行分类。

总体思路是学习独立的嵌入函数。在此上下文中,嵌入函数是神经网络对训练样本和测试样本的输出表示。这些比较模型使用一种称为“元学习”的方法进行端到端训练。在测试时,预测是基于比较测试样本特征与每个类别的训练集特征之间的距离做出的。

以下是整体示意图:你有一个少样本训练集 D_train 和一个测试样本 P_test。你为训练集学习一个嵌入 G,为测试样本学习一个嵌入 F,然后计算相似度以对目标类别标签做出最终预测。

什么是元学习?

元学习被称为“学会学习”。如果我们考虑少样本学习的 N-way K-shot 设置(总共有 N 个类别,每个类别只有 K 个示例),在元学习中,每个“情节”都有一组不同的训练类别和一组测试示例。这些训练类别可能并非完整训练数据中的所有类别,而是从数据中随机采样的一部分类别。该情节的目标是训练一个“学习者”,使其能够基于这个训练集对该测试集进行预测。这就完成了一个元学习情节,输出一个学习者。在第二个元学习情节中,再次采样一组不同的类别形成训练数据集,并有一个测试集,元学习者再次优化学习者以解决该情节中的问题。这个过程在多个情节中重复,每个情节可能采样不同的训练类别集合。在元测试时,你最终拥有希望部署最终模型的训练类别和测试样本设置。这里的元训练和元测试情节中的类别是不相交的,目标是你已经学会了“学习一个模型”的方法。

另一种理解方式是:在经典的机器学习算法设置中,你有一个训练集,学习一个具有特定数量参数的模型,目标是在测试集上获得良好性能。在元学习中,你有一个元训练集,它有自己的训练和测试分割,对应一个元学习情节。你学习一组元参数 θ,用于在新的元测试集上训练学习者。元学习者知道如何为元测试情节中出现的训练类别集合生成模型,以解决这些测试样本。

匹配网络:一个具体例子

让我们看一个元学习思想的具体例子。这被称为“匹配网络”,是 2016 年 NeurIPS 上发表的一种方法。匹配网络的前提是结合参数模型和非参数模型。参数模型从训练样本中缓慢学习模型参数,理想情况下需要大数据集以避免过拟合。不幸的是,在我们讨论的少样本和零样本设置中,可能没有大数据集。另一方面,像 K 近邻这样的非参数模型允许快速吸收新样本,并且对一种称为“灾难性遗忘”的现象具有鲁棒性。灾难性遗忘是指:如果一个模型在一组类别上训练(例如在 MNIST 的 0-6 类上训练),当你在稍后出现的 7 和 8 类上训练或微调模型时,模型可能会忘记 0 或 1 的样子。神经网络模型已知容易受到这种现象的影响。非参数模型则自动对此类现象具有鲁棒性。

在匹配网络方法中,提出结合两者的优点。在训练阶段,你学习基于余弦相似度的嵌入模型。你有一个样本集 S(像一个元学习情节中的训练集)和一个查询样本 Q(像该情节中的测试集)。你学习嵌入函数 F,为训练集和测试集生成相应的嵌入。然后使用余弦距离来衡量相似度。在测试时,一旦获得这些嵌入,就使用基于余弦距离的最近邻方法来分类测试样本。

以下是匹配网络中使用的架构。在一个元学习情节中,你有一组来自给定训练类别的样本,以及该情节中的一个测试样本。这些训练样本作为序列输入给一个 LSTM,LSTM 为每个输入训练样本输出一个表示。你同时获得测试样本的表示。然后,一个比较模块查看测试样本与每个训练样本之间的相似性。该比较在公式中给出为 a(X_hat, X_i),其中 X_i 是不同的训练样本。在这个方法中,a 函数的计算非常简单:取测试图像嵌入 h(X_hat) 与每个训练图像嵌入 g(X_i) 之间的余弦相似度,然后对这些余弦相似度进行 softmax 操作,得到一个分布,显示测试样本最接近哪个训练样本,将其与类别标签相乘就得到测试样本的最终类别标签预测。这里的一个细节是,LSTM 是双向的。这是因为我们不希望 LSTM 依赖于作为输入给出的训练样本序列。通过双向(前向和反向)处理,试图抵消对序列的依赖。

关系网络:一个改进想法

一个改进的想法被称为“关系网络”,发表于 2018 年 CVPR。在这种方法中,作者质疑使用余弦嵌入的做法。相反,该方法提出训练和学习一个数据驱动的非线性度量,而不是使用余弦距离度量。这使得模型能够提取数据样本之间复杂的非线性关系,从而更好地泛化到新类别。事实上,关系网络也容易扩展到更具挑战性的零样本设置,其中未见类别没有任何样本。

以下是整体示意图。你仍然有 S 和 Q,即训练样本和查询样本。它们经过一个学习的嵌入函数 F_φ(该网络的参数)。现在,你取属于一个类别的一组样本的类别均值 μ,并将查询样本与该类别的均值表示进行比较。如何比较?这种比较也是由模型学习的,而不是使用余弦距离。因此,在训练阶段,你元学习嵌入模块 F 和关系模块(学习表示之间关系或距离度量的模块)。在测试阶段,给定查询样本,你使用在嵌入空间中对每个训练类别均值的“关系分数”来最终预测查询样本的类别。

以下是整体架构。在给定的元学习情节中,你有训练集和作为单独测试样本输入的查询样本。所有这些都经过相同的嵌入模块 f_φ,得到相应的特征图。与匹配网络不同,关系网络将这些特征图连接起来。黄色条对应 x_j 的特征,其他颜色的条是训练集中每个其他 x_i 的特征。连接后,将其馈送到关系模块 G(其参数也是学习的),最终为 x_j 与每个 x_i 之间的相似性产生一个标量分数,给出为一个关系分数向量,最后通过对关系分数进行 softmax 或 argmax 操作,可以预测出最终的 one-hot 向量。

这个网络如何训练?关系分数 R_ij 对训练网络至关重要。损失函数由参数集上的求和给出:(R_ij - 1(y_i == y_j))^2。这意味着,每当查询样本的标签 y_i 与训练样本的标签 y_j 匹配时,我们希望 R_ij 为 1(因为相似度高);当标签不匹配时,R_ij 将被强制小于 1。

这如何用于少样本或零样本学习?在少样本学习场景中,我们所描述的正是所做的:你对嵌入模块进行逐元素求和以获得类别特征图,将类别特征图与查询特征图结合(我们刚谈过连接),然后获得关系分数以最终得到预测标签。对于零样本,因为你可能对某些类别没有任何样本,所以需要所谓的语义类别嵌入。你可以获得关于类别的元信息,可以是一组属性、类别标签的任何其他文本描述,或者直接取类别标签本身的词向量表示。现在,连接和嵌入操作符比较类别标签的嵌入与每个训练样本的嵌入,用于获得相似性并能够给出最终结果。


数据生成方法:学习“增强”

上一节我们介绍了基于“比较”的嵌入学习方法。本节中,我们来看看第二类方法——数据生成方法,也称为幻觉或特征合成方法。

这类方法的直觉是学习“增强”。我们看到了学习比较,现在我们讨论学习增强。在这里,我们学习一个生成模型来“幻觉”出新的、未见类别数据以进行数据增强。通过生成更多数据,我们将少样本或零样本学习问题简化为标准的监督学习问题。

如何做到这一点?一个标准方法是学习一个以某些元信息为条件的生成器,使用基础类别中的数据。然后,我们以未见类别的元信息为条件生成新类别特征。最后,我们在基础类别样本和生成的新类别样本上训练一个分类器。简而言之,在你的元学习情节中,你有一组训练样本和一个测试样本。使用一些元信息和训练样本本身,你训练一个生成模型,最终将其提供给一个分类器。当然,由于是生成模型,你可能有一个对抗性组件来判断生成的特征是真实的还是伪造的,但你也有一个分类组件来指示如何分类这些最终生成的特征。

FCLS-WGAN:一个具体例子

这个领域的一个流行方法是 FCLS-WGAN。目标是:给定场景类别的训练集,我们学习一个条件生成器 G,它接收输入 Z(你给 GAN 的标准随机噪声)和对应于每个类别的类别嵌入。使用这些,生成器学习生成图像特征。我们这里不需要生成图像,因为我们的目标不是生成漂亮的图像,而是生成可以分类的图像表示,这使其成为一个更可行的问题。为了确保这种方法生成的特征是好的,还有一个判别器模块,它最小化生成特征上的分类损失。FCLS-WGAN 尝试以属于不同类别的某些属性为条件来生成图像特征。生成的是哪种图像特征?这些是标准图像在 CNN 模型上的输出特征。如果你使用 VGG 或 ResNet 作为 CNN,你会得到图像的某种表示。我们现在要求生成器 G 生成类似的向量特征,而不是生成图像。

如何将其扩展到少样本学习?这将是零样本学习的方法,其中某些类别没有样本,这就是为什么我们需要类别嵌入。然而,在少样本学习中,你已经有一些类别的样本,它们也可以用于帮助生成。因此,对于少样本,你可能不需要类别嵌入,而可能只使用噪声本身作为输入的生成器。当将这种方法扩展到少样本学习时,这样的 GAN 如何训练?该方法使用两个组件 CLS 和 WGAN,因此得名。WGAN 组件使用标准的 GAN 损失,但使用一种称为 WGAN 或 Wasserstein GAN 的变体,这是一种试图缓解 GAN 中模式崩溃问题的变体。此外,还有一个分类损失用于对生成的特征进行分类。在你的数据集中有一些图像,你有一个 CNN,其输出给出一些真实特征 x。另一方面,你的生成器接收噪声向量和一些类别信息,也生成一些图像特征 x_tilde。然后,判别器接收 x 和 x_tilde 以及条件信息(类别),并判断这些 x 和 x_tilde 是真实的还是伪造的。但还有另一个损失,它获取生成的特征并尝试将它们分类到你的类别全集中的某个类别。最终损失是 GAN 损失和分类损失的组合,使用系数 β 适当加权。

重复一下,为了训练分类器,你使用预训练的生成器,以类别嵌入为条件生成未见类别样本,分类器通常是一个 softmax 分类器,在训练集(给出 x)和生成的未见类别图像特征(x_tilde)上训练,两者一起用于训练此框架中的分类器模块。


参数初始化方法:学习“微调”

上一节我们介绍了基于“增强”的数据生成方法。现在,让我们转向第三类方法,即参数初始化方法,它们尝试使用先验知识来改变搜索策略。

这里的核心直觉是学习“微调”。我们看到了学习比较和学习增强,现在我们讨论通过学习微调来解决少样本问题。这意味着什么?我们学习一组元模型参数,这些参数对于所有类别(在此上下文中我们说“任务”,每个类别是一个任务)是通用的。我们学习一组可以应用于所有类别或任务的元参数,使得对于给定类别的极少样本,你可以微调那些元模型参数来学习该类别的参数。

以下是其工作原理的总体直觉或方法。对于每个元学习情节,你有 D_train 和 D_test。你更新一些任务特定参数 φ_i,以最小化该类别的总体参数损失。记住,你有一个 θ,这是一组总体模型参数,你用它初始化模型。然后你微调它,以解决当前元学习情节中仅一个特定类别的问题。然后,在给定元学习情节中的所有任务中,你现在更新元参数 θ,以最小化所有这些类别的损失。在测试时,如果你有来自某个少样本类别的几个样本,你已经学习了作为元模型参数的 θ,你现在可以使用这些少量样本微调 θ,以获得测试时该特定少样本类别的模型。

MAML:模型无关元学习

这类方法中最流行的方法称为 MAML,代表“模型无关元学习”。多年来 MAML 有几个改进,但我们将讨论基本方法,其思想非常相似。任务特定参数 φ_i 通过优化获得。总体公式如下:记住,这类方法的目标是学习一个先验,使得模型能够轻松适应新任务。我们在此讨论少样本学习的背景,期望少样本类别有几个样本。这里的“先验”指的是良好的参数初始化,这正是 θ 或元模型参数试图实现的。

你的任务特定更新是:从初始化 θ 开始,在每个元学习情节中,更新任务特定参数 θ_i‘,其值为 θ - α * ∇_θ L(θ, D_train_i),这为你提供了该元学习情节中特定类别的参数。一旦完成,θ 然后使用 θ - β * ∇_θ Σ_i L(θ_i‘, D_test_i) 更新。从视觉上看,正如我们在上一张幻灯片上解释的,你试图学习一个 θ,如果微调,可以轻松获得 θ_1、θ_2 和 θ_3*,这些是类别 1、2 和 3 的理想模型参数。因此,元模型参数指的是可以轻松微调的 θ。如果你观察任务特定更新和元更新,你会注意到,你现在可以用 θ - α * ∇_θ L(θ, D_train_i) 替换损失内部的 θ_i‘。当你这样代入时,你会看到 θ 等于 θ - β * ∇_θ [ L(θ - α * ∇_θ L(θ, D_train_i), D_test_i) ]。在某种意义上,这几乎等同于对 θ 进行二阶导数计算。然而,这提供了一种使用此元学习方法计算 θ 更新的高效有效方式。

以下是相同方法的算法视图。你随机初始化 θ。在给定的元学习情节中采样一组任务。对于该元学习情节中的任务,从每个类别中采样一组数据点(记住,这里的类别和任务含义相同)。你评估相对于每个类别的损失梯度,并更新任务特定参数 θ_i‘。然后,你为元更新从相同类别中采样一组数据点。然后,基于这些样本进行元更新,结合所有任务特定损失的梯度。每个任务在这里是一个类别,这给出了你的元模型参数更新,完成一个元学习情节。然后你重复此过程,取下一组任务。

扩展到零样本学习:Meta-ZSL

我们说过 MAML 用于少样本学习。这能用于零样本学习吗?一个更新的方法展示了如何做到这一点,这被称为 Meta-ZSL 或广义零样本学习的元学习。这里的想法是再次学习以类别属性为条件,然后使用与 MAML 相同的元学习框架进行训练,从而促进向新类别的泛化。

关键思想是使用元学习框架,其中每个情节都有一组训练类别和测试类别,但在每个情节中,它们将模拟零样本学习类型的设置。让我们看看这是如何完成的。有一个 GAN 与分类器模块耦合,这导致了三个元学习者:一个生成器 G(元学习者,学习元模型参数),一个判别器 D(也学习元模型参数),然后是一个分类器,检查生成特征的质量。你可以将其视为特征合成方法(用于进行零样本学习)和 MAML(用于实现基于元学习的方法)的组合。

让每个模块 D、G 和 C 的参数分别为 θ_D、θ_G 和 θ_C,我们将 θ_GC 表示为 θ_G 和 θ_C 的组合参数集。现在,学习是如何完成的?给定已见类别的图像和属性,通过 CNN 传递图像,你会得到一组图像特征 x 和相应的属性 a。然后,给定噪声和属性,要求生成器生成特征 x_tilde。给定 x_tilde 和 a,判别器尝试判断这是 0 还是 1(真实或伪造),然后你有一个分类器,它判断生成的特征是否属于特定类别。这类似于特征合成框架,但这里有一个关键组件,即元学习框架,这将其与 FCLS-WGAN 区分开来。

这是通过以下方式完成的:对于判别器,目标是最大化生成真实图像特征 x 和相应属性 a_c 的对数似然,并最小化生成图像特征和相应属性的对数似然。另一方面,生成器试图通过获取属性和噪声并生成 x_tilde 来欺骗判别器,我们希望判别器将其称为真实,并且你有分类损失。除了这个目标,你现在还有元学习情节。基本更新是:给定元模型参数 θ_D,你现在为元学习情节中的每个任务或类别获得 θ_D‘,与 MAML 非常相似。然后,在该元学习情节结束时执行元更新,结合该元学习情节中所有任务的损失,并更新 θ_D、θ_GC 等。因此,你将 FCLS-WGAN 置于元学习框架中,就得到了 Meta-ZSL。

一旦完成,推理的方式是使用学习到的生成器生成未见类别样本(假设你可以将未见类别的属性作为条件输入给生成器),得到 x_hat。一旦生成未见类别样本或其特征,你可以使用分类器,也可以训练任何其他分类器来预测零样本类别或所考虑问题中任何其他类别的类别标签。


总结

本节课中,我们一起学习了解决少样本和零样本学习问题的三类主要方法:

  1. 嵌入学习方法:核心是“学习比较”,通过元学习训练模型,使其能够衡量样本或类别之间的相似性,例如匹配网络和关系网络。

  2. 数据生成方法:核心是“学习增强”,通过生成模型(如 FCLS-WGAN)合成新类别的特征或数据,将问题转化为有充足数据的标准监督学习。

  3. 参数初始化方法:核心是“学习微调”,通过元学习(如 MAML)获得一组良好的模型初始化参数,使得模型在面对新类别时,仅需少量样本进行微调即可快速适应,并可扩展至零样本学习(如 Meta-ZSL)。

这些方法各有侧重,共同为数据稀缺场景下的机器学习提供了强大的工具。

075:自监督学习 🧠

在本节课中,我们将学习一个当前非常热门的话题——自监督学习。我们将探讨其基本概念、动机以及几种核心的实现方法。

概述

上一节我们讨论了无监督学习的各种任务。本节中,我们将深入探讨自监督学习,这是一种特殊的无监督学习方法,它通过巧妙地从无标签数据中构造监督信号来学习有用的数据表示。

自监督学习是无监督学习的一种变体。其核心思想是:在没有显式标注或类别标签的情况下,利用数据本身来生成某种“标签”,从而在无标签数据上诱导出一个监督学习模型。具体来说,我们会设计一些被称为“前置任务”或“辅助任务”的监督任务,通过这些任务学习有意义的表示。这些学到的表示可以使模型更好地准备去解决下游任务,例如分类或语义分割。

为什么需要自监督学习?

我们知道,深度监督学习在拥有大量标注数据时效果很好。然而,现实世界中存在海量的无标签数据。如何利用这些数据?我们从人类学习中获得灵感:人类并非通过监督来学习一切,而是通过观察、预测和自我反馈来学习。自监督学习正是借鉴了这种自我反馈的学习范式。

常见的自监督前置任务

以下是几种在计算机视觉中流行的自监督前置任务示例。

1. 图像修复 🖼️

这个任务的目标是遮挡或移除图像的某一部分,然后要求网络完成图像。这不需要外部标签。例如,在2016年CVPR发表的Context Encoders工作中,研究者训练了一个编码器-解码器框架来填充图像的缺失部分。损失函数结合了L2损失和对抗损失,以生成更真实的修复结果。

核心公式:

总损失 = L2损失 + 对抗损失

2. 解决拼图游戏 🧩

这个任务的目标是教会模型理解图像由多个部分组成,并学习部分到对象及其空间排列的映射。具体做法是,将图像分成9个瓦片并打乱顺序,然后让神经网络预测正确的排列顺序。这是一个分类问题,使用交叉熵损失进行训练。

核心代码逻辑:

# 假设有9个图像块,对应N种可能的排列
# 网络输出一个N维的概率分布
predicted_permutation_index = model(shuffled_patches)
loss = cross_entropy_loss(predicted_permutation_index, correct_permutation_index)

3. 预测旋转角度 🔄

给定一张图像,对其应用多种旋转(如0°,90°,180°,270°)。然后使用同一个CNN模型来预测所应用的旋转角度。这有助于网络学习图像特征。训练时使用标准的交叉熵损失。

核心公式:

设旋转操作函数为 G, CNN为 F。

模型输出:p = F(G(image)), 损失:L = CrossEntropy(p, rotation_label)

4. 图像着色 🎨

此任务的目标是获取灰度图像,并预测其彩色版本。假设数据集中已有彩色图像,我们将其转换为灰度图作为输入,让网络预测颜色。为了简化任务并得到更鲜艳的颜色,通常将输出颜色空间(如Lab空间中的a、b通道)量化为多个区间(例如313个),从而将回归问题转化为分类问题。

核心思路:

输入:灰度图(L通道)。

输出:量化后的颜色类别(a、b通道的组合)。

posted @ 2026-10-17 13:17  绝不原创的飞龙  阅读(5)  评论(0)    收藏  举报