序列之思:从零推导循环神经网络与注意力机制
编辑本文从第一性原理出发,系统性地探讨了现代神经网络处理序列数据的核心思想。文章首先剖析了传统神经网络在处理变长、有序数据时遇到的根本瓶颈,然后循序渐进,逻辑必然地推导出循环神经网络(RNN)的核心设计——参数共享。在此基础上,文章进一步构建了经典的编码器-解码器(Encoder-Decoder)架构,并对其“信息瓶颈”问题进行了深刻的批判。最终,通过对问题的层层剖析,文章独立地“重新发明”了注意力(Attention)机制,并详细阐述了其Query, Key, Value三大核心组件的内在逻辑与设计哲学。这是一篇带领读者从问题根源走向前沿方案的深度技术之旅。
引言:为何传统网络无法理解序列?
传统的前馈神经网络(Feed-forward Neural Networks, FNNs)对输入向量并行处理,在前向传播期间并未对序列中元素之间复杂先后依赖关系进行处理,因此,对序列数据的位置信息的捕获以及序列元素之间的复杂先后依赖关系的抽象不足,FNN类传统的神经网络不适合处理这类序列问题。此外,FNN和卷积神经网络(Convolutional Neural Networks, CNNs)处理的都是固定长度的输入和输出,无法处理变长的输入和输出。
神经网络如何处理任意长度的输入或者输出的问题,即输入输出的数据长度是可变的。例如文本在不同语言之间的翻译问题,输入的文本长度和输出的文本长度是不固定的。举个例子,当输入的是英文“How can we use variable input lengths”,其对应的中文翻译是“我们如何使用可变输入长度”,两者长度并不等同(分别7和12)。
此外,例如语言数据和蛋白质结构数据等类型的数据存在有顺序依赖性,即数据是位置敏感的,同一个数据的不同顺序所代表的含义是不同的,例如:“I love you”和“you love I”,虽然顺序不同,但是表达的含义发生了变化。上述这种不同元素之间具有严格的先后依赖关系的数据称为序列数据。与此同时,序列数据不止有上述离散的情况,还存在有连续的情况。例如股票的价格随着时间的流逝而发生变化,此时时间是连续的,股票的价格也是连续的。
因此,为解决上述问题,需先对序列数据进行建模,然后再构建新的神经网络结构解决变长输入输出、以及数据之间的先后依赖关系的问题。
为序列建模——时间的引入与挑战
序列数据指的是数据中的元素之间存在有严格先后顺序,不同的顺序含义不同。因此,首先需要对数据中的顺序进行建模。受到股票市场股价随时间变化的启发,引入时间维度,将序列数据视为随着时间流逝的元素组成的列表,该列表中的元素自然是按照时间顺序排序的。时间是连续的,可被划分为若干个时间间隔相同的时间片,每个时间片的索引称为时间步,而每个时间步对应的时间片所包含的数据称为一次采样/观测/状态。
由微分学的知识可知,上述对顺序的建模方式通过增加时间步的个数(即缩短采样的时间间隔),可拟合连续的序列数据。假设在一个有限的时间区间 [0, T] 内,我们将它划分为 N个时间片。那么,随着 N\to\infty,采样序列对真实函数 f(t)的最大误差趋于零。用数学公式表达就是:
其中,t_i是第i个时间步的时刻,A_i是在t_i时刻的采样值。
序列数据的具体含义由数据中的组成元素以及元素的顺序共同表达,引入时间维度的序列数据只解决了位置信息,其不能表达各元素复杂的先后依赖关系。位置索引的方式只能告知“谁在哪里”,却无法给出“位置2的数据是如何依赖于位置1的”信息。因此,需要对不同位置是如何依赖于前面各元素的关系建模。
循环神经网络(RNN)——让网络拥有“记忆”
核心思想:模拟人脑的阅读流程
受到上述人脑处理序列数据的启发,可对人脑的工作流程进行建模。假设某人正在阅读一段文字“I love you”,大脑首先阅读到“I”字,记住并理解该单词的含义后,阅读第二个字“love”。在记住并理解该字的含义后,将其与前面已经理解的“I”进行整合理解后,继续阅读第三个字,并重复上述过程直到结束,得出该文本序列的最终含义“我爱你”。上述工作流程可被建模为:逐位置处理每一个时间步的数据,理解当前时间步的数据所表达的含义后,与前一时间步的理解相整合,得出本时间步为止的综合理解,该建模方式具有不受输入长度的限制,在解决了对复杂依赖关系的建模的同时,解决了神经网络的变长输入的难题。
RNN单元:记忆的基本模块
需要注意的是,即使逐位置处理输入数据每一个元素是标量时,也应将该位置的元素/数据视为向量。原因是在序列数据中,是按照时间片划分数据的,时间片是一个跨度(即时间间隔),因此将各时间片中的数据视为一个向量,即各位置的元素被视为一个向量。
由于需要逐位置(即逐时间步)对序列中的元素进行处理,因此可先建模每个时间步如何处理,然后建模每一个神经网络层,最后构建整个神经网络架构。对每个时间步执行单步计算的核心逻辑单元(Unit)称为一个RNN单元(RNN Unit),其是一个参数化的向量值函数f_{\pmb \theta}: (\pmb x_{t}, \pmb h_{t - 1}) \mapsto \pmb h_t,其中:
- \pmb \theta:神将网络参数
- \pmb x_{t}:t时刻的输入向量
- \pmb h_{t -1}:t-1时刻的隐藏状态向量(即对累计到t-1时刻时止的先验知识/理解的表示向量/数值表示)
- \pmb h_t:t时刻的隐藏状态向量(即累计到t时刻时止的先验知识/理解的表示向量/数值表示)
该映射可被表示为下图:
一个重要的“错误”:参数不共享的陷阱
将这些计算单元串联起来,在逻辑上就构成了一个RNN层(RNN Layer)。具体而言,在数学层面上,该RNN层可建模为下式:
其中W_i是第i个计算单元的参数矩阵。从该建模方式可以看出,在宏观上,一层RNN层指的是:处理一个完整的输入序列并生成一个输出序列的逻辑处理/计算单元。一个完整的RNN可以由多个这种层进行堆叠,每一个层的输出是另一个层的输入,层与层之间是顺序流动的,因此,其处理方式是串行的。
上述建模是从宏观的角度来进行建模的,并未涉及到对于具体细节的处理。现从最基础的层面的细节(即RNN单元)继续讨论。在RNN单元中,涉及到当前时刻的输入\pmb x_t以及前一个时间步t-1时所累计的“理解”——隐藏状态/上下文向量\pmb h_{t - 1}。这里涉及到如何将上下文向量\pmb h_{t - 1}与当前时刻的输入\pmb x_t信息融合和浓缩成新的一个单一表示,一个最直观且容易操作的办法就是采用线性组合的方式,该操作类似于颜料调色板将多种基础颜色搅拌融合成一个单一的新颜色,新颜色中包含由所有基础颜色,只是比例/强度不同。此外,由于线性组合的方式表达能力有限(即只能表达线性关系),因此引入非线性函数增强表达能力。上述过程可以建模为:
其中W_t为当前计算单元的参数,\pmb b_t为线性偏置项,\sigma是一个非线性的向量值函数。当有了RNN单元的具体建模后,可进一步对RNN层进行建模,只需要将这若干个计算单元串联起来即可。
然而,当RNN层中的RNN单元数量比较多时,由于每个计算单元都独立的享有参数W_t, \pmb b_t,上述建模方式会造成参数量巨大的问题。而参数量巨大的问题会造成对数据量需求庞大,容易过拟合以及空间复杂度高的问题。这会导致RNN网络训练效率低下,最终获取到的模型泛化能力不佳的问题。
最终解法:参数共享的威力与归纳偏置
为此,可借鉴CNN时的方法,对于每一层中每个RNN单元的参数共享,即:
这里隐含的归纳偏置是,假设处理序列的底层语法或时间规律是稳态的(Stationary),即在序列的不同位置应用的是同一套规则,模型处理序列的规则不应随位置而变。因此,“含义解析方式”不应随时间步剧烈改变。采用参数共享的方式,正是将这种“规则不变性”的先验知识注入到了模型中,从而降低模型复杂度,提高泛化能力。
编码器-解码器架构——构建完整的序列到序列模型
从编码器到解码器
上述RNN网络可接受变长输入,并最终得到的一个信息融合和浓缩后的上下文向量(Context Vector)。若把获得最终上下文向量的表示的过程称为编码(Encoding),则上述RNN被称为编码器(Encoder)。现在有了对序列数据的综合理解,可以接下来根据所获得的上下文向量进行进一步的处理以实现下游任务,即建模解码器(Decoder),实现对最终上下文向量表示的解码(Decoding)。
自回归生成:解码器的工作方式
根据上述问题描述,可得最终的目标是实现一个编码器,实现对上下文向量的解码,并获得长度可变的解码序列数据。由于解码数据是一个序列数据,其每个时间步所解码的数据除了依赖于上一个时间步的上下文向量,还应依赖于上一个时间步所解码的结果。因此,可以将建模好的RNN编码器修改一下,变为一个RNN解码器。具体而言,首先将RNN编码器的初始状态修改为编码完成的上下文向量,并在初始状态,给定一个特殊的输入标记作为开始,则该RNN编码器即可变身为RNN解码器。
需要注意的是,RNN解码器在每个时间步不止计算当前的上下文向量表示,也需要给出基于当前的上下文向量以及当前时间步的输入(即上一个时间步所生成的预测向量)时的解码向量。假设在第t时间步,\varphi是矩阵U为参数的向量值函数,该函数又被称为解码函数,进而解码器的RNN单元解码过程可建模为下式:
进而可将解码器RNN单元组合成一个共享参数的RNN层,并可堆叠多个RNN层,以加强解码能力。上述解码过程被称为自回归生成(Auto Regressive),即每个时间步一次生成一个元素,每次生成新的元素时,将之前生成的元素输入进去。
采用上述的编码器对序列数据进行编码,并采用解码器对编码后的上下文向量进行解码,生成对应的解码序列数据的模型称为编码器-解码器(Encoder-Decoder)模型,是序列对序列(Sequence-to-Sequence)模型的一种实现方式。
注意力机制——打破信息瓶颈的革命
Encoder-Decoder架构的神经网络模型存在有哪些优缺点?Encoder-Decoder模型可以处理变长的输入输出序列,且能够建模复杂的序列元素之间的先后依赖关系。但是该模型是序贯式的依次处理输入的每一个元素(即串行处理),效率低下;此外,由于Encoder将序列数据信息融合浓缩成单一的新的向量表示,当输入序列长度较长时,使用线性组合会在较远处稀释最前面的信息,造成灾难性遗忘,使得解码器部分生成的结果不佳,即长距离依赖问题,这个问题也被称为信息瓶颈。
核心洞见:“回头看”的艺术
由于串行处理变为并行化处理较为困难,现先解决长距离依赖的问题。产生长距离依赖问题的根本原因是将多个“理解”压缩成一个最终的单一上下文向量表示,而这个单一上下文向量表示的质量会制约着解码器生成的结果的质量。因此,若解码器在解码时不止能参考编码器生成的最终上下文向量,而是能够参考编码器任意时刻的隐藏状态向量,从中提取必要的信息以生成最佳的预测结果,即能够打破该瓶颈。
为了实现上述解决方案,需对上述过程进行建模。在建模过程中所面临的首要问题是:在当前时间步t时,解码器如何知道需要参考编码器的哪一时刻或哪些时刻的“理解”(即隐藏状态向量)?此外,编码器在知道参考哪些隐藏状态向量后,需先综合“理解”这些隐藏状态向量后,才能作为内化的先验知识,作为当前时间步生成预测值时的知识补充。因此,在建模时需要建模的核心要素是:
- 从编码器的所有时刻中,查找合适的参考向量,即解码器如何知道该看哪里?
- 找到参考向量后,综合理解参考向量,即解码器找到后如何综合理解?
在上述两个过程中,由于编码时存在有信息丢失,存在有认知不确定性和建模时存在的固有不确定性,无法硬性规定解码器参考哪一或哪些编码器的隐藏状态,因此需将上述过程建模为概率模型。
机制建模:Query, Key, 和 Value 的“角色扮演”
现对引入参考向量解码器解码过程采用数学语言进行描述,令输入序列长度为m,\pmb c_t为t时刻解码器综合理解所有可能隐藏状态向量所生成的参考向量,假设现在位于解码器的时间步t,已知的信息包括上一个时间步t-1时的隐藏状态向量\pmb s_{t - 1}、\pmb c_{t - 1}以及t时刻的输入值\hat{\pmb y}_{t}。则解码器t时刻的隐藏状态向量\pmb s_t可被表示为:
这与Encoder-Decoder的解码过程唯一不同的是引入了参考向量\pmb c_{t - 1}和\pmb c_t。现在的核心问题就是,参考向量\pmb c_{t - 1}和\pmb c_t如何获取。为解决该核心问题,首先需解决查找问题,解码器如何知道该看哪里?解码器在t时刻的隐藏状态\pmb s_t为对当前输入以及已有先验知识的信息整合和综合理解,其在一定程度上包含有若想生成最佳的预测值\hat{\pmb y}_t所需要补充哪些信息的表示,因此,需要找到一个能根据解码器当前的信息生成代表当前解码器意图的解码函数,即:
其中\mathrm{Query}被称为查询映射,\pmb q_t为t时刻的查询向量(即解码器寻求补足信息的意图的向量化表示)。
若想根据查询意图\pmb q_t查询适当的参考向量,需知道编码器中各隐藏状态都包含有什么信息,为了高效匹配,只需要知道各隐藏状态所包含信息的摘要或关键字即可。令编码器所生成的全部隐藏状态向量集合为\mathcal H = \{\pmb h_i | i = 0,1,\cdots, m\},需要找到一个编码函数,即:
其中\mathrm{Key}被称为关键字映射,\pmb k_i为编码器第i个隐藏状态的关键字向量(即第i个隐藏状态所包含信息的总结/摘要/关键字)。
由于\pmb q_t代表了在t时刻解码器想要解码时所需要的额外信息的向量表示(即查询意图),为了能够知道哪些隐藏状态向量是所需要的,只需要做\pmb q_t与\pmb k_i的相关性检测(即向量相似度匹配),而向量点积即可完成该功能,定义查询向量\pmb q_t与编码器第i个隐藏状态的关键字向量\pmb k_i的内积e_{ti}为下式:
e_{ti}也被称为得分,得分越高,则两者越相似,匹配程度越高。
此时,已经解决“解码器如何知道该看哪里”的问题。现在开始解决“解码器找到后如何综合理解”的问题。解码器在解码阶段直接利用编码器阶段所生成的隐藏状态向量作为参考向量进行解码是存在不足的,原因在于:编码器的隐藏状态的目的是为了记忆和理解已有信息,它将这些记忆和理解打包成一个“知识块”——它是一个动态的、不断被新的信息所更新和丰富的知识载体,交给下一个时间步的自己(即编码器自身),继续处理直到处理完当前序列中的所有位置。这导致编码器的隐藏状态的优化目标只是用来记忆和理解输入序列的元素及其依赖关系,其并没有着重于在解码器解码阶段应该着重的如何提取并利用“知识块”中的哪些信息。
为了解决上述不足,引入一个专门用来从隐藏状态提取信息的函数\mathrm{Value},学习如何以一种最有效的方式提取内容,即:
它代表的是:为了能够更好的解码,解码器应该从给定的隐藏状态向量中提取什么信息。函数\mathrm{Value}类似于打铁中的材料预处理过程,拿到原始材料——铁锭(原始的隐藏状态\pmb h_i),根据需求打造成粗胚——\mathrm{Value}(\pmb h_i),在粗胚上进行精细加工——后续的解码过程。这种方式比直接采用\pmb h_i作为解码器解码时的参考向量要好,它将解码过程(下游任务)与编码过程关联起来一同优化(即端到端),使得模型编解码适配度更高,泛化性能更好。
终极融合:加权的期望与上下文
到目前为止,对于编码器中的任一隐藏状态向量\pmb h_i,其有键值对(\pmb k_i, \pmb v_i),其中\pmb k_i是对\pmb h_i所包含信息的高度总结,\pmb v_i是利用h_i中的信息进行解码时所需要关注的全部信息。这种将索引和具体取值解耦的方式,赋予了模型更大的灵活性和表达能力,它允许对“如何索引信息”和“如何呈现信息”进行分开学习。
现在,我们有了“看哪里”的依据(得分e_{ti})和“看什么”的内容(值向量\pmb v_i)接下来就是综合理解。该过程的核心问题是解决整合所有可能的信息向量\pmb v_i,形成单一的参考向量\pmb c_t。根据大数定律,样本均值依概率收敛于总体均值,因此可采用一阶原点矩进行估计,即:
其中K表示的是关键字的随机变量,\Pr(\cdot)为概率分布,V为值的随机变量。将上式展开,可得:
其中\Pr(K)可通过softmax函数进行建模,可得具体分布函数:
其中\pmb e = [e_{t0}, e_{t1}, \cdots, e_{tm}]^\top。
上述整个过程是对获取参考向量\pmb c_t的建模,总结一下生成\pmb c_t的整个过程:
- 更新解码器的隐藏状态\pmb s_t
- 生成Query
- 计算得分并获得新的参考向量\pmb c_t
- 生成最终的预测值\hat{\pmb y}_t
在上述建模中,\mathrm{Key}, \mathrm{Value}, \mathrm{Query}全部都是可学习函数,由于需要做信息正好,可以采用线性变换和引入非线性增强表达能力。上述模型被称为注意力机制(Attention Mechanism),跨越编码器的这种注意力称为交叉注意力机制(Cross Attention),查询向量与键向量的内积的得分称为注意力得分。注意力机制的整个过程可类比为概率性数据库检索,若将每一个状态向量称为一条记录的话,每一条存储的记录都有对应的键。首先注意力机制生成一个检索请求,然后去与数据库中的键进行比对,由于是概率性的,因此需计算相似度,获得与每一条记录的匹配概率。最后,通过点估计返回最终的结果。
总结
从克服传统网络处理序列数据的局限性出发,我们一步步构建了能够处理变长序列并拥有“记忆”的RNN。进而,通过模拟“编码-解码”的过程,我们得到了强大的Seq2Seq模型。最后,通过洞察其信息瓶颈,我们引入了革命性的注意力机制,让模型学会了“专注”,极大地提升了处理长序列的能力。这条从简单循环到动态注意力的演进之路,正是过去十年中序列建模领域最激动人心的思想跃迁。
- 1
-
分享