内容抄自:视频从中文Transformer到BERT的模型精讲,以及基于BERT情感分类实战
代码文本来自:github讲解和内容介绍
一、简介
说到自然语言处理、语言模型、 命名实体识别、机器翻译,可能很多人想到的LSTM等循环神经网络, 但目前其实LSTM起码在自然语言处理领域已经过时了, 在Stanford阅读理解数据集(SQuAD2.0)榜单里, 机器的成绩已经超人类表现,这很大程度要归功于transformer的BERT预训练模型。今天我们来讲一下transformer模型,你不需要有很多深度学习和数学基础,我来用简单的语言和可视化的方法从零讲起。transformer是谷歌大脑在2017年底发表的论文attention is all you need中所提出的seq2seq模型. 现在已经取得了大范围的应用和扩展, 而BERT就是从transformer中衍生出来的预训练语言模型。
在我们开始之前, 允许我简单说一下目前自然语言处理领域的现状, 目前transformer模型已经得到广泛认可和应用, 而应用的方式主要是先进行预训练语言模型, 然后把预训练的模型适配给下游任务, 以完成各种不同的任务, 如分类, 生成, 标记(NER)等等, 预训练模型非常重要, 预训练的模型的性能直接影响下游任务的性能,我有信心让小伙伴们充分理解transformer并具备一定衍生模型的设计和编写能力。
本文主要从 transformer编码器(理论部分)的各个组成结构开始介绍,如下所示。为了让大家充分理解和初步使用transformer和训练BERT, 并应用到自己的需求上:
- transformer模型的直观认识;
- Positional encoding, 即位置嵌入(或位置编码);
- Self attention mechanism , 即自注意力机制与注意力矩阵可视化;
- Layer normalization和残差连接.
- transformer encoder 整体结构.
二、transformer模型的直观认识
在深度学习中,我们常用的模型是LSTM(长短时记忆神经网络模型)。所以,我们首先聊一下transformer和LSTM的最大区别, 就是LSTM的训练是迭代的, 是一个字接一个字的来, 当前这个字过完LSTM单元后, 才可以进下一个字;而transformer的训练是并行了, 就是所有字是全部同时训练的, 这样就大大加快了计算效率, transformer使用了位置嵌入positional encoding来理解语言的顺序, 使用自注意力机制和全连接层来进行计算,使得模型能有效对文本重点表示进行学习和改进。
如下图所示,transformer模型主要分为两大部分, 分别是编码器和解码器, 编码器负责把自然语言序列映射成为隐藏层(下图中第2步用九宫格比喻的部分), 它是含有自然语言序列的数学表达。 然后,解码器把隐藏层再映射为自然语言序列, 从而使我们可以解决各种问题, 如情感分类, 命名实体识别, 语义关系抽取, 摘要生成, 机器翻译等等, 下面我们简单阐述一下下图的每一步都做了什么:
- 输入自然语言序列到编码器: Why do we work?(为什么要工作);
- 解析语言序列,编码器输出的隐藏层,再将其输入到解码器中;
- 输入起始符号“
”到解码器; - 解码器输出得到第一个字"为";
- 将得到的第一个字"为"作为输入,给解码器;
- 得到第二个字"什";
- 将得到的第二字再作为输入, 直到解码器输出 终止符“
”, 即序列生成完成。
本文的内容限于编码器部分, 即把自然语言序列映射为隐藏层的数学表达的过程, 因为理解了编码器中的结构后, 理解解码器就非常简单了,最重要的是BERT预训练模型只用到了编码器的部分, 也就是先用编码器训练一个语言模型, 然后再把它适配给其他五花八门的任务。因为,我们用编码器就能够完成一些自然语言处理中比较主流的任务, 如情感分类, 语义关系分析, 命名实体识别等, 解码器的内容和序列到序列模型有机会我们会涉及到。
三、位置编码
为方便理解, 本文将transformer编码器(下图)的内容分做了1, 2, 3, 4个方框的序号,便于我们理解位置编码、自注意力、残差链接等编码器整体架构内容:
由于transformer模型没有循环神经网络(RNN)的迭代操作, 所以我们必须提供每个字的位置信息给transformer, 才能识别出语言中的顺序关系。
现在定义一个位置嵌入的概念, 也就是positional encoding, 位置嵌入的维度为[max sexquenceLength, embedding dimension], 嵌入的维度(embedding dimension)同词向量的维度(word embedding dimension), max sequenceLength属于超参数, 指的是限定的最大单个句长。
注意, transformer模型一般以“字”为单位训练, 也就是说我们不用分词了。 首先我们要初始化字向量为[vocab size, embedding dimension],其中,wocab size为总共的字库数量, embedding dimension为字向量的维度,也是每个字的数学表达。
在这里论文中使用了sin和cos函数的线性变换来提供模型的位置信息:
上式中指的是句中字的位置, 取值范围是, 指的是词向量的维度, 取值范围是, 上面有和一组公式, 也就是对应着维度的一组奇数和偶数的序号的维度, 例如一组, 一组, 分别用上面的和函数做处理, 从而产生不同的周期性变化, 而位置嵌入在维度上随着维度序号增大, 周期变化会越来越慢, 而产生一种包含位置信息的纹理, 就像论文原文中第六页讲的, 位置嵌入函数的周期从到变化, 而每一个位置在维度上都会得到不同周期的和函数的取值组合, 从而产生独一的纹理位置信息, 模型从而学到位置之间的依赖关系和自然语言的时序特性。
下面画一下位置嵌入, 可见纵向观察, 随着增大, 位置嵌入函数呈现不同的周期变化。
其所绘制的图形如下所示,我们可以看到不同维度的跨度不同,随着维度增大,维度周期越大:
四、自注意力self attention mechanism
我们现在有词向量矩阵和位置嵌入,假设我们现在有一些句子,的维度是,首先,我们再字向量里查到对应词的嵌入,然后与位置嵌入元素相加,得到最终embedding的维度:
简单数学表达解释为:
X: [batch_size, len, embedding_size]
W: [embedding_size, hidden_dimension]
XW = [batch_size, len, hidden_dimension] # 消掉了embedding_size
下一步,为了学到多重含义的表达,对做线性映射,也就是分配三个权重,
线性映射之后,形成三个矩阵,为和线性变换之前的维度一致.
下面,进行多头注意力机制,也就是multi head attention。
为什么是多头的呢?因为我们要用注意力机制来提取多重语义的含义,我们首先定义一个超参数是,也就是head的数量,注意embedding dimension必须整除于,因为我们要把分割成份。
从上图不难看出,我们把分割成份,也就是“头”的个数份;
分割后,的维度为
之后,我们把中的进行一下转置,为了方便后续的计算:
转置后,的维度为
上图中,我们拿出一组heads来解释一下muti head attention的含义。一组heads也就是一组分割后的,它们的维度都是,我们先计算与的转置的点积。注意,上图它们各自的维度,我们还记得点积的集合意义,两个向量越相似(夹角越小),它们的点积就越大,否则就越小。
我们再这里首先用代表第一个字的c1行与c1列相乘,得到一个数值c1c1,它也就是位于注意力矩阵的第一行第一列的c1c1,这里的含义是第一个字与第一个字的注意力机制,然后依次求得c1c2,c1c3,...。
注意力矩阵的第一行就是指的是第一个字与这六个字(上图的max sequence length大小)的哪个比较相关,通过softmax获得对应的归一化概率:
上式中,就是自注意力机制,我们先求,也就是求注意力矩阵,然后用注意力矩阵给V加权, 是为了把注意力矩阵变成标准正态分布,使得softmax归一化知乎的结果更加稳定,以便反向传播的时候获得平衡的梯度。
目前,我们得到了注意力矩阵,并用softmax归一化,使得每个字跟其他所有字的注意力权重的和为1。注意力矩阵的作用就是一个注意力权重的概率分布,我们要用注意力矩阵的权重给V进行加权。上图中,我们从注意力矩阵去除一行(和为1),然后依次点乘V的列,矩阵V的每一行代表着每个字向量的数学表达。
我么上面的操作正是用注意力权重进行这些数学表达的加权线性组合,从而使每个字向量都含有当前句子内所有字向量的信息。注意,进行点乘运算后,V的维度没有变化,仍为
Attention Mask(注意力遮蔽)
注意, 在上面的计算过程中, 我们通常使用来计算, 也就是一次计算多句话, 也就是的维度是, 是句长,而一个是由多个不等长的句子组成的, 我们就需要按照这个中最大的句长对剩余的句子进行补齐长度, 我们一般用来进行填充, 这个过程叫做.
但这时在进行的时候就会产生问题, 回顾函数, 是1(即padding过程为补0,所以softmax时它为1), 是有值的, 这样的话中被的部分就参与了运算, 就等于是让无效的部分参与了运算, 会产生很大隐患。这时就需要做一个让这些无效区域不参与运算, 我们一般给无效区域加一个很大的负数的偏置, 也就是:
经过上式的我们使无效区域经过计算之后还几乎为, 这样就避免了无效区域参与计算。
五. Layer Normalization和残差连接
3.1 ) 残差连接:
我们在上一步得到了经过注意力矩阵加权之后的, 也就是, 我们对它进行一下转置, 使其和的维度一致, 也就是, 然后把他们加起来做残差连接, 直接进行元素相加, 因为他们的维度一致:
在之后的运算里, 每经过一个模块的运算, 都要把运算之前的值和运算之后的值相加, 从而得到残差连接, 训练的时候可以使梯度直接走捷径反传到最初始层:
3.2) :
的作用是把神经网络中隐藏层归一为标准正态分布, 也就是独立同分布, 以起到加快训练速度, 加速收敛的作用:
上式中以矩阵的行为单位求均值;
上式中以矩阵的行为单位求方差;
然后用每一行的每一个元素减去这行的均值, 再除以这行的标准差, 从而得到归一化后的数值, 是为了防止除;
之后引入两个可训练参数来弥补归一化的过程中损失掉的信息, 注意表示元素相乘而不是点积, 我们一般初始化为全, 而为全.
六. transformer encoder整体结构.
经过上面3个步骤(3,4,5节的位置编码,自注意力,残差和正规化等操作), 我们已经基本了解到来编码器的主要构成部分, 我们下面用公式把一个的计算过程整理一下:
1). 字向量与位置编码:
2). 自注意力机制:
3). 残差连接与
4). 下面进行结构图中的第4部分, 也就是, 其实就是两层线性映射并用激活函数激活, 比如说:
5). 重复3).:
七、小结
本文到现在的位置已经讲完了transformer的编码器的部分, 了解到了transformer是怎样获得自然语言的位置信息的, 注意力机制是怎样的, 其实举个语言情感分类的例子, 我们已经知道, 经过自注意力机制, 一句话中的每个字都含有这句话中其他所有字的信息, 那么我们可不可以添加一个空白字符到句子最前面, 然后让句子中的所有信息向这个空白字符汇总, 然后再映射成想要分的类别呢?
这就是BERT, 我们下次会讲到. 在BERT的预训练中, 我们给每句话的句头加一个特殊字符, 然后句末再加一个特殊字符, 之后模型预训练完毕之后, 我们就可以用句头的特殊字符的完成一些分类任务了.