Transformer构架

架构图

翻译词语

  • input embedding:源文本嵌入层
  • output embedding:目标文本嵌入层
  • positional encoding:位置编码器
  • linear:线性层
  • add & norm:规范化层
  • multi-head attention:多头自注意力
  • feed forward:前馈全连接
  • masked:掩码

四部分

  • 输入部分
    • inputs:源文本嵌入层及其位置编码器
    • outputs:目标文本嵌入层及位置其位置编码器
  • 编码器部分
    • N个编码器堆叠
    • 每个编码器有两个子层
      • 第一个子层结构:一个多头自注意力子层、规范化层、一个残差连接
      • 第二个子层结构:一个前馈全连接子层、规范化层、一个残差连接
  • 解码器部分
    • N个解码器堆叠
    • 每个解码器有三个子层
      • 第一个子层结构:一个多头自注意力子层、规范化层、一个残差连接
      • 第二个子层结构:一个多头注意力子层、规范化层、一个残差连接
      • 第三个子层结构:一个前馈全连接子层、规范化层、一个残差连接
  • 输出部分
    • linear:线性层
    • softmax:softmax层

输入部分

作用

  • 文本嵌入层的作用:
    • 将文本中词汇的数字表示转变为向量表示,在高纬度空间捕捉词汇间的关系
  • 位置编码器:
    • 将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中
    • dropout:置零比率

正文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import math
import matplotlib.pyplot as plt
import numpy as np
import copy

# 输入部分

# 文本嵌入层
embedding = nn.Embedding(10,3)
input1 = torch.LongTensor([[1,2,4,5],[4,3,2,9]])
print(embedding(input1))

# padding_idx参数
embedding = nn.Embedding(10, 3, padding_idx=0)
input1 = torch.LongTensor([[0, 2, 0, 5]])
print(embedding(input1))

# 构建Embedding类实现文本嵌入层
class Embeddings(nn.Module):
def __init__(self, d_model, vocab):
# d_model: 词嵌入的维度
# vocab: 词表的大小
super(Embeddings, self).__init__()
# 定义Embedding层
self.lut = nn.Embedding(vocab, d_model)
# 将参数传入类中
self.d_model = d_model

def forward(self, x):
# x: 代表输入进模型的文本通过词汇映射后的数字张量
return self.lut(x) * math.sqrt(self.d_model)

# 实例化
d_model = 512
vocab = 1000
x = Variable(torch.LongTensor([[100, 2, 421, 508], [491, 998, 1, 221]]))
emb = Embeddings(d_model, vocab)
embr = emb(x)
print("ember:", embr)
print(embr.shape)

# 演示Dropout
m = nn.Dropout(p=0.2)
input1 = torch.randn(4, 5)
output = m(input1)
print(output)

# 演示unsqueeze
x = torch.tensor([1, 2, 3, 4])
y = torch.unsqueeze(x, 0)
print(y)
z = torch.unsqueeze(x, 1)
print(z)


# 构建位置编码器的类
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout, max_len=5000):
# d_model: 代表词嵌入的维度
# dropout: 代表Dropout层的置零比率
super(PositionalEncoding, self).__init__()

# 实例化Dropout层
self.dropout = nn.Dropout(p=dropout)

# 初始化一个位置编码矩阵,大小是max_len * d_model
pe = torch.zeros(max_len, d_model)

# 初始化一个绝对位置矩阵, max_len * 1
position = torch.arange(0, max_len).unsqueeze(1)

# 定义一个变化矩阵div_term,跳跃式的初始化
div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0)))

# 将前面定义的变化矩阵进行奇数,偶数的分别赋值
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)

# 将二维张良扩充成三维张量
pe = pe.unsqueeze(0)

# 将位置编码矩阵注册成模型的buffer, 这个buffer不是模型中的参数,不跟随优化器同步更新
# 注册成buffer后我们就可以在模型保存后重新加载的时候,将这个位置编码器和模型参数一同加载进来
self.register_buffer('pe', pe)

def forward(self, x):
# x: 代表问呗序列的词嵌入表示
# 首先明确pe的编码太长了。将第二个维度,也就是max_len对应的那个维度缩小成x句子的长度
x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False)
return self.dropout(x)

# 实例化
d_model = 512
dropout = 0.1
max_len = 60

x = embr
pe = PositionalEncoding(d_model, dropout, max_len)
pe_result = pe(x)
print(pe_result)
# print(pe_result.shape)

# 绘制词汇向量中特征的分布曲线
# 第一步设置一个画布
plt.figure(figsize=(15, 5))

# 实例化PositionalEncoding类对象,词嵌入维度给20,置零比率设置为0
pe = PositionalEncoding(20, 0)

# 向pe中传入一个全零初始化的x,相当于展示pe
y = pe(Variable(torch.zeros(1, 100, 20)))
plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())
plt.legend(["dim %d"%p for p in [4, 5, 6, 7]])
plt.show()

编码器部分

掩码张量

解释

  • 代表位置被遮掩或者不被遮掩,尺寸不定,一般只有1和0的元素
  • 作用:
    • 让另外一个张量中的一些数值被遮掩
    • attention张量中可能有未来的信息,因需要一层一层计算所以把未来的信息进行遮掩

正文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# triu 对下三角进行赋0
print(np.triu([[1, 2, 3], [4, 5, 6], [7, 8, 9],[10, 11, 12]], k=-1))
print(np.triu([[1, 2, 3], [4, 5, 6], [7, 8, 9],[10, 11, 12]], k=0))
print(np.triu([[1, 2, 3], [4, 5, 6], [7, 8, 9],[10, 11, 12]], k=1))

# 构建掩码张量的函数
def subsequent_mask(size):
# size: 代表掩码张量后两个维度,形成一个方阵
attn_shape = (1, size, size)

# 使用np.ones()先构建一个全1的张量,然后利用np.triu()形成上三角矩阵
subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')

# 使得这个三角矩阵反转
return torch.from_numpy(1 - subsequent_mask)

# 实例化
size = 5
sm = subsequent_mask(size)
# print("sm:", sm)

# 可视化
plt.figure(figsize=(5, 5))
plt.imshow(subsequent_mask(20)[0])
plt.show()

注意力机制

解释

  • 不是从头到尾的观察后进行判断,而是用最有辨识度部分做出快速判断(允许错误)
  • 计算规则:
    • 需要三个指定的输入Q(query),K(key),V(value),然后通过公式得到注意力的计算结果,这个结果代表query在key和value作用下的表示,计算的规则有很多中
    • 比喻解释:
      • Q是一段准备被概括的文本;K是给出的提示;V是大脑中的对提示K的延申
      • 当Q=K=V时,称作自注意力机制

正文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 注意力
# # 分解函数
# x = Variable(torch.randn(5, 5))
# print(x)

# mask = Variable(torch.zeros(5, 5))
# print(mask)

# y = x.masked_fill(mask == 0, -1e9)
# print(y)

# 构建attention函数
def attention(query, key, value, mask=None, dropout=None):
# query, key, value: 代表注意力的三个输入张量
# mask: 掩码张量
# dropout: 传入dropout实例化对象
# 首先将query的最后一个维度提取出来,代表的是词嵌入的维度
d_k = query.size(-1)

# 按照注意力计算公式,就将query和key的转置进行矩阵乘法,然后除以缩放系数
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)

# 判断是否使用掩码张量
if mask is not None:
# 利用masked_fill方法,将掩码张量和0进行位置的意义比较,如果等于0,替换成一个非常小的数
scores = scores.masked_fill(mask == 0, -1e9)

# 对scores的最后一个维度上进行softmax操作
p_attn = F.softmax(scores, dim=-1)

# 判断是否使用dropout
if dropout is not None:
p_attn = dropout(p_attn)

# 最后一步完成p_attn和value张量的惩罚,并返回query注意力表示
return torch.matmul(p_attn, value), p_attn

# 实例化
query = key = value = pe_result
attn, p_attn = attention(query, key, value)
print('attn:', attn)
print(attn.shape)
print('p_attn:', p_attn)
print(p_attn.shape)

多头注意力机制

解释


解码器部分


输出部分