MENU

Transformer

April 25, 2020 • Read: 334 • Deep Learning

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步用九宫格代表的部分),然后解码器再把隐藏层映射为自然语言序列。例如下图机器翻译的例子

g

本篇文章内容仅限于编码器部分,即把自然语言序列映射为隐藏层的数学表达的过程。因为理解了编码器中的结构,再理解解码器就很简单了,最重要的是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$的总维度

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

下面画一下位置嵌入,纵向观察,可见随着$embedding \ dimension$序号增大,位置嵌入函数呈现不同的周期变化

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")

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操作,让这些无效的区域不参与运算,一般是给无效区域加一个很大的负数偏置,即

$$ \begin{align*} &Z_{illegal}=Z_{illegal}+bias_{illegal}\\ &bias_{illegal}→-∞ \end{align*} $$

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.} $$

Archives Tip
QR Code for this page
Tipping QR Code