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
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
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
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
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)
其他一些小模块
还有一些小模块比较简单,比如前向网络,前向网络是两层全连接层接一个残差连接和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)
参考
https://blog.csdn.net/u012526436/article/details/86295971
https://www.jianshu.com/p/6670f775625f
原文地址:https://www.cnblogs.com/zhouxiaosong/p/11032431.html
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 如何实现四元数的运算
- 最牛一篇布隆过滤器详解
- 编写一个IDEA插件之:开发环境准备那些坑
- 编写一个IDEA插件之:使用PSI分析Java代码
- 编写一个IDEA插件之:自动生成Java代码
- 编写一个IDEA插件之:事件监听
- 重新加载故障节点上的 Ceph 卷
- 一个Spring Bean从诞生到逝去的九次人生转折!
- 原创 | 详解git rebase,让你走上git大神之路
- 启用chrome浏览器内置的二维码生成插件
- ZeroLogon漏洞(CVE-2020-1472)防御性指南
- 原创 | 随机数大家都会用,但是你知道生成随机数的算法吗?
- 原创 | codeforces 1425E,一万种情况的简单题
- 原创 | codeforces 1417C,逆向思考的数据结构题
- 原创 | 操作失误不要慌,这个命令给你的Git一次反悔的机会