Transformer(三) -- 相关代码解读

因为有很多事情耽搁了,今天终于完成了Transformer的完整学习,接下来,将分为三篇文章来进行记录:

1. Transformer介绍

2. 相关博客

3. 相关代码解读

其实,最重要的就是这篇代码解读,只是看懂了论文,其实并不是真的懂了,对于我这码农,最重要的还是要能把论文变成代码,还好,伟大的github上,果然有我需要的代码,所以我赶紧下载下来,开始研读。github链接

比较不幸运的是,github作者在前阵子刚更新了代码,所以其github上的代码会与我接下来讲的内容会有些出入。

一. 文件结构

  • hyperparams.py 包含所需的所有参数
  • prepro.py 为源和目标创建词汇表文件
  • data_load.py 包含有关加载和批处理数据的函数
  • modules.py 具有用于编码器/解码器网络的所有构建块
  • train.py 模型整体架构
  • eval.py 用于测试

二. 模型结构

看了好久的代码,整理了一段不短地时间,终于把模型的架构,以及重要的代码块都理解了。一下是整理出来的模型结构。

三. 部分代码解读

1. position: 由于Attention没有像RNN那样有前后关系这一特性,所以加入位置编码来获取字词在居中的位置。

这一部分的代码就比较简单了,就是将论文中的公式复现。

        position_ind = tf.tile(tf.expand_dims(tf.range(T), 0), [N, 1])

        # First part of the PE function: sin and cos argument
        position_enc = np.array([
            [pos / np.power(10000, 2. * i / num_units) for i in range(num_units)]
            for pos in range(T)])

        # Second part, apply the cosine to even columns and sin to odds
        position_enc[:, 0::2] = np.sin(position_enc[:, 0::2])  # dim 2i
        position_enc[:, 1::2] = np.cos(position_enc[:, 1::2])  # dim 2i + 1

 其中,T表示的是句子的词数(实际长度为hp.maxlen)。

2. key_masks: 

key_masks = tf.expand_dims(tf.sign(tf.reduce_sum(tf.abs(self.enc), axis=-1)), -1)

 这个key_masks的作用我并不是很缺人,目前认为,其是为了增强有词的部分和弱化没有<padding>的部分

3. feedward

    with tf.variable_scope(scope, reuse=reuse):
        # Inner layer
        params = {"inputs": inputs, "filters": num_units[0], "kernel_size": 1,
                  "activation": tf.nn.relu, "use_bias": True}
        outputs = tf.layers.conv1d(**params)

        # Readout layer
        params = {"inputs": outputs, "filters": num_units[1], "kernel_size": 1,
                  "activation": None, "use_bias": True}
        outputs = tf.layers.conv1d(**params)

        # Residual connection
        outputs += inputs

        # Normalize
        outputs = normalize(outputs)

feedforward层是有两层卷积层拼接而成的,进行进一步的特征提取,其中第一层,将每个特征值变成四个特征值,然后第二层再将这些得到的四个特征值们拼接起来,再提取成1个特征值。这里就不具体介绍卷积相关的知识了。

4. multihead_Attention -- encoder

        # Linear projections
        Q = tf.layers.dense(queries, num_units, activation=tf.nn.relu)  # (N, T_q, C)
        K = tf.layers.dense(keys, num_units, activation=tf.nn.relu)  # (N, T_k, C)
        V = tf.layers.dense(keys, num_units, activation=tf.nn.relu)  # (N, T_k, C)

        # Split and concat
        Q_ = tf.concat(tf.split(Q, num_heads, axis=2), axis=0)  # (h*N, T_q, C/h)
        K_ = tf.concat(tf.split(K, num_heads, axis=2), axis=0)  # (h*N, T_k, C/h)
        V_ = tf.concat(tf.split(V, num_heads, axis=2), axis=0)  # (h*N, T_k, C/h)

        # Multiplication
        outputs = tf.matmul(Q_, tf.transpose(K_, [0, 2, 1]))  # (h*N, T_q, T_k)

        # Scale
        outputs = outputs / (K_.get_shape().as_list()[-1] ** 0.5)

        # key Masking
        key_masks = tf.sign(tf.reduce_sum(tf.abs(keys), axis=-1))  # (N, T_k)
        key_masks = tf.tile(key_masks, [num_heads, 1])  # (h*N, T_k)
        key_masks = tf.tile(tf.expand_dims(key_masks, 1), [1, tf.shape(queries)[1], 1])  # (h*N, T_q, T_k)

        paddings = tf.ones_like(outputs) * (-2 ** 32 + 1)
        outputs = tf.where(tf.equal(key_masks, 0), paddings, outputs)  # (h*N, T_q, T_k)

        # Causality = Future blinding
        if causality:
            diag_vals = tf.ones_like(outputs[0, :, :])  # (T_q, T_k)
            tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense()  # (T_q, T_k)
            masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(outputs)[0], 1, 1])  # (h*N, T_q, T_k)

            paddings = tf.ones_like(masks) * (-2 ** 32 + 1)
            outputs = tf.where(tf.equal(masks, 0), paddings, outputs)  # (h*N, T_q, T_k)

        # Activation
        outputs = tf.nn.softmax(outputs)  # (h*N, T_q, T_k)

        # Query Masking
        query_masks = tf.sign(tf.reduce_sum(tf.abs(queries), axis=-1))  # (N, T_q)
        query_masks = tf.tile(query_masks, [num_heads, 1])  # (h*N, T_q)
        query_masks = tf.tile(tf.expand_dims(query_masks, -1), [1, 1, tf.shape(keys)[1]])  # (h*N, T_q, T_k)
        outputs *= query_masks  # broadcasting (N, T_q, C)

        # Dropouts
        outputs = tf.layers.dropout(outputs, rate=dropout_rate, training=tf.convert_to_tensor(is_training))

        # Weighted sum
        outputs = tf.matmul(outputs, V_)  # (h*N, T_q, C/h)

        # Restore shape
        outputs = tf.concat(tf.split(outputs, num_heads, axis=0), axis=2)  # (N, T_q, C)

        # Residual connection
        outputs += queries

        # Normalize
        outputs = normalize(outputs)  # (N, T_q, C)

这里的第一个key_masks, query_masks是为了防止<pad>词加入到前向运算中,这是因为无论你的<pad>是否有可用的词编码,经过mask矩阵后,这个词编码都会被抛弃掉,置为0,那么这就不会参与到实际的运算中。但是,反向传播仍然会更新<pad>的词向量。

其中的大部分运算时参照公式:

Attention(Q,K,V)=softmax(\frac{QK^{T}}{\sqrt{d_{k}}})V

5.multihead_Attention -- decoder

        # Causality = Future blinding
        if causality:
            diag_vals = tf.ones_like(outputs[0, :, :])  # (T_q, T_k)
            tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense()  # (T_q, T_k)
            masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(outputs)[0], 1, 1])  # (h*N, T_q, T_k)

            paddings = tf.ones_like(masks) * (-2 ** 32 + 1)
            outputs = tf.where(tf.equal(masks, 0), paddings, outputs)  # (h*N, T_q, T_k)

接下来的这部分代码,可以说是seq2seq中最重要的部分了,这段代码使得Attention在计算中,前面的词不会考虑后面的词,这是怎么做到的呢? 首先,这次的mask矩阵的样子跟其他的不同,他是一个只有左下角和对角线全是1,其他都是0的矩阵,这样就能保证前面的词与后面的词运算的结果被遮掉。读者可以直接拿上一个向量[a, b, c], 以及对应的mask矩阵进行尝试。最后就会发现,结果的第一行,只有a,第二行只有a,b。

四. 总结

终于写完啦,妥了两个星期,中间多了很多事情,终于有一点点空余时间让我来把这些事情给做完。真好

猜你喜欢

转载自blog.csdn.net/qq_28634403/article/details/88120161