它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内.它相当于以前所学习的BN层
如图所示,输入到每个子层以及规范化层的过程中,还使用了残差链接(也就是跳跃连接,比如说按照正常逻辑,第一层与第二层连接,第二层与第三层连接,即1-->2-->3,但是如果第一层跳过第二层直接与第三层连接,那么称为残差连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构),在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构.
多头注意力层+规范化层+ 残差连接
前馈全连接层+ 规范化层+残差连接
子层连接结构的代码分析:
掌握编码器层的实现过程.
作为编码器的组成单元, 每个编码器层完成一次对输入的特征提取过程, 即编码过程.
编码器用于对输入进行指定的特征提取过程, 也称为编码, 它由N个编码器层堆叠而成,编码器的输出成为解码器的输入的一部分.
编码器的结构图:它是由N个编码器层堆叠而成,如下图所示,这里的N=4
# 第一个实例化参数layer, 它是一个编码器层的实例化对象, 因此需要传入编码器层的参数
# 又因为编码器层中的子层是不共享的, 因此需要使用深度拷贝各个对象.
# 编码器中编码器层的个数N
由N个解码器层堆叠而成
每个解码器层由三个子层连接结构组成
第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
解码器层中的各个部分,如,多头注意力机制,规范化层,前馈全连接网络,子层连接结构都与编码器中的实现相同. 因此这里可以直接拿来构建解码器层.
作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程.
"""初始化函数的参数有5个, 分别是size,代表词嵌入的维度大小, 同时也代表解码器层的尺寸, 第二个是self_attn,多头自注意力机制对象,也就是说这个注意力机制需要Q=K=V, 第三个是src_attn,常规的多头注意力机制对象,这里Q!=K=V, 第四个是前馈全连接层对象,最后就是droupout置0比率. # 在初始化函数中, 主要就是将这些输入传到类中 # 按照结构图使用clones函数克隆三个子层连接对象. """forward函数中的参数有4个,分别是来自上一层的输入x, 来自编码器层的语义存储变量mermory(也就是编码器的输出), 以及源数据掩码张量和目标数据掩码张量. # 将memory表示成m方便之后使用 # 将x传入第一个子层结构(即多头自注意力机制子层结构),第一个子层结构的输入分别是x和self-attn函数,因为是自注意力机制,所以Q,K,V都是x, # 最后一个参数是目标数据掩码张量,这时要对目标数据进行遮掩,因为此时模型可能还没有生成任何目标数据, # 比如在解码器准备生成第一个字符或词汇时,我们其实已经传入了第一个字符以便计算损失, # 但是我们不希望在生成第一个字符时模型能利用这个信息(也就是说我们不希望在模型开始生成第一个字符前,模型已将提前知道了这个字符),因此我们会将其遮掩,同样生成第二个字符或词汇时, # 模型只能看见和使用第一个字符或词汇信息,第二个字符以及之后的信息都不允许被模型看到和使用. # 接着进入第二个子层,这个子层中采用常规的注意力机制(也就是说此时Q,K,V 不一定全都相等),q是输入x; k,v是编码层输出memory, # 同样也传入source_mask,但是进行源数据遮掩的原因跟对目标数据的遮掩的原因是不同的,它并非是抑制未来的信息泄漏,而是遮蔽掉对结果没有意义的字符而产生的注意力值(也就是说遮蔽掉对结果没有意义的字符而将注意力集中在有意义的词上面),比如说,对于一句话:“我爱美丽的中国”,我们要提取句子的主要部分,即"我爱中国",而屏蔽掉对结果没有意义的字符,比如"美丽"这个修饰词 # 以此提升模型效果和训练速度. 这样就完成了第二个子层的处理. # 最后一个子层就是前馈全连接子层,经过它的处理后就可以返回结果.这就是我们的解码器层结构.
# 类的实例化参数与解码器层类似, 相比多出了src_attn, 但是和self_attn是同一个类.
#为了便于演示,我们将自注意力机制和常规注意力机制都采用多头注意力机制
# 前馈全连接层也和之前相同
# x是来自目标数据的词嵌入表示, 但形式和源数据的词嵌入表示相同, 这里使用per充当.
# memory是来自编码器的输出
掌握解码器的实现过程.
根据编码器的结果以及上一次预测的结果(解码器是逐个字符进行解码), 对下一次可能出现的'值'进行特征表示.
解码器的结构图:它是由N个解码器层堆叠而成,如下图所示,这里的N=4
"""初始化函数的参数有两个,第一个就是解码器层layer,第二个是解码器层的个数N.""" # 首先使用clones方法克隆了N个layer,然后实例化了一个规范化层. # 因为数据走过了所有的解码器层后最后要做规范化处理. """forward函数中的参数有4个,x代表目标数据的嵌入表示(即上一层的输出结果),memory是编码器层的输出, # 然后就是对每个层进行循环,当然这个循环就是变量x通过每一个层的处理, # 得出最后的结果,再进行一次规范化返回即可.
# 分别是解码器层layer和解码器层的个数N
#为了便于演示,我们将自注意力机制和常规注意力机制都拷贝多头自注意力机制
# 输入参数与解码器层的输入参数相同
#memory是编码器层的输出
通过对上一步的线性变化得到指定维度的输出, 也就是转换维度的作用.
使最后一维的向量中的数字缩放到0-1的概率值域内, 并满足他们的和为1.
线性层和softmax层的代码分析:
# nn.functional工具包装载了网络层中那些只进行计算, 而没有参数的层
# 将线性层和softmax计算层一起实现, 因为二者的共同目标是生成最后的结构
# 首先就是使用nn中的预定义线性层进行实例化, 得到一个对象self.project等待使用,
# 这个线性层的参数有两个, 就是初始化函数传进来的两个参数: d_model(词嵌入维度), vocab_size(词表大小)
"""前向逻辑函数中输入是上一层的输出张量x"""
# 在函数中, 首先使用上一步得到的self.project对x进行线性变化,
# 然后使用F中已经实现的log_softmax对最后一个维度进行softmax处理这里的最后一个维度是 vocab_size(词表大小),对它进行softmax得到的是当讲预测的词输入词表中各个词的概率值,也就是说,经过softmax后,最后一个维度是vocab_size个概率值,我们选择概率最大的值所对应的词汇作为预测值
# 在这里之所以使用log_softmax是因为和我们这个pytorch版本的损失函数实现有关, 在其他版本中将修复.
# log_softmax就是对softmax的结果又取了对数, 因为对数函数是单调递增函数,
# 因此对最终我们取最大的概率值没有影响. 最后返回结果即可.
# 词嵌入维度是512维
# 输入x是上一层网络的输出, 我们使用来自解码器层的输出
通过上面的小节, 我们已经完成了所有组成部分的实现, 接下来就来实现完整的编码器-解码器结构.
编码器-解码器结构的代码实现
"""初始化函数中有5个参数, 分别是编码器对象, 解码器对象, 源数据嵌入函数, 目标数据嵌入函数, 以及输出部分的类别生成器对象 #定义源数据嵌入函数,它的作用是对源数据进行词嵌入 #定义目标数据嵌入函数,它的作用是对目标数据进行词嵌入
#将编码器实例化对象en赋值给encoder #将解码器实例化对象de赋值给decoder # 假设源数据与目标数据相同, 实际中并不相同
接着将基于以上结构构建用于训练的模型.
Tansformer模型构建过程的代码分析
"""该函数用来构建模型, 有7个参数,分别是源数据特征(词汇)总数,目标数据特征(词汇)总数, 编码器和解码器堆叠数,词嵌入维度,前馈全连接网络中变换矩阵的维度, 多头注意力结构中的多头数,以及置零比率dropout.""" # 首先得到一个深度拷贝命令,接下来很多结构都需要进行深度拷贝, # 来保证他们彼此之间相互独立,不受干扰. # 实例化了多头注意力类,得到对象attn # 然后实例化前馈全连接类,得到对象ff # 实例化位置编码类,得到对象position # 分别是编码器层,解码器层,源数据Embedding层和位置编码组成的有序结构, # 目标数据Embedding层和位置编码组成的有序结构,以及类别生成器层. # 在编码器层中有attention子层以及前馈全连接子层, # 在解码器层中有两个attention子层以及前馈全连接层. #这里的 nn.Sequential()中存放的是两个实例化对象,在程序执行过程中,它会按照先后顺序依次执行,本例中数据先通过词嵌入层,再通过位置编码层 # 模型结构完成后,接下来就是初始化模型中的参数,比如线性层中的变换矩阵 # 这里一但判断参数的维度大于1,则会将其初始化成一个服从均匀分布的矩阵,如果是单一的维度,就直接初始化为0
# 其他参数都使用默认值
# 根据Transformer结构图构建的最终模型结构
类的初始化函数传入5个参数, 分别是编码器对象, 解码器对象, 源数据嵌入函数, 目标数据嵌入函数, 以及输出部分的类别生成器对象.
有7个参数,分别是源数据特征(词汇)总数,目标数据特征(词汇)总数,编码器和解码器堆叠数,词向量映射维度,前馈全连接网络中变换矩阵的维度,多头注意力结构中的多头数,以及置零比率dropout.
该函数最后返回一个构建好的模型对象.
掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩,也可以说被替换, 它的表现形式是一个张量.
在transformer中, 掩码张量的主要作用在应用attention(将在下一小节讲解)时,有一些生成的attention张量中的值计算有可能已知了未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩. 关于解码器的有关知识将在后面的章节中讲解.
生成掩码张量的代码分析:
"""生成向后遮掩的掩码张量, 参数size是掩码张量最后两个维度的大小, 它的最后两维形成一个方阵"""
我们观察事物时,之所以能够快速判断一种事物(当然允许判断是错误的), 是因为我们大脑能够很快把注意力放在事物最具有辨识度的部分从而作出判断,而并非是从头到尾的观察一遍事物后,才能有判断结果. 正是基于这样的理论,就产生了注意力机制.
什么是注意力计算规则:
它需要三个指定的输入Q(query), K(key), V(value), 然后通过公式得到注意力的计算结果, 这个结果代表query在key和value作用下的表示. 而这个具体的计算规则有很多种, 我这里只介绍我们用到的这一种.
我们这里使用的注意力的计算规则:
假如我们有一个问题: 给出一段文本,使用一些关键词对它进行描述!
为了方便统一正确答案,这道题可能预先已经给大家写出了一些关键词作为提示.其中这些给出的提示就可以看作是key,
而整个的文本信息就相当于是query,value的含义则更抽象,可以比作是你看到这段文本信息后,脑子里浮现的答案信息,
这里我们又假设大家最开始都不是很聪明,第一次看到这段文本后脑子里基本上浮现的信息就只有提示这些信息,
因此key与value基本是相同的,但是随着我们对这个问题的深入理解,通过我们的思考脑子里想起来的东西原来越多,
并且能够开始对我们query也就是这段文本,提取关键信息进行表示. 这就是注意力作用的过程, 通过这个过程,
我们最终脑子里的value发生了变化,
根据提示key生成了query的关键词表示方法,也就是另外一种特征表示方法.
刚刚我们说到key和value一般情况下默认是相同,与query是不同的,这种是我们一般的注意力输入形式,
但有一种特殊情况,就是我们query与key和value相同,这种情况我们称为自注意力机制,就如同我们的刚刚的例子,
使用一般注意力机制,是使用不同于给定文本的关键词表示它. 而自注意力机制,
需要用给定文本自身来表达自己,也就是说你需要从给定文本中抽取关键词来表述它, 相当于对文本自身的一次特征提取.
注意力机制是注意力计算规则能够应用的深度学习网络的载体, 除了注意力计算规则外, 还包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使用自注意力计算规则的注意力机制称为自注意力机制.
注意力机制在网络中实现的图形表示:
注意力计算规则的代码分析:
带有mask的输入参数:
带有mask的输出效果:
什么是多头注意力机制:
从多头注意力的结构图中,貌似这个所谓的多个头就是指多组线性变换层,其实并不是,我只有使用了一组线性变化层,即三个变换张量对Q,K,V分别进行线性变换,这些变换不会改变原有张量的尺寸,因此每个变换矩阵都是方阵,得到输出结果后,多头的作用才开始显现,每个头开始从词义层面分割输出的张量,也就是每个头都想获得一组Q,K,V进行注意力机制的计算,但是句子中的每个词的表示只获得一部分,也就是只分割了最后一维的词嵌入向量. 这就是所谓的多头,将每个头的获得的输入送到注意力机制中, 就形成多头注意力机制.
多头注意力机制结构图:
多头注意力机制的作用:
这种结构设计能让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表达,实验表明可以从而提升模型效果.
"""用于生成相同网络层的克隆函数, 它的参数module表示要克隆的目标网络层, N代表需要克隆的数量"""
"""在类的初始化时, 会传入三个参数,head代表头数,embedding_dim代表词嵌入的维度,
"""前向逻辑函数, 它的输入参数有四个,前三个就是注意力机制需要的Q, K, V,
最后一个是注意力机制中可能需要的mask掩码张量,默认是None. """
在Transformer中前馈全连接层就是具有两层线性层的全连接网络.
考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力.
前馈全连接层的代码分析:
"""初始化函数有三个输入参数分别是d_model, d_ff,和dropout=0.1,第一个是线性层的输入维度也是第二个线性层的输出维度,
因为我们希望输入通过前馈全连接层后输入和输出的维度不变. 第二个参数d_ff就是第二个线性层的输入维度和第一个线性层的输出维度.
"""输入参数为x,代表来自上一层的输出"""
它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内.
"""初始化函数有两个参数, 一个是features, 表示词嵌入的维度,
另一个是eps它是一个足够小的数, 在规范化公式的分母中出现,
"""输入参数x代表来自上一层的输出"""
如图所示,输入到每个子层以及规范化层的过程中,还使用了残差链接(跳跃连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构),在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构.
子层连接结构的代码分析:
"""它输入参数有两个, size以及dropout, size一般是都是词嵌入维度的大小,
dropout本身是对模型结构中的节点数进行随机抑制的比率,
又因为节点被抑制等效就是该节点的输出都是0,因此也可以把dropout看作是对输出矩阵的随机置0的比率.
"""前向逻辑函数中, 接收上一个层或者子层的输入作为第一个参数,
将该子层连接中的子层函数作为第二个参数"""
作为编码器的组成单元, 每个编码器层完成一次对输入的特征提取过程, 即编码过程.
"""它的初始化函数参数有四个,分别是size,其实就是我们词嵌入维度的大小,它也将作为我们编码器层的大小,
第二个self_attn,之后我们将传入多头自注意力子层实例化对象, 并且是自注意力机制,
第三个是feed_froward, 之后我们将传入前馈全连接层实例化对象, 最后一个是置0比率dropout."""
"""forward函数中有两个输入参数,x和mask,分别代表上一层的输出,和掩码张量mask."""
编码器用于对输入进行指定的特征提取过程, 也称为编码, 由N个编码器层堆叠而成.
"""初始化函数的两个参数分别代表编码器层和编码器层的个数"""
"""forward函数的输入和编码器层相同, x代表上一层的输出, mask代表掩码张量"""