【Day25】 Transformer 实作包(二)

开始施工

  • 今天我们要来制作 Transformer Decoder 的部分,一样先上个图方便施工

以下都跟 Hung-yi Lee 老师及 Tensorflow tutorial 几乎一样

Create DecoderLayer

#  一个 Decoder 里头有 N 个 DecoderLayer,
#  一个 DecoderLayer 有三个 sub-layers
#            1. 自注意的 MHA, 
#            2. 关注 Encoder 输出的 MHA
#            3. FFN
class DecoderLayer(tf.keras.layers.Layer):
  def __init__(self, d_model, num_heads, dff, rate=0.1):
    super(DecoderLayer, self).__init__()

    self.ffn = point_wise_feed_forward_network(d_model, dff)

    # 定义每个 sub-layer 用的 LayerNorm
    self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
    self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
    self.layernorm3 = tf.keras.layers.LayerNormalization(epsilon=1e-6)

    # 定义每个 sub-layer 用的 Dropout
    self.dropout1 = tf.keras.layers.Dropout(rate)
    self.dropout2 = tf.keras.layers.Dropout(rate)
    self.dropout3 = tf.keras.layers.Dropout(rate)


  def call(self, x, enc_output, training,combined_mask, inp_padding_mask):
    
    # enc_output 为 Encoder 输出序列,shape 为 (batch_size, input_seq_len, d_model)
    # attn_weights_block_1 则为 (batch_size, num_heads, target_seq_len, target_seq_len)
    # attn_weights_block_2 则为 (batch_size, num_heads, target_seq_len, input_seq_len)

    # sub-layer 1: Decoder layer 自己对输出序列做注意力。
    # 我们同时需要 look ahead mask 以及输出序列的 padding mask 
    attn1, attn_weights_block1 = do_MultiHeadAttention(x,x,x,combined_mask)
    attn1 = self.dropout1(attn1, training=training)
    out1 = self.layernorm1(attn1 + x)

    # sub-layer 2: Decoder layer 关注 Encoder 的最後输出
    # 记得我们一样需要对 Encoder 的输出套用 padding mask 
    
    attn2, attn_weights_block2 = do_MultiHeadAttention(
                                                        enc_output,
                                                        enc_output,
                                                        out1,
                                                        inp_padding_mask
                                                       ) 
    # (batch_size, target_seq_len, d_model)
    attn2 = self.dropout2(attn2, training=training)
    out2 = self.layernorm2(attn2 + out1)  # (batch_size, target_seq_len, d_model)

    # sub-layer 3: FFN 部分跟 Encoder layer 完全一样
    ffn_output = self.ffn(out2)  # (batch_size, target_seq_len, d_model)

    ffn_output = self.dropout3(ffn_output, training=training)
    out3 = self.layernorm3(ffn_output + out2)  # (batch_size, target_seq_len, d_model)

    # 除了主要输出 `out3` 以外,输出 multi-head 注意权重方便之後理解模型内部状况
    return out3, attn_weights_block1, attn_weights_block2
   

最後跟 Encoder 一样加上 position encoding 跟 Embedding,Decoder 就炼成了

class Decoder(tf.keras.layers.Layer):
  # 初始参数跟 Encoder 只差在用 `target_vocab_size` 而非 `inp_vocab_size`
  def __init__(self, num_layers, d_model, num_heads, dff, target_vocab_size, 
               rate=0.1):
    super(Decoder, self).__init__()

    self.d_model = d_model

    # 为 (目标语言)建立词嵌入层
    self.embedding = tf.keras.layers.Embedding(target_vocab_size, d_model)
    self.pos_encoding = positional_encoding(target_vocab_size, self.d_model)

    self.dec_layers = [DecoderLayer(d_model, num_heads, dff, rate) 
                       for _ in range(num_layers)]
    self.dropout = tf.keras.layers.Dropout(rate)

  # 呼叫时的参数跟 DecoderLayer 一模一样
  def call(self, x, enc_output, training, 
           combined_mask, inp_padding_mask):

    tar_seq_len = tf.shape(x)[1]
    attention_weights = {}  # 用来存放每个 Decoder layer 的注意权重

    # 这边跟 Encoder 做的事情完全一样
    x = self.embedding(x)  # (batch_size, tar_seq_len, d_model)
    x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
    x += self.pos_encoding[:, :tar_seq_len, :]
    x = self.dropout(x, training=training)


    for i, dec_layer in enumerate(self.dec_layers):
      x, block1, block2 = dec_layer(x, enc_output, training,
                                    combined_mask, inp_padding_mask)

      # 将从每个 Decoder layer 取得的注意权重全部存下来回传,方便观察
      attention_weights['decoder_layer{}_block1'.format(i + 1)] = block1
      attention_weights['decoder_layer{}_block2'.format(i + 1)] = block2

    # x.shape == (batch_size, tar_seq_len, d_model)
    return x, attention_weights

Create Transformer

我们即将到站了,把 Encoder 跟 Decoder 组起来就完成了

class Transformer(tf.keras.Model):
  def __init__(self, num_layers, d_model, num_heads, dff, input_vocab_size, target_vocab_size, rate=0.1):
    super(Transformer, self).__init__()
    self.encoder = Encoder(num_layers, d_model, num_heads, dff, input_vocab_size, rate)
    self.decoder = Decoder(num_layers, d_model, num_heads, dff, target_vocab_size, rate)
    # 这个 FFN 输出一样大的 logits 数,等通过 softmax 就代表每个输入的出现机率
    self.final_layer = tf.keras.layers.Dense(target_vocab_size)

  def call(self, inp, tar, training, enc_padding_mask, combined_mask, dec_padding_mask):

    enc_output = self.encoder(inp, training, enc_padding_mask)  
    dec_output, attention_weights = self.decoder(tar, enc_output, training, combined_mask, dec_padding_mask)
    final_output = self.final_layer(dec_output) 

    return final_output, attention_weights

接下来就可以拿这个 model 去做各种的实验了,建议是从 Tensorflow tutorial开始做起,来解决一个机器翻译的问题,这边就不详细介绍了,你可以看这篇(Transformer 圣经)就会很仔细地带你走过一次 Tensorflow tutorial。

Transfomer 无敌的吧?

这玩意儿这麽厉害,难道没有什麽缺点吗?

答案是有的,在後来的 Universal Transformers 里面提出了 2 个 Transformers 的主要问题。

  1. 有一些 RNN 可以轻松解决的问题 Transformer 无法很好的处理,Transformer 在处理局部讯息的时候并没有 RNN 及 CNN 好(所以当你用原版的 Transformer 处理一些任务时是有可能被轻易地被打败的)。
  2. Transformers 并不具有图灵完备性(也不是很懂)
  • Universal Transformers 解决了上面两个问题,让 Transformer 不会再轻易的被 RNN 及 CNN 击败,不过尽管如此它还是有付出一些代价的,论文里头有些东西也是写得不清不楚,它的图灵完备性也没有数学的证明过程,有兴趣的话可以参考看看这篇论文。

  • 但其实你可以很直觉的理解到,再一开始做 Position Encoding 的时候就加进了不是真正的输入了,这样子的东西设计的再怎麽厉害理应还是会有所误差。

  • 最後我觉得应该这麽说 "Transformer + RNN + CNN" 才是无敌的吧!XD

小结

终於把 Transformer 复习完了, 最後几天想要重新回归到音乐的部份,再和大家做分享,Transformer 的部份就写到这边,谢谢大家。

/images/emoticon/emoticon09.gif/images/emoticon/emoticon13.gif/images/emoticon/emoticon14.gif/images/emoticon/emoticon22.gif/images/emoticon/emoticon28.gif


<<:  爬虫怎麽爬 从零开始的爬虫自学 DAY11 python列表基础篇

>>:  [2021铁人赛 Day10] General Skills 07

GitHub Self-hosted runners - 自订代理程序环境的最佳选项

Self-hosted runners 介绍 在前面几篇文章实作 GitHub Action Wor...

Hook 的规则 ( Day18 )

使用 Hook 官方设定需要遵守的两个规则,并提供了一个 linter plugin 来自动化地实行...

Day-25 Deadlock

Deadlock tags: IT铁人 介绍 Deadlock是Multi-process常产生的问...

[iT铁人赛Day11]JAVA回圈

这次要来细讲回圈了 上次说到回圈有分成:1. for回圈,2. while回圈以及3. do whi...

Flutter - Flutter 网路 GIF 图片重复播放

Flutter - Flutter 网路 GIF 图片重复播放 参考资料 Flutter开发实战系列...