序列数据
是由一组相互关联的样本组成的数据,其中任意样本对应的标记是由其自身和其他样本共同决定的;序列数据任务
是输入或输出为序列数据的机器学习任务,用传统机器学习模型处理他们是困难的,比如 序列模型(1)—— 难处理的序列数据 中第 3 节的例子one-to-one
模型,即一个输入一个输出。这种模型会把序列数据作为一个整体来考虑,其输入输出的尺寸必须是固定的,比如输入一张固定尺寸的图像输出其类别,或者输入一段固定长度的句子预测下一个词 Note:借助一些手段,可以强行用传统模型处理变长的输入输出问题,如 各种监督学习范式(强监督、半监督、多标记、偏标记、多示例、多示例多标记、标记分布…) 中的多示例多标记问题,但是这些模型本质上还是要将输入输出处理成固定的长度(比如用固定长度的 0/1 向量选出不同数量的样本作为输入输出)。至于样本间的关系,虽然可以通过网络自己学出来,但由于缺乏考虑这种关系的显式结构,所以很难学得好(可以参考 从模型容量的视角看监督学习)
many-to-many
模型,即支持可变长度的输入输出,并且最好能对序列样本间的关系进行显式建模,注意这样的模型也可以直接用来处理 one-to-many
,many-to-one
甚至 one-to-one
问题。语音识别、本文情感分析、序列预测等等序列任务都能被这种模型更好地处理 RNN 就是一种良好的序列模型,下图给出了各类输入输出情况下的 RNN 结构
传统监督学习任务
(many-to-one),常将序列模型作为 “特征提取器”,只使用最后一个隐状态 ht\pmb{h}_thhht 作为整个序列的特征向量,用它接一个分类头或回归头作为整个模型的输出。在训练时,只要像图像分类等普通监督学习任务一样训练即可标准语言模型任务
(one-to-many),即不断根据之前序列样本预测下一个样本值的任务,通常会如下图所示做 Autoregress,这时我们会增加一个分类头或回归头将隐状态 h\pmb{h}hhh 变换为输出 x\pmb{x}xxx,推断时不断地将上一步模型输出合并到下一步模型的输入中。在训练时,会构造很多以连续的 n 个样本作为输入,紧接着第 n+1 个样本作为标签的自监督样例,详见下文 1.4.1 节Seq2Seq 任务
(many-to-many),通常使用 Encoder-Decoder 结构。这时 Encoder 就是类似 1 中的 many-to-one 序列特征提取器,Decoder 就是类似 2 中的 one-to-many 序列生成器,Encoder 提取的特征作为 Decoder 的初始 seed,二者结合就能做 many-to-many 了。训练时通常用 teacher-forcing 形式,详见下文 1.4.2 节LSTM 是对 RNN 模型的改进,可以有效缓解 RNN 的梯度消失(见下文 1.3.2 节)问题,其结构如下所示
这个结构看上去很复杂,不过我们可以从先从宏观角度来理解:相比 RNN,LSTM 针对序列数据性质增加了对数据处理过程的限制,从而减少了模型的弹性/容量,使它能在使得相同样本量下更好地提取序列信息,这和 CNN 比 MLP 能更好地处理图像数据是一个道理
现在来仔细看一下这个结构,LSTM 的设计灵感来自于计算机的逻辑门,它的输出和 RNN 一样仍然是隐状态 H\mathbf{H}H,只是生成过程更复杂。相比 RNN,LSTM 引入了记忆单元cell
C\mathbf{C}C,它和隐状态 H\mathbf{H}H 具有相同的形状,用于记录附加的信息并产生输出 H\mathbf{H}H,其他的所有门都是为了控制这个记忆单元服务的。具体而言,设隐藏层维度为 hhh,batch size 为 nnn,样本维度为 ddd,则批量输入为 Xt∈Rn×d\mathbf{X}_t \in \mathbb{R}^{n \times d}Xt∈Rn×d,前一时刻隐状态为 Ht−1∈Rn×h\mathbf{H}_{t-1} \in \mathbb{R}^{n \times h}Ht−1∈Rn×h
列一下公式,有
C~t=tanh(XtWxc+Ht−1Whc+bc),It=σ(XtWxi+Ht−1Whi+bi),Ft=σ(XtWxf+Ht−1Whf+bf),Ot=σ(XtWxo+Ht−1Who+bo),\begin{aligned} \tilde{\mathbf{C}}_t &= \text{tanh}(\mathbf{X}_t \mathbf{W}_{xc} + \mathbf{H}_{t-1} \mathbf{W}_{hc} + \mathbf{b}_c), \\ \mathbf{I}_t &= \sigma(\mathbf{X}_t \mathbf{W}_{xi} + \mathbf{H}_{t-1} \mathbf{W}_{hi} + \mathbf{b}_i),\\ \mathbf{F}_t &= \sigma(\mathbf{X}_t \mathbf{W}_{xf} + \mathbf{H}_{t-1} \mathbf{W}_{hf} + \mathbf{b}_f),\\ \mathbf{O}_t &= \sigma(\mathbf{X}_t \mathbf{W}_{xo} + \mathbf{H}_{t-1} \mathbf{W}_{ho} + \mathbf{b}_o), \end{aligned} C~tItFtOt=tanh(XtWxc+Ht−1Whc+bc),=σ(XtWxi+Ht−1Whi+bi),=σ(XtWxf+Ht−1Whf+bf),=σ(XtWxo+Ht−1Who+bo), 这里的 W,b\pmb{W},\pmb{b}WWW,bbb 都是要学习的参数,相比 RNN 多了三组。另外,所有信息选取都是通过对应位置乘以 (0,1) 间小数的方式进行的,如图可见有 “遗忘门 F\mathbf{F}F 去除部分老记忆 Ct−1\mathbf{C}_{t-1}Ct−1”、“输入门 I\mathbf{I}I 合并部分候选记忆 C~\mathbf{\tilde{C}}C~” 和 “输出门 Ot\mathbf{O}_tOt 保留部分新记忆 Ct\mathbf{C}_tCt” 三处,公式表示为
Ct=Ft⊙Ct−1+It⊙C~tHt=Ot⊙tanh(Ct).\begin{aligned} \mathbf{C}_t &= \mathbf{F}_t \odot \mathbf{C}_{t-1} + \mathbf{I}_t \odot \tilde{\mathbf{C}}_t\\ \mathbf{H}_t &= \mathbf{O}_t \odot \tanh(\mathbf{C}_t). \end{aligned} CtHt=Ft⊙Ct−1+It⊙C~t=Ot⊙tanh(Ct).
直观地看这个结构,其实就是每次 batch 输入先产生和 RNN 中 H\mathbf{H}H 完全一样的 C~t\mathbf{\tilde{C}}_tC~t 作为 “当前步记忆”,然后用遗忘和输入门控制它和 “历史序列记忆” Ct−1\mathbf{C}_{t-1}Ct−1 混合得到 “最新序列记忆” Ct\mathbf{C}_{t}Ct,最后使用输出门将 Ct\mathbf{C}_{t}Ct 衰减后输出为 Ht\mathbf{H}_tHt
最后再回到宏观来看,LTSM 通过学习四组系数 & 偏置参数,要求模型按照上述逻辑构造记忆并从中提取输出,相比 RNN 直接一个 FC 加激活函数,它通过模型结构引导其做出更符合序列性质的行为,从而缓解了 RNN 的梯度消失和梯度爆炸问题,大幅提升了 RNN 的性能
Stacked RNN/Stacked LSTM
,其容量更大,能表示的映射关系也更加复杂,如下图所示无论 RNN 还是 LSTM,模型都只能利用隐藏状态间接地获取之前序列的信息,由于隐藏状态的维度一定远远小于之前的变长序列所有样本的连接维度,这种做法无可避免地会损失一些信息,体现在微观层面上就是两个经典问题
梯度消失
:参数更新梯度被近期样本主导(和 MLP 里的梯度消失不太一样)梯度爆炸
:参数更新梯度梯度趋近 ∞\infin∞在 RNN 中这两个问题尤其严重(可以参考 RNN梯度消失和爆炸的原因),以梯度消失为例,近期的序列样本主导了优化方向,这会导致模型快速忘记相隔时间比较久的早期序列输入,比如下例
这个 RNN 的任务是根据先前序列预测下一个单词,在 x1x_1x1 处输入了 “China”,但由于梯度消失这个信息几乎没法被记住,模型难以在相隔较远的 ht+1h_{t+1}ht+1 处给出 “Chinese” 的预测
这里模型大概能学到应该输出一个语言,但是具体是什么语言会被近期序列的倾向所主导
LSTM 通过引入 “记忆单元” 缓解了这些问题,但依然无法完全解决。一个简单粗暴的优化方案是直接同时从两个方向训练 RNN 或 LSTM,这样得到的 Bidirectional RNN/Bidirectional LSTM
结构如下
这样一来,一个方向的早期样本就成了另一个方向的近期样本,可以缓解 RNN/LSTM 的遗忘问题。另外这里输出的 yyy 是两个模型输出的向量拼接,它也可以像上面那样进行 stack 从而扩展容量
处理序列数据时往往要做一步 embedding,使得序列样本变得可以处理。比如常见的文本模型,你没法直接向网络输入一个单词,这时有几个做法
一个问题是,FC 嵌入层往往很大,有时甚至比模型参数还多不少,如果直接把它接在 RNN 或 LSTM 的输入之前一起训练,很容易导致嵌入层过拟合,影响模型性能。这时我们可以先用一个别的任务专门训练这个 FC 嵌入层,这就是所谓的 预训练pre-train
过程,几个注意点是
完成预训练后,直接用预训练模型得到的 FC 嵌入层参数初始化目标模型的 FC 嵌入层,然后在目标任务训练过程中有时将其固定住只训练模型的其他部分,有时也带着这个 FC 层一起训练,这称为 微调fine-turn
过程
所有涉及预处理的部分都可以用类似思路进行预训练,可以有效提高模型性能
many-to-one
形式的,但是通过巧妙的应用,其能处理的问题涵盖了 one-to-many
,many-to-one
和 one-to-one
所有情况,下面简单举两个例子进行说明many-to-one
的形式,另外由于输入长度必须固定,某种程度上也能看做 one-to-one
形式 换个角度看,此模型也可理解为先用序列模型提取前驱序列的特征(RNN/LSTM 视角下就是隐变量),再用这个特征做多分类来选择生成下一个 token,直接把这两件事放在一起进行 end-to-end 的训练
one-to-many
形式to-one
任务的模型完成 to-many
任务。监督学习模型基本都是 to-one 的,所以借助这个结构,我们甚至可以用 MLP 等八竿子打不着的模型来做文本生成,但由于这些网络缺乏对于序列任务的归纳偏置,提取序列特征的能力差,效果通常很差。可以参考 序列模型(1)—— 难处理的序列数据机器翻译的输入和输出都是变长度序列,是典型的 many-to-many
问题,机器学习中也称这种任务为 “Seq2Seq” 任务。为了让 RNN/LSTM 有能力处理这类问题,模型要设计成特殊的 “编码器-解码器架构”。正如其名,这种架构含有两个组件
编码器Encoder
:接受一个长度可变的序列作为输入, 并将其转换为具有固定形状的编码状态,即从输入序列中提取一个特征向量。原始的 RNN/LSTM 可以完成这种 many-to-one
的任务,但特征向量中不可避免地会损失长跨度样本的信息,这也是后面出现注意力机制的重要原因解码器Decoder
:将固定形状的编码状态映射到长度可变的序列。这恰好是上面 1.4.1 节那种 one-to-many
任务,所以 Decoder 也可以看做使用 Encoder 编码特征作为初始种子特征向量的自动文本生成任务。为了能自动控制输出长度,通常要增加 “起始” 和 “终止” 两个特殊 token机器翻译模型设计得很巧妙,通过连接 many-to-one
和 one-to-many
的两个组件真正实现了 many-to-many
任务
训练阶段,Encoder 和 Decoder 连在一起作为一个 many-to-many 模型训练。以文本翻译任务为例,对于数据库中一个形如 (样本序列,标签序列)(样本序列, 标签序列)(样本序列,标签序列) 的样本,如下操作
每次计算的损失梯度会从 Decoder 一直回传到 Encoder,同时更新两个组件的参数
测试阶段,Encoder 输入待翻译的句子,Decoder 输入 “起始” token,让它如 1.4.1 节一样自回归地生成序列,直到输出 “结束” token 未知
本节介绍的 “编码器-解码器结构” 是序列学习领域的一个重要模型,后面的 transformer 其实也是基于这个架构设计的
正如上文 1.3.2 节的讨论,RNN 和 LSTM 这种传统序列模型对长跨度序列样本的特征提取能力有限,很容易出现梯度消失等问题,这个问题即使用双向模型也无法完全解决。Attention 是针对这个遗忘问题设计的,以机器翻译任务为例
可见当句子长度超过 20 词时,传统序列模型翻译的 BLEU 评分会迅速下降,增加 attention 机制后问题解决
Attention 机制最早提出于 ICLR 2015 的文章 Neural machine translation by jointly learning to align and translate,这篇文章基于 Encoder-Decoder 结构做机器翻译任务,它的思想很简单,Decoder 输出每个词时不要再只依赖一个隐状态特征向量 s\pmb{s}sss,而是去看完整地看一遍要翻译的原句,从原句的所有样本中提取信息汇聚成上下文向量 c\pmb{c}ccc 作为 Decoder 的附加输入,而所谓 attention,本质就是汇聚上下文向量时的权重
Note:c\pmb{c}ccc 可以作为附加信息和 s\pmb{s}sss 一起作为解码器输入;也可以完全不用 s\pmb{s}sss 只依靠 c\pmb{c}ccc 和输入 token x′\pmb{x}'xxx′ 进行解码,因为 c\pmb{c}ccc 中已经包含了从来自原始序列所有样本的特征
将这样的 attention 机制加入 RNN/LSTM Encoder-Decoder 结构,如下
相比原始 RNN/LSTM Encoder-Decoder,差别仅在于多了一个上下文向量 ci\pmb{c}_iccci 作为解码的附加信息(下图显示了注意力汇聚过程和附加输入结构,忽略了注意力计算过程)
下面不严谨地歇写一下上下文向量的计算过程
注意力评分函数
aaa,计算输入 Decoder 的隐状态 sj\pmb{s}_jsssj 和 Encoder 的所有样本输出 h1,...,hm\pmb{h}_1,...,\pmb{h}_mhhh1,...,hhhm 的注意力得分
α1,...,αm\alpha_1,...,\alpha_mα1,...,αm,这个得分体现的是 sj\pmb{s}_jsssj 和 Encoder 各个输出 hi\pmb{h}_ihhhi 的相关性上下文向量
,直接将其作为解码 s\pmb{s}sss 时输入 RNN Decoder 的附加信息即可上述过程总结成一句话就是:利用 s\pmb{s}sss 和 h\pmb{h}hhh 的相关性对 h\pmb{h}hhh 加权求和得到 c\pmb{c}ccc。这里有一个问题,直接用原始的 s\pmb{s}sss 和 h\pmb{h}hhh 向量往往不够灵活,比如二者维度可能不同,或者我们想在更高维度计算相关性,又或者想在更高维度汇聚 h\pmb{h}hhh 中的信息,于是我们可以加一步抽象化
查询向量query
q\pmb{q}qqq键向量key
k\pmb{k}kkk值向量key
v\pmb{v}vvv这里的 Q,K,V\pmb{Q},\pmb{K},\pmb{V}QQQ,KKK,VVV 矩阵都是自己学出来的,在汇聚上下文向量 c\pmb{c}ccc 时,通过 q,k\pmb{q},\pmb{k}qqq,kkk 计算相关性,对 v\pmb{v}vvv 加权求和进行汇聚,如下图所示
注意力评分函数可以有多种设计
加性注意力additive attention
,它允许 q\pmb{q}qqq 和 k\pmb{k}kkk 是不同长度的向量(事实上原始论文中的 q\pmb{q}qqq 和 k\pmb{k}kkk 就是 s\pmb{s}sss 和 h\pmb{h}hhh 本身),给定 q∈Rq,k∈Rk\pmb{q}\in\mathbb{R}^q, \pmb{k}\in\mathbb{R}^kqqq∈Rq,kkk∈Rk,注意力得分为缩放点积注意力scaled dot-product attention
,它的计算效率更高,但要求 q\pmb{q}qqq 和 k\pmb{k}kkk 是相同长度的向量。假设查询和键的所有元素都是独立的 ddd 维随机变量, 且都是均值0方差1,那么两个向量的点积的均值为0方差为ddd,将点积除以 d\sqrt{d}d 使其方差为1,注意力得分为引入 attention 机制,要求模型显式地考虑输出 token 和输入句子各个 token 间的相关性,可以解决传统 Seq2Seq 模型的遗忘问题。通过可视化训练后的 attention 向量,发现机器确实能够学到输入句子和输出句子各个 token 间合理的相关性
但这种能力是有代价的,设输入序列长 mmm,输出序列长 ttt,对传统模型引入 attention 机制会使计算复杂复从 Q(m+t)Q(m+t)Q(m+t) 大幅上升到 Q(m×t)Q(m\times t)Q(m×t)
值得注意的是,这种和传统序列模型结合的 attention 在 transformer 语境中被称为 cross attention,用来强调 attention 计算发生在 Decoder 和 Encoder 之间
上一篇:【高度预估】基于matlab卡尔曼滤波和粒子滤波无人机离地高度估计【含Matlab源码 2255期】
下一篇:【2022世界杯开源项目实战】使用docker部署world-cup-2022-cli-dashboard数据看板工具