1.解码器介绍
解码器部分:
- 由N个解码器层堆叠而成
- 每个解码器层由三个子层连接结构组成
- 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
- 第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
- 第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
解码器中的多头注意力机制,规范化层,前馈全连接层,子层连接结构与编码器中的实现相同,我们可以直接拿来构建解码器层
2.解码器层
2.1解码器层的作用
- 作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程.
2.2解码器层代码实现
代码语言:javascript复制# 使用DecoderLayer的类实现解码器层
class DecoderLayer(nn.Module):
def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
"""初始化函数的参数有5个, 分别是size,代表词嵌入的维度大小, 同时也代表解码器层的尺寸,
第二个是self_attn,多头自注意力对象,也就是说这个注意力机制需要Q=K=V,
第三个是src_attn,多头注意力对象,这里Q!=K=V, 第四个是前馈全连接层对象,最后就是droupout置0比率.
"""
super(DecoderLayer, self).__init__()
# 在初始化函数中, 主要就是将这些输入传到类中
self.size = size
self.self_attn = self_attn
self.src_attn = src_attn
self.feed_forward = feed_forward
# 按照结构图使用clones函数克隆三个子层连接对象.
self.sublayer = clones(SublayerConnection(size, dropout), 3)
def forward(self, x, memory, source_mask, target_mask):
"""forward函数中的参数有4个,分别是来自上一层的输入x,
来自编码器层的语义存储变量mermory, 以及源数据掩码张量和目标数据掩码张量.
"""
# 将memory表示成m方便之后使用
m = memory
# 将x传入第一个子层结构,第一个子层结构的输入分别是x和self-attn函数,因为是自注意力机制,所以Q,K,V都是x,
# 最后一个参数是目标数据掩码张量,这时要对目标数据进行遮掩,因为此时模型可能还没有生成任何目标数据,
# 比如在解码器准备生成第一个字符或词汇时,我们其实已经传入了第一个字符以便计算损失,
# 但是我们不希望在生成第一个字符时模型能利用这个信息,因此我们会将其遮掩,同样生成第二个字符或词汇时,
# 模型只能使用第一个字符或词汇信息,第二个字符以及之后的信息都不允许被模型使用.
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, target_mask))
# 接着进入第二个子层,这个子层中常规的注意力机制,q是输入x; k,v是编码层输出memory,
# 同样也传入source_mask,但是进行源数据遮掩的原因并非是抑制信息泄漏,而是遮蔽掉对结果没有意义的字符而产生的注意力值,
# 以此提升模型效果和训练速度. 这样就完成了第二个子层的处理.
x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, source_mask))
# 最后一个子层就是前馈全连接子层,经过它的处理后就可以返回结果.这就是我们的解码器层结构.
return self.sublayer[2](x, self.feed_forward)
- 类的初始化函数的参数有5个, 分别是size,代表词嵌入的维度大小, 同时也代表解码器层的尺寸,第二个是self_attn,多头自注意力对象,也就是说这个注意力机制需要Q=K=V,第三个是src_attn,多头注意力对象,这里Q!=K=V, 第四个是前馈全连接层对象,最后就是droupout置0比率.
- forward函数的参数有4个,分别是来自上一层的输入x,来自编码器层的语义存储变量mermory, 以及源数据掩码张量和目标数据掩码张量.
- 最终输出了由编码器输入和目标数据一同作用的特征提取结果
3.解码器
3.1解码器的作用
- 根据编码器的结果以及上一次预测的结果, 对下一次可能出现的'值'进行特征表示
3.2解码器的代码分析
代码语言:javascript复制# 使用类Decoder来实现解码器
class Decoder(nn.Module):
def __init__(self, layer, N):
"""初始化函数的参数有两个,第一个就是解码器层layer,第二个是解码器层的个数N."""
super(Decoder, self).__init__()
# 首先使用clones方法克隆了N个layer,然后实例化了一个规范化层.
# 因为数据走过了所有的解码器层后最后要做规范化处理.
self.layers = clones(layer, N)
self.norm = LayerNorm(layer.size)
def forward(self, x, memory, source_mask, target_mask):
"""forward函数中的参数有4个,x代表目标数据的嵌入表示,memory是编码器层的输出,
source_mask, target_mask代表源数据和目标数据的掩码张量"""
# 然后就是对每个层进行循环,当然这个循环就是变量x通过每一个层的处理,
# 得出最后的结果,再进行一次规范化返回即可.
for layer in self.layers:
x = layer(x, memory, source_mask, target_mask)
return self.norm(x)
- 实例化参数
# 分别是解码器层layer和解码器层的个数N
size = 512
d_model = 512
head = 8
d_ff = 64
dropout = 0.2
c = copy.deepcopy
attn = MultiHeadedAttention(head, d_model)
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
layer = DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
N = 8
- 输入参数
# 输入参数与解码器层的输入参数相同
x = pe_result
memory = en_result
mask = Variable(torch.zeros(8, 4, 4))
source_mask = target_mask = mask
- 调用
de = Decoder(layer, N)
de_result = de(x, memory, source_mask, target_mask)
print(de_result)
print(de_result.shape)
- 输出效果
tensor([[[ 0.9898, -0.3216, -1.2439, ..., 0.7427, -0.0717, -0.0814],
[-0.7432, 0.6985, 1.5551, ..., 0.5232, -0.5685, 1.3387],
[ 0.2149, 0.5274, -1.6414, ..., 0.7476, 0.5082, -3.0132],
[ 0.4408, 0.9416, 0.4522, ..., -0.1506, 1.5591, -0.6453]],
[[-0.9027, 0.5874, 0.6981, ..., 2.2899, 0.2933, -0.7508],
[ 1.2246, -1.0856, -0.2497, ..., -1.2377, 0.0847, -0.0221],
[ 3.4012, -0.4181, -2.0968, ..., -1.5427, 0.1090, -0.3882],
[-0.1050, -0.5140, -0.6494, ..., -0.4358, -1.2173, 0.4161]]],
grad_fn=<AddBackward0>)
torch.Size([2, 4, 512])
4.输出部分
4.1输出部分介绍
- 输出部分包含:
- 线性层
- softmax层
4.2线性层的作用
- 通过对上一步的线性变化得到指定维度的输出, 也就是转换维度的作用
4.3softmax层的作用
- 使最后一维的向量中的数字缩放到0-1的概率值域内, 并满足他们的和为1.
4.3线性层和softmax层代码实现
代码语言:javascript复制# nn.functional工具包装载了网络层中那些只进行计算, 而没有参数的层
import torch.nn.functional as F
# 将线性层和softmax计算层一起实现, 因为二者的共同目标是生成最后的结构
# 因此把类的名字叫做Generator, 生成器类
class Generator(nn.Module):
def __init__(self, d_model, vocab_size):
"""初始化函数的输入参数有两个, d_model代表词嵌入维度, vocab_size代表词表大小."""
super(Generator, self).__init__()
# 首先就是使用nn中的预定义线性层进行实例化, 得到一个对象self.project等待使用,
# 这个线性层的参数有两个, 就是初始化函数传进来的两个参数: d_model, vocab_size
self.project = nn.Linear(d_model, vocab_size)
def forward(self, x):
"""前向逻辑函数中输入是上一层的输出张量x"""
# 在函数中, 首先使用上一步得到的self.project对x进行线性变化,
# 然后使用F中已经实现的log_softmax进行的softmax处理.
# 在这里之所以使用log_softmax是因为和我们这个pytorch版本的损失函数实现有关, 在其他版本中将修复.
# log_softmax就是对softmax的结果又取了对数, 因为对数函数是单调递增函数,
# 因此对最终我们取最大的概率值没有影响. 最后返回结果即可.
return F.log_softmax(self.project(x), dim=-1)
- nn.Linear演示 ,实例化参数
>>> m = nn.Linear(20, 30)
>>> input = torch.randn(128, 20)
>>> output = m(input)
>>> print(output.size())
torch.Size([128, 30])
# 词嵌入维度是512维
d_model = 512
# 词表大小是1000
vocab_size = 1000
- 输入参数
# 输入x是上一层网络的输出, 我们使用来自解码器层的输出
x = de_result
- 调用
gen = Generator(d_model, vocab_size)
gen_result = gen(x)
print(gen_result)
print(gen_result.shape)
- 输出效果
tensor([[[-7.8098, -7.5260, -6.9244, ..., -7.6340, -6.9026, -7.5232],
[-6.9093, -7.3295, -7.2972, ..., -6.6221, -7.2268, -7.0772],
[-7.0263, -7.2229, -7.8533, ..., -6.7307, -6.9294, -7.3042],
[-6.5045, -6.0504, -6.6241, ..., -5.9063, -6.5361, -7.1484]],
[[-7.1651, -6.0224, -7.4931, ..., -7.9565, -8.0460, -6.6490],
[-6.3779, -7.6133, -8.3572, ..., -6.6565, -7.1867, -6.5112],
[-6.4914, -6.9289, -6.2634, ..., -6.2471, -7.5348, -6.8541],
[-6.8651, -7.0460, -7.6239, ..., -7.1411, -6.5496, -7.3749]]],
grad_fn=<LogSoftmaxBackward>)
torch.Size([2, 4, 1000])