论文:Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context

Transformers具有学习长期依赖的潜力,但在语言模型的设置中受到固定长度上下文的限制。Transformer-XL引入了两点创新——循环机制(Recurrence Mechanism)相对位置编码(Relative Positional Encoding),不仅可以捕获长距离依赖性,还可以解决上下文碎片问题。Transformer-XL学习的依赖性比RNN长80%,比vanilla Transformers长450%,在短序列和长序列上都能获得更好的性能,并且在评估过程中比vanilla Transformers快1800倍。

 

循环机制(Segment-Level Recurrence with State Reuse)

循环机制的目标是通过利用之前段的信息来实现长期依赖性。与vanilla Transformer类似,Transformer-XL处理第一个标记段,但它会保留隐藏层的输出。处理后面的段时,每个隐藏层都会接收两个输入:

  1. 该段的前一个隐藏层的输出,和vanilla Transformer相同(如下图中的灰色箭头所示)。
  2. 上一个段的隐藏层的输出(如绿色箭头所示),可以使模型创建长期依赖关系。

其中,\tau表示第几段,n表示第几层,h表示隐层的输出。SG(\cdot )表示停止计算梯度,[h_{u}\circ h_{v}]表示在长度维度上的两个隐层的拼接,W是模型参数。与Transformer唯一关键的不同就在于Key和Value矩阵的计算上,即k_{\tau +1}^{n}v_{\tau +1}^{n},它们基于的是扩展后的上下文隐层状态\tilde{h}_{\tau +1}^{n-1}进行计算,h_{\tau }^{n-1}是之前段的缓存。

从技术上讲,这两个输入会被拼接,然后用于计算当前段的Key和Value矩阵。该步骤为网络提供了更多关于每个表征的权重(重要性)的信息,但它不会更改Value矩阵。

                                       图:Transformer-XL语言模型的训练和测试示意。来源:Transformer-XL
该概念可以扩展到更长的依赖上。使用相同的方法,利用前面多个段的信息,只要GPU内存允许,在测试阶段也可以获得更长的依赖。

循环机制的另一个优点是其测试速度快。在每个步骤中,它可以一次前进一整个段(而不是像在vanilla Transformer中一次只能前进一个表征),并使用先前段的数据来预测当前段的表征。
 

相对位置编码

循环机制引入了新的挑战——原始位置编码将每个段分开处理,因此,来自不同段的表征会具有相同的位置编码。例如,第一和第二段的第一个表征将具有相同的编码,虽然它们的位置和重要性并不相同(比如第一个段中的第一个表征可能重要性低一些)。这种混淆可能会错误地影响网络。

针对此问题,论文提出了一种新的位置编码方式。这种位置编码是每个注意力模块的一部分。它不会仅在第一层之前编码位置,而且会基于表征之间的相对距离而非绝对位置进行编码。

在Transformer中,第一层的计算查询q_{i}^{T}和键k_{j}之间的attention分数的方式为:

其中,E_{x_{i}}是词i的embedding,E_{x_{j}}是词j的embedding,U_{i}U_{j}是位置向量,这个式子实际上是(W_{q}(E_{x_{i}}+U_{i}))^{T}\cdot (W_{k}(E_{x_{j}}+U_{j}))的展开,就是Transformer中的标准格式。
在Transformer-XL中,对上述的attention计算方式进行了变换,转为相对位置的计算,而且不仅仅在第一层这么计算,在每一层都是这样计算。

从技术上讲,它对注意力头分数(Attention Head’s Score)的计算方式不再是简单的乘法(Qi⋅Kj),而是包括四个部分:

  1. 内容权重——没有添加原始位置编码的原始分数。
  2. 相对于当前内容的位置偏差(Qi)。该项使用正弦类函数来计算表征之间的相对距离(例如i-j),用以替代当前表征的绝对位置。
  3. 可学习的全局内容偏差u——用于调整其他表征内容(Kj)的重要性。
  4. 可学习的全局位置偏差v——仅根据表征之间的距离调整重要性(例如,最后一个词可能比前一段中的词更重要)。
     

Transformer-XL模型的整体计算公式整理如下

 

multihead attention 代码如下

def rel_multihead_attn(w, r, r_w_bias, r_r_bias, attn_mask, mems, d_model,
                       n_head, d_head, dropout, dropatt, is_training,
                       kernel_initializer, scope='rel_attn'):
  scale = 1 / (d_head ** 0.5)
  with tf.variable_scope(scope):
    qlen = tf.shape(w)[0]
    rlen = tf.shape(r)[0]
    bsz = tf.shape(w)[1]

    cat = tf.concat([mems, w],
                    0) if mems is not None and mems.shape.ndims > 1 else w
    w_heads = tf.layers.dense(cat, 3 * n_head * d_head, use_bias=False,
                              kernel_initializer=kernel_initializer, name='qkv')
    r_head_k = tf.layers.dense(r, n_head * d_head, use_bias=False,
                               kernel_initializer=kernel_initializer, name='r')

    w_head_q, w_head_k, w_head_v = tf.split(w_heads, 3, -1)
    w_head_q = w_head_q[-qlen:]

    klen = tf.shape(w_head_k)[0]

    w_head_q = tf.reshape(w_head_q, [qlen, bsz, n_head, d_head])
    w_head_k = tf.reshape(w_head_k, [klen, bsz, n_head, d_head])
    w_head_v = tf.reshape(w_head_v, [klen, bsz, n_head, d_head])

    r_head_k = tf.reshape(r_head_k, [rlen, n_head, d_head])

    rw_head_q = w_head_q + r_w_bias
    rr_head_q = w_head_q + r_r_bias

    AC = tf.einsum('ibnd,jbnd->ijbn', rw_head_q, w_head_k)
    BD = tf.einsum('ibnd,jnd->ijbn', rr_head_q, r_head_k)
    BD = rel_shift(BD)

    attn_score = (AC + BD) * scale
    attn_mask_t = attn_mask[:, :, None, None]
    attn_score = attn_score * (1 - attn_mask_t) - 1e30 * attn_mask_t

    attn_prob = tf.nn.softmax(attn_score, 1)
    attn_prob = tf.layers.dropout(attn_prob, dropatt, training=is_training)

    attn_vec = tf.einsum('ijbn,jbnd->ibnd', attn_prob, w_head_v)
    size_t = tf.shape(attn_vec)
    attn_vec = tf.reshape(attn_vec, [size_t[0], size_t[1], n_head * d_head])

    attn_out = tf.layers.dense(attn_vec, d_model, use_bias=False,
                               kernel_initializer=kernel_initializer, name='o')
    attn_out = tf.layers.dropout(attn_out, dropout, training=is_training)

    output = tf.contrib.layers.layer_norm(attn_out + w, begin_norm_axis=-1)
  return output

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值