【RNN及其变体】

【RNN及其变体】

目录

一、什么是RNN模型

二、RNN模型的分类

三、传统RNN模型

3.1 传统RNN的内部结构

3.2 Pytorch构建RNN模型

3.3 传统RNN优缺点

四、LSTM模型

4.1 什么是LSTM

4.2 LSTM的优缺点

4.3 LSTM的内部结构图

4.4 LSTM工作流程概况

4.5 Bi-LSTM介绍

4.6 Bi-LSTM的优缺点概述

4.7 Pytorch构建LSTM模型

五、GRU模型

5.1 什么是GRU

5.2 GRU的优缺点

5.3 GRU的内部结构图

5.4 GRU工作流程概况

5.5 Bi-GRU介绍

5.6 Bi-GRU的优缺点

5.7 Pytorch构建GRU模型

六、注意力机制

6.1 注意力机制由来

6.2 什么是注意力机制

6.3 注意力机制分类

6.3.1 soft attention(软注意力机制)

6.3.2 hard attention(硬注意力机制)

6.3.3 self attention(自注意力机制)

6.4 注意力机制规则

6.5 什么是神经网络注意力机制

6.6 seq2seq架构中注意力机制作用

一、什么是RNN模型

概念

RNN(Recurrent Neural Network),中文称作循环神经网络,是一种专门用于处理序列数据的神经网络架构。一般以序列数据为输入,通过网络内部的结构设计有效捕捉序列之间的关系特征,一般也是以序列形式进行输出。它的特点是能够捕捉序列数据中的时间依赖关系,广泛应用于自然语言处理(NLP)、时间序列预测、语音识别等领域。

核心概念

序列数据 (Sequential Data):指的是数据项之间存在顺序关系的数据,例如:

文本:句子中的单词顺序

语音:音频信号的时间序列

股票价格:随时间变化的股价

视频:连续的帧序列

循环连接 (Recurrent Connection):RNN的核心在于其循环连接,它允许信息在网络内部循环流动。这使得网络可以保留过去的信息,并将其用于处理当前的输入。

隐藏状态 (Hidden State):RNN在每个时间步都会维护一个隐藏状态,这个隐藏状态包含了过去的信息。它可以被认为是网络的"记忆"。

时间步 (Time Step):指的是序列数据中的每个元素,例如文本中的一个单词、语音中的一个音频帧。

工作原理

输入:RNN接收一个序列数据作为输入,每次输入序列中的一个元素 (时间步)。

隐藏状态更新:对于每个时间步,RNN根据当前的输入和上一个时间步的隐藏状态计算出新的隐藏状态。这个计算通常使用激活函数(如tanh或ReLU)。

输出:RNN根据当前的隐藏状态计算出当前的输出。

循环:上述过程会在序列的每个时间步重复进行,直到序列结束。

每个时间步的计算公式如下

隐藏状态更新:

h_t=σ(W_hh_{t−1}+W_xx_t+b_h)

h_t​:当前时间步的隐藏状态。

h_{t−1}​:上一个时间步的隐藏状态。

x_t​:当前时间步的输入。

W_h​,W_x​:权重矩阵。

b_h:偏置项。

σ:激活函数(如 tanh 或 ReLU)。

输出计算:

y_t=W_yh_t+b_y

y_t​:当前时间步的输出。

W_y:输出层的权重矩阵。

b_y​:输出层的偏置项。

RNN模型作用:

RNN的主要作用是处理序列数据,并捕捉数据中的时间依赖关系。

因为RNN结构能够很好利用序列之间的关系,因此针对自然界具有连续性的输入序列,如人类的语言、语音等进行很好的处理。

广泛应用于NLP领域的各项任务,如文本分类、情感分析、意图识别、机器翻译等。

二、RNN模型的分类

从两个角度对RNN模型进行分类,第一个角度是输入和输出的结构,第二个角度是RNN的内部构造。

按照输入和输出的结构进行分类:

N vs N-RNN:输入序列和输出序列的长度相同,并且通常在时间步上是一一对应的。它是RNN最基础的结构形式,最大的特点就是:输入和输出序列是等长的。由于这个限制的存在, 使其适用范围比较小, 可用于生成等长度的合辙诗句。

N vs 1-RNN:输入一个长度为 N 的序列,输出一个单一的值或向量。有时候我们要处理的问题输入是一个序列,而要求输出是一个单独的值而不是序列。我们只要在最后一个隐藏层输出h上进行线性变换就可以了,大部分情况下,为了更好的明确结果,还要使用sigmoid或者softmax进行处理。

1 vs N-RNN:输入一个单一的值或向量,输出一个长度为 N 的序列。最常采用的一种方式就是使该输入作用于每次的输出之上,这种结构可用于将图片生成文字任务等。

N vs M-RNN:输入一个长度为 N 的序列,输出一个长度为 M 的序列,其中 N 和 M 可以不相同。

这是一种不限输入输出长度的RNN结构,它由编码器和解码器两部分组成,两者的内部结构都是某类RNN,它也被称为seq2seq架构。输入数据首先通过编码器, 最终输出一个隐含变量c, 之后最常用的做法是使用这个隐含变量c作用在解码器进行解码的每一步上,以保证输入信息被有效利用。

(seq2seq架构最早被提出应用于机器翻译,因为其输入输出不受限制,如今也是应用最广的RNN模型结构。在机器翻译、阅读理解、文本摘要等众多领域都进行了非常多的应用实践。)

按照RNN的内部构造进行分类:

传统RNN

LSTM

Bi-LSTM

GRU

Bi-GRU

三、传统RNN模型

3.1 传统RNN的内部结构

内部结构分析:

我们把目光集中在中间的方块部分,它的输入有两部分:分别是h{t-1}以及x_t,代表上一时间步的隐藏层输出以及此时间步的输入。它们进入RNN结构体后,会"融合"到一起,这种融合我们根据结构解释可知是将二者进行拼接,形成新的张量[x_t, h{t-1}]。之后这个新的张量将通过一个全连接层(线性层),该层使用tanh作为激活函数,最终得到该时间步的输出h_t,它将作为下一个时间步的输入和x_{t+1}一起进入结构体。以此类推...

3.2 Pytorch构建RNN模型

示例1

import torch

import torch.nn as nn

def dm_rnn_for_base():

'''

第一个参数:input_size(输入张量x的维度)

第二个参数:hidden_size(隐藏层的维度,隐藏层的神经元个数)

第三个参数:num_layer(隐藏层的数量)

'''

rnn = nn.RNN(input_size=5, hidden_size=6, num_layers=1) #A

'''

第一个参数:sequence_length(输入序列的长度),每个句子1个词

第二个参数:batch_size(批次的样本数量),3个句子

第三个参数:input_size(输入张量的维度),每个词用5维张量表示

'''

input = torch.randn(1, 3, 5) #B

'''

第一个参数:num_layer * num_directions(层数*网络方向)

第二个参数:batch_size(批次的样本数)

第三个参数:hidden_size(隐藏层的维度,隐藏层神经元的个数)

'''

h0 = torch.randn(1, 3, 6) #C

# [1,3,5],[1,3,6] ---> [1,3,6],[1,3,6]

output, hn = rnn(input, h0)

print('output--->',output.shape, output)

print('hn--->',hn.shape, hn)

print('rnn模型--->', rnn)

# 结论: 若句子只有1个词,output输出结果等于hn

示例2

# 输入数据长度发生变化

def dm_rnn_for_sequencelen():

'''

第一个参数:input_size(输入张量x的维度)

第二个参数:hidden_size(隐藏层的维度,隐藏层的神经元个数)

第三个参数:num_layer(隐藏层的数量)

'''

rnn = nn.RNN(5, 6, 1) #A

'''

第一个参数:sequence_length(输入序列的长度),每个句子20个词

第二个参数:batch_size(批次的样本数量),3个句子

第三个参数:input_size(输入张量的维度),每个词用5维张量表示

'''

input = torch.randn(20, 3, 5) #B

'''

第一个参数:num_layer * num_directions(层数*网络方向)

第二个参数:batch_size(批次的样本数)

第三个参数:hidden_size(隐藏层的维度,隐藏层神经元的个数)

'''

h0 = torch.randn(1, 3, 6) #C

# [20,3,5],[1,3,6] --->[20,3,6],[1,3,6]

output, hn = rnn(input, h0)

print('output--->', output.shape, output)

print('hn--->', hn.shape, hn)

print('rnn模型--->', rnn)

# 结果: 若句子由多个词组成, output的最后一组词向量(每个句子的最后一个词组成)等于h0

示例3

def dm_run_for_hiddennum():

"""

第一个参数:input_size(输入张量x的维度)

第二个参数:hidden_size(隐藏层的维度, 隐藏层的神经元个数)

第三个参数:num_layer(隐藏层的数量)

"""

rnn = nn.RNN(5, 6, 2) # A 隐藏层个数从1-->2 下面程序需要修改的地方?

'''

第一个参数:sequence_length(输入序列的长度)

第二个参数:batch_size(批次的样本数量)

第三个参数:input_size(输入张量的维度)

'''

input = torch.randn(1, 3, 5) # B

'''

第一个参数:num_layer * num_directions(层数*网络方向)

第二个参数:batch_size(批次的样本数)

第三个参数:hidden_size(隐藏层的维度,隐藏层神经元的个数)

'''

h0 = torch.randn(2, 3, 6) # C

output, hn = rnn(input, h0)

print('output-->', output.shape, output)

print('hn-->', hn.shape, hn)

print('rnn模型--->', rnn) # nn模型---> RNN(5, 6, num_layers=2)

# 结论:若只有1个隐藏层,output输出结果等于hn

# 结论:如果有2个隐藏层,hn有2个,output输出结果等于最后1个隐藏层的hn

3.3 传统RNN优缺点

传统RNN的优点

由于内部结构简单,对计算资源要求低,相比之后我们要学习的RNN变体(LSTM和GRU模型)参数总量少了很多,在短序列任务上性能和效果都表现优异。

结构简单,易于理解和实现:

传统RNN的结构非常直观,只有一个隐藏层和循环连接,因此容易理解其工作原理。

由于结构简单,其代码实现也相对容易。

适用于处理序列数据:

传统RNN的核心优势在于它能够处理具有顺序关系的数据,例如文本、语音、时间序列等。

通过循环连接,RNN可以将过去的信息传递到当前时刻,从而学习序列数据中的依赖关系。

能够处理可变长度的序列:

传统RNN可以处理长度不固定的序列,而无需提前固定输入数据的长度。

这使得它在处理自然语言、语音等具有可变长度数据的任务中非常灵活。

参数共享:

传统RNN在每个时间步都共享相同的参数(权重矩阵和偏置项),从而减少了模型参数的数量。

参数共享还允许RNN学习序列数据中跨时间步的通用模式。

计算效率较高 (相对于更复杂的RNN变体):

由于结构简单,传统 NN的计算复杂度相对较低,训练速度相对较快。

在一些计算资源有限的场景下,传统RNN是一个可行的选择。

传统RNN的缺点

传统RNN在解决长序列之间的关联时,通过实践证明经典RNN表现很差。原因是在进行反向传播的时候,过长的序列导致梯度的计算异常,发生梯度消失或爆炸。

梯度消失问题:

这是传统RNN最主要的缺点。在处理长序列时,梯度在反向传播过程中会逐渐衰减,导致网络无法学习到长距离依赖关系。

梯度消失使得模型难以捕捉输入序列中较早的信息,从而限制了其在长序列任务上的性能。

梯度爆炸问题:

与梯度消失相反,在某些情况下,梯度在反向传播过程中可能会呈指数级增长,导致训练不稳定甚至发散。

梯度爆炸也限制了传统RNN在某些任务中的应用。

难以捕捉长期依赖:

由于梯度消失问题,传统RNN很难学习长距离的依赖关系,即难以记住输入序列中较早的信息,并将其用于影响后面的输出。

这意味着在处理文本、语音等长序列时,传统RNN很难理解上下文的含义。

训练不稳定:

由于梯度消失和梯度爆炸问题,传统RNN的训练过程可能会不稳定,需要小心调整超参数。

无法并行计算:

由于RNN的循环结构,每个时间步的计算都依赖于前一个时间步的隐藏状态,因此无法进行并行计算,训练速度受到限制。

梯度消失或爆炸介绍

根据反向传播算法和链式法则, 梯度的计算可以简化为以下公式:

其中sigmoid的导数值域是固定的,在[0,0.25]之间。而一旦公式中的w也小于1,那么通过这样的公式连乘后,最终的梯度就会变得非常非常小,这种现象称作梯度消失。反之,如果我们人为的增大w的值,使其大于1,那么连乘够就可能造成梯度过大,称作梯度爆炸。

梯度消失或爆炸的危害:

如果在训练过程中发生了梯度消失,权重无法被更新,最终导致训练失败;梯度爆炸所带来的梯度过大,大幅度更新网络参数,在极端情况下,结果会溢出(NaN值)。

四、LSTM模型

4.1 什么是LSTM

概念

LSTM(Long Short-Term Memory)也称为长短期记忆网络,是一种改进的循环神经网络(RNN),专门设计用于解决传统RNN的梯度消失问题和长程依赖问题。LSTM通过引入门机制和细胞状态,能够更好地捕捉长序列数据中的长期依赖关系。

核心思想

通过引入门机制(输入门、遗忘门、输出门)和细胞状态(Cell State)来控制信息的流动,从而决定哪些信息需要保留、哪些信息需要丢弃。

内部结构

遗忘门:决定了哪些信息应该被丢弃(即遗忘)。它读取当前输入和前一时刻的隐藏状态,然后输出一个0到1之间的数值,表示当前时刻的信息应当保留或丢弃的比例。

输入门:决定了哪些信息需要被存储到当前的单元状态中。通过这个门来更新单元状态的记忆。

细胞状态:可以将其视为一条贯穿整个网络的"传送带",携带长期记忆;信息通过细胞状态传递,并由各个门控机制选择性地修改。

输出门:控制从单元状态到隐藏状态的信息流出,决定当前的隐藏状态输出多少细胞状态的内容。

① 细胞状态(Cell State)

作用:细胞状态C_t是LSTM核心,用于存储长期信息

特点:

细胞状态在整个时间步中传递,只有少量的线性交互

通过门机制更新细胞状态

② 遗忘门(Forget Gate)

作用:决定哪些信息从细胞状态中丢弃

公式:

f_t=σ(W_f⋅[h_{t−1},x_t]+b_f)

f_t:遗忘门的输出(0表示完全丢弃,1表示完全保留)

W_f​,b_f​:权重矩阵和偏置项

σ​:Sigmoid​激活函数

③ 输入门(Input Gate)

作用:决定哪些新信息存储到细胞状态中

公式:

i_t=σ(Wi⋅[h_{t−1},x_t]+b_i)

i_t:输入门的输出(0 到 1 之间的值)

W_i,b_i:权重矩阵和偏置项

σ:Sigmoid激活函数

④ 候选细胞状态(Candidate Cell State)

作用:生成新的候选值,用于更新细胞状态

公式:

\tilde{C}t=tanh⁡(W_C⋅[h{t−1},x_t]+b_C)

\tilde{C}_t:候选细胞状态

W_C,b_C:权重矩阵和偏置项

tanh⁡:双曲正切激活函数

⑤ 更新细胞状态

作用:细胞状态 C_t​ 是LSTM的记忆,结合遗忘门和输入门,更新细胞状态

公式:

C_t=f_t⋅C{t−1}+i_t⋅\tilde{C}t​

C_t:更新后的细胞状态

遗忘门 f_t 决定了上一时刻的细胞状态 C_{t-1} 中保留多少信息

输入门 i_t 决定了当前时刻输入x_t 中有多少新信息被添加到细胞状态中

⑥ 输出门(Output Gate)

作用:决定细胞状态的哪些部分输出到隐藏状态

公式:

o_t=σ(W_o⋅[h_{t−1},x_t]+b_o)​

o_t:输出门的输出(0 到 1 之间的值)

W_o,b_o:权重矩阵和偏置项

σ:Sigmoid激活函数

⑦ 隐藏状态(Hidden State)

作用:作为LSTM的输出,传递到下一个时间步

公式:

h_t=o_t⋅tanh⁡(C_t)

h_t:当前时间步的隐藏状态

C_t:是当前时刻的细胞状态

4.2 LSTM的优缺点

LSTM的优点:

能够捕捉长期依赖:通过门控机制,LSTM能够记住长期的依赖关系,解决了传统RNN无法记住长期信息的问题。

避免梯度消失

细胞状态 C_t 的更新公式中,C_{t−1} 和 C_t 之间是线性关系(通过遗忘门 f_t 控制)

LSTM的梯度主要通过细胞状态 C_t​ 传播,而细胞状态的更新是线性的,梯度路径更加稳定

线性关系避免了梯度在时间步之间的连乘,从而缓解了梯度消失问题

灵活的记忆控制:LSTM通过遗忘门和输入门灵活地控制信息的传递,使得模型能够记住有用的信息,并丢弃不必要的信息。

LSTM的缺点:

计算开销较大,由于包含多个门的计算,训练和推理时需要更多的计算资源

相对于简单的RNN和GRU(门控递归单元),LSTM较为复杂,调参时需要更多的时间和精力

4.3 LSTM的内部结构图

遗忘门结构分析:

与传统RNN的内部结构计算非常相似,首先将当前时间步输入x_t与上一个时间步隐藏状态h{t-1}拼接,得到[x_t,h{t-1}],然后通过一个全连接层做变换,最后通过sigmoid函数进行激活得到f_t。我们可以将f_t看作是门值,好比一扇门开合的大小程度,门值都将作用在通过该扇门的张量上,遗忘门门值将作用在上一层的细胞状态上,代表遗忘过去的多少信息,又因为遗忘门门值是由x_t,h{t-1}计算得来的,因此整个公式意味着根据当前时间步输入和上一个时间步隐藏状态h{t-1}来决定遗忘多少上一层的细胞状态所携带的过往信息。

输入门结构分析:

我们看到输入门的计算公式有两个,第一个就是产生输入门门值的公式,它和遗忘门公式几乎相同,区别只是在于它们之后要作用的目标上。这个公式意味着输入信息有多少需要进行过滤,输入门的第二个公式是与传统RNN的内部结构计算相同。对于LSTM来讲,它得到的是当前的细胞状态,而不是像经典RNN一样得到的是隐藏状态。

细胞状态更新分析:

细胞更新的结构与计算公式非常容易理解,这里没有全连接层,只是将刚刚得到的遗忘门门值与上一个时间步得到的C_{t-1}相乘,再加上输入门门值与当前时间步得到的未更新C_t相乘的结果。最终得到更新后的C_t作为下一个时间步输入的一部分。整个细胞状态更新过程就是对遗忘门和输入门的应用。

输出门结构分析:

输出门部分的公式也是两个,第一个即是计算输出门的门值,它和遗忘门、输入门计算方式相同。第二个即是使用这个门值产生隐藏状态h_t,它将作用在更新后的细胞状态C_t上,并做tanh激活,最终得到h_t作为下一时间步输入的一部分。整个输出门的过程,就是为了产生隐藏状态h_t。

4.4 LSTM工作流程概况

LSTM工作流程:

遗忘门:

根据当前输入 x_t​ 和前一隐藏状态 h{t−1}​,决定细胞状态 C{t−1}​ 中哪些信息需要丢弃。

输入门:

根据当前输入 x_t和前一隐藏状态 h_{t−1},决定哪些新信息需要添加到细胞状态中。

更新细胞状态:

结合遗忘门和输入门的结果,更新细胞状态 C_t。

输出门:

根据当前输入 x_t 和前一隐藏状态 h_{t−1},决定细胞状态 C_t 中哪些信息需要输出。

生成隐藏状态:

根据输出门的结果和更新后的细胞状态,生成当前时间步的隐藏状态 h_t。

4.5 Bi-LSTM介绍

概念

Bi-LSTM(双向长短期记忆网络,Bidirectional Long Short-Term Memory Network)是一种扩展版的LSTM(长短期记忆网络),它通过结合正向LSTM和反向LSTM来捕捉序列数据的上下文信息。与传统的单向LSTM(仅从过去到现在的时间序列建模)不同,Bi-LSTM能够同时从过去和未来的上下文信息中学习,从而提高模型的表现,尤其在需要了解整个序列上下文的任务中非常有效。它没有改变LSTM本身任何的内部结构,只是将LSTM应用两次且方向不同,再将两次得到的LSTM结果进行拼接作为最终输出。

核心思想

Bi-LSTM的核心思想是通过两个独立的LSTM层分别处理序列的正向和反向信息,然后将两个方向的隐藏状态结合起来,生成最终的输出。

正向LSTM:从序列的开始到结束处理数据。

反向LSTM:从序列的结束到开始处理数据。

通过结合正向和反向的信息,Bi-LSTM能够同时捕捉过去和未来的上下文信息。

内部结构

输入层:将输入序列传递给两个LSTM网络(正向和反向)。

正向LSTM:按照时间顺序处理输入序列(从第一个时间步到最后一个时间步)。

反向LSTM:逆序处理输入序列(从最后一个时间步到第一个时间步)。

合并层:正向和反向LSTM的输出通常被拼接在一起,形成一个包含更多上下文信息的表示。

输出层:将合并后的表示传递到下游任务,进行分类、回归或者其他任务的预测。

① 正向 LSTM

- 输入:序列的正向数据 x_1,x_2,…,x_T。 - 隐藏状态:\overrightarrow{h_t}。 - 细胞状态:\overrightarrow{C_t}。

② 反向 LSTM

- 输入:序列的反向数据 x_T,x_{T−1},…,x_1。 - 隐藏状态:\overleftarrow{h_t}。 - 细胞状态:\overleftarrow{C_t}。

③ 结合正向和反向信息

- 将正向和反向的隐藏状态拼接起来,生成最终的隐藏状态:

h_t=[\overrightarrow{h_t},\overleftarrow{h_t}]

- 最终的隐藏状态 ht 包含了序列的完整上下文信息。

- ,:表示拼接操作。

④ 输出层:

- 将双向隐藏状态输入到输出层,得到最终的输出 y_1, y_2, ..., y_T。 - 输出层可以是线性层、softmax层等,根据具体任务而定。

4.6 Bi-LSTM的优缺点概述

Bi-LSTM的优点

捕捉上下文信息:通过结合正向和反向的信息,Bi-LSTM能够更好地捕捉序列的上下文依赖关系

适用于需要全局信息的任务:在自然语言处理(NLP)等任务中,Bi-LSTM能够同时考虑过去和未来的信息

性能优于单向LSTM:在许多任务中,Bi-LSTM的表现优于单向LSTM

Bi-LSTM的缺点

计算复杂度高:Bi-LSTM需要同时计算正向和反向的LSTM,计算量是单向LSTM的两倍

参数量大:Bi-LSTM的参数比单向LSTM多,训练时间较长

难以并行化:与LSTM类似,Bi-LSTM需要按时间步依次计算

4.7 Pytorch构建LSTM模型

LSTM API说明:

# 创建lstm对象

# input_size:输入特征的维度

# hidden_size:隐藏状态的维度

# num_layers:LSTM的层数(默认值为1)

# batch_first:如果为True,输入和输出的形状为 (batch_size, seq_len, input_size);否则为 (seq_len, batch_size, input_size)

# bidirectional:如果为True,使用双向LSTM;否则为单向LSTM(默认False)

# dropout:在多层LSTM中,是否在层之间应用dropout(默认值为0)

lstm = nn.LSTM(input_size, hidden_size, num_layers, bidirectional, batch_first, dropout)

# 调用lstm对象

# 输入

# input:形状为 (seq_len, batch_size, input_size) 或 (batch_size, seq_len, input_size)(如果batch_first=True)

# h_0:初始隐藏状态,形状为 (num_layers * num_directions, batch_size, hidden_size)

# c_0:初始记忆单元状态,形状为 (num_layers * num_directions, batch_size, hidden_size)

# 输出

# output:每个时间步的隐藏状态,形状为 (seq_len, batch_size, hidden_size * num_directions) 或 (batch_size, seq_len, hidden_size * num_directions)(如果batch_first=True)

# h_n:最后一个时间步的隐藏状态,形状为 (num_layers * num_directions, batch_size, hidden_size)

# c_n:最后一个时间步的记忆单元状态,形状为 (num_layers * num_directions, batch_size, hidden_size)

output, (hn, cn) = lstm(x, (h0, c0))

nn.LSTM使用示例:

# 定义LSTM的参数含义: (input_size, hidden_size, num_layers)

# 定义输入张量的参数含义: (sequence_length, batch_size, input_size)

# 定义隐藏层初始张量和细胞初始状态张量的参数含义: (num_layers * num_directions, batch_size, hidden_size)

import torch.nn as nn

import torch

def dm_lstm():

# 创建LSTM层

lstm = nn.LSTM(input_size=5, hidden_size=6, num_layers=2)

# 创建输入张量

input = torch.randn(size=(1, 3, 5))

# 初始化隐藏状态

h0 = torch.randn(size=(2, 3, 6))

# 初始化细胞状态

c0 = torch.randn(size=(2, 3, 6))

# hn输出两层隐藏状态, 最后1个隐藏状态值等于output输出值

output, (hn, cn) = lstm(input, (h0, c0))

print('output--->', output.shape, output)

print('hn--->', hn.shape, hn)

print('cn--->', cn.shape, cn)

五、GRU模型

5.1 什么是GRU

概念

GRU(Gated Recurrent Unit)也称为门控循环单元,是一种改进版的RNN。同LSTM一样能够有效捕捉长序列之间的语义关联,通过引入两个"门"机制(重置门和更新门)来控制信息的流动,从而避免了传统RNN中的梯度消失问题,并减少了LSTM模型中的复杂性。

核心思想

通过引入更新门 (Update Gate) 和重置门 (Reset Gate) 来控制信息在网络中的流动。这些门控机制决定哪些信息应该保留、哪些信息应该丢弃,从而有效地捕获长距离的依赖关系。

内部结构

隐藏状态:包含了过去时间步的记忆,并随着时间步的推移不断更新。

重置门:决定在计算候选隐藏状态时,要忽略多少先前的隐藏状态。

更新门:决定在多大程度上保留先前的隐藏状态,以及在多大程度上更新为新的隐藏状态。

候选隐藏状态:基于当前输入和经过重置门过滤后的前一时刻隐藏状态计算出的新的隐藏状态的候选值。

更新后的隐藏状态:最终的隐藏状态,由先前的隐藏状态和候选隐藏状态加权求和得到。

① 重置门(Reset Gate)

作用:决定如何将新的输入与之前的隐藏状态结合。

当重置门值接近0时,表示当前时刻的输入几乎不依赖上一时刻的隐藏状态。

当重置门值接近1时,表示当前时刻的输入几乎完全依赖上一时刻的隐藏状态。

公式:

r_t=σ(W_r⋅[h_{t−1},x_t]+b_r)

r_t:重置门的输出

W_r 和 b_r:重置门的权重和偏置

σ​:sigmoid​函数,输出值在 0 到 1 之间

② 更新门(Update Gate)

作用:决定多少之前的信息需要保留,多少新的信息需要更新。

当更新门值接近0时,意味着网络只记住旧的隐藏状态,几乎没有新的信息。

当更新门值接近1时,意味着网络更倾向于使用新的隐藏状态,记住当前输入的信息。

公式:

z_t=σ(W_z⋅[h_{t−1},x_t]+b_z)

z_t:更新门的输出

W_z 和 b_z:更新门的权重和偏置

σ:sigmoid函数,输出值在 0 到 1 之间

③ 候选隐藏状态(Candidate Hidden State)

作用:捕捉当前时间步的信息,多少前一隐藏状态的信息被保留。

公式:

\tilde{h}t=tanh⁡(W_h⋅[r_t⊙h{t−1},x_t]+b_h)​

\tilde{h}_t:候选隐藏状态

W_h 和 b_h:候选隐藏状态的权重和偏置

tanh⁡:双曲正切函数,用于将值压缩到 -1 到 1 之间

⊙:逐元素乘法

④ 最终隐藏状态(Final Hidden State)

作用:控制信息更新,传递长期依赖。

公式:

h_t=(1−z_t)⊙h{t−1}+z_t⊙\tilde{h}t​

h_t:当前时间步的隐藏状态

z_t:更新门的输出,控制新旧信息的比例

⊙:逐元素乘法

5.2 GRU的优缺点

GRU的优点

比LSTM更简单:GRU只有两个门(与LSTM的三个门相比),因此它的计算复杂度更低,训练和推理速度较快。

避免梯度消失:通过更新门和重置门的设计,GRU可以有效地捕获长时依赖。

更新门值≈0时,ht≈h{t−1},梯度通过 h{t−1} 直接传递,形成残差连接(类似 ResNet),避免梯度消失。

更新门值≈1时,ht≈\tilde{h}_t,梯度通过非线性变换传播,但门控的平滑性可抑制梯度爆炸。

重置门值≈0时,h_{t−1} 对候选状态的贡献被抑制,模型更依赖当前输入 x_t。

重置门值≈1时,h_{t−1} 完全参与候选状态计算,保留长期依赖。

线性路径:更新门的残差连接 (h_t=(1−z_t)⊙h{t−1}+z_t⊙\tilde{h}t) 允许梯度在时间步间直接传递,减少连乘效应。

门控的平滑性:Sigmoid 函数输出值在 [0, 1] 之间,避免梯度剧烈波动。

性能与LSTM相当:在很多任务中,GRU和LSTM的表现非常接近,GRU的性能往往能够与LSTM相匹敌,但需要的计算资源较少。

GRU的缺点

对超长序列的捕捉能力弱于LSTM:LSTM 通过独立的细胞状态(Cell State)显式存储长期信息,而 GRU 直接更新隐藏状态,可能导致长程依赖信息逐渐稀释。

门控机制的非线性限制:更新门和重置门的 Sigmoid 激活函数可能导致梯度饱和(接近 0 或 1),影响参数更新。LSTM 的细胞状态更新包含线性操作(C_t=f_t⋅C{t−1}+i_t⋅\tilde{C}t),梯度传播更稳定。

不可并行计算:时间步之间的依赖关系仍然限制了其并行化程度。

5.3 GRU的内部结构图

内部结构分析:

和之前分析过的LSTM中的门控一样,首先计算更新门和重置门的门值,分别是z_t和r_t,计算方法就是使用x_t与h{t-1}拼接进行线性变换,再经过sigmoid激活。之后重置门门值作用在了h{t-1}上,代表控制上一时间步传来的信息有多少可以被利用。接着就是使用这个重置后的h{t-1}进行基本的RNN计算,即与x_t拼接进行线性变化,经过tanh激活,得到新的h_t。最后更新门的门值会作用在新的h_t,而1-门值会作用在h{t-1}上,随后将两者的结果相加,得到最终的隐藏状态输出h_t,这个过程意味着更新门有能力保留之前的结果,当门值趋于1时,输出就是新的h_t;而当门值趋于0时,输出就是上一时间步的h_{t-1}。

5.4 GRU工作流程概况

GRU工作流程:

计算重置门 r_t 和更新门 z_t。

根据重置门的结果计算候选隐藏状态 \tilde{h}_t。

使用更新门将候选隐藏状态与上一时刻的隐藏状态结合,得到当前时刻的隐藏状态 h_t。

将 h_t 作为输出,并传递到下一时刻。

5.5 Bi-GRU介绍

概念

Bi-GRU(Bidirectional Gated Recurrent Unit)是 GRU的改进,它通过将正向和反向GRU结合在一起,能够同时利用输入序列的过去和未来信息。双向GRU是一种在序列建模中常见的结构,尤其在自然语言处理(NLP)和时间序列分析中具有很大的优势。

核心思想

同时利用序列的正向和反向信息:

正向 GRU:从序列的起始位置到结束位置处理数据,捕捉过去的信息。

反向 GRU:从序列的结束位置到起始位置处理数据,捕捉未来的信息。

将正向和反向GRU的隐藏状态结合起来,得到更全面的序列表示。

内部结构

输入层:将输入序列传递给两个GRU网络(正向和反向)。

正向GRU:按照时间顺序处理输入序列(从第一个时间步到最后一个时间步)。

反向GRU:逆序处理输入序列(从最后一个时间步到第一个时间步)。

合并层:正向和反向GRU的输出通常被拼接在一起,形成一个包含更多上下文信息的表示。

输出层:将合并后的表示传递到下游任务,进行分类、回归或者其他任务的预测。

① 正向 GRU

输入:序列的正向数据 x_1,x_2,…,x_T。

隐藏状态:\overrightarrow{h_t}。

② 反向 GRU

输入:序列的反向数据 x_T,x_{T−1},…,x_1。

隐藏状态:\overleftarrow{h_t}。

③ 结合正向和反向信息

将正向和反向的隐藏状态拼接起来,生成最终的隐藏状态:

h_t=[\overrightarrow{h_t},\overleftarrow{h_t}]

最终的隐藏状态 ht 包含了序列的完整上下文信息。

,:表示拼接操作。

④ 输出层:

将双向隐藏状态输入到输出层,得到最终的输出 y_1, y_2, ..., y_T。

输出层可以是线性层、softmax层等,根据具体任务而定。

5.6 Bi-GRU的优缺点

Bi-GRU的优点

捕捉上下文信息:通过结合正向和反向的信息,Bi-GRU能够更好地捕捉序列的上下文依赖关系

适用于需要全局信息的任务:在自然语言处理(NLP)等任务中,Bi-GRU能够同时考虑过去和未来的信息

性能优于单向GRU:在许多任务中,Bi-GRU的表现优于单向GRU

Bi-GRU的缺点

计算复杂度高:Bi-GRU需要同时计算正向和反向的GRU,计算量是单向GRU的两倍

参数量大:Bi-GRU的参数比单向GRU多,训练时间较长

难以并行化:与GRU类似,Bi-GRU需要按时间步依次计算

5.7 Pytorch构建GRU模型

GRU API说明:

# 创建gru对象

# input_size:输入特征的维度

# hidden_size:隐藏状态的维度

# num_layers:GRU的层数(默认值为1)

# batch_first:如果为True,输入和输出的形状为 (batch_size, seq_len, input_size);否则为 (seq_len, batch_size, input_size)

# bidirectional:如果为True,使用双向GRU;否则为单向GRU(默认False)

# dropout:在多层GRU中,是否在层之间应用dropout(默认值为0)

gru = nn.GRU(input_size, hidden_size, num_layers, bidirectional, batch_first, dropout)

# 调用gru对象

# 输入

# input:形状为 (seq_len, batch_size, input_size) 或 (batch_size, seq_len, input_size)(如果batch_first=True)

# h_0:初始隐藏状态,形状为 (num_layers * num_directions, batch_size, hidden_size)

# 输出

# output:每个时间步的隐藏状态,形状为 (seq_len, batch_size, hidden_size * num_directions) 或 (batch_size, seq_len, hidden_size * num_directions)(如果batch_first=True)

# h_n:最后一个时间步的隐藏状态,形状为 (num_layers * num_directions, batch_size, hidden_size)

output, hn = gru(x, h0)

nn.GRU使用示例:

# 定义GRU的参数含义: (input_size, hidden_size, num_layers)

# 定义输入张量的参数含义: (sequence_length, batch_size, input_size)

# 定义隐藏层初始张量的参数含义: (num_layers * num_directions, batch_size, hidden_size)

import torch.nn as nn

import torch

def dm_gru():

# 创建GRU层

gru = nn.GRU(input_size=5, hidden_size=6, num_layers=2)

# 创建输入张量

input = torch.randn(size=(1, 3, 5))

# 初始化隐藏状态

h0 = torch.randn(size=(2, 3, 6))

# hn输出两层隐藏状态, 最后1个隐藏状态值等于output输出值

output, hn = gru(input, h0)

print('output--->', output.shape, output)

print('hn--->', hn.shape, hn)

输出结果:

output---> torch.Size([1, 3, 6]) tensor([[[-0.1109, -1.0413, 0.3340, 0.6548, -0.1967, 0.4516], [ 0.0095, 1.0576, -0.3029, 0.2907, -0.3876, 0.4790], [ 0.3430, -1.2316, 0.5145, 1.0067, 0.2637, 0.1618]]], grad_fn=) hn---> torch.Size([2, 3, 6]) tensor([[[ 0.1325, 0.5354, 0.3423, -0.6943, 0.9261, -0.0672], [ 0.6470, 0.3491, -0.3319, -0.8313, 0.0370, -0.2859], [ 0.2856, -0.7553, 1.2566, -0.0828, 0.4304, -0.1633]],

[[-0.1109, -1.0413, 0.3340, 0.6548, -0.1967, 0.4516], [ 0.0095, 1.0576, -0.3029, 0.2907, -0.3876, 0.4790], [ 0.3430, -1.2316, 0.5145, 1.0067, 0.2637, 0.1618]]], grad_fn=)

六、注意力机制

6.1 注意力机制由来

seq2seq模型架构包括三部分,分别是encoder(编码器)、decoder(解码器)、中间语义张量c。

编码器:将输入序列编码为一个固定长度的上下文向量(Context Vector->中间语义张量c)。

解码器:基于该上下文向量生成输出序列。

图中表示的是一个中文到英文的翻译:欢迎 来 北京 → welcome to BeiJing。编码器首先处理中文输入"欢迎 来 北京",通过GRU模型获得每个时间步的输出张量,最后将它们拼接成一个中间语义张量c;接着解码器将使用这个中间语义张量c以及每一个时间步的隐藏层张量, 逐个生成对应的翻译语言。

早期在解决机器翻译这一类seq2seq问题时,通常采用的做法是利用一个编码器(Encoder)和一个解码器(Decoder)构建端到端的神经网络模型,但是基于编码解码的神经网络存在两个问题:

问题1:如果翻译的句子很长很复杂,比如直接一篇文章输进去,模型的计算量很大,并且模型的准确率下降严重。

问题2:在翻译时,可能在不同的语境下,同一个词具有不同的含义,但是网络对这些词向量并没有区分度,没有考虑词与词之间的相关性,导致翻译效果比较差。

针对这样的问题,注意力机制被提出,最初是为了解决Seq2Seq模型在机器翻译任务中的信息瓶颈和长序列问题而提出的。

6.2 什么是注意力机制

概念

用于增强神经网络模型性能的技术

注意力机制(Attention Mechanism)是深度学习中一种重要的技术,它起源于神经科学中的“视觉注意”机制,即人类在处理视觉信息时,能够聚焦于特定区域(例如:某个物体、某个细节等),从而更加高效地处理信息。借用这一思想,注意力机制在深度学习中广泛应用,特别是在处理序列数据(如文本、语音、时间序列等)时,能够帮助模型集中关注输入序列中的关键部分,从而提高模型的性能。

背景

在传统的序列模型(如RNN、LSTM、GRU)中,模型通常通过固定的方式处理输入序列,无法灵活地关注输入序列中的不同部分。这种限制在处理长序列或复杂任务时尤为明显。注意力机制的引入解决了这一问题,它允许模型动态地分配权重,从而更有效地利用输入信息。

核心思想

让模型在处理输入序列时,动态地学习哪些部分应该被重点关注,而不是像传统方法那样对所有部分同等对待。它通过计算每个输入位置的注意力权重,并根据这些权重对输入信息进行加权求和,从而得到一个更加关注重要信息的表示。

作用

注意力机制的核心作用是动态地分配权重,从而更好地捕捉输入数据中的重要信息。以下是注意力机制的主要作用:

解决信息瓶颈问题

传统的 Seq2Seq 模型依赖于一个固定长度的上下文向量,导致信息丢失。

注意力机制通过动态地关注输入序列的不同部分,避免了信息瓶颈问题。

捕捉长距离依赖关系

在长序列任务中,传统的 RNN 模型难以捕捉远距离依赖关系。

注意力机制能够直接计算输入序列中任意两个元素之间的关系,从而更好地捕捉长距离依赖。

提高模型的表达能力

注意力机制通过动态分配权重,能够更灵活地处理输入数据。

这种灵活性使得模型能够更好地适应不同的任务和数据分布。

增强模型的可解释性

注意力权重可以直观地反映模型在决策过程中关注了哪些部分。

这种可解释性在 NLP 和 CV 任务中尤为重要。

6.3 注意力机制分类

通俗来讲就是对于模型的每一个输入项,可能是图片中的不同部分,或者是语句中的某个单词分配一个权重,这个权重的大小就代表了我们希望模型对该部分一个关注程度。这样一来,通过权重大小来模拟人在处理信息的注意力的侧重,有效的提高了模型的性能,并且一定程度上降低了计算量。

深度学习中的注意力机制通常可分为三类: 软注意(全局注意)、硬注意(局部注意)和自注意(内注意)

软注意机制(Soft/Global Attention): 对每个输入项分配的权重为0-1之间,也就是某些部分关注的多一点,某些部分关注的少一点,因为对大部分信息都有考虑,但考虑程度不一样,所以相对来说计算量比较大。

硬注意机制(Hard/Local Attention,[了解即可]): 对每个输入项分配的权重非0即1,和软注意不同,硬注意机制只考虑哪部分需要关注,哪部分不关注,也就是直接舍弃掉一些不相关项。优势在于可以减少一定的时间和计算成本,但有可能丢失掉一些本应该注意的信息。

自注意力机制(Self/Intra Attention): 对每个输入项分配的权重取决于输入项之间的相互作用,即通过输入项内部的"表决"来决定应该关注哪些输入项。和前两种相比,在处理很长的输入时,具有并行计算的优势。

6.3.1 soft attention(软注意力机制)

需要注意:注意力机制是一种通用的思想和技术,不依赖于任何模型,换句话说,注意力机制可以用于任何模型。我们这里只是以文本处理领域的Encoder-Decoder框架为例进行理解。这里我们分别以普通Encoder-Decoder框架以及加Attention的Encoder-Decoder框架分别做对比。

其实Attention机制可以看作,Target中每个单词是对Source每个单词的加权求和,而权重是Source中每个单词对Target中每个单词的重要程度。因此Attention的本质思想会表示成下图:

将Source中的构成元素看作是一系列的数据对,给定Target中的某个元素Query,通过计算Query和各个Key的相似性或者相关性,即权重系数;然后对Value进行加权求和,并得到最终的Attention数值。将本质思想表示成公式如下:

深度学习中的注意力机制中提到:Source 中的 Key 和 Value 合二为一,指向的是同一个东西,也即输入句子中每个单词对应的语义编码,所以可能不容易看出这种能够体现本质思想的结构。因此Attention计算转换为下面3个阶段。

输入由三部分构成:Query、Key和Value。其中,(Key, Value)是具有相互关联的KV对,Query是输入的“问题”,Attention可以将Query转化为与Query最相关的向量表示。

Attention的计算主要分3步,如下图所示。

Attention 3步计算过程:

第一步:查询向量Query与每个键向量Key进行相似度计算,得到注意力分数(Attention Score);

常用的相似度计算方法包括:

点积 (Dot Product): score(Q, K) = Q ⋅ K^T

缩放点积 (Scaled Dot Product): score(Q, K) = \frac{Q ⋅ K^T}{\sqrt{d_k}} (d_k是键向量的维度)

加性 (Additive): score(Q,K)=W*tanh(W_qQ+W_kK)​

第二步:对Attention Score进行Softmax归一化,得到权值矩阵;

α = softmax(score(Q, K))​

第三步:权重矩阵与值向量Value进行加权求和计算,得到上下文向量c。

context{vector} = \displaystyle \sum{i}α_i * V_i​

Query、Key和Value的含义是什么呢?我们以刚才大脑读图为例。Value可以理解为人眼视网膜对整张图片信息的原始捕捉,不受“注意力”所影响。我们可以将Value理解为像素级别的信息,那么假设只要一张图片呈现在人眼面前,图片中的像素都会被视网膜捕捉到。Key与Value相关联,Key是图片原始信息所对应的关键性提示信息,比如“锦江饭店”部分是将图片中的原始像素信息抽象为中文文字和牌匾的提示信息。一个中文读者看到这张图片时,读者大脑有意识地向图片获取信息,即发起了一次Query,Query中包含了读者的意图等信息。在一次读图过程中,Query与Key之间计算出Attention Score,得到最具有吸引力的部分,并只对具有吸引力的Value信息进行提取,反馈到大脑中。就像上面的例子中,经过大脑的注意力机制的筛选,一次Query后,大脑只关注“锦江饭店”的牌匾部分。

再以一个搜索引擎的检索为例。使用某个Query去搜索引擎里搜索,搜索引擎里面有好多文章,每个文章的全文可以被理解成Value;文章的关键性信息是标题,可以将标题认为是Key。搜索引擎用Query和哪些文章的标题Key进行匹配,看看相似度(计算Attention Score)。我们想得到跟Query相关的知识,于是用这些相似度将检索的文章Value做一个加权和,那么就得到了一个新的信息,新的信息融合了相关性强的文章们,而相关性弱的文章可能被过滤掉。

查询 (Query):查询向量表示当前要关注的目标,可以把它理解为“我正在寻找什么?”。类似于搜索时的“提问”,用于匹配相关的键(Key)。例如:在机器翻译中,查询向量Query代表的是解码器当前要翻译的目标词语的嵌入向量(解码器的隐藏状态)。

键 (Key):键向量表示输入序列每个元素的关键特征,可以把它理解为“所有元素的关键属性是什么?”。它们是用来与 Query进行比较,以确定哪些信息与当前Query最为相关。类似于数据库中的“索引”,用于与查询(Query)计算相关性。例如:在机器翻译中,键向量Key代表的是源语言句子中所有词语的嵌入向量(编码器的隐藏状态)。

值 (Value):值向量表示输入序列每个元素的实际信息,可以把它理解为“与当前Query最相关的元素的实际内容是什么?”。这些内容将被加权平均,以产生最终的输出。类似于数据库中的“实际数据”,根据权重加权后生成最终输出。通常与键向量Key相同。例如:在机器翻译的例子中,值向量Value代表的是源语言句子中所有词语的嵌入向量(编码器的输出结果)。

6.3.2 hard attention(硬注意力机制)

在6.3.1章节我们使用了一种软性注意力的方式进行Attention机制,它通过注意力分布来加权求和融合各个输入向量。而硬性注意力(Hard Attention)机制则不是采用这种方式,它是根据注意力分布选择输入向量中的一个作为输出。这里有两种选择方式:

选择注意力分布中,分数最大的那一项对应的输入向量作为Attention机制的输出。

根据注意力分布进行随机采样,采样结果作为Attention机制的输出。

硬性注意力通过以上两种方式选择Attention的输出,这会使得最终的损失函数与注意力分布之间的函数关系不可导,导致无法使用反向传播算法训练模型,硬性注意力通常需要使用强化学习来进行训练。因此,一般深度学习算法会使用软性注意力的方式进行计算。

6.3.3 self attention(自注意力机制)

Self Attention是Google在transformer模型中提出的,上面介绍的都是一般情况下Attention发生在Target元素Query和Source中所有元素之间。而Self Attention,**指的是Source内部元素之间或者Target内部元素之间发生的Attention机制**,也可以理解为**Target=Source**这种特殊情况下的注意力机制。当然,具体的计算过程仍然是一样的,只是计算对象发生了变化而已。

上面内容也有说到,一般情况下Attention本质上是Target和Source之间的一种单词对齐机制。那么如果是Self Attention机制,到底学的是哪些规律或者抽取了哪些特征呢?或者说引入Self Attention有什么增益或者好处呢?仍然以机器翻译为例来说明, 如下图所示:

Attention的发展主要经历了两个阶段:

- 从上图中可以看到, self Attention可以远距离的捕捉到语义层面的特征(its的指代对象是Law)。 - 应用传统的RNN、LSTM在获取长距离语义特征和结构特征的时候, 需要按照序列顺序依次计算, 距离越远的联系信息的损耗越大, 有效提取和捕获的可能性越小。 - 但是应用self-attention时, 计算过程中会直接将句子中任意两个token的联系通过一个计算步骤直接联系起来。

6.4 注意力机制规则

它需要三个指定的输入Q(query), K(key), V(value), 然后通过计算公式得到注意力的结果, 这个结果代表query在key和value作用下的注意力表示。当输入的Q=K=V时, 称作自注意力计算规则(Self Attention);当Q、K、V不相等时,称作一般注意力计算规则。

例子:seq2seq架构翻译应用中的Q、K、V解释

seq2seq模型架构包括三部分,分别是encoder(编码器)、decoder(解码器)、中间语义张量c。

图中表示的是一个中文到英文的翻译:欢迎 来 北京 → welcome to BeiJing。编码器首先处理中文输入"欢迎 来 北京",通过GRU模型获得每个时间步的输出张量,最后将它们拼接成一个中间语义张量c;接着解码器将使用这个中间语义张量c以及每一个时间步的隐藏层张量, 逐个生成对应的翻译语言.

在上述机器翻译架构中加入Attention的方式有两种:

第一种Tensorflow版本(传统方式),如下图所示:

上图翻译应用中的Q、K、V解释

查询张量Q: 解码器每个时间步的输出或者是当前输入的x

键张量K: 编码器每个时间步的输出结果组合而成

值张量V: 编码器每个时间步的输出结果组合而成

第二种Pytorch版本(改进版),如下图所示:

上图翻译应用中的Q、K、V解释

查询张量Q: 解码器每个时间步的输出或者是当前输入的x

键张量K: 解码器上一个时间步的隐藏状态(第1个时间步的隐藏状态=编码器最后1个时间步的隐藏状态)

值张量V: 编码器每个时间步的输出结果组合而成

两个版本对比:

pytorch版本的是乘型attention,tensorflow版本的是加型attention。

pytorch将当前输入(input)与上一个单元(unit)的隐藏状态(prev_hidden)进行点积并缩放得到score,之后将score经过softmax得到attenion_weights。

tensorflow将当前输入(input)与上一个单元(unit)的隐藏状态(prev_hidden)拼接起来线性计算得到score,之后将score经过softmax得到attenion_weights。

乘型注意力(点积注意力)通常用于现代深度学习模型,如Transformer,它计算查询和键的点积并进行缩放处理。它计算效率高,能够更好地并行化,适合大规模任务。

加性注意力则通过一个小的神经网络来计算注意力权重,这使得它能捕捉更复杂的关系,但计算上可能更加昂贵,通常用于早期的RNN模型中,如Bahdanau注意力用于神经机器翻译。

解码过程如下:

采用自回归机制,比如:输入“go”来预测“welcome”,输入“welcome”来预测"to",输入“to”来预测“Beijing”。在输入“welcome”来预测"to"解码中,可使用注意力机制。

查询张量Q:一般可以是“welcome”词嵌入层以后的结果,查询张量Q为生成谁就是谁的查询张量(比如这里为了生成“to”,则查询张量就是“to”的查询张量,请仔细体会这一点)

键向量K:一般可以是上一个时间步的隐藏层输出

值向量V:一般可以是编码器每个时间步的输出结果组合而成

查询张量Q来生成“to”,去检索“to”单词和“欢迎”、“来”、“北京”三个单词的权重分布,注意力结果表示(用权重分布 乘以内容V)

常见的注意力计算规则:

将Q,K进行纵轴(1轴)拼接, 做一次线性变化, 再使用softmax处理获得结果最后与V做张量乘法,更简单直接,适合计算资源有限或任务较简单的情况:

将Q,K进行纵轴(1轴)拼接, 做一次线性变化后再使用tanh函数激活, 然后再进行内部求和,最后使用softmax处理获得结果再与V做张量乘法,引入了非线性变换和内部求和,可能捕捉更复杂的依赖关系,但计算复杂度更高:

将Q与K的转置做点积运算, 然后除以一个缩放系数(防止点积值过大或过小,导致梯度爆炸或消失), 再使用softmax处理获得结果最后与V做张量乘法:

说明:当注意力权重矩阵和V都是三维张量且第一维代表为batch条数时, 则做bmm运算,bmm是一种特殊的张量乘法运算。

bmm运算演示:

# 如果参数1形状是(b × n × m), 参数2形状是(b × m × p), 则输出为(b × n × p)

input = torch.randn(10, 3, 4)

mat2 = torch.randn(10, 4, 5)

res = torch.bmm(input, mat2)

res.size()

# 输出结果:

torch.Size([10, 3, 5])

6.5 什么是神经网络注意力机制

注意力机制是注意力计算规则能够应用在深度学习网络的载体, 同时包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使用自注意力计算规则的注意力机制称为自注意力机制.

深度神经网络中的注意力机制(Attention Mechanism) 是一种强大的工具,用于增强模型对输入数据的理解能力。它通过动态地分配权重,使模型能够关注输入数据中的重要部分,从而提升模型的性能。注意力机制广泛应用于自然语言处理(NLP)、计算机视觉(CV)、语音处理和多模态任务等领域。

说明: NLP领域中, 当前的注意力机制大多数应用于seq2seq架构, 即编码器和解码器模型。

请思考:为什么要在深度神经网络中引入注意力机制?

rnn等循环神经网络,随着时间步的增长,前面单词的特征会遗忘,造成对句子特征提取不充分

rnn等循环神经网络是一个时间步一个时间步的提取序列特征,效率低下

研究者开始思考,能不能对32个单词(序列)同时提取事物特征,而且还是并行的,所以引入注意力机制

6.6 seq2seq架构中注意力机制作用

编码器端的注意力机制

主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示,一般使用自注意力(self-attention)。

编码器的任务是生成输入序列的表示。在传统架构中,编码器输出一个固定长度的上下文向量。

在引入注意力机制后,编码器输出的是所有时间步的隐藏状态,而不是单一的上下文向量。

这些隐藏状态包含了输入序列的完整信息,为解码器提供了更丰富的上下文。

编码器端Self-Attention主要用于捕捉输入序列的内部依赖关系。

解码器端的注意力机制

能够根据模型目标有效的聚焦编码器的输出结果, 当其作为解码器的输入时提升效果。改善以往编码器输出是单一定长张量, 无法存储过多信息的情况。

解码器的任务是基于编码器的输出生成目标序列。在传统架构中,解码器仅依赖于单一的上下文向量。

在引入注意力机制后,解码器在每个时间步动态地计算注意力权重,并根据这些权重对编码器的隐藏状态进行加权求和,得到一个上下文向量。

这个上下文向量反映了当前时间步需要关注的输入序列部分,从而帮助解码器生成更准确的输出。

解码器端Encoder-Decoder Attention主要用于让解码器选择性地关注编码器输出的相关信息。

注意力机制在网络中实现的图形表示:

总结:编码器端与解码器端的注意力机制区别

特点编码器端的注意力机制解码器端的注意力机制机制类型自注意力机制(Self-Attention)编码器-解码器注意力(Encoder-Decoder Attention)查询(Query)来自输入序列本身的隐藏状态(h_t)来自解码器当前隐藏状态(s_t)键(Key)和 值(Value)来自输入序列的隐藏状态(h_1, h_2, ..., h_T)来自编码器的每个时间步的隐藏状态(h_1, h_2, ..., h_T)作用增强输入序列的上下文信息表示基于解码器的当前状态来选择最相关的编码器信息

🌸 相关推荐

杀马特是什么意思(日本视觉系和欧美摇滚的结合体)
國際足球總會
英国正版365官方网站

國際足球總會

📅 08-12 👀 4942
凉拌金针菇的做法
英国正版365官方网站

凉拌金针菇的做法

📅 11-01 👀 590