美文网首页
transformer编码层-Bert底层介绍

transformer编码层-Bert底层介绍

作者: 骆旺达 | 来源:发表于2021-01-03 15:08 被阅读0次

内容抄自:视频从中文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, 并应用到自己的需求上:

  1. transformer模型的直观认识;
  2. Positional encoding, 即位置嵌入(或位置编码);
  3. Self attention mechanism , 即自注意力机制注意力矩阵可视化;
  4. Layer normalization和残差连接.
  5. transformer encoder 整体结构.

二、transformer模型的直观认识

在深度学习中,我们常用的模型是LSTM(长短时记忆神经网络模型)。所以,我们首先聊一下transformer和LSTM的最大区别, 就是LSTM的训练是迭代的, 是一个字接一个字的来, 当前这个字过完LSTM单元后, 才可以进下一个字;而transformer的训练是并行了, 就是所有字是全部同时训练的, 这样就大大加快了计算效率, transformer使用了位置嵌入positional encoding来理解语言的顺序, 使用自注意力机制和全连接层来进行计算,使得模型能有效对文本重点表示进行学习和改进。

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

  1. 输入自然语言序列到编码器: Why do we work?(为什么要工作);
  2. 解析语言序列,编码器输出的隐藏层,再将其输入到解码器中;
  3. 输入起始符号“<start>”到解码器;
  4. 解码器输出得到第一个字"为";
  5. 将得到的第一个字"为"作为输入,给解码器;
  6. 得到第二个字"什";
  7. 将得到的第二字再作为输入, 直到解码器输出 终止符“<end>”, 即序列生成完成。
transformer模型架构

本文的内容限于编码器部分, 即把自然语言序列映射为隐藏层的数学表达的过程, 因为理解了编码器中的结构后, 理解解码器就非常简单了,最重要的是BERT预训练模型只用到了编码器的部分, 也就是先用编码器训练一个语言模型, 然后再把它适配给其他五花八门的任务。因为,我们用编码器就能够完成一些自然语言处理中比较主流的任务, 如情感分类, 语义关系分析, 命名实体识别等, 解码器的内容和序列到序列模型有机会我们会涉及到。

三、位置编码

为方便理解, 本文将transformer编码器(下图)的内容分做了1, 2, 3, 4个方框的序号,便于我们理解位置编码、自注意力、残差链接等编码器整体架构内容:


transformer编码器

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

PE_{(pos,2i)}=sin(pos/10000^{2i/d_{model}})
PE_{(pos,2i+1)}=cos(pos/10000^{2i/d_{model}})

上式中pos指的是句中字的位置, 取值范围是[0, \ max \ sequence \ Length), i指的是词向量的维度, 取值范围是[0, \ embedding \ dimension), 上面有sincos一组公式, 也就是对应着embedding \ dimension维度的一组奇数和偶数的序号的维度, 例如0, 1一组, 2, 3一组, 分别用上面的sincos函数做处理, 从而产生不同的周期性变化, 而位置嵌入在embedding \ dimension维度上随着维度序号增大, 周期变化会越来越慢, 而产生一种包含位置信息的纹理, 就像论文原文中第六页讲的, 位置嵌入函数的周期从2 \pi10000 * 2 \pi变化, 而每一个位置在embedding \ dimension维度上都会得到不同周期的sincos函数的取值组合, 从而产生独一的纹理位置信息, 模型从而学到位置之间的依赖关系和自然语言的时序特性。
下面画一下位置嵌入, 可见纵向观察, 随着embedding \ dimension增大, 位置嵌入函数呈现不同的周期变化。

其所绘制的图形如下所示,我们可以看到不同维度的跨度不同,随着维度增大,维度周期越大:


不同维度的位置编码,横坐标是维度,纵坐标是字段长度 不同维度的位置编码

四、自注意力self attention mechanism

我们现在有词向量矩阵和位置嵌入,假设我们现在有一些句子X,X的维度是[batch size,sequence length],首先,我们再字向量里查到对应词的嵌入,然后与位置嵌入元素相加,得到最终embedding的维度:
[batch size, sequence length, embedding dimension]
简单数学表达解释为:
X \in \Re^{batch size * seq.len}
X_{embedding} = EmbeddingLookup(X)+Positional Encoding
X_{embedding} \in \Re^{batch size * seq.len * embed.dim}

X: [batch_size, len, embedding_size]
W: [embedding_size, hidden_dimension]
XW = [batch_size, len, hidden_dimension]   # 消掉了embedding_size
多头注意力机制

下一步,为了学到多重含义的表达,对X_{embedding}做线性映射,也就是分配三个权重,W_Q,W_K,W_V \in \Re^{embed.dim * embed.dim}
线性映射之后,形成三个矩阵,为Q,K,V和线性变换之前的维度一致.

Q=Linear(X_{embedding})=X_{embedding}W_Q
K=Linear(X_{embedding})=X_{embedding}W_K
V=Linear(X_{embedding})=X_{embedding}W_V

下面,进行多头注意力机制,也就是multi head attention。
为什么是多头的呢?因为我们要用注意力机制来提取多重语义的含义,我们首先定义一个超参数是h,也就是head的数量,注意embedding dimension必须整除于h,因为我们要把embedding .dimension分割成h份。

从上图不难看出,我们把embedding .dimension分割成h份,也就是“头”的个数份;
分割后,Q,K,V的维度为[batch size, sequence length, h , embedding dimension/h]
之后,我们把Q,K,V中的sequence length,h进行一下转置,为了方便后续的计算:
转置后,Q,K,V的维度为[batch size, h ,sequence length,embedding dimension/h]

自注意力机制计算

上图中,我们拿出一组heads来解释一下muti head attention的含义。一组heads也就是一组分割后的Q,K,V,它们的维度都是[sequence Length, embedding Dimension/h],我们先计算QK的转置的点积。注意,上图它们各自的维度,我们还记得点积的集合意义,两个向量越相似(夹角越小),它们的点积就越大,否则就越小。

点积介绍

我们再这里首先用代表第一个字的c1行与c1列相乘,得到一个数值c1c1,它也就是位于注意力矩阵的第一行第一列的c1c1,这里的含义是第一个字与第一个字的注意力机制,然后依次求得c1c2,c1c3,...。

注意力矩阵的第一行就是指的是第一个字与这六个字(上图的max sequence length大小)的哪个比较相关,通过softmax获得对应的归一化概率:
Attention(Q,K,V)=softmax( \frac{QK^T}{ \sqrt{d_k}} )V

上式中,就是自注意力机制,我们先求QK^T,也就是求注意力矩阵,然后用注意力矩阵给V加权,\sqrt{d_k} 是为了把注意力矩阵变成标准正态分布,使得softmax归一化知乎的结果更加稳定,以便反向传播的时候获得平衡的梯度。

自注意力机制后,对V进行加权

目前,我们得到了注意力矩阵,并用softmax归一化,使得每个字跟其他所有字的注意力权重的和为1。注意力矩阵的作用就是一个注意力权重的概率分布,我们要用注意力矩阵的权重给V进行加权。上图中,我们从注意力矩阵去除一行(和为1),然后依次点乘V的列,矩阵V的每一行代表着每个字向量的数学表达。
我么上面的操作正是用注意力权重进行这些数学表达的加权线性组合,从而使每个字向量都含有当前句子内所有字向量的信息。注意,进行点乘运算后,V的维度没有变化,仍为[batch size, h ,sequence length,embedding dimension/h]

Attention Mask(注意力遮蔽)

Attention Mask

注意, 在上面self \ attention的计算过程中, 我们通常使用mini \ batch来计算, 也就是一次计算多句话, 也就是X的维度是[batch \ size, \ sequence \ length], sequence \ length是句长,而一个mini \ batch是由多个不等长的句子组成的, 我们就需要按照这个mini \ batch中最大的句长对剩余的句子进行补齐长度, 我们一般用0来进行填充, 这个过程叫做padding.

但这时在进行softmax的时候就会产生问题, 回顾softmax函数\sigma (\mathbf {z} )_{i}={\frac {e^{z_{i}}}{\sum _{j=1}^{K}e^{z_{j}}}}, e^0是1(即padding过程为补0,所以softmax时它为1), 是有值的, 这样的话softmax中被padding的部分就参与了运算, 就等于是让无效的部分参与了运算, 会产生很大隐患。这时就需要做一个mask让这些无效区域不参与运算, 我们一般给无效区域加一个很大的负数的偏置, 也就是:
z_{illegal} = z_{illegal} + bias_{illegal}
bias_{illegal} \to -\infty
e^{z_{illegal}} \to 0
经过上式的masking我们使无效区域经过softmax计算之后还几乎为0, 这样就避免了无效区域参与计算。

五. Layer Normalization和残差连接

3.1 ) 残差连接:
我们在上一步得到了经过注意力矩阵加权之后的V, 也就是Attention(Q, \ K, \ V), 我们对它进行一下转置, 使其和X_{embedding}的维度一致, 也就是[batch \ size, \ sequence \ length, \ embedding \ dimension], 然后把他们加起来做残差连接, 直接进行元素相加, 因为他们的维度一致:
X_{embedding} + Attention(Q, \ K, \ V)
在之后的运算里, 每经过一个模块的运算, 都要把运算之前的值和运算之后的值相加, 从而得到残差连接, 训练的时候可以使梯度直接走捷径反传到最初始层:
X + SubLayer(X)

3.2) LayerNorm:
Layer \ Normalization的作用是把神经网络中隐藏层归一为标准正态分布, 也就是i.i.d独立同分布, 以起到加快训练速度, 加速收敛的作用:
\mu_{i}=\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_{i}} {\sqrt{\sigma^{2}_{i}+\epsilon}} + \beta
然后用每一行每一个元素减去这行的均值, 再除以这行的标准差, 从而得到归一化后的数值, \epsilon是为了防止除0;
之后引入两个可训练参数\alpha, \ \beta来弥补归一化的过程中损失掉的信息, 注意\odot表示元素相乘而不是点积, 我们一般初始化\alpha为全1, 而\beta为全0.

normalization(目的是让模型收敛的更快,如下右的梯度图所示)

六. transformer encoder整体结构.

经过上面3个步骤(3,4,5节的位置编码,自注意力,残差和正规化等操作), 我们已经基本了解到来transformer编码器的主要构成部分, 我们下面用公式把一个transformer \ block的计算过程整理一下:

1). 字向量与位置编码:
X = EmbeddingLookup(X) + PositionalEncoding
X \in \mathbb{R}^{batch \ size \ * \ seq. \ len. \ * \ embed. \ dim.}

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} = Linear(Activate(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.}

encoder of transformer

七、小结

本文到现在的位置已经讲完了transformer的编码器的部分, 了解到了transformer是怎样获得自然语言的位置信息的, 注意力机制是怎样的, 其实举个语言情感分类的例子, 我们已经知道, 经过自注意力机制, 一句话中的每个字都含有这句话中其他所有字的信息, 那么我们可不可以添加一个空白字符到句子最前面, 然后让句子中的所有信息向这个空白字符汇总, 然后再映射成想要分的类别呢?
这就是BERT, 我们下次会讲到. 在BERT的预训练中, 我们给每句话的句头加一个特殊字符, 然后句末再加一个特殊字符, 之后模型预训练完毕之后, 我们就可以用句头的特殊字符的hidden \ state完成一些分类任务了.

相关文章

网友评论

      本文标题:transformer编码层-Bert底层介绍

      本文链接:https://www.haomeiwen.com/subject/szjuoktx.html