Transformer细节解读
共 2902字,需浏览 6分钟
·
2021-04-02 23:48
本系列将对Transformer的关键细节进行解读,尽可能用数学语言阐述,要读懂本系列的读者,只需具备基础的线性代数知识即可。
self-attention
attention定义如公式(1)所示:
当 时,就是所谓的“self-attention”。在主流深度学习框架中,只要求 。
不妨设 ,这里 表示词序列 的长度, 表示词序列 的长度, 表示每个词向量的维度,那么公式(1)中的 。首先将 展开来写:
其中, ,表示单个词向量。接下来对 进行展开:
其中, 表示向量的内积,用来衡量向量 和 向量的相似度, 越大则表示 向量 和 向量 越相似。 表示 按行 依次对 中每行所在位置的元素进行 操作,即:
是单增函数,即 越大, 越大。
最后对 展开:
公式(5)中任一行向量 表明, 和 越相似, 的系数越大,即 在结果行向量 贡献的比例越大。换另一种角度理解, 建模了词序列 中各个词向量 和词向量 的相关关系。
softmax padding mask
由于词序列是不定长的,所以需要在后面进行零填充固定到统一长度,因此才会出现所谓的"padding mask"。
对 分析(这里还没有进行softmax操作)。假定 ,即零填充向量,那么 。由于 表示向量 和 向量的相似度。往往假定,零填充向量和向量 是最不相关的。因此,在进行softmax操作前,我们需要将 ,那么 。
常规的做法
假设词序列 的padding mask矩阵为 词序列 的padding mask矩阵为 记 ,先将 取反,然后乘以 ,其次加上 ,最后进行 操作。
快速的做法
由于后面还要做query padding mask,所以词序列 的padding mask矩阵是怎么样的,在这里没有任何关系,不妨假定为全 矩阵。换句话说,只需要知道词序列 的padding mask矩阵 ,直接对 取反,然后乘以 ,其次加上 ,最后进行 操作即可。
query padding mask
query padding mask只需: 。但实际上,Transformer是不需要每次都做query padding mask,因为Transformer中的各个词向量是独立操作的,互不干扰了,即论文中的“并行”,所以只需在输出之前的最后一步做query padding mask即可。
关于 缩放因子
缩放因子是为了防止 不至于过大或过小(即正值更小,负值更大),那么 做 就比较平均。
MHSA
不妨设 ,SA如公式(1)所示:
MHSA则分别先对 进行线性升维操作(即全连接操作,参数不共享),输出 ,然后按“头”做 次,最后把结果拼接起来。如公式(2)所示:
其中,
说白了,MHSA就是分别先把 进行线性升维,然后对每个“头”并行做 ,总共 个“头”,最后把结果拼接起来。
时间复杂度分析
假定单个矩阵的运算使用常规做法,那么纯 的时间复杂度如公式(4)所示:
MHSA的时间复杂度如公式(5)所示:
公式(5)中的 表示MHSA做线性升维并行的开销时间, 表示做多“头”并行的开销时间。
假定忽略并行开销的时间,且 ,那么有公式(6):
公式(6)表明MHSA总可以通过调节 来获得比 更少的时间复杂度。通常, ,因此总有: 。
优势在哪里?
假定 ,那么MHSA既能用更少的时间复杂度,又能获得更好的性能。因为高维信息总是不劣于低维信息,比如:低维空间不可分的问题,高维空间可分;低维空间很难优化的,高维空间更容易优化,所以神经网络的向量维度都要逐步上升的。如果 ,MHSA的性能会低于SA,因为“先分后聚”的做法,还是会有损性能的。
但是仔细一想,不管是线性升维还是并行,都需要更多的内存开销,因此MHSA也属于用空间复杂度换时间复杂度的例子吧。
总结来说,MHSA的优势:
“分治的思想,充分利用显卡的并行性,通过空间复杂度换取时间复杂度。如果 ,那么还能有更好的性能”
值得注意的是,并行在移动终端和嵌入设备,表现得不好~
通常来说,基于Transformer的神经网络模型只在两处做padding mask,一处是在多头注意力中做softmax padding mask(当然,有多个Transformer就有多个softmax padding mask),另一处是loss前的最后一步做padding mask。
为什么Transformer只在多头注意力中做softmax padding mask?其他层呢?不是说padding mask很重要吗?
不急,一步一步来回答。列举一下Transformer的组件:多头注意力层、全连接层、Dropout层、激活函数层、LN层和add层。
首先,Dropout层、激活函数层和add层对词序列中的各个词向量是独立操作的,显然成立,那么本应被零填充的词向量是什么,无关紧要,因为不会影响其他非零填充向量,所以只需在loss前的最后一步做padding mask。
其次,全连接层对词序列中的各个词向量也是独立操作的,因为全连接层会给每个词向量都独立分配一个向量权重和偏置参数,也是易得(有兴趣的读者,自己可以推导一下),那么本应被零填充的词向量是什么,无关紧要,因为不会影响其他非零填充向量的参数训练,所以也只需在loss前的最后一步做padding mask。
最后,就只剩下LN层了。大部分深度学习框架都是沿着词向量维度做标准化,然后参数也是沿着词向量维度。换句话说,所有词向量共享一个 向量参数和一个 向量参数。那么本应零填充却没有零填充,会不会影响共享向量参数的更新呢?
LN层的独立
答案是不一定会。要看场景,比如:A、B、C共用一台电脑,C每次使用电脑,都恢复电脑原样,那么在A和B看来,电脑只被A和B共用,因为C不会改变电脑。
对应到神经网络中,“C每次使用电脑,都恢复电脑原样”对应只在loss前的最后一步做padding mask,“C不会改变电脑”对应零填充向量不改变共享参数的更新。
那么,只在loss前的最后一步做padding mask,零填充向量是否会改变共享参数的更新,即是否参与共享参数的梯度计算呢?答案是不会。这里笔者就直接把最后的结果写出来,推导还是比较繁琐的,具体可以参考BN的反向传播推导:
其中, 表示词序列, 表示 mask向量,所在位置为0,那么 该位置为零填充。由公式(1)表示,只需在loss前的最后一步做padding mask,那么零填充不会参与共享参数梯度的计算。当然,公式(1)不是严谨的推导,只是大概表达了零填充向量不参与共享参数的梯度计算。
结论
共享,不等价于一定不能相互独立。要看场景。就好比,对所有进程,CPU是共享的,但当某进程使用时,就是独占CPU的,所以感觉就是,每个进程独立使用一个CPU。