Transformer是谷歌大脑在2017年底发表的论文attention is all you need中所提出的seq2seq模型。现在已经取得了大范围的应用和扩展,而BERT就是从Transformer中衍生出来的预训连语言模型
这篇文章分为以下几个部分
- Transformer直观认识
- Positional Encoding
- Self Attention Mechanism
- 残差连接和Layer Normalization
- Transformer Encoder整体结构
0. Transformer直观认识
Transformer和LSTM的最大区别,就是LSTM的训练是迭代的、串行的,必须要等以上一个字处理完,才可以处理下一个字。而Transformer的训练时并行的,即所有字是同时训练的,这样就大大增加了计算效率。Transformer使用了位置嵌入(Positional Encoding)来理解语言的顺序,使用自注意力机制(Self Attention Mechanism)和全连接层进行计算,这些后面会讲到
Transformer模型主要分为两大部分,分别是编码器和解码器。编码器负责把输入(语言序列)隐射成隐藏层(下图中第2步用九宫格代表的部分),然后解码器再把隐藏层映射为自然语言序列。例如下图机器翻译的例子
本篇文章内容仅限于编码器部分,即把自然语言序列映射为隐藏层的数学表达的过程。因为理解了编码器中的结构,再理解解码器就很简单了,最重要的是BERT也只用到了编码器的部分
上图为Transformer Block结构图,注意:下面的内容标题编号分别对应着图中1,2,3,4个方框的序号
1. Positional Encoding
由于Transformer模型没有循环神经网络的迭代操作, 所以我们必须提供每个字的位置信息给Transformer, 才能识别出语言中的顺序关系
现在定义一个位置嵌入的概念, 也就是Positional Encoding,位置嵌入的维度为[max sequence length, embedding dimension]
, 嵌入的维度其实与词向量的维度是相同的,max sequence length
属于超参数, 指的是限定的最大单个句长
注意,我们一般以字为单位训练Transformer模型,首先初始化字向量为[vocab size, embedding dimension]
,vocab size
为总共的字库数量,embedding dimension
为字向量的维度
论文中使用了sin和cos函数的线性变换来提供给模型位置信息:
上式中pos指的是一句话中某个字的位置,取值范围是[0, max sequence length),i指的是词向量的维度序号,取值范围是[0, embedding dimension),d_{text{model}}指的是embedding dimension的总维度
上面有sin和cos一组公式,也就是对应着embedding dimension维度的一组奇数和偶数的序号的维度,例如0, 1一组, 2, 3一组,分别用上面的sin和cos函数做处理,从而产生不同的周期性变化,而位置嵌入在embedding dimension维度上随着维度序号增大,周期变化会越来越慢,最终产生一种包含位置信息的纹理,就像论文原文中第六页讲的,位置嵌入函数的周期从2 pi到10000 * 2 pi变化,而每一个位置在embedding dimension维度上都会得到不同周期的sin和cos函数的取值组合,从而产生独一的纹理位置信息,从而使得模型学到位置之间的依赖关系和自然语言的时序特性
下面画一下位置嵌入,纵向观察,可见随着embedding dimension序号增大,位置嵌入函数呈现不同的周期变化
代码语言:javascript复制import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import math
def get_positional_encoding(max_seq_len, embed_dim):
# 初始化一个positional encoding
# embed_dim: 字嵌入的维度
# max_seq_len: 最大的序列长度
positional_encoding = np.array([
[pos / np.power(10000, 2 * i / embed_dim) for i in range(embed_dim)]
if pos != 0 else np.zeros(embed_dim) for pos in range(max_seq_len)])
positional_encoding[1:, 0::2] = np.sin(positional_encoding[1:, 0::2]) # dim 2i 偶数
positional_encoding[1:, 1::2] = np.cos(positional_encoding[1:, 1::2]) # dim 2i 1 奇数
return positional_encoding
positional_encoding = get_positional_encoding(max_seq_len=100, embed_dim=16)
plt.figure(figsize=(10,10))
sns.heatmap(positional_encoding)
plt.title("Sinusoidal Function")
plt.xlabel("hidden dimension")
plt.ylabel("sequence length")
代码语言:javascript复制plt.figure(figsize=(8, 5))
plt.plot(positional_encoding[1:, 1], label="dimension 1")
plt.plot(positional_encoding[1:, 2], label="dimension 2")
plt.plot(positional_encoding[1:, 3], label="dimension 3")
plt.legend()
plt.xlabel("Sequence length")
plt.ylabel("Period of Positional Encoding")
2. Self Attention Mechanism
Attention Mask
注意,在上面Self Attention的计算过程中,我们通常使用mini-batch来计算,也就是一次计算多句话,即X的维度是[batch_size, sequence length],sequence length是句长,而一个mini-batch是由多个不等长的句子组成的,我们需要按照这个mini-batch中最大的句长对剩余的句子进行补齐,一般用0进行填充,这个过程叫做padding
但这时在进行softmax就会产生问题。回顾softmax函数sigma(z)_i=frac{e^{z_i}}{sum_{j=1}^K e^{z_j}},e^0是1,是有值的,这样的话softmax中被padding的部分就参与了运算,相当于让无效的部分参与了运算,这可能会产生很大的隐患。因此需要做一个mask操作,让这些无效的区域不参与运算,一般是给无效区域加一个很大的负数偏置,即
$$ Z_{illegal}=Z_{illegal} bias_{illegal}\ bias_{illegal}→-∞ $$
3. 残差连接和Layer Normalization
残差连接
我们在上一步得到了经过注意力矩阵加权之后的V,也就是Attention(Q, K, V), 我们对它进行一下转置,使其和X_{embedding}的维度一致,也就是[batch size, sequence length, embedding dimension],然后把他们加起来做残差连接,直接进行元素相加,因为他们的维度一致:
在之后的运算里,每经过一个模块的运算,都要把运算之前的值和运算之后的值相加,从而得到残差连接,训练的时候可以使梯度直接走捷径反传到最初始层:
Layer Normalization
Layer Normalization的作用是把神经网络中隐藏层归一为标准正态分布,也就是i.i.d独立同分布,以起到加快训练速度,加速收敛的作用
上式中以矩阵的行(row)为单位求均值;
上式中以矩阵的行(row)为单位求方差
然后用每一行的每一个元素减去这行的均值,再除以这行的标准差**,从而得到归一化后的数值,epsilon是为了防止除0
之后引入两个可训练参数alpha,beta来弥补归一化的过程中损失掉的信息,注意odot表示元素相乘而不是点积,我们一般初始化alpha为全1,而beta为全0
4. Transformer Encoder整体结构
经过上面3个步骤,我们已经基本了解到来Transformer编码器的主要构成部分,我们下面用公式把一个transformer block的计算过程整理一下:
1). 字向量与位置编码
2). 自注意力机制
$$ Q = Linear(X) = XW_{Q}\ K = Linear(X) = XW_{K}\ V = Linear(X) = XW_{V}\ X_{attention} = SelfAttention(Q, K, V) $$
3). 残差连接与Layer Normalization
$$ X_{attention} = X X_{attention}\ X_{attention} = LayerNorm(X_{attention}) $$
4). 下面进行Transformer block结构图中的第4部分,也就是FeedForward,其实就是两层线性映射并用激活函数激活,比如说ReLU
5). 重复3).
$$ X_{hidden} = X_{attention} X_{hidden}\ X_{hidden} = LayerNorm(X_{hidden}) $$
其中,