注意:这一文章“基于Transformer的文本情感分析编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制 + Positional Encoding位置编码)”
该文章实现的Transformer的Model类型模型,实际是改造过的特别版的Transformer,因为Transformer的Model类型模型中只实现了Encoder编码器,
而没有对应实现的Decoder解码器,并且因为当前Transformer的Model类型模型处理的是分类任务,
所以我们此处只用了Encoder编码器来提取特征,最后通过全连接层网络来拟合分类。
""" 文本嵌入层的代码分析 """
"""
输入部分包含:
1.源文本嵌入层及其位置编码器
2.目标文本嵌入层及其位置编码器
"""
"""
1.输入部分 嵌入层:
嵌入层类Embeddings:
Embeddings(词嵌入维度, 词表大小)
嵌入层最终得到一个三维的经过了Embedding层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
1.embedding = nn.Embedding(词表大小, 词嵌入维度)
创建Embedding嵌入层。
2.output = embedding(input)
批量句子中的单词索引值传入Embedding嵌入层,输出单词索引对应的词嵌入维度的权重向量。
2维[batch size, 句子最大长度max_len]的input传入Embedding嵌入层输出3维[batch size, 句子最大长度max_len, 词嵌入维度]的output。
3.output * math.sqrt(词嵌入维度)
math.sqrt(词嵌入维度)作为Embedding嵌入层输出output的缩放系数。
该sqrt结果值越大那么上述两者相乘的结果值就越大,该sqrt结果值越小那么上述两者相乘的结果值就越小。
2.输入部分 位置编码器:
位置编码层类PositionalEncoding:
PositionalEncoding(词嵌入维度, Dropout的置0丢弃比率, 默认值max_len=5000)
位置编码层最终得到一个三维的经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
1.pe = torch.zeros(max_len句子最大长度, 词嵌入维度)
初始化一个位置编码的2维矩阵,shape为[max_len句子最大长度, 词嵌入维度]。
2.position = torch.arange(0., max_len).unsqueeze(1)
初始化一个绝对位置编码的2维矩阵,为一个shape为[max_len句子最大长度, 1]的2维矩阵,元素值为从0到max_len-1。
torch.arange(0., max_len):得到一个1维的shape为[max_len]的向量,元素值为从0到max_len-1。
torch.arange(0., max_len).unsqueeze(1):得到一个2维shape为[max_len, 1]的矩阵,元素值为从0到max_len-1。
arange方法获得一个连续自然数的一维的shape为[max_len]向量:torch.arange(0., 3) 得到 tensor([0., 1., 2.])。
torch.arange(0., 3).unsqueeze(1)得到tensor([[0.], [1.], [2.]]),shape为[max_len, 1]
3.div_term = torch.exp(torch.arange(0., 词嵌入维度, 2) * -(math.log(10000.0) / 词嵌入维度))
生成一个shape为[词嵌入维度数除以2]的1维向量,向量中每个数为极小的数值。
向量中每个极小的数值都带一个后缀e-0X,从e-01、e-02、e-03、e-04、...不等。e-01:10^(-1)即0.1。
1.torch.arange(0., 词嵌入维度, 2):
生成一个shape为[词嵌入维度数除以2]的1维向量。
比如 torch.arange(0., 10, 2)得tensor([0., 2., 4., 6., 8.])
2.-(math.log(10000.0) / 词嵌入维度):
该负数作为一个缩放系数。首先除法运算得到一个很小的正值小数然后取反得负数。
作用为将绝对位置编码的2维矩阵position中的自然数缩放成足够小的数字,有助于在之后的梯度下降过程中更快的收敛。
比如 math.log(10000.0) / 512 得一个很小的正值小数 0.017988946039015984
3.torch.arange(0., 词嵌入维度, 2) * -(math.log(10000.0) / 词嵌入维度):
生成一个shape为[词嵌入维度数除以2]的1维向量,向量中每个数为极小的数值。
向量中每个极小的数值都带一个后缀e-0X,从e-01、e-02、e-03、e-04、...不等。
e-01:10^(-1)即0.1。
4.pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
位置编码的2维矩阵中的数值为自然数经过缩放后的足够小的数值,有助于在之后的梯度下降过程中更快的收敛。
位置编码的2维矩阵的shape为[max_len句子最大长度, 词嵌入维度]。
sin正弦波和cos余弦波的值域范围都是1到-1很好的控制了嵌入数值的大小, 有助于梯度的快速计算。
1.position:shape为[max_len句子最大长度, 1]的2维矩阵,元素值为从0到max_len-1。
2.div_term:shape为[词嵌入维度数除以2]的1维向量,向量中每个数为极小的数值。
3.position * div_term:[max_len句子最大长度, 1] * [词嵌入维度数除以2] 得 [max_len句子最大长度, 词嵌入维度数除以2]
4.pe[:, 0::2] = sin正弦函数([max_len句子最大长度, 词嵌入维度数除以2])
把数据填充到位置编码的2维矩阵中的偶数列上
5.pe[:, 1::2] = cos余弦函数([max_len句子最大长度, 词嵌入维度数除以2])
把数据填充到位置编码的2维矩阵中的奇数列上
5.pe = pe.unsqueeze(0)
位置编码的2维矩阵增加一个维度,从[max_len句子最大长度, 词嵌入维度]变成[1, max_len句子最大长度, 词嵌入维度]。
变成3维后的位置编码矩阵目的是和经过Embedding后输出的三维矩阵进行相加。
6.register_buffer('pe', pe)
把3维的pe位置编码矩阵注册成模型的buffer。
我们把它认为是对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不需要随着优化步骤进行更新的增益对象。
注册之后我们就可以在模型保存后重新加载时和模型结构与参数一同被加载。
7.embedding = embedding + Variable(pe[:, :embedding.size(1)], requires_grad=False)
三维的embedding嵌入词向量和三维的位置编码矩阵pe进行相加,最终得到经过了Embedding层和位置编码的嵌入词向量。
embedding:3维的[batch size, 句子最大长度max_len, 词嵌入维度]的批量文本序列的嵌入词向量。
pe:3维的[1, max_len句子最大长度, 词嵌入维度]的位置编码矩阵。
embedding.size(1):句子最大长度max_len。
pe[:, :embedding.size(1)]:
等同于pe[:, :x.size(1), :]的写法。
因为位置编码矩阵pe的第二维max_len句子最大长度的默认值为5000,一般已经大于实际可能的句子长度。
因此需要从位置编码矩阵pe中进行切片取出和embedding的最大句子长度max_len相同的第二维行数,其他维度的数量不变。
requires_grad=False:因为位置编码矩阵pe不需要进行梯度求解的,因此把requires_grad设置成false,最后使用Variable进行封装。
8.dropout(x)
经过了Embedding层和位置编码的嵌入词向量最终还要经过“p=丢弃率”的Dropout层。
"""
#======================================= 嵌入层 =========================================#
"""
文本嵌入层的作用:
无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示,
希望在这样的高维空间捕捉词汇间的关系.
"""
# 导入必备的工具包
import torch
# 预定义的网络层torch.nn, 工具开发者已经帮助我们开发好的一些常用层,
# 比如,卷积层, lstm层, embedding层等, 不需要我们再重新造轮子.
import torch.nn as nn
# 数学计算工具包
import math
# torch中变量封装函数Variable.
from torch.autograd import Variable
# 定义Embeddings类来实现文本嵌入层,这里s说明代表两个一模一样的嵌入层, 他们共享参数.
# 该类继承nn.Module, 这样就有标准层的一些功能, 这里我们也可以理解为一种模式, 我们自己实现的所有层都会这样去写.
class Embeddings(nn.Module):
def __init__(self, d_model, vocab):
"""类的初始化函数, 有两个参数, d_model: 指词嵌入的维度, vocab: 指词表的大小."""
# 接着就是使用super的方式指明继承nn.Module的初始化函数, 我们自己实现的所有层都会这样去写.
super(Embeddings, self).__init__()
"""创建Embedding嵌入层:embedding = nn.Embedding(词表大小, 词嵌入维度)"""
# 之后就是调用nn中的预定义层Embedding, 获得一个词嵌入对象self.lut
self.lut = nn.Embedding(vocab, d_model) #nn.Embedding(词表大小, 词嵌入维度)
# 最后就是将d_model传入类中
self.d_model = d_model #词嵌入维度
"""
1.embedding = nn.Embedding(词表大小, 词嵌入维度)
创建Embedding嵌入层。
2.output = embedding(input)
批量句子中的单词索引值传入Embedding嵌入层,输出单词索引对应的词嵌入维度的权重向量。
2维[batch size, 句子最大长度max_len]的input传入Embedding嵌入层输出3维[batch size, 句子最大长度max_len, 词嵌入维度]的output。
3. output * math.sqrt(词嵌入维度)
math.sqrt(词嵌入维度)作为Embedding嵌入层输出output的缩放系数。
该sqrt结果值越大那么上述两者相乘的结果值就越大,该sqrt结果值越小那么上述两者相乘的结果值就越小。
"""
def forward(self, x):
"""可以将其理解为该层的前向传播逻辑,所有层中都会有此函数
当传给该类的实例化对象参数时, 自动调用该类函数
参数x: 因为Embedding层是首层, 所以代表输入给模型的文本通过词汇映射后的张量"""
"""
embedding(input) * math.sqrt(词嵌入维度)
math.sqrt(词嵌入维度)在此处相当于缩放系数,该sqrt结果值越大那么上述两者相乘的结果值就越大,
该sqrt结果值越小那么上述两者相乘的结果值就越小。
"""
# 将x传给self.lut并与根号下self.d_model相乘作为结果返回
return self.lut(x) * math.sqrt(self.d_model)
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# print("embr:", embr)
# print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#============================ 位置编码器 =========================================#
"""
位置编码器的作用:
因为在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,
将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失.
"""
"""
RuntimeError: exp_vml_cpu not implemented for 'Long'
把 div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
修改为 div_term = torch.exp(torch.arange(0., d_model, 2) * -(math.log(10000.0) / d_model))
就是int值的0修改为Float值的0.
RuntimeError: expected device cpu and dtype Float but got device cpu and dtype Long
把 position = torch.arange(0, max_len).unsqueeze(1)
修改为 position = torch.arange(0., max_len).unsqueeze(1)
就是int值的0修改为Float值的0.
"""
# 定义位置编码器类, 我们同样把它看做一个层, 因此会继承nn.Module
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout, max_len=5000):
"""位置编码器类的初始化函数, 共有三个参数, 分别是d_model: 词嵌入维度,
dropout: 置0比率, max_len: 每个句子的最大长度"""
super(PositionalEncoding, self).__init__()
# 实例化nn中预定义的Dropout层, 并将dropout传入其中, 获得对象self.dropout
self.dropout = nn.Dropout(p=dropout)
""" 初始化一个位置编码矩阵:pe = torch.zeros(max_len句子最大长度, 词嵌入维度) """
# 初始化一个位置编码矩阵, 它是一个0阵,矩阵的大小是max_len x d_model.
pe = torch.zeros(max_len, d_model)
# 初始化一个绝对位置矩阵, 在我们这里,词汇的绝对位置就是用它的索引去表示.
# 所以我们首先使用arange方法获得一个连续自然数向量,然后再使用unsqueeze方法拓展向量维度使其成为矩阵,
# 又因为参数传的是1,代表矩阵拓展的位置,会使向量变成一个max_len x 1 的矩阵,
position = torch.arange(0., max_len).unsqueeze(1)
print("position.shape:",position.shape) # [max_len句子最大长度, 1]
# 绝对位置矩阵初始化之后,接下来就是考虑如何将这些位置信息加入到位置编码矩阵中,
# 最简单思路就是先将max_len x 1的绝对位置矩阵, 变换成max_len x d_model形状,然后覆盖原来的初始位置编码矩阵即可,
# 要做这种矩阵变换,就需要一个1xd_model形状的变换矩阵div_term,我们对这个变换矩阵的要求除了形状外,
# 还希望它能够将自然数的绝对位置编码缩放成足够小的数字,有助于在之后的梯度下降过程中更快的收敛。
# 这样我们就可以开始初始化这个变换矩阵了。
# 首先使用arange获得一个自然数矩阵, 但是细心的同学们会发现, 我们这里并没有按照预计的一样初始化一个1xd_model的矩阵,
# 而是有了一个跳跃,只初始化了一半即1xd_model/2 的矩阵。 为什么是一半呢,其实这里并不是真正意义上的初始化了一半的矩阵,
# 我们可以把它看作是初始化了两次,而每次初始化的变换矩阵会做不同的处理,
# 第一次初始化的变换矩阵分布在正弦波上,第二次初始化的变换矩阵分布在余弦波上,
# 并把这两个矩阵分别填充在位置编码矩阵的偶数和奇数位置上,组成最终的位置编码矩阵。
div_term = torch.exp(torch.arange(0., d_model, 2) * -(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
# 这样我们就得到了位置编码矩阵pe, pe现在还只是一个二维矩阵,要想和embedding的输出(一个三维张量)相加,
# 就必须拓展一个维度,所以这里使用unsqueeze拓展维度.
pe = pe.unsqueeze(0)
print("pe.shape",pe.shape) #([1, 60, 512]
# 最后把pe位置编码矩阵注册成模型的buffer,什么是buffer呢,
# 我们把它认为是对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不需要随着优化步骤进行更新的增益对象.
# 注册之后我们就可以在模型保存后重加载时和模型结构与参数一同被加载.
self.register_buffer('pe', pe)
"""
位置编码层类:PositionalEncoding(词嵌入维度, Dropout的置0丢弃比率, 默认值max_len=5000)
1.pe = torch.zeros(max_len句子最大长度, 词嵌入维度)
初始化一个位置编码的2维矩阵,shape为[max_len句子最大长度, 词嵌入维度]。
2.position = torch.arange(0., max_len).unsqueeze(1)
初始化一个绝对位置编码的2维矩阵,为一个shape为[max_len句子最大长度, 1]的2维矩阵,元素值为从0到max_len-1。
torch.arange(0., max_len):得到一个1维的shape为[max_len]的向量,元素值为从0到max_len-1。
torch.arange(0., max_len).unsqueeze(1):得到一个2维shape为[max_len, 1]的矩阵,元素值为从0到max_len-1。
arange方法获得一个连续自然数的一维的shape为[max_len]向量:torch.arange(0., 3) 得到 tensor([0., 1., 2.])。
torch.arange(0., 3).unsqueeze(1)得到tensor([[0.], [1.], [2.]]),shape为[max_len, 1]
3.div_term = torch.exp(torch.arange(0., 词嵌入维度, 2) * -(math.log(10000.0) / 词嵌入维度))
生成一个shape为[词嵌入维度数除以2]的1维向量,向量中每个数为极小的数值。
向量中每个极小的数值都带一个后缀e-0X,从e-01、e-02、e-03、e-04、...不等。e-01:10^(-1)即0.1。
1.torch.arange(0., 词嵌入维度, 2):
生成一个shape为[词嵌入维度数除以2]的1维向量。
比如 torch.arange(0., 10, 2)得tensor([0., 2., 4., 6., 8.])
2.-(math.log(10000.0) / 词嵌入维度):
该负数作为一个缩放系数。首先除法运算得到一个很小的正值小数然后取反得负数。
作用为将绝对位置编码的2维矩阵position中的自然数缩放成足够小的数字,有助于在之后的梯度下降过程中更快的收敛。
比如 math.log(10000.0) / 512 得一个很小的正值小数 0.017988946039015984
3.torch.arange(0., 词嵌入维度, 2) * -(math.log(10000.0) / 词嵌入维度):
生成一个shape为[词嵌入维度数除以2]的1维向量,向量中每个数为极小的数值。
向量中每个极小的数值都带一个后缀e-0X,从e-01、e-02、e-03、e-04、...不等。
e-01:10^(-1)即0.1。
4.pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
位置编码的2维矩阵中的数值为自然数经过缩放后的足够小的数值,有助于在之后的梯度下降过程中更快的收敛。
位置编码的2维矩阵的shape为[max_len句子最大长度, 词嵌入维度]。
sin正弦波和cos余弦波的值域范围都是1到-1很好的控制了嵌入数值的大小, 有助于梯度的快速计算。
1.position:shape为[max_len句子最大长度, 1]的2维矩阵,元素值为从0到max_len-1。
2.div_term:shape为[词嵌入维度数除以2]的1维向量,向量中每个数为极小的数值。
3.position * div_term:[max_len句子最大长度, 1] * [词嵌入维度数除以2] 得 [max_len句子最大长度, 词嵌入维度数除以2]
4.pe[:, 0::2] = sin正弦函数([max_len句子最大长度, 词嵌入维度数除以2])
把数据填充到位置编码的2维矩阵中的偶数列上.
5.pe[:, 1::2] = cos余弦函数([max_len句子最大长度, 词嵌入维度数除以2])
把数据填充到位置编码的2维矩阵中的奇数列上
5.pe = pe.unsqueeze(0)
位置编码的2维矩阵增加一个维度,从[max_len句子最大长度, 词嵌入维度]变成[1, max_len句子最大长度, 词嵌入维度]。
变成3维后的位置编码矩阵目的是和经过Embedding后输出的三维矩阵进行相加。
6.register_buffer('pe', pe)
把3维的pe位置编码矩阵注册成模型的buffer。
我们把它认为是对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不需要随着优化步骤进行更新的增益对象。
注册之后我们就可以在模型保存后重新加载时和模型结构与参数一同被加载。
7.embedding = embedding + Variable(pe[:, :embedding.size(1)], requires_grad=False)
三维的embedding嵌入词向量和三维的位置编码矩阵pe进行相加,最终得到经过了Embedding层和位置编码的嵌入词向量。
embedding:3维的[batch size, 句子最大长度max_len, 词嵌入维度]的批量文本序列的嵌入词向量。
pe:3维的[1, max_len句子最大长度, 词嵌入维度]的位置编码矩阵。
embedding.size(1):句子最大长度max_len。
pe[:, :embedding.size(1)]:
等同于pe[:, :x.size(1), :]的写法。
因为位置编码矩阵pe的第二维max_len句子最大长度的默认值为5000,一般已经大于实际可能的句子长度。
因此需要从位置编码矩阵pe中进行切片取出和embedding的最大句子长度max_len相同的第二维行数,其他维度的数量不变。
requires_grad=False:因为位置编码矩阵pe不需要进行梯度求解的,因此把requires_grad设置成false,最后使用Variable进行封装。
8.dropout(x)
经过了Embedding层和位置编码的嵌入词向量最终还要经过“p=丢弃率”的Dropout层。
"""
def forward(self, x):
# print("self.pe[:, :x.size(1)]",self.pe[:, :x.size(1)].shape) #torch.Size([1, 4, 512])
# print("self.pe[:, :x.size(1)]",self.pe[:, :x.size(1), :].shape) #torch.Size([1, 4, 512])
"""forward函数的参数是x, 表示文本序列的词嵌入表示"""
# 在相加之前我们对pe做一些适配工作, 将这个三维张量的第二维也就是句子最大长度的那一维将切片到与输入的x的第二维相同即x.size(1),
# 因为我们默认max_len为5000一般来讲实在太大了,很难有一条句子包含5000个词汇,所以要进行与输入张量的适配.
# 最后使用Variable进行封装,使其与x的样式相同,但是它是不需要进行梯度求解的,因此把requires_grad设置成false.
x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False)
# 最后使用self.dropout对象进行'丢弃'操作, 并返回结果.
return self.dropout(x)
"""
Variable(torch.zeros(...), requires_grad=True/False)
1.torch.autograd.Variable是autograd的核心类,Variable整合了Tensor,并整合了反向传播的相关实现。
2.Variable包含3个属性:
data:保存了Tensor,是本体真实数据。
grad:保存了上述data属性值的梯度值,与data属性的形状一致。
grad_fn:指向Function对象,用于反向传播时的梯度计算之用。
3.requires_grad
1.requires_grad=True:在反向传播时会对所封装的Tensor数据进行梯度求解。
2.requires_grad=False:在反向传播时不会对所封装的Tensor数据进行梯度求解。
3.比如输入的样本数据和目标标签数据使用Variable进行封装的同时而又不需要求梯度时,那么便可以设置requires_grad=False,
那么它是不需要进行梯度求解的。
"""
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# Variable containing:
# ( 0 ,.,.) =
# 35.9321 3.2582 -17.7301 ... 3.4109 13.8832 39.0272
# 8.5410 -3.5790 -12.0460 ... 40.1880 36.6009 34.7141
# -17.0650 -1.8705 -20.1807 ... -12.5556 -34.0739 35.6536
# 20.6105 4.4314 14.9912 ... -0.1342 -9.9270 28.6771
#
# ( 1 ,.,.) =
# 27.7016 16.7183 46.6900 ... 17.9840 17.2525 -3.9709
# 3.0645 -5.5105 10.8802 ... -13.0069 30.8834 -38.3209
# 33.1378 -32.1435 -3.9369 ... 15.6094 -29.7063 40.1361
# -31.5056 3.3648 1.4726 ... 2.8047 -9.6514 -23.4909
# [torch.FloatTensor of size 2x4x512]
# # 调用:
# # 位置编码层类:PositionalEncoding(词嵌入维度, Dropout的置0丢弃比率, 默认值max_len=5000)
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# print("pe_result:", pe_result)
"""
>>> torch.arange(0., 5)
tensor([0., 1., 2., 3., 4.])
>>> torch.arange(0., 5).shape
torch.Size([5])
>>> torch.arange(0., 5).unsqueeze(1).shape
torch.Size([5, 1])
"""
"""
>>> a = torch.arange(0., 10, 2)
>>> print(a) #tensor([0., 2., 4., 6., 8.])
tensor([0., 2., 4., 6., 8.])
>>> print(a.shape)
torch.Size([5])
>>> math.log(10000.0) / 512
0.017988946039015984
>>> torch.exp(torch.arange(0., 10, 2) * -(math.log(10000.0) / 512))
tensor([1.0000, 0.9647, 0.9306, 0.8977, 0.8660])
"""
#=========================绘制词汇向量中特征的分布曲线=========================#
"""
绘制词汇向量中特征的分布曲线
每条颜色的曲线代表某一个词汇中的特征在不同位置的含义.
保证同一词汇随着所在位置不同它对应位置嵌入向量会发生变化.
正弦波和余弦波的值域范围都是1到-1这又很好的控制了嵌入数值的大小, 有助于梯度的快速计算.
"""
import matplotlib.pyplot as plt
import numpy as np
"""
1.pe = PositionalEncoding(词嵌入维度, Dropout的置0丢弃比率, 默认值max_len=5000)
位置编码层类PositionalEncoding(词嵌入维度, Dropout的置0丢弃比率, 默认值max_len=5000)
位置编码层最终得到一个三维的经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
2.embedding = Variable(torch.zeros(1, 100, 20))
值全为0的三维的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
3.output = pe(embedding)
传入值全为0的三维的词嵌入张量,那么经过位置编码层的output输出值仅为包含位置编码信息的三维张量,实际并不包含Embedding层的词嵌入信息。
4.plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())
定义画布的横纵坐标, 横坐标到100的长度,纵坐标是某一个词汇中的词嵌入维特征在不同句子长度下对应的值。
保证同一词汇随着所在位置不同它对应位置嵌入向量会发生变化。
因为词嵌入维度总共有20维, 我们这里只查看词嵌入维度的4、5、6、7维的值。
"""
# # 创建一张15 x 5大小的画布
# plt.figure(figsize=(15, 5))
#
# # 实例化PositionalEncoding类得到pe对象, 输入参数是20和0
# # PositionalEncoding(词嵌入维度, Dropout的置0丢弃比率, 默认值max_len=5000)
# pe = PositionalEncoding(20, 0)
#
# # 然后向pe传入被Variable封装的tensor, 这样pe会直接执行forward函数,
# # 且这个tensor里的数值都是0, 被处理后相当于位置编码张量
# y = pe(Variable(torch.zeros(1, 100, 20)))
#
# # 然后定义画布的横纵坐标, 横坐标到100的长度, 纵坐标是某一个词汇中的某维特征在不同长度下对应的值
# # 因为总共有20维之多, 我们这里只查看4,5,6,7维的值.
# plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())
# #查看所有维的值
# # plt.plot(np.arange(100), y[0, :, :].data.numpy())
#
# # 在画布上填写维度提示信息
# plt.legend(["dim %d"%p for p in [4,5,6,7]])
# plt.show()
"""
编码器部分:
1.由N个编码器层堆叠而成
2.每个编码器层由两个子层连接结构组成
3.第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
4.第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
"""
"""
掩码张量
1.掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,
至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩,
也可以说被替换, 它的表现形式是一个张量.
2.在transformer中, 掩码张量的主要作用应用在attention注意力计算中的注意力得分张量scores上。
在生成注意力得分张量scores中的值的其计算过程有可能是已知了未来信息而计算得到的,
而未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,
但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,
因此,未来的信息可能被提前利用,所以我们会对attention注意力计算中的注意力得分张量scores中的值进行遮掩。
3.掩码张量的可视化输出效果分析
通过观察可视化掩码张量中的方阵, 值为1的部分为黄色代表信息会被遮掩, 值为0的部分为紫色代表信息不会被遮掩。
以矩阵的从左上角到右下角的对角线所在的值和在对角线左下方的所有值均置为1,在对角线右上方的所有值均置为0。
横坐标代表每个目标词汇的位置, 纵坐标代表可查看的目标单词的位置;
在横坐标0的位置向纵轴上望过去一列都是黄色的, 代表全部信息会被遮掩,说明第一个目标单词还没有产生。
在横坐标1的位置向纵轴上望过去一列中纵坐标0的位置为紫色代表信息不会被遮掩,
说明第一个目标单词已经产生,其他后面位置的单词看不到, 以此类推。
在横坐标2的位置向纵轴上望过去一列中纵坐标0、1的位置为紫色代表信息不会被遮掩,
说明第一、二个目标单词已经产生,其他后面位置的单词看不到, 以此类推。
4.scores = scores.masked_fill(mask == 0, -1e9)
掩码张量mask中值为1的部分对应到注意力得分张量scores中相同位置的值则保持不变,代表注意力得分张量scores中的该部分信息不会被遮掩。
掩码张量mask中值为0的部分对应到注意力得分张量scores中相同位置的值则全置为-1e9极小值,代表注意力得分张量scores中的该部分信息会被遮掩。
通过掩码张量mask得知, 值为1的部分代表信息不会被遮掩, 值为0的部分代表信息会被遮掩。
5.源输入数据的掩码张量
进行源数据遮掩的原因并非是抑制信息泄漏,而是遮蔽掉对结果没有意义的字符而产生的注意力值,
把注意力集中在源数据的某一部分,不让源数据的注意力被分散,以此提升模型效果和训练速度。
6.目标数据的掩码张量
形状为[batch size, 句子最大长度max_len, 句子最大长度max_len]。
目标数据掩码张量用于对目标数据进行遮掩,因为此时模型可能还没有生成任何目标数据。
比如在解码器准备生成第一个字符或词汇时,我们其实已经传入了第一个字符'<bos>'以便计算损失,
但是我们不希望在生成第一个字符时模型能利用这个信息,因此我们会将目标数据的第一个字符遮掩。
同样生成第二个字符或词汇时,模型只能使用目标数据的第一个字符或词汇信息,
不允许模型使用第二个字符以及之后的信息。
目标数据的掩码张量只能看到当前单词前面的单词,但看不到当前单词和后面的单词。
比如预测第三个单词,只能第一第二个单词,看不到当前第三个单词和后面的单词。
比如目标语句[我,爱,北京,天安门],那么解码器预测"我"时,解码器输入的第一个单词是'<bos>'以便计算损失,
那么只看到第一个单词'<bos>',看不到"我"。预测"爱"时,解码器输入的第二个单词是'我',
那么只看到['<bos>','我'],看不到"爱"。
7.Bert: 判断一句话是否完整!
source: 一句话
target: 这句话的最后一个标点符号
next_char == '.?!'
next_char == '<END>'
"""
# 导入必备的工具包
import torch
import numpy as np
import matplotlib.pyplot as plt
"""
mask掩码张量的形状可以定义为[batch size, 句子最大长度max_len, 句子最大长度max_len],
因为mask需要和注意力得分张量scores进行匹配形状:scores.masked_fill(mask == 0, -1e9)
1.attn_shape = (1, size, size)
定义掩码张量的形状为[1, size, size],最后两个维度形成一个方阵,即[1, 句子最大长度max_len, 句子最大长度max_len]。
该掩码张量的目的为向后遮掩。
2.subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')
1.np.ones(attn_shape):掩码张量中全部值初始化为1。
2.np.triu的k=1:
以矩阵的从左上角到右下角的对角线整体向右上方移动一格作为标准线,标准线所在的值和在标准线右上方的所有值原封不动,
而在标准线左下方的所有值均置为0。
3.np.triu(初始化值为1掩码张量, k=1):
以掩码张量的从左上角到右下角的对角线整体向右上方移动一格作为标准线,标准线所在的值和在标准线右上方的所有值原封不动均为1,
而在标准线左下方的所有值均置为0,形成右上方值全为1的三角阵,其他位置值全为0。
4.astype('uint8'):为了节约空间,使其中的数据类型变为unit8(无符号8位整型)
3.torch.from_numpy(1 - subsequent_mask)
掩码张量中的0值变成1,1值变成0。
即以矩阵的从左上角到右下角的对角线所在的值和在对角线左下方的所有值均置为1,在对角线右上方的所有值均置为0。
通过可视化掩码张量得知, 值为1的部分为黄色代表信息会被遮掩, 值为0的部分为紫色代表信息不会被遮掩。
1.1 - subsequent_mask
表示1减去subsequent_mask掩码张量中的任何值,相当于掩码张量中的0值变成1,1值变成0。
原理是1减掩码张量中的0值等于1,1减掩码张量中的1值等于0。
2.torch.from_numpy(numpy类型变量):把numpy的ndarray 转换为 pytorch的tensor
tensor类型变量.numpy():把pytorch的tensor 转换为 numpy的ndarray
"""
def subsequent_mask(size):
"""生成向后遮掩的掩码张量, 参数size是掩码张量最后两个维度的大小, 它的最后两维形成一个方阵"""
# 在函数中, 首先定义掩码张量的形状
attn_shape = (1, size, size)
# 然后使用np.ones方法向这个形状中添加1元素,形成上三角阵, 最后为了节约空间,
# 再使其中的数据类型变为无符号8位整形unit8
subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')
# 最后将numpy类型转化为torch中的tensor, 内部做一个1 - 的操作,
# 在这个其实是做了一个三角阵的反转, subsequent_mask中的每个元素都会被1减,
# 如果是0, subsequent_mask中的该位置由0变成1
# 如果是1, subsequent_mask中的该位置由1变成0
return torch.from_numpy(1 - subsequent_mask)
# #输入实例:
# # 生成的掩码张量的最后两维的大小
# size = 5
# #调用
# mask = subsequent_mask(size)
# print("mask:", mask)
# # 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)
#
# plt.figure(figsize=(5,5))
# plt.imshow(subsequent_mask(20)[0])
# plt.show()
"""
np.tril(input, k=-1/0/1)
k=-1:以矩阵的从左上角到右下角的对角线整体向左下方移动一格作为标准线,标准线所在的值和在标准线左下方的所有值原封不动,而在标准线右上方的所有值均置为0。
k=0:以矩阵的从左上角到右下角的对角线作为标准线,标准线所在的值和在标准线左下方的所有值原封不动,而在标准线右上方的所有值均置为0。
k=1:以矩阵的从左上角到右下角的对角线整体向右上方移动一格作为标准线,标准线所在的值和在标准线左下方的所有值原封不动,而在标准线右上方的所有值均置为0。
np.triu(input, k=-1/0/1)
k=-1:以矩阵的从左上角到右下角的对角线整体向左下方移动一格作为标准线,标准线所在的值和在标准线右上方的所有值原封不动,而在标准线左下方的所有值均置为0。
k=0:以矩阵的从左上角到右下角的对角线作为标准线,标准线所在的值和在标准线右上方的所有值原封不动,而在标准线左下方的所有值均置为0。
k=1:以矩阵的从左上角到右下角的对角线整体向右上方移动一格作为标准线,标准线所在的值和在标准线右上方的所有值原封不动,而在标准线左下方的所有值均置为0。
---------------------------------------------------------------------
>>> np.triu([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], k=-1)
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 0, 10, 11, 12],
[ 0, 0, 15, 16]])
>>> np.triu([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], k=0)
array([[ 1, 2, 3, 4],
[ 0, 6, 7, 8],
[ 0, 0, 11, 12],
[ 0, 0, 0, 16]])
>>> np.triu([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], k=1)
array([[ 0, 2, 3, 4],
[ 0, 0, 7, 8],
[ 0, 0, 0, 12],
[ 0, 0, 0, 0]])
---------------------------------------------------------------------
>>> np.tril([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], k=-1)
array([[ 0, 0, 0, 0],
[ 5, 0, 0, 0],
[ 9, 10, 0, 0],
[13, 14, 15, 0]])
>>> np.tril([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], k=0)
array([[ 1, 0, 0, 0],
[ 5, 6, 0, 0],
[ 9, 10, 11, 0],
[13, 14, 15, 16]])
>>> np.tril([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], k=1)
array([[ 1, 2, 0, 0],
[ 5, 6, 7, 0],
[ 9, 10, 11, 12],
[13, 14, 15, 16]])
---------------------------------------------------------------------
>>> np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]], k=-1)
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 0, 8, 9],
[ 0, 0, 12]])
>>> np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]], k=0)
array([[ 1, 2, 3],
[ 0, 5, 6],
[ 0, 0, 9],
[ 0, 0, 0]])
>>> np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]], k=1)
array([[ 0, 2, 3],
[ 0, 0, 6],
[ 0, 0, 0],
[ 0, 0, 0]])
"""
"""
1.注意力:
我们观察事物时,之所以能够快速判断一种事物(当然允许判断是错误的),
是因为我们大脑能够很快把注意力放在事物最具有辨识度的部分从而作出判断,
而并非是从头到尾的观察一遍事物后,才能有判断结果. 正是基于这样的理论,就产生了注意力机制.
2.注意力计算规则:
它需要三个指定的输入Q(query), K(key), V(value), 然后通过公式得到注意力的计算结果,
这个结果代表query在key和value作用下的表示. 而这个具体的计算规则有很多种, 我这里只介绍我们用到的这一种.
3.注意力机制:
注意力机制是注意力计算规则能够应用的深度学习网络的载体, 除了注意力计算规则外,
还包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体.
使用自注意力计算规则的注意力机制称为自注意力机制.
4.Q, K, V的比喻解释:
Q是一段准备被概括的文本; K是给出的提示; V是大脑中的对提示K的延伸。当Q=K=V时, 称作自注意力机制。
假如我们有一个问题: 给出一段文本query,使用一些关键词对它进行描述!
为了方便统一正确答案,这道题可能预先已经给大家写出了一些关键词作为提示。其中这些给出的提示信息就可以看作是key,
而整个的文本信息就相当于是query。而value的含义则更抽象,可以比作是你看到这段文本信息后,脑子里浮现的答案信息。
这里我们又假设大家最开始都不是很聪明,第一次看到这段文本后脑子里基本上浮现的信息就只有提示信息key,
因此此时可以认为key与value基本是相同的,但是随着我们对这个问题的深入理解,通过我们的不断思考,脑子里浮现的答案信息越来越多,
并且能够开始对这段文本也就是query开始进行提取关键信息进行表示,那么这就是注意力作用的过程。
通过这个过程,我们最终脑子里的value已经和一开始的value发生了变化。
根据提示信息key生成了query的关键词表示方法,也就是一种特征表示方法。
刚刚我们说到key和value一般情况下默认是相同,与query是不同的,这种是我们一般的注意力输入形式,
就如同我们的刚刚的例子,使用一般注意力机制,是使用不同于给定文本query的关键词来表示它。
但有一种特殊情况,也就是query与key和value相同,这种情况我们称为自注意力机制,
而自注意力机制,需要用给定文本query自身来表达自己,也就是说你需要从给定文本中抽取关键词来表述它, 相当于对文本query自身的一次特征提取。
5.注意力计算规则的函数: attention
它的输入就是Q,K,V以及mask和dropout, mask用于掩码, dropout用于随机置0.
它的输出有两个, query的注意力表示以及注意力张量.
"""
import torch
import math
import torch.nn.functional as F
# torch中变量封装函数Variable.
from torch.autograd import Variable
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
from day08.掩码张量_2 import subsequent_mask
"""
tensor变量.masked_fill(掩码张量mask == 0, -1e9)
1.掩码张量mask中值为1的部分对应到tensor变量中相同位置的值则保持不变,代表信息不会被遮掩。
而掩码张量mask中值为0的部分对应到tensor变量中相同位置的值则全置为-1e9极小值,代表信息会被遮掩。
subsequent_mask返回的掩码张量mask中0值的位置对应到tensor变量中同样的位置上的值均会被置换为-1e9极小值,代表信息会被遮掩。
2.编码器输入的掩码张量
定义源数据编码器输入的初始化掩码张量, 因为元素都是1, 在我们这里1代表不遮掩,因此相当于对源数据没有任何遮掩。
source_mask = Variable(torch.ones(1, 1, 10))
3.通过掩码张量mask得知, 值为1的部分代表信息不会被遮掩, 值为0的部分代表信息会被遮掩。
# 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)
-------------------------------------------------------------------------
attention(query, key, value, mask=None, dropout=None)
如果为自注意机制,那么Q(query)=K(key)=V(value)
Q(query):经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
K(key)和V(value):Q是一段准备被概括的文本,那么K是给出的提示; V是大脑中的对提示K的延伸。
mask:掩码张量mask
dropout:nn.Dropout(p=置0丢弃率)
dropout本身是对模型结构中的节点数进行随机抑制的比率,
又因为节点被抑制等效就是该节点的输出都是0,因此也可以把dropout看作是对输出矩阵的随机置0的比率。
-------------------------------------------------------------------------
如果为自注意机制,那么Q(query)=K(key)=V(value),
Q/K/V均为经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
1.scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
1.K(key)的转置为:[batch size, 词嵌入维度, 句子最大长度max_len]
2.Q(query)和K(key)的转置进行点积:
那么 [batch size, 句子最大长度max_len, 词嵌入维度] * [batch size, 词嵌入维度, 句子最大长度max_len],
得到 [batch size, 句子最大长度max_len, 句子最大长度max_len]
2.matmul(p_attn, value)
即 p_attn 和 Q(query)进行点积运算,
那么 [batch size, 句子最大长度max_len, 句子最大长度max_len] * [batch size, 句子最大长度max_len, 词嵌入维度],
得到 [batch size, 句子最大长度max_len, 词嵌入维度]
-------------------------------------------------------------------------
1.d_k = query.size(-1):d_k即为 词嵌入维度
Q(query):经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
query.size(-1):取出词嵌入维度
2.scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
Q(query)和K(key)的转置进行点积乘的结果再除以缩放系数sqrt(d_k),这种计算方法也称为缩放点积注意力计算,得到注意力得分张量scores
1.key.transpose(-2, -1):最后两个维度之间互换,即K(key)进行转置
2.matmul(query, key.transpose(-2, -1)):Q(query)和K(key)的转置进行点积乘
3.math.sqrt(d_k):作为缩放系数,d_k(词嵌入维度)进行开根号
3.scores = scores.masked_fill(mask == 0, -1e9)
掩码张量mask中值为1的部分对应到注意力得分张量scores中相同位置的值则保持不变,代表注意力得分张量scores中的该部分信息不会被遮掩。
掩码张量mask中值为0的部分对应到注意力得分张量scores中相同位置的值则全置为-1e9极小值,代表注意力得分张量scores中的该部分信息会被遮掩。
通过掩码张量mask得知, 值为1的部分代表信息不会被遮掩, 值为0的部分代表信息会被遮掩。
源输入数据的掩码张量
进行源数据遮掩的原因并非是抑制信息泄漏,而是遮蔽掉对结果没有意义的字符而产生的注意力值,
把注意力集中在源数据的某一部分,不让源数据的注意力被分散,以此提升模型效果和训练速度。
目标数据的掩码张量
形状为[batch size, 句子最大长度max_len, 句子最大长度max_len]。
目标数据掩码张量用于对目标数据进行遮掩,因为此时模型可能还没有生成任何目标数据。
比如在解码器准备生成第一个字符或词汇时,我们其实已经传入了第一个字符'<bos>'以便计算损失,
但是我们不希望在生成第一个字符时模型能利用这个信息,因此我们会将目标数据的第一个字符遮掩。
同样生成第二个字符或词汇时,模型只能使用目标数据的第一个字符或词汇信息,
不允许模型使用第二个字符以及之后的信息。
目标数据的掩码张量只能看到当前单词前面的单词,但看不到当前单词和后面的单词。
比如预测第三个单词,只能第一第二个单词,看不到当前第三个单词和后面的单词。
比如目标语句[我,爱,北京,天安门],那么解码器预测"我"时,解码器输入的第一个单词是'<bos>'以便计算损失,
那么只看到第一个单词'<bos>',看不到"我"。预测"爱"时,解码器输入的第二个单词是'我',
那么只看到['<bos>','我'],看不到"爱"。
4.p_attn = F.softmax(scores, dim = -1)
对注意力得分张量scores的最后一个维度的值进行转换为概率值
5.p_attn = dropout(p_attn)
对p_attn张量中的值按照丢弃率为百分之多少的进行置为0
6.torch.matmul(p_attn, value), p_attn
注意力计算的最后一步:p_attn张量和V(value)点积乘
"""
def attention(query, key, value, mask=None, dropout=None):
"""注意力机制的实现, 输入分别是query, key, value, mask: 掩码张量,
dropout是nn.Dropout层的实例化对象, 默认为None
"""
# 在函数中, 首先取query的最后一维的大小, 一般情况下就等同于我们的词嵌入维度, 命名为d_k
d_k = query.size(-1)
# print("d_k:",d_k) #64
# 按照注意力公式, 将query与key的转置相乘, 这里面key是将最后两个维度进行转置,
# 再除以缩放系数根号下d_k, 这种计算方法也称为缩放点积注意力计算.
# 得到注意力得分张量scores
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
# print("scores.shape",scores.shape) #torch.Size([2, 8, 4, 4])
# 接着判断是否使用掩码张量
if mask is not None:
# 使用tensor的masked_fill方法, 将掩码张量和scores张量每个位置一一比较, 如果掩码张量处为0
# 则对应的scores张量用-1e9这个值来替换, 如下演示
scores = scores.masked_fill(mask == 0, -1e9)
# print("scores.shape:", scores.shape) #torch.Size([2, 4, 4])
# print("scores:",scores)
# tensor([[[1.4670e+04, -1.0000e+09, -1.0000e+09, -1.0000e+09],
# [-7.1877e+02, 1.3407e+04, -1.0000e+09, -1.0000e+09],
# [-7.7895e+02, 1.1335e+03, 1.3097e+04, -1.0000e+09],
# [-1.8603e+02, -5.8361e+02, 1.7998e+02, 1.1442e+04]],
#
# [[1.1710e+04, -1.0000e+09, -1.0000e+09, -1.0000e+09],
# [4.9352e+02, 1.3066e+04, -1.0000e+09, -1.0000e+09],
# [-7.1906e+02, 6.3984e+02, 1.3662e+04, -1.0000e+09],
# [6.2098e+02, 3.5394e+02, -5.2597e+02, 1.3532e+04]]],
# grad_fn= < MaskedFillBackward0 >)
# 对scores的最后一维进行softmax操作, 使用F.softmax方法, 第一个参数是softmax对象, 第二个是目标维度.
# 这样获得最终的注意力张量
p_attn = F.softmax(scores, dim = -1)
# 之后判断是否使用dropout进行随机置0
if dropout is not None:
# 将p_attn传入dropout对象中进行'丢弃'处理
p_attn = dropout(p_attn)
# 最后, 根据公式将p_attn与value张量相乘获得最终的query注意力表示, 同时返回注意力张量
return torch.matmul(p_attn, value), p_attn
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512])
#===================================================
#输入参数:
# 我们令输入的query, key, value都相同, 位置编码的输出
# query = key = value = pe_result
#调用:
# attn, p_attn = attention(query, key, value)
# print("attn:", attn)
# print("p_attn:", p_attn)
#===================================================
#带有mask的输入参数:
# query = key = value = pe_result
# # 令mask为一个2x4x4的零张量
# mask = Variable(torch.zeros(2, 4, 4))
# #调用:
# attn, p_attn = attention(query, key, value, mask=mask)
# print("attn:", attn)
# print("p_attn:", p_attn)
#===================================================
# #输入实例:
# # 生成的掩码张量的最后两维的大小
# size = 4
# #调用
# mask = subsequent_mask(size)
# print("subsequent_mask.shape:", mask.shape) #torch.Size([1, 4, 4])
# print("subsequent_mask:", mask)
# # tensor([[[1, 0, 0, 0],
# # [1, 1, 0, 0],
# # [1, 1, 1, 0],
# # [1, 1, 1, 1]]], dtype=torch.uint8)
# #带有mask的输入参数:
# query = key = value = pe_result
# # print("pe_result.shape",pe_result.shape) #torch.Size([2, 4, 512])
# #调用:
# attn, p_attn = attention(query, key, value, mask=mask)
# print("attn.shape:", attn.shape) #torch.Size([2, 4, 512])
# print("p_attn.shape:", p_attn.shape) #torch.Size([2, 4, 4])
# print("attn:", attn)
# print("p_attn:", p_attn)
#===================================================
"""
>>> input = Variable(torch.randn(5, 5))
>>> input
Variable containing:
2.0344 -0.5450 0.3365 -0.1888 -2.1803
1.5221 -0.3823 0.8414 0.7836 -0.8481
-0.0345 -0.8643 0.6476 -0.2713 1.5645
0.8788 -2.2142 0.4022 0.1997 0.1474
2.9109 0.6006 -0.6745 -1.7262 0.6977
[torch.FloatTensor of size 5x5]
>>> mask = Variable(torch.zeros(5, 5))
>>> mask
Variable containing:
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
[torch.FloatTensor of size 5x5]
>>> input.masked_fill(mask == 0, -1e9)
Variable containing:
-1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
-1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
-1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
-1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
-1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
[torch.FloatTensor of size 5x5]
----------------------------------------
mask = subsequent_mask(size)
print("mask:", mask)
# 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)
input = Variable(torch.randn(5, 5))
print("input:", input)
# input: tensor([[ 0.7355, -1.7779, -0.9368, 0.8789, 0.6941],
# [-1.3478, -0.8884, 0.1631, 1.3854, 0.1796],
# [-0.6873, -0.7180, 0.1878, 0.7216, 0.3455],
# [ 0.9504, -0.4424, 1.5504, 0.1826, -2.4970],
# [ 0.2762, 0.7762, 0.1749, -0.9793, -0.1307]])
scores = input.masked_fill(mask == 0, -1e9)
print("scores:", scores)
# scores: tensor([[[ 7.3554e-01, -1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09],
# [-1.3478e+00, -8.8837e-01, -1.0000e+09, -1.0000e+09, -1.0000e+09],
# [-6.8731e-01, -7.1800e-01, 1.8781e-01, -1.0000e+09, -1.0000e+09],
# [ 9.5039e-01, -4.4245e-01, 1.5504e+00, 1.8260e-01, -1.0000e+09],
# [ 2.7617e-01, 7.7617e-01, 1.7487e-01, -9.7926e-01, -1.3075e-01]]])
scores = input.masked_fill(mask == 0, 100)
print("scores:", scores)
# scores: tensor([[[ -0.6732, 100.0000, 100.0000, 100.0000, 100.0000],
# [ -0.5004, 0.2380, 100.0000, 100.0000, 100.0000],
# [ 0.6119, -0.3316, -1.3848, 100.0000, 100.0000],
# [ -0.2672, 0.3029, 0.8813, 1.5983, 100.0000],
# [ -0.3998, -0.9995, 2.9381, -0.4206, -0.7675]]])
"""
"""
多头注意力机制:
从多头注意力的结构图中,貌似这个所谓的多个头就是指多组线性变换层,其实并不是,只有使用了一组线性变化层,
即三个变换张量对Q,K,V分别进行线性变换,这里对Q,K,V分别进行线性变换操作并不会改变原有张量的尺寸,因此每个变换矩阵都是方阵N*N的,
得到输出结果后,多头的作用才开始显现,每个头开始从词义层面分割输出的张量,
也就是每个头都想获得一组Q,K,V进行注意力机制的计算,每个头只获得句子中的每个词的一部分表示,
也就是只分割了最后一维的词嵌入向量. 这就是所谓的多头,将每个头的获得的输入送到注意力机制中, 就形成多头注意力机制.
多头注意力机制的作用:
这种结构设计能让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,
让词义拥有来自更多元的表达,实验表明可以从而提升模型效果.
"""
# 用于深度拷贝的copy工具包
import copy
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
from day08.注意力机制_3 import attention
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
# 首先需要定义克隆函数, 因为在多头注意力机制的实现中, 用到多个结构相同的线性层.
# 我们将使用clone函数将他们一同初始化在一个网络层列表对象中. 之后的结构中也会用到该函数.
def clones(module, N):
"""用于生成相同网络层的克隆函数, 它的参数module表示要克隆的目标网络层, N代表需要克隆的数量"""
# 在函数中, 我们通过for循环对module进行N次深度拷贝, 使其每个module成为独立的层,
# 然后将其放在nn.ModuleList类型的列表中存放.
return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
"""
MultiHeadedAttention 多头注意力机制
1.Q(query)、K(key)、V(value)分别对应一个Linear线性层,多头注意力结构中的最后一个输出层也为Linear线性层,
多头注意力结构一共需要4个Linear线性层,每个Linear线性层结构相同但内存上互相独立。
2.多头注意力中的多头定义为head,每个头都分配等量的“单词在词嵌入维度上的”特征信息,
即每个头分别从词嵌入维度的词向量中所分配得到的等量特征信息,因此需要保证 embedding_dim // head 能整除。
3.输入到attention函数中进行多头注意力计算的传入参数Q(query)、K(key)、V(value)的形状均为[batch size, head, 句子最大长度max_len, d_k],
第三维的句子长度维度max_len和第四维的词向量维度d_k相邻,这样多头注意力机制才能找到词义与句子位置的关系。
从attention函数中可以看到,利用的正是query/key/value四维输入的第三维和第四维。
第二维的head代表每个头,那么每个头分别拥有所有每个单词(第三维)的一部分等量的词向量特征信息(第四维),
也就是说这样我们就得到了每个头(第二维)的词向量特征信息(第三维和第四维)。
1.MultiHeadedAttention(head, embedding_dim, dropout=0.1)
head:多头注意力中的多头
embedding_dim:词嵌入维度/词向量维度
dropout=0.1:置0比率,默认值0.1
dropout本身是对模型结构中的节点数进行随机抑制的比率,
又因为节点被抑制等效就是该节点的输出都是0,因此也可以把dropout看作是对输出矩阵的随机置0的比率。
2.MultiHeadedAttention的__init__
1.assert embedding_dim % head == 0
d_k = embedding_dim // head
assert用于断言。判断词嵌入维度embedding_dim能否整除多头注意力中的多头head。
因为后面需要给多头head中的每个头都分配等量的“单词在词嵌入维度上的”特征信息,
也就是每个头都分配有 embedding_dim / head 的等量特征信息。
比如head=8,embedding_dim=512,那么每个头都分配得到512/8=64维的词向量维度的特征信息。
2.其他
head = head:多头注意力中的多头head数量
attn = None:初始化注意力张量p_attn
dropout = nn.Dropout(p=置0丢弃率)
dropout本身是对模型结构中的节点数进行随机抑制的比率,
又因为节点被抑制等效就是该节点的输出都是0,因此也可以把dropout看作是对输出矩阵的随机置0的比率。
3.linears = nn.ModuleList([copy.deepcopy(nn.Linear(embedding_dim, embedding_dim)) for _ in range(指定生成的线性层数量)])
import copy:深度拷贝的copy工具包
因为在多头注意力机制的实现中, 需要用到多个结构相同但内存上互相独立的线性层。
那么通过深拷贝的方式复制出多个结构相同但内存上互相独立的线性层。
并且最后将多个线性层都一同初始化封装到同一个nn.ModuleList网络层列表对象中。
1.通过nn.Linear(输入维度,输出维度)创建线性层对象,线性层内部拥有一个可用于对输入张量进行维度变换的矩阵,
该矩阵形状为"输入维度X输出维度",输入张量与变换矩阵进行相乘后线性层便输出指定输出维度的张量。
2.nn.Linear(embedding_dim词向量维度, embedding_dim词向量维度)
线性层内部拥有一个“embedding_dim * embedding_dim”的变换矩阵,可用于对输入张量进行维度变换的矩阵。
实际输入张量经过该线性层后的维度并不会改变。
3.需要创建4个指定的nn.Linear(embedding_dim词向量维度, embedding_dim词向量维度)的线性层,
Q/K/V各需要一个线性层:out = Linear(Q)、out = Linear(K)、out = Linear(V)
最后还需要一个线性层作为多头注意力结构中的最后一个输出层,
用于对前一层的输出进行线性变换后得到最终的多头注意力结构的输出。
3.MultiHeadedAttention的forward
1.Q(query)=K(key)=V(value)
如果为自注意力机制,那么Q(query)=K(key)=V(value),
Q/K/V均为经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
2.mask掩码张量
mask掩码张量的形状可以定义为[batch size, 句子最大长度max_len, 句子最大长度max_len],
因为mask需要和注意力得分张量scores进行匹配形状:scores.masked_fill(mask == 0, -1e9)。
源输入数据的掩码张量
进行源数据遮掩的原因并非是抑制信息泄漏,而是遮蔽掉对结果没有意义的字符而产生的注意力值,
把注意力集中在源数据的某一部分,不让源数据的注意力被分散,以此提升模型效果和训练速度。
目标数据的掩码张量
形状为[batch size, 句子最大长度max_len, 句子最大长度max_len]。
目标数据掩码张量用于对目标数据进行遮掩,因为此时模型可能还没有生成任何目标数据。
比如在解码器准备生成第一个字符或词汇时,我们其实已经传入了第一个字符'<bos>'以便计算损失,
但是我们不希望在生成第一个字符时模型能利用这个信息,因此我们会将目标数据的第一个字符遮掩。
同样生成第二个字符或词汇时,模型只能使用目标数据的第一个字符或词汇信息,
不允许模型使用第二个字符以及之后的信息。
目标数据的掩码张量只能看到当前单词前面的单词,但看不到当前单词和后面的单词。
比如预测第三个单词,只能第一第二个单词,看不到当前第三个单词和后面的单词。
比如目标语句[我,爱,北京,天安门],那么解码器预测"我"时,解码器输入的第一个单词是'<bos>'以便计算损失,
那么只看到第一个单词'<bos>',看不到"我"。预测"爱"时,解码器输入的第二个单词是'我',
那么只看到['<bos>','我'],看不到"爱"。
3.batch_size = query.size(0)
获取的是批量数据中的样本数。
Q(query)为经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
4.query, key, value = [model(x).view(batch_size, -1, head, d_k).transpose(1, 2)
for model, x in zip(linears, (query, key, value))]
输出的query/key/value的形状均为[batch size, head, 句子最大长度max_len, d_k],
第三维的句子长度维度max_len和第四维的词向量维度d_k相邻,这样多头注意力机制才能找到词义与句子位置的关系。
从attention函数中可以看到,利用的正是query/key/value四维输入的第三维和第四维。
第二维的head代表每个头,那么每个头分别拥有所有每个单词(第三维)的一部分等量的词向量特征信息(第四维),
也就是说这样我们就得到了每个头(第二维)的词向量特征信息(第三维和第四维)。
1.for model, x in zip(linears, (query, key, value))
linears线性层列表中有4个Linear线性层,此处只会遍历出前3个Linear线性层分别对应Q(query)、K(key)、V(value)。
2.[model(x).view(batch_size, -1, head, d_k).transpose(1, 2)]
1.Q(query)、K(key)、V(value)分别传入3个Linear(embedding_dim词向量维度, embedding_dim词向量维度)的线性层中。
其Linear线性层输出张量的形状为[batch size, 句子最大长度max_len, 词嵌入维度]。
2.head:多头注意力中的多头。d_k:每个头分别从词嵌入维度的词向量中所分配得到的等量特征信息。
head * d_k = embedding_dim词向量维度
3.view(batch_size, -1, head, d_k).transpose(1, 2)
先把[batch size, 句子最大长度max_len, 词嵌入维度]转换为[batch size, 句子最大长度max_len, head, d_k],
然后再转换为[batch size, head, 句子最大长度max_len, d_k]。
3.[batch size, head, 句子最大长度max_len, d_k]
为了让代表句子长度维度max_len和词向量维度d_k能够相邻,这样多头注意力机制才能找到词义与句子位置的关系,
从attention函数中可以看到,利用的正是query/key/value四维输入的第三维和第四维。
第二维的head代表每个头,那么每个头分别拥有所有每个单词(第三维)的一部分等量的词向量特征信息(第四维),
也就是说这样我们就得到了每个头(第二维)的词向量特征信息(第三维和第四维)。
5.x, attn = attention(query, key, value, mask=mask, dropout=dropout)
通过多头注意力计算后,便得到了每个头计算结果组成的4维张量。
输入的query/key/value的形状均为[batch size, head, 句子最大长度max_len, d_k]。
最后返回的注意力输出结果x的形状为[batch size, head, 句子最大长度max_len, d_k],
返回的attn的形状为[batch size, head, 句子最大长度max_len, 句子最大长度max_len]。
从attention函数中可以看到,利用的正是query/key/value四维输入的第三维和第四维。
第二维的head代表每个头,那么每个头分别拥有所有每个单词(第三维)的一部分等量的词向量特征信息(第四维),
也就是说这样我们就得到了每个头(第二维)的词向量特征信息(第三维和第四维)。
1.d_k = query.size(-1):
d_k即为 词嵌入维度。
而在多头注意力中,d_k实质为“embedding_dim词向量维度/head”,即每个头从词嵌入维度的词向量中所获得的等量特征信息。
Q(query):经过了Embedding层和位置编码层的词嵌入张量。此处形状为[batch size, head, 句子最大长度max_len, d_k]。
query.size(-1):取出词嵌入维度
2.scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
Q(query)和K(key)的转置进行点积乘:
即 [batch size, head, 句子最大长度max_len, d_k] * [batch size, head, d_k, 句子最大长度max_len]
的结果为 [batch size, head, 句子最大长度max_len, 句子最大长度max_len]
Q(query)和K(key)的转置进行点积乘的结果再除以缩放系数sqrt(d_k),这种计算方法也称为缩放点积注意力计算,
得到注意力得分张量scores。
1.key.transpose(-2, -1):
最后两个维度之间互换,即K(key)进行转置。
从 [batch size, head, 句子最大长度max_len, d_k] 变成 [batch size, head, d_k, 句子最大长度max_len]
2.matmul(query, key.transpose(-2, -1)):Q(query)和K(key)的转置进行点积乘
3.math.sqrt(d_k):作为缩放系数,d_k(词嵌入维度)进行开根号
3.scores = scores.masked_fill(mask == 0, -1e9)
掩码张量mask中值为1的部分对应到注意力得分张量scores中相同位置的值则保持不变,代表注意力得分张量scores中的该部分信息不会被遮掩。
掩码张量mask中值为0的部分对应到注意力得分张量scores中相同位置的值则全置为-1e9极小值,代表注意力得分张量scores中的该部分信息会被遮掩。
通过掩码张量mask得知, 值为1的部分代表信息不会被遮掩, 值为0的部分代表信息会被遮掩。
4.p_attn = F.softmax(scores, dim = -1)
对注意力得分张量scores的最后一个维度的值进行转换为概率值
5.p_attn = dropout(p_attn)
对p_attn张量中的值按照丢弃率为百分之多少的进行置为0
6.torch.matmul(p_attn, value), p_attn
注意力计算的最后一步:p_attn张量和V(value)点积乘
6.x = x.transpose(1, 2).contiguous().view(batch_size, -1, head * d_k)
从[batch size, head, 句子最大长度max_len, d_k]变换为[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
1.输入的x的形状为[batch size, head, 句子最大长度max_len, d_k],
x.transpose(1, 2)后变换为[batch size, 句子最大长度max_len, head, d_k],
view(batch_size, -1, head * d_k) 后变换为 [batch size, 句子最大长度max_len, embedding_dim词向量维度]。
2.transpose(维度的索引值, 维度的索引值) 和 view(维度值, 维度值) 之间的前后调用顺序
因为view进行维度变换之后也不会改变元素值的顺序存储结构,而transpose进行维度变换之后会改变元素值的顺序存储结构。
1.view(维度值, 维度值).transpose(维度的索引值, 维度的索引值)
先view后transpose,无需调用contiguous()。
2.transpose(维度的索引值, 维度的索引值).view(维度值, 维度值)
先transpose后view,需要调用contiguous()。
7.linears[-1](x)
最后使用线性层列表中的最后一个线性层对输入x进行线性变换后得到最终的多头注意力结构的输出。
"""
"""
1.view(*args) → Tensor
返回一个有相同数据但大小不同的tensor。
返回的tensor必须有与原tensor相同的数据和相同数目的元素,但可以有不同的大小。
一个tensor必须是连续的contiguous()才能被查看。
2.contiguous() → Tensor
返回一个内存连续的有相同数据的tensor,如果原tensor内存连续则返回原tensor
3.is_contiguous() → bool
如果该tensor在内存中是连续的则返回True。
=======================================================
1.transpose(维度的索引值, 维度的索引值) 和 view(维度值, 维度值) 之间的前后调用顺序
因为view进行维度变换之后也不会改变元素值的顺序存储结构,而transpose进行维度变换之后会改变元素值的顺序存储结构。
1.view(维度值, 维度值).transpose(维度的索引值, 维度的索引值)
先view后transpose的话,两者中间无需调用contiguous()。
2.transpose(维度的索引值, 维度的索引值).contiguous().view(维度值, 维度值)
先transpose后view的话,两者中间需要调用contiguous()。
3.contiguous()的作用
返回一个内存连续的有相同数据的tensor,如果原tensor内存连续则返回原tensor。
也就是说contiguous()便可以把经过了transpose或t()操作的tensor重新处理为具有内存连续的并且数据值并没有改动的tensor。
2.x = x.transpose(1, 2).contiguous().view(batch_size, -1, head * d_k)
输入的x的形状为[batch size, head, 句子最大长度max_len, d_k],
先执行x.transpose(1, 2)后变换为[batch size, 句子最大长度max_len, head, d_k],
然后因为先执行transpose后执行view的话,两者中间先要执行contiguous,
把经过了transpose或t()操作的tensor重新处理为具有内存连续的有相同数据的tensor,
最后才能执行view(batch_size, -1, head * d_k) 把 [batch size, 句子最大长度max_len, head, d_k]
变换为 [batch size, 句子最大长度max_len, embedding_dim词向量维度],head * d_k 等于 embedding_dim词向量维度。
3.model(x).view(batch_size, -1, head, d_k).transpose(1, 2)
view(batch_size, -1, head, d_k) 会把 model模型输出 处理为 [batch_size, -1, head, d_k],
最终再transpose(1, 2) 转换为 (batch_size, head, -1, d_k)
4.data = data.view(batch size, -1).t().contiguous()
view不会改变内存中元素存储的顺序,但transpose和t()都会改变内存中元素存储的顺序,t()表示T转置。
最后再使用contiguous()把经过了transpose或t()操作的tensor重新处理为具有内存连续的有相同数据的tensor。
"""
# 我们使用一个类来实现多头注意力机制的处理
class MultiHeadedAttention(nn.Module):
def __init__(self, head, embedding_dim, dropout=0.1):
"""在类的初始化时, 会传入三个参数,head代表头数,embedding_dim代表词嵌入的维度,
dropout代表进行dropout操作时置0比率,默认是0.1."""
super(MultiHeadedAttention, self).__init__()
# 在函数中,首先使用了一个测试中常用的assert语句,判断h是否能被d_model整除,
# 这是因为我们之后要给每个头分配等量的词特征.也就是embedding_dim/head个.
assert embedding_dim % head == 0
# 得到每个头获得的分割词向量维度d_k
self.d_k = embedding_dim // head
# 传入头数h
self.head = head
"""
1.通过nn.Linear(输入维度,输出维度)创建线性层对象,线性层内部拥有一个可用于对输入张量进行维度变换的矩阵,
该矩阵形状为"输入维度X输出维度",输入张量与变换矩阵进行相乘后线性层便输出指定输出维度的张量。
2.nn.Linear(embedding_dim词向量维度, embedding_dim词向量维度)
线性层内部拥有一个“embedding_dim * embedding_dim”的变换矩阵,可用于对输入张量进行维度变换的矩阵。
实际输入张量经过该线性层后的维度并不会改变。
3.需要创建4个指定的nn.Linear(embedding_dim词向量维度, embedding_dim词向量维度)的线性层,
Q/K/V各需要一个线性层:out = Linear(Q)、out = Linear(K)、out = Linear(V)
最后还需要一个线性层作为多头注意力结构中的最后一个输出层,用于对前一层的输出进行线性变换后得到最终的多头注意力结构的输出。
4.linears = nn.ModuleList([copy.deepcopy(nn.Linear(embedding_dim, embedding_dim)) for _ in range(指定生成的线性层数量)])
import copy:深度拷贝的copy工具包
因为在多头注意力机制的实现中, 需要用到多个结构相同但内存上互相独立的线性层。
那么通过深拷贝的方式复制出多个结构相同但内存上互相独立的线性层。
并且最后将多个线性层都一同初始化封装到同一个nn.ModuleList网络层列表对象中。
"""
# 然后获得线性层对象,通过nn的Linear实例化,它的内部变换矩阵是embedding_dim x embedding_dim,然后使用clones函数克隆四个,
# 为什么是四个呢,这是因为在多头注意力中,Q,K,V各需要一个,最后拼接的矩阵还需要一个,因此一共是四个.
self.linears = clones(nn.Linear(embedding_dim, embedding_dim), 4)
# self.attn为None,它代表最后得到的注意力张量,现在还没有结果所以为None.
self.attn = None
# 最后就是一个self.dropout对象,它通过nn中的Dropout实例化而来,置0比率为传进来的参数dropout.
self.dropout = nn.Dropout(p=dropout)
"""
MultiHeadedAttention的forward
1.Q(query)=K(key)=V(value)
如果为自注意力机制,那么Q(query)=K(key)=V(value),
Q/K/V均为经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
2.mask掩码张量
mask掩码张量的形状可以定义为[batch size, 句子最大长度max_len, 句子最大长度max_len],
因为mask需要和注意力得分张量scores进行匹配形状:scores.masked_fill(mask == 0, -1e9)。
源输入数据的掩码张量
进行源数据遮掩的原因并非是抑制信息泄漏,而是遮蔽掉对结果没有意义的字符而产生的注意力值,
把注意力集中在源数据的某一部分,不让源数据的注意力被分散,以此提升模型效果和训练速度。
目标数据的掩码张量
形状为[batch size, 句子最大长度max_len, 句子最大长度max_len]。
目标数据掩码张量用于对目标数据进行遮掩,因为此时模型可能还没有生成任何目标数据。
比如在解码器准备生成第一个字符或词汇时,我们其实已经传入了第一个字符'<bos>'以便计算损失,
但是我们不希望在生成第一个字符时模型能利用这个信息,因此我们会将目标数据的第一个字符遮掩。
同样生成第二个字符或词汇时,模型只能使用目标数据的第一个字符或词汇信息,
不允许模型使用第二个字符以及之后的信息。
目标数据的掩码张量只能看到当前单词前面的单词,但看不到当前单词和后面的单词。
比如预测第三个单词,只能第一第二个单词,看不到当前第三个单词和后面的单词。
比如目标语句[我,爱,北京,天安门],那么解码器预测"我"时,解码器输入的第一个单词是'<bos>'以便计算损失,
那么只看到第一个单词'<bos>',看不到"我"。预测"爱"时,解码器输入的第二个单词是'我',
那么只看到['<bos>','我'],看不到"爱"。
3.batch_size = query.size(0)
获取的是批量数据中的样本数。
Q(query)为经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
4.query, key, value = [model(x).view(batch_size, -1, head, d_k).transpose(1, 2)
for model, x in zip(linears, (query, key, value))]
输出的query/key/value的形状均为[batch size, head, 句子最大长度max_len, d_k],
第三维的句子长度维度max_len和第四维的词向量维度d_k相邻,这样多头注意力机制才能找到词义与句子位置的关系。
从attention函数中可以看到,利用的正是query/key/value四维输入的第三维和第四维。
第二维的head代表每个头,那么每个头分别拥有所有每个单词(第三维)的一部分等量的词向量特征信息(第四维),
也就是说这样我们就得到了每个头(第二维)的词向量特征信息(第三维和第四维)。
1.for model, x in zip(linears, (query, key, value))
linears线性层列表中有4个Linear线性层,此处只会遍历出前3个Linear线性层分别对应Q(query)、K(key)、V(value)。
2.[model(x).view(batch_size, -1, head, d_k).transpose(1, 2)]
1.Q(query)、K(key)、V(value)分别传入3个Linear(embedding_dim词向量维度, embedding_dim词向量维度)的线性层中。
其Linear线性层输出张量的形状为[batch size, 句子最大长度max_len, 词嵌入维度]。
2.head:多头注意力中的多头。d_k:每个头分别从词嵌入维度的词向量中所分配得到的等量特征信息。
head * d_k = embedding_dim词向量维度
3.view(batch_size, -1, head, d_k).transpose(1, 2)
先把[batch size, 句子最大长度max_len, 词嵌入维度]转换为[batch size, 句子最大长度max_len, head, d_k],
然后再转换为[batch size, head, 句子最大长度max_len, d_k]。
3.[batch size, head, 句子最大长度max_len, d_k]
为了让代表句子长度维度max_len和词向量维度d_k能够相邻,这样多头注意力机制才能找到词义与句子位置的关系,
从attention函数中可以看到,利用的正是query/key/value四维输入的第三维和第四维。
第二维的head代表每个头,那么每个头分别拥有所有每个单词(第三维)的一部分等量的词向量特征信息(第四维),
也就是说这样我们就得到了每个头(第二维)的词向量特征信息(第三维和第四维)。
5.x, attn = attention(query, key, value, mask=mask, dropout=dropout)
通过多头注意力计算后,便得到了每个头计算结果组成的4维张量。
输入的query/key/value的形状均为[batch size, head, 句子最大长度max_len, d_k]。
最后返回的注意力输出结果x的形状为[batch size, head, 句子最大长度max_len, d_k],
返回的attn的形状为[batch size, head, 句子最大长度max_len, 句子最大长度max_len]。
从attention函数中可以看到,利用的正是query/key/value四维输入的第三维和第四维。
第二维的head代表每个头,那么每个头分别拥有所有每个单词(第三维)的一部分等量的词向量特征信息(第四维),
也就是说这样我们就得到了每个头(第二维)的词向量特征信息(第三维和第四维)。
1.d_k = query.size(-1):
d_k即为 词嵌入维度。
而在多头注意力中,d_k实质为“embedding_dim词向量维度/head”,即每个头从词嵌入维度的词向量中所获得的等量特征信息。
Q(query):经过了Embedding层和位置编码层的词嵌入张量。此处形状为[batch size, head, 句子最大长度max_len, d_k]。
query.size(-1):取出词嵌入维度
2.scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
Q(query)和K(key)的转置进行点积乘:
即 [batch size, head, 句子最大长度max_len, d_k] * [batch size, head, d_k, 句子最大长度max_len]
的结果为 [batch size, head, 句子最大长度max_len, 句子最大长度max_len]
Q(query)和K(key)的转置进行点积乘的结果再除以缩放系数sqrt(d_k),这种计算方法也称为缩放点积注意力计算,
得到注意力得分张量scores。
1.key.transpose(-2, -1):
最后两个维度之间互换,即K(key)进行转置。
从 [batch size, head, 句子最大长度max_len, d_k] 变成 [batch size, head, d_k, 句子最大长度max_len]
2.matmul(query, key.transpose(-2, -1)):Q(query)和K(key)的转置进行点积乘
3.math.sqrt(d_k):作为缩放系数,d_k(词嵌入维度)进行开根号
3.scores = scores.masked_fill(mask == 0, -1e9)
掩码张量mask中值为1的部分对应到注意力得分张量scores中相同位置的值则保持不变,代表注意力得分张量scores中的该部分信息不会被遮掩。
掩码张量mask中值为0的部分对应到注意力得分张量scores中相同位置的值则全置为-1e9极小值,代表注意力得分张量scores中的该部分信息会被遮掩。
通过掩码张量mask得知, 值为1的部分代表信息不会被遮掩, 值为0的部分代表信息会被遮掩。
4.p_attn = F.softmax(scores, dim = -1)
对注意力得分张量scores的最后一个维度的值进行转换为概率值
5.p_attn = dropout(p_attn)
对p_attn张量中的值按照丢弃率为百分之多少的进行置为0
6.torch.matmul(p_attn, value), p_attn
注意力计算的最后一步:p_attn张量和V(value)点积乘
6.x = x.transpose(1, 2).contiguous().view(batch_size, -1, head * d_k)
从[batch size, head, 句子最大长度max_len, d_k]变换为[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
1.输入的x的形状为[batch size, head, 句子最大长度max_len, d_k],
x.transpose(1, 2)后变换为[batch size, 句子最大长度max_len, head, d_k],
view(batch_size, -1, head * d_k) 后变换为 [batch size, 句子最大长度max_len, embedding_dim词向量维度]。
2.transpose(维度的索引值, 维度的索引值) 和 view(维度值, 维度值) 之间的前后调用顺序
因为view进行维度变换之后也不会改变元素值的顺序存储结构,而transpose进行维度变换之后会改变元素值的顺序存储结构。
1.view(维度值, 维度值).transpose(维度的索引值, 维度的索引值)
先view后transpose,无需调用contiguous()。
2.transpose(维度的索引值, 维度的索引值).view(维度值, 维度值)
先transpose后view,需要调用contiguous()。
7.linears[-1](x)
最后使用线性层列表中的最后一个线性层对输入x进行线性变换后得到最终的多头注意力结构的输出。
"""
def forward(self, query, key, value, mask=None):
"""前向逻辑函数, 它的输入参数有四个,前三个就是注意力机制需要的Q, K, V,
最后一个是注意力机制中可能需要的mask掩码张量,默认是None. """
# 如果存在掩码张量mask
if mask is not None:
# 使用unsqueeze拓展维度
mask = mask.unsqueeze(0)
# 接着,我们获得一个batch_size的变量,他是query尺寸的第1个数字,代表有多少条样本.
batch_size = query.size(0)
# 之后就进入多头处理环节
# 首先利用zip将输入QKV与三个线性层组到一起,然后使用for循环,将输入QKV分别传到线性层中,
# 做完线性变换后,开始为每个头分割输入,这里使用view方法对线性变换的结果进行维度重塑,多加了一个维度h,代表头数,
# 这样就意味着每个头可以获得一部分词特征组成的句子,其中的-1代表自适应维度,
# 计算机会根据这种变换自动计算这里的值.然后对第二维和第三维进行转置操作,
# 为了让代表句子长度维度和词向量维度能够相邻,这样注意力机制才能找到词义与句子位置的关系,
# 从attention函数中可以看到,利用的是原始输入的倒数第一和第二维.这样我们就得到了每个头的输入.
query, key, value = \
[model(x).view(batch_size, -1, self.head, self.d_k).transpose(1, 2)
for model, x in zip(self.linears, (query, key, value))]
#[batch size, head, 句子最大长度max_len, d_k]:[2, 8, 4, 64]
# print("query.shape:",query.shape) #torch.Size([2, 8, 4, 64])
# print("key.shape:",key.shape) #torch.Size([2, 8, 4, 64])
# print("value.shape:",value.shape) #torch.Size([2, 8, 4, 64])
# 得到每个头的输入后,接下来就是将他们传入到attention中,
# 这里直接调用我们之前实现的attention函数.同时也将mask和dropout传入其中.
x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout)
# print("x.shape:", x.shape) #torch.Size([2, 8, 4, 64]) 即 [batch size, head, 句子最大长度max_len, d_k]
# print("attn.shape:", self.attn.shape) #torch.Size([2, 8, 4, 4]) 即 [batch size, head, 句子最大长度max_len, 句子最大长度max_len]
# 通过多头注意力计算后,我们就得到了每个头计算结果组成的4维张量,我们需要将其转换为输入的形状以方便后续的计算,
# 因此这里开始进行第一步处理环节的逆操作,先对第二和第三维进行转置,然后使用contiguous方法,
# 这个方法的作用就是能够让转置后的张量应用view方法,否则将无法直接使用,
# 所以,下一步就是使用view重塑形状,变成和输入形状相同.
x = x.transpose(1, 2).contiguous().view(batch_size, -1, self.head * self.d_k)
# 最后使用线性层列表中的最后一个线性层对输入进行线性变换得到最终的多头注意力结构的输出.
return self.linears[-1](x)
#-----------------------------------------
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# #-----------------------------------------
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512])
#
# #-----------------------------------------
# #实例化参数:
# # 头数head
# head = 8
# # 词嵌入维度embedding_dim
# embedding_dim = 512
# # 置零比率dropout
# dropout = 0.2
# #输入参数:
# # 假设输入的Q,K,V仍然相等
# #自注意力机制:QKV相同,均为“经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的”词嵌入张量
# query = value = key = pe_result
# # 输入的掩码张量mask
# mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# #mask掩码张量的形状可以定义为[batch size, 句子最大长度max_len, 句子最大长度max_len],
# #因为mask需要和注意力得分张量scores进行匹配形状:scores.masked_fill(mask == 0, -1e9)
#
# #调用:
# mha = MultiHeadedAttention(head, embedding_dim, dropout)
# mha_result = mha(query, key, value, mask)
# print(mha_result)
#-----------------------------------------
"""
torch.transpose 和 tensor变量.view 的区别
1.torch.transpose
torch.transpose(tensor变量, 维度的索引值, 维度的索引值)
tensor变量.transpose(维度的索引值, 维度的索引值)
指定的两个维度之间相互互换,参数位置为维度的索引值之间的互换。
比如x.shape为[2, 3],那么torch.transpose(x, 0, 1)或者x.transpose(0, 1)的结果的shape即为[3, 2]。
2.tensor变量.view
tensor变量.view(维度值, 维度值)
指定的任意维度之间相互互换,参数位置为维度值之间的互换。
比如x.shape为[1, 2, 3, 4],那么x.view(1, 3, 2, 4)的结果的shape即为[1, 3, 2, 4]。
3.torch.transpose 和 tensor.view 本质不相同
1.x.shape为[1, 2, 3, 4],虽然torch.transpose(x, 1, 2)和x.view(1, 3, 2, 4)转换后的x的shape都为[1, 3, 2, 4],
但是两者转换后的数据布局不一致,两者之间torch.equal(transpose结果, view结果)为False。
2.比如x=[10, 11, 12, 13],在内存中每个元素值是顺序存储的,即使使用view进行维度转换后其元素内部的存储顺序也不会变的,
即view不会更改内存中的张量布局,其每个元素值仍然是顺序存储的。而transpose进行维度转换后其元素内部的存储顺序是会变的,
即transpose会更改内存中的张量布局。
3.二维矩阵进行transpose则是行列值之间互换,整行变成整列,整列变成整行。
二维矩阵进行view则不是行列值之间互换,而是先把二维矩阵压平为一维维度的元素值顺序存储结构,
然后再把一维的结构按需要转换的结构进行按顺序一个值一个值地从左到右从上到下地往里面存储,
变换结构前和变换结构后的元素值的内部存储顺序都不会变,都是按顺序存储。
4.例子:
>>> a = torch.randn(2, 3)
>>> b = a.transpose(1, 0) #参数位置为维度的索引值之间的互换
>>> c = a.view(3, 2) #参数位置为维度值之间的互换
>>> a
tensor([[-2.1413, -0.6464, -1.3298],
[-0.4464, 0.4075, 0.5765]])
>>> b
tensor([[-2.1413, -0.4464],
[-0.6464, 0.4075],
[-1.3298, 0.5765]])
>>> c
tensor([[-2.1413, -0.6464],
[-1.3298, -0.4464],
[ 0.4075, 0.5765]])
"""
"""
tensor.view演示:
>>> x = torch.randn(4, 4)
>>> x.size() #x.size()同x.shape
torch.Size([4, 4])
>>> y = x.view(16)
>>> y.size()
torch.Size([16])
>>> z = x.view(-1, 8) # -1对应的该维度是推断出来的,而不是指定出来的
>>> z.size()
torch.Size([2, 8])
>>> a = torch.randn(1, 2, 3, 4)
>>> a.size() #x.size()同x.shape
torch.Size([1, 2, 3, 4])
>>> b = a.transpose(1, 2) # 交换2维和3维
>>> b.size()
torch.Size([1, 3, 2, 4])
>>> c = a.view(1, 3, 2, 4) # 不更改内存中的张量布局
>>> c.size()
torch.Size([1, 3, 2, 4])
>>> torch.equal(b, c)
False
"""
"""
torch.transpose演示:
>>> x = torch.randn(2, 3)
>>> x.shape
torch.Size([2, 3])
>>> y = torch.transpose(x, 0, 1)
>>> y.shape
torch.Size([3, 2])
>>> x
tensor([[ 0.2268, 0.7229, -0.1330],
[-0.0229, -1.6108, -0.7225]])
>>> y
tensor([[ 0.2268, -0.0229],
[ 0.7229, -1.6108],
[-0.1330, -0.7225]])
"""
"""
1.前馈全连接层:
在Transformer中前馈全连接层就是具有两层线性层的全连接网络.
2.前馈全连接层的作用:
考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力.
3.ReLU函数公式: ReLU(x)=max(0, x)
"""
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
import torch.nn.functional as F
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
from day08.多头注意力机制_4 import MultiHeadedAttention
"""
PositionwiseFeedForward 前馈全连接层的结构
[batch size, 句子最大长度max_len, embedding_dim词向量维度]的多头注意力机制的输出
---> 第一个 nn.Linear线性层 ---> relu ---> dropout ---> 第二个 nn.Linear线性层
---> [batch size, 句子最大长度max_len, embedding_dim词向量维度]的前馈全连接层的输出
1.__init__(d_model, d_ff, dropout=0.1)
1.d_model:第一个线性层的输入维度,也是第二个线性层的输出维度。
便可以保证input输入通过前馈全连接层后,原始输入和最终输出的维度不变。
d_ff:第一个线性层的输出维度,也是第二个线性层的输入维度
dropout=0.1:置0丢弃率
dropout本身是对模型结构中的节点数进行随机抑制的比率,
又因为节点被抑制等效就是该节点的输出都是0,因此也可以把dropout看作是对输出矩阵的随机置0的比率。
2.创建两个nn.Linear线性层对象和一个Dropout层
w1 = nn.Linear(d_model, d_ff)
w2 = nn.Linear(d_ff, d_model)
dropout = nn.Dropout(dropout)
2.forward(x)
w2(self.dropout(F.relu(w1(x))))
首先经过第一个线性层,然后使用relu函数进行激活,之后再使用dropout进行随机置0,最后通过第二个线性层,返回最终结果。
输入参数x是多头注意力机制的输出,形状为 [batch size, 句子最大长度max_len, embedding_dim词向量维度]。
多头注意力机制的输出经过前馈全连接层后的形状不变仍然为[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
"""
# 通过类PositionwiseFeedForward来实现前馈全连接层
class PositionwiseFeedForward(nn.Module):
def __init__(self, d_model, d_ff, dropout=0.1):
"""初始化函数有三个输入参数分别是d_model, d_ff,和dropout=0.1,第一个是线性层的输入维度也是第二个线性层的输出维度,
因为我们希望输入通过前馈全连接层后输入和输出的维度不变. 第二个参数d_ff就是第二个线性层的输入维度和第一个线性层的输出维度.
最后一个是dropout置0比率."""
super(PositionwiseFeedForward, self).__init__()
# 首先按照我们预期使用nn实例化了两个线性层对象,self.w1和self.w2
# 它们的参数分别是d_model, d_ff和d_ff, d_model
self.w1 = nn.Linear(d_model, d_ff)
self.w2 = nn.Linear(d_ff, d_model)
# 然后使用nn的Dropout实例化了对象self.dropout
self.dropout = nn.Dropout(dropout)
def forward(self, x):
"""输入参数为x,代表来自上一层的输出"""
# 首先经过第一个线性层,然后使用Funtional中relu函数进行激活,
# 之后再使用dropout进行随机置0,最后通过第二个线性层w2,返回最终结果.
return self.w2(self.dropout(F.relu(self.w1(x))))
# #-----------------------------------------
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# #-----------------------------------------
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-----------------------------------------
# #实例化参数:
# # 头数head
# head = 8
# # 词嵌入维度embedding_dim
# embedding_dim = 512
# # 置零比率dropout
# dropout = 0.2
# #输入参数:
# # 假设输入的Q,K,V仍然相等
# #自注意力机制:QKV相同,均为“经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的”词嵌入张量
# query = value = key = pe_result
# # 输入的掩码张量mask
# mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# #mask掩码张量的形状可以定义为[batch size, 句子最大长度max_len, 句子最大长度max_len],
# #因为mask需要和注意力得分张量scores进行匹配形状:scores.masked_fill(mask == 0, -1e9)
#
# #调用:
# mha = MultiHeadedAttention(head, embedding_dim, dropout)
# mha_result = mha(query, key, value, mask)
# print("mha_result.shape",mha_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# #-----------------------------------------
#
# #实例化参数:
# d_model = 512
# # 线性变化的维度
# d_ff = 64
# dropout = 0.2
# #输入参数:
# # 输入参数x可以是多头注意力机制的输出
# x = mha_result
# #调用:
# ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# ff_result = ff(x)
# print("ff_result.shape",ff_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
"""
规范化层的作用:
它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,
这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,
使其特征数值在合理范围内.
"""
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
from day08.多头注意力机制_4 import MultiHeadedAttention
from day08.前馈全连接层_5 import PositionwiseFeedForward
"""
LayerNorm 规范化层:对前馈全连接层的输出的结果做规范化公式计算
1.__init__(features, eps=1e-6):
1.features:词嵌入维度/词向量维度
eps=1e-6:极小值,在规范化公式的分母中出现,用于防止分母为0,避免在除法中除以0报错。默认值1e-6。
2.a2 = nn.Parameter(torch.ones(词嵌入维度)) 相当于权重W,或者说缩放参数
b2 = nn.Parameter(torch.zeros(词嵌入维度)) 相当于偏置b
根据词嵌入维度初始化两个参数张量a2和b2,这两个张量就是规范化层的参数。
a2初始化为1张量,也就是里面的元素都是1。b2初始化为0张量,也就是里面的元素都是0。
由于直接对前馈全连接层的输出的结果做规范化公式计算,将改变结果的正常表征,因此就需要有参数作为调节因子,
使其即能满足规范化要求,又能不改变针对目标的表征。
最后使用nn.parameter封装,代表他们是模型的参数,可用于模型训练过程中自动进行调整。
2.forward(x)
规范化层的输入参数x为前馈全连接层的输出的结果。
1.mean = x.mean(-1, keepdim=True)
对输入变量x求其最后一个维度的均值,keepdim=True表示保持输出维度与输入维度一致。
2.std = x.std(-1, keepdim=True)
求最后一个维度的标准差,keepdim=True表示保持输出维度与输入维度一致。
3.a2 * (x - mean) / (std + eps) + b2
用x减去均值除以标准差获得规范化的结果,最后对规范化的结果进行点积乘以缩放参数a2(相当于权重W),最后加上偏置b2。
"""
# 通过LayerNorm实现规范化层的类
class LayerNorm(nn.Module):
def __init__(self, features, eps=1e-6):
"""初始化函数有两个参数, 一个是features, 表示词嵌入的维度,
另一个是eps它是一个足够小的数, 在规范化公式的分母中出现,
防止分母为0.默认是1e-6."""
super(LayerNorm, self).__init__()
# 根据features的形状初始化两个参数张量a2,和b2,第一个初始化为1张量,
# 也就是里面的元素都是1,第二个初始化为0张量,也就是里面的元素都是0,这两个张量就是规范化层的参数,
# 因为直接对上一层得到的结果做规范化公式计算,将改变结果的正常表征,因此就需要有参数作为调节因子,
# 使其即能满足规范化要求,又能不改变针对目标的表征.最后使用nn.parameter封装,代表他们是模型的参数。
self.a2 = nn.Parameter(torch.ones(features))
self.b2 = nn.Parameter(torch.zeros(features))
# 把eps传到类中
self.eps = eps
def forward(self, x):
"""输入参数x代表来自上一层的输出"""
# 在函数中,首先对输入变量x求其最后一个维度的均值,并保持输出维度与输入维度一致.
# 接着再求最后一个维度的标准差,然后就是根据规范化公式,用x减去均值除以标准差获得规范化的结果,
# 最后对结果乘以我们的缩放参数,即a2,*号代表同型点乘,即对应位置进行乘法操作,加上位移参数b2.返回即可.
mean = x.mean(-1, keepdim=True)
std = x.std(-1, keepdim=True)
return self.a2 * (x - mean) / (std + self.eps) + self.b2
#-----------------------------------------
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# #-----------------------------------------
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-----------------------------------------
# #实例化参数:
# # 头数head
# head = 8
# # 词嵌入维度embedding_dim
# embedding_dim = 512
# # 置零比率dropout
# dropout = 0.2
# #输入参数:
# # 假设输入的Q,K,V仍然相等
# #自注意力机制:QKV相同,均为“经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的”词嵌入张量
# query = value = key = pe_result
# # 输入的掩码张量mask
# mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# #mask掩码张量的形状可以定义为[batch size, 句子最大长度max_len, 句子最大长度max_len],
# #因为mask需要和注意力得分张量scores进行匹配形状:scores.masked_fill(mask == 0, -1e9)
#
# #调用:
# mha = MultiHeadedAttention(head, embedding_dim, dropout)
# mha_result = mha(query, key, value, mask)
# print("mha_result.shape",mha_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# #-----------------------------------------
#
# #实例化参数:
# d_model = 512
# # 线性变化的维度
# d_ff = 64
# dropout = 0.2
# #输入参数:
# # 输入参数x可以是多头注意力机制的输出
# x = mha_result
# #调用:
# ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# ff_result = ff(x)
# print("ff_result.shape",ff_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-----------------------------------------
#
# #实例化参数:
# features = d_model = 512
# eps = 1e-6
# #输入参数:
# # 输入x来自前馈全连接层的输出
# x = ff_result
# #调用:
# ln = LayerNorm(features, eps)
# ln_result = ln(x)
# print("ln_result.shape",ln_result.shape)
"""
子层连接结构:
输入到每个子层以及规范化层的过程中,还使用了残差链接(跳跃连接),
因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构),
在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构.
每个编码器层中有两个残差连接子层
1.第一个残差连接子层:input + 通过(input=>多头注意力计算层=>规范化层)得到的输出ouuput
2.第二个残差连接子层:input + 通过(input=>前馈全连接层=>规范化层)得到的输出ouuput
"""
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
import torch.nn.functional as F
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
from day08.多头注意力机制_4 import MultiHeadedAttention
from day08.规范化层_6 import LayerNorm
"""
SublayerConnection 残差连接子层结构的类
1.__init__(size, dropout=0.1)
1.size:一般是都是词嵌入维度/词向量维度
dropout=0.1:置0的丢弃率。
dropout本身是对模型结构中的节点数进行随机抑制的比率,
又因为节点被抑制等效就是该节点的输出都是0,因此也可以把dropout看作是对输出矩阵的随机置0的比率。
2.norm = LayerNorm(词嵌入维度/词向量维度)
创建LayerNorm规范化层类的实例对象
3.dropout = nn.Dropout(p=dropout)
创建Dropout层对象
2.forward(x, sublayer)
x:如果实现的是第一个残差连接子层的话,x则为位置编码器的输出。如果实现的是第二个残差连接子层的话,x则为多头注意力机制的输出。
sublayer:可以是forward函数对象或者类的实例对象。
如果实现的是第一个残差连接子层的话,为lambda创建的MultiHeadedAttention多头注意力计算类的forward函数对象。
之所以使用lambda创建forward函数对象是为了在调用forward函数时候实现实参传递的通用化,
因为MultiHeadedAttention类的forward函数需要传入多个实参,而PositionwiseFeedForward前馈全连接层类的forward函数只传入一个实参,
为了兼容多个类的不同实数数量的forward函数的调用,所以才使用lambda创建forward函数对象。
如果实现的是第二个残差连接子层的话,为PositionwiseFeedForward前馈全连接层类的实例对象。
x + Dropout(sublayer(LayerNorm(x)))
此处实现的是第一个残差连接子层:
x:位置编码器的输出,即[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
sublayer:forward函数对象,要使用函数对象sublayer(x)的写法才是真的调用forward函数。
1.每个编码器层中有两个残差连接子层
1.实现第一个残差连接子层:
input + 通过(input=>多头注意力计算层=>规范化层)得到的输出ouuput。
input为位置编码器的输出:[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
2.实现第二个残差连接子层:
input + 通过(input=>前馈全连接层=>规范化层)得到的输出ouuput
input为多头注意力机制的输出:[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
2.对位置编码器的输出x进行LayerNorm规范化,然后将规范化后的结果作为实参传给forward函数对象sublayer,
此时的函数对象sublayer(x)的写法代表真的调用forward函数传入规范化后的结果,
然后再对多头注意力计算层的输出结果进行Dropout操作,随机停止一些网络中神经元的作用,来防止过拟合。
最后还有一个add的残差连接操作,因为存在残差连接/跳跃连接,
所以是将输入x与Dropout后的结果相加作为最终的残差连接子层的输出。
3.sublayer(LayerNorm(x))
1.如果实现的是第一个残差连接子层的话,即先把位置编码器的输出x进行规范化后,
再输出给MultiHeadedAttention多头注意力类的forward函数对象。
2.如果实现的是第二个残差连接子层的话,即先把多头注意力机制的输出x进行规范化后,
再输出给PositionwiseFeedForward前馈全连接层类的forward函数。
3.虽然在结构图中LayerNorm规范化层类是在sublayer(多头注意力类/前馈全连接层类)的后面,
此处之所以把LayerNorm规范化层类放到sublayer(多头注意力类/前馈全连接层类)的前面,
并不会影响真实效果,第一个原因因为在残差连接中BN层就可以放置到任意多处,比如可以数据输入后就接BN层,
也可以残差连接输出结果前或后都能接BN层,或者BN层放到残差连接的过程中都行。
第二个原因是LayerNorm规范化层类放到sublayer(多头注意力类/前馈全连接层类)的前面或后面,
实际两者在Transformer整个结构图中的数据流向都不会影响,
因为每个子层(sublayer(多头注意力类/前馈全连接层类))之间的数据传递都是要经过LayerNorm规范化层的,
所以LayerNorm规范化层放在其前面或后面都不影响每个子层之间的数据传递,实际效果都一样。
3.实例对象 = lambda x: 实例对象(传入forward函数的实参)
lambda表达式传入了forward函数的实参,但实际只是写出了调用forward函数时要传入的为何种实参,
此时实际仍然没有真的调用forward函数,相当于把forward函数作为函数对象返回出去,
实际要到该sublayer函数对象传入实参值时才是真的调用forward函数,比如函数对象sublayer(x)的写法。
"""
# 使用SublayerConnection来实现子层连接结构的类
class SublayerConnection(nn.Module):
def __init__(self, size, dropout=0.1):
"""它输入参数有两个, size以及dropout, size一般是都是词嵌入维度的大小,
dropout本身是对模型结构中的节点数进行随机抑制的比率,
又因为节点被抑制等效就是该节点的输出都是0,因此也可以把dropout看作是对输出矩阵的随机置0的比率.
"""
super(SublayerConnection, self).__init__()
# 实例化了规范化对象self.norm
self.norm = LayerNorm(size)
# 又使用nn中预定义的droupout实例化一个self.dropout对象.
self.dropout = nn.Dropout(p=dropout)
def forward(self, x, sublayer):
"""前向逻辑函数中, 接收上一个层或者子层的输入作为第一个参数,
将该子层连接中的子层函数作为第二个参数"""
# 我们首先对输出进行规范化,然后将结果传给子层处理,之后再对子层进行dropout操作,
# 随机停止一些网络中神经元的作用,来防止过拟合. 最后还有一个add操作,
# 因为存在跳跃连接,所以是将输入x与dropout后的子层输出结果相加作为最终的子层连接输出.
return x + self.dropout(sublayer(self.norm(x)))
# #-----------------------------------------
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# #-----------------------------------------
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-----------------------------------------
#
# #实例化参数
# size = 512
# dropout = 0.2
# head = 8
# d_model = 512
# #输入参数:
# # 令x为位置编码器的输出
# x = pe_result #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# mask = Variable(torch.zeros(8, 4, 4))
"""
每个编码器层中有两个子层,此处实现的是第一个残差连接子层:
input + 通过(input=>多头注意力计算层=>规范化层)得到的输出ouuput。
input为位置编码器的输出:[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
"""
# # 假设子层中装的是多头注意力层, 实例化这个类
# self_attn = MultiHeadedAttention(head, d_model)
"""
sublayer = lambda x: self_attn(x, x, x, mask)
1.self_attn(x, x, x, mask)表示使用的是自注意力机制,此处Q/K/V相同,
均为“经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的”词嵌入张量。
2.mask掩码张量的形状定义为[batch size, 句子最大长度max_len, 句子最大长度max_len]
3.实例对象 = lambda x: 实例对象(传入forward函数的实参)
lambda表达式传入了forward函数的实参,但实际只是写出了调用forward函数时要传入的为何种实参,
此时实际仍然没有真的调用forward函数,相当于把forward函数作为函数对象返回出去,
实际要到该sublayer函数对象传入实参值时才是真的调用forward函数,比如函数对象sublayer(x)的写法。
"""
# # 使用lambda获得一个函数类型的子层
# sublayer = lambda x: self_attn(x, x, x, mask)
# print("sublayer",sublayer) #<function <lambda> at 0x0000020F8467C678>
# #调用:
# sc = SublayerConnection(size, dropout)
# sc_result = sc(x, sublayer)
# # print("sc_result:", sc_result)
# print("sc_result.shape:", sc_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
"""
EncoderLayer编码器层的作用:
EncoderLayer编码器层作为Encoder编码器的组成单元, 每个EncoderLayer编码器层完成一次对输入的特征提取过程, 即编码过程.
"""
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
from day08.多头注意力机制_4 import MultiHeadedAttention, clones
from day08.子层连接结构_7 import SublayerConnection
from day08.前馈全连接层_5 import PositionwiseFeedForward
"""
EncoderLayer编码器层
1.__init__(size, self_attn, feed_forward, dropout)
1.size:词嵌入维度/词向量维度
self_attn:MultiHeadedAttention多头注意力的实例对象, 并且是自注意力机制
feed_froward:PositionwiseFeedForward前馈全连接层的实例对象
dropout:dropout层的置零比率
2.sublayer = clones(SublayerConnection(size, dropout), 2)
EncoderLayer编码器层中有2个SublayerConnection残差连接子层结构,所以使用clones函数中的copy.deepcopy对其进行深拷贝。
深拷贝2个SublayerConnection残差连接子层类,第一个残差连接子层其中实现MultiHeadedAttention多头注意力计算层,
第二个残差连接子层其中实现PositionwiseFeedForward前馈全连接层。
最终深拷贝出来的2个残差连接子层都存到nn.ModuleList类型的列表变量sublayer中。
3.每个编码器层中有两个残差连接子层
1.实现第一个残差连接子层:
input + 通过(input=>多头注意力计算层=>规范化层)得到的输出ouuput。
input为位置编码器的输出:[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
2.实现第二个残差连接子层:
input + 通过(input=>前馈全连接层=>规范化层)得到的输出ouuput
input为多头注意力机制的输出:[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
2.forward(x, mask)
x:位置编码器的输出,即[batch size, 句子最大长度max_len, embedding_dim词向量维度]
mask:掩码张量,即[batch size, 句子最大长度max_len, 句子最大长度max_len]
1.x = sublayer[0](x, lambda x: self_attn(x, x, x, mask))
sublayer[0]:获取第一个残差连接子层。
lambda x: self_attn(x, x, x, mask):
实现的是第一个残差连接子层的话,为lambda创建的MultiHeadedAttention多头注意力计算类的forward函数对象。
之所以使用lambda创建forward函数对象是为了在调用forward函数时候实现实参传递的通用化,
因为MultiHeadedAttention类的forward函数需要传入多个实参,而PositionwiseFeedForward类的forward函数只传入一个实参,
为了兼容多个类的不同实数数量的forward函数的调用,所以才使用lambda创建forward函数对象。
输入x:位置编码器的输出,即[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
输出x:多头注意力机制的输出,即[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
2.sublayer[1](x, feed_forward)
sublayer[1]:获取第二个残差连接子层。
feed_forward:实现的是第二个残差连接子层的话,为PositionwiseFeedForward前馈全连接层类的实例对象。
输入x:多头注意力机制的输出,即[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
输出:前馈全连接层的输出,即[batch size, 句子最大长度max_len, embedding_dim词向量维度]。
"""
# 使用EncoderLayer类实现编码器层
class EncoderLayer(nn.Module):
def __init__(self, size, self_attn, feed_forward, dropout):
"""它的初始化函数参数有四个,分别是size,其实就是我们词嵌入维度的大小,它也将作为我们编码器层的大小,
第二个self_attn,之后我们将传入多头自注意力子层实例化对象, 并且是自注意力机制,
第三个是feed_froward, 之后我们将传入前馈全连接层实例化对象, 最后一个是置0比率dropout."""
super(EncoderLayer, self).__init__()
# 首先将self_attn和feed_forward传入其中.
self.self_attn = self_attn
self.feed_forward = feed_forward
# 如图所示, 编码器层中有两个子层连接结构, 所以使用clones函数进行克隆
self.sublayer = clones(SublayerConnection(size, dropout), 2)
# 把size传入其中
self.size = size
def forward(self, x, mask):
"""forward函数中有两个输入参数,x和mask,分别代表上一层的输出,和掩码张量mask."""
# 里面就是按照结构图左侧的流程. 首先通过第一个子层连接结构,其中包含多头自注意力子层,
# 然后通过第二个子层连接结构,其中包含前馈全连接子层. 最后返回结果.
x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
return self.sublayer[1](x, self.feed_forward)
# #-----------------------------------------
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# #-----------------------------------------
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-----------------------------------------
#
# #实例化参数:
# size = 512
# head = 8
# d_model = 512
# d_ff = 64
# # 令x为位置编码器的输出
# x = pe_result #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# dropout = 0.2
# self_attn = MultiHeadedAttention(head, d_model) #多头注意力机制
# ff = PositionwiseFeedForward(d_model, d_ff, dropout) # 前馈全连接层的结构
# mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# #调用:
# el = EncoderLayer(size, self_attn, ff, dropout)
# el_result = el(x, mask)
# # print(el_result)
# print("el_result.shape:",el_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
"""
编码器的作用:
编码器用于对输入进行指定的特征提取过程, 也称为编码, 由N个编码器层堆叠而成.
"""
# 用于深度拷贝的copy工具包
import copy
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
from day08.多头注意力机制_4 import MultiHeadedAttention, clones
from day08.前馈全连接层_5 import PositionwiseFeedForward
from day08.编码器层_8 import EncoderLayer
from day08.规范化层_6 import LayerNorm
"""
Encoder编码器
1.__init__(layer, N):
1.layer:EncoderLayer编码器层类实例对象
N:EncoderLayer编码器层的个数
2.layers = clones(layer, N)
使用clones函数中的copy.deepcopy(模型层类实例对象)深拷贝N个EncoderLayer编码器层类实例对象放到nn.ModuleList网络层列表对象layers中
3.norm = LayerNorm(layer.size)
初始化一个规范化层, 它将用在编码器的最后面
2.forward(x, mask):
for layer in layers:
x = layer(x, mask)
return norm(x)
1.输入x:经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
mask掩码张量:[batch size, 句子最大长度max_len, 句子最大长度max_len]
2.nn.ModuleList网络层列表对象layers中封装有通过深拷贝方式创建出来的N个EncoderLayer编码器层类实例对象。
第一个EncoderLayer编码器层传入的是经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量,
后面每个EncoderLayer编码器层之间把上一层编码器的输出作为下一层编码器的输入,
最后一层输出来的数据再经过LayerNorm规范化层进行规范化再作为最后输出。
"""
# 使用Encoder类来实现编码器
class Encoder(nn.Module):
def __init__(self, layer, N):
"""初始化函数的两个参数分别代表编码器层和编码器层的个数"""
super(Encoder, self).__init__()
# 首先使用clones函数克隆N个编码器层放在self.layers中
self.layers = clones(layer, N)
# 再初始化一个规范化层, 它将用在编码器的最后面.
self.norm = LayerNorm(layer.size)
def forward(self, x, mask):
"""forward函数的输入和编码器层相同, x代表上一层的输出, mask代表掩码张量"""
# 首先就是对我们克隆的编码器层进行循环,每次都会得到一个新的x,
# 这个循环的过程,就相当于输出的x经过了N个编码器层的处理.
# 最后再通过规范化层的对象self.norm进行处理,最后返回结果.
for layer in self.layers:
x = layer(x, mask)
return self.norm(x)
# #-----------------------------------------
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# #-----------------------------------------
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-----------------------------------------
#
# # #实例化参数:
# # size = 512
# # head = 8
# # d_model = 512
# # d_ff = 64
# # 令x为位置编码器的输出,经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# x = pe_result #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# # dropout = 0.2
# # self_attn = MultiHeadedAttention(head, d_model) #多头注意力机制
# # ff = PositionwiseFeedForward(d_model, d_ff, dropout) # 前馈全连接层的结构
# # mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # #调用:
# # el = EncoderLayer(size, self_attn, ff, dropout)
# # el_result = el(x, mask)
# # # print(el_result)
# # print("el_result.shape:",el_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-----------------------------------------
#
# #实例化参数:
# # 第一个实例化参数layer, 它是一个编码器层的实例化对象, 因此需要传入编码器层的参数
# # 又因为编码器层中的子层是不共享的, 因此需要使用深度拷贝各个对象.
# size = 512
# head = 8
# d_model = 512
# d_ff = 64
# c = copy.deepcopy # 复制对象,两者作用相同
# attn = MultiHeadedAttention(head, d_model)#多头注意力机制
# dropout = 0.2
# ff = PositionwiseFeedForward(d_model, d_ff, dropout)# 前馈全连接层的结构
#
# """
# 1.深拷贝一份模型类实例对象,创建出一个全新的结构相同但内存上互相独立的模型类实例对象。
# copy.deepcopy(nn.Linear等已经定好的模型类)
# copy.deepcopy(继承Module类的自定义类)
#
# 2.EncoderLayer(size, c(attn), c(ff), dropout)
# size:词嵌入维度/词向量维度
# dropout:dropout层的置零比率
# copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# copy.deepcopy(继承Module类的PositionwiseFeedForward前馈全连接层的实例对象):创建出一个全新的结构相同但内存上互相独立的前馈全连接层类实例对象。
# 之所以传入EncoderLayer类编码器层类进行初始化的是通过深拷贝创建出的全新的结构相同但内存上互相独立的模型类实例对象,
# 是因为Encoder编码器中需要构建多个EncoderLayer编码器层,
# 而每个EncoderLayer编码器层里面都拥有结构相同的MultiHeadedAttention多头注意力计算层和PositionwiseFeedForward前馈全连接层,
# 因此每个EncoderLayer编码器层里面都需要使用结构相同但内存上互相独立的多头注意力计算层类和前馈全连接层类的实例对象,
# 所以每次执行一次copy.deepcopy(模型类实例对象)就会创建一个全新的结构相同但内存上互相独立的模型类实例对象。
# """
# layer = EncoderLayer(size, c(attn), c(ff), dropout)
#
# # 编码器中编码器层的个数N
# N = 8
# mask = Variable(torch.zeros(8, 4, 4))#[batch size, 句子最大长度max_len, 句子最大长度max_len]
# #调用:
# en = Encoder(layer, N)
# en_result = en(x, mask)
# # print(en_result)
# print("en_result.shape",en_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
"""
解码器部分:
1.由N个解码器层堆叠而成
2.每个解码器层由三个子层连接结构组成
第一个子层连接结构 包括一个多头自注意力子层和规范化层以及一个残差连接
第二个子层连接结构 包括一个多头注意力子层和规范化层以及一个残差连接
第三个子层连接结构 包括一个前馈全连接子层和规范化层以及一个残差连接
3.三个子层连接结构 之间的区别
1.第一个子层连接结构:
使用的是 多头自注意力,Q(query)=K(key)=V(value),
三者均为经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
2.第二个子层连接结构:
使用的是 多头注意力(非多头自注意力),K(key)=V(value),但 Q(query)!=K(key) 并且 Q(query)!=V(value)。
K(key)和V(value)均为Encoder编码器的最终输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
Q(query)为经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量。
3.第三个子层连接结构:使用的是 前馈全连接子层
说明
解码器层中的各个部分,如,多头注意力机制,规范化层,前馈全连接网络,
子层连接结构都与编码器中的实现相同. 因此这里可以直接拿来构建解码器层.
解码器层的作用:
作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程.
"""
# 用于深度拷贝的copy工具包
import copy
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
from day08.多头注意力机制_4 import MultiHeadedAttention, clones
from day08.前馈全连接层_5 import PositionwiseFeedForward
from day08.编码器层_8 import EncoderLayer
from day08.子层连接结构_7 import SublayerConnection
from day08.编码器_9 import Encoder
"""
DecoderLayer 解码器层的类
1.__init__(size, self_attn, src_attn, feed_forward, dropout):
1.size:词嵌入维度/词向量维度, 同时也代表解码器层的尺寸
self_attn:MultiHeadedAttention多头注意力机制类的实例对象,但该实例对象此处只用作计算多头自注意力,也即为自注意力机制,
这个自注意力机制此处要求Q=K=V。
src_attn:MultiHeadedAttention多头注意力机制类的实例对象,但该实例对象此处只用作计算多头注意力(非多头自注意力),
也即这个注意力机制此处要求K=V,但Q!=K并且Q!=V。
feed_forward:PositionwiseFeedForward前馈全连接层类的实例对象
dropout:置0丢弃比率
2.sublayer = clones(SublayerConnection(size, dropout), 3)
DecoderLayer 解码器层中有2个SublayerConnection残差连接子层结构,所以使用clones函数中的copy.deepcopy对其进行深拷贝。
深拷贝3个SublayerConnection残差连接子层类,第一个残差连接子层其中实现MultiHeadedAttention多头自注意力计算层,
自注意力机制要求Q=K=V;第二个残差连接子层其中实现MultiHeadedAttention多头注意力计算层(非多头自注意力),
要求K=V,但Q!=K并且Q!=V;第三个残差连接子层其中实现PositionwiseFeedForward前馈全连接层。
最终深拷贝出来的3个残差连接子层都存到nn.ModuleList类型的列表变量sublayer中。
2.forward(x, memory, source_mask, target_mask)
1.x:经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
mermory:编码器层的语义存储变量,即Encoder编码器的最终输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
source_mask:源数据的掩码张量,[batch size, 句子最大长度max_len, 句子最大长度max_len]
target_mask:目标数据的掩码张量,[batch size, 句子最大长度max_len, 句子最大长度max_len]
2.forward函数最终输出了由Encoder编码器最终输出的语义存储变量和目标数据一同作用的特征提取结果。
3.x = sublayer[0](x, lambda x: self_attn(x, x, x, target_mask))
1.sublayer[0]:
第一个残差连接子层其中实现MultiHeadedAttention多头自注意力计算层,自注意力机制要求Q=K=V均为输入x。
Q/K/V均为“经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的”词嵌入张量。
2.target_mask目标数据的掩码张量:
形状为[batch size, 句子最大长度max_len, 句子最大长度max_len]。
目标数据掩码张量用于对目标数据进行遮掩,因为此时模型可能还没有生成任何目标数据。
比如在解码器准备生成第一个字符或词汇时,我们其实已经传入了第一个字符'<bos>'以便计算损失,
但是我们不希望在生成第一个字符时模型能利用这个信息,因此我们会将目标数据的第一个字符遮掩。
同样生成第二个字符或词汇时,模型只能使用目标数据的第一个字符或词汇信息,
不允许模型使用第二个字符以及之后的信息。
目标数据的掩码张量只能看到当前单词前面的单词,但看不到当前单词和后面的单词。
比如预测第三个单词,只能第一第二个单词,看不到当前第三个单词和后面的单词。
比如目标语句[我,爱,北京,天安门],那么解码器预测"我"时,解码器输入的第一个单词是'<bos>'以便计算损失,
那么只看到第一个单词'<bos>',看不到"我"。预测"爱"时,解码器输入的第二个单词是'我',
那么只看到['<bos>','我'],看不到"爱"。
3.实例对象 = lambda x: 实例对象(传入forward函数的实参)
lambda表达式传入了forward函数的实参,但实际只是写出了调用forward函数时要传入的为何种实参,
此时实际仍然没有真的调用forward函数,相当于把forward函数作为函数对象返回出去,
实际要到该sublayer函数对象传入实参值时才是真的调用forward函数,比如函数对象sublayer(x)的写法。
4.x = sublayer[1](x, lambda x: src_attn(x, m, m, source_mask))
1.sublayer[1]:第二个残差连接子层其中实现MultiHeadedAttention多头注意力计算层(非多头自注意力),要求K=V,但Q!=K并且Q!=V;
2.x:x为Q,Q!=K并且Q!=V,经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
3.m:m为K和为V,K=V,编码器层的语义存储变量,即Encoder编码器的最终输出,
即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
4.source_mask:进行源数据遮掩的原因并非是抑制信息泄漏,而是遮蔽掉对结果没有意义的字符而产生的注意力值,
把注意力集中在源数据的某一部分,不让源数据的注意力被分散,以此提升模型效果和训练速度。
5.实例对象 = lambda x: 实例对象(传入forward函数的实参)
lambda表达式传入了forward函数的实参,但实际只是写出了调用forward函数时要传入的为何种实参,
此时实际仍然没有真的调用forward函数,相当于把forward函数作为函数对象返回出去,
实际要到该sublayer函数对象传入实参值时才是真的调用forward函数,比如函数对象sublayer(x)的写法。
5.sublayer[2](x, feed_forward)
sublayer[2]:第三个残差连接子层其中实现PositionwiseFeedForward前馈全连接层。
"""
# 使用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)
# #-------------------------------------------------------------------
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# #-------------------------------------------------------------------
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# # #实例化参数:
# # size = 512
# # head = 8
# # d_model = 512
# # d_ff = 64
# # 令x为位置编码器的输出,经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# x = pe_result #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# # dropout = 0.2
# # self_attn = MultiHeadedAttention(head, d_model) #多头注意力机制
# # ff = PositionwiseFeedForward(d_model, d_ff, dropout) # 前馈全连接层的结构
# # mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # #调用:
# # el = EncoderLayer(size, self_attn, ff, dropout)
# # el_result = el(x, mask)
# # # print(el_result)
# # print("el_result.shape:",el_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 第一个实例化参数layer, 它是一个编码器层的实例化对象, 因此需要传入编码器层的参数
# # 又因为编码器层中的子层是不共享的, 因此需要使用深度拷贝各个对象.
# size = 512
# head = 8
# d_model = 512
# d_ff = 64
# c = copy.deepcopy # 复制对象,两者作用相同
# attn = MultiHeadedAttention(head, d_model)#多头注意力机制
# dropout = 0.2
# ff = PositionwiseFeedForward(d_model, d_ff, dropout)# 前馈全连接层的结构
#
# """
# 1.深拷贝一份模型类实例对象,创建出一个全新的结构相同但内存上互相独立的模型类实例对象。
# copy.deepcopy(nn.Linear等已经定好的模型类)
# copy.deepcopy(继承Module类的自定义类)
#
# 2.EncoderLayer(size, c(attn), c(ff), dropout)
# size:词嵌入维度/词向量维度
# dropout:dropout层的置零比率
# copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# copy.deepcopy(继承Module类的PositionwiseFeedForward前馈全连接层的实例对象):创建出一个全新的结构相同但内存上互相独立的前馈全连接层类实例对象。
# 之所以传入EncoderLayer类编码器层类进行初始化的是通过深拷贝创建出的全新的结构相同但内存上互相独立的模型类实例对象,
# 是因为Encoder编码器中需要构建多个EncoderLayer编码器层,
# 而每个EncoderLayer编码器层里面都拥有结构相同的MultiHeadedAttention多头注意力计算层和PositionwiseFeedForward前馈全连接层,
# 因此每个EncoderLayer编码器层里面都需要使用结构相同但内存上互相独立的多头注意力计算层类和前馈全连接层类的实例对象,
# 所以每次执行一次copy.deepcopy(模型类实例对象)就会创建一个全新的结构相同但内存上互相独立的模型类实例对象。
# """
# layer = EncoderLayer(size, c(attn), c(ff), dropout)
#
# # 编码器中编码器层的个数N
# N = 8
# mask = Variable(torch.zeros(8, 4, 4))#[batch size, 句子最大长度max_len, 句子最大长度max_len]
# #调用:
# en = Encoder(layer, N)
# en_result = en(x, mask)
# # print(en_result)
# print("en_result.shape",en_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 类的实例化参数与解码器层类似, 相比多出了src_attn, 但是和self_attn是同一个类.
# head = 8
# size = 512
# d_model = 512
# d_ff = 64
# dropout = 0.2
# self_attn = src_attn = MultiHeadedAttention(head, d_model, dropout)
# # 前馈全连接层也和之前相同
# ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# """
# pe_result:经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# en_result:Encoder编码器的最终输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# mask:掩码张量,[batch size, 句子最大长度max_len, 句子最大长度max_len]
# source_mask:源数据的掩码张量
# target_mask:目标数据的掩码张量
# """
# #输入参数:
# # x是来自目标数据的词嵌入表示, 但形式和源数据的词嵌入表示相同, 这里使用per充当.
# x = pe_result
# # memory是来自编码器的输出
# memory = en_result
#
# """
# Variable(torch.zeros(...), requires_grad=True/False)
# 1.torch.autograd.Variable是autograd的核心类,Variable整合了Tensor,并整合了反向传播的相关实现。
# 2.Variable包含3个属性:
# data:保存了Tensor,是本体真实数据。
# grad:保存了上述data属性值的梯度值,与data属性的形状一致。
# grad_fn:指向Function对象,用于反向传播时的梯度计算之用。
# 3.requires_grad
# 1.requires_grad=True:在反向传播时会对所封装的Tensor数据进行梯度求解。
# 2.requires_grad=False:在反向传播时不会对所封装的Tensor数据进行梯度求解。
# 3.比如输入的样本数据和目标标签数据使用Variable进行封装的同时而又不需要求梯度时,那么便可以设置requires_grad=False,
# 那么它是不需要进行梯度求解的。
# """
#
# # 实际中source_mask和target_mask并不相同, 这里为了方便计算使他们都为mask
# mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# source_mask = target_mask = mask
# #调用:
# dl = DecoderLayer(size, self_attn, src_attn, ff, dropout)
# dl_result = dl(x, memory, source_mask, target_mask)
# # print(dl_result)
# print("dl_result.shape",dl_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
"""
解码器的作用:
根据编码器的结果以及上一次预测的结果, 对下一次可能出现的"值"进行特征表示.
"""
# 用于深度拷贝的copy工具包
import copy
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
from day08.多头注意力机制_4 import MultiHeadedAttention, clones
from day08.前馈全连接层_5 import PositionwiseFeedForward
from day08.编码器层_8 import EncoderLayer
from day08.规范化层_6 import LayerNorm
from day08.编码器_9 import Encoder
from day08.解码器层_10 import DecoderLayer
"""
Decoder 解码器
1.__init__(layer, N):
1.layer:DecoderLayer解码器层的实例对象
2.N:Decoder解码器中的DecoderLayer解码器层的个数N
3.layers = clones(layer, N)
使用clones函数中的copy.deepcopy(模型层类实例对象)深拷贝N个DecoderLayer解码器层类实例对象放到nn.ModuleList网络层列表对象layers中
4.norm = LayerNorm(layer.size)
实例化了一个规范化层,它将用在Decoder解码器中的最后面
2.forward(x, memory, source_mask, target_mask):
1.x:目标数据经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量,即目标数据的词嵌入表示
memory:Encoder编码器的最终输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
source_mask:源数据的掩码张量,即[batch size, 句子最大长度max_len, 句子最大长度max_len]
target_mask:目标数据的掩码张量,即[batch size, 句子最大长度max_len, 句子最大长度max_len]
2.for layer in layers:
x = layer(x, memory, source_mask, target_mask)
return norm(x)
nn.ModuleList网络层列表对象layers中封装有通过深拷贝方式创建出来的N个DecoderLayer解码器层类实例对象。
每个DecoderLayer解码器层之间把上一层解码器层的输出作为下一层解码器层的输入,
最后一层解码器层输出来的数据再经过LayerNorm规范化层进行规范化再作为最后输出。
"""
# 使用类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)
# #-------------------------------------------------------------------
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# #-------------------------------------------------------------------
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# # #实例化参数:
# # size = 512
# # head = 8
# # d_model = 512
# # d_ff = 64
# # 令x为位置编码器的输出,经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# x = pe_result #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# # dropout = 0.2
# # self_attn = MultiHeadedAttention(head, d_model) #多头注意力机制
# # ff = PositionwiseFeedForward(d_model, d_ff, dropout) # 前馈全连接层的结构
# # mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # #调用:
# # el = EncoderLayer(size, self_attn, ff, dropout)
# # el_result = el(x, mask)
# # # print(el_result)
# # print("el_result.shape:",el_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 第一个实例化参数layer, 它是一个编码器层的实例化对象, 因此需要传入编码器层的参数
# # 又因为编码器层中的子层是不共享的, 因此需要使用深度拷贝各个对象.
# size = 512
# head = 8
# d_model = 512
# d_ff = 64
# c = copy.deepcopy # 复制对象,两者作用相同
# attn = MultiHeadedAttention(head, d_model)#多头注意力机制
# dropout = 0.2
# ff = PositionwiseFeedForward(d_model, d_ff, dropout)# 前馈全连接层的结构
#
# """
# 1.深拷贝一份模型类实例对象,创建出一个全新的结构相同但内存上互相独立的模型类实例对象。
# copy.deepcopy(nn.Linear等已经定好的模型类)
# copy.deepcopy(继承Module类的自定义类)
#
# 2.EncoderLayer(size, c(attn), c(ff), dropout)
# 1.size:词嵌入维度/词向量维度
# 2.dropout:dropout层的置零比率
# 3.copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# 4.copy.deepcopy(继承Module类的PositionwiseFeedForward前馈全连接层的实例对象):创建出一个全新的结构相同但内存上互相独立的前馈全连接层类实例对象。
# 之所以传入EncoderLayer类编码器层类进行初始化的是通过深拷贝创建出的全新的结构相同但内存上互相独立的模型类实例对象,
# 是因为Encoder编码器中需要构建多个EncoderLayer编码器层,
# 而每个EncoderLayer编码器层里面都拥有结构相同的MultiHeadedAttention多头注意力计算层和PositionwiseFeedForward前馈全连接层,
# 因此每个EncoderLayer编码器层里面都需要使用结构相同但内存上互相独立的多头注意力计算层类和前馈全连接层类的实例对象,
# 所以每次执行一次copy.deepcopy(模型类实例对象)就会创建一个全新的结构相同但内存上互相独立的模型类实例对象。
# """
# layer = EncoderLayer(size, c(attn), c(ff), dropout)
#
# # 编码器中编码器层的个数N
# N = 8
# mask = Variable(torch.zeros(8, 4, 4))#[batch size, 句子最大长度max_len, 句子最大长度max_len]
# #调用:
# en = Encoder(layer, N)
# en_result = en(x, mask)
# # print(en_result)
# print("en_result.shape",en_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# # #实例化参数:
# # # 类的实例化参数与解码器层类似, 相比多出了src_attn, 但是和self_attn是同一个类.
# # head = 8
# # size = 512
# # d_model = 512
# # d_ff = 64
# # dropout = 0.2
# # self_attn = src_attn = MultiHeadedAttention(head, d_model, dropout)
# # # 前馈全连接层也和之前相同
# # ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# # """
# # pe_result:经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# # en_result:Encoder编码器的最终输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# # mask:掩码张量,[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # source_mask:源数据的掩码张量
# # target_mask:目标数据的掩码张量
# # """
# # #输入参数:
# # # x是来自目标数据的词嵌入表示, 但形式和源数据的词嵌入表示相同, 这里使用per充当.
# # x = pe_result
# # # memory是来自编码器的输出
# # memory = en_result
# #
# # """
# # Variable(torch.zeros(...), requires_grad=True/False)
# # 1.torch.autograd.Variable是autograd的核心类,Variable整合了Tensor,并整合了反向传播的相关实现。
# # 2.Variable包含3个属性:
# # data:保存了Tensor,是本体真实数据。
# # grad:保存了上述data属性值的梯度值,与data属性的形状一致。
# # grad_fn:指向Function对象,用于反向传播时的梯度计算之用。
# # 3.requires_grad
# # 1.requires_grad=True:在反向传播时会对所封装的Tensor数据进行梯度求解。
# # 2.requires_grad=False:在反向传播时不会对所封装的Tensor数据进行梯度求解。
# # 3.比如输入的样本数据和目标标签数据使用Variable进行封装的同时而又不需要求梯度时,那么便可以设置requires_grad=False,
# # 那么它是不需要进行梯度求解的。
# # """
# #
# # # 实际中source_mask和target_mask并不相同, 这里为了方便计算使他们都为mask
# # mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # source_mask = target_mask = mask
# # #调用:
# # dl = DecoderLayer(size, self_attn, src_attn, ff, dropout)
# # dl_result = dl(x, memory, source_mask, target_mask)
# # # print(dl_result)
# # print("dl_result.shape",dl_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 分别是解码器层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)
# """
# 1.深拷贝一份模型类实例对象,创建出一个全新的结构相同但内存上互相独立的模型类实例对象。
# copy.deepcopy(nn.Linear等已经定好的模型类)
# copy.deepcopy(继承Module类的自定义类)
#
# 2.DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
# 1.d_model:词嵌入维度/词向量维度
# 2.dropout:dropout层的置零比率
# 3.copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):
# 创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# 但该实例对象传入到DecoderLayer解码器层中便作为第一个残差连接子层中的多头自注意力,也即为自注意力机制,这个自注意力机制要求Q=K=V。
# Q/K/V均为“经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的”词嵌入张量。
# 4.copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):
# 创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# 但该实例对象传入到DecoderLayer解码器层中便作为第二个残差连接子层中的多头注意力(非多头自注意力),要求K=V,但Q!=K并且Q!=V;
# Q:Q!=K并且Q!=V,Q为经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# K=V:两者均为编码器层的语义存储变量,即Encoder编码器的最终输出,
# 5.copy.deepcopy(继承Module类的PositionwiseFeedForward前馈全连接层的实例对象):
# 创建出一个全新的结构相同但内存上互相独立的前馈全连接层类实例对象。
# 之所以传入EncoderLayer类编码器层类进行初始化的是通过深拷贝创建出的全新的结构相同但内存上互相独立的模型类实例对象,
# 是因为Encoder编码器中需要构建多个EncoderLayer编码器层,
# 而每个EncoderLayer编码器层里面都拥有结构相同的MultiHeadedAttention多头注意力计算层和PositionwiseFeedForward前馈全连接层,
# 因此每个EncoderLayer编码器层里面都需要使用结构相同但内存上互相独立的多头注意力计算层类和前馈全连接层类的实例对象,
# 所以每次执行一次copy.deepcopy(模型类实例对象)就会创建一个全新的结构相同但内存上互相独立的模型类实例对象。
# """
# layer = DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
#
# """
# pe_result:目标数据经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量,即目标数据的词嵌入表示
# en_result:Encoder编码器的最终输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# mask:掩码张量,[batch size, 句子最大长度max_len, 句子最大长度max_len]
# source_mask:源数据的掩码张量
# target_mask:目标数据的掩码张量
# """
# 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",de_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
"""
输出部分包含:
线性层:通过对上一步的线性变化得到指定维度的输出, 也就是转换维度的作用.
softmax层:使最后一维的向量中的数字缩放到0-1的概率值域内, 并满足他们的和为1
"""
# 用于深度拷贝的copy工具包
import copy
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
# nn.functional工具包装载了网络层中那些只进行计算, 而没有参数的层
import torch.nn.functional as F
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
from day08.多头注意力机制_4 import MultiHeadedAttention, clones
from day08.前馈全连接层_5 import PositionwiseFeedForward
from day08.编码器层_8 import EncoderLayer
from day08.编码器_9 import Encoder
from day08.解码器层_10 import DecoderLayer
from day08.解码器_11 import Decoder
"""
Generator 类别生成器类
1.__init__(d_model, vocab_size):
1.d_model:词嵌入维度/词向量维度
vocab_size:目标语料的词表大小
2.project = nn.Linear(d_model, vocab_size)
nn.Linear定义线性层创建实例化对象,输入维度词嵌入维度/词向量维度,输出维度目标语料的词表大小
2.forward(x):
F.log_softmax(project(x), dim=-1)
前向逻辑函数中输入x是Decoder解码器的输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]。
首先使用Linear线性层对Decoder解码器的输出中的最后一个维度(词嵌入维度/词向量维度)转换为目标语料的词表大小。
然后使用log_softmax对其进行的softmax处理。在这里之所以使用log_softmax是因为和pytorch版本的损失函数实现有关,
在其他版本中将修复。log_softmax就是对softmax的结果又取了对数,因为对数函数是单调递增函数,
因此对于取最大的概率值没有影响,最后返回结果即可。
"""
# 将线性层和softmax计算层一起实现, 因为二者的共同目标是生成最后的结构
# 因此把类的名字叫做Generator,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)
# #-------------------------------------------------------------------
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# #-------------------------------------------------------------------
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# # #实例化参数:
# # size = 512
# # head = 8
# # d_model = 512
# # d_ff = 64
# # 令x为位置编码器的输出,经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# x = pe_result #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# # dropout = 0.2
# # self_attn = MultiHeadedAttention(head, d_model) #多头注意力机制
# # ff = PositionwiseFeedForward(d_model, d_ff, dropout) # 前馈全连接层的结构
# # mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # #调用:
# # el = EncoderLayer(size, self_attn, ff, dropout)
# # el_result = el(x, mask)
# # # print(el_result)
# # print("el_result.shape:",el_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 第一个实例化参数layer, 它是一个编码器层的实例化对象, 因此需要传入编码器层的参数
# # 又因为编码器层中的子层是不共享的, 因此需要使用深度拷贝各个对象.
# size = 512
# head = 8
# d_model = 512
# d_ff = 64
# c = copy.deepcopy # 复制对象,两者作用相同
# attn = MultiHeadedAttention(head, d_model)#多头注意力机制
# dropout = 0.2
# ff = PositionwiseFeedForward(d_model, d_ff, dropout)# 前馈全连接层的结构
#
# """
# 1.深拷贝一份模型类实例对象,创建出一个全新的结构相同但内存上互相独立的模型类实例对象。
# copy.deepcopy(nn.Linear等已经定好的模型类)
# copy.deepcopy(继承Module类的自定义类)
#
# 2.EncoderLayer(size, c(attn), c(ff), dropout)
# 1.size:词嵌入维度/词向量维度
# 2.dropout:dropout层的置零比率
# 3.copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# 4.copy.deepcopy(继承Module类的PositionwiseFeedForward前馈全连接层的实例对象):创建出一个全新的结构相同但内存上互相独立的前馈全连接层类实例对象。
# 之所以传入EncoderLayer类编码器层类进行初始化的是通过深拷贝创建出的全新的结构相同但内存上互相独立的模型类实例对象,
# 是因为Encoder编码器中需要构建多个EncoderLayer编码器层,
# 而每个EncoderLayer编码器层里面都拥有结构相同的MultiHeadedAttention多头注意力计算层和PositionwiseFeedForward前馈全连接层,
# 因此每个EncoderLayer编码器层里面都需要使用结构相同但内存上互相独立的多头注意力计算层类和前馈全连接层类的实例对象,
# 所以每次执行一次copy.deepcopy(模型类实例对象)就会创建一个全新的结构相同但内存上互相独立的模型类实例对象。
# """
# layer = EncoderLayer(size, c(attn), c(ff), dropout)
#
# # 编码器中编码器层的个数N
# N = 8
# mask = Variable(torch.zeros(8, 4, 4))#[batch size, 句子最大长度max_len, 句子最大长度max_len]
# #调用:
# en = Encoder(layer, N)
# en_result = en(x, mask)
# # print(en_result)
# print("en_result.shape",en_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# # #实例化参数:
# # # 类的实例化参数与解码器层类似, 相比多出了src_attn, 但是和self_attn是同一个类.
# # head = 8
# # size = 512
# # d_model = 512
# # d_ff = 64
# # dropout = 0.2
# # self_attn = src_attn = MultiHeadedAttention(head, d_model, dropout)
# # # 前馈全连接层也和之前相同
# # ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# # """
# # pe_result:经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# # en_result:Encoder编码器的最终输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# # mask:掩码张量,[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # source_mask:源数据的掩码张量
# # target_mask:目标数据的掩码张量
# # """
# # #输入参数:
# # # x是来自目标数据的词嵌入表示, 但形式和源数据的词嵌入表示相同, 这里使用per充当.
# # x = pe_result
# # # memory是来自编码器的输出
# # memory = en_result
# #
# # """
# # Variable(torch.zeros(...), requires_grad=True/False)
# # 1.torch.autograd.Variable是autograd的核心类,Variable整合了Tensor,并整合了反向传播的相关实现。
# # 2.Variable包含3个属性:
# # data:保存了Tensor,是本体真实数据。
# # grad:保存了上述data属性值的梯度值,与data属性的形状一致。
# # grad_fn:指向Function对象,用于反向传播时的梯度计算之用。
# # 3.requires_grad
# # 1.requires_grad=True:在反向传播时会对所封装的Tensor数据进行梯度求解。
# # 2.requires_grad=False:在反向传播时不会对所封装的Tensor数据进行梯度求解。
# # 3.比如输入的样本数据和目标标签数据使用Variable进行封装的同时而又不需要求梯度时,那么便可以设置requires_grad=False,
# # 那么它是不需要进行梯度求解的。
# # """
# #
# # # 实际中source_mask和target_mask并不相同, 这里为了方便计算使他们都为mask
# # mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # source_mask = target_mask = mask
# # #调用:
# # dl = DecoderLayer(size, self_attn, src_attn, ff, dropout)
# # dl_result = dl(x, memory, source_mask, target_mask)
# # # print(dl_result)
# # print("dl_result.shape",dl_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 分别是解码器层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)
# """
# 1.深拷贝一份模型类实例对象,创建出一个全新的结构相同但内存上互相独立的模型类实例对象。
# copy.deepcopy(nn.Linear等已经定好的模型类)
# copy.deepcopy(继承Module类的自定义类)
#
# 2.DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
# 1.d_model:词嵌入维度/词向量维度
# 2.dropout:dropout层的置零比率
# 3.copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):
# 创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# 但该实例对象传入到DecoderLayer解码器层中便作为第一个残差连接子层中的多头自注意力,也即为自注意力机制,这个自注意力机制要求Q=K=V。
# Q/K/V均为“经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的”词嵌入张量。
# 4.copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):
# 创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# 但该实例对象传入到DecoderLayer解码器层中便作为第二个残差连接子层中的多头注意力(非多头自注意力),要求K=V,但Q!=K并且Q!=V;
# Q:Q!=K并且Q!=V,Q为经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# K=V:两者均为编码器层的语义存储变量,即Encoder编码器的最终输出,
# 5.copy.deepcopy(继承Module类的PositionwiseFeedForward前馈全连接层的实例对象):
# 创建出一个全新的结构相同但内存上互相独立的前馈全连接层类实例对象。
# 之所以传入EncoderLayer类编码器层类进行初始化的是通过深拷贝创建出的全新的结构相同但内存上互相独立的模型类实例对象,
# 是因为Encoder编码器中需要构建多个EncoderLayer编码器层,
# 而每个EncoderLayer编码器层里面都拥有结构相同的MultiHeadedAttention多头注意力计算层和PositionwiseFeedForward前馈全连接层,
# 因此每个EncoderLayer编码器层里面都需要使用结构相同但内存上互相独立的多头注意力计算层类和前馈全连接层类的实例对象,
# 所以每次执行一次copy.deepcopy(模型类实例对象)就会创建一个全新的结构相同但内存上互相独立的模型类实例对象。
# """
# layer = DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
#
# """
# pe_result:目标数据经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量,即目标数据的词嵌入表示
# en_result:Encoder编码器的最终输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# mask:掩码张量,[batch size, 句子最大长度max_len, 句子最大长度max_len]
# source_mask:源数据的掩码张量
# target_mask:目标数据的掩码张量
# """
# 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",de_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab_size = 1000 #目标语料的词表大小
# #输入参数:
# # 输入x是上一层网络的输出, 我们使用来自解码器层的输出
# x = de_result #Decoder解码器的输出 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# #调用:
# gen = Generator(d_model, vocab_size)
# gen_result = gen(x)
# # print(gen_result)
# print("gen_result.shape",gen_result.shape) #torch.Size([2, 4, 1000]) 即 [batch size, 句子最大长度max_len, 目标语料的词表大小]
"""
nn.Linear演示:
>>> m = nn.Linear(20, 30)
>>> input = torch.randn(128, 20)
>>> output = m(input)
>>> print(output.size())
torch.Size([128, 30])
"""
# 用于深度拷贝的copy工具包
import copy
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
# nn.functional工具包装载了网络层中那些只进行计算, 而没有参数的层
import torch.nn.functional as F
from day08.输入部分实现_1 import PositionalEncoding
from day08.输入部分实现_1 import Embeddings
from day08.多头注意力机制_4 import MultiHeadedAttention, clones
from day08.前馈全连接层_5 import PositionwiseFeedForward
from day08.编码器层_8 import EncoderLayer
from day08.编码器_9 import Encoder
from day08.解码器层_10 import DecoderLayer
from day08.解码器_11 import Decoder
from day08.输出部分实现_12 import Generator
"""
EncoderDecoder 把编码器和解码器封装起来,实现“编码器-解码器”结构
EncoderDecoder(Encoder编码器类实例对象, Decoder解码器类实例对象,
nn.Sequential(输入源语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象),
nn.Sequential(输出目标语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象),
输出部分的Generator类别生成器类实例对象)
EncoderDecoder实例对象(输入源语言张量, 输出目标语言张量, 源数据的掩码张量, 目标数据的掩码张量)
1.__init__(encoder, decoder, source_embed, target_embed, generator):
encoder = encoder:Encoder编码器类实例对象
decoder = decoder:Decoder解码器类实例对象
src_embed = source_embed:nn.Sequential(输入源语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象)
tgt_embed = target_embed:nn.Sequential(输出目标语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象)
generator = generator:输出部分的Generator类别生成器类实例对象
2.forward(self, source, target, source_mask, target_mask):
forward函数最终返回的是“编码器-解码器”结构输出的解码器层的语义存储变量,
此时该输出信息仍然还没有经过输出部分的Generator类别生成器。
1.source:输入源语言张量
target:输出目标语言张量
source_mask:输入源数据的掩码张量
target_mask:目标数据的掩码张量
2.decode函数(encode函数(source, source_mask), source_mask, target, target_mask)
decode函数最终返回的是“编码器-解码器”结构输出的解码器层的语义存储变量,
此时该输出信息仍然还没有经过输出部分的Generator类别生成器。
1.第一步 先执行 encode函数(输入源语言张量, 输入源数据的掩码张量) 其中首先实现了
输入源语言的嵌入层张量 = nn.Sequential(输入源语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象)(输入源语言张量),
然后实现了 编码器层输出的语义存储变量 = Encoder编码器类实例对象(输入源语言的嵌入层张量,输入源数据的掩码张量),
即最终 Encoder编码器返回的输出即编码器层的语义存储变量。
2.第二步 然后执行 decode函数(Encoder编码器输出的语义存储变量, 输入源数据的掩码张量, 输出目标语言张量, 目标数据的掩码张量)
其中首先实现 输出目标语言的嵌入层张量 = nn.Sequential(输出目标语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象)(输出目标语言张量),
解码器层输出的语义存储变量 = Decoder解码器类实例对象(输出目标语言的嵌入层张量, Encoder编码器输出的语义存储变量, 输入源数据的掩码张量, 目标数据的掩码张量)
3.encode函数(输入源语言张量source, 输入源数据的掩码张量source_mask),
Encoder编码器类实例对象encoder(src_embed(source), source_mask)
1.src_embed(source):
src_embed:nn.Sequential(输入源语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象)
输入源语言的嵌入层张量 = nn.Sequential(输入源语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象)(输入源语言张量source)
2.最终执行 编码器层输出的语义存储变量 = Encoder编码器类实例对象(输入源语言的嵌入层张量, 输入源数据的掩码张量)
4.decode函数(Encoder编码器输出的语义存储变量memory, 输入源数据的掩码张量source_mask, 输出目标语言张量target, 目标数据的掩码张量target_mask)
Decoder解码器类实例对象decoder(tgt_embed(target), memory, source_mask, target_mask)
1.tgt_embed(target):
tgt_embed:nn.Sequential(输出目标语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象)
输出目标语言的嵌入层张量 = nn.Sequential(输出目标语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象)(输出目标语言张量target)
2.最终执行 解码器层输出的语义存储变量 = Decoder解码器类实例对象(输出目标语言的嵌入层张量, Encoder编码器输出的语义存储变量, 输入源数据的掩码张量, 目标数据的掩码张量)
"""
# 使用EncoderDecoder类来实现编码器-解码器结构
class EncoderDecoder(nn.Module):
def __init__(self, encoder, decoder, source_embed, target_embed, generator):
"""初始化函数中有5个参数, 分别是编码器对象, 解码器对象,
源数据嵌入函数, 目标数据嵌入函数, 以及输出部分的类别生成器对象
"""
super(EncoderDecoder, self).__init__()
# 将参数传入到类中
self.encoder = encoder
self.decoder = decoder
self.src_embed = source_embed
self.tgt_embed = target_embed
self.generator = generator
def forward(self, source, target, source_mask, target_mask):
"""在forward函数中,有四个参数, source代表源数据, target代表目标数据,
source_mask和target_mask代表对应的掩码张量"""
"""
1.decode_output.shape:[8, 10, 512]
head=8:多头注意力结构中的多头数
max_len=10:代表输入序列中每个句子的最大长度
d_model=512:
词嵌入维度/词向量维度。同时也是第一个线性层的输入维度,也是第二个线性层的输出维度。
便可以保证input输入通过前馈全连接层后,原始输入和最终输出的维度不变。
2.decode_output.shape:[8, 9, 512]
head=8:多头注意力结构中的多头数
max_len=9:代表目标序列中每个句子的最大长度(因为第3点的左闭右开原因,所以10减了1变成9)
d_model=512:
词嵌入维度/词向量维度。同时也是第一个线性层的输入维度,也是第二个线性层的输出维度。
便可以保证input输入通过前馈全连接层后,原始输入和最终输出的维度不变。
3.Batch(source, target)
class Batch 中的 __init__ 执行的 self.trg = trg[:, :-1] 和 self.trg_y = trg[:, 1:]。
因为上述初始化操作对target的左闭右开的操作,target.shape会从torch.Size([8, 10])变成torch.Size([8, 9])
4.max_len
输入序列中每个句子的最大长度=10
目标序列中每个句子的最大长度=9(因为第3点的左闭右开原因,所以10减了1变成9)
5.decode_output解码器输出 ---> 输出部分的Generator类别生成器类 得出类别概率值
其中是通过 SimpleLossCompute(model.generator, criterion, optimizer) 传入model.generator(Generator类别生成器类实例对象),
其底层class SimpleLossCompute中的__call__会自动调用model.generator对解码器的输出进行softmax转换为概率值。
"""
# 在函数中, 将source, source_mask传入编码函数, 得到结果后,
# 与source_mask,target,和target_mask一同传给解码函数.
return self.decode(self.encode(source, source_mask), source_mask,
target, target_mask)
def encode(self, source, source_mask):
"""编码函数, 以source和source_mask为参数"""
# 使用src_embed对source做处理, 然后和source_mask一起传给self.encoder
return self.encoder(self.src_embed(source), source_mask)
def decode(self, memory, source_mask, target, target_mask):
"""解码函数, 以memory即编码器的输出, source_mask, target, target_mask为参数"""
# 使用tgt_embed对target做处理, 然后和source_mask, target_mask, memory一起传给self.decoder
return self.decoder(self.tgt_embed(target), memory, source_mask, target_mask)
"""
model = make_model(source_vocab, target_vocab, N=6, d_model=512, d_ff=2048, head=8, dropout=0.1)
返回一个实现了“编码器-解码器”结构的继承了nn.Module的自定义模型类
source_vocab:输入源语言的词表大小
target_vocab:输出目标语言的词表大小
N=6:Encoder编码器中的EncoderLayer编码器层的层数 和 Decoder解码器中的DecoderLayer解码器层的层数
d_model=512:词嵌入维度/词向量维度。同时也是第一个线性层的输入维度,也是第二个线性层的输出维度。
便可以保证input输入通过前馈全连接层后,原始输入和最终输出的维度不变。
d_ff=2048:第一个线性层的输出维度,也是第二个线性层的输入维度,实际也即是前馈全连接网络中变换矩阵的维度数
head=8:多头注意力结构中的多头数
dropout=0.1:置零丢弃比率
"""
def make_model(source_vocab, target_vocab, N=6, d_model=512, d_ff=2048, head=8, dropout=0.1):
"""该函数用来构建模型, 有7个参数,分别是源数据特征(词汇)总数,目标数据特征(词汇)总数,
编码器和解码器堆叠数,词向量映射维度,前馈全连接网络中变换矩阵的维度,
多头注意力结构中的多头数,以及置零比率dropout."""
# 首先得到一个深度拷贝命令,接下来很多结构都需要进行深度拷贝,
# 来保证他们彼此之间相互独立,不受干扰.
c = copy.deepcopy
# 实例化了多头注意力类,得到对象attn
attn = MultiHeadedAttention(head, d_model)
# 然后实例化前馈全连接类,得到对象ff
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# 实例化位置编码类,得到对象position
position = PositionalEncoding(d_model, dropout)
"""
EncoderDecoder(Encoder编码器类实例对象, Decoder解码器类实例对象,
nn.Sequential(输入源语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象),
nn.Sequential(输出目标语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象),
输出部分的Generator类别生成器类实例对象)
EncoderDecoder(
Encoder编码器类(EncoderLayer编码器层类(词向量维度,深拷贝的多头注意力,深拷贝的前馈全连接层,置零丢弃比率),Encoder编码器中的EncoderLayer编码器层的层数),
Decoder解码器类(DecoderLayer解码器层类(词向量维度,深拷贝的多头自注意力,深拷贝的多头注意力,深拷贝的前馈全连接层,置零丢弃比率),Decoder解码器中的DecoderLayer解码器层的层数),
nn.Sequential(Embeddings嵌入层类(词向量维度,输入源语言的词表大小), 深拷贝的位置编码器),
nn.Sequential(Embeddings嵌入层类(词向量维度,输出目标语言的词表大小), 深拷贝的位置编码器),
Generator类别生成器(词向量维度,输出目标语言的词表大小)
)
"""
# 根据结构图, 最外层是EncoderDecoder,在EncoderDecoder中,
# 分别是编码器层,解码器层,源数据Embedding层和位置编码组成的有序结构,
# 目标数据Embedding层和位置编码组成的有序结构,以及类别生成器层.
# 在编码器层中有attention子层以及前馈全连接子层,
# 在解码器层中有两个attention子层以及前馈全连接层.
model = EncoderDecoder(
Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N),
Decoder(DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout), N),
nn.Sequential(Embeddings(d_model, source_vocab), c(position)),
nn.Sequential(Embeddings(d_model, target_vocab), c(position)),
Generator(d_model, target_vocab))
# 模型结构完成后,接下来就是初始化模型中的参数,比如线性层中的变换矩阵
# 这里一但判断参数的维度大于1,则会将其初始化成一个服从均匀分布的矩阵,
for p in model.parameters():
"""判断如果可学习的训练权重的维度数大于1(比如2维/3维张量)即会执行下面的均匀初始化操作"""
if p.dim() > 1:
"""
1.UserWarning: nn.init.xavier_uniform is now deprecated in favor of nn.init.xavier_uniform_(p)
用户警告:nn.init.xavier_uniform现在已被弃用,取而代之的是nn.init.xavier_uniform_(p)
2.xavier_uniform_:带一个下划线的后缀的表示数据变量在更新时原地址更新,不需要返回新变量
"""
# nn.init.xavier_uniform(p)
nn.init.xavier_uniform_(p)
return model
# #输入参数:
# source_vocab = 11 #输入源语言的词表大小
# target_vocab = 11 #输出目标语言的词表大小
# N = 6
# # 其他参数都使用默认值
# #调用:
# model = make_model(source_vocab, target_vocab, N)
# print(model.generator) # Generator((project): Linear(in_features=512, out_features=11, bias=True))
# # print(model)
"""
nn.init.xavier_uniform(p) 改为使用 nn.init.xavier_uniform_(p)
xavier_uniform_:带一个下划线的后缀的表示数据变量在更新时原地址更新,不需要返回新变量
UserWarning: nn.init.xavier_uniform is now deprecated in favor of nn.init.xavier_uniform_(p)
用户警告:nn.init.xavier_uniform现在已被弃用,取而代之的是nn.init.xavier_uniform_(p)
nn.init.xavier_uniform演示:
# 结果服从均匀分布U(-a, a)
>>> w = torch.empty(3, 5)
>>> w = nn.init.xavier_uniform_(w, gain=nn.init.calculate_gain('relu'))
>>> w
tensor([[-0.7742, 0.5413, 0.5478, -0.4806, -0.2555],
[-0.8358, 0.4673, 0.3012, 0.3882, -0.6375],
[ 0.4622, -0.0794, 0.1851, 0.8462, -0.3591]])
"""
"""
EncoderDecoder(
(encoder): Encoder(
(layers): ModuleList(
(0): EncoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(1): EncoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(2): EncoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(3): EncoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(4): EncoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(5): EncoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
)
(norm): LayerNorm()
)
(decoder): Decoder(
(layers): ModuleList(
(0): DecoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(src_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(2): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(1): DecoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(src_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(2): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(2): DecoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(src_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(2): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(3): DecoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(src_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(2): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(4): DecoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(src_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(2): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
(5): DecoderLayer(
(self_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(src_attn): MultiHeadedAttention(
(linears): ModuleList(
(0): Linear(in_features=512, out_features=512, bias=True)
(1): Linear(in_features=512, out_features=512, bias=True)
(2): Linear(in_features=512, out_features=512, bias=True)
(3): Linear(in_features=512, out_features=512, bias=True)
)
(dropout): Dropout(p=0.1, inplace=False)
)
(feed_forward): PositionwiseFeedForward(
(w1): Linear(in_features=512, out_features=2048, bias=True)
(w2): Linear(in_features=2048, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(sublayer): ModuleList(
(0): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(1): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
(2): SublayerConnection(
(norm): LayerNorm()
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
)
(norm): LayerNorm()
)
(src_embed): Sequential(
(0): Embeddings(
(lut): Embedding(11, 512)
)
(1): PositionalEncoding(
(dropout): Dropout(p=0.1, inplace=False)
)
)
(tgt_embed): Sequential(
(0): Embeddings(
(lut): Embedding(11, 512)
)
(1): PositionalEncoding(
(dropout): Dropout(p=0.1, inplace=False)
)
)
(generator): Generator(
(project): Linear(in_features=512, out_features=11, bias=True)
)
)
"""
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab = 1000
# #输入参数:
# # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
# x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
# #调用:
# emb = Embeddings(d_model, vocab)
# embr = emb(x)
# # print("embr:", embr)
# # print("embr.shape:", embr.shape) #torch.Size([2, 4, 512])
#
# #-------------------------------------------------------------------
# # 实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 置0比率为0.1
# dropout = 0.1
# # 句子最大长度
# max_len = 60
# # 输入参数:
# # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
# x = embr #torch.Size([2, 4, 512])
# pe = PositionalEncoding(d_model, dropout, max_len)
# pe_result = pe(x)
# # print("pe_result:", pe_result)
# print("pe_result.shape:", pe_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# # #实例化参数:
# # size = 512
# # head = 8
# # d_model = 512
# # d_ff = 64
# # 令x为位置编码器的输出,经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# x = pe_result #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# # dropout = 0.2
# # self_attn = MultiHeadedAttention(head, d_model) #多头注意力机制
# # ff = PositionwiseFeedForward(d_model, d_ff, dropout) # 前馈全连接层的结构
# # mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # #调用:
# # el = EncoderLayer(size, self_attn, ff, dropout)
# # el_result = el(x, mask)
# # # print(el_result)
# # print("el_result.shape:",el_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 第一个实例化参数layer, 它是一个编码器层的实例化对象, 因此需要传入编码器层的参数
# # 又因为编码器层中的子层是不共享的, 因此需要使用深度拷贝各个对象.
# size = 512
# head = 8
# d_model = 512
# d_ff = 64
# c = copy.deepcopy # 复制对象,两者作用相同
# attn = MultiHeadedAttention(head, d_model)#多头注意力机制
# dropout = 0.2
# ff = PositionwiseFeedForward(d_model, d_ff, dropout)# 前馈全连接层的结构
#
# """
# 1.深拷贝一份模型类实例对象,创建出一个全新的结构相同但内存上互相独立的模型类实例对象。
# copy.deepcopy(nn.Linear等已经定好的模型类)
# copy.deepcopy(继承Module类的自定义类)
#
# 2.EncoderLayer(size, c(attn), c(ff), dropout)
# 1.size:词嵌入维度/词向量维度
# 2.dropout:dropout层的置零比率
# 3.copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# 4.copy.deepcopy(继承Module类的PositionwiseFeedForward前馈全连接层的实例对象):创建出一个全新的结构相同但内存上互相独立的前馈全连接层类实例对象。
# 之所以传入EncoderLayer类编码器层类进行初始化的是通过深拷贝创建出的全新的结构相同但内存上互相独立的模型类实例对象,
# 是因为Encoder编码器中需要构建多个EncoderLayer编码器层,
# 而每个EncoderLayer编码器层里面都拥有结构相同的MultiHeadedAttention多头注意力计算层和PositionwiseFeedForward前馈全连接层,
# 因此每个EncoderLayer编码器层里面都需要使用结构相同但内存上互相独立的多头注意力计算层类和前馈全连接层类的实例对象,
# 所以每次执行一次copy.deepcopy(模型类实例对象)就会创建一个全新的结构相同但内存上互相独立的模型类实例对象。
# """
# layer = EncoderLayer(size, c(attn), c(ff), dropout)
#
# # Encoder编码器中编码器层的个数N
# N = 8
# # mask = Variable(torch.zeros(8, 4, 4))#[batch size, 句子最大长度max_len, 句子最大长度max_len]
# #调用:
# en = Encoder(layer, N)
# # en_result = en(x, mask)
# # # print(en_result)
# # print("en_result.shape",en_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# # #实例化参数:
# # # 类的实例化参数与解码器层类似, 相比多出了src_attn, 但是和self_attn是同一个类.
# # head = 8
# # size = 512
# # d_model = 512
# # d_ff = 64
# # dropout = 0.2
# # self_attn = src_attn = MultiHeadedAttention(head, d_model, dropout)
# # # 前馈全连接层也和之前相同
# # ff = PositionwiseFeedForward(d_model, d_ff, dropout)
# # """
# # pe_result:经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# # en_result:Encoder编码器的最终输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# # mask:掩码张量,[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # source_mask:源数据的掩码张量
# # target_mask:目标数据的掩码张量
# # """
# # #输入参数:
# # # x是来自目标数据的词嵌入表示, 但形式和源数据的词嵌入表示相同, 这里使用per充当.
# # x = pe_result
# # # memory是来自编码器的输出
# # memory = en_result
# #
# # """
# # Variable(torch.zeros(...), requires_grad=True/False)
# # 1.torch.autograd.Variable是autograd的核心类,Variable整合了Tensor,并整合了反向传播的相关实现。
# # 2.Variable包含3个属性:
# # data:保存了Tensor,是本体真实数据。
# # grad:保存了上述data属性值的梯度值,与data属性的形状一致。
# # grad_fn:指向Function对象,用于反向传播时的梯度计算之用。
# # 3.requires_grad
# # 1.requires_grad=True:在反向传播时会对所封装的Tensor数据进行梯度求解。
# # 2.requires_grad=False:在反向传播时不会对所封装的Tensor数据进行梯度求解。
# # 3.比如输入的样本数据和目标标签数据使用Variable进行封装的同时而又不需要求梯度时,那么便可以设置requires_grad=False,
# # 那么它是不需要进行梯度求解的。
# # """
# #
# # # 实际中source_mask和target_mask并不相同, 这里为了方便计算使他们都为mask
# # mask = Variable(torch.zeros(8, 4, 4)) #[batch size, 句子最大长度max_len, 句子最大长度max_len]
# # source_mask = target_mask = mask
# # #调用:
# # dl = DecoderLayer(size, self_attn, src_attn, ff, dropout)
# # dl_result = dl(x, memory, source_mask, target_mask)
# # # print(dl_result)
# # print("dl_result.shape",dl_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 分别是解码器层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)
# """
# 1.深拷贝一份模型类实例对象,创建出一个全新的结构相同但内存上互相独立的模型类实例对象。
# copy.deepcopy(nn.Linear等已经定好的模型类)
# copy.deepcopy(继承Module类的自定义类)
#
# 2.DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
# 1.d_model:词嵌入维度/词向量维度
# 2.dropout:dropout层的置零比率
# 3.copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):
# 创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# 但该实例对象传入到DecoderLayer解码器层中便作为第一个残差连接子层中的多头自注意力,也即为自注意力机制,这个自注意力机制要求Q=K=V。
# Q/K/V均为“经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的”词嵌入张量。
# 4.copy.deepcopy(继承Module类的MultiHeadedAttention多头注意力的实例对象):
# 创建出一个全新的结构相同但内存上互相独立的多头注意力类实例对象。
# 但该实例对象传入到DecoderLayer解码器层中便作为第二个残差连接子层中的多头注意力(非多头自注意力),要求K=V,但Q!=K并且Q!=V;
# Q:Q!=K并且Q!=V,Q为经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量
# K=V:两者均为编码器层的语义存储变量,即Encoder编码器的最终输出,
# 5.copy.deepcopy(继承Module类的PositionwiseFeedForward前馈全连接层的实例对象):
# 创建出一个全新的结构相同但内存上互相独立的前馈全连接层类实例对象。
# 之所以传入EncoderLayer类编码器层类进行初始化的是通过深拷贝创建出的全新的结构相同但内存上互相独立的模型类实例对象,
# 是因为Encoder编码器中需要构建多个EncoderLayer编码器层,
# 而每个EncoderLayer编码器层里面都拥有结构相同的MultiHeadedAttention多头注意力计算层和PositionwiseFeedForward前馈全连接层,
# 因此每个EncoderLayer编码器层里面都需要使用结构相同但内存上互相独立的多头注意力计算层类和前馈全连接层类的实例对象,
# 所以每次执行一次copy.deepcopy(模型类实例对象)就会创建一个全新的结构相同但内存上互相独立的模型类实例对象。
# """
# layer = DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
#
# """
# pe_result:目标数据经过了Embedding层和位置编码层的[batch size, 句子最大长度max_len, 词嵌入维度]的词嵌入张量,即目标数据的词嵌入表示
# en_result:Encoder编码器的最终输出,即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# mask:掩码张量,[batch size, 句子最大长度max_len, 句子最大长度max_len]
# source_mask:源数据的掩码张量
# target_mask:目标数据的掩码张量
# """
# 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",de_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
#
# #-------------------------------------------------------------------
#
# #实例化参数:
# # 词嵌入维度是512维
# d_model = 512
# # 词表大小是1000
# vocab_size = 1000 #目标语料的词表大小
# # #输入参数:
# # # 输入x是上一层网络的输出, 我们使用来自解码器层的输出
# # x = de_result #Decoder解码器的输出 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
# #调用:
# gen = Generator(d_model, vocab_size) #Generator 生成器类
# # gen_result = gen(x)
# # # print(gen_result)
# # print("gen_result.shape",gen_result.shape) #torch.Size([2, 4, 1000]) 即 [batch size, 句子最大长度max_len, 目标语料的词表大小]
#
# #-------------------------------------------------------------------
#
# #实例化参数
# vocab_size = 1000 #词表大小
# d_model = 512 # 词嵌入维度是512维
# encoder = en # Encoder编码器
# decoder = de #Decoder解码器
# #nn.Embedding(vocab_size 词汇总数, embed_dim 单词嵌入维度)
# source_embed = nn.Embedding(vocab_size, d_model) #输入源语言的Embedding嵌入层
# target_embed = nn.Embedding(vocab_size, d_model) #输出目标语言的Embedding嵌入层
# generator = gen #Generator 生成器类的实例对象
#
# """
# 输入源语言张量和输出目标语言张量:[batch size, 句子最大长度max_len]
# source_mask源数据的掩码张量和target_mask目标数据的掩码张量:[batch size, 句子最大长度max_len, 句子最大长度max_len]
# """
# #输入参数:
# # 假设此处测试中使用相同的源数据和目标数据, 但实际场景中通常都并不相同
# source = target = Variable(torch.LongTensor([[100, 2, 421, 508], [491, 998, 1, 221]]))
# # 假设此处测试中使用相同的src_mask和tgt_mask相同,但实际场景中通常都并不相同
# source_mask = target_mask = Variable(torch.zeros(8, 4, 4))
# """
# Variable(torch.zeros(...), requires_grad=True/False)
# 1.torch.autograd.Variable是autograd的核心类,Variable整合了Tensor,并整合了反向传播的相关实现。
# 2.Variable包含3个属性:
# data:保存了Tensor,是本体真实数据。
# grad:保存了上述data属性值的梯度值,与data属性的形状一致。
# grad_fn:指向Function对象,用于反向传播时的梯度计算之用。
# 3.requires_grad
# 1.requires_grad=True:在反向传播时会对所封装的Tensor数据进行梯度求解。
# 2.requires_grad=False:在反向传播时不会对所封装的Tensor数据进行梯度求解。
# 3.比如输入的样本数据和目标标签数据使用Variable进行封装的同时而又不需要求梯度时,那么便可以设置requires_grad=False,
# 那么它是不需要进行梯度求解的。
# """
#
# """
# EncoderDecoder 把编码器和解码器封装起来,实现“编码器-解码器”结构
# EncoderDecoder(Encoder编码器类实例对象, Decoder解码器类实例对象,
# nn.Sequential(输入源语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象),
# nn.Sequential(输出目标语言的Embeddings嵌入层类实例对象, 位置编码器类实例对象),
# 输出部分的Generator类别生成器类实例对象)
# EncoderDecoder实例对象(输入源语言张量, 输出目标语言张量, 源数据的掩码张量, 目标数据的掩码张量)
# """
#
# #调用:
# #EncoderDecoder(Encoder编码器类实例对象, Decoder解码器类实例对象,
# # 输入源语言的Embedding嵌入层实例对象, 输出目标语言的Embedding嵌入层实例对象,
# # 输出部分的Generator类别生成器类实例对象)
# # ed = EncoderDecoder(encoder, decoder, source_embed, target_embed, generator)
#
# source_vocab = 1000 #词表大小
# target_vocab = 1000
# ed = EncoderDecoder(encoder, decoder,
# nn.Sequential(Embeddings(d_model, source_vocab), pe),
# nn.Sequential(Embeddings(d_model, source_vocab), pe),
# generator)
#
# ed_result = ed(source, target, source_mask, target_mask)
# # print(ed_result)
# print("ed_result.shape",ed_result.shape) #torch.Size([2, 4, 512]) 即 [batch size, 句子最大长度max_len, embedding_dim词向量维度]
"""
1.将通过一个小的copy任务完成模型的基本测试工作.
2.copy任务介绍:
1.任务描述:
针对数字序列进行学习, 学习的最终目标是使输出与输入的序列相同.
如输入[1, 5, 8, 9, 3], 输出也是[1, 5, 8, 9, 3].
2.任务意义:
copy任务在模型基础测试中具有重要意义,因为copy操作对于模型来讲是一条明显规律, 因此模型能否在短时间内,
小数据集中学会它,可以帮助我们断定模型所有过程是否正常,是否已具备基本学习能力.
"""
"""
使用copy任务进行模型基本测试的四步:
第一步: 构建数据集生成器
第二步: 获得Transformer模型及其优化器和损失函数
第三步: 运行模型进行训练和评估
第四步: 使用模型进行贪婪解码
"""
# 用于深度拷贝的copy工具包
import copy
import torch
import torch.nn as nn
# torch中变量封装函数Variable.
from torch.autograd import Variable
# nn.functional工具包装载了网络层中那些只进行计算, 而没有参数的层
import torch.nn.functional as F
import numpy as np
#------------------------第一步: 构建数据集生成器----------------------#
# 导入工具包Batch, 它能够对原始样本数据生成对应批次的掩码张量
from pyitcast.transformer_utils import Batch
"""pip install pyitcast"""
""" 批量训练数据generator生成器 """
def data_generator(V, batch, num_batch):
"""该函数用于随机生成copy任务的数据, 它的三个输入参数是V: 随机生成数字的最大值+1,
batch: 每次输送给模型更新一次参数的数据量, num_batch: 一共输送num_batch次完成一轮
"""
"""
num_batch:一个epoch遍历多少个批量数据,即一个epoch中的批次数量
batch:批量大小,批量的数据的shape为(batch, 10)
"""
# 使用for循环遍历nbatches
for i in range(num_batch):
# 在循环中使用np的random.randint方法随机生成[1, V)的整数,
# 分布在(batch, 10)形状的矩阵中, 然后再把numpy形式转换称torch中的tensor.
data = torch.from_numpy(np.random.randint(1, V, size=(batch, 10)))
"""
报错:RuntimeError: Expected tensor for argument #1 'indices' to have scalar type Long;
but got torch.IntTensor instead (while checking arguments for embedding)
分析:训练的批量样本数据输入值需要是long值的Tensor数据,而不是int值的Tensor数据.
解决:
1.把输入数据data和目标数据data的类型值都从int转换为long
1.可以加long()
source = Variable(data, requires_grad=False).long()
target = Variable(data, requires_grad=False).long()
2.也可以加torch.LongTensor
source = Variable(torch.LongTensor(data), requires_grad=False)
target = Variable(torch.LongTensor(data), requires_grad=False)
2.如果有使用以下这个第三方pyitcast包下的transformer_utils.py的话,可以如下修改
class SimpleLossCompute
def __call__(self, x, y, norm):
#return loss.data[0] * norm
#loss.data[0] 改成 loss.data.item()
return loss.data.item() * norm
3.tensor变量.item() 的用法
x = torch.randn(1)
print(x) #tensor([-0.4464])
print(x.item()) #-0.44643348455429077
"""
# 接着使数据矩阵中的第一列数字都为1, 这一列也就成为了起始标志列,
# 当解码器进行第一次解码的时候, 会使用起始标志列作为输入.
data[:, 0] = 1
# 因为是copy任务, 所有source与target是完全相同的, 且数据样本作用变量不需要求梯度
# 因此requires_grad设置为False
source = Variable(torch.LongTensor(data), requires_grad=False)
target = Variable(torch.LongTensor(data), requires_grad=False)
"""使用yield作为返回值的话,那么该函数的返回即会变成generator生成器"""
# 使用Batch对source和target进行对应批次的掩码张量生成, 最后使用yield返回
yield Batch(source, target)
#输入参数:
# 将生成0-10的整数
V = 11
# 每次喂给模型20个数据进行参数更新
batch = 20
# 连续喂30次完成全部数据的遍历, 也就是1轮
num_batch = 30
#调用:
res = data_generator(V, batch, num_batch)
print(res) #<generator object data_generator at 0x000002D2870AA4C8>
"""
Variable(torch.zeros(...), requires_grad=True/False)
1.torch.autograd.Variable是autograd的核心类,Variable整合了Tensor,并整合了反向传播的相关实现。
2.Variable包含3个属性:
data:保存了Tensor,是本体真实数据。
grad:保存了上述data属性值的梯度值,与data属性的形状一致。
grad_fn:指向Function对象,用于反向传播时的梯度计算之用。
3.requires_grad
1.requires_grad=True:在反向传播时会对所封装的Tensor数据进行梯度求解。
2.requires_grad=False:在反向传播时不会对所封装的Tensor数据进行梯度求解。
3.比如输入的样本数据和目标标签数据使用Variable进行封装的同时而又不需要求梯度时,那么便可以设置requires_grad=False,
那么它是不需要进行梯度求解的。
"""
#------------------------第二步: 获得Transformer模型及其优化器和损失函数----------------------#
"""
获取“编码器-解码器”结构的模型类实例对象:model = make_model(source_vocab, target_vocab, N=6, d_model=512, d_ff=2048, head=8, dropout=0.1)
optimizer优化器:optimizer = get_std_opt(model)
标签平滑:criterion = LabelSmoothing(size=目标数据的词汇表大小, padding_idx=0, smoothing=0.0)
损失:loss = SimpleLossCompute(model.generator, criterion, optimizer)
开启训练模式:model.train()
开启评估模式:model.eval()
训练开始:run_epoch(批量训练数据generator生成器, model, loss)
"""
from day08.模型构建_13 import make_model
# 导入优化器工具包get_std_opt, 该工具用于获得标准的针对Transformer模型的优化器
# 该标准优化器基于Adam优化器, 使其对序列到序列的任务更有效.
from pyitcast.transformer_utils import get_std_opt
# 导入标签平滑工具包, 该工具用于标签平滑, 标签平滑的作用就是小幅度的改变原有标签值的值域
# 因为在理论上即使是人工的标注数据也可能并非完全正确, 会受到一些外界因素的影响而产生一些微小的偏差
# 因此使用标签平滑来弥补这种偏差, 减少模型对某一条规律的绝对认知, 以防止过拟合. 通过下面示例了解更多.
from pyitcast.transformer_utils import LabelSmoothing
# 导入损失计算工具包, 该工具能够使用标签平滑后的结果进行损失的计算,
# 损失的计算方法可以认为是交叉熵损失函数.
# from pyitcast.transformer_utils import SimpleLossCompute
from day08. My_transformer_utils import SimpleLossCompute
"""
model = make_model(source_vocab, target_vocab, N=6, d_model=512, d_ff=2048, head=8, dropout=0.1)
返回一个实现了“编码器-解码器”结构的继承了nn.Module的自定义模型类
source_vocab:输入源语言的词表大小
target_vocab:输出目标语言的词表大小
N=6:Encoder编码器中的EncoderLayer编码器层的层数 和 Decoder解码器中的DecoderLayer解码器层的层数
d_model=512:词嵌入维度/词向量维度。同时也是第一个线性层的输入维度,也是第二个线性层的输出维度。
便可以保证input输入通过前馈全连接层后,原始输入和最终输出的维度不变。
d_ff=2048:第一个线性层的输出维度,也是第二个线性层的输入维度,实际也即是前馈全连接网络中变换矩阵的维度数
head=8:多头注意力结构中的多头数
dropout=0.1:置零丢弃比率
"""
# 使用make_model获得model
model = make_model(V, V, N=2)
"""关于模型的optimizer优化器 = get_std_opt(“编码器-解码器”结构的模型类model)"""
# 使用get_std_opt获得模型优化器
model_optimizer = get_std_opt(model)
"""
criterion = LabelSmoothing(size, padding_idx, smoothing)
使用LabelSmoothing实例化一个criterion对象
size:目标数据的词汇表大小,也是模型最后一层输出的张量中的最后一维大小
padding_idx:表示要将哪些tensor中的数字替换成0, 一般padding_idx=0表示不进行替换
smoothing:标签的平滑程度。比如原来标签值为1, 那么对它进行平滑之后它的值域变为[1-smoothing, 1+smoothing]
设置padding_idx=某个单词对应的索引值
表示要将哪些tensor中的数字替换成0,一般padding_idx=0表示不进行替换。
传入Embedding的索引值某个单词对应的索引值在通过Embedding层之后,该索引值对应的输出权重值全为0.0000。
"""
# 使用LabelSmoothing获得标签平滑对象
criterion = LabelSmoothing(size=V, padding_idx=0, smoothing=0.5)
"""
model.generator:
获取的是 输出部分的Generator类别生成器类实例对象
打印 Generator((project): Linear(in_features=512, out_features=11, bias=True))
decode_output解码器输出 ---> 输出部分的Generator类别生成器类 得出类别概率值
其中是通过 SimpleLossCompute(model.generator, criterion, optimizer) 传入model.generator(Generator类别生成器类实例对象),
其底层class SimpleLossCompute中的__call__会自动调用model.generator对解码器的输出进行softmax转换为概率值。
"""
# 使用SimpleLossCompute获得利用标签平滑结果的损失计算方法
loss = SimpleLossCompute(model.generator, criterion, model_optimizer)
#==================标签平滑示例==============
"""
标签平滑图像分析:
我们目光集中在黄色小方块上, 它相对于横坐标横跨的值域就是标签平滑后的正向平滑值域, 我们可以看到大致是从0.5到2.5.
它相对于纵坐标横跨的值域就是标签平滑后的负向平滑值域, 我们可以看到大致是从-0.5到1.5.
总的值域空间由原来的[0, 2]变成了[-0.5, 2.5],其正向平滑值域为[0.5, 2.5],其负向平滑值域为[-0.5, 1.5]。
"""
from pyitcast.transformer_utils import LabelSmoothing
import matplotlib.pyplot as plt
# # 使用LabelSmoothing实例化一个crit对象.
# # 第一个参数size代表目标数据的词汇总数, 也是模型最后一层得到张量的最后一维大小
# # 这里是5说明目标词汇总数是5个. 第二个参数padding_idx表示要将那些tensor中的数字
# # 替换成0, 一般padding_idx=0表示不进行替换. 第三个参数smoothing, 表示标签的平滑程度
# # 如原来标签的表示值为1, 则平滑后它的值域变为[1-smoothing, 1+smoothing].
# crit = LabelSmoothing(size=5, padding_idx=0, smoothing=0.5)
#
# # 假定一个任意的模型最后输出预测结果和真实结果
# predict = Variable(torch.FloatTensor(
# [[0, 0.2, 0.7, 0.1, 0],
# [0, 0.2, 0.7, 0.1, 0],
# [0, 0.2, 0.7, 0.1, 0]]))
#
# # 标签的表示值是0,1,2
# target = Variable(torch.LongTensor([2, 1, 0]))
#
# # 将predict, target传入到对象中
# crit(predict, target)
#
# # 绘制标签平滑图像
# plt.imshow(crit.true_dist)
# plt.show()
#------------------------第三步: 运行模型进行训练和评估----------------------#
# 导入模型单轮训练工具包run_epoch, 该工具将对模型使用给定的损失函数计算方法进行单轮参数更新.
# 并打印每轮参数更新的损失结果.
from pyitcast.transformer_utils import run_epoch
"""
获取“编码器-解码器”结构的模型类实例对象:model = make_model(source_vocab, target_vocab, N=6, d_model=512, d_ff=2048, head=8, dropout=0.1)
optimizer优化器:optimizer = get_std_opt(model)
标签平滑:criterion = LabelSmoothing(size=目标数据的词汇表大小, padding_idx=0, smoothing=0.0)
损失:loss = SimpleLossCompute(model.generator, criterion, optimizer)
开启训练模式:model.train()
开启评估模式:model.eval()
训练开始:run_epoch(批量训练数据generator生成器, model, loss)
"""
def run_1(model, loss, epochs=10):
"""模型训练函数, 共有三个参数, model代表将要进行训练的模型
loss代表使用的损失计算方法, epochs代表模型训练的轮数"""
# 遍历轮数
for epoch in range(epochs):
# 模型使用训练模式, 所有参数将被更新
model.train()
"""
data_generator(V, batch, num_batch)
V:随机所生成[1, V)左开右闭范围的整数
num_batch:一个epoch遍历多少个批量数据,即一个epoch中的批次数量
batch:批量大小,批量的数据的shape为(batch, 10)
"""
# 训练时, batch_size是8
run_epoch(data_generator(V, 8, 20), model, loss)
# 模型使用评估模式, 参数将不会变化
model.eval()
# 评估时, batch_size是8
run_epoch(data_generator(V, 8, 5), model, loss)
#输入参数:
# 进行10轮训练
# epochs = 10
# model和loss都是来自上一步的结果
# run_1(model, loss)
#------------------------第四步: 使用模型进行贪婪解码----------------------#
# 导入贪婪解码工具包greedy_decode, 该工具将对最终结进行贪婪解码
# 贪婪解码的方式是每次预测都选择概率最大的结果作为输出,
# 它不一定能获得全局最优性, 但却拥有最高的执行效率.
from pyitcast.transformer_utils import greedy_decode
def run_2(model, loss, epochs=10):
for epoch in range(epochs):
model.train()
run_epoch(data_generator(V, 8, 20), model, loss)
model.eval()
run_epoch(data_generator(V, 8, 5), model, loss)
# 模型进入测试模式
model.eval()
# 假定的输入张量
source = Variable(torch.LongTensor([[1,3,2,5,4,6,7,8,9,10]]))
# 定义源数据掩码张量, 因为torch.ones生成的元素都是1, 在我们这里1代表不遮掩
# 因此相当于对源数据没有任何遮掩.
source_mask = Variable(torch.ones(1, 1, 10))
# 最后将model, src, src_mask, 解码的最大长度限制max_len, 默认为10
# 以及起始标志数字, 默认为1, 我们这里使用的也是1
result = greedy_decode(model, source, source_mask, max_len=10, start_symbol=1)
print(result)
# run_2(model, loss)
#------------------------ 测试 test_100.csv ----------------------#
all_lines = []
import csv
readinput = csv.reader(open("./test_100.csv",encoding="utf-8"))
for lines in readinput:
all_lines.append(lines)
all_lines_int = []
for i in range(len(all_lines)):
temp = []
for index in all_lines[i]:
temp.append(int(index))
all_lines_int.append(temp)
def run(model, loss, epochs=200):
for epoch in range(epochs):
model.train()
run_epoch(data_generator(V, 8, 20), model, loss)
model.eval()
run_epoch(data_generator(V, 8, 5), model, loss)
if epochs > 25:
count = 0
count_sum = 0
for i in range(len(all_lines_int)):
# 假定的输入张量
source = Variable(torch.LongTensor([all_lines_int[i]]))
# print(source)
# 定义源数据掩码张量, 因为torch.ones生成的元素都是1, 在我们这里1代表不遮掩
# 因此相当于对源数据没有任何遮掩.
source_mask = Variable(torch.ones(1, 1, 10))
# 最后将model, src, src_mask, 解码的最大长度限制max_len, 默认为10
# 以及起始标志数字, 默认为1, 我们这里使用的也是1
result = greedy_decode(model, source, source_mask, max_len=10, start_symbol=1)
# print("result",result)
# print("source",source)
# print(result == source)
# print(sum(result == source))
# print(torch.sum(result == source) == 10)
count_sum += torch.sum(result == source)
if torch.sum(result == source) == 10:
count += 1
print("count",count)
print("count_sum",count_sum)
run(model, loss)
all_lines = []
import csv
readinput = csv.reader(open("./test_100.csv",encoding="utf-8"))
for lines in readinput:
all_lines.append(lines)
# print(len(all_lines)) #100
all_lines_int = []
for i in range(len(all_lines)):
temp = []
for index in all_lines[i]:
temp.append(int(index))
all_lines_int.append(temp)
result = [1, 5, 10, 2, 6, 4, 7, 8, 3, 9]
for i in range(len(all_lines_int)):
# print(all_lines_int[i])
if i == 99:
indexs = 0
print(all_lines_int[i])
# print(all_lines[i] == ['1', '5', '10', '2', '6', '4', '7', '8', '3', '9'])
# print(all_lines[i] == [1, 5, 10, 2, 6, 4, 7, 8, 3, 9])
for index in all_lines_int[i]:
print(index == result[indexs])
indexs+=1
"""
Embedding
keras.layers.Embedding(input_dim, output_dim, embeddings_initializer='uniform',
embeddings_regularizer=None, activity_regularizer=None,
embeddings_constraint=None, mask_zero=False, input_length=None)
1.将正整数(索引值)转换为固定尺寸的稠密向量。 例如: [[4], [20]] -> [[0.25, 0.1], [0.6, -0.2]]
该层只能用作模型中的第一层。
2.Embedding层输入是一维[单个字符/单个单词],那么输出为二维[单个字符/单个单词, embedding_dim];
Embedding层输入是二维[batch_size, 单个字符/单个单词],那么输出为三维[batch_size, 单个字符/单个单词, embedding_dim]。
1.例子
model = Sequential()
model.add(Embedding(1000, 64, input_length=10))
# 模型将输入一个大小为 (batch, input_length) 的整数矩阵。
# 输入中最大的整数(即词索引)不应该大于 999 (词汇表大小)
# 现在 model.output_shape == (None, 10, 64),其中 None 是 batch 的维度。
# 输入input_array的shape为(batch_size, sequence_length) 的 2D 张量
input_array = np.random.randint(1000, size=(32, 10))
model.compile('rmsprop', 'mse')
#输出output_array的shape为(batch_size, sequence_length, output_dim) 的 3D 张量
output_array = model.predict(input_array)
assert output_array.shape == (32, 10, 64)
2.输入尺寸
尺寸为 (batch_size, sequence_length) 的 2D 张量。
3.输出尺寸
尺寸为 (batch_size, sequence_length, output_dim) 的 3D 张量。
4.参数
input_dim: int > 0。词汇表大小, 即,最大整数 index + 1。
output_dim: int >= 0。词向量的维度。
embeddings_initializer: embeddings 矩阵的初始化方法 (详见 initializers)。
embeddings_regularizer: embeddings matrix 的正则化方法 (详见 regularizer)。
embeddings_constraint: embeddings matrix 的约束函数 (详见 constraints)。
mask_zero:
是否把 0 看作为一个应该被遮蔽的特殊的 "padding" 值。这对于可变长的 循环神经网络层 十分有用。
如果设定为 True,那么接下来的所有层都必须支持 masking,否则就会抛出异常。
如果 mask_zero 为 True,作为结果,索引 0 就不能被用于词汇表中(input_dim 应该与 vocabulary + 1 大小相同)。
input_length:
输入序列的长度,当它是固定的时。 如果你需要连接 Flatten 和 Dense 层,
则这个参数是必须的 (没有它,dense 层的输出尺寸就无法计算)。
"""
"""
nn.Embedding演示
>>> embedding = nn.Embedding(10, 3)
>>> input = torch.LongTensor([[1,2,4,5],[4,3,2,9]])
>>> embedding(input)
tensor([[[-0.0251, -1.6902, 0.7172],
[-0.6431, 0.0748, 0.6969],
[ 1.4970, 1.3448, -0.9685],
[-0.3677, -2.7265, -0.1685]],
[[ 1.4970, 1.3448, -0.9685],
[ 0.4362, -0.4004, 0.9400],
[-0.6431, 0.0748, 0.6969],
[ 0.9124, -2.3616, 1.1151]]])
>>> embedding = nn.Embedding(10, 3, padding_idx=0)
>>> input = torch.LongTensor([[0,2,0,5]])
>>> embedding(input)
tensor([[[ 0.0000, 0.0000, 0.0000],
[ 0.1535, -2.0309, 0.9315],
[ 0.0000, 0.0000, 0.0000],
[-0.1655, 0.9897, 0.0635]]])
"""
import torch.nn as nn
import torch
embedding = nn.Embedding(10, 3)
x = torch.tensor([[0,1],[8,9]])
result = embedding(x)
# print(result)
# tensor([[[ 0.8168, -0.9338, -0.3293],
# [ 0.8791, 0.4560, 1.4109]],
# [[ 0.3612, 0.9988, -0.5982],
# [ 0.8584, -0.5055, 1.5467]]], grad_fn=<EmbeddingBackward>)
"""
设置padding_idx=某个单词对应的索引值
表示要将哪些tensor中的数字替换成0,一般padding_idx=0表示不进行替换。
传入Embedding的索引值某个单词对应的索引值在通过Embedding层之后,该索引值对应的输出权重值全为0.0000。
"""
embedding = nn.Embedding(10, 3, padding_idx=5)
x = torch.tensor([[0,1],[5,9]])
result = embedding(x)
print(result)
# tensor([[[ 1.1561, 0.2048, -0.1670],
# [-0.4186, -0.1274, -0.9189]],
# [[ 0.0000, 0.0000, 0.0000],
# [-1.3729, -0.5038, -0.5846]]], grad_fn=<EmbeddingBackward>)
"""
报错:RuntimeError: index out of range: Tried to access index 10 out of table with 9 rows.
报错意思可以翻译为:RuntimeError: index out of range: Tried to access index vocab词表大小 out of table with 单词索引值 rows.
分析:Embedding(词表大小, 词嵌入维度):
1.词表大小代表所有不重复单词数的总数大小,词嵌入维度代表一个单词对应的词嵌入维度的向量。
Embedding底层中会构建一个“词表大小*词嵌入维度”的二维权重矩阵,行数即为词表大小,列数即为词嵌入维度。
每个不重复的词的索引值对应二维权重矩阵中的一行,那么一个单词通过Embedding层输出后为该单词的索引对应那行的词嵌入维度向量,
也就是说把该单词的索引值对应的二维权重矩阵中的那一行的词嵌入维度向量取出来,作为该单词对应的权重值向量信息。
2.<start>和<end>同样一开始也是要放到词表WordToIndex和IndexToWord的两个字典中,<start>对应索引值0,<end>对应索引值1。
3.Embedding层要求传入的单词索引值必须在“0到词表大小减1”的这个范围之间,传入到Embedding层中的单词索引值大于或等于词表大小值的时候,
就会报错表示单词索引值超出了词表大小范围。
"""
# embedding = nn.Embedding(10, 3)
# x = torch.tensor([[0,1],[5,10]])
# result = embedding(x)
# print(result)
"""
nn.Dropout演示:
>>> m = nn.Dropout(p=0.2)
>>> input = torch.randn(4, 5)
>>> output = m(input)
>>> output
Variable containing:
0.0000 -0.5856 -1.4094 0.0000 -1.0290
2.0591 -1.3400 -1.7247 -0.9885 0.1286
0.5099 1.3715 0.0000 2.2079 -0.5497
-0.0000 -0.7839 -1.2434 -0.1222 1.2815
[torch.FloatTensor of size 4x5]
torch.unsqueeze演示:
>>> x = torch.tensor([1, 2, 3, 4])
>>> torch.unsqueeze(x, 0)
tensor([[ 1, 2, 3, 4]])
>>> torch.unsqueeze(x, 1)
tensor([[ 1],
[ 2],
[ 3],
[ 4]])
"""
import torch.nn as nn
import torch
import math
a = torch.arange(0., 10, 2)
print(a) #tensor([0., 2., 4., 6., 8.])
print(a.shape) #torch.Size([5])
#0.017988946039015984
print(math.log(10000.0) / 512)
#tensor([1.0000, 0.9647, 0.9306, 0.8977, 0.8660])
print(torch.exp(torch.arange(0., 10, 2) * -(math.log(10000.0) / 512)))
position = torch.arange(0., 10).unsqueeze(1)
print("position.shape",position.shape) #torch.Size([10, 1])
div_term = torch.exp(torch.arange(0., 512, 2) * -(math.log(10000.0) / 512))
""" e-01:10^(-1)即0.1"""
print("div_term:",div_term)
print("div_term.shape",div_term.shape) #torch.Size([5])
print((position * div_term).shape) #torch.Size([10, 5]) = [10, 1] * [5]
sin = torch.sin( position * div_term )
cos = torch.cos( position * div_term )
print(sin)
print(cos)
# tensor([[ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],
# [ 8.4147e-01, 8.2186e-01, 8.0196e-01, 7.8189e-01, 7.6172e-01],
# [ 9.0930e-01, 9.3641e-01, 9.5814e-01, 9.7489e-01, 9.8705e-01],
# [ 1.4112e-01, 2.4509e-01, 3.4278e-01, 4.3364e-01, 5.1731e-01],
# [-7.5680e-01, -6.5717e-01, -5.4861e-01, -4.3421e-01, -3.1672e-01],
# [-9.5892e-01, -9.9385e-01, -9.9823e-01, -9.7503e-01, -9.2771e-01],
# [-2.7942e-01, -4.7522e-01, -6.4403e-01, -7.8150e-01, -8.8542e-01],
# [ 6.5699e-01, 4.5239e-01, 2.2877e-01, 6.2483e-04, -2.1963e-01],
# [ 9.8936e-01, 9.9067e-01, 9.1736e-01, 7.8228e-01, 6.0082e-01],
# [ 4.1212e-01, 6.7637e-01, 8.6724e-01, 9.7475e-01, 9.9818e-01]])
# tensor([[ 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
# [ 0.5403, 0.5697, 0.5974, 0.6234, 0.6479],
# [-0.4161, -0.3509, -0.2863, -0.2227, -0.1604],
# [-0.9900, -0.9695, -0.9394, -0.9011, -0.8558],
# [-0.6536, -0.7537, -0.8361, -0.9008, -0.9485],
# [ 0.2837, 0.1107, -0.0595, -0.2221, -0.3733],
# [ 0.9602, 0.8799, 0.7650, 0.6239, 0.4648],
# [ 0.7539, 0.8918, 0.9735, 1.0000, 0.9756],
# [-0.1455, 0.1363, 0.3981, 0.6229, 0.7994],
# [-0.9111, -0.7366, -0.4979, -0.2233, 0.0603]])
# pe = torch.ones(10, 10)
# print(pe)
# pe[:, 0::2] = sin
# print(pe)
# pe[:, 1::2] = cos
# print(pe)
# 导入必备的工具包
import torch
import numpy as np
import matplotlib.pyplot as plt
# torch中变量封装函数Variable.
from torch.autograd import Variable
def subsequent_mask(size):
"""生成向后遮掩的掩码张量, 参数size是掩码张量最后两个维度的大小, 它的最后两维形成一个方阵"""
# 在函数中, 首先定义掩码张量的形状
attn_shape = (1, size, size)
# 然后使用np.ones方法向这个形状中添加1元素,形成上三角阵, 最后为了节约空间,
# 再使其中的数据类型变为无符号8位整形unit8
subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')
# 最后将numpy类型转化为torch中的tensor, 内部做一个1 - 的操作,
# 在这个其实是做了一个三角阵的反转, subsequent_mask中的每个元素都会被1减,
# 如果是0, subsequent_mask中的该位置由0变成1
# 如果是1, subsequent_mask中的该位置由1变成0
return torch.from_numpy(1 - subsequent_mask)
#输入实例:
# 生成的掩码张量的最后两维的大小
size = 5
#调用
mask = subsequent_mask(size)
print("mask:", mask)
# 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)
input = Variable(torch.randn(5, 5))
print("input:", input)
# input: tensor([[ 0.7355, -1.7779, -0.9368, 0.8789, 0.6941],
# [-1.3478, -0.8884, 0.1631, 1.3854, 0.1796],
# [-0.6873, -0.7180, 0.1878, 0.7216, 0.3455],
# [ 0.9504, -0.4424, 1.5504, 0.1826, -2.4970],
# [ 0.2762, 0.7762, 0.1749, -0.9793, -0.1307]])
scores = input.masked_fill(mask == 0, -1e9)
print("scores:", scores)
# scores: tensor([[[ 7.3554e-01, -1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09],
# [-1.3478e+00, -8.8837e-01, -1.0000e+09, -1.0000e+09, -1.0000e+09],
# [-6.8731e-01, -7.1800e-01, 1.8781e-01, -1.0000e+09, -1.0000e+09],
# [ 9.5039e-01, -4.4245e-01, 1.5504e+00, 1.8260e-01, -1.0000e+09],
# [ 2.7617e-01, 7.7617e-01, 1.7487e-01, -9.7926e-01, -1.3075e-01]]])
# scores = input.masked_fill(mask == 0, 100)
# print("scores:", scores)
# scores: tensor([[[ -0.6732, 100.0000, 100.0000, 100.0000, 100.0000],
# [ -0.5004, 0.2380, 100.0000, 100.0000, 100.0000],
# [ 0.6119, -0.3316, -1.3848, 100.0000, 100.0000],
# [ -0.2672, 0.3029, 0.8813, 1.5983, 100.0000],
# [ -0.3998, -0.9995, 2.9381, -0.4206, -0.7675]]])
# -*- coding: utf-8 -*-
import numpy as np
import torch
import torch.nn as nn
import time
from torch.autograd import Variable
def subsequent_mask(size):
"Mask out subsequent positions."
attn_shape = (1, size, size)
subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')
return torch.from_numpy(subsequent_mask) == 0
class Batch:
"Object for holding a batch of data with mask during training."
def __init__(self, src, trg=None, pad=0):
self.src = src
self.src_mask = (src != pad).unsqueeze(-2)
if trg is not None:
self.trg = trg[:, :-1]
self.trg_y = trg[:, 1:]
self.trg_mask = \
self.make_std_mask(self.trg, pad)
self.ntokens = (self.trg_y != pad).data.sum()
@staticmethod
def make_std_mask(tgt, pad):
"Create a mask to hide padding and future words."
tgt_mask = (tgt != pad).unsqueeze(-2)
tgt_mask = tgt_mask & Variable(
subsequent_mask(tgt.size(-1)).type_as(tgt_mask.data))
return tgt_mask
def run_epoch(data_iter, model, loss_compute):
"Standard Training and Logging Function"
start = time.time()
total_tokens = 0
total_loss = 0
tokens = 0
for i, batch in enumerate(data_iter):
out = model.forward(batch.src, batch.trg,
batch.src_mask, batch.trg_mask)
loss = loss_compute(out, batch.trg_y, batch.ntokens)
total_loss += loss
total_tokens += batch.ntokens
tokens += batch.ntokens
if i % 50 == 1:
elapsed = time.time() - start
print("Epoch Step: %d Loss: %f Tokens per Sec: %f" %
(i, loss / batch.ntokens, tokens / elapsed))
start = time.time()
tokens = 0
return total_loss / total_tokens
class NoamOpt:
"Optim wrapper that implements rate."
def __init__(self, model_size, factor, warmup, optimizer):
self.optimizer = optimizer
self._step = 0
self.warmup = warmup
self.factor = factor
self.model_size = model_size
self._rate = 0
def step(self):
"Update parameters and rate"
self._step += 1
rate = self.rate()
for p in self.optimizer.param_groups:
p['lr'] = rate
self._rate = rate
self.optimizer.step()
def rate(self, step = None):
"Implement `lrate` above"
if step is None:
step = self._step
return self.factor * \
(self.model_size ** (-0.5) *
min(step ** (-0.5), step * self.warmup ** (-1.5)))
def get_std_opt(model):
return NoamOpt(model.src_embed[0].d_model, 2, 4000,
torch.optim.Adam(model.parameters(), lr=0, betas=(0.9, 0.98), eps=1e-9))
class LabelSmoothing(nn.Module):
"Implement label smoothing."
def __init__(self, size, padding_idx, smoothing=0.0):
super(LabelSmoothing, self).__init__()
self.criterion = nn.KLDivLoss(size_average=False)
self.padding_idx = padding_idx
self.confidence = 1.0 - smoothing
self.smoothing = smoothing
self.size = size
self.true_dist = None
def forward(self, x, target):
assert x.size(1) == self.size
true_dist = x.data.clone()
true_dist.fill_(self.smoothing / (self.size - 2))
true_dist.scatter_(1, target.data.unsqueeze(1), self.confidence)
true_dist[:, self.padding_idx] = 0
mask = torch.nonzero(target.data == self.padding_idx)
if mask.dim() > 0:
true_dist.index_fill_(0, mask.squeeze(), 0.0)
self.true_dist = true_dist
return self.criterion(x, Variable(true_dist, requires_grad=False))
class SimpleLossCompute:
"A simple loss compute and train function."
def __init__(self, generator, criterion, opt=None):
self.generator = generator
self.criterion = criterion
self.opt = opt
def __call__(self, x, y, norm):
x = self.generator(x)
loss = self.criterion(x.contiguous().view(-1, x.size(-1)),
y.contiguous().view(-1)) / norm
loss.backward()
if self.opt is not None:
self.opt.step()
self.opt.optimizer.zero_grad()
# return loss.data[0] * norm
return loss.data.item() * norm
def greedy_decode(model, src, src_mask, max_len, start_symbol):
memory = model.encode(src, src_mask)
ys = torch.ones(1, 1).fill_(start_symbol).type_as(src.data)
for i in range(max_len-1):
out = model.decode(memory, src_mask,
Variable(ys),
Variable(subsequent_mask(ys.size(1))
.type_as(src.data)))
prob = model.generator(out[:, -1])
_, next_word = torch.max(prob, dim = 1)
next_word = next_word.data[0]
ys = torch.cat([ys,
torch.ones(1, 1).type_as(src.data).fill_(next_word)], dim=1)
return ys