Transformer

2020-04-26 14:18:32 浏览数 (1)

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函数的线性变换来提供给模型位置信息:

PE{(pos,2i)} = sin(pos / 10000^{2i/d_{text{model}}}) \ PE{(pos,2i 1)} = cos(pos / 10000^{2i/d_{text{model}}})

上式中pos指的是一句话中某个字的位置,取值范围是[0, max sequence length)i指的是词向量的维度序号,取值范围是[0, embedding dimension)d_{text{model}}指的是embedding dimension的总维度

上面有sincos一组公式,也就是对应着embedding dimension维度的一组奇数和偶数的序号的维度,例如0, 1一组, 2, 3一组,分别用上面的sincos函数做处理,从而产生不同的周期性变化,而位置嵌入在embedding dimension维度上随着维度序号增大,周期变化会越来越慢,最终产生一种包含位置信息的纹理,就像论文原文中第六页讲的,位置嵌入函数的周期从2 pi10000 * 2 pi变化,而每一个位置在embedding dimension维度上都会得到不同周期的sincos函数的取值组合,从而产生独一的纹理位置信息,从而使得模型学到位置之间的依赖关系和自然语言的时序特性

下面画一下位置嵌入,纵向观察,可见随着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]​,然后把他们加起来做残差连接,直接进行元素相加,因为他们的维度一致:

X_{embedding} Attention(Q, K, V)

在之后的运算里,每经过一个模块的运算,都要把运算之前的值和运算之后的值相加,从而得到残差连接,训练的时候可以使梯度直接走捷径反传到最初始层:

X SubLayer(X)
Layer Normalization

Layer Normalization的作用是把神经网络中隐藏层归一为标准正态分布,也就是i.i.d独立同分布,以起到加快训练速度,加速收敛的作用

mu_{j}=frac{1}{m} sum^{m}_{i=1}x_{ij}

上式中以矩阵的行(row)为单位求均值;

sigma^{2}_{j}=frac{1}{m} sum^{m}_{i=1}(x_{ij}-mu_{j})^{2}

上式中以矩阵的行(row)为单位求方差

LayerNorm(x)=alpha odot frac{x_{ij}-mu_{j}}{sqrt{sigma^{2}_{j} epsilon}} beta

然后用每一行的每一个元素减去这行的均值,再除以这行的标准差**,从而得到归一化后的数值,epsilon是为了防止除0

之后引入两个可训练参数alpha,beta来弥补归一化的过程中损失掉的信息,注意odot表示元素相乘而不是点积,我们一般初始化alpha为全1,而beta为全0

4. Transformer Encoder整体结构

经过上面3个步骤,我们已经基本了解到来Transformer​编码器的主要构成部分,我们下面用公式把一个​transformer block​的计算过程整理一下:

1). 字向量与位置编码

X = EmbeddingLookup(X) PositionalEncoding

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

X_{hidden} = Activate(Linear(Linear(X_{attention})))

5). 重复3).

$$ X_{hidden} = X_{attention} X_{hidden}\ X_{hidden} = LayerNorm(X_{hidden}) $$

其中,

X_{hidden} in mathbb{R}^{batch size * seq. len. * embed. dim.}

0 人点赞