Transformer解析与tensorflow代码解读【未完】

时间:2019-06-18
本文章向大家介绍Transformer解析与tensorflow代码解读【未完】,主要包括Transformer解析与tensorflow代码解读【未完】使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

本文是针对谷歌Transformer模型的解读,根据我自己的理解顺序记录的。

另外,针对Kyubyong实现的tensorflow代码进行解读,代码地址https://github.com/Kyubyong/transformer

这里不会详细描述Transformer的实现机理,如果有不了解Transformer的可以先阅读文章《Attention is all you need》,以及我列出的一些参考博客,都是不错的解读。

Layer Normalization

首先是Layer Normalization部分,和Batch Normalization有点不一样,BN能够让模型收敛的更快,但是BN的缺点也比较明显。

BN的缺点:

  1,BN特别依赖Batch Size;当Batch size很小的适合,BN的效果就非常不理想了。在很多情况下,Batch size大不了,因为你GPU的显存不够。所以,通常会有其他比较麻烦的手段去解决这个问题,比如MegDet的CGBN等;

  2,BN对处理序列化数据的网络比如RNN是不太适用的;So,BN的应用领域减少了一半。

  3,BN只在训练的时候用,inference的时候不会用到,因为inference的输入不是批量输入。这也不一定是BN的缺点,但这是BN的特点。

BN是在batch的方向上计算均值方差,而LN是在每一条数据维度的方向上计算均值方差,换句话说,LN的操作类似于将BN做了一个“转置”,对同一层网络的输出做一个标准化。下图比较清晰:

 1 def ln(inputs, epsilon = 1e-8, scope="ln"):
 2     '''Applies layer normalization. See https://arxiv.org/abs/1607.06450.
 3     inputs: A tensor with 2 or more dimensions, where the first dimension has `batch_size`.
 4     epsilon: A floating number. A very small number for preventing ZeroDivision Error.
 5     scope: Optional scope for `variable_scope`.
 6       
 7     Returns:
 8       A tensor with the same shape and data dtype as `inputs`.
 9     '''
10 
11 
12     '''
13         使用层归一layer normalization
14         tensorflow 在实现 Batch Normalization(各个网络层输出的归一化)时,主要用到nn.moments和batch_normalization
15         其中moments作用是统计矩,mean 是一阶矩,variance 则是二阶中心矩
16         tf.nn.moments 计算返回的 mean 和 variance 作为 tf.nn.batch_normalization 参数进一步调用
17         :param inputs: 一个有2个或更多维度的张量,第一个维度是batch_size
18         :param epsilon: 很小的数值,防止区域划分错误
19         :param scope: 
20         :return: 返回一个与inputs相同shape和数据的dtype
21         '''
22     with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
23         inputs_shape = inputs.get_shape()
24         params_shape = inputs_shape[-1:]
25     
26         mean, variance = tf.nn.moments(inputs, [-1], keep_dims=True)
27         beta= tf.get_variable("beta", params_shape, initializer=tf.zeros_initializer())
28         gamma = tf.get_variable("gamma", params_shape, initializer=tf.ones_initializer())
29         normalized = (inputs - mean) / ( (variance + epsilon) ** (.5) )
30         outputs = gamma * normalized + beta
31         
32     return outputs
View Code

Mask

mask表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果。Transformer 模型里面涉及两种 mask,分别是 padding mask 和 sequence mask。

其中,padding mask 在所有的 scaled dot-product attention 里面都需要用到,而 sequence mask 只有在 decoder 的 self-attention 里面用到。

Padding Mask

对于输入序列一般我们都要进行padding补齐,也就是说设定一个统一长度N,在较短的序列后面填充0到长度为N,如果输入的序列长度大于N,则截取左边长度为N的内容,把多余的直接舍弃。对于那些补零的数据来说,我们的attention机制不应该把注意力放在这些位置上,所以我们需要进行一些处理。具体的做法是,把这些位置的值加上一个非常大的负数(负无穷),这样经过softmax后,这些位置的权重就会接近0。Transformer的padding mask实际上是一个张量,每个值都是一个Boolean,值为false的地方就是要进行处理的地方。

Sequence Mask

sequence mask是为了使decoder不能看见未来的信息。因为Transformer不是rnn结构的,因此我们要想办法在time_step 为 t 的时刻,把 t 时刻之后的信息隐藏起来。具体做法就是产生一个上三角矩阵,上三角的值全为0,把这个矩阵作用在每一个序列上。

  对于 decoder 的 self-attention,里面使用到的 scaled dot-product attention,同时需要padding mask 和 sequence mask 作为 attn_mask,具体实现就是两个mask相加作为attn_mask。
  其他情况,attn_mask 一律等于 padding mask。

 这边代码中会用到一些tf的函数,一个比较有用的tf.where()的用法:https://blog.csdn.net/ustbbsy/article/details/79564828

 注意这段代码里面type in ("f", "future", "right"): 部分是描述用一个下三角矩阵来做sequence mask的。

 1 def mask(inputs, queries=None, keys=None, type=None):
 2     '''
 3             对Keys或Queries进行遮盖
 4             :param inputs: (N, T_q, T_k)
 5             :param queries: (N, T_q, d)
 6             :param keys: (N, T_k, d)
 7             :return:
 8     '''
 9     """Masks paddings on keys or queries to inputs
10     inputs: 3d tensor. (N, T_q, T_k)
11     queries: 3d tensor. (N, T_q, d)
12     keys: 3d tensor. (N, T_k, d)
13 
14     e.g.,
15     >> queries = tf.constant([[[1.],
16                         [2.],
17                         [0.]]], tf.float32) # (1, 3, 1)
18     >> keys = tf.constant([[[4.],
19                      [0.]]], tf.float32)  # (1, 2, 1)
20     >> inputs = tf.constant([[[4., 0.],
21                                [8., 0.],
22                                [0., 0.]]], tf.float32)
23     >> mask(inputs, queries, keys, "key")
24     array([[[ 4.0000000e+00, -4.2949673e+09],
25         [ 8.0000000e+00, -4.2949673e+09],
26         [ 0.0000000e+00, -4.2949673e+09]]], dtype=float32)
27     >> inputs = tf.constant([[[1., 0.],
28                              [1., 0.],
29                               [1., 0.]]], tf.float32)
30     >> mask(inputs, queries, keys, "query")
31     array([[[1., 0.],
32         [1., 0.],
33         [0., 0.]]], dtype=float32)
34     """
35 
36     padding_num = -2 ** 32 + 1
37     if type in ("k", "key", "keys"):
38         # Generate masks
39         masks = tf.sign(tf.reduce_sum(tf.abs(keys), axis=-1))  # (N, T_k)
40         masks = tf.expand_dims(masks, 1) # (N, 1, T_k)
41         masks = tf.tile(masks, [1, tf.shape(queries)[1], 1])  # (N, T_q, T_k)
42 
43         # Apply masks to inputs
44         paddings = tf.ones_like(inputs) * padding_num
45         outputs = tf.where(tf.equal(masks, 0), paddings, inputs)  # (N, T_q, T_k)
46     elif type in ("q", "query", "queries"):
47         # Generate masks
48         masks = tf.sign(tf.reduce_sum(tf.abs(queries), axis=-1))  # (N, T_q)
49         masks = tf.expand_dims(masks, -1)  # (N, T_q, 1)
50         masks = tf.tile(masks, [1, 1, tf.shape(keys)[1]])  # (N, T_q, T_k)
51 
52         # Apply masks to inputs
53         outputs = inputs*masks
54     elif type in ("f", "future", "right"):
55         diag_vals = tf.ones_like(inputs[0, :, :])  # (T_q, T_k)
56         tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense()  # (T_q, T_k)
57         masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(inputs)[0], 1, 1])  # (N, T_q, T_k)
58 
59         paddings = tf.ones_like(masks) * padding_num
60         outputs = tf.where(tf.equal(masks, 0), paddings, inputs)
61     else:
62         print("Check if you entered type correctly!")
63 
64 
65     return outputs
View Code

Context-Attention

也就是论文里提到的Encoder-Decoder Attention,是两个不同序列之间的attention,与来源于自身的 self-attention 相区别。context-attention有很多,这里使用的是scaled dot-product。通过 query 和 key 的相似性程度来确定 value 的权重分布。

实际上这部分代码就是self attention用到的QKV的公式的核心代码,不管是Encoder-Decoder Attention还是Self Attention都是用的这里的scaled dot-product方法。

 1 def scaled_dot_product_attention(Q, K, V,
 2                                  causality=False, dropout_rate=0.,
 3                                  training=True,
 4                                  scope="scaled_dot_product_attention"):
 5     '''See 3.2.1.
 6     Q: Packed queries. 3d tensor. [N, T_q, d_k].
 7     K: Packed keys. 3d tensor. [N, T_k, d_k].
 8     V: Packed values. 3d tensor. [N, T_k, d_v].
 9     causality: If True, applies masking for future blinding
10     dropout_rate: A floating point number of [0, 1].
11     training: boolean for controlling droput
12     scope: Optional scope for `variable_scope`.
13     '''
14     '''
15         查看原论文中3.2.1attention计算公式:Attention(Q,K,V)=softmax(Q K^T /√dk ) V
16         :param Q: 查询,三维张量,[N, T_q, d_k].
17         :param K: keys值,三维张量,[N, T_k, d_v].
18         :param V: values值,三维张量,[N, T_k, d_v].
19         :param causality: 布尔值,如果为True,就会对未来的数值进行遮盖
20         :param dropout_rate: 0到1之间的一个数值
21         :param training: 布尔值,用来控制dropout
22         :param scope: 
23     '''
24     with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
25         d_k = Q.get_shape().as_list()[-1]
26 
27         # dot product
28         outputs = tf.matmul(Q, tf.transpose(K, [0, 2, 1]))  # (N, T_q, T_k)
29 
30         # scale
31         outputs /= d_k ** 0.5
32 
33         # key masking
34         outputs = mask(outputs, Q, K, type="key")
35 
36         # causality or future blinding masking
37         if causality:
38             outputs = mask(outputs, type="future")
39 
40         # softmax
41         outputs = tf.nn.softmax(outputs)
42         attention = tf.transpose(outputs, [0, 2, 1])
43         tf.summary.image("attention", tf.expand_dims(attention[:1], -1))
44 
45         # query masking
46         outputs = mask(outputs, Q, K, type="query")
47 
48         # dropout
49         outputs = tf.layers.dropout(outputs, rate=dropout_rate, training=training)
50 
51         # weighted sum (context vectors)
52         outputs = tf.matmul(outputs, V)  # (N, T_q, d_v)
53 
54     return outputs
View Code

Multi-head attention

多头self attention就是Transoformer的核心,就是用上面提到的QKV公式算出分布之后,用h份合在一起来表示,论文中的h为8。

这部分代码主要是先产生QKV向量,然后按照h头来进行划分,然后调用上面的scaled dot-product的方法来计算的。

另外这里可以看到代码里将8份self attention分别计算后后concat起来了,然后在self attention层后接了残差连接和layer normalization。

 1 def multihead_attention(queries, keys, values,
 2                         num_heads=8, 
 3                         dropout_rate=0,
 4                         training=True,
 5                         causality=False,
 6                         scope="multihead_attention"):
 7     '''Applies multihead attention. See 3.2.2
 8     queries: A 3d tensor with shape of [N, T_q, d_model].
 9     keys: A 3d tensor with shape of [N, T_k, d_model].
10     values: A 3d tensor with shape of [N, T_k, d_model].
11     num_heads: An int. Number of heads.
12     dropout_rate: A floating point number.
13     training: Boolean. Controller of mechanism for dropout.
14     causality: Boolean. If true, units that reference the future are masked.
15     scope: Optional scope for `variable_scope`.
16         
17     Returns
18       A 3d tensor with shape of (N, T_q, C)  
19     '''
20     '''
21         查看原论文中3.2.2中multihead_attention构建,
22         这里是将不同的Queries、Keys和values方式线性地投影h次是有益的。
23         线性投影分别为dk,dk和dv尺寸。在每个预计版本进行queries、keys、values,
24         然后并行执行attention功能,产生dv维输出值。这些被连接并再次投影,产生最终值
25         :param queries: 三维张量[N, T_q, d_model]
26         :param keys: 三维张量[N, T_k, d_model]
27         :param values: 三维张量[N, T_k, d_model]
28         :param num_heads: heads数
29         :param dropout_rate: 
30         :param training: 控制dropout机制
31         :param causality: 控制是否遮盖
32         :param scope: 
33         :return: 三维张量(N, T_q, C) 
34     '''
35     d_model = queries.get_shape().as_list()[-1]
36     with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
37         # Linear projections
38         Q = tf.layers.dense(queries, d_model, use_bias=False) # (N, T_q, d_model)
39         K = tf.layers.dense(keys, d_model, use_bias=False) # (N, T_k, d_model)
40         V = tf.layers.dense(values, d_model, use_bias=False) # (N, T_k, d_model)
41         
42         # Split and concat
43         Q_ = tf.concat(tf.split(Q, num_heads, axis=2), axis=0) # (h*N, T_q, d_model/h)
44         K_ = tf.concat(tf.split(K, num_heads, axis=2), axis=0) # (h*N, T_k, d_model/h)
45         V_ = tf.concat(tf.split(V, num_heads, axis=2), axis=0) # (h*N, T_k, d_model/h)
46 
47         # Attention
48         outputs = scaled_dot_product_attention(Q_, K_, V_, causality, dropout_rate, training)
49 
50         # Restore shape
51         outputs = tf.concat(tf.split(outputs, num_heads, axis=0), axis=2 ) # (N, T_q, d_model)
52               
53         # Residual connection
54         outputs += queries
55               
56         # Normalize
57         outputs = ln(outputs)
58  
59     return outputs
View Code

Positional Embedding

就目前而言,Transformer 架构还没有提取序列顺序的信息,这个信息对于序列而言非常重要,如果缺失了这个信息,可能我们的结果就是:所有词语都对了,但是无法组成有意义的语句。因此模型对序列中的词语出现的位置进行编码。论文中使用的方法是在偶数位置使用正弦编码,在奇数位置使用余弦编码。

代码里有一点,

N, T = tf.shape(inputs)[0], tf.shape(inputs)[1]
position_ind = tf.tile(tf.expand_dims(tf.range(T), 0), [N, 1]) # (N, T)
outputs = tf.nn.embedding_lookup(position_enc, position_ind)

 这里为什么直接用tf.range()之后,建立好了position_enbedding之后直接lookup呢,因为输入的句子顺序本来就是0,1,2,...,T,本来就是顺序输入的。

 1 def positional_encoding(inputs,
 2                         maxlen,
 3                         masking=True,
 4                         scope="positional_encoding"):
 5     '''Sinusoidal Positional_Encoding. See 3.5
 6     inputs: 3d tensor. (N, T, E)
 7     maxlen: scalar. Must be >= T
 8     masking: Boolean. If True, padding positions are set to zeros.
 9     scope: Optional scope for `variable_scope`.
10 
11     returns
12     3d tensor that has the same shape as inputs.
13     '''
14     '''
15         参看论文3.5,由于模型没有循环和卷积,为了让模型知道句子的编号,
16         就必须加入某些绝对位置信息,来表示token之间的关系。  
17         positional encoding和embedding有相同的维度,这两个能够相加。
18         :param inputs: 
19         :param maxlen: 
20         :param masking: 
21         :param scope: 
22         :return: 
23     '''
24 
25     E = inputs.get_shape().as_list()[-1] # static
26     N, T = tf.shape(inputs)[0], tf.shape(inputs)[1] # dynamic
27     with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
28         # position indices
29         position_ind = tf.tile(tf.expand_dims(tf.range(T), 0), [N, 1]) # (N, T)
30 
31         # First part of the PE function: sin and cos argument
32         position_enc = np.array([
33             [pos / np.power(10000, (i-i%2)/E) for i in range(E)]
34             for pos in range(maxlen)])
35 
36         # Second part, apply the cosine to even columns and sin to odds.
37         position_enc[:, 0::2] = np.sin(position_enc[:, 0::2])  # dim 2i
38         position_enc[:, 1::2] = np.cos(position_enc[:, 1::2])  # dim 2i+1
39         position_enc = tf.convert_to_tensor(position_enc, tf.float32) # (maxlen, E)
40 
41         # lookup
42         outputs = tf.nn.embedding_lookup(position_enc, position_ind)
43 
44         # masks
45         if masking:
46             outputs = tf.where(tf.equal(inputs, 0), inputs, outputs)
47 
48         return tf.to_float(outputs)
View Code

其他一些小模块

 还有一些小模块比较简单,比如前向网络,前向网络是两层全连接层接一个残差连接和layer normalization。  

还用了一个Label Smoothing技术,简单来说就是本来ground truth标签是1的,他改到比如说0.9333,本来是0的,他改到0.0333,这是一个比较经典的平滑技术了。

另外值得注意的是这里用了一个Noam计划衰减学习率,我之前没怎么接触过这种,网上资料也不多,我自己写了个公式:

 1 def ff(inputs, num_units, scope="positionwise_feedforward"):
 2     '''position-wise feed forward net. See 3.3
 3     
 4     inputs: A 3d tensor with shape of [N, T, C].
 5     num_units: A list of two integers.
 6     scope: Optional scope for `variable_scope`.
 7 
 8     Returns:
 9       A 3d tensor with the same shape and dtype as inputs
10     '''
11     with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
12         # Inner layer
13         outputs = tf.layers.dense(inputs, num_units[0], activation=tf.nn.relu)
14 
15         # Outer layer
16         outputs = tf.layers.dense(outputs, num_units[1])
17 
18         # Residual connection
19         outputs += inputs
20         
21         # Normalize
22         outputs = ln(outputs)
23     
24     return outputs
25 
26 def label_smoothing(inputs, epsilon=0.1):
27     '''Applies label smoothing. See 5.4 and https://arxiv.org/abs/1512.00567.
28     inputs: 3d tensor. [N, T, V], where V is the number of vocabulary.
29     epsilon: Smoothing rate.
30     
31     For example,
32     
33     ```
34     import tensorflow as tf
35     inputs = tf.convert_to_tensor([[[0, 0, 1], 
36        [0, 1, 0],
37        [1, 0, 0]],
38 
39       [[1, 0, 0],
40        [1, 0, 0],
41        [0, 1, 0]]], tf.float32)
42        
43     outputs = label_smoothing(inputs)
44     
45     with tf.Session() as sess:
46         print(sess.run([outputs]))
47     
48     >>
49     [array([[[ 0.03333334,  0.03333334,  0.93333334],
50         [ 0.03333334,  0.93333334,  0.03333334],
51         [ 0.93333334,  0.03333334,  0.03333334]],
52 
53        [[ 0.93333334,  0.03333334,  0.03333334],
54         [ 0.93333334,  0.03333334,  0.03333334],
55         [ 0.03333334,  0.93333334,  0.03333334]]], dtype=float32)]   
56     ```    
57     '''
58     V = inputs.get_shape().as_list()[-1] # number of channels
59     return ((1-epsilon) * inputs) + (epsilon / V)
60 
61 
62 def noam_scheme(init_lr, global_step, warmup_steps=4000.):
63     '''Noam scheme learning rate decay
64     init_lr: initial learning rate. scalar.
65     global_step: scalar.
66     warmup_steps: scalar. During warmup_steps, learning rate increases
67         until it reaches init_lr.
68     '''
69     step = tf.cast(global_step + 1, dtype=tf.float32)
70     return init_lr * warmup_steps ** 0.5 * tf.minimum(step * warmup_steps ** -1.5, step ** -0.5)
View Code

参考

https://blog.csdn.net/u012526436/article/details/86295971

https://www.jianshu.com/p/6670f775625f

原文地址:https://www.cnblogs.com/zhouxiaosong/p/11032431.html