最近在手写Transformer,有了一些理解,之前虽然感觉自己理论(Transformer理论图解)都理解了,但是写出来依然发现有很多地方不太明白,弄明白之后写出来记录一下。

先记录一下手写的一些细节吧,最后会贴上完整的github链接。

首先就是Transformer的结构,左侧的encoder层和右侧的decoder层,实际的Transformer的encoder和decoder就是由N个encoder层和N个docoder层组成。

我们继续观察模型,发现多头注意力(注意力基础注意力理论图解)有两种,一种就是单纯的Multi-Head Attention,另一种是Masked Multi-Head Attention。

那么这两种多头注意力机制有什么区别呢,很显然,就是Mask机制。

这个Mask机制实际上是一个遮蔽机制,由于Transformer的并行要求(事实上这也是Transformer相比RNN的一大优势),需要将target进行embedding然后输入到decoder中,因此就需要使用一个机制,屏蔽掉当前预测位及其之后的文本,比如:输出文本是“ 我爱你 ”,那么在预测“ 爱 ”的时候,当前网络应该只可见“我”字。

那么是怎么做的呢?就是使用一个下三角矩阵,由于最终的embedding的文本还要有开始标记(“<BOS> 我 爱 你 <EOS>”),因此对于这句话的5*5的下三角矩阵,正好可以遮蔽掉预测字后面的字,比如矩阵第二行有2个1,因此只有“ <BOS> 我 ”对网络可见,并试图使网络预测“爱”。

除此之外还有一个mask,就是要mask掉padding,由于对于短文本,我们需要将文本padding到定长,我们并不需要注意力机制关注这部分padding信息,因此我们同样可以使用mask机制强行清空注意力结果(乘0或一个极小的数字)。

详见 Multi-Head Attention 代码。

import tensorflow as tf


class MutiHeadAttention(tf.keras.layers.Layer):
"""
多头注意力
"""

def __init__(self, d_model, num_heads):
"""

:param d_model: muti attn 输出的维度
:param num_heads: 多头个数
"""
super().__init__()

assert d_model % num_heads == 0
self.num_heads = num_heads
self.d_model = d_model

self.deep = self.d_model // self.num_heads

self.WQ = tf.keras.layers.Dense(self.d_model)
self.WK = tf.keras.layers.Dense(self.d_model)
self.WV = tf.keras.layers.Dense(self.d_model)

self.dense = tf.keras.layers.Dense(self.d_model)

def split_heads(self, x, batch_size):
"""
分头,
:param x:
:param batch_size:
:return:
"""
x = tf.reshape(x, (batch_size, -1, self.num_heads, self.deep))
# perm 是新的维度索引,假如x维度是[1,2,3,4],perm=[0,2,1,3]转置后x维度将变成[1,3,2,4]
return tf.transpose(x, perm=[0, 2, 1, 3])

def call(self, q, k, v, mask):
batch_size = tf.shape(q)[0]

q = self.WQ(q)
k = self.WK(k)
v = self.WV(v)

q = self.split_heads(q, batch_size)
k = self.split_heads(k, batch_size)
v = self.split_heads(v, batch_size)

attention_output, attention_weight = dot_attention(q, k, v, mask)

attention_output = tf.transpose(attention_output, perm=[0, 2, 1, 3])
attention_output = tf.reshape(attention_output, (batch_size, -1, self.d_model))

_output = self.dense(attention_output)
return _output, attention_weight


#
#
def dot_attention(q, k, v, mask):
"""
点乘
:param q:
:param k:
:param v:
:param mask:
:return:
"""
qk = tf.matmul(q, k, transpose_b=True)

# 计算根号下dk对qk进行缩放,类似于 emm 归一化
dk = tf.cast(tf.shape(k)[-1], tf.float32)
scaled_attention_logits = qk / tf.math.sqrt(dk)

if mask is not None:
scaled_attention_logits += (mask * -1e9)

attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)

_output = tf.matmul(attention_weights, v)
return _output, attention_weights

其他的encoder层、deocder层、以及最终的encoder、deocder这里不再描述,不是很复杂。

到这里其实还好,下面的问题一度让我怀疑我的理解出了很大的问题。

都写完之后,我想测试一下这个模型的效果,于是继续手写了一下测试,但是发现了一个问题,训练阶段传的target,在测试阶段传什么?如果只传<BOS>,那么最终的结果将天花乱坠,并且再预测阶段传完整的target也就没有意义了。

经过一段时间的研究发现,Transformer的测试阶段依然需要n次模型迭代,就类似于RNN,用n-1次得输出,作为output target,传入n次,由模型预测第n个词。

这是由于Transformer的teaching force决定的,因此在Transformer的训练和测试中就存在一个gap。

虽然知道之后感觉挺简单的,但是当时确实困惑了好久,也算掌握了一个新知识,写下来 记录一下。

完整代码链接


0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注