Bert前篇:手把手带你详解Transformer原理

技术

前言本文将从最直观的感受以及内部公式计算,详细描述了transformer,文章较长,请耐心阅读与理解,你将会收获很多。

说到自然语言处理, 语言模型, 命名实体识别, 机器翻译, 可能很多人想到的LSTM等循环神经网络, 但目前其实LSTM起码在自然语言处理领域已经过时了, 在Stanford阅读理解数据集(SQuAD2.0)榜单里, 机器的成绩已经超人类表现, 这很大程度要归功于transformer的BERT预训练模型. 今天我们来讲一下transformer模型, 你不需要有很多深度学习和数学基础, 我来用简单的语言和可视化的方法从零讲起.

transformer是谷歌大脑在2017年底发表的论文attention is all you need中所提出的seq2seq模型. 现在已经取得了大范围的应用和扩展, 而BERT就是从transformer中衍生出来的预训练语言模型.

在我们开始之前, 允许我简单说一下目前自然语言处理领域的现状, 目前transformer模型已经得到广泛认可和应用, 而应用的方式主要是先进行预训练语言模型, 然后把预训练的模型适配给下游任务, 以完成各种不同的任务, 如分类, 生成, 标记等等, 预训练模型非常重要, 预训练的模型的性能直接影响下游任务的性能。

为了让大家充分理解和初步使用transformer和训练BERT, 并应用到自己的需求上,将从以下几个方面讲解:

transformer编码器(理论部分):

  1. 模型的直觉, 建立直观认识;
  2. , 即位置嵌入(或位置编码);
  3. , 即自注意力机制与注意力矩阵可视化;
  4. 和残差连接.
  5. 整体结构.

0.模型的直觉, 建立直观认识

首先来说一下transformer和LSTM的最大区别, 就是LSTM的训练是迭代的, 是一个接一个字的来, 当前这个字过完LSTM单元, 才可以进下一个字, 而transformer的训练是并行了, 就是所有字是全部同时训练的, 这样就大大加快了计算效率, transformer使用了位置嵌入来理解语言的顺序, 使用自注意力机制和全连接层来进行计算, 这些后面都会详细讲解.

transformer模型主要分为两大部分, 分别是编码器和解码器 , 编码器负责把自然语言序列映射成为 隐藏层 (下图中第2步用九宫格比喻的部分), 含有自然语言序列的数学表达. 然后解码器把隐藏层再映射为自然语言序列, 从而使我们可以解决各种问题, 如情感分类, 命名实体识别, 语义关系抽取, 摘要生成, 机器翻译等等, 下面我们简单说一下下图的每一步都做了什么:

  1. 输入自然语言序列到编码器: Why do we work?(为什么要工作);
  2. 编码器输出的隐藏层, 再输入到解码器;
  3. 输入(起始)符号到解码器;
  4. 得到第一个字"为";
  5. 将得到的第一个字"为"落下来再输入到解码器;
  6. 得到第二个字"什";
  7. 将得到的第二字再落下来, 直到解码器输出(终止符), 即序列生成完成.

picture.image本节内容限于编码器部分, 即把自然语言序列映射为隐藏层的数学表达的过程, 因为理解了编码器中的结构, 理解解码器就非常简单了, 最重要的是BERT预训练模型只用到了编码器的部分, 也就是先用编码器训练一个语言模型, 然后再把它适配给其他五花八门的任务(其实这就是Bert).

而且我们用编码器就能够完成一些自然语言处理中比较主流的任务, 如情感分类, 语义关系分析, 命名实体识别等, 解码器的内容和序列到序列模型有机会我们会涉及到.

Transformer Block结构图, 注意: 为方便查看, 下面的内容分别对应着上图第1, 2, 3, 4个方框的序号:

picture.image1., 即位置嵌入(或位置编码)

由于transformer模型没有循环神经网络的迭代操作, 所以我们必须 提供每个字的位置信息给transformer, 才能识别出语言中的顺序关系.

现在定义一个位置嵌入的概念, 也就是, 位置嵌入的维度为, 嵌入的维度同词向量的维度, 属于超参数, 指的是限定的最大单个句长.

注意, 我们一般以字为单位训练transformer模型, 也就是说我们不用分词了, 首先我们要初始化字向量为, 为总共的字库数量, 为字向量的维度, 也是每个字的数学表达.

在这里论文中 使用了和函数的线性变换来提供给模型位置信息 :

上式中指的是句中字的位置, 取值范围是, 指的是词向量的维度, 取值范围是;

上面有和一组公式, 也就是对应着维度的一组奇数和偶数的序号的维度, 例如一组, 一组, 分别用上面的和函数做处理, 从而产生不同的周期性变化;

而位置嵌入在维度上随着维度序号增大, 周期变化会越来越慢, 而 产生一种包含位置信息的纹理 ;

就像论文原文中第六页讲的, 位置嵌入函数的周期从到变化 , 而每一个位置在维度上都会得到不同周期的和函数的取值组合, 从而产生独一的纹理位置信息, 模型从而学到位置之间的依赖关系和自然语言的时序特性.

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

  
# 导入依赖库  
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 奇数  
    # 归一化, 用位置嵌入的每一行除以它的模长  
    # denominator = np.sqrt(np.sum(position\_enc**2, axis=1, keepdims=True))  
    # position\_enc = position\_enc / (denominator + 1e-8)  
    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")  

picture.image

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

picture.image2., 自注意力机制

picture.image注意, 在上面的计算过程中, 我们通常使用来计算, 也就是一次计算多句话, 也就是的维度是, 是句长, 而一个是由多个不等长的句子组成的, 我们就需要按照这个中最大的句长对剩余的句子进行补齐长度, 我们一般 用来进行填充, 这个过程叫做 .

但这时在进行的时候就会产生问题:

回顾函数

,

是1, 是有值的, 这样的话中被的部分就参与了运算, 就等于是让无效的部分参与了运算, 会产生很大隐患, 这时就需要做一个让这些无效区域不参与运算, 我们一般给无效区域加一个很大的负数的偏置, 也就是:经过上式的我们使无效区域经过计算之后还几乎为, 这样就避免了无效区域参与计算.

3.和残差连接

1). 残差连接:

我们在上一步得到了经过注意力矩阵加权之后的, 也就是, 我们对它进行一下转置, 使其和的维度一致, 也就是, 然后把他们加起来做残差连接, 直接进行元素相加, 因为他们的维度一致:

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

2). :

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

上式中以矩阵的行为单位求均值:

上式中以矩阵的行为单位求方差:

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

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

4.整体结构

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

1). 字向量与位置编码:

2). 自注意力机制:

X_{attention} = SelfAttention(Q, \ K, \ V) \tag3). 残差连接与

4). 下面进行结构图中的第4部分, 也就是, 其实就是两层线性映射并用激活函数激活, 比如说:

5). 重复3).:

小结:

我们到现在位置已经讲完了transformer的编码器的部分, 了解到了transformer是怎样获得自然语言的位置信息的, 注意力机制是怎样的, 其实举个语言情感分类的例子, 我们已经知道, 经过自注意力机制, 一句话中的每个字都含有这句话中其他所有字的信息, 那么我们可不可以添加一个空白字符到句子最前面, 然后让句子中的所有信息向这个空白字符汇总, 然后再映射成想要分的类别呢? 这就是BERT, 后面我们将会详细说到bert应用的几个场景任务中。

在BERT的预训练中, 我们给每句话的句头加一个特殊字符, 然后句末再加一个特殊字符, 之后模型预训练完毕之后, 我们就可以用句头的特殊字符的完成一些分类任务了.

原文链接点击阅读原文获取更多内容

扫码加我好友进微信群|QQ群

picture.image

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
银行业全渠道智能营销白皮书
总结百家银行实践经验,如何将营销数智化落到实处?
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论