Transformer 细节理解

解决什么

Transformer 解决的是 Sequence-to-Sequence 问题,笼统地讲是从一个输入的序列推理一个结果序列,例如

  1. 从问句推理答句。
  2. 从以往温度推理未来温度。
  3. 从题面推理代码。

我想特别提一下第三条,毕竟我很感兴趣。在 2022 年 2 月 1 号,DeepMind 使用钞能力(最多 41B 个参数的网络……)基于 Transformer 训练了一个从算法题面推理代码的模型,在 Codeforces 能够达到约 1300 分的 Rank。虽然很低,但这项任务也确实太难了。具体信息可以搜索 AlphaCode

Transformer 在解决这个问题上使用了 Attention 机制中的 Self-Attention,采用 Encoder-Decoder 为主要结构。因为效果很好,所以现在有一种万物归为 Transformer 的节奏,不论什么问题都想来一发这个。

Attention

看着名字挺好,其实就是加权。你肯定见过加权。

GPA=ScoreiCreditiCrediti\text{GPA}=\frac{\sum \text{Score}_i \text{Credit}_i}{\sum \text{Credit}_i}

Attention 说白了就是这东西,三个矩阵 Q,K,VQ,K,V,按照 QQKK 的相似度,对 VV 的不同行加权求和。

Attention(Q,K,V)=Softmax(QKTd)V\text{Attention}(Q,K,V)=\text{Softmax}(\frac{QK^T}{\sqrt{d}})V

这东西最初的背景是在 KVK-V 的二元组集合内使用查询 QQ 寻找最匹配的 VV

Self-Attention

至于 Self-Attention,就是用线性变换从一个输入变换出 Q,K,VQ,K,V。至于这个变换中蕴藏着什么性质和意义,你大概不感兴趣。

Transformer 在做什么

始终记住 Transformer 是一个基于 Attention 的结构,无论 Self-Attention 与否,其做的事情始终在于三个矩阵 Q,K,VQ,K,V

Attention(Q,K,V)=Softmax(QKTd)V\text{Attention}(Q,K,V)=\text{Softmax}(\frac{QK^T}{\sqrt{d}})V

也就是说,Attention 最终输出的是 VV 的加权,Self-Attention 时,输出 VV 来自于输入本身。

然后来看 Transformer 的结构。

首先 Encoder 端的主体是一个标准的 Self-Attention,输入一个 N×FN \times FNN 为句长,FF 为 token 的特征长)输出即 N×FN \times F

在 Decoder 端,事情略有不同。来自 Encoder 的输入分别乘上两个矩阵转换为 Decoder 端的 K,VK,VQQ 则来自上一个时刻 Decoder 的输出进行一边 Self-Attention。随后三者进行一次 QQ 来自自身、K,VK,V 来自 Encoder 的 Attention 操作,输出一个与 QQ 等长的结果。也就是说在实际的 Test 过程,Decoder 每次吐出一个词,需要循环多次才能输出最终的结果。

数个细节问题

Token 和 Mask

此时就出现了数个问题:

  1. 如何得知一个句子已经结束?
  2. 因为 Decoder 的输出长度和 QQ 的长度一致,那么如何无中生有输出第一个单词?
  3. 进一步的,为了效率,我们总不能训练的时候每个 sample 都不停循环吧?

第一个问题引出的解决方案是添加特殊 token “EOS”(End of Sentence),这个 token 单纯的表示句子结束。如果 Decoder 吐出了这个 token,就结束循环。

第二个问题引出的解决方案仍然是特殊 token “SOS”(Start of Sentence),这个 token 表示句子开始。这样,第一次 QQ 到底扔什么进去就解决了。

第三个问题实际上是不存在的。考虑 Self-Attention 实际的运行过程,当 QQ 长度(指句长,也就是 QQ 的第一维)为 LL 的时候,我们实际上对特征执行了 LL 次查询,输出了一个长度为 LL 的结果,所以我们只需要训练最后一次循环即可。

例如有样例

1
2
1 1 4 5 1 4 翻译为
2 2 5 6 2 5

对输出添加两个特殊 token 之后,(用 S 代表 SOS,E代表 EOS)

1
S 2 2 5 6 2 5 E

最后一次循环喂进去的是

1
2
3
4
5
6
# Encoder 端输入
1 1 4 5 1 4
# Decoder 端输入
S 2 2 5 6 2 5
# 期望 Decoder 给出的答案
2 2 5 6 2 5 E

依照 Mask 的设置,(通常)Decoder 在查询第 ii 个位置时,ii 之后的输入因为被置为负无穷而被ReLU拦下所以不产生贡献。因此这实际上已经分别检查了每一步的预测是否正确。

所以能看到 Pytorch 中给出的 Transformer 实现中有数个 Mask。

  • src_mask
  • tgt_mask
  • memory_mask
  • src_key_padding_mask
  • tgt_key_padding_mask
  • memory_key_padding_mask

其中的 tgt_mask 就是用来在第 ii 次查询时屏蔽查询能提供的信息。例如对于一般 Q=4Q=4 的例子,即

1
2
3
4
0   -inf    -inf    -inf
0 0 -inf -inf
0 0 0 -inf
0 0 0 0

*_key_padding_mask 是做 padding 用的,解决同一个 batch 中句子不等长的问题。因为很多文章讲得很清楚,在 CV 中一般也没这种问题,就不再提及了。

满足交换的 Attention

Attention 有一个非常重要的特性,就是它这种先乘后加的计算满足交换律:它不关心你的输入顺序,[,A,,B,][\dots,A,\dots,B,\dots][,B,,A,][\dots,B,\dots,A,\dots] 的结果一致。然而很多任务中的输入有序,「我遛狗」和「狗溜我」显然有巨大的表述区别。当然,可能实际上差不多。分割成 patch 的图像同样也有着二维上的相对顺序关系。

为了解决这个问题,我们为 Transformer 加入位置编码,既有拿 sin、cos 复合的编码,也有自学的编码。问题至此就解决了,因为实验表现很好。

不过前段时间我看到一个问题,意识到其实引入位置编码本身也没能建模顺序关系。除了加法,Attention 的计算还有乘法,交换律使得位置编码在展开之后依然没有顺序。所以这种编码在实践中引入位置关系可能仍是依赖了网络的非线性性。

Learning Rate

论文中给出的建议为 [3×105,5×105][3 \times 10^5, 5 \times 10^5]。实践时发现这个建议离奇重要,小就进局部最优,大就爆炸。虽然对未知的原因感到很烦但是只能谨慎在这个区间内选择了。


Transformer 细节理解
https://blog.chenc.me/2021/11/21/details-in-transformer/
作者
CC
发布于
2021年11月21日
许可协议