转自:https://www.bilibili.com/video/BV1Mt411J734
https://github.com/aespresso/a_journey_into_math_of_ml
说到自然语言处理, 语言模型, 命名实体识别, 机器翻译, 可能很多人想到的LSTM等循环神经网络, 但目前其实LSTM起码在自然语言处理领域已经过时了, 在Stanford阅读理解数据集(SQuAD2.0)榜单里, 机器的成绩已经超人类表现, 这很大程度要归功于transformer的BERT预训练模型.
transformer是谷歌大脑在2017年底发表的论文attention is all you need中所提出的seq2seq模型. 现在已经取得了大范围的应用和扩展, 而BERT就是从transformer中衍生出来的预训练语言模型.
在我们开始之前, 允许我简单说一下目前自然语言处理领域的现状, 目前transformer模型已经得到广泛认可和应用, 而应用的方式主要是先进行预训练语言模型, 然后把预训练的模型适配给下游任务, 以完成各种不同的任务, 如分类, 生成, 标记等等, 预训练模型非常重要, 预训练的模型的性能直接影响下游任务的性能, 通过我制作的这几期视频, 我有信心让小伙伴们充分理解transformer并具备一定衍生模型的设计和编写能力.
首先来说一下transformer和LSTM的最大区别, 就是LSTM的训练是迭代的, 是一个接一个字的来, 当前这个字过完LSTM单元, 才可以进下一个字, 而transformer的训练是并行的, 就是所有字是全部同时训练的, 这样就大大加快了计算效率, transformer使用了位置嵌入 ( ) 来理解语言的顺序, 使用自注意力机制和全连接层来进行计算, 这些后面都会详细讲解.
transformer模型主要分为两大部分, 分别是编码器和解码器, 编码器负责把自然语言序列映射成为隐藏层(下图中第2步用九宫格比喻的部分), 含有自然语言序列的数学表达. 然后解码器把隐藏层再映射为自然语言序列, 从而使我们可以解决各种问题, 如情感分类, 命名实体识别, 语义关系抽取, 摘要生成, 机器翻译等等, 下面我们以机器翻译为例简单说一下下图的每一步都做了什么:
我们这节课的内容限于编码器部分, 即把自然语言序列映射为隐藏层的数学表达的过程, 因为理解了编码器中的结构, 理解解码器就非常简单了,最重要的是BERT预训练模型只用到了编码器的部分, 也就是先用编码器训练一个语言模型, 然后再把它适配给其他五花八门的任务.
如果你不知道语言模型是什么, 没关系, 这丝毫不影响本节课内容的理解, 下次我们讲BERT的时候会讲.
而且我们用编码器就能够完成一些自然语言处理中比较主流的任务, 如情感分类, 语义关系分析, 命名实体识别等, 解码器的内容和序列到序列模型有机会我们会涉及到.
编码器的输入X是一个自然语言序列,它的维度是[batch size,sequence length],batch size是一次训练句子的个数,sequence length是句子的长度。它通过查阅字向量表或者其他方式得到每个字embedding dimension维的嵌入向量。
上面是一个transformer block,实际网络中可以堆叠多个block。
我们通过编码器输出的 X h i d d e n X_{hidden} Xhidden就是隐藏层,下面详细介绍每个模块。
由于transformer模型没有循环神经网络的迭代操作, 所以我们必须提供每个字的位置信息给transformer, 才能识别出语言中的顺序关系.
现在定义一个位置嵌入的概念, 也就是 p o s i t i o n a l e n c o d i n g positional \ encoding positional encoding, 位置嵌入的维度为 [ m a x s e q u e n c e l e n g t h , e m b e d d i n g d i m e n s i o n ] [max \ sequence \ length, \ embedding \ dimension] [max sequence length, embedding dimension], 嵌入的维度和词向量的维度相同,所以我们计算出位置嵌入直接和词向量按位相加即可。 m a x s e q u e n c e l e n g t h max \ sequence \ length max sequence length属于超参数, 指的是限定的最大单个句长.
注意, 我们一般以字为单位训练transformer模型, 也就是说我们不用分词了, 首先我们要初始化字向量为 [ v o c a b s i z e , e m b e d d i n g d i m e n s i o n ] [vocab \ size, \ embedding \ dimension] [vocab size, embedding dimension], v o c a b s i z e vocab \ size vocab size为总共的字库数量, e m b e d d i n g d i m e n s i o n embedding \ dimension embedding dimension为字向量的维度, 也是每个字的数学表达.
在这里论文中使用了 s i n sin sin和 c o s cos cos函数的线性变换来提供给模型位置信息:
P E ( p o s , 2 i ) = s i n ( p o s / 1000 0 2 i / d model ) P E ( p o s , 2 i + 1 ) = c o s ( p o s / 1000 0 2 i / d model ) (eq.1) PE_{(pos,2i)} = sin(pos / 10000^{2i/d_{\text{model}}}) \quad PE_{(pos,2i+1)} = cos(pos / 10000^{2i/d_{\text{model}}})\tag{eq.1} PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel)(eq.1)
上式中 p o s pos pos指的是句中字的位置, 取值范围是 [ 0 , m a x s e q u e n c e l e n g t h ) [0, \ max \ sequence \ length) [0, max sequence length), i i i指的是词向量的维度, 取值范围是 [ 0 , e m b e d d i n g d i m e n s i o n ) [0, \ embedding \ dimension) [0, embedding dimension), 上面有 s i n sin sin和 c o s cos cos一组公式, 也就是对应着 e m b e d d i n g d i m e n s i o n embedding \ dimension embedding dimension维度的一组奇数和偶数的序号的维度, 例如 0 , 1 0, 1 0,1一组, 2 , 3 2, 3 2,3一组, 分别用上面的 s i n sin sin和 c o s cos cos函数做处理, 从而产生不同的周期性变化, 而位置嵌入在 e m b e d d i n g d i m e n s i o n embedding \ dimension embedding dimension维度上随着维度序号增大, 周期变化会越来越慢, 而产生一种包含位置信息的纹理, 就像论文原文中第六页讲的, 位置嵌入函数的周期从 2 π 2 \pi 2π到 10000 ∗ 2 π 10000 * 2 \pi 10000∗2π变化, 而每一个位置在 e m b e d d i n g d i m e n s i o n embedding \ dimension embedding dimension维度上都会得到不同周期的 s i n sin sin和 c o s cos cos函数的取值组合, 从而产生独一的纹理位置信息, 模型从而学到位置之间的依赖关系和自然语言的时序特性.
下面画一下位置嵌入, 可见纵向观察, 随着 e m b e d d i n g d i m e n s i o n embedding \ dimension embedding dimension增大, 位置嵌入函数呈现不同的周期变化.
Attention Mask
注意, 在上面 s e l f a t t e n t i o n self \ attention self attention的计算过程中, 我们通常使用 m i n i b a t c h mini \ batch mini batch来计算, 也就是一次计算多句话, 也就是 X X X的维度是 [ b a t c h s i z e , s e q u e n c e l e n g t h ] [batch \ size, \ sequence \ length] [batch size, sequence length], s e q u e n c e l e n g t h sequence \ length sequence length是句长, 而一个 m i n i b a t c h mini \ batch mini batch是由多个不等长的句子组成的, 我们就需要按照这个 m i n i b a t c h mini \ batch mini batch中最大的句长对剩余的句子进行补齐长度, 我们一般用 0 0 0来进行填充, 这个过程叫做 p a d d i n g padding padding.
但这时在进行 s o f t m a x softmax softmax的时候就会产生问题, 回顾 s o f t m a x softmax softmax函数 σ ( z ) i = e z i ∑ j = 1 K e z j \sigma (\mathbf {z} )_{i}={\frac {e^{z_{i}}}{\sum _{j=1}^{K}e^{z_{j}}}} σ(z)i=∑j=1Kezjezi, e 0 e^0 e0是1, 是有值的, 这样的话 s o f t m a x softmax softmax中被 p a d d i n g padding padding的部分就参与了运算, 就等于是让无效的部分参与了运算, 会产生很大隐患, 这时就需要做一个 m a s k mask mask让这些无效区域不参与运算, 我们一般给无效区域加一个很大的负数的偏置, 也就是:
z i l l e g a l = z i l l e g a l + b i a s i l l e g a l z_{illegal} = z_{illegal} + bias_{illegal} zillegal=zillegal+biasillegal
b i a s i l l e g a l → − ∞ bias_{illegal} \to -\infty biasillegal→−∞
e z i l l e g a l → 0 e^{z_{illegal}} \to 0 ezillegal→0
经过上式的 m a s k i n g masking masking我们使无效区域经过 s o f t m a x softmax softmax计算之后还几乎为 0 0 0, 这样就避免了无效区域参与计算.
1). 残差连接:
我们在上一步得到了经过注意力矩阵加权之后的 V V V, 也就是 A t t e n t i o n ( Q , K , V ) Attention(Q, \ K, \ V) Attention(Q, K, V), 我们对它进行一下转置, 使其和 X e m b e d d i n g X_{embedding} Xembedding的维度一致, 也就是 [ b a t c h s i z e , s e q u e n c e l e n g t h , e m b e d d i n g d i m e n s i o n ] [batch \ size, \ sequence \ length, \ embedding \ dimension] [batch size, sequence length, embedding dimension], 然后把他们加起来做残差连接, 直接进行元素相加, 因为他们的维度一致:
X e m b e d d i n g + A t t e n t i o n ( Q , K , V ) X_{embedding} + Attention(Q, \ K, \ V) Xembedding+Attention(Q, K, V)
在之后的运算里, 每经过一个模块的运算, 都要把运算之前的值和运算之后的值相加, 从而得到残差连接, 训练的时候可以使梯度直接走捷径反传到最初始层:
X + S u b L a y e r ( X ) (eq. 5) X + SubLayer(X) \tag{eq. 5} X+SubLayer(X)(eq. 5)
2). LayerNorm:
L a y e r N o r m a l i z a t i o n Layer Normalization LayerNormalization的作用是把神经网络中隐藏层归一为标准正态分布, 也就是 i . i . d i.i.d i.i.d独立同分布, 以起到加快训练速度, 加速收敛的作用:
μ i = 1 m ∑ i = 1 m x i j \mu_{i}=\frac{1}{m} \sum^{m}_{i=1}x_{ij} μi=m1i=1∑mxij
上式中以矩阵的行 ( r o w ) (row) (row)为单位求均值;
σ j 2 = 1 m ∑ i = 1 m ( x i j − μ j ) 2 \sigma^{2}_{j}=\frac{1}{m} \sum^{m}_{i=1} (x_{ij}-\mu_{j})^{2} σj2=m1i=1∑m(xij−μj)2
上式中以矩阵的行 ( r o w ) (row) (row)为单位求方差;
L a y e r N o r m ( x ) = α ⊙ x i j − μ i σ i 2 + ϵ + β (eq.6) LayerNorm(x)=\alpha \odot \frac{x_{ij}-\mu_{i}} {\sqrt{\sigma^{2}_{i}+\epsilon}} + \beta \tag{eq.6} LayerNorm(x)=α⊙σi2+ϵxij−μi+β(eq.6)
然后用每一行的每一个元素减去这行的均值, 再除以这行的标准差, 从而得到归一化后的数值, ϵ \epsilon ϵ是为了防止除 0 0 0;
之后引入两个可训练参数 α , β \alpha, \ \beta α, β来弥补归一化的过程中损失掉的信息, 注意 ⊙ \odot ⊙表示元素相乘而不是点积, 我们一般初始化 α \alpha α为全 1 1 1, 而 β \beta β为全 0 0 0.
经过上面3个步骤, 我们已经基本了解到来 t r a n s f o r m e r transformer transformer编码器的主要构成部分, 我们下面用公式把一个 t r a n s f o r m e r b l o c k transformer \ block transformer block的计算过程整理一下:
1). 字向量与位置编码:
X = E m b e d d i n g L o o k u p ( X ) + P o s i t i o n a l E n c o d i n g (eq.2) X = EmbeddingLookup(X) + PositionalEncoding \tag{eq.2} X=EmbeddingLookup(X)+PositionalEncoding(eq.2)
X ∈ R b a t c h s i z e ∗ s e q . l e n . ∗ e m b e d . d i m . X \in \mathbb{R}^{batch \ size \ * \ seq. \ len. \ * \ embed. \ dim.} X∈Rbatch size ∗ seq. len. ∗ embed. dim.
2). 自注意力机制:
Q = L i n e a r ( X ) = X W Q Q = Linear(X) = XW_{Q} Q=Linear(X)=XWQ
K = L i n e a r ( X ) = X W K (eq.3) K = Linear(X) = XW_{K} \tag{eq.3} K=Linear(X)=XWK(eq.3)
V = L i n e a r ( X ) = X W V V = Linear(X) = XW_{V} V=Linear(X)=XWV
X a t t e n t i o n = S e l f A t t e n t i o n ( Q , K , V ) (eq.4) X_{attention} = SelfAttention(Q, \ K, \ V) \tag{eq.4} Xattention=SelfAttention(Q, K, V)(eq.4)
3). 残差连接与 L a y e r N o r m a l i z a t i o n Layer \ Normalization Layer Normalization
X a t t e n t i o n = X + X a t t e n t i o n (eq. 5) X_{attention} = X + X_{attention} \tag{eq. 5} Xattention=X+Xattention(eq. 5)
X a t t e n t i o n = L a y e r N o r m ( X a t t e n t i o n ) (eq. 6) X_{attention} = LayerNorm(X_{attention}) \tag{eq. 6} Xattention=LayerNorm(Xattention)(eq. 6)
4). 下面进行 t r a n s f o r m e r b l o c k transformer \ block transformer block结构图中的第4部分, 也就是 F e e d F o r w a r d FeedForward FeedForward, 其实就是两层线性映射并用激活函数激活, 比如说 R e L U ReLU ReLU:
X h i d d e n = A c t i v a t e ( L i n e a r ( L i n e a r ( X a t t e n t i o n ) ) ) (eq. 7) X_{hidden} = Activate(Linear(Linear(X_{attention}))) \tag{eq. 7} Xhidden=Activate(Linear(Linear(Xattention)))(eq. 7)
5). 重复3).:
X h i d d e n = X a t t e n t i o n + X h i d d e n X_{hidden} = X_{attention} + X_{hidden} Xhidden=Xattention+Xhidden
X h i d d e n = L a y e r N o r m ( X h i d d e n ) X_{hidden} = LayerNorm(X_{hidden}) Xhidden=LayerNorm(Xhidden)
X h i d d e n ∈ R b a t c h s i z e ∗ s e q . l e n . ∗ e m b e d . d i m . X_{hidden} \in \mathbb{R}^{batch \ size \ * \ seq. \ len. \ * \ embed. \ dim.} Xhidden∈Rbatch size ∗ seq. len. ∗ embed. dim.
小结:
我们到现在位置已经讲完了transformer的编码器的部分, 了解到了transformer是怎样获得自然语言的位置信息的, 注意力机制是怎样的, 其实举个语言情感分类的例子, 我们已经知道, 经过自注意力机制, 一句话中的每个字都含有这句话中其他所有字的信息, 那么我们可不可以添加一个空白字符到句子最前面, 然后让句子中的所有信息向这个空白字符汇总, 然后再映射成想要分的类别呢? 这就是BERT, 我们下次会讲到.
在BERT的预训练中, 我们给每句话的句头加一个特殊字符, 然后句末再加一个特殊字符, 之后模型预训练完毕之后, 我们就可以用句头的特殊字符的 h i d d e n s t a t e hidden \ state hidden state完成一些分类任务了.