NLP领域中,特征提取可谓是经历了显著的“变迁”与发展。回首过往,RNN曾以其独特的序列建模能力一度引领潮流,如今正如同明日黄花,逐步淡出历史舞台。紧接着,LSTM以解决长时依赖问题的独特设计展现出强大的生命力,虽已非最前沿,却仍老骥伏枥,若能进一步优化,其潜力不可小觑。
而今,Transformer架构如日中天,凭借自注意力机制彻底革新了特征提取的方法,已在NLP诸多任务中发挥着中流砥柱的作用。
本文笔者将深入浅出剖析RNN、LSTM以及Transformer的核心结构原理,一起见证算法是怎样做到一浪更比一浪强的。
RNN
为什么会出现RNN?
要搞明白这个问题,我们是不是可以先回想一下,在RNN之前的模型还有什么可改进点?
没错,从这里着手,问题就将会迎刃而解了。细想BP、CNN(卷积神经网络),可以当做能够拟合任意函数的黑盒子,只要训练数据足够,给定特定的x,就能得到期望的y。结构图如下:
但是不难发现,它们都只能单独的处理一个个的输入,前一个输入和后一个输入是完全没有关系的。而我们实际应用中肯定有需要处理序列信息的任务吧,即就是前面的输入和后面的输入是有关系的。
例如,当我们理解某句话意思时,孤立的理解每个词肯定是不够的,而是需要处理这些词连接起来的整个序列,“词本无意,意由境生”,也正是此意。还有,处理视频的时候,当前也不能只单独的分析每一帧,而是要分析这些帧连接起来的整个序列。
为了解决一些这样类似的问题,能够更好的处理序列的信息,RNN就诞生了。
RNN解释
我们通常所说的RNN实际上有两种,一种是Recurrent Neural Networks,即循环神经网络,一种是Recursive Neural Networks,即递归神经网络。
循环神经网络,是一种时间上进行线性递归的神经网络,用于处理序列数据。
相比于传统的神经网络,它可以处理序列变化的数据。比如某个词汇的含义根据上下文内容不同而有所不同,RNN则可以很好地解决这类问题。
递归神经网络(Recursive Neural Networks)被视为循环神经网络的推广,它是一种在结构上进行递归的神经网络,常用于NLP中的序列学习,它的输入数据本质不一定是时序的,但结构却往往更加复杂。
我们这里只说循环神经网络RNN。
首先来看一个简单的RNN,它由输入层、一个隐藏层和一个输出层组成:
如果将W的那个带箭头的圈去掉,它就变成了最普通的全连接神经网络。
- x是一个向量,它表示输入层的值。
- s是一个向量,它表示隐藏层的值(这里隐藏层面画了一个节点,你也可以想象这一层其实是多个节点,节点数与向量s的维度相同)。
- U是输入层到隐藏层的权重矩阵。
- o也是一个向量,它表示输出层的值。
- V是隐藏层到输出层的权重矩阵。
那么,现在我们来看看W是什么。循环神经网络的隐藏层的值s不仅仅取决于当前这次的输入x,还取决于上一次隐藏层的值s。而权重矩阵W就是隐藏层上一次的值作为这一次的输入的权重。
具体如下图所示。可以清楚的看到,上一时刻的隐藏层是如何影响当前时刻的隐藏层的。
当然我们也可以将上面的图按时间线展开,循环神经网络也可以画成下面的样子:
现在就可以清晰看出,该网络在 t t t时刻接收输入 x t x_t xt,隐藏层的值 s t s_t st由上一时刻的 s t − 1 s_{t-1} st−1和 x t x_t xt的权重矩阵W决定。
公式表示如下:
O t = g ( V ∗ S t ) O_t = g(V*S_t) Ot=g(V∗St)
S t = f ( U ∗ X t + W ∗ S t − 1 ) S_t = f(U*X_t+W*S_{t-1}) St=f(U∗Xt+W∗St−1)
f即为RNN的激活函数,通常是tanh。用代码表示RNN如下:
class RNN:
def step(self, x):
self.h = np.tanh(np.dot(self.W_hh, self.h) + np.dot(self.W_xh, x)) #更新隐藏层
y = np.dot(self.W_hy, self.h) #获得输出
return y
RNN结构
根据输入输出长度的不同,可以将RNN结构分为如下图所示五类。
one-to-one
最基本的单层网络,本质也即是全连接神经网络。输入是x,经过变换Wx+b和激活函数f得到输出y。
one-to-many
输入是一个单独的值,输出是一个序列。此时,有两种建模方式:
- 只在其中的某一个序列进行计算,以序列第一个进行输入计算为例:
- 把输入信息X作为每个阶段的输入:
通常用于图像生成文字(输入的X就是图像的特征,输出的y序列就是一段句子,例如看图说话等)、图像生成语音或音乐等领域。
many-to-one
输入是一个序列,输出是一个单独的值。此时,通常只在最后一个h上进行输出变换就可以了。
这种结构通常用来处理序列分类问题。如输入一段文字判别它所属的类别,输入一个句子判断其情感倾向,输入一段视频并判断它的类别等等。
many-to-many
最经典的RNN结构,输入输出都是等长的序列数据。
假设输入为X=(x1, x2, x3, x4),每个x是一个单词的词向量。
为了建模序列问题,RNN引入了隐状态h(hidden state)的概念,h可以对序列形的数据提取特征,接着再转换为输出。先从h1的计算开始看:
h2的计算和h1类似。要注意的是,在计算时,每一步使用的参数U、W、b都是一样的,也就是说每个步骤的参数都是共享的,这是RNN的重要特点。
依次计算剩下来的(使用相同的参数U,W,b):
这里为了方便起见,只画出序列长度为4的情况,实际上,这个计算过程可以无限地持续下去。得到输出值的方法就是直接通过h进行计算:
正如之前所说,一个箭头就表示对对应的向量做一次类似于f(Wx+b)的变换,这里的这个箭头就表示对h1进行一次变换,得到输出y1。
剩下的输出类似进行(使用和y1同样的参数V和c):
这也就是最经典的RNN结构,它的输入是x1, x2, …xn,输出为y1, y2, …yn,也就是说,**输入和输出序列必须要是等长的。**由于这个限制的存在,经典RNN的适用范围比较小,但也有一些问题适合用经典的RNN结构建模,如:
计算视频中每一帧的分类标签。因为要对每一帧进行计算,因此输入和输出序列等长。
many-to-many
还有一种many-to-one结构,输入、输出为不等长的序列。
这种结构是Encoder-Decoder,也叫Seq2Seq,是RNN的一个重要变种。原始的many-to-many的RNN要求序列等长,然而我们遇到的大部分问题序列都是不等长的,如机器翻译中,源语言和目标语言的句子往往并没有相同的长度。
为此,Encoder-Decoder结构先将输入数据编码成一个上下文语义向量c。 其中,语义向量c可以有多种表达式,具体如下:
① c = h 4 ①c=h_4 ①c=h4
② c = q ( h 4 ) ②c=q(h_4) ②c=q(h4)
③ c = q ( h 1 , h 2 , h 3 , h 4 ) ③c=q(h_1,h_2,h_3,h_4) ③c=q(h1,h2,h3,h4)
其中,最简单的方法就是把Encoder的最后一个隐状态赋值给c(①),另外还可以对最后的隐状态做一个变换得到c(②),也可以对所有的隐状态做变换(③)。
拿到c之后,再用另一个RNN网络对其进行解码,这部分网络称为Decoder。Decoder的RNN可以与Encoder的一样,也可以不一样。具体做法就是将c当做之前的初始状态h0输入到Decoder中:
也可以将c当做每一步的输入。
由于Encoder-Decoder结构不限制输入和输出的序列长度,因此应用的范围非常广泛,如机器翻译、文本摘要、阅读理解、语音识别等。
RNN缺陷
同普通卷积神经网络类似的是,RNN的优化也使用的是反向传播,不过是带时序的版本,即BPTT(Backpropagation Through Time),它与BP的原理完全一样,只不过计算过程与时间有关。
与普通的反向传播算法一样,它重复地使用链式法则,区别在于损失函数不仅依赖于当前时刻的输出层,也依赖于下一时刻。所以参数W在更新梯度时,必须考虑当前时刻的梯度和下一时刻的梯度,传播示意图如下:
而计算梯度时,t时刻的导数会传播到t-1,t-2,… ,1时刻,这样就有了连乘的系数。
连乘一直带来了两个问题:梯度爆炸和消失。 而且,在前向过程中,开始时刻的输入对后面时刻的影响越来越小,这就是长距离依赖问题。这样依赖,RNN就失去了“记忆”能力。
为了缓解这些问题,LSTM便应运而生。
LSTM
为什么会出现LSTM?
这个在上一章节末刚刚谈到的,因为RNN会导致"梯度消失"和"梯度爆炸"问题,学习能力有限,在实际任务中的效果很难达到预期。为了增强RNN的学习能力,缓解网络的梯度消失等问题,LSTM(Long short-term memory, LSTM)神经网络便横空出世了。它可以对有价值的信息进行长期记忆,不仅减小循环神经网络的学习难度,并且由于其增加了长时记忆,在一定程度上也缓解了梯度消失的问题。(注:梯度爆炸的问题用一般的梯度裁剪方法就可以解决)。
LSTM VS RNN
RNN都具有一种重复神经网络模块的链式的形式,在标准的RNN中,这个重复的模块只有一个非常简单的结构,例如一个tanh层。
而在LSTM中的重复模块中,不同于RNN,这里有四个神经网络层,以一种非常特殊的方式进行交互:
LSTM核心解释
LSTM通过“门”结构来删除或者增加信息到细胞状态cell state。这个cell state承载着之前所有状态的信息,每到新的时刻,就有相应的操作来决定舍弃什么旧信息以及添加什么新信息。这个状态与隐藏层状态h不同,在更新过程中,它的更新是缓慢的,而隐藏层状态h的更新是迅速的。
LSTM的网络结构图如下,输入包括 h t − 1 h_{t-1} ht−1, x t x_t xt,输出 h t h_t ht,状态为 c t − 1 c_{t-1} ct−1, c t c_t ct。
遗忘门与遗忘阶段
遗忘门决定从上一时刻的状态 c t − 1 c_{t-1} ct−1中丢弃哪些信息。它通过将上一状态的输出 h t − 1 h_{t-1} ht−1和当前状态输入信息 x t x_t xt作为输入,经过一个Sigmoid激活函数,生成一个在[0,1]范围内的遗忘因子。当遗忘门输出为0时,表示完全丢弃对应部分的细胞状态;输出为1时,则表示完全保留。这个阶段完成了对上一个节点cell state进行选择性忘记。
输入门与选择记忆阶段
输入门决定哪些新的信息应当加入到细胞状态中。同样地,将上一状态的输出 h t − 1 h_{t-1} ht−1和当前状态输入信息 x t x_t xt作为输入,通过sigmoid函数生成一个在[0,1]之间的保留因子it,表示信息应保留的程度。同时,还有一个“候选细胞状态”通过上一状态的输出 h t − 1 h_{t-1} ht−1、当前状态输入信息 x t x_t xt以及tanh激活函数生成,这个候选状态包含了新的潜在信息。
新的细胞状态 C t C_t Ct是上一时刻细胞状态经过遗忘门过滤后的信息 f t ∗ C t − 1 f_t*C{t-1} ft∗Ct−1与当前时刻经过输入门过滤后的信息 i t ∗ C t i_t*C_t it∗Ct的加权和,这样就实现了对历史信息的选择性保留和新信息的选择性添加。它将用于产生下一状态的隐藏层 h t h_t ht,也就是当前单元的输出。
输出门与输出阶段
输出门决定如何将当前细胞状态
C
t
C_t
Ct转换为当前时刻的隐藏状态
h
t
h_t
ht,即要将何种信息输出给下一个时间步或其他层使用。它同样通过Sigmoid函数处理
h
t
−
1
h_{t-1}
ht−1和
x
t
x_t
xt得到一个在[0,1]范围内的输出因子
O
t
O_t
Ot。
然后,细胞状态
C
t
C_t
Ct先经过tanh函数压缩到[-1,1]范围内并提取有用信息,再与输出门的输出结果
O
t
O_t
Ot相乘,最终生成LSTM block当前时刻的隐藏状态
h
t
h_t
ht。
LSTM缺陷
LSTM的门结构能够有效减缓长序列问题中可能出现的梯度消失或爆炸,虽然并不能杜绝这种现象,但在更长的序列问题上表现优于传统RNN。
但是,LSTM内部结构相对较复杂, 因此训练效率在同等算力下较传统RNN低很多。
Transformer
为什么会出现Transformer?
对RNN和LSTM进行一番分析之后,不难看出,二者还共同存在一个问题:
效率:均需要逐个词处理,后一个词要等到前一个词的隐藏状态输出以后才能开始处理,而不能完全并行地处理序列中的词。
终于,2017年Google在神作《Attention is all you need》提出了Transformer架构,通过自注意力机制彻底摆脱了RNN的时间顺序约束,实现了完全并行化的序列处理。
Transfromer是一个N进N出的结构,也就是说每个Transformer单元相当于一层的RNN层,接收一整个句子所有词作为输入,然后为句子中的每个词都做出一个输出。但是与RNN不同的是,Transformer能够同时处理句子中的所有词,并且任意两个词之间的操作距离都是1,这么一来就很好地解决了上面提到的RNN的效率问题和距离问题。
Transformer精髓
Transformer的关键创新在于其自注意力机制(Self-Attention Mechanism),这是其架构的核心基石。它允许模型在生成当前词的表示时,不仅考虑当前位置的信息,还能充分考虑整个输入序列所有词的上下文信息,并对其进行加权融合。这样使得,模型可以更好地捕捉词语之间的依赖关系和长距离依赖。
注意力机制的计算效率通过使用矩阵运算和并行计算得到提高。
除此之外,Transformer还结合了残差连接(Residual Connections)和层归一化(Layer Normalization)技术,这两种技术的引入极大提升了模型训练的稳定性和收敛速度。
以机器翻译任务为例,下面这句话中,通过理解上下文可以看出,it指代的是The animal,而Tranformer就能很好地帮我们得出这个结论。
Transformer架构
宏观层面
首先将这个模型可以看成是一个黑箱操作。在机器翻译中,就是输入一种语言,经Transformer输出另一种语言。
拆开这个黑箱,可以看到它的本质就是一个Encoder-Decoder结构,由编码组件、解码组件和它们之间的连接组成。
Encoders由6个Encoder block组成,同样Decoders是6个Decoder block组成,每个block结构相同,但不共享参数。与所有的生成模型相同的是,编码器的输出会作为解码器的输入,如图所示:
微观层面
Transformer内部结构如下图所示,由编码器(Encoder)和解码器(Decoder)两大部分组成。
左边的Encoder最后演化成了如今鼎鼎大名的Bert,右边的Decoder则变成了无人不知的GPT模型。
再更细化可分为输入、输出、编码器、解码器四部分。
输入
输入部分包含:
- 源文本嵌入层及其位置编码器
- 目标文本嵌入层及其位置编码器
无论源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转换为向量表示。
在Embedding层后加入位置编码器,则是将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中,以弥补位置信息的缺失。
输出
输出部分包含:
- 线性层:通过对上一步的线性变化得到指定维度的输出,也就是转换维度的作用。
- softmax层:使最后一维的向量中的数字缩放到0~1的概率值域内, 并满足他们的和为1。
编码器
- 由N个编码器层堆叠而成。
- 每个编码器层由两个子层连接结构组成。
- 第一个子层连接结构包括一个多头自注意力子层和归一化层以及一个残差连接。
- 第二个子层连接结构包括一个前馈全连接子层和归一化层以及一个残差连接。
解码器
- 由N个解码器层堆叠而成。
- 每个解码器层由三个子层连接结构组成。
- 第一个子层连接结构包括一个多头自注意力子层和归一化层以及一个残差连接。
- 第二个子层连接结构包括一个多头注意力子层和归一化层以及一个残差连接。
- 第三个子层连接结构包括一个前馈全连接子层和归一化层以及一个残差连接。
Transformer优缺点
优点
- 并行处理:Transformer能够高效进行并行计算,提高了训练速度。
- 全局信息捕获:可以通过自注意力机制获取全局上下文信息,这对于理解复杂的语言结构至关重要。
- 长距离依赖:通过自注意力机制,Transformer能够直接捕获任意两个输入位置之间的依赖关系,有效地解决了长距离依赖问题。
缺点
- 计算资源消耗:由于自注意力机制涉及到大规模的矩阵运算,尤其是全尺寸的注意力矩阵,这可能导致Transformer在计算和内存资源上的需求较高。
可以说,RNN、LSTM和Transformer分别代表了不同阶段NLP特征提取技术的演进历程,它们各自的特点与优势推动了NLP乃至整个人工智能领域向前发展,不断刷新着我们对自然语言理解和生成能力的认知边界。