1、self-attention机制
写在前面,transformers里的核心,self-attention机制:
Attention
(
Q
,
K
,
V
)
=
softmax
(
Q
K
T
d
k
)
V
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
Attention(Q,K,V)=softmax(dkQKT)V
(1)self-attention的本质是改变输入tokens的embedding,使其嵌入上下文信息
今天看了3Blue1Brown对attention的解读,以上公式可以理解为输入上下文的每个token计算得到的attention,就是变相原有embedding加上变化量,这里也就是根本本质:改变输入tokens的embedding。
先不管训练阶段,在推理阶段,本质是对输入Query的每个token进行结合上下文信息的embedding的改变。怎么理解这句话呢?——以下例说明
假设我们有一个简单的输入句子:“I love coding”,并且我们要通过自注意力机制来计算每个Token的嵌入向量。
-
初始化嵌入:
- 首先,我们将每个Token转换为初始的嵌入向量(假设这些嵌入向量是预训练的):
embedding_I = [0.1, 0.2]
embedding_love = [0.3, 0.4]
embedding_coding = [0.5, 0.6] embeddings = np.array([embedding_I, embedding_love, embedding_coding])
-
权重矩阵(固定的):
- 假设我们有固定的权重矩阵
Wq
、Wk
和Wv
:
- 假设我们有固定的权重矩阵
Wq = np.array([[0.1, 0.2], [0.3, 0.4]])
Wk = np.array([[0.5, 0.6], [0.7, 0.8]])
Wv = np.array([[0.9, 1.0], [1.1, 1.2]])
-
计算 Q、K、V:
- 使用固定的权重矩阵计算查询向量(Q)、键向量(K)和值向量(V):
Q = np.dot(embeddings, Wq)
K = np.dot(embeddings, Wk)
V = np.dot(embeddings, Wv)
-
计算注意力得分:
- 计算每个Token的查询向量(Q)与所有Token的键向量(K)的点积,得到注意力得分:```````
attention_scores = np.dot(Q, K.T)
#attention_scores = [[0.031, 0.071, 0.111], [0.077, 0.177, 0.277], [0.123, 0.283, 0.443]]
-
缩放和 softmax:
- 对注意力得分进行缩放和softmax操作,得到注意力权重:
d_k = K.shape[1]
scaled_attention_scores = attention_scores / np.sqrt(d_k)
# scaled_attention_scores = [[0.0219, 0.0502, 0.0785], [0.0545, 0.1253, 0.1960], [0.0870, 0.2003, 0.3136]]
attention_weights = softmax(scaled_attention_scores, axis=-1)
# attention_weights = [[0.298, 0.401, 0.301], [0.288, 0.423, 0.289], [0.280, 0.440, 0.280]]
-
计算注意力输出:
- 使用注意力权重对值向量(V)进行加权求和,得到更新后的嵌入向量:
attention_output = np.dot(attention_weights, V)
# attention_output = [[0.705, 0.787], [0.706, 0.788], [0.707, 0.790]]
小结:
- 初始嵌入向量(Embeddings) 是通过预训练词向量得到的。
- 固定的权重矩阵(Wq、Wk、Wv) 用于计算查询向量(Q)、键向量(K)和值向量(V)。
- 注意力得分和注意力权重 用于计算每个Token在当前上下文中的重要性。
- 注意力输出(Attention Output) 是通过对值向量(V)进行加权求和得到的,这个输出向量即是每个Token在当前上下文中的更新后的表示向量。
所以,在推理过程中,每个Token的嵌入向量会根据输入文本的不同而动态变化,这种变化是通过固定的权重矩阵和自注意力机制来实现的。虽然权重矩阵是固定的,但不同的输入会导致不同的注意力权重,从而产生不同的注意力输出(即更新后的嵌入向量)。
2、注意力与自注意力在编码器解码器中的不同体现(三种不同的注意力计算)
Encoder部分:Self-Attention:
Decoder部分:Cross-Attention以及Mask-Self-Attention:
注意力机制(Cross-Attention)用于解码器中,允许解码器在生成输出时关注编码器的输出。这意味着 Query 向量来自解码器,Key 和 Value 向量来自编码器。这种机制用于跨序列的注意力计算。
(a) 注意力和自注意力在模型的不同部分:
Encoder部分只有自注意力机制,关注输入序列本身tokens之间的上下文信息。但是Encoder部分输出后,Encoder部分输出的K和V就输入进Decoder部分进行注意力计算(Cross-Attention),此时的Q是来自解码器的输出,当然最开始是
<START>
\text{<START>}
<START>,后面随着预测词的不断输出,会逐渐把前一个预测词不断填入模型的outputs(shifted right)部分,而这里又有一个重要的点,对于预测词的输入,这里的Self-Attention是MASK的,可以简单理解为每次的预测值输入长度并没有符合设定的长度,所以需要mask后面的字符。可以理解为注意力在解码器的Cross-Attention部分是对输出序列(Q)以及在输入序列中做注意力操作;而自注意力是输入序列在自身做自注意力操作。
3、 从推理阶段理解transformers
在推理过程中,虽然模型的权重矩阵 Wq
、Wk
和 Wv
是固定的,但输入文本的每个Token的嵌入向量会根据输入的不同动态变化。具体来说,每个Token的最终嵌入向量是通过自注意力机制计算得到的,这个过程包括计算查询向量、键向量和值向量,并基于注意力权重对值向量进行加权求和。因此,即使权重矩阵不变,不同的输入文本仍然会产生不同的嵌入向量,从而捕捉不同的上下文信息。
①举例:在推理阶段,例如输入“x1、x2、x3”——从推理阶段理解transformers
模型会将“x1、x2、x3”嵌入为embedding,在encoder做self-attention部分,输出的K、V矩阵就会输入进decoder部分,但是最开始outputs(shifted right)部分是输入,作为attention之后的Q,结合encoder输出的K、V矩阵就能计算attention(cross-attention),层归一化和残差连接、前馈网络增强模型的表达能力、最后logits接全连接层再softmax将词库中概率最大的词输出。输出预测单词y1后,就继续把y1作为outputs(shifted right)部分输入,周而复始。
(4)从训练阶段理解transformers
训练阶段:假设输入是x1、x2、x3,真实标签Y1、Y2、Y3,transformers在训练时,是输入x1、x2、x3,decoder在outputs(shifted right)里从起始符开始,从结束符这样预测输出了y1,但是在预测y2时,decoder在outputs(shifted right)里的输入是真实标签Y1,使其输出y2,继续把真实标签Y2输入进去,输出y3…训练与推理阶段的区别就是放进outputs(shifted right)部分的是真实值还是预测值。
这个过程是怎么反向传播训练权重的?——从训练阶段理解transformers
(1)前向传播
假设输入序列为
{
x
1
,
x
2
,
x
3
}
\{x_1, x_2, x_3\}
{x1,x2,x3},真实标签序列为
{
Y
1
,
Y
2
,
Y
3
}
\{Y_1, Y_2, Y_3\}
{Y1,Y2,Y3}。训练过程包括以下步骤:
a. 编码器处理输入
编码器将输入序列
{
x
1
,
x
2
,
x
3
}
\{x_1, x_2, x_3\}
{x1,x2,x3} 转换为一组编码表示
{
E
1
,
E
2
,
E
3
}
\{E_1, E_2, E_3\}
{E1,E2,E3}:
假设输入序列为 { x 1 , x 2 , x 3 } \{x_1, x_2, x_3\} {x1,x2,x3},真实标签序列为 { Y 1 , Y 2 , Y 3 } \{Y_1, Y_2, Y_3\} {Y1,Y2,Y3}。训练过程包括以下步骤:
编码器将输入序列 { x 1 , x 2 , x 3 } \{x_1, x_2, x_3\} {x1,x2,x3} 转换为一组编码表示 { E 1 , E 2 , E 3 } \{E_1, E_2, E_3\} {E1,E2,E3}:
E
i
=
Encoder
(
x
i
)
E_i = \text{Encoder}(x_i)
Ei=Encoder(xi)
b. 解码器生成预测
解码器从开始符号
<START>
\text{<START>}
<START>开始,逐步生成预测:
-
步骤1:输入 <START> \text{<START>} <START> 预测 y ^ 1 \hat{y}_1 y^1:
y ^ 1 = Decoder ( <START> , E 1 , E 2 , E 3 ) \hat{y}_1 = \text{Decoder}(\text{<START>}, E_1, E_2, E_3) y^1=Decoder(<START>,E1,E2,E3)
-
步骤2:输入真实标签 Y 1 Y_1 Y1 预测 y ^ 2 \hat{y}_2 y^2:
y ^ 2 = Decoder ( Y 1 , E 1 , E 2 , E 3 ) \hat{y}_2 = \text{Decoder}(Y_1, E_1, E_2, E_3) y^2=Decoder(Y1,E1,E2,E3)
-
步骤3:输入真实标签 Y 2 Y_2 Y2 预测 y ^ 3 \hat{y}_3 y^3:
y ^ 3 = Decoder ( Y 2 , E 1 , E 2 , E 3 ) \hat{y}_3 = \text{Decoder}(Y_2, E_1, E_2, E_3) y^3=Decoder(Y2,E1,E2,E3)
(2)计算损失
损失函数衡量预测和真实标签之间的差异。通常使用交叉熵损失:
L i = − ∑ j Y i , j log ( y ^ i , j ) \mathcal{L}_i = -\sum_{j} Y_{i,j} \log(\hat{y}_{i,j}) Li=−j∑Yi,jlog(y^i,j)
其中 Y i , j Y_{i,j} Yi,j 是真实标签的概率分布, y ^ i , j \hat{y}_{i,j} y^i,j 是模型预测的概率分布。对于整个序列,损失函数是每个预测的损失之和:
L = ∑ i L i \mathcal{L} = \sum_{i} \mathcal{L}_i L=i∑Li
(3)反向传播
反向传播过程包括计算损失函数对模型参数的梯度,并更新这些参数。
a. 计算梯度
①计算每个位置的损失对预测的梯度:
对于预测 y ^ i \hat{y}_i y^i,损失对其的梯度是:
∂ L ∂ y ^ i \frac{\partial \mathcal{L}}{\partial \hat{y}_i} ∂y^i∂L
②梯度传递到解码器的输出层:
计算损失函数相对于解码器输出层权重的梯度:
∂ L ∂ W output = ∂ L ∂ y ^ i ⋅ ∂ y ^ i ∂ W output \frac{\partial \mathcal{L}}{\partial W_{\text{output}}} = \frac{\partial \mathcal{L}}{\partial \hat{y}_i} \cdot \frac{\partial \hat{y}_i}{\partial W_{\text{output}}} ∂Woutput∂L=∂y^i∂L⋅∂Woutput∂y^i
这里 W output W_{\text{output}} Woutput 是解码器输出层的权重矩阵。
③计算梯度传递到解码器的内部层:
使用链式法则,将梯度从输出层传递到解码器的各个层:
∂ L ∂ W decoder = ∑ i ∂ L ∂ y ^ i ⋅ ∂ y ^ i ∂ W decoder \frac{\partial \mathcal{L}}{\partial W_{\text{decoder}}} = \sum_{i} \frac{\partial \mathcal{L}}{\partial \hat{y}_i} \cdot \frac{\partial \hat{y}_i}{\partial W_{\text{decoder}}} ∂Wdecoder∂L=i∑∂y^i∂L⋅∂Wdecoder∂y^i
这里 W decoder W_{\text{decoder}} Wdecoder 是解码器内部层的权重矩阵。
④计算梯度传递到编码器的参数:
如果编码器的参数也在训练中,计算解码器输出相对于编码器输入的梯度:
∂
L
∂
W
encoder
=
∑
i
∂
L
∂
y
^
i
⋅
∂
y
^
i
∂
E
i
⋅
∂
E
i
∂
W
encoder
\frac{\partial \mathcal{L}}{\partial W_{\text{encoder}}} = \sum_{i} \frac{\partial \mathcal{L}}{\partial \hat{y}_i} \cdot \frac{\partial \hat{y}_i}{\partial E_i} \cdot \frac{\partial E_i}{\partial W_{\text{encoder}}}
∂Wencoder∂L=i∑∂y^i∂L⋅∂Ei∂y^i⋅∂Wencoder∂Ei
(4)更新参数
利用优化算法(如Adam、SGD等),根据计算出的梯度更新模型的参数:
W output ← W output − η ∂ L ∂ W output W_{\text{output}} \leftarrow W_{\text{output}} - \eta \frac{\partial \mathcal{L}}{\partial W_{\text{output}}} Woutput←Woutput−η∂Woutput∂L
W decoder ← W decoder − η ∂ L ∂ W decoder W_{\text{decoder}} \leftarrow W_{\text{decoder}} - \eta \frac{\partial \mathcal{L}}{\partial W_{\text{decoder}}} Wdecoder←Wdecoder−η∂Wdecoder∂L
W encoder ← W encoder − η ∂ L ∂ W encoder W_{\text{encoder}} \leftarrow W_{\text{encoder}} - \eta \frac{\partial \mathcal{L}}{\partial W_{\text{encoder}}} Wencoder←Wencoder−η∂Wencoder∂L
其中, η \eta η 是学习率。
4、Mask机制
在Mask-Self-Attention模块上,由于在训练阶段,每个输出Y只和Y前面时刻的输出做attention计算,由于看不到当前时刻后面的内容,所以需要做mask,其实并行性是核心关键,这里需要把每个时刻的Y的self-attention计算出来,为了方便操作,将各个时刻的attention计算的向量拼接成矩阵计算,如下图所示。
所以总结起来就是:mask是因为在训练阶段,由于真实标签Y是按预测输出顺序的输入进去作为Q,而这一块的attention的计算具备时序性,所以需要对还未输出的真实标签进行mask掩码。由于softmax的指数性质,在mask时采用接近负无穷大的值,这样计算softmax时probability趋近于0