传统Word Embedding的预训练表示是上下文无关的,例如word2vec,glove,fasttext,在训练好词向量之后不能表示多义单词,例如:bank deposit(银行) VS river band(岸边)
BERT: Bidirectional Encoder Representations from Transformers。它是一种预训练语言的表示,上下文相关。
为了预测或推断一个元素,例如图像中的像素或者句子中的单词,使用注意力权重来估计其他元素与其相关的强度,并将由注意力权重加权的值的总和作为计算最终目标的特征。
Step 1:计算其他元素与待预测元素的相关性权重。
Step 2:通关softmax将相关性权重归一化。
Step 3:根据相关性权重对其他元素进行。
权重计算方式:
{ Q T K d o t Q T W K g e n e r a l W [ Q ; K ] c o n c a t V T t a n h ( W 1 Q + W 2 K ) p e r c e p t r o n ( a d d i t i v e ) \begin{cases} Q^TK \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad dot\\ Q^TWK \quad \quad \quad \quad \quad \quad \quad \quad general\\ W[Q;K] \quad \quad \quad \quad \quad \quad \quad \ \ \ concat\\ V^Ttanh(W_1Q+W_2K) \quad perceptron(additive) \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧QTKdotQTWKgeneralW[Q;K] concatVTtanh(W1Q+W2K)perceptron(additive)
分类:
Encoder是由6个相同的层构成,每个层又含有两个子层。第一个子层由多重自注意力机制构成,第二层是由全连接层构成。其中每个子层都通过残差连接,并且通过层归一化(layer normalization).。每个子层的输出如下:
L a y e r N o r m ( x + S u b l a y e r ( x ) ) LayerNorm(x+Sublayer(x)) LayerNorm(x+Sublayer(x))
注:为了实现残差连接,模型中的所有子层和词向量都是一样的维度,即:512。
Decoder也是由6个相同的层构成,每个层除了拥有Encoder中的子层还包括一个多重注意力机制层(不是自注意力)。每个子层同样是残差连接,输出通过层归一化。其中的多重自注意力机制与Encoder中的不同,因为在输出阶段,预测第 i i i个位置的输出时,只能依赖于位置 i i i以及之前的信息,不能获取 i i i位置后的信息。因此在做自注意力机制时需要将 i i i位置后的计算得到的权重置为0。
与 d o t dot dot形式相似,区别在于要除以一个 d k \sqrt{d_k} dk,公式如下:
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dkQKT)V
多头注意力机制允许模型共同关注来自不同位置的不同表示子空间的信息。公式如下:
M u l t i H e a d ( Q , K , V ) = C o n c a t ( h e a d 1 , . . . , h e a d n ) W o h e a d i = A t t e n t i o n ( Q W i Q , K i K , V W i V ) MultiHead(Q,K,V)=Concat(head_1,...,head_n)W^o\\ head_i=Attention(QW_i^Q,K_i^K,VW_i^V) MultiHead(Q,K,V)=Concat(head1,...,headn)Woheadi=Attention(QWiQ,KiK,VWiV)
作用:
两个线性映射层,中间加一个RELU。公式如下:
F F N ( x ) = m a x ( 0 , x W 1 + b 1 ) W 2 + b 2 FFN(x)=max(0,xW_1+b_1)W_2+b_2 FFN(x)=max(0,xW1+b1)W2+b2
作用:
因为该模型没有时序特征,所以模型需要加入额外的时序信息。公式如下:
P E ( p o s , 2 i ) = s i n ( p o s / 1000 0 2 i / d m o d e l ) P E ( p o s , 2 i + 1 ) = c o s ( p o s / 1000 0 2 i / d m o d e l ) PE(pos,2i)=sin(pos/10000^{2i/d_{model}})\\ PE(pos,2i+1)=cos(pos/10000^{2i/d_{model}}) PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel)
p o s pos pos是单词的位置, i i i是单词向量的维度
其中每个单词的表示都是由三部分组成:token embedding(WordPiece),segment embedding,position embedding。对于句子对,两个句子之间用[SEP]分隔,并且为每个单词表示加入segment embedding A and B。
WordPiece字面理解是把word拆成piece一片一片,它的主要实现方式叫做BPE(Byte-Pair Encoding)双字节编码。
BPE的过程可以理解为把一个单词再拆分,使得我们的单词表会变得精简,并且寓意更加清晰。比如"loved",“loving”,“loves"这三个单词。其实本身的语义都是“爱”的意思,但是如果我们以单词为单位,那它们就算不一样的词,在英语中不同后缀的词非常的多,就会使得单词表变的很大,训练速度变慢,训练的效果也不是太好。BPE算法通过训练,能够把上面的3个单词拆分成"lov”,“ed”,“ing”,"es"几部分,这样可以把词的本身的意思和时态分开,有效的减少了词表的数量。
算法步骤如下:
首先将词分成一个一个的字符,然后在词的范围内统计字符对出现的次数,每次将次数最多的字符对保存起来,直到循环次数结束。
原始词表 {'l o w e r ': 2, 'n e w e s t ': 6, 'w i d e s t ': 3, 'l o w ': 5}
出现最频繁的序列 ('s', 't') 9
合并最频繁的序列后的词表 {'n e w e st ': 6, 'l o w e r ': 2, 'w i d e st ': 3, 'l o w ': 5}
出现最频繁的序列 ('e', 'st') 9
合并最频繁的序列后的词表 {'l o w e r ': 2, 'l o w ': 5, 'w i d est ': 3, 'n e w est ': 6}
出现最频繁的序列 ('est', '') 9
合并最频繁的序列后的词表 {'w i d est': 3, 'l o w e r ': 2, 'n e w est': 6, 'l o w ': 5}
出现最频繁的序列 ('l', 'o') 7
合并最频繁的序列后的词表 {'w i d est': 3, 'lo w e r ': 2, 'n e w est': 6, 'lo w ': 5}
出现最频繁的序列 ('lo', 'w') 7
合并最频繁的序列后的词表 {'w i d est': 3, 'low e r ': 2, 'n e w est': 6, 'low ': 5}
出现最频繁的序列 ('n', 'e') 6
合并最频繁的序列后的词表 {'w i d est': 3, 'low e r ': 2, 'ne w est': 6, 'low ': 5}
出现最频繁的序列 ('w', 'est') 6
合并最频繁的序列后的词表 {'w i d est': 3, 'low e r ': 2, 'ne west': 6, 'low ': 5}
出现最频繁的序列 ('ne', 'west') 6
合并最频繁的序列后的词表 {'w i d est': 3, 'low e r ': 2, 'newest': 6, 'low ': 5}
出现最频繁的序列 ('low', '') 5
合并最频繁的序列后的词表 {'w i d est': 3, 'low e r ': 2, 'newest': 6, 'low': 5}
出现最频繁的序列 ('i', 'd') 3
合并最频繁的序列后的词表 {'w id est': 3, 'newest': 6, 'low': 5, 'low e r ': 2}
为了训练深度双向表示,作者随机mask一定比例(15%)的输入单词,然后预测这些被屏蔽的单词。然后,对应于mask的最终隐藏向量将被输入到softmax中,这与标准的LM相似。
但是这种做法有两个缺点:
为了训练理解句子关系的模型,作者又加入了一个预测下语句话的二分类任务,即判断下一句话是否是正确的。
通常讲的语言模型其实是根据上文内容预测下一个可能跟随的单词,就是常说的自左向右的语言模型任务,或者反过来也行,就是根据下文预测前面的单词,这种类型的LM被称为自回归语言模型。GPT 就是典型的自回归语言模型。ELMO尽管看上去利用了上文,也利用了下文,但是本质上仍然是自回归LM,这个跟模型具体怎么实现有关系。ELMO是做了两个方向(从左到右以及从右到左两个方向的语言模型),但是是分别有两个方向的自回归LM,然后把LSTM的两个方向的隐节点状态拼接到一起,来体现双向语言模型这个事情的。所以其实是两个自回归语言模型的拼接,本质上仍然是自回归语言模型。
自回归语言模型有优点有缺点,缺点是只能利用上文或者下文的信息,不能同时利用上文和下文的信息,当然,貌似ELMO这种双向都做,然后拼接看上去能够解决这个问题,因为融合模式过于简单,所以效果其实并不是太好。它的优点,其实跟下游NLP任务有关,比如生成类NLP任务,比如文本摘要,机器翻译等,在实际生成内容的时候,就是从左向右的,自回归语言模型天然匹配这个过程。而Bert这种DAE模式,在生成类NLP任务中,就面临训练过程和应用过程不一致的问题,导致生成类的NLP任务到目前为止都做不太好。
自回归语言模型只能根据上文预测下一个单词,或者反过来,只能根据下文预测前面一个单词。相比而言,Bert通过在输入X中随机Mask掉一部分单词,然后预训练过程的主要任务之一是根据上下文单词来预测这些被Mask掉的单词,如果你对Denoising Autoencoder比较熟悉的话,会看出,这确实是典型的DAE的思路。那些被Mask掉的单词就是在输入侧加入的所谓噪音。类似Bert这种预训练模式,被称为DAE LM。
这种DAE LM的优缺点正好和自回归LM反过来,它能比较自然地融入双向语言模型,同时看到被预测单词的上文和下文,这是好处。缺点是啥呢?主要在输入侧引入[Mask]标记,导致预训练阶段和Fine-tuning阶段不一致的问题,因为Fine-tuning阶段是看不到[Mask]标记的。DAE吗,就要引入噪音,[Mask] 标记就是引入噪音的手段,这个正常。
XLNet的出发点就是:能否融合自回归LM和DAE LM两者的优点。就是说如果站在自回归LM的角度,如何引入和双向语言模型等价的效果;如果站在DAE LM的角度看,它本身是融入双向语言模型的,如何抛掉表面的那个[Mask]标记,让预训练和Fine-tuning保持一致。
在预训练阶段,引入Permutation Language Model的训练目标,具体实现方式是,通过随机取一句话排列的一种,然后将末尾一定量的词给“遮掩”(和 BERT 里的直接替换 “[MASK]” 有些不同)掉,最后用 AR 的方式来按照这种排列方式依此预测被“遮掩”掉的词。比如输入的句子X由 x 1 , x 2 , x 3 , x 4 x_1,x_2,x_3,x_4 x1,x2,x3,x4四个单词顺序构成。我们假设,其中,要预测的单词是 x 3 x_3 x3,位置在Position 3,要想让它能够在上文Context_before中,也就是Position 1或者Position 2的位置看到Position 4的单词 x 4 x_4 x4。可以这么做:假设我们固定住x3所在位置,就是它仍然在Position 3,之后随机排列组合句子中的其他单词,在随机排列组合后的各种可能里,再选择一部分作为模型预训练的输入X。比如随机排列组合后,抽取出 x 4 , x 2 , x 3 , x 1 x_4,x_2,x_3,x_1 x4,x2,x3,x1这一个排列组合作为模型的输入X。于是, x 3 x_3 x3就能同时看到上文 x 2 x_2 x2,以及下文 x 4 x_4 x4的内容了,但是形式上看上去仍然是从左到右在预测后一个单词,这就是XLNet的基本思想。
当然,上面讲的仍然是基本思想。难点其实在于具体怎么做才能实现上述思想。首先,需要强调一点,尽管上面讲的是把句子X的单词排列组合后,再随机抽取例子作为输入,但是,实际上你是不能这么做的,因为Fine-tuning阶段你不可能也去排列组合原始输入。所以,就必须让预训练阶段的输入部分,看上去仍然是 x 1 , x 2 , x 3 , x 4 x_1,x_2,x_3,x_4 x1,x2,x3,x4这个输入顺序,但是可以在Transformer部分做些工作,来达成我们希望的目标。具体而言,XLNet采取了Attention掩码的机制,你可以理解为,当前的输入句子是X,要预测的单词Ti是第i个单词,前面1到i-1个单词,在输入部分观察,并没发生变化,该是谁还是谁。但是在Transformer内部,通过Attention掩码,从X的输入单词里面,也就是Ti的上文和下文单词中,随机选择i-1个,放到Ti的上文位置中,把其它单词的输入通过Attention掩码隐藏掉,于是就能够达成我们期望的目标。具体实现的时候,XLNet是用“双流自注意力模型”实现的。
为了实现 Permutation 加上 AR 预测过程,首先我们会发现,打乱顺序后位置信息非常重要,同时对每个位置来说,需要预测的是内容信息(对应位置的词),于是输入就不能包含内容信息,不然模型学不到东西,只需要直接从输入 copy 到输出就好了。
于是这里就造成了位置信息与内容信息的割裂,因此在 BERT 这样的位置信息+内容信息输入 Self-Attention (自注意力) 的流(Stream)之外,作者们还增加了另一个只有位置信息作为 Self-Attention 中 query 输入的流。文中将前者称为 Content Stream,而后者称为 Query Stream。
内容流自注意力其实就是标准的Transformer的计算过程;主要是引入了Query流自注意力,其实就是用来代替Bert的那个[Mask]标记的,因为XLNet希望抛掉[Mask]标记符号,但是比如知道上文单词 x 1 , x 2 x_1,x_2 x1,x2,要预测单词 x 3 x_3 x3,此时在x3对应位置的Transformer最高层去预测这个单词,但是输入侧不能看到要预测的单词 x 3 x_3 x3,Bert其实是直接引入[Mask]标记来覆盖掉单词x3的内容的,等于说[Mask]是个通用的占位符号。而XLNet因为要抛掉[Mask]标记,但是又不能看到 x 3 x_3 x3的输入,于是Query流,就直接忽略掉 x 3 x_3 x3输入了,只保留这个位置信息。这样子就能利用 Query Stream 在对需要预测位置进行预测的同时,又不会泄露当前位置的内容信息。其实XLNet只是扔了表面的[Mask]占位符号,内部还是引入Query流来忽略掉被Mask的这个单词。和Bert比,只是实现方式不同而已。
核心就是说,尽管当前输入看上去仍然是 x 1 − > x 2 − > x 3 − > x 4 x_1->x_2->x_3->x_4 x1−>x2−>x3−>x4,但是我们已经改成随机排列组合的另外一个顺序 x 3 − > x 2 − > x 4 − > x 1 x_3->x_2->x_4->x_1 x3−>x2−>x4−>x1了,如果用这个例子用来从左到右训练LM,意味着当预测x2的时候,它只能看到上文x3;当预测x4的时候,只能看到上文x3和x2,以此类推……这样,比如对于x2来说,就看到了下文x3了。这种在输入侧维持表面的X句子单词顺序,但是其实在Transformer内部,看到的已经是被重新排列组合后的顺序,是通过Attention掩码来实现的。如下图所示:
输入看上去仍然是x1,x2,x3,x4,可以通过不同的掩码矩阵,让当前单词Xi只能看到被排列组合后的顺序x3->x2->x4->x1中自己前面的单词。这样就在内部改成了被预测单词同时看到上下文单词,但是输入侧看上去仍然维持原先的单词顺序了。关键要看明白上图右侧那个掩码矩阵,它的坐标是1-2-3-4,就是表面那个X的单词顺序,通过掩码矩阵,就能改成你想要的排列组合,并让当前单词看到它该看到的所谓上文,其实是掺杂了上文和下文的内容。这是attention mask来实现排列组合的背后的意思。
因为当我们按上面提到的实现,在 Permutation 后对每个位置进行预测的话,会导致优化过难,训练难以收敛,于是作者们就做了和 BERT 中类似的操作。训练时,只对每句话部分位置进行预测。
这些预测位置如何选取呢,选当前排列的最后几个位置。举个例子,假如有 1234567,先随机挑一个排列,5427163,那么假设对最后两个位置预测,于是就需要依此对6和3进行预测。通过挑结尾的位置,在 AR 中,就能在预测时用到尽可能多的可知信息。
这里再谈一个有意思的点,挑选最后几个,那么到底该挑选几个呢,总得给个标准吧。于是作者这里设了一个超参数 K,K 等于总长度除以需要预测的个数。拿上面的例子,总长为 7 而需要预测为 2,于是 K = 7/2.
而论文中实验得出的最佳 K 值介于 6 和 7 (更好)之间,其实如果我们取 K 的倒数,然后转为百分比,就会发现最佳的比值介于 14.3% 到 16.7% 之间,还记得 BERT 论文的同学肯定就会开始觉得眼熟了。因为 BERT 里将 Token 遮掩成 “[MASK]” 的百分比就是 15%,正好介于它们之间。
其实思想很简单,因为一般训练 Transformer 时,会按照一定长度,将文本处理成一段(segment)一段的。比如说 BERT 预处理时,就会先处理成一个个 512 长度的样本,即使可能处理前的文本更长。这样子的话,有些更长的上下文信息,模型就是学习不到的。
于是 Segment Recurrence Mechanism 想做的就是,能不能在前一段计算完后,将它计算出的隐状态(hidden states)都保存下来,放入一个 Memory 中去,之后在当前分段计算时,将之前存下来的隐状态和当前段的隐状态拼起来作为 Attention 机制的 K 和 V,从而获得更长的上下文信息。
假设在 segment1 中已经用了从 1 开始编码的绝对位置向量,那么在 segment2 中,我们该用什么样的位置编码呢,从 1 开始的绝对位置编码吗?这样的话,在复用 segment1 时,整个过程中就会有两个 1 位置,这样是会出问题的,因为模型会搞不清想让它学习的位置信息。当然也有个做法就是从 segment1 长度 +1 开始给 segment2 加上位置编码,但这样会让位置编码表过长,而且不一定能充分学习,还有就是这样不太符合人类写作的常识,我们其实都是一段段写,不会有人会认真数我现在写到了第 1000 个字,然后第 1000 个字会和第 10 个字有什么关系,更多会关心在某一段中一个字词和其他字词的相对关系。因此就可以用上这里提到的,相对位置编码,不再关心句中词的绝对信息,而是相对的,比如说两个词之间隔了多少个词这样的相对信息。
解决输入是多句的情况,加入s+和s-分别表示两个单词在一句话内和不在一句话内。
目标:解决Transformer中输入文本长度固定的问题,使得Transformer具有强的长距离依赖
贡献有两个:
在训练期间,前一个段的隐藏序列会输入至后一个段作为额外的输入。如下图:
令两个连续的长度为 L L L的段表示为 s τ = [ x τ , 1 , . . . , x τ , L ] 和 s τ + 1 = [ x τ + 1 , 1 , . . . , x τ + 1 , L ] s_{\tau}=[x_{\tau,1},...,x_{\tau,L}]和s_{\tau+1}=[x_{\tau+1,1},...,x_{\tau+1,L}] sτ=[xτ,1,...,xτ,L]和sτ+1=[xτ+1,1,...,xτ+1,L], s τ 在 第 n 层 产 生 的 隐 藏 状 态 为 h τ n ∈ R L × d s_{\tau}在第n层产生的隐藏状态为h_{\tau}^n \in R^{L \times d} sτ在第n层产生的隐藏状态为hτn∈RL×d,那么 s τ + 1 产 生 的 h τ + 1 n s_{\tau+1}产生的h_{\tau+1}^n sτ+1产生的hτ+1n,如下计算:
S G ( ⋅ ) 代 表 s t o p − g r a d i e n t , [ ⋅ ] 代 表 向 量 拼 接 SG(\cdot)代表stop-gradient,[\cdot]代表向量拼接 SG(⋅)代表stop−gradient,[⋅]代表向量拼接。由上面的公式可以看出前段与后段的依赖性体现在计算 h τ + 1 n 时 需 要 h τ n − 1 h_{\tau+1}^n时需要h_{\tau}^{n-1} hτ+1n时需要hτn−1。因此每个段的依赖性的长度是随层数的增加而线性增加的,具体为 N × L N \times L N×L,解释为第N层可以依赖的上下文长度为 N × L N \times L N×L(包含自身)。
此外这个操作带来的另一个好处就是,在评估阶段可以重复使用前段的表示,而不是像原始模型那样从头开始计算。
在标准的Transformer中,位置信息是由绝对位置编码的。但是在Transformer-XL中,这不能区分 h τ + 1 n 和 h τ n h_{\tau+1}^n和h_{\tau}^n hτ+1n和hτn中的同位置元素,因为他们的位置编码时相同的。传统的Transformer的注意力得分计算如下:
Transformer-XL计算注意力得分如下:
共有如下几个变化:
https://www.cnblogs.com/huangyc/p/10223075.html
https://zhuanlan.zhihu.com/p/70257427
https://zhuanlan.zhihu.com/p/71916499