『Transformer/BERT』Transformer和BERT的位置编码



为什么要对位置进行编码?

Attention提取特征的时候,可以获取全局每个词对之间的关系,但是并没有显式保留时序信息,或者说位置信息。就算打乱序列中token的顺序,最后所得到的Attention结果也不会变,这会丢失语言中的时序信息,因此需要额外对位置进行编码以引入时序信息。


Position Embedding in Transformer

在Transformer中,位置编码是由 s i n / c o s sin/cos sin/cos函数生成的固定值。

具体做法:用不同频率的正余弦函数对位置信息进行编码,位置编码向量的维度与文本编码向量的维度相同,即 d m o d e l d_{model} dmodel。因此二者可以直接相加作为token最终的编码向量。

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表示所在维度。

这种位置编码方式可以方便模型学习相对位置特征,对于相隔为 k k k的两个位置 p 1 p_1 p1 p 2 = p 1 + k p_2=p_1+k p2=p1+k P E ( p 2 , ) PE_{(p_2,)} PE(p2,)可以表示为 P E ( p 1 , ) PE_{(p_1,)} PE(p1,)的线性组合。

P E ( p 2 , 2 i ) = s i n ( ( p 1 + k ) / 1000 0 2 i / d m o d e l ) = s i n ( p 1 / 1000 0 2 i / d m o d e l ) C ( k , 1 ) + c o s ( p 1 / 1000 0 2 i / d m o d e l ) C ( k , 2 ) = P E ( p 1 , 2 i ) C ( k , 1 ) + P E ( p 1 , 2 i + 1 ) C ( k , 2 ) PE_{(p_2,2i)} = sin((p_1+k)/10000^{2i/d_{model}})\\= sin(p_1/10000^{2i/d_{model}})C_{(k,1)} + cos(p_1/10000^{2i/d_{model}})C_{(k,2)}\\=PE_{(p_1,2i)}C_{(k,1)} + PE_{(p_1,2i+1)}C_{(k,2)} PE(p2,2i)=sin((p1+k)/100002i/dmodel)=sin(p1/100002i/dmodel)C(k,1)+cos(p1/100002i/dmodel)C(k,2)=PE(p1,2i)C(k,1)+PE(p1,2i+1)C(k,2)

即使测试集中某些样本超出了最大文本长度,这种编码方式仍然可以获得有效的相对位置表示。


Position Embedding in BERT

在BERT中,与一般的词嵌入编码类似,位置编码也是随机生成且可训练的,维度为[seq_length, width],其中seq_length代表序列长度,width代表每一个token对应的向量长度。

从实现上可以看到,BERT中将位置编码创建为一个tensorflow变量,并将其broadcast到与词嵌入编码同维度后相加。

with tf.control_dependencies([assert_op]):
      full_position_embeddings = tf.get_variable(
          name=position_embedding_name,
          shape=[max_position_embeddings, width],
          initializer=create_initializer(initializer_range))

      # 这里position embedding是可学习的参数,[max_position_embeddings, width]
      # 但是通常实际输入序列没有达到max_position_embeddings
      # 所以为了提高训练速度,使用tf.slice取出句子长度的embedding
      position_embeddings = tf.slice(full_position_embeddings, [0, 0],
                                     [seq_length, -1])
      num_dims = len(output.shape.as_list())

      # word embedding之后的tensor是[batch_size, seq_length, width]
      # 因为位置编码是与输入内容无关,它的shape总是[seq_length, width]
      # 我们无法把位置Embedding加到word embedding上
      # 因此我们需要扩展位置编码为[1, seq_length, width]
      # 然后就能通过broadcasting加上去了。
      position_broadcast_shape = []
      for _ in range(num_dims - 2):
        position_broadcast_shape.append(1)
      position_broadcast_shape.extend([seq_length, width])
      position_embeddings = tf.reshape(position_embeddings,
                                       position_broadcast_shape)
      output += position_embeddings

两者之间的区别

Transformer的位置编码是一个固定值,因此只能标记位置,但是不能标记这个位置有什么用。

BERT的位置编码是可学习的Embedding,因此不仅可以标记位置,还可以学习到这个位置有什么用。

BERT选择这么做的原因可能是,相比于Transformer,BERT训练所用的数据量充足,完全可以让模型自己学习。


如何延拓BERT的位置编码?

我们知道,BERT模型最多只能处理512个token的文本,其原因在于BERT使用了随机初始化训练出来的绝对位置编码,最大位置设为为512,若是文本长于512便无位置编码可用。

另一方面,Attention的 O ( n 2 ) O(n^2) O(n2)复杂度使得长序列的显存用量极大,一般显卡就连finetune也做不到。

苏神提出了一种层次分解的方法将BERT的位置编码最多可以延拓至 n 2 n^2 n2,即 51 2 2 = 26 万 512^2=26万 5122=26

具体内容可自行阅读苏神博客

层次分解位置编码,让BERT可以处理超长文本


参考

以上内容总结摘录自以下诸多博客以及《百面深度学习》一书。

bert和transformer的位置编码有什么区别?

BERT源码解析 摘自该博客

BERT源码分析PART I

多种位置编码方式讲解

相对位置编码和绝对位置编码原理及源码

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值