当前位置:   article > 正文

【日常】Transformer要点记录及实现demo(PyTorch与Tensorflow)_transformer pytorch demo

transformer pytorch demo

1 序言

近期抽空重整了一遍Transformer(论文下载)。

距离Transformer提出差不多有四年了,也算是一个老生常谈的话题,关于Transformer的讲解有相当多的线上资源可以参考,再不济详读一遍论文也能大致掌握,但是如果现在要求从零开始写出一个Transformer,可能这并不是很轻松的事情。笔者虽然之前也已经数次应用,但是主要还是基于Tensorflowkeras框架编写,然而现在Tensorflow有些问题,这将在本文的第三部分Tensorflow 实现与问题中详细说明。考虑到之后可能还是主要会在PyTorch的框架下进行开发,正好趁过渡期空闲可以花时间用PyTorch实现一个Transformer的小demo,一方面是熟悉PyTorch的开发,另一方面也是加深对Transformer的理解,毕竟将来大约是会经常需要使用,并且在其基础上进行改良的。

事实上很多事情都是如此,看起来容易,做起来就会发现有很多问题,本文在第一部分Transformer模型详解及注意点中将记录笔者在本次Transformer实现中做的一些值得注意的点;第二部分将展示PyTorchTransformer模型的实现代码,以及如何使用该模型完成一个简单的seq2seq预测任务;第三部分同样会给出TensorflowTransformer模型的实现代码,以及目前Tensorflow的一些问题。

本文不再赘述Transformer的原理,这个已经有很多其他文章进行了详细说明,因此需要一些前置的了解知识,可以通过上面的论文下载 链接阅读原文。



2 Transformer模型详解及注意点

Transformer

上图是Transformer的结构图解, 当中大致包含如下几个元素:

  • Position Encoding: 位置编码;
  • Position-wise Feed-Forward Networks: 即图中的Feed Forward模块, 这个其实是一个非常简单的模块, 简单实现就是一个只包含一个隐层的神经网络;
  • Multihead AttentionScaled Dot-Product Attention: 注意力机制;
  • EncoderDecoder: 编码器与解码器(核心部件);
  • 关于上述部件在本文3.1节中的transformer.py代码中都有相应的类与其对应, 并且笔者已经做了非常详细的注释(英文), 以下主要就实现上的细节做说明, 可结合本文3.1节中的transformer.py代码一起理解;
  1. Position-wise Feed-Forward Networks正如上述是一个非常简单的三层神经网络: F F N ( x ) = max ⁡ ( 0 , x W 1 + b 1 ) W 2 + b 2 {\rm FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2 FFN(x)=max(0,xW1+b1)W2+b2隐层中使用的是ReLU激活函数(即 max ⁡ ( 0 , x ) \max(0,x) max(0,x)), 但是要确保的是该模块的输入与输出的维度是完全相同的;

  2. 关于Position Encoding的理解:

  • Transformer中的Sinusoidal Position Encoding: P E ( p o s , 2 i ) = sin ⁡ ( p o s 1000 0 2 i d m o d e l ) P E ( p o s , 2 i + 1 ) = cos ⁡ ( p o s 1000 0 2 i d m o d e l ) {\rm PE}({\rm pos},2i)=\sin\left(\frac{\rm pos}{10000^{\frac{2i}{d_{\rm model}}}}\right)\\{\rm PE}({\rm pos},2i+1)=\cos\left(\frac{\rm pos}{10000^{\frac{2i}{d_{\rm model}}}}\right) PE(pos,2i)=sin(10000dmodel2ipos)PE(pos,2i+1)=cos(10000dmodel2ipos)
    • p o s \rm pos pos是位置的索引值, 即 0 , 1 , 2 , . . . , N − 1 0,1,2,...,N-1 0,1,2,...,N1, N为序列长度;
    • i i i是每个位置的Position Encoding的维度中的位置, 如将每个位置编码成64位的嵌入向量, 则 i i i取值范围就是 0 , 1 , 2 , . . . , 31 0,1,2,...,31 0,1,2,...,31, 2 i 2i 2i 2 i + 1 2i+1 2i+1分别表示奇数位与偶数位;
    • p o s + k {\rm pos}+k pos+k位置的encoding可以通过 p o s \rm pos pos位置的encoding线性表示得到: P E ( p o s + k , 2 i ) = sin ⁡ ( w i ( p o s + k ) ) = sin ⁡ ( w i p o s ) cos ⁡ ( w i k ) + cos ⁡ ( w i p o s ) sin ⁡ ( w i k ) P E ( p o s + k , 2 i + 1 ) = cos ⁡ ( w i ( p o s + k ) ) = cos ⁡ ( w i p o s ) cos ⁡ ( w i k ) − sin ⁡ ( w i p o s ) sin ⁡ ( w i k ) w i = 1 1000 0 2 i d m o d e l {\rm PE}({\rm pos}+k,2i)=\sin(w_i({\rm pos}+k))=\sin(w_i{\rm pos})\cos(w_ik)+\cos(w_i{\rm pos})\sin(w_ik)\\{\rm PE}({\rm pos}+k,2i+1)=\cos(w_i({\rm pos}+k))=\cos(w_i{\rm pos})\cos(w_ik)-\sin(w_i{\rm pos})\sin(w_ik)\\w_i=\frac{1}{10000^{\frac{2i}{d_{\rm model}}}} PE(pos+k,2i)=sin(wi(pos+k))=sin(wipos)cos(wik)+cos(wipos)sin(wik)PE(pos+k,2i+1)=cos(wi(pos+k))=cos(wipos)cos(wik)sin(wipos)sin(wik)wi=10000dmodel2i1化简得: P E ( p o s + k , 2 i ) = cos ⁡ ( w i k ) P E ( p o s , 2 i ) + sin ⁡ ( w i k ) P E ( p o s , 2 i + 1 ) P E ( p o s + k , 2 i + 1 ) = cos ⁡ ( w i k ) P E ( p o s , 2 i + 1 ) − sin ⁡ ( w i k ) P E ( p o s , 2 i ) {\rm PE}({\rm pos}+k,2i)=\cos(w_ik){\rm PE}({\rm pos},2i)+\sin(w_ik){\rm PE}({\rm pos,2i+1})\\{\rm PE}({\rm pos}+k,2i+1)=\cos(w_ik){\rm PE}({\rm pos,2i+1})-\sin(w_ik){\rm PE}({\rm pos,2i}) PE(pos+k,2i)=cos(wik)PE(pos,2i)+sin(wik)PE(pos,2i+1)PE(pos+k,2i+1)=cos(wik)PE(pos,2i+1)sin(wik)PE(pos,2i)
    • 可以看到其实Sinusoidal Position Encoding是呈周期性变化的;
  • BERT中的Position Encoding由嵌入层训练得到, 而非Transformer中的数学公式给定;
    class BertEmbeddings(nn.Module):
    
    	def __init__(self, config):
    		super().__init__()
    		self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id)	# (vocab_size, hidden_size)
    		self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)			# (512, hidden_size)
    		self.token_type_embeddings = nn.Embedding(config.type_vocab_size, config.hidden_size)				# (2, hidden_size)
    
    		# self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load
    		# any TensorFlow checkpoint file
    		self.LayerNorm = BertLayerNorm(config.hidden_size, eps=config.layer_norm_eps)
    		self.dropout = nn.Dropout(config.hidden_dropout_prob)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  1. 关于multihead attentionscaled dot-product attention输入张量形状说明:
  • scaled dot-product attention:
    A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K ⊤ d k ) V {\rm Attention}(Q, K, V) = {\rm softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V Attention(Q,K,V)=softmax(dk QK)V
    • q: 即查询矩阵 Q Q Q, 形状为(batch_size, n_head, len_q, d_q);
    • k: 即键矩阵 K K K, 形状为(batch_size, n_head, len_k, d_k);
    • v: 即值矩阵 V V V, 形状为(batch_size, n_head, len_v, d_v);
    • mask: 掩码矩阵, 形状应当为(batch_size, n_head, len_q, len_k), 不过其实只要(len_q, len_k)即可, 因为另外两个维度可以直接用unsqueezeextend来复制扩充;
    • 注意:
      • d_qd_k的大小必须相等;
      • len_klen_v大小必须相等;
      • 论文中为了便于处理还另外假设了d_q = d_k = d_v = d_model / n_head = 8, 并且d_model = 512, n_head = 8;
  • multihead attention: 假设该模块的输入输出维度分别为标量值d_input, d_output;
    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 h ) W O h e a d i = A t t e n t i o n ( Q W i Q , K W i K , V W i V ) {\rm MultiHead}(Q, K, V) = {\rm Concat}({\rm head}_1, ... , {\rm head}_h)W^O \\ {\rm head}_i = {\rm Attention}(QW_i^Q, KW_i^K, VW_i^V) MultiHead(Q,K,V)=Concat(head1,...,headh)WOheadi=Attention(QWiQ,KWiK,VWiV)
    • 其中:
      • W i Q ∈ R d m o d e l × d q W_i^Q \in \mathcal{R}^{d_{\rm model} × d_q} WiQRdmodel×dq
      • W i K ∈ R d m o d e l × d k W_i^K \in \mathcal{R}^{d_{\rm model} × d_k} WiKRdmodel×dk
      • W i V ∈ R d m o d e l × d v W_i^V \in \mathcal{R}^{d_{\rm model} × d_v} WiVRdmodel×dv
      • W O ∈ R h d v × d m o d e l W^O \in \mathcal{R}^{hd_v × d_{\rm model}} WORhdv×dmodel
      • h h h是多头注意力的头数;
      • 注意一定有 d q = d k d_q = d_k dq=dk成立;
      • As is mentioned in paper, h = 8 h = 8 h=8 and d k = d v = d m o d e l h = 64 d_k = d_v = \frac{d_{\rm model}}{h} = 64 dk=dv=hdmodel=64 is set as default.
    • q: 即查询矩阵 Q Q Q, 形状为(batch_size, len_q, d_q);
    • k: 即键矩阵 Q Q Q, 形状为(batch_size, len_k, d_k);
    • v: 即值矩阵 Q Q Q, 形状为(batch_size, len_v, d_v);
    • mask: 掩码矩阵, 同scaled dot-product attention中的描述;
    • 注意:
      • len_k, len_v应当被padding到等长, 即长度应为scaled dot-product attention中的len_k = len_v;
      • d_input等于 d m o d e l d_{\rm model} dmodel
      • d_output等于d_q(以及d_kd_v, 正如scaled dot-product attention中所提, 它们是相等的);
      • 特别地, 代码实现时可以设置d_input = d_output, 这样可以便于进行残差连接的计算(因为残差连接需要将输入加到输出上再归一化后得到最终输出);
  1. 关于multihead attentionscaled dot-product attention输出张量形状说明:
    attention
  • scaled dot-product attentionmultihead attention的一部分, 详细结构可以查看paper中的图
    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/469259
推荐阅读
相关标签