TensorFlow 2.0 教程-Transformer
这里我们将实现一个Transformer模型,将葡萄牙语翻译为英语。Transformer的核心思想是self-attention--通过关注序列不同位置的内容获取句子的表示。
Transformer的一些优点:
- 不受限于数据的时间/空间关系
- 可以并行计算
- 远距离token的相互影响不需要通过很长的时间步或很深的卷积层
- 可以学习远程依赖
Transformer的缺点:
- 对于时间序列,输出需要根据整个历史,而不是当前状态和输入,可能造成效率较低
- 如果想要获取时间空间信息,需要额外的位置编码
In [1]:
1 | from __future__ import absolute_import, division, print_function, unicode_literals |
1.数据输入pipeline
我们将使用到Portugese-English翻译数据集。
该数据集包含大约50000个训练样例,1100个验证示例和2000个测试示例。
tfds.load :加载数据集.1.下载数据并将其另存为tfrecord文件. 2.加载tfrecord并创建tf.data.Dataset. metadata: 元数据 用于描述数据的数据,比如数码照片的EXIF信息,它就是一种用来描述数码图片的元数据
1 | examples, metadata = tfds.load('ted_hrlr_translate/pt_to_en', with_info=True, |
将数据转化为subwords格式
1 | train_examples, val_examples = examples['train'], examples['validation'] |
把单词用英语和葡萄牙语分别进行编码
2**13: 2的3次方大小的词汇表大小
1 | tokenizer_en = tfds.features.text.SubwordTextEncoder.build_from_corpus( |
token转化测试
1 | sample_str = 'hello world, tensorflow 2' |
1 | [3222, 439, 150, 7345, 1378, 2824, 2370, 7881] |
添加start、end的token表示
lang1:用于葡萄牙语翻译的开始
lang2:用于英语翻译完成的结束
1 | def encode(lang1, lang2): |
过滤长度超过40的数据
tf.logical_and : 与运算
1 | MAX_LENGTH=40 |
将python运算,转换为tensorflow运算节点。
这是一个可以把 TensorFlow 和 Python 原生代码无缝衔接起来的函数,有了它,你就可以在 TensorFlow 里面自由的实现你想要的功能,而不用考虑 TensorFlow 有没有实现它的 API
1 | def tf_encode(pt, en): |
构造数据集
In [9]:
1 | BUFFER_SIZE = 20000 |
In [10]:
1 | de_batch, en_batch = next(iter(train_dataset)) |
Out[10]:
1 | (<tf.Tensor: id=311363, shape=(64, 40), dtype=int64, numpy= |
2.位置嵌入
将位置编码矢量添加得到词嵌入,相同位置的词嵌入将会更接近,但并不能直接编码相对位置
基于角度的位置编码方法如下:
得到角度
1 | def get_angles(pos, i, d_model): |
In [12]:
1 | def positional_encoding(position, d_model): |
获得位置嵌入编码
position: 一句话中某个token的位置(0-50), 如果一句话不够50个单词,那么就需要padding
depth : 对每一个token进行编码 d_model:512维
1 | pos_encoding = positional_encoding(50, 512) |
3.掩码
为了避免输入中padding的token对句子语义的影响,需要将padding位mark掉,原来为0的padding项的mark输出为1
1 | def create_padding_mark(seq): |
1 | <tf.Tensor: id=311819, shape=(2, 1, 1, 5), dtype=float32, numpy= |
look-ahead mask 用于对未预测的token进行掩码 这意味着要预测第三个单词,只会使用第一个和第二个单词。 要预测第四个单词,仅使用第一个,第二个和第三个单词,依此类推。 ??????
1 | def create_look_ahead_mark(size): |
In [16]:
1 | # x = tf.random.uniform((1,3)) |
4.Scaled dot product attention
进行attention计算的时候有3个输入 Q (query), K (key), V (value)。计算公式如下:
点积注意力通过深度d_k的平方根进行缩放,因为较大的深度会使点积变大,由于使用softmax,会使梯度变小。 例如,考虑Q和K的均值为0且方差为1.它们的矩阵乘法的均值为0,方差为dk。我们使用dk的根用于缩放(而不是任何其他数字),因为Q和K的matmul应该具有0的均值和1的方差。
在这里我们将被掩码的token乘以-1e9(表示负无穷)
,这样softmax之后就为0,不对其他token产生影响。
In [17]:
1 | def scaled_dot_product_attention(q, k, v, mask): |
使用attention获取需要关注的语义
In [18]:
1 | def print_out(q, k, v): |
attention测试
In [19]:
1 | # 显示为numpy类型 |
In [20]:
1 | # 关注重复的key(第3、4个), 返回对应的value(平均) |
In [21]:
1 | # 关注第1、2个key, 返回对应的value(平均) |
In [22]:
1 | # 依次放入每个query |
5.Mutil-Head Attention
mutil-head attention包含3部分:
- 线性层与分头
- 缩放点积注意力
- 头连接
- 末尾线性层
每个多头注意块有三个输入; Q(查询),K(密钥),V(值)。 它们通过第一层线性层并分成多个头
。
注意:点积注意力时需要使用mask, 多头输出需要使用tf.transpose调整各维度。
Q,K和V不是一个单独的注意头,而是分成多个头,因为它允许模型共同参与来自不同表征空间的不同信息。 在拆分之后,每个头部具有降低的维度,总计算成本与具有全维度的单个头部注意力相同。
python 中assert断言
是声明其布尔值必须为真的判定,如果发生异常就说明表达式为假。可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,则会抛出AssertError并且包含错误信息。如果它为真,就不做任何事
"//"不管两者出现任何数,都以整除结果为准,不对小数部分进行处理,直接抛弃,也就是整除法
1 | # 构造mutil head attention层 |
测试多头attention
In [24]:
1 | temp_mha = MutilHeadAttention(d_model=512, num_heads=8) |
point wise前向网络
In [25]:
1 | def point_wise_feed_forward_network(d_model, diff): |
In [26]:
1 | sample_fnn = point_wise_feed_forward_network(512, 2048) |
Out[26]:
1 | TensorShape([64, 50, 512]) |
6.编码器和解码器
- 通过N个编码器层,为序列中的每个字/令牌生成输出。
- 解码器连接编码器的输出和它自己的输入(自我注意)以预测下一个字。
编码层
每个编码层包含以下子层
- Multi-head attention(带掩码)
- Point wise feed forward networks
每个子层中都有残差连接,并最后通过一个正则化层。残差连接有助于避免深度网络中的梯度消失问题。 每个子层输出是LayerNorm(x + Sublayer(x)),规范化是在d_model维的向量上。Transformer一共有n个编码层。
In [27]:
1 | class LayerNormalization(tf.keras.layers.Layer): |
In [28]:
1 | class EncoderLayer(tf.keras.layers.Layer): |
encoder层测试
In [29]:
1 | sample_encoder_layer = EncoderLayer(512, 8, 2048) |
Out[29]:
1 | TensorShape([64, 43, 512]) |
解码层
每个编码层包含以下子层:
- Masked muti-head attention(带padding掩码和look-ahead掩码)
- Muti-head attention(带padding掩码)value和key来自encoder输出,query来自Masked muti-head attention层输出
- Point wise feed forward network
每个子层中都有残差连接,并最后通过一个正则化层。残差连接有助于避免深度网络中的梯度消失问题。
每个子层输出是LayerNorm(x + Sublayer(x)),规范化是在d_model维的向量上。Transformer一共有n个解码层。
当Q从解码器的第一个注意块接收输出,并且K接收编码器输出时,注意权重表示基于编码器输出给予解码器输入的重要性。 换句话说,解码器通过查看编码器输出并自我关注其自己的输出来预测下一个字。
ps:因为padding在后面所以look-ahead掩码同时掩padding
In [30]:
1 | class DecoderLayer(tf.keras.layers.Layer): |
测试解码层
In [31]:
1 | sample_decoder_layer = DecoderLayer(512, 8, 2048) |
Out[31]:
1 | TensorShape([64, 50, 512]) |
编码器
编码器包含:
- Input Embedding
- Positional Embedding
- N个编码层
In [32]:
1 | class Encoder(layers.Layer): |
编码器测试
In [33]:
1 | sample_encoder = Encoder(2, 512, 8, 1024, 5000, 200) |
Out[33]:
1 | TensorShape([64, 120, 512]) |
解码器
解码器包含以下部分:1、输出嵌入;2、位置编码;3、n个解码层
输出嵌入和位置编码叠加后输入解码器,解码器最后的输出送给一个全连接
In [34]:
1 | # import pdb |
解码器测试
In [35]:
1 | sample_decoder = Decoder(2, 512,8,1024,5000, 200) |
Out[35]:
1 | (TensorShape([64, 100, 512]), TensorShape([64, 8, 100, 100])) |
创建Transformer
Transformer包含编码器、解码器和最后的线性层,解码层的输出经过线性层后得到Transformer的输出
In [36]:
1 | class Transformer(tf.keras.Model): |
Transformer测试
In [37]:
1 | sample_transformer = Transformer( |
Out[37]:
1 | TensorShape([64, 26, 8000]) |
7.实验设置
设置超参
In [38]:
1 | num_layers = 4 |
优化器
带自定义学习率调整的Adam优化器
In [39]:
1 | class CustomSchedule(tf.keras.optimizers.schedules.LearningRateSchedule): |
In [40]:
1 | learing_rate = CustomSchedule(d_model) |
In [41]:
1 | # 测试 |
Out[41]:
1 | Text(0, 0.5, 'train step') |
损失和指标
由于目标序列是填充的,因此在计算损耗时应用填充掩码很重要。 padding的掩码为0,没padding的掩码为1
In [42]:
1 | loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, |
In [43]:
1 | train_loss = tf.keras.metrics.Mean(name='train_loss') |
8、训练和保持模型
In [44]:
1 | transformer = Transformer(num_layers, d_model, num_heads, dff, |
In [45]:
1 | # 构建掩码 |
创建checkpoint管理器
In [46]:
1 | checkpoint_path = './checkpoint/train' |
target分为target_input和target real. target_input是传给解码器的输入,target_real是其左移一个位置的结果,每个target_input位置对应下一个预测的标签
如句子=“SOS A丛林中的狮子正在睡觉EOS”
target_input =“SOS丛林中的狮子正在睡觉”
target_real =“丛林中的狮子正在睡觉EOS”
transformer是个自动回归模型:它一次预测一个部分,并使用其到目前为止的输出,决定下一步做什么。
在训练期间使用teacher-forcing,即无论模型当前输出什么都强制将正确输出传给下一步。
而预测时则根据前一个的输出预测下一个词
为防止模型在预期输出处达到峰值,模型使用look-ahead mask
In [47]:
1 | @tf.function |
葡萄牙语用作输入语言,英语是目标语言。
In [48]:
1 | EPOCHS = 20 |