Transformer前传:Seq2Seq与注意力机制Attention
前言
参考了以下大佬的博客
https://blog.csdn.net/v_july_v/article/details/127411638
https://blog.csdn.net/andy_shenzl/article/details/140146699
https://blog.csdn.net/weixin_42475060/article/details/121101749
https://blog.csdn.net/weixin_43334693/article/details/130189238
https://blog.csdn.net/weixin_42110638/article/details/134016569
https://www.cnblogs.com/nickchen121/p/16470569.html
非常感谢!
Seq2Seq
定义
Seq2Seq
是一个Encoder-Decoder
结构的网络,它的输入是一个序列,输出也是一个序列,
Encoder
使用循环神经网络(RNN,GRU,LSTM等
),将一个可变长度的信号序列(输入句子)变为固定维度的向量编码表达,
Decoder
使用循环神经网络(RNN,GRU,LSTM等
),将这个固定长度的编码变成可变长度的目标信号序列(生成目标语言)
图中的圆角矩形即为cell:可以是RNN,GRU,LSTM
等结构
- 相当于将
RNN
中的h0h_0h0输入Encoder
(将Decoder
部分理解成RNN
,Encoder
输出的Encoder state
作为h0h_0h0)
Seq2Seq
模型通过 端到端(?) 的训练方式,将输入序列和目标序列直接关联起来,避免了传统方法中繁琐的特征工程和手工设计的对齐步骤。这使得模型能够自动学习从输入到输出的映射关系,提高了序列转换任务的性能和效率
端到端(End-to-End Learning
)
如何理解这个端到端(End-to-End Learning
)?
输入序列到输出序列的全过程都由模型学习,我们不需要进行设计规则或特征
工作原理
Seq2Seq
中的编码器使用循环神经网络将输入序列转换为固定维度的上下文向量,而解码器使用这个向量和另一个循环神经网络逐步生成输出序列
编解码器
编码器Encoder
把一个不定长的输入序列x1,⋯ ,xtx_1,\cdots,x_tx1,⋯,xt输出到一个编码状态CCC
- 编码过程中,编码器会逐个读取输入序列的元素,并更新其内部隐藏状态
- 编码完成后,编码器将最后一个输出的隐藏状态或经过某种变换的隐藏状态作为上下文向量/编码状态传递给解码器
解码器Decoder
输出yty^tyt的条件概率将基于之前的输出序列y1,⋯ ,yt−1y^1,\cdots,y^{t-1}y1,⋯,yt−1和编码器输出的编码状态CCC
- 在每个时间步ttt上,解码器将根据上一个时间步的输出yt−1y^{t-1}yt−1,当前隐藏状态以及编码状态C编码状态C编码状态C来生成当前时间步输出yty^tyt
作用
对于给定的输入序列x1,⋯ ,xt,⋯ ,xTx_1,\cdots,x_t,\cdots,x_Tx1,⋯,xt,⋯,xT,使输出序列y1,⋯ ,yt′,⋯ ,yT′y^1,\cdots,y^{t'},\cdots,y^{T'}y1,⋯,yt′,⋯,yT′的条件概率值最大
就相当于求:
argmax(P(y1,⋯ ,⋯ ,yT′∣x1,⋯ ,xT))
\begin{align}
argmax(P(y^1,\cdots,\cdots,y^{T'}|x_1,\cdots,x_T))
\end{align}
argmax(P(y1,⋯,⋯,yT′∣x1,⋯,xT))
得到一组使条件概率最大的输出序列y1,⋯ ,yt′,⋯ ,yT′y^1,\cdots,y^{t'},\cdots,y^{T'}y1,⋯,yt′,⋯,yT′
最大似然估计
对于上面的做法,我们要使用最大似然估计来求出使得条件概率最大的输出序列y1,⋯ ,yt′,⋯ ,yT′y^1,\cdots,y^{t'},\cdots,y^{T'}y1,⋯,yt′,⋯,yT′
由于解码器的时间步输出值由前面的时间步输出以及当前时间步隐藏状态和编码状态C共同决定
最大似然估计为:
P(y1,⋯ ,⋯ ,yT′∣x1,⋯ ,xT)=∏t′=1T′P(yt′∣y1,⋯ ,yt′−1,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)
P(y1,⋯,⋯,yT′∣x1,⋯,xT)=t′=1∏T′P(yt′∣y1,⋯,yt′−1,C)
多个概率连乘,最后的值会很小,不利于存储,进行取对数操作
logP(y1,⋯ ,⋯ ,yT′∣x1,⋯ ,xT)=∑t′=1T′logP(yt′∣y1,⋯ ,yt′−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) logP(y1,⋯,⋯,yT′∣x1,⋯,xT)=t′=1∑T′logP(yt′∣y1,⋯,yt′−1,C)
转换为使每个yt′y^{t'}yt′的条件概率最大的问题,最终得到最优的输出序列
注意力机制Attention Mechanism
核心逻辑
从关注全部到关注重点
- Attention机制处理长文本时,能够抓住重点,不丢失重要信息
- Attention机制类似于人类看图片的逻辑,会将注意力放在图片的焦点上
怎么做注意力
例子
比如对于这张图片,红色部分是人们更关注的
人看这张图片的过程可以描述为:人(查询对象Q(Query)
)看这张图片(被查询对象V(Values)
),判断图片中哪些信息更重要,哪些信息更不重要(计算Q和V里事物的重要度(相关程度))
相似度计算
K (Key)
:与Q
匹配的键,通过计算Q
和K
的相似度来得到注意力权重
类比:
- Q:用户的搜索请求,比如搜索科幻电影
- K:所有电影标签
- V:电影本身内容
计算Q与K的相似度,得到权重向量,最终与V加权求和
方法
使用点乘的形式计算相似度(i=1,2,...,n)(i=1,2,...,n)(i=1,2,...,n)
f(Q,Ki)=QT⋅Ki \begin{align} f(Q,K_i) = Q^T\cdot K_i \end{align} f(Q,Ki)=QT⋅Ki
感知器
f(Q,Ki)=vTTanh(W⋅Q+U⋅Ki)v,W,U:可学习的参数矩阵
\begin{align}
f(Q,K_i) = v^TTanh(W\cdot Q+U \cdot K_i)\\
v,W,U:可学习的参数矩阵\\
\end{align}
f(Q,Ki)=vTTanh(W⋅Q+U⋅Ki)v,W,U:可学习的参数矩阵
权重点乘
f(Q,Ki)=Q⋅W⋅KiW:可学习的参数矩阵 \begin{align} f(Q,K_i) = Q\cdot W \cdot K_i \\ W:可学习的参数矩阵 \end{align} f(Q,Ki)=Q⋅W⋅KiW:可学习的参数矩阵
权重拼接
f(Q,Ki)=W[Q;Ki]将Q和K向量拼接在一起,通过可学习参数矩阵W做线性变化 \begin{align} f(Q,K_i) = W[Q;K_i] \\ 将Q和K向量拼接在一起,通过可学习参数矩阵W做线性变化 \end{align} f(Q,Ki)=W[Q;Ki]将Q和K向量拼接在一起,通过可学习参数矩阵W做线性变化
softMax+归一化
αi=softMax(f(Q,Ki)dk)dk:缩放因子,是Query向量和K向量的维度
\begin{align}
\alpha_i = softMax(\frac{f(Q,K_i)}{\sqrt{d_k}})\\
d_k:缩放因子,是Query向量和K向量的维度
\end{align}
αi=softMax(dkf(Q,Ki))dk:缩放因子,是Query向量和K向量的维度
为什么要除以一个dk\sqrt{d_k}dk(?)
假设Q,K的每个元素是独立随机变量,均值为0,方差为1,那么点乘操作之后,均值为0,每个元素的方差为Q和K的维度dkd_kdk
当dkd_kdk很大时,分布就会集中在绝对值大的区域,造成概率两极化(接近0/1),造成梯度消失
加权求和
针对计算出的αi,对V中所有的value进行加权求和,得到Attention向量:针对计算出的\alpha_i,对V中所有的value进行加权求和,得到Attention向量:针对计算出的αi,对V中所有的value进行加权求和,得到Attention向量:
Attention(Q,K,V)=∑i=1nαi⋅Vi=∑i=1nsoftMax(f(Q,Ki)dk)⋅Vi \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} Attention(Q,K,V)=i=1∑nαi⋅Vi=i=1∑nsoftMax(dkf(Q,Ki))⋅Vi
seq2seq with Attention
在传统的seq2seq
中有一个显著的缺点:
长句子问题
最初引入注意力机制是为了解决机器翻译中遇到的长句子(超过50字)性能下降问题
传统的机器翻译在长句子上的效果并不理想,本质原因是:
在Encoder-Decoder
结构中,Encoder会将输入序列中的所有词统一编码成一个语义特征C后,再进行解码;因此C中就包含了输入序列的所有信息,它的长度就成了限制性能的瓶颈。对于长句子,语义特征C可能并不能存下所有的信息,造成了效果不佳
结构
在最初的seq2seq
中,结构为
整个输入序列被编码为一个固定长度的编码状态C
而引入Attention机制
之后,整个输入序列不再被编码成一个定长的编码状态C
,而是编码成一个向量序列C=[C1,⋯ ,Cn]C=[C_1,\cdots,C_n]C=[C1,⋯,Cn]
核心在于:Ci根据输出生成单词yi不断变化C_i根据输出生成单词y_i不断变化Ci根据输出生成单词yi不断变化
从解码器输出的角度:
- 每个输出词y都会受到每个输入词x1,x2,x3,x4的影响,但是每个输入词对最终输出词y的影响权重不一样,这个权重由Attention计算得到
工作原理
此时Decoder
t时刻有3个输入: t-1时刻的隐藏状态输出和循环神经网络输出,以及Encoder
输出的context-vec
编码器Encoder
结构
双向循环神经网络
在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
(?)
就是分词后的单词在词库V中对应的唯一整数索引
- 将语料分词后的
- 特征提取
- 将前一步得到的词向量作为输入,传入
Encoder
的特征提取器-使用BiRNNs
- 将前一步得到的词向量作为输入,传入
- 状态输出
- 两个方向的RNNs都会产生隐藏状态输出,将这两个隐藏状态输出拼接,作为1个完整的隐藏状态输出h
解码器Decoder
结构
流程
- 隐藏状态
- 前一时刻的隐藏状态输出st−1s_{t-1}st−1
- 计算相似度score
- 计算前一时刻隐藏状态输出st−1s_{t-1}st−1与Encoder的第j步隐藏状态输出hjh_jhj的相似度score
- 计算注意力权重
- 将上一步的相似度score进行softMax归一化
计算上下文向量Context Vec
得到的hj与权重αij进行加权求和,得到上下文向量 得到的h_j与权重\alpha_{ij}进行加权求和,得到上下文向量 得到的hj与权重αij进行加权求和,得到上下文向量
公式
注意力分数计算
使用Decoder前一时刻隐藏状态st−1s_{t-1}st−1,Encoder中第j个时间步的隐藏状态hjh_jhj,进行计算得到注意力权重αij\alpha_{ij}αij
计算匹配得分
匹配得分eij表示Decoder在第i−1步的隐藏状态输出si−1,与Encoder在第j步的隐藏状态输出hj之间的匹配程度 匹配得分e_{ij}表示Decoder在第i-1步的隐藏状态输出s_{i-1},与Encoder在第j步的隐藏状态输出h_j之间的匹配程度 匹配得分eij表示Decoder在第i−1步的隐藏状态输出si−1,与Encoder在第j步的隐藏状态输出hj之间的匹配程度
- 方法1:感知器
eij=vT⋅Tanh(W⋅si−1+U⋅hj) \begin{align} e_{ij} = v^T\cdot Tanh(W\cdot s_{i-1} + U\cdot h_j) \end{align} eij=vT⋅Tanh(W⋅si−1+U⋅hj) - 方法2:使用二次型矩阵计算
二次型:一个向量和矩阵的乘积
eij=si−1⋅Wi−1,j⋅hj e_{ij} = s_{i-1}\cdot W_{i-1,j} \cdot h_j eij=si−1⋅Wi−1,j⋅hj
计算权重 αij\alpha_{ij}αij:
使用softMax
函数进行归一化
αij=eij∑i=1neik \alpha_{ij} = \frac{e_{ij}}{\sum_{i=1}^{n}e_{ik}} αij=∑i=1neikeij
计算上下文向量 Context Vec
Ci=∑j=1nαij⋅hjn:序列长度Ci:解码步骤i的上下文向量αij:输入序列中第j个词对解码步骤i的注意力权重hj:编码器输出的第j个词的隐藏状态 \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} Ci=j=1∑nαij⋅hjn:序列长度Ci:解码步骤i的上下文向量αij:输入序列中第j个词对解码步骤i的注意力权重hj:编码器输出的第j个词的隐藏状态
比如对于"我 爱 机器 学习" -> "I love machine learning"这个翻译过程
对于i=1,就是要计算 "I"
与 "我","爱","机器","学习"
每个时刻输入的相关性
类别
自注意力机制Self Attention
Self-Attention的关键点:Q,K,V
都是来源于同一个序列X
,通过X
找到X
里面的关键点
架构
Self-Attention
有3个输入 Q,K,V
:
这三个输入来源于同一个输入序列X
的词向量x
的线性变换
定义三个可学习的参数矩阵:WQ,WK,WVW_Q,W_K,W_VWQ,WK,WV
Q,K,V
定义如下:
Q=x⋅WQK=x⋅WKV=x⋅WV
\begin{align}
Q = x \cdot W_Q\\
K = x\cdot W_K \\
V = x\cdot W_V
\end{align}
Q=x⋅WQK=x⋅WKV=x⋅WV
流程
先以向量为例子:
-
得到初始的
Q,K,V
对于序列X中第i个单词Xi的词向量xi (i ∈(0,n]):Qi=xi⋅WQKi=xi⋅WKVi=xi⋅WV \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} 对于序列X中第i个单词Xi的词向量xi (i ∈(0,n]):Qi=xi⋅WQKi=xi⋅WKVi=xi⋅WV
-
计算当前查询Qi与其他Kj(包括当前Qi对应的Ki)的相似度计算当前查询Q_i与其他K_j(包括当前Q_i对应的K_i)的相似度计算当前查询Qi与其他Kj(包括当前Qi对应的Ki)的相似度
使用点乘计算相似度:sij=Qi⋅Kj (i,j ∈[0,n]) \begin{align} 使用点乘计算相似度:\\ s_{ij} = Q_i \cdot K_j\ \ \ (i,j\ \in [0,n]) \end{align} 使用点乘计算相似度:sij=Qi⋅Kj (i,j ∈[0,n])
-
归一化+softMax
需要对前面得到的sij进行归一化:除以缩放因子dKdK:K的维度sij′=sijdKAi=softMax(S′)=exp(sij′)∑k=1nexp(sik′) \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} 需要对前面得到的sij进行归一化:除以缩放因子dKdK:K的维度sij′=dKsijAi=softMax(S′)=∑k=1nexp(sik′)exp(sij′)
- 加权求和
Zi=Ai⋅Vi \begin{align} Z_i = A_i \cdot V_i\\ \end{align} Zi=Ai⋅Vi
以矩阵形式:
对于这个softMax
:
Qi⋅KiT是一个word2word的attention Map经过softMax后:矩阵内每个元素和为1,相当于每个元素之间都有对应的权重
\begin{align}
Q_i\cdot K_i^T是一个word2word的attention\ Map \\
经过softMax后:\\
矩阵内每个元素和为1,相当于每个元素之间都有对应的权重
\end{align}
Qi⋅KiT是一个word2word的attention Map经过softMax后:矩阵内每个元素和为1,相当于每个元素之间都有对应的权重
假设输入"I have a dream"
:
形成了一张4×4的注意力图,每个单词与单词之间都有对应的权重
与普通Attention机制的区别
- 普通Attention:
- 在Encoder-Decoder模型中,查询向量Q是Decoder的输出,K和V位于Encoder
- self Attention:
- 在Encoder-Decoder模型中,Q,K,V都是Encoder中的元素
与RNNs相比
- self Attention 将任意两个单词通过一个计算步骤Qi⋅KjQ_i\cdot K_jQi⋅Kj动态联系在一起,不会存在RNNs中有效信息随时间序列推移而变得难以捕获的问题
- 并且RNNs计算是通过时间步序列进行串行,而self Attention可以对一句话中的每个单词单独的进行 Attention 值的计算,也就是说可以并行计算
Masked Self Attention掩码自注意力机制
架构
相较于self Attention
:在归一化和softMax
之间多了一层Masked
操作
Masked
指的是:在做语言模型(如机器翻译,文本生成…)时,不给模型看到未来的信息
假设在Masked
之前,我们已经得到了一个attentionMap:
Masked
要做的就是,只保留下三角矩阵
如这个形式:
如何理解(?)
- 对于单一个单词"I"
- 只能和
"I"
自己有attention
- 只能和
- 对于第二个单词"have"
- 只能和
"I","have"
有attention
- 只能和
- 对于第三个单词"a"
- 只能和
"I","have","a"
有attention
- 只能和
- 对于第四个单词"dream"
- 只能和
"I","have","a","dream"
有attention
- 只能和
Masked
后的attentionMap
矩阵,经softMax
处理后,横轴的attention weight
之和为1
Attention(Q,K,V)
计算公式变为:
Attention(Q,K,V)=Zi=softMax(sijdK+Mask)⋅V Attention(Q,K,V) = Z_i = softMax(\frac{s_{ij}}{\sqrt{d_K}}+Mask)\cdot V Attention(Q,K,V)=Zi=softMax(dKsij+Mask)⋅V
Multi-head Self Attention多头自注意力机制
回顾一下self Attention
的整个流程:
对于序列X中第i个单词Xi的词向量xi (i ∈(0,n]):Qi=xi⋅WQ,Ki=xi⋅WK,Vi=xi⋅WV使用点乘计算相似度:sij=Qi⋅Kj (i,j ∈[0,n])sij′=sijdKAi=softMax(S′)=exp(sij′)∑k=1nexp(sik′)Zi=Ai⋅Vi
\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}
对于序列X中第i个单词Xi的词向量xi (i ∈(0,n]):Qi=xi⋅WQ,Ki=xi⋅WK,Vi=xi⋅WV使用点乘计算相似度:sij=Qi⋅Kj (i,j ∈[0,n])sij′=dKsijAi=softMax(S′)=∑k=1nexp(sik′)exp(sij′)Zi=Ai⋅Vi
所谓的多头,就是对于self Attention的加权求和得到的Z分割成n份 Z1,Z2,⋯ ,ZnZ_1,Z_2,\cdots,Z_nZ1,Z2,⋯,Zn
n通常取8
经过全连接层后,获得一个新的Z′Z'Z′
架构
输入序列x通过与h个不同的WQ,WK,WV进行线性变换,得到h个不同的Q,K,V,最后得到h个Z,拼接后与另一个矩阵W进行线性变化后,得到Z′输入序列x通过与h个不同的W_Q,W_K,W_V进行线性变换,得到h个不同的Q,K,V,最后得到h个Z,拼接后与另一个矩阵W进行线性变化后,得到Z'输入序列x通过与h个不同的WQ,WK,WV进行线性变换,得到h个不同的Q,K,V,最后得到h个Z,拼接后与另一个矩阵W进行线性变化后,得到Z′
流程
- 定义多组W,生成多组Q,K,V
Qik=xi⋅WkQKik=xi⋅WkKVik=xi⋅WkVk∈[0,h−1] \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} Qik=xi⋅WkQKik=xi⋅WkKVik=xi⋅WkVk∈[0,h−1] - 得到多组Attention(Q,K,V)
sik,jk=Qik⋅KjkAik=softMax(sik,jkdK)Zik=Aik⋅Vik \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} sik,jk=Qik⋅KjkAik=softMax(dKsik,jk)Zik=Aik⋅Vik - 多组输出拼接后,与W0进行线性变换,降低维度
拼接:Zi=concat([Zi1,Zi2,⋯ ,Zih]))Zi′=W0⋅Zi \begin{align} 拼接:\\ Z_i = concat([Z_{i1},Z_{i2},\cdots,Z_{ih}])) \\ Z_i' = W_0\cdot Z_i \end{align} 拼接:Zi=concat([Zi1,Zi2,⋯,Zih]))Zi′=W0⋅Zi
注意:除了第0个Encoder
,其他Encoder
都不需要进行word Embedding
;它可以将前面一层Encoder
的输出作为输入
为什么(?)
因为还没学到Transformer,所以暂时回答不上来qaq;
学完Transformer的架构后再解答