Seq2Seq

定义

Seq2Seq是一个Encoder-Decoder结构的网络,它的输入是一个序列,输出也是一个序列,

Encoder使用循环神经网络(RNN,GRU,LSTM等),将一个可变长度的信号序列(输入句子)变为固定维度的向量编码表达,

Decoder使用循环神经网络(RNN,GRU,LSTM等),将这个固定长度的编码变成可变长度的目标信号序列(生成目标语言)

对不定长序列,我们通常会采取填充/截断策略以保证输入可靠

填充:通常使用0或者特殊标记<pad>作为填充符

策略:

  • 固定长度填充+截断
    • 统一填充到max_length,剩余截断
  • 动态填充
    • 对于每个batch,统一填充到batch中最长序列的长度
  • 滑动窗口截断
    • 对长文本使用滑动窗口截取多个片段

372f36c72e28a3d4a48d8d3b14f594ec.png

image.png

图中的圆角矩形即为cell:可以是RNN,GRU,LSTM等结构

  • 相当于将RNN中的$h_0$输入Encoder (将Decoder部分理解成RNNEncoder输出的Encoder state作为$h_0$)

Seq2Seq模型通过 端到端(?) 的训练方式,将输入序列和目标序列直接关联起来,避免了传统方法中繁琐的特征工程和手工设计的对齐步骤。这使得模型能够自动学习从输入到输出的映射关系,提高了序列转换任务的性能和效率

端到端(End-to-End Learning)

如何理解这个端到端(End-to-End Learning)?

输入序列到输出序列的全过程都由模型学习,我们不需要进行设计规则或特征

工作原理

Seq2Seq中的编码器使用循环神经网络将输入序列转换为固定维度的上下文向量,而解码器使用这个向量和另一个循环神经网络逐步生成输出序列

编解码器

编码器Encoder

把一个不定长的输入序列$x_1,\cdots,x_t$输出到一个编码状态$C$

  • 编码过程中,编码器会逐个读取输入序列的元素,并更新其内部隐藏状态
  • 编码完成后,编码器将最后一个输出的隐藏状态或经过某种变换的隐藏状态作为上下文向量/编码状态传递给解码器

解码器Decoder

输出$y^t$的条件概率将基于之前的输出序列$y^1,\cdots,y^{t-1}$和编码器输出的编码状态$C$

  • 在每个时间步$t$上,解码器将根据上一个时间步的输出$y^{t-1}$,当前隐藏状态以及$编码状态C$来生成当前时间步输出$y^t$

作用

对于给定的输入序列$x_1,\cdots,x_t,\cdots,x_T$,使输出序列$y^1,\cdots,y^{t’},\cdots,y^{T’}$的条件概率值最大

就相当于求:
$$
\begin{align}
argmax(P(y^1,\cdots,\cdots,y^{T’}|x_1,\cdots,x_T))
\end{align}
$$

得到一组使条件概率最大的输出序列$y^1,\cdots,y^{t’},\cdots,y^{T’}$

最大似然估计

对于上面的做法,我们要使用最大似然估计来求出使得条件概率最大的输出序列$y^1,\cdots,y^{t’},\cdots,y^{T’}$

由于解码器的时间步输出值由前面的时间步输出以及当前时间步隐藏状态和编码状态C共同决定

最大似然估计为:

$$
P(y^1,\cdots,\cdots,y^{T’}|x_1,\cdots,x_T) = \prod_{t’=1}^{T’}P(y^{t’}|y^1,\cdots,y^{t’-1},C)
$$
多个概率连乘,最后的值会很小,不利于存储,进行取对数操作

$$
logP(y^1,\cdots,\cdots,y^{T’}|x_1,\cdots,x_T) = \sum_{t’=1}^{T’}logP(y^{t’}|y^1,\cdots,y^{t’-1},C)
$$

转换为使每个$y^{t’}$的条件概率最大的问题,最终得到最优的输出序列

注意力机制Attention Mechanism

核心逻辑

从关注全部到关注重点

  • Attention机制处理长文本时,能够抓住重点,不丢失重要信息
  • Attention机制类似于人类看图片的逻辑,会将注意力放在图片的焦点上

怎么做注意力

例子

image.png

比如对于这张图片,红色部分是人们更关注的

人看这张图片的过程可以描述为:(查询对象Q(Query))看这张图片(被查询对象V(Values)),判断图片中哪些信息更重要,哪些信息更不重要(计算Q和V里事物的重要度(相关程度))

相似度计算

attention-计算图.png

K (Key):与Q匹配的键,通过计算QK的相似度来得到注意力权重

类比:

  • Q:用户的搜索请求,比如搜索科幻电影
  • K:所有电影标签
  • V:电影本身内容

计算Q与K的相似度,得到权重向量,最终与V加权求和

方法

使用点乘的形式计算相似度$(i=1,2,…,n)$

$$
\begin{align}
f(Q,K_i) = Q^T\cdot K_i
\end{align}
$$

感知器

$$
\begin{align}
f(Q,K_i) = v^TTanh(W\cdot Q+U \cdot K_i)\
v,W,U:可学习的参数矩阵\
\end{align}
$$
权重点乘

$$
\begin{align}
f(Q,K_i) = Q\cdot W \cdot K_i \
W:可学习的参数矩阵
\end{align}
$$

权重拼接

$$
\begin{align}
f(Q,K_i) = W[Q;K_i] \
将Q和K向量拼接在一起,通过可学习参数矩阵W做线性变化
\end{align}
$$

softMax+归一化

$$
\begin{align}
\alpha_i = softMax(\frac{f(Q,K_i)}{\sqrt{d_k}})\
d_k:缩放因子,是Query向量和K向量的维度
\end{align}
$$
为什么要除以一个$\sqrt{d_k}$(?)

假设Q,K的每个元素是独立随机变量,均值为0,方差为1,那么点乘操作之后,均值为0,每个元素的方差为Q和K的维度$d_k$

当$d_k$很大时,分布就会集中在绝对值大的区域,造成概率两极化(接近0/1),造成梯度消失

加权求和

$针对计算出的\alpha_i,对V中所有的value进行加权求和,得到Attention向量:$

$$
\begin{align}
Attention(Q,K,V) = \sum_{i=1}^{n} \alpha_i \cdot V_i = \sum_{i=1}^{n} softMax(\frac{f(Q,K_i)}{\sqrt{d_k}}) \cdot V_i
\end{align}
$$

seq2seq with Attention

在传统的seq2seq中有一个显著的缺点:

长句子问题

最初引入注意力机制是为了解决机器翻译中遇到的长句子(超过50字)性能下降问题

传统的机器翻译在长句子上的效果并不理想,本质原因是:

Encoder-Decoder结构中,Encoder会将输入序列中的所有词统一编码成一个语义特征C后,再进行解码;因此C中就包含了输入序列的所有信息,它的长度就成了限制性能的瓶颈。对于长句子,语义特征C可能并不能存下所有的信息,造成了效果不佳

结构

在最初的seq2seq中,结构为

c6de925e2cdcf2aa7448d481fdcbe44f.png

整个输入序列被编码为一个固定长度的编码状态C

而引入Attention机制之后,整个输入序列不再被编码成一个定长的编码状态C,而是编码成一个向量序列$C=[C_1,\cdots,C_n]$

核心在于:$C_i根据输出生成单词y_i不断变化$

74acee126c5946da911dfb54dfa97673.png

从解码器输出的角度:

  • 每个输出词y都会受到每个输入词x1,x2,x3,x4的影响,但是每个输入词对最终输出词y的影响权重不一样,这个权重由Attention计算得到

工作原理

image.png

此时Decodert时刻有3个输入: t-1时刻的隐藏状态输出和循环神经网络输出,以及Encoder输出的context-vec

编码器Encoder

结构

image.png

双向循环神经网络

Seq2Seq with Attention的编码器中,使用了双向的RNN,GRU,LSTM结构,简称为RNNs

双向RNNs(BiRNNs)由前向RNNs和后向RNNs,分别处理序列前半部分和后半部分

比如对于”I love deep-learning”,通过BiRNNs可以同时知道love前面是I,后面是deep-learning

流程
  • 输入处理
    • 将语料分词后的token_id分批次传入embedding层,转换为词向量(Word2Vec形式,one-hot形式……)
      什么是token-id(?)
      就是分词后的单词在词库中对应的唯一整数索引
  • 特征提取
    • 将前一步得到的词向量作为输入,传入Encoder的特征提取器-使用BiRNNs
  • 状态输出
    • 两个方向的RNNs都会产生隐藏状态输出,将这两个隐藏状态输出拼接,作为1个完整的隐藏状态输出h

解码器Decoder

结构

image.png

流程
  • 隐藏状态
    • 前一时刻的隐藏状态输出$s_{t-1}$
  • 计算相似度score
    • 计算前一时刻隐藏状态输出$s_{t-1}$与Encoder的第j步隐藏状态输出$h_j$的相似度score
  • 计算注意力权重
    • 将上一步的相似度score进行softMax归一化

计算上下文向量Context Vec

image.png

$$
得到的h_j与权重\alpha_{ij}进行加权求和,得到上下文向量
$$

公式

注意力分数计算

使用Decoder前一时刻隐藏状态$s_{t-1}$,Encoder中第j个时间步的隐藏状态$h_j$,进行计算得到注意力权重$\alpha_{ij}$

计算匹配得分

$$
匹配得分e_{ij}表示Decoder在第i-1步的隐藏状态输出s_{i-1},与Encoder在第j步的隐藏状态输出h_j之间的匹配程度
$$

  • 方法1:感知器
    $$
    \begin{align}
    e_{ij} = v^T\cdot Tanh(W\cdot s_{i-1} + U\cdot h_j)
    \end{align}
    $$
  • 方法2:使用二次型矩阵计算

二次型:一个向量和矩阵的乘积

$$
e_{ij} = s_{i-1}\cdot W_{i-1,j} \cdot h_j
$$

计算权重 $\alpha_{ij}$:

使用softMax函数进行归一化

$$
\alpha_{ij} = \frac{exp(e_{ij})}{\sum_{i=1}^{n}exp(e_{ik})}
$$

计算上下文向量 Context Vec

$$
\begin{align}
C_i = \sum_{j=1}^{n}\alpha_{ij}\cdot h_j \
n:序列长度\
C_i:解码步骤i的上下文向量\
\alpha_{ij}:输入序列中第j个词对解码步骤i的注意力权重\
h_j:编码器输出的第j个词的隐藏状态\
\end{align}
$$

比如对于”我 爱 机器 学习” -> “I love machine learning”这个翻译过程

对于i=1,就是要计算 "I""我","爱","机器","学习" 每个时刻输入的相关性

类别

自注意力机制Self Attention

Self-Attention的关键点:Q,K,V都是来源于同一个序列X,通过X找到X里面的关键点

架构

self-attention.png

Self-Attention 有3个输入 Q,K,V

这三个输入来源于同一个输入序列X的词向量x的线性变换

定义三个可学习的参数矩阵:$W_Q,W_K,W_V$

Q,K,V定义如下:
$$
\begin{align}
Q = x \cdot W_Q\
K = x\cdot W_K \
V = x\cdot W_V
\end{align}
$$

流程

先以向量为例子:

  • 得到初始的Q,K,V
    $$
    \begin{align}
    对于序列X中第i个单词X_i的词向量x_i\ \ (i \ \in (0,n]):\
    Q_i = x_i \cdot W_Q\
    K_i = x_i \cdot W_K\
    V_i = x_i \cdot W_V\
    \end{align}
    $$
    qkv.jpg

  • $计算当前查询Q_i与其他K_j(包括当前Q_i对应的K_i)的相似度$
    $$
    \begin{align}
    使用点乘计算相似度:\
    s_{ij} = Q_i \cdot K_j\ \ \ (i,j\ \in [0,n])
    \end{align}
    $$
    Q-K乘积.jpg

  • 归一化+softMax
    $$
    \begin{align}
    需要对前面得到的s_{ij}进行归一化:除以缩放因子\sqrt{d_K}\
    d_K:K的维度\
    s_{ij}’ = \frac{s_{ij}}{\sqrt{d_{K}}}\
    A_i = softMax(S’) = \frac{exp(s’{ij})}{\sum{k=1}^{n}exp(s’_{ik})}
    \end{align}
    $$

qk-scale.jpg

  • 加权求和
    $$
    \begin{align}
    Z_i = A_i \cdot V_i\
    \end{align}
    $$
    qk-softmax.jpg

以矩阵形式:

QKV-矩阵表示.jpg

QKVZ-结果.jpg

对于这个softMax

$$
\begin{align}
Q_i\cdot K_i^T是一个word2word的attention\ Map \
经过softMax后:\
矩阵内每个元素和为1,相当于每个元素之间都有对应的权重
\end{align}
$$
假设输入"I have a dream":

注意力机制矩阵图.jpg

形成了一张4×4的注意力图,每个单词与单词之间都有对应的权重

与普通Attention机制的区别

  • 普通Attention:
    • 在Encoder-Decoder模型中,查询向量Q是Decoder的输出,K和V位于Encoder
  • self Attention:
    • 在Encoder-Decoder模型中,Q,K,V都是Encoder中的元素

与RNNs相比

  • self Attention 将任意两个单词通过一个计算步骤$Q_i\cdot K_j$动态联系在一起,不会存在RNNs中有效信息随时间序列推移而变得难以捕获的问题
  • 并且RNNs计算是通过时间步序列进行串行,而self Attention可以对一句话中的每个单词单独的进行 Attention 值的计算,也就是说可以并行计算

Masked Self Attention掩码自注意力机制

架构

masked-attention.png

相较于self Attention:在归一化和softMax之间多了一层Masked操作

Masked指的是:在做语言模型(如机器翻译,文本生成…)时,不给模型看到未来的信息

假设在Masked之前,我们已经得到了一个attentionMap:

image.png

Masked要做的就是,只保留下三角矩阵

如这个形式:

mask-attention-map.jpg

如何理解(?)

  • 对于单一个单词”I”
    • 只能和"I"自己有attention
  • 对于第二个单词”have”
    • 只能和"I","have"有attention
  • 对于第三个单词”a”
    • 只能和"I","have","a"有attention
  • 对于第四个单词”dream”
    • 只能和"I","have","a","dream"有attention

Masked后的attentionMap矩阵,经softMax处理后,横轴的attention weight之和为1

mask-attention-map-softmax.jpg

Attention(Q,K,V)计算公式变为:

$$
Attention(Q,K,V) = Z_i = softMax(\frac{s_{ij}}{\sqrt{d_K}}+Mask)\cdot V
$$

Multi-head Self Attention多头自注意力机制

回顾一下self Attention的整个流程:
$$
\begin{align}
对于序列X中第i个单词X_i的词向量x_i\ \ (i \ \in (0,n]):\
Q_i = x_i \cdot W_Q,
K_i = x_i \cdot W_K,
V_i = x_i \cdot W_V \
使用点乘计算相似度:\
s_{ij} = Q_i \cdot K_j\ \ \ (i,j\ \in [0,n]) \
s_{ij}’ = \frac{s_{ij}}{\sqrt{d_{K}}}\
A_i = softMax(S’) = \frac{exp(s’{ij})}{\sum{k=1}^{n}exp(s’_{ik})}\
Z_i = A_i \cdot V_i\
\end{align}
$$

所谓的多头,就是对于self Attention的加权求和得到的Z分割成n份 $Z_1,Z_2,\cdots,Z_n$

n通常取8

经过全连接层后,获得一个新的$Z’$

架构

multi-head-attention.png

$输入序列x通过与h个不同的W_Q,W_K,W_V进行线性变换,得到h个不同的Q,K,V,最后得到h个Z,拼接后与另一个矩阵W进行线性变化后,得到Z’$

流程
  • 定义多组W,生成多组Q,K,V
    $$
    \begin{align}
    Q_{ik} = x_i\cdot W_{k}^{Q}\
    K_{ik} = x_i\cdot W_{k}^{K}\
    V_{ik} = x_i\cdot W_{k}^{V}\
    k \in [0,h-1]
    \end{align}
    $$
  • 得到多组Attention(Q,K,V)
    $$
    \begin{align}
    s_{ik,jk} = Q_{ik}\cdot K_{jk} \
    A_{ik} = softMax(\frac{s_{ik,jk}}{\sqrt{d_{K}}})\
    Z_{ik} = A_{ik}\cdot V_{ik}\
    \end{align}
    $$
  • 多组输出拼接后,与W0进行线性变换,降低维度
    $$
    \begin{align}
    拼接:\
    Z_i = concat([Z_{i1},Z_{i2},\cdots,Z_{ih}])) \
    Z_i’ = W_0\cdot Z_i
    \end{align}
    $$

ccaf18c2ba751ab569c0621c6e376f3b.png

注意:除了第0个Encoder,其他Encoder都不需要进行word Embedding;它可以将前面一层Encoder的输出作为输入

为什么(?)

因为Transformer中Encoders是很多层堆叠在一起,第i层输入是第i-1层输出(i >= 1),此时第i层输入已经是向量形式了

Transformer

TransfomerSeq2Seq架构(换句话说是Encoder-Decoder结构)的一种模型实现

词元化Tokenization

把初始的输入文本变为Token

Tokenization这个概念其实与英语中把一个单词分为词根,前缀,后缀很类似

比如:

  • subword拆解成sub+word这样两个token
  • encoding拆解成encod+ing

image.png

不同的模型拆Token的方式不太一样

image.png

这里的词表就类似于字典

Tokenization的优点:有利于减小词表的数量(Token总数是小于文字总数的)

比如对于GPT-4的Token,10w个Token其实就表示了非常多语言的文字,如果只用字典,单单汉字就有好几万个

Embedding

将Token转换成向量,映射到一个新的数学空间,每一个Token都通过Embedding,映射成为新的数学空间中的一个点

位置编码Position embedding

前面的自注意力机制不是挺完美的吗,为什么还要引入位置编码呢

引入

$$
\begin{align}
对于self Attention的Q,K,V来说,都是由输入序列X=[x_1,x_2,\cdots,x_n]经过线性变换得到的\
最后计算Attention(Q,K,V)时,采用的是加权求和的方式\
也就是说,如果将X打乱,最终得到的Attention(Q,K,V)的值是不变的\
表明self\ Attention丢掉了X的序列信息
\end{align}
$$

为了解决这个问题,Transfomer的提出者提出了Positon Embedding,即对X进行Attention计算之前,在X的词向量中加入位置信息

最终X的词向量表示为
$$
X_{final\ embedding} = Embedding+Position\ Embedding
$$

位置编码

问题:为什么最终形式是三角函数(?)

一个好的位置编码需要满足的条件:

  • 值有界:位置编码的值必须有界,出现极端的大数值会导致训练不稳定。而且后面要跟语义向量相加,会喧宾夺主
  • 唯一:每个词的位置编码必须唯一,并且编码差异要够大,这样transformer才能更好地区分不同的位置

单调有界函数

其实是行不通的,单调有界函数,当自变量达到某个临界值,函数值的变化将非常缓慢;也就是说,相邻两个位置的编码值会十分接近,不利于Transformer进行区分

普通周期函数

也不行,因为过了周期T后,编码值就一样了,违背了唯一性条件

多维周期函数

例子

这个方法听上去很抽象,我们举个二进制表示数字的例子

0ff587c080874896843d2ad4642150aa.png

$$
\begin{align}
对于2^0位置,周期长度为2\
对于2^1位置,周期长度为4\
对于2^2位置,周期长度为8\
\end{align}
$$
每个维度都存在一个周期,并且这个周期是一个等比数列,并且对于每一个2进制序列,都唯一的表示一个数,不会出现编码重复的情况

细节

我们可以模仿二进制,将每个维度的频率(1/T)表示为一个等比数列

但是这样有一个问题,频率必然是小于1的,如果位置编码的维度d过大,那么最后几个维度的频率会变得非常非常低

因此我们需要根据位置编码的维度d对公比进行调整,d越大,公比越低

ps:这里的公比要理解成频率的下降速度

那么我们就可以得到第i个维度的频率公式:

$$
\omega_i = (\frac{1}{10000^{\frac{1}{d}}})^i \ \ \ \ \ (1\leq i\leq d)
$$

缺点:仍存在频率随维度上升下降太快的问题

改进:使用三角函数进行延缓

$$
\begin{align}
p(t,i) = sin((\frac{t}{10000^{\frac{1}{d}}})^i)\
t:词的位置index\
d:位置向量的维度\
i:位置向量的第i个维度\
\end{align}
$$

但是这样还是有个问题

我们以d=2的位置向量为例

$$
\begin{align}
假设已知一个词的位置t,我们想求它后面某个位置t+\Delta t的位置编码\
为了方便,下文将sin((\frac{t}{10000^{\frac{1}{d}}})^i)简写为sin(t)\
于是有位置编码(sin(t+\Delta t),sin(t+\Delta t))\
但是(sin(t+\Delta t),sin(t+\Delta t))好像并不能用(sin(t),sin(t))经线性变换得到\
也就是说,对于每一个新位置我们都需要从头计算
\end{align}
$$

如果能用当前位置的位置编码经线性变换就表示成新位置的位置编码就好了

$$
\begin{align}
如果位置编码变成(sin(t+\Delta t),cos(t+\Delta t))\
那么我们就可以用积化和差公式将其表示成M\cdot(sin(t),cos(t))\
\end{align}
$$

那么我们就可以得到位置编码的最终形式:

$$
\begin{align}
p(t,i) = \begin{cases}
sin(\frac{t}{10000^{\frac{2k}{d}}})\ \ \ if\ \ i=2k\
cos(\frac{t}{10000^{\frac{2k}{d}}})\ \ \ if\ \ i=2k+1\
\end{cases}\
t:词的位置index\
d:位置向量的维度\
i:位置向量的第i个维度\
\end{align}
$$

为什么频率的分母中的常数要选择10000呢(?)

  • 10000 是一个经验选择的常数,目的是为了确保位置编码的频率范围足够大,能够覆盖大多数自然语言任务中常见的序列长度

结构

整体结构
tf-整体框架.jpg

以机器翻译模型为例子

从黑盒角度上来说,就是输入一个序列,输出另一个序列

64ea0ab5066a470ca35ac850b57666ee.png

拆开这个黑盒,会发现是由编码组件,解码组件以及他们之间的连接组成的

80b2b08f3fb84357aa22e284729bc6e6.png

  • 编码部分是由多个Encoder组成的;将输入转换成词向量
  • 解码部分由相同数量个Decoder组成;得到编码器的输出词向量后,生成翻译结果

2eafa72a109544fcbc70268b42b1660d.png

核心部件

Encoders中的小的EncoderDecoders中的小的Decoder

前馈神经网络FFN

前馈神经网络FFN通常由两层全连接层组成:

$$
\begin{align}
对于输入x:\
FFN(x) = W_2\cdot (ReLU(W_1\cdot x+b_1))+b_2
\end{align}
$$

编码器Encoder

image.png

编码器的作用是将Embbeding处理后的词向量转换为新的向量(由于自注意力机制,包含更多语义信息),每个向量都表示当前输入词向量在整个序列中的语义关系,适合用于分析相似度

结构

image.png

对于单个Encoder来说,有两个子部件:自注意力self Attention与前馈神经网络FFN

而每个子部件传输过程中都要经历残差网络+归一化(Add & Normalize)这个过程:比如输入经self Attention计算后输出,这个输出需要经过残差网络+归一化(Add & Normalize)才能输入到FFN

解码器Decoder

解码器根据编码器输出的分析内容向量来生成预测回答(自回归,一个字一个字往外蹦)

解码器有两个输入:

  • 编码器生成的分析内容向量
  • 解码器输出的一部分内容(Output Probabilities处的概率对应的内容),右移(shifted right)后重新进行词嵌入和位置编码嵌入,再输入进解码器,与编码器的分析内容向量一起预测下一个字的输出

当解码器输出一个特殊的Token-结束符,下一次读入它时就会停止自回归

image.png

结构

image.png

这里的自注意力使用Masked Mult-Head self-Attention带掩码的多头自注意力机制

这样就使得解码器也具有理解复杂段落语义的能力;

也就是说,解码器其实是一个既能理解复杂段落语义,又可以自回归地一个字一个字地生成预测内容的部件

基于这个结论,事实上,新的大模型很多都采用了Decoder-Only的架构,将上下文对话Prompt和生成回答Response合并在一起,用特殊的Token进行分隔

image.png

Masked Multi-Head self-Attention 带掩码的多头自注意力机制

Masked运算与前面提到的相同,即对Attention Map矩阵的上三角部分进行一些操作,使得模型不能看到未来的信息

为什么(?)

因为生成下一个字的任务本身就是基于自己以及自己前面的内容;模型如果看到了未来的正确信息,就相当于抄答案了

Multi-Head Attention 多头注意力机制

用于对编码器输出的分析内容向量以及解码器经残差网络和归一化后的向量进行Attention运算

为什么这里不是自注意力机制了(?)

  • 因为数据不是同源的了

Transfomer中的Mask

padding mask

在编码器和解码器中都有padding mask,位置在softmax之前

编码器和解码器都有各自的输入,但是不同长度的输入会导致注意力分数计算存在偏差

为了消除这种偏差,我们引入padding mask

可以简单理解成:填充与截断

  • 对于较短的序列,我们需要填充一个值使得其达到预设的长度,同时这个值不影响全局概率值,因此我们应该选择负无穷作为填充值,这样一来其概率就为0
    $$
    \lim_{x->-∞}exp(x) = 0
    $$
  • 对于较长的序列,我们将其右侧多出部分截断舍弃

sequence mask

这个就是前文Mask self-Attention的掩码形式,对Attention Map的上三角矩阵进行操作(可以直接用0覆盖),使其只能基于当前自己内容及以前内容进行生成

Add & Normalize

image.png

Add & Normalize层分为Add和Norm两个部分

$$
\begin{align}
多头自注意力层到前馈神经网络层:\
LayerNorm(X+multiHeadSelfAttention(X))\
前馈神经网络后:\
LayerNorm(X+FFN(X))\
\end{align}
$$

为什么计算时都要加上一个X呢?这就是Add部分

Add

b9951837dd639046ca37c9fba8b4efc5.png

Z = Attention(Q,K,V)处加上一个残差块X,这样做的优点:

  • 避免梯度消失:
    • 对于多头自注意力机制。有很多组的$W_Q,W_K,W_V$
    • 如果可学习的矩阵值很小了,梯度计算再乘在一起,那么就会出现梯度非常非常小的梯度消失问题,引入残差块X后,就不至于梯度消失
  • 防止在深度神经网络的训练过程中发生退化的问题:
    • 退化指的是:网络层数增加,Loss下降,趋于稳定饱和,再增加网络层数,Loss反而增大
ResNet残差神经网络

df2011b93622dc7da054b8853b16bafd.png

残差连接主要就是防止神经网络退化

对于上图,X是输入值

$$
\begin{align}
第1层:X’= W_1\cdot X\
激活:F(X) = ReLU(X’) = ReLU(W_1\cdot X)\
第2层:X’’ =W_2\cdot F(X) = W_2\cdot ReLU(W_1\cdot X)\
引入残差:X’’’ = X’’+X\
激活:ReLU(X’’’) = ReLU(W_2\cdot ReLU(W_1\cdot X)+X)\
\end{align}
$$

Normalize

使用层归一化(Layer Normalization),也叫做横向归一化

为什么不用批归一化/纵向归一化?

前面我们提到的padding mask,如果使用纵向归一化会把填充值也计算进去

核心思想是:

  • 对神经网络中每一层的每个样本,按特征维度进行归一化;
  • 在其所有特征(或通道)上计算均值和标准差;然后,使用这些统计数据来规范化该样本的每个特征

计算公式

$$
\begin{align}
给定输入张量X \in R^{Batch_size\times Dim}\
均值:\mu = \frac{1}{D}\sum_{i=1}^{Dim}X[:,i]\
方差:\sigma = \frac{1}{D}\sum_{i=1}^{Dim}(X[:,i]-\mu)^2\
归一化:\hat{X[:,i]} = \frac{X[:,i]-\mu}{\sqrt{\sigma^2-\epsilon}}\
\epsilon:防止除零的小常数(如 1e−5)
\end{align}
$$