编码器部分实现
目标
- 了解编码器中各个组成部分的作用
- 掌握编码器中各个组成部分的实现过程
编码器部分
- 由N个编码器堆叠组成
- 每个编码器由两个子层连接结构组成
- 第一个子连接结构包括一个多头注意力子层和规范化层以及一个残差连接
- 第二个子层连接结构包括一个前馈全链接子层和规范化层以及一个残差连接
掩码张量
目标
- 了解什么是掩码张量以及它的作用
- 掌握生成掩码张量的实现过程
什么是掩码张量?
掩代表着烟, 码就是我们张量中的数值, 他的尺寸不定, 里面一半只有1和0个元素, 代表位置被遮掩或者不被遮掩, 至于是0位置被遮掩还是1位置被遮掩可以自定义
因此它的主要作用就是让另一个张量中的一些数值被遮掩, 也可以说是被替换, 它的表现形式是一个张量
掩码张量的作用
在transformer中, 掩码张量的主要作用在应用attention
时, 有一些生成的attention
张量中的值计算有可能已知了未来信息而得到的, 未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,
但是理论上解码器的输出却不是一次性就能产生最终结果的, 而是一次次通过上次结果综合得出的, 因此, 未来的信息可能被提前利用, 所以, 我们会进行遮掩
代码分析
import numpy as np
import torch
import matplotlib.pyplot as plt
def subsequent_mask(size):
"""生成向后遮掩的掩码张量, 参数size是掩码张量最后两个维度的大小, 它的最后两维形成一个方阵"""
# 在函数中, 首先定义掩码张量的形状
attn_shape = (1, size, size)
# 然后使用 np.ones 方法向这个形状中添加 1 元素, 形成三角阵
# 最后为了节约空间, 再使其中的数据类型变为无符号的8位整形uint8
mask = np.triu(np.ones(attn_shape), k=1).astype("uint8")
# 最后将 numpy 类型转化为 torch 的 tensor, 内部做一个 1- 的操作
# 在这个其实是做了一个三角阵的反转, mask 中每个元素都会被 1-
# 如果是0, 位置由 0 变为1, 如果为1, 位置由 1 变为0
return torch.from_numpy(1 - mask)
if __name__ == '__main__':
mask = subsequent_mask(20)
"""output:
tensor([[[1, 0, 0, 0, 0],
[1, 1, 0, 0, 0],
[1, 1, 1, 0, 0],
[1, 1, 1, 1, 0],
[1, 1, 1, 1, 1]]], dtype=torch.uint8)
"""
掩码张量可视化
import matplotlib.pyplot as plt
mask = subsequent_mask(20)
plt.figure(figsize=(5, 5))
plt.imshow(mask[0])
plt.show()
!()[]
- 黄色是代表1的部分, 代表被掩的部分, 紫色代表没有被遮掩的信息, 横坐标代表目标词汇的位置, 纵坐标代表可看到的位置
- 0 的位置都是被遮住了, 说明第一次词还没有产出, 第二个位置看过去到1的词, 说明第一个词开始产出, 其他位置看不到, 以此类推
np.triu说明
np.triu
函数用于返回一个三角形矩阵, 默认下三角为 0, 上三角为 1, 可以通过参数 k 来控制上三角和下三角的位置。
import numpy as np
# 保留主对角线下一行和右行
s = np.triu([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], k=-1)
print(s)
# 保留主对角线和右行
s = np.triu([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], k=0)
print(s)
# 保留主对角线右行
s = np.triu([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], k=1)
print(s)
"""output:
[[ 1 2 3]
[ 4 5 6]
[ 0 8 9]
[ 0 0 12]]
[[1 2 3]
[0 5 6]
[0 0 9]
[0 0 0]]
[[0 2 3]
[0 0 6]
[0 0 0]
[0 0 0]]
"""