概
Transformer.
主要内容
流程:
-
输出词句(source tokens) R S \mathbb{R}^S RS, 通过字典(nn.Embedding)得到相应的embeddings:
x i ∈ R D , i = 1 ⋯ , S , x_i \in \mathbb{R}^D, i=1\cdots, S, xi∈RD,i=1⋯,S,
由于是按照batch来计算的, 故整个可以输入可以有下列表示:
X ∈ R B × S × D . X \in \mathbb{R}^{B\times S \times D}. X∈RB×S×D.
注: pytorch里输入是(S, B, D). -
纯粹的attention不具备捕捉输入顺序的功能, 所以引入position embeddings:
p i , 2 j = sin ( i / 1000 0 2 j / D ) , p i , 2 j + 1 = cos ( i / 1000 0 2 j / D ) . p_{i, 2j} = \sin (i / 10000^{2j/D}), \: p_{i, 2j+1} = \cos (i / 10000^{2j/D}). pi,2j=sin(i/100002j/D),pi,2j+1=cos(i/100002j/D).x i = x i + p i . x_i = x_i + p_i. xi=xi+pi.
-
encoder部分, 总共有N个, 每个进行如下的操作:
multi-attention: 首先, 定义权重矩阵 W Q , W K , W V ∈ R D × D W^Q, W^K, W^V \in \mathbb{R}^{D\times D} WQ,WK,WV∈RD×D,
Q = X W Q , K = X W K , V = X W V , Q = XW^Q, \\ K = XW^K, \\ V = XW^V, Q=XWQ,K=XWK,V=XWV,
注: 这里的都是按batch的矩阵乘法(torch.matmul).接下来变形(假设有 H H H个heads)
( B , S , D ) → ( B , S , H × D / H ) → ( B , H , S , D / H ) . (B, S, D) \rightarrow (B, S, H \times D/H) \rightarrow (B, H, S, D/H). (B,S,D)→(B,S,H×D/H)→(B,H,S,D/H).
此时 Q , K , V ∈ R B × H × S × D / H Q, K, V\in \mathbb{R}^{B\times H \times S \times D/H} Q,K,V∈RB×H×S×D/H.接下来计算scores,
Z = Q K T ∈ R B × H × S × S , Z = QK^T \in \mathbb{R}^{B\times H \times S \times S}, Z=QKT∈RB×H×S×S,
注: 这里的 K T K^T KT实际上是key.transpose(-2, -1), 此矩阵乘法是按照最后两个维度进行的(torch.matmul(Q, K.transpose(-2, -1))).接下来对dim=-1进行softmax:
Z = S o f t m a x ( Z Q ) , Z =\mathrm{Softmax}(\frac{Z}{Q}), Z=Softmax(QZ),
一般的代码实现中是:
Z = D r o p o u t ( S o f t m a x ( Z Q ) ) , Z = \mathrm{Dropout}(\mathrm{Softmax}(\frac{Z}{Q})), Z=Dropout(Softmax(QZ)),
计算最后的结果
Z = Z V , Z = Z V, Z=ZV,
依旧是torch.matmul(Z, V)的意思, 再转成 Z ∈ R B × S × D Z \in \mathbb{R}^{B \times S \times D} Z∈RB×S×D, 最后outer projection, 根据 W D × D W^{D \times D} WD×D,
Z = Z W , Z = ZW, Z=ZW,
最后有个残差连接:
X = L a y e r N o r m ( X + Z ) , X = \mathrm{LayerNorm}(X + Z), X=LayerNorm(X+Z),
依旧实际中采用
X = L a y e r N o r m ( X + D r o p o u t ( Z ) ) . X = \mathrm{LayerNorm}(X + \mathrm{Dropout}(Z)). X=LayerNorm(X+Dropout(Z)).feed forward: 这部分就是简单的:
X = L a y e r N o r m ( X + R e L U ( X W 1 + b 1 ) W 2 + b 2 ) , X = \mathrm{LayerNorm}(X + \mathrm{ReLU}(XW_1 + b_1) W_2 + b2), X=LayerNorm(X+ReLU(XW1+b1)W2+b2),
在实际中加入dropout:
X = L a y e r N o r m ( X + D r o p o u t [ D r o p o u t [ R e L U ( X W 1 + b 1 ) ] W 2 + b 2 ] ) . X = \mathrm{LayerNorm}(X + \mathrm{Dropout}[\mathrm{Dropout}[\mathrm{ReLU}(XW_1 + b_1)] W_2 + b2]). X=LayerNorm(X+Dropout[Dropout[ReLU(XW1+b1)]W2+b2]). -
decoder部分, 同样由N个部件组成, 每个部件由self-attention, multi-attention 和 feed forward三部分组成, self-attention 和 feed forward 就是上面介绍的, multi-attention部分出入主要在于:
Q = Y W Q , K = X W K , V = X W V , Q = YW^Q, \\ K = XW^K, \\ V = XW^V, Q=YWQ,K=XWK,V=XWV,
这里用 Y ∈ R B × T × D Y \in \mathbb{R}^{B \times T \times D} Y∈RB×T×D指代target embeddings. 需要注意的 T , S T, S T,S即tokens的数量不一定一致, 但是矩阵乘法部分是没有问题的. -
output probabilities, 输出最后的概率:
P = s o f t m a x ( V W ) ∈ R B × T × N v o c , P = \mathrm{softmax}(VW) \in \mathbb{R}^{B \times T \times N_{voc}}, P=softmax(VW)∈RB×T×Nvoc,
这里 N v o c N_{voc} Nvoc是字典的长度.
一个很重要的问题是, source, target是什么? 这篇博文讲得很清楚, 这里复述一下. 举个例子, 翻译任务, “You are welcome.” -> “Da nada” 英语翻译成西班牙语, 那么 source = [‘You’, ‘are’, ‘welcome’, ‘pad’], target = [‘start’, ‘Da’, ‘nada’, ‘pad’], 预测的目标就是[‘Da’, ‘nada’].
在inference的时候, 是没有target的, 故流程如下:
- source = [‘You’, ‘are’, ‘welcome’, ‘pad’]通过encoder转成特征表示 f f f用于重复利用;
- target = [‘start’, ‘pad’], 输入decoder, 配合 f f f得到预测, 取第一个预测’Da’(假设如此);
- 将其加入target = [‘start’, ‘Da’, ‘pad’], 重复2, 得到预测[‘Da’, ‘nada’].
- 倘若还有后续, 便是重复上面的过程, 这是一种greedy的搜索方式.
问题: 那么为什么训练的时候不采取这种方式呢? 上面提到的那篇博文中, 提到这么做会导致训练困难且冗长, 但是我的感觉是, 这篇文章采取的是auto-agressive的逻辑, 所以每一个预测仅与它之前的词有关, 所以当已知target的时候, 重复上面的操作等价于直接传入整个target的预测. 因为在inference的时候, 只能一个一个来, 故比较恶心. 下面贴个上面博文的流程图, 感觉会清楚不少.
下面给出一些分析(多半是看别人的)
Positional Encoding
auto_regressive
注意到文章中有这么一句话:
At each step the model is auto-regressive [10], consuming the previously generated symbols as additional input when generating the next.
在代码中是通过mask实现的, 假设
p
p
p代表scores, 一般来说attention的输出就是
o
=
p
V
,
o = pV,
o=pV,
此时是不满足auto-regressive, 为了保证
o
o
o仅与
V
1
,
⋯
,
v
i
V_1, \cdots, v_i
V1,⋯,vi有关(假设此为第i个token), 只需
p
j
=
0
,
∀
j
>
i
.
p_j = 0, \forall j > i.
pj=0,∀j>i.
若
p
=
s
o
f
t
m
a
x
(
z
)
,
p = \mathrm{softmax}(z),
p=softmax(z),
只需
p
=
s
o
f
t
m
a
x
(
z
+
m
)
,
m
j
=
0
,
j
≤
i
,
m
j
=
−
∞
,
j
>
i
.
p = \mathrm{softmax}(z + m), \\ m_j = 0, j \le i, \quad m_j = -\infty, j > i.
p=softmax(z+m),mj=0,j≤i,mj=−∞,j>i.
这里
m
m
m即为mask.
实际上, 代码中还出现了pad_mask, 估计是tokens除了词以外还有别的类别和标签之类的符号, 这些不用于value部分就加上了.
当然mask是非强制性的.
额外的细节
注意到下面给出的代码中, 用于训练的标签smoothing的, 这个直觉上是对的, 毕竟替代词应该是不少的, 严格的one-hot不是好的主意.
代码
Pytorch 1.8 版本是有Transformer的实现的, 就是比较复杂, 感觉还是配合下面的比较容易理解: