最后更新于
最后更新于
本文介绍XLNet的代码的第三部分,需要首先阅读和,读者阅读前需要了解XLNet的原理,不熟悉的读者请先阅读。
目录
这里是相对位置编码,注意这里的”相对”位置指的是两个位置的差,而不是Transformer里的相对位置编码。
而Transformer-XL(包括follow它的XLNet)为什么又要使用一种新的”相对”位置编码呢?这是由于Transformer-XL引入了之前的context(cache)带来的问题。
举个例子:比如一种情况是”a b c | d e f”(它表示context是”a b c”,当前输入是”d e f”);第二种情况是”d b c | a e f”。那么在第二个时刻,这两种情况计算e到a的attention时都是用e的Embedding(Word Embedding+Position Embedding)去乘以a的Embedding(WordEmbedding+Position Embedding)【当然实际还要把它们经过Q和K变换矩阵,但是这不影响结论】,因此计算结果是相同的,但是实际上一个a是在它的前面,而另一个a是在很远的context里。
因此我们发现问题的关键是Transformer使用的是一种”绝对”位置的编码(之前的那种使用正弦函数依然是绝对位置编码,但是它能够让上层学习到两个位置的相对信息,所以当时也叫作相对位置编码。但是对于一个位置,它还是一个固定的编码,请一定要搞清楚)。
为了解决这个问题,Transformer-XL引入了”真正”的相对位置编码方法。它不是把位置信息提前编码在输入的Embedding里,而是在计算attention的时候根据当前的位置和要attend to的位置的相对距离来”实时”告诉attention head。比如当前的query是qτ,i$ q{\tau ,i} $,要attend to的key是kτ,j$ k{\tau ,j} $,那么只需要知道i和j的位置差,然后就可以使用这个位置差的位置Embedding。
我们回到前面的例子,context大小是96,而输入的序列长度是128。因此query的下标i的取值范围是96-223,而key的下标j的取值范围是0-223,所以它们的差的取值范围是(96-223)-(223-0),所以位置差总共有352种可能的取值,所以上面返回的pos_emb的shape是(352, 8, 1024)。
下面是详细的相对位置编码的原理和代码,不感兴趣的读者可以跳过,但是至少需要知道前面的基本概念以及pos_emb的shape的含义。
在标准的Transformer里,同一个segment的qi$ q{i} $和kj$ k{j} $的attention score可以这样分解:
Aabsi,j=ETxiWTqWkExj(a)+ETxiWTqWkUj(b)+UTiWTqWkExj(c)+UTiWTqWkUj(d)$ \begin{matrix} A_{i,j}^{abs} & \ & \ \end{matrix} $
参考上面的公式,并且因为希望只考虑相对的位置,所以我们(Transformer-XL)提出如下的相对位置Attention计算公式:
Areli,j=ETxiWTqWk,EExj(a)+ETxiWTqWk,RRi−j(b)+uTWk,EExj(c)+vTWk,RRi−j(d)$ \begin{matrix} A_{i,j}^{rel} & \ & \ \end{matrix} $
和前面的Aabsi,j$ A{i,j}^{abs} $相比,第一个是把(b)和(d)里的绝对位置编码Uj$ U{j} $都替换成相对位置编码向量Ri−j$ R_{i−j} $。注意这里的R是之前介绍的”相对”的正弦函数的编码方式,它是固定的没有可以学习的参数。
在(c)中用可训练的u∈Rd$ u \in R^{d} $替代原来的UTiWTq$ U{i}^{T}W{q}^{T} $。因为我们假设Attention score只依赖于i和j的相对位置,而与i的绝对位置无关,所以这里对于所有i都相同。也就是UTWTq$ U^{T}W_{q}^{T} $,所以可以用一个新的u$ u $来表示。类似的是(d)中的v∈Rd$ v \in R^{d} $。
最后,我们把key的变换矩阵Wk$ W{k} $拆分成Wk,E$ W{k,E} $和Wk,R$ W_{k,R} $,分别表示与内容相关的key和与位置相关的key。
在上面的新公式里,每一项的意义都非常清晰:(a)表示内容的计算,也就是xi$ x{i} $的Embedding乘以变换矩阵Wq$ W{q} $和xj$ x{j} $的Embedding乘以Wk,E$ W{k,E} $的内积;(b)表示基于内容的位置偏置,也就是i的向量乘以相对位置编码;(c)全局的内容偏置;(d)全局的位置偏置。
因此Transformer-XL里的计算过程如下:
h^n−1τqnτ,knτ,vnτAnτ,i,janτonτhnτ=[SG(mn−1τ∘hn−1τ)]=hn−1τWnqT,h^n−1τWnk,ET,h^n−1τWnvT=qnτ,iTknτ,j+qnτ,iTWnk,RRi−j+uTknτ,j+vTWnk,RRi−j=Mask-Softmax(Anτ)vnτ=LayerNorm(Linear(anτ)+hn−1τ)=Positionwise-Feed-Forward(onτ)$ \begin{matrix} h\limits^{^}{\tau }^{n−1} & \q{\tau }^{n} & \A{\tau ,i,j}^{n} & \ & \a{\tau }^{n} & \o{\tau }^{n} & \h{\tau }^{n} & \ \end{matrix} $
第一个等式说明当前的输入是memory(mn−1τ$ m{\tau }^{n−1} $)和上一个时刻的隐状态hn−1τ$ h{\tau }^{n−1} $,SG()的意思是不参与梯度计算。∘$ \circ $表示向量拼接。
第二个等式计算query、key和value,其中query只能用上一个时刻的隐状态hn−1τ$ h{\tau }^{n−1} $,而key和value是使用上面的h^n−1τ$ h\limits^{^}{\tau }^{n−1} $。因为Key分成了Wk,E$ W{k,E} $和Wk,R$ W{k,R} $,这里只用表示内容的Wk,E$ W_{k,E} $。
第四个公式用softmax把score变成概率,需要处理Mask(mask的概率为0)
第五个公式是残差连接和LayerNorm
最后是全连接层
接下来我们看怎么获得相对位置编码,注意,上面的计算过程要在后面才会介绍。
这个函数返回相对位置的编码,它返回一个(352, 8, 1024)的Tensor。8表示batch,1024表示位置编码的维度,它是和隐单元相同大小,这样可以相加(残差连接)。
前面我们介绍过,这里再复述一下。context大小是96,而输入的序列长度是128。因此query的下标i的取值范围是96-223,而key的下标j的取值范围是0-223,所以它们的差的取值范围是(96-223)-(223-0),所以位置差总共有352种可能的取值,所以上面返回的pos_emb的shape是(352, 8, 1024)。fwd_pos_seq的范围是[224, -127],表示当i>=j时(从后往前)从i attend to j的最大范围是223 attend to 0(加上attend to 自己总共224个可能取值);而i<j时最小值是-127。参考下图:
在介绍positional_embedding函数之前我们介绍一下Tensorflow的einsum函数,这个函数在下面很多地方都会用到,所以这里单独作为一个小节来介绍一下。
如果读者熟悉Latex的公式,则上面内容在Latex里会显示为:
C[i,k]=∑jA[i,j]∗B[j,k]$ C[i,k] = \sum \limits_{j}A[i,j] \ast B[j,k] $
上面的这个公式简写为:
那这种简写方法是什么意思呢?或者说怎么从上面的公式得到简写的呢?它的过程如下:
删除变量、括号和逗号
把”*“变成”,”
去掉求和符号
把输出移动右边,把”=”变成”->”
下面我们按照前面的4个步骤看看这个过程:
下面我们来看一些例子,请读者理解它们的含义,如果不理解,可以按照前面的方法一步一步来。
相对位置编码向量的获得就介绍到这,后面在Attention的计算是会用到pos_emb,我们到时候再看具体的计算。
这一对是核心的计算two stream attention的地方。
这段代码把下一层的输出更新mem,然后把它作为上一层的输入。核心是使用two_stream_rel_attn函数计算Attention。我们先看mem的更新。
_cache_mem函数代码如下:
我们先看输入参数:
h (128, 8, 1024) 上一层的内容(context)隐状态
g (21, 8, 1204) 上一层的查询(query)隐状态,只需要计算Mask的哪些位置
r (352, 8, 1024) 相对位置编码,函数relative_positional_encoding的返回值
r_w_bias (16, 64)
r_r_bias (16, 64)
seg_mat (128, 224, 8, 2) 表示i(范围是0-127)和j(被attend to的,包括mem因此范围是0-223)是否在同一个segment里,1是True
r_s_bias (16, 64)
seg_embed (2, 16, 64) 表示两个Token处于相同或者不同Segment(只有两种可能)的embedding
attn_mask_h (128, 224, 8, 1) 这是内容stream的mask,attn_mask_h(i,j)表示i能否attend to j,1表示不能
attn_mask_g (128, 224, 8, 1) 这是查询stream的mask,attn_mask_h(i,j)表示i能否attend to j,1表示不能
target_mapping (21, 128, 8) 表示21个Mask的Token下标,使用one-hot编码(1 out of 128)的方式,注意如果Mask的不够21个,则padding的内容全是零(而不是有且仅有一个1)。
d_model 1024 隐状态大小
n_head 16 attention head个数
d_head 64 每个head的隐状态大小
dropout dropout
dropatt Attention的dropout
is_traning True,表示Pretraining
kernel_initializer 初始化类
完整的代码如下:
head_projection函数的作用是把输入的1024维的向量乘以proj_weight变成(16, 64)。为了实现上面的计算,我们可以使用reshape:
而通过einsum可以一步到位,实现上面的计算过程(当然阅读起来稍微难以理解一点)。
我们可以这样解读’ibh,hnd->ibnd’:输入h的shape是(i/224, b/8, h/1024),proj_weight是(h/1024, n/16, d/64),输出的shape是(i/224,b/8,n/16,d/64)。并且:
head[i,b,n,d]=∑hh[i,b,h]projweight[hnd]$ head[i,b,n,d] = \sum \limits{h}h[i,b,h]proj_weight[hnd] $
抛开那些无关的i,b,n,d维度,它的核心就是把输入的1024/h使用一个矩阵(proj_weight)变换成(16,64)。请读者仔细理解einsum的含义,下文还会阅读很多tf.einsum函数,作者不会这样详细分解了,请确保理解后再往下阅读。
tf.slice的作用是从一个Tensor里截取(slice)一部分,它的主要参数是第二个和第三个,分别表示每一维开始的下标和每一维的长度,如果长度为-1,则表示一直截取从开始下标到最后所有的。下面是几个例子:
x是(128, 352, 8, 16),这个x是前面bd,它是relative_positional_encoding函数的输出经过经过head_projection得到的k_head_r和q_head计算得到的。relative_positional_encoding函数的输出是(352, 8, 1024),而第一维的128是tile(复制)出来的,因此都是相同的(包括batch/8的维度也是tile出来的)。
x(i,d,…)表示i(输入下标,0-127)的距离为d(0-351)的相对位置编码。但是我们实际需要的是x(i,j,…),j是实际的下标,因此需要把距离d变成真实的下标j(i-d),这就是这个函数的作用。注意:d为0表示最大的距离224(其实最大只能是223)。
如果读者没有办法理解其中的细节也可以暂时跳过,但是至少需要知道输出(128,224)的含义:(i,j)的含义是位置i attend to j的位置编码,x(1,4)的值和x(3,6)的值是相同的,因为它们的相对位置都是-3(1-3和4-6)。
需要注意的地方都在注释里,请仔细阅读:
这一段是Self-Attention之后的全连接层+LayerNorm。
代码比较简单,就是调用positionwise_ffn函数,下面是positionwise_ffn的代码:
这一段非常简单,只是返回结果。如果是pretraining,则返回查询stream的输出output_g(21, 8, 1024),否则返回内容stream的输出output_h(128, 8, 1024),返回之前会再做一个dropout,最终返回的是output, new_mems和lookingup_table(32000, 1024)。
终于把最复杂的transformer_xl函数分析完了,我们返回上一层回到XLNetModel的构造函数,再往上返回two_stream_loss函数:
上面的代码注意就是调用modeling.lm_loss根据xlnet_model的输出计算loss。
XLNet的Pretraining代码介绍完毕,接下来会介绍Fine-Tuning,敬请关注!
显示Disqus评论(需要科学上网,有Disqus的广告)
创建于: 2020-04-11 10:52:54
目录: default
标签: 无
BERT是使用”绝对”位置编码,它把每一个位置都编码成一个向量。而在Transformer里,每个位置还是编码成一个向量,不过这个向量是固定的,而且是正弦(余弦)的函数形式,这样上层可以通过它来学习两个词的相对位置的关系,具体读者可以参考。
而这里的”相对”位置编码是Transformer-XL里提出的一种更加一般的位置编码方式(作者在里的说法是不正确的,当时只是阅读了论文,没有详细看代码)。
在标准的Transformer里,如果使用绝对位置编码,假设最大的序列长度为,则只需要一个位置编码矩阵,其中表示第i个位置的Embedding。而Transformer的第一层的输入是Word Embedding加上Position Embedding,从而把位置信息输入给了Transformer,这样它的Attention Head能利用这里面包含的位置信息。那Transformer-XL如果直接使用这种编码方式行不行呢?我们来试一下:
和前面一样,假设两个相邻的segment为和。假设的第n层的隐状态序列为,那么计算公式如下:
上式中是segment的每一个词的Embedding的序列。我们发现和都是加了,因此假设模型发现了输入有这个向量(包含),那么它也无法通过这个向量判断到底是当前segment的第i个位置还是前一个segment的第i个位置。
第三个公式根据前面的Arel$ A^{rel} $计算attention得分。基本和相同,只不过EW$ EW $都已经计算到q,k和v里了,所以用它们替代就行。
图:相对位置编码的attend范围
计算正弦的位置编码是通过下面介绍的函数positional_embedding来实现的,读者可以对照来阅读,那里是PyTorch的实现。
这个函数返回一个Tensor,这个Tensor的元素由Einstein summation习惯的简写公式定义。所谓的指的是由Albert Einstein引入的一种公式简写法。比如计算矩阵A和B的乘积得到C。则C的元素的定义为:
变量ac对应的(a)和(c),变量r_w_bias对应公式里的u$ u $;而bd对应的(b)和(d),变量r_r_bias对应公式里的v$ v $。请读者对照公式进行理解。而ef是相对Segment Embedding。最终把它们加起来就是Attention Score。
上面的代码比较简单,使用tf.einsum把隐状态(21, 8, 1024)变换成(21, 8, 32000)的logits,然后使用sparse_softmax_cross_entropy_with_logits计算交叉熵损失。不熟悉sparse_softmax_cross_entropy_with_logits的读者可以参考,也可以购买作者的的第六章,里面详细的介绍了Tensorflow的基础知识。
请继续阅读。
原网址: