论文分享-- >Attention is all you need

本次分享的论文是鼎鼎有名的 a t t e n t i o n   i s   a l l   y o u   n e e d attention\ is\ all\ you\ need attention is all you need,论文链接attention is all you need,其参考的 t e n s o r f l o w tensorflow tensorflow 实现代码tensorflow代码实现

自己水平有限,在读这篇论文和实现代码时,感觉比较吃力,花了两三天才搞懂了一些,在此总结下。

废话不多说,直接带着代码看论文介绍的网络结构。

下面总结是以论文实验 机器翻译来说的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bTSPmlAh-1584426097531)(https://github.com/Njust-taoye/transformer-tensorflow/raw/master/images/transformer-architecture.png)]

我们分部分来看:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7SVz5ldn-1584426097532)(https://mchromiak.github.io/articles/2017/Sep/12/Transformer-Attention-is-all-you-need/img/encoder.png)]
建议可以先看看 台大教授李宏毅关于transformer的课程:https://www.bilibili.com/video/av56239558?from=search&seid=17256210640645614178

Stage1 Encode Input

和普遍的做法一样,对文本输入做 w o r d   e m b e d d i n g word\ embedding word embedding 操作,

embedding_encoder = tf.get_variable("embedding_encoder", [Config.data.source_vocab_size, Config.model.model_dim], self.dtype)(注意这里的model_dim)

embedding_inputs = embedding_encoder

上面其实就是做个输入文本的 e m b e d d i n g embedding embedding矩阵而已。

模型里已经剔除了 R N N RNN RNN C N N CNN CNN,如何体现输入文本的先后关系呢?而这种序列的先后关系对模型有着至关重要的作用,于是论文中提出了 P o s i t i o n   E n c o d i n g Position\ Encoding Position Encoding 骚操作~,论文是这样做的:

P E ( p o s , 2 i ) = s i n ( 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)=sin(pos/100002i/dmodel)
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+1) = cos(pos/10000^{2i/d_{model}})(奇数位置处) PE(pos,2i+1)=cos(pos/100002i/dmodel)

这里的 d m o d e l d_{model} dmodel指的是上面 w o r d   e m b e d d i n g word\ embedding word embedding 的维度, p o s pos pos就是当前的词在整个句子中的位置,例如第一个词还是第二个词等, i i i 就是遍历 d m o d e l d_{model} dmodel时的值,在代码中是这样做的:

def positional_encoding(dim, sentence_length, dtype=tf.float32):

    encoded_vec = np.array([pos/np.power(10000, 2*i/dim) for pos in range(sentence_length) for i in range(dim)])#对每个位置处都产生一个维度为dim的向量。
    encoded_vec[::2] = np.sin(encoded_vec[::2])#偶数位置处
    encoded_vec[1::2] = np.cos(encoded_vec[1::2])#奇数位置处

    return tf.convert_to_tensor(encoded_vec.reshape([sentence_length, dim]), dtype=dtype)
#Positional Encoding
with tf.variable_scope("positional-encoding"):
                positional_encoded = positional_encoding(Config.model.model_dim, Config.data.max_seq_length, dtype=self.dtype)

上面生成的 p o s i t i o n a l _ e n c o d e d positional\_encoded positional_encoded其实就是位置信息的 e m b e d d i n g embedding embedding 矩阵。论文中提到这样做的原因,就是希望模型能很容易的学到相对先后的位置信息。

# Add
position_inputs = tf.tile(tf.range(0, Config.data.max_seq_length), [self.batch_size])#将range(0, max_seq_length)列表复制batch_size次,生成shape为[batch_size, max_seq_length]的张量。

position_inputs = tf.reshape(position_inputs,[self.batch_size, Config.data.max_seq_length]) # batch_size x [0, 1, 2, ..., n]#未经过embedding的位置输入信息。

好了 输 入 文 本 和 位 置 信 息 的 e m b e d d i n g 输入文本和位置信息的embedding embedding矩阵都做好了,该 l o o k _ u p look\_up look_up 了。

encoded_inputs = tf.add(tf.nn.embedding_lookup(embedding_inputs, inputs),  tf.nn.embedding_lookup(positional_encoded, position_inputs))

这与输入文本信息就结合的其位置信息了,作为 e n c o d e r encoder encoder的整体输入,这部分对应的上面那张图的** s t a g e 1 stage1 stage1**部分,这部分的操作就是如下:

p o s i t i o n a l   e n c o d i n g s   d d e d ⊕ e m b e d d e d   i n p u t s positional\ encodings\ dded ⊕ embedded\ inputs positional encodings ddedembedded inputs

d e c o d e _ i n p u t   e m b e d d i n g decode\_input\ embedding decode_input embedding同理,就不再赘述了。

Stage2 Multi Head Attention

multi head attention 绝对是transform里面的 一个重点和优点,正是有了这个机制,transform才能有这么好的效果。

当时论文读到这里有点懵逼,什么叫 m u l t i   h e a d multi\ head multi head?再仔细看看论文吧?

这里写图片描述

由上图我们可以看出 m u l t i   h e a d   a t t e n t i o n multi\ head\ attention multi head attention 有三个相同的输入,不妨分别记为 Q 、 K 、 V Q、K、V QKV,其实就是上面的 E n c o d e   I n p u t Encode\ Input Encode Input s h a p e shape shape均为 [ b a t c h _ s i z e , m a x _ s e q _ l e n g t h , d i m ] [batch\_size, max\_seq\_length, dim] [batch_size,max_seq_length,dim]。论文中提到对三个输入做 n u m _ h e a d num\_head num_head次不同的线性映射,即为:

def _linear_projection(self, q, k, v):
    q = tf.layers.dense(q, self.linear_key_dim, use_bias=False)
    k = tf.layers.dense(k, self.linear_key_dim, use_bias=False)
    v = tf.layers.dense(v, self.linear_value_dim, use_bias=False)
	return q, k, v

上述代码就是做线性映射,其中 l i n e a r _ k e y _ d i m 、 l i n e a r _ v a l u e _ d i m linear\_key\_dim、linear\_value\_dim linear_key_dimlinear_value_dim就是映射的 u n i t s units units个数。这里面相当于把 n u m _ h e a d num\_head num_head次的线性映射一起做了,后面需要把每一个 h e a d head head映射结果分割开,故需要保证 l i n e a r _ k e y _ d i m linear\_key\_dim linear_key_dim l i n e a r _ v a l u e _ d i m linear\_value\_dim linear_value_dim 能整除 n u m _ h e a d num\_head num_head。 经过线性映射后生成的 q 、 k 、 v q、k、v qkv s h a p e shape shape分别为 [ b a t c h _ s i z e , m a x _ s e q _ l e n g t h , l i n e a r _ k e y _ d i m ] , [ b a t c h _ s i z e , m a x _ s e q _ l e n g t h , l i n e a r _ k e y _ d i m ] , [ b a t c h _ s i z e , m a x _ s e q _ l e n g t h , l i n e a r _ v a l u e _ d i m ] [batch\_size, max\_seq\_length, linear\_key\_dim], [batch\_size, max\_seq\_length, linear\_key\_dim], [batch\_size, max\_seq\_length, linear\_value\_dim] [batch_size,max_seq_length,linear_key_dim],[batch_size,max_seq_length,linear_key_dim],[batch_size,max_seq_length,linear_value_dim]

然后按 n u m _ h e a d s num\_heads num_heads分割开来得:(这里分割开,相当于初始个num_head 不同的权重,下面将会做num_head 次不同的attention操作理解这非常重要

def _split_heads(self, q, k, v): 
    def split_last_dimension_then_transpose(tensor, num_heads, dim):
    ┆   t_shape = tensor.get_shape().as_list()
    ┆   tensor = tf.reshape(tensor, [-1] + t_shape[1:-1] + [num_heads, dim // num_heads])
    ┆   return tf.transpose(tensor, [0, 2, 1, 3]) # [batch_size, num_heads, max_seq_len, dim]

    qs = split_last_dimension_then_transpose(q, self.num_heads, self.linear_key_dim)
    ks = split_last_dimension_then_transpose(k, self.num_heads, self.linear_key_dim)
    vs = split_last_dimension_then_transpose(v, self.num_heads, self.linear_value_dim)

    return qs, ks, vs

论文提到,这时生成的 q s 、 k s 、 v s qs、ks、vs qsksvs可以并行的放入到 a t t e n t i o n _ f u n c t i o n attention\_function attention_function 中,那么这个 a t t e n t i o n _ f u n c t i o n attention\_function attention_function 是个什么样的结构呢?

这里写图片描述

上图所示的结构在论文中被称为 S c a l e d   D o t _ P r o d u c t   A t t e n t i o n Scaled\ Dot\_Product\ Attention Scaled Dot_Product Attention,其 a t t e n t i o n attention attention值的计算公式如下:
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\left ( \frac{QK^{T}}{\sqrt{d_k}} \right )V Attention(Q,K,V)=softmax(dk QKT)V

在这里插入图片描述


由上面可知,其公式中的 Q 、 K 、 V Q、K、V QK 分别对应的是 q s 、 k s 、 v s qs、ks、vs qsksvs,其实都是 E n c o d e r   I n p u t Encoder\ Input Encoder Input只是做了不同的线性映射,其中 q s 、 k s qs、ks qsks 的维度相同。我们可以这样理解 Q K T QK^T QKT操作,假设 Q Q Q s h a p e shape shape [ m a x _ s e q _ l e n g t h , d i m ] [max\_seq\_length, dim] [max_seq_length,dim]的矩阵, V V V s h a p e shape shape相同,那么经过 Q K T QK^T QKT操作以后,变成了 s h a p e shape shape [ m a x _ s e q _ l e n g t h , m a x _ s e q _ l e n g t h ] [max\_seq\_length, max\_seq\_length] [max_seq_length,max_seq_length]的矩阵,怎样理解这个生成的矩阵呢?其实就是做了个 s e l f _ a t t e n t i o n self\_attention self_attention操作,即是当前句子中每个词和其他词做个乘积形成的矩阵,以得到每个词的权重,以便学习当前应该 f o u c s foucs foucs到哪个词。那么为什么要除以 d k \sqrt{d_k} dk 呢?论文中说到,当两个矩阵做 d o t   p r o d u c t dot\ product dot product时,可能会变得很大(试想一下,两个矩阵相互独立,且均值为0,方差为1,那么经过矩阵相乘以后,均值还为0,方差变成 d k d_k dk),经过 s o f t m a x softmax softmax后,梯度可能会变得很小,为了抵消这种效果,再除以 d k \sqrt{d_k} dk 。其代码如下:

def _scaled_dot_product(self, qs, ks, vs):
    key_dim_per_head = self.linear_key_dim // self.num_heads

    o1 = tf.matmul(qs, ks, transpose_b=True)
    o2 = o1 / (key_dim_per_head**0.5)

    if self.masked:
    ┆   diag_vals = tf.ones_like(o2[0, 0, :, :]) # (batch_size, num_heads, query_dim, key_dim)
    ┆   tril = tf.contrib.linalg.LinearOperatorTriL(diag_vals).to_dense() # (q_dim, k_dim)
    ┆   masks = tf.tile(tf.reshape(tril, [1, 1] + tril.get_shape().as_list()),
    ┆   ┆   ┆   ┆   ┆   [tf.shape(o2)[0], tf.shape(o2)[1], 1, 1])
    ┆   paddings = tf.ones_like(masks) * -1e9
    ┆   o2 = tf.where(tf.equal(masks, 0), paddings, o2)

    o3 = tf.nn.softmax(o2)
    return o3

好了,过了 s e l f _ a t t e n t i o n self\_attention self_attention后:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XPjiunL2-1584426097533)(https://mchromiak.github.io/articles/2017/Sep/12/Transformer-Attention-is-all-you-need/img/MultiHead.png)]

由上图可知,再过 c o n c a t concat concat操作:(不同的head关注的点可能不一样,这里concat操作,相当于把num_head次不同的attention结果集成在一起,理解这非常重要

def _concat_heads(self, outputs):

    def transpose_then_concat_last_two_dimenstion(tensor):
    ┆   tensor = tf.transpose(tensor, [0, 2, 1, 3]) # [batch_size, max_seq_len, num_heads, dim]
    ┆   t_shape = tensor.get_shape().as_list()
    ┆   num_heads, dim = t_shape[-2:]
    ┆   return tf.reshape(tensor, [-1] + t_shape[1:-2] + [num_heads * dim])

    return transpose_then_concat_last_two_dimenstion(outputs)

论文中提到,这样做后,再过一层线性映射。

output = tf.layers.dense(output, self.model_dim)

故整个 M u l t i   H e a d   A t t e n t i o n Multi\ Head\ Attention Multi Head Attention 操作如下:

def multi_head(self, q, k, v):
    q, k, v = self._linear_projection(q, k, v)
    qs, ks, vs = self._split_heads(q, k, v)
    outputs = self._scaled_dot_product(qs, ks, vs)
    output = self._concat_heads(outputs)
    output = tf.layers.dense(output, self.model_dim)
    return tf.nn.dropout(output, 1.0 - self.dropout)

然后在做个 r e s N e t resNet resNet l a y e r N o r m a l i z a t i o n layerNormalization layerNormalization

def _add_and_norm(self, x, sub_layer_x, num=0):
    with tf.variable_scope(f"add-and-norm-{num}"):
    ┆   return tf.contrib.layers.layer_norm(tf.add(x, sub_layer_x)) # with Residual connection

这里面的 s u b _ l a y e r _ x sub\_layer\_x sub_layer_x 就是上面 m u t l i   h e a d mutli\ head mutli head的输出, x x x就是 e n c o d e r _ i n p u t encoder\_input encoder_input
矩阵并行化计算过程:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

上面就是 S t a g e 2 Stage2 Stage2 的整个过程。

Stage3 Feed Forward

这一步就比较简单了,就是做两层的 f u l l y _ c o n n e c t i o n fully\_connection fully_connection 而已,只不过内层的 f u l l y _ c o n n e c t i o n fully\_connection fully_connection 会过 r e l u relu 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

别问我为啥 m a x max max

同理再过 r e N e t 、 n o r m a l i z a t i o n reNet、normalization reNetnormalization

D e c o d e Decode Decode部分和上面差不多,只不过在 D e c o d e   I n p u t Decode\ Input Decode Input S t a g e 1 Stage1 Stage1部分,做 s e l f   a t t e n t i o n self\ attention self attention时,我们不能使当前词的后面的词对当前词产生影响,因为在当前我们实际是不知道后面应该有哪些词的,只不过在 t r a i n train train的时候可以批量的训练,但是在 d e c o d e decode decode的时候是不知道的。那么该怎么消除后面词对当前词的影响呢?

s e l f   a t t e n t i o n self\ attention self attention时,会得到 a t t e n t i o n attention attention矩阵,我们只需要保留该矩阵的下三角部分即可,然后再做 s o f t m a x softmax softmax,既可消除后面词对当前词的影响。

def _scaled_dot_product(self, qs, ks, vs):
    key_dim_per_head = self.linear_key_dim // self.num_heads

    o1 = tf.matmul(qs, ks, transpose_b=True)
    o2 = o1 / (key_dim_per_head**0.5)

    if self.masked:
    ┆   diag_vals = tf.ones_like(o2[0, 0, :, :]) # (batch_size, num_heads, query_dim, key_dim)
    ┆   tril = tf.contrib.linalg.LinearOperatorTriL(diag_vals).to_dense() # (q_dim, k_dim)
    ┆   masks = tf.tile(tf.reshape(tril, [1, 1] + tril.get_shape().as_list()),
    ┆   ┆   ┆   ┆   ┆   [tf.shape(o2)[0], tf.shape(o2)[1], 1, 1])
    ┆   paddings = tf.ones_like(masks) * -1e9
    ┆   o2 = tf.where(tf.equal(masks, 0), paddings, o2)

    o3 = tf.nn.softmax(o2)
    return o3

剩余部分的 S t a g e 4 、 S t a g e 5 Stage4、Stage5 Stage4Stage5与上面类似,就不再赘述了。

整个论文的过程可以用如下动画解释:

在这里插入图片描述

个人看法

  1. 该论文摈弃了 R N N 、 C N N RNN、CNN RNNCNN 等作为基本的模型,而是单纯的采用 A t t e n t i o n Attention Attention结构,使得计算并行性大大提高。
  2. 没想到 a t t e n t i o n attention attention也可以单独的作为神经网络的一层,甚至可以看作对 i n p u t input input r e p e s e n t a t i o n repesentation repesentation
  3. Transformer的多头注意力机制能从不同角度对每个对象的重要性进行评价,从而能更好的学习输入对象中存在的各种关系并对其进行表征。
    在这里插入图片描述
    不同的head, attention的注意力不一样
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值