2.1 认识Transformer架构
学习目标
- 了解Transformer模型的作用.
- 了解Transformer总体架构图中各个组成部分的名称.
Transformer模型的作用
- 基于seq2seq架构的transformer模型可以完成NLP领域研究的典型任务, 如机器翻译, 文本生成等. 同时又可以构建预训练语言模型,用于不同任务的迁移学习.
- 声明:
- 在接下来的架构分析中, 我们将假设使用Transformer模型架构处理从一种语言文本到另一种语言文本的翻译工作, 因此很多命名方式遵循NLP中的规则. 比如: Embeddding层将称作文本嵌入层, Embedding层产生的张量称为词嵌入张量, 它的最后一维将称作词向量等.
Transformer总体架构图
- Transformer总体架构可分为四个部分:
- 输入部分
- 输出部分
- 编码器部分
- 解码器部分
- 输入部分包含:
- 源文本嵌入层及其位置编码器
- 目标文本嵌入层及其位置编码器
- 输出部分包含:
- 线性层
- softmax层
- 编码器部分:
- 由N个编码器层堆叠而成
- 每个编码器层由两个子层连接结构组成
- 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
- 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
- 解码器部分:
- 由N个解码器层堆叠而成
- 每个解码器层由三个子层连接结构组成
- 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
- 第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
- 第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
小节总结
学习了Transformer模型的作用:
- 基于seq2seq架构的transformer模型可以完成NLP领域研究的典型任务, 如机器翻译, 文本生成等. 同时又可以构建预训练语言模型,用于不同任务的迁移学习.
Transformer总体架构可分为四个部分:
- 输入部分
- 输出部分
- 编码器部分
- 解码器部分
输入部分包含:
- 源文本嵌入层及其位置编码器
- 目标文本嵌入层及其位置编码器
输出部分包含:
- 线性层
- softmax处理器
编码器部分:
- 由N个编码器层堆叠而成
- 每个编码器层由两个子层连接结构组成
- 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
- 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
解码器部分:
- 由N个解码器层堆叠而成
- 每个解码器层由三个子层连接结构组成
- 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
- 第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
- 第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
2.2 输入部分实现
学习目标
- 了解文本嵌入层和位置编码的作用.
- 掌握文本嵌入层和位置编码的实现过程.
- 输入部分包含:
- 源文本嵌入层及其位置编码器
- 目标文本嵌入层及其位置编码器
文本嵌入层的作用
- 无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示, 希望在这样的高维空间捕捉词汇间的关系.
- pytorch 0.3.0及其必备工具包的安装:
1 | # 使用pip安装的工具包包括pytorch-0.3.0, numpy, matplotlib, seaborn |
- 文本嵌入层的代码分析:
1 | # 导入必备的工具包 |
- nn.Embedding演示:
1 | >>> embedding = nn.Embedding(10, 3) |
- 实例化参数:
1 | # 词嵌入维度是512维 |
- 输入参数:
1 | # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4 |
- 调用:
1 | emb = Embeddings(d_model, vocab) |
- 输出效果:
1 | embr: Variable containing: |
位置编码器的作用
- 因为在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失.
- 位置编码器的代码分析:
1 | # 定义位置编码器类, 我们同样把它看做一个层, 因此会继承nn.Module |
- nn.Dropout演示:
1 | >>> m = nn.Dropout(p=0.2) |
- torch.unsqueeze演示:
1 | >>> x = torch.tensor([1, 2, 3, 4]) |
- 实例化参数:
1 | # 词嵌入维度是512维 |
- 输入参数:
1 | # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512 |
- 调用:
1 | pe = PositionalEncoding(d_model, dropout, max_len) |
- 输出效果:
1 | pe_result: Variable containing: |
- 绘制词汇向量中特征的分布曲线:
1 | import matplotlib.pyplot as plt |
- 输出效果:
- 效果分析:
- 每条颜色的曲线代表某一个词汇中的特征在不同位置的含义.
- 保证同一词汇随着所在位置不同它对应位置嵌入向量会发生变化.
- 正弦波和余弦波的值域范围都是1到-1这又很好的控制了嵌入数值的大小, 有助于梯度的快速计算.
小节总结
学习了文本嵌入层的作用:
- 无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示, 希望在这样的高维空间捕捉词汇间的关系.
学习并实现了文本嵌入层的类: Embeddings
- 初始化函数以d_model, 词嵌入维度, 和vocab, 词汇总数为参数, 内部主要使用了nn中的预定层Embedding进行词嵌入.
- 在forward函数中, 将输入x传入到Embedding的实例化对象中, 然后乘以一个根号下d_model进行缩放, 控制数值大小.
- 它的输出是文本嵌入后的结果.
学习了位置编码器的作用:
- 因为在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失.
学习并实现了位置编码器的类: PositionalEncoding
- 初始化函数以d_model, dropout, max_len为参数, 分别代表d_model: 词嵌入维度, dropout: 置0比率, max_len: 每个句子的最大长度.
- forward函数中的输入参数为x, 是Embedding层的输出.
- 最终输出一个加入了位置编码信息的词嵌入张量.
实现了绘制词汇向量中特征的分布曲线:
- 保证同一词汇随着所在位置不同它对应位置嵌入向量会发生变化.
- 正弦波和余弦波的值域范围都是1到-1, 这又很好的控制了嵌入数值的大小, 有助于梯度的快速计算.
2.3 编码器部分实现
学习目标
- 了解编码器中各个组成部分的作用.
- 掌握编码器中各个组成部分的实现过程.
- 编码器部分:
- 由N个编码器层堆叠而成
- 每个编码器层由两个子层连接结构组成
- 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
- 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
2.3.1 掩码张量
- 学习目标:
- 了解什么是掩码张量以及它的作用.
- 掌握生成掩码张量的实现过程.
- 什么是掩码张量:
- 掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩,也可以说被替换, 它的表现形式是一个张量.
- 掩码张量的作用:
- 在transformer中, 掩码张量的主要作用在应用attention(将在下一小节讲解)时,有一些生成的attention张量中的值计算有可能已知了未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩. 关于解码器的有关知识将在后面的章节中讲解.
- 生成掩码张量的代码分析:
1 | def subsequent_mask(size): |
- np.triu演示:
1 | >>> np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]], k=-1) |
- 输入实例:
1 | # 生成的掩码张量的最后两维的大小 |
- 调用:
1 | sm = subsequent_mask(size) |
- 输出效果:
1 | # 最后两维形成一个下三角阵 |
- 掩码张量的可视化:
1 | plt.figure(figsize=(5,5)) |
- 输出效果:
- 效果分析:
- 通过观察可视化方阵, 黄色是1的部分, 这里代表被遮掩, 紫色代表没有被遮掩的信息, 横坐标代表目标词汇的位置, 纵坐标代表可查看的位置;
- 我们看到, 在0的位置我们一看望过去都是黄色的, 都被遮住了,1的位置一眼望过去还是黄色, 说明第一次词还没有产生, 从第二个位置看过去, 就能看到位置1的词, 其他位置看不到, 以此类推.
2.3.1 掩码张量总结:
- 学习了什么是掩码张量:
- 掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩, 也可以说被替换, 它的表现形式是一个张量.
- 学习了掩码张量的作用:
- 在transformer中, 掩码张量的主要作用在应用attention(将在下一小节讲解)时,有一些生成的attetion张量中的值计算有可能已知量未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩. 关于解码器的有关知识将在后面的章节中讲解.
- 学习并实现了生成向后遮掩的掩码张量函数: subsequent_mask
- 它的输入是size, 代表掩码张量的大小.
- 它的输出是一个最后两维形成1方阵的下三角阵.
- 最后对生成的掩码张量进行了可视化分析, 更深一步理解了它的用途.
- 学习了什么是掩码张量:
2.3.2 注意力机制
- 学习目标:
- 了解什么是注意力计算规则和注意力机制.
- 掌握注意力计算规则的实现过程.
- 什么是注意力:
- 我们观察事物时,之所以能够快速判断一种事物(当然允许判断是错误的), 是因为我们大脑能够很快把注意力放在事物最具有辨识度的部分从而作出判断,而并非是从头到尾的观察一遍事物后,才能有判断结果. 正是基于这样的理论,就产生了注意力机制.
- 什么是注意力计算规则:
- 它需要三个指定的输入Q(query), K(key), V(value), 然后通过公式得到注意力的计算结果, 这个结果代表query在key和value作用下的表示. 而这个具体的计算规则有很多种, 我这里只介绍我们用到的这一种.
- 我们这里使用的注意力的计算规则:
- Q, K, V的比喻解释:
1 | 假如我们有一个问题: 给出一段文本,使用一些关键词对它进行描述! |
- 什么是注意力机制:
- 注意力机制是注意力计算规则能够应用的深度学习网络的载体, 除了注意力计算规则外, 还包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使用自注意力计算规则的注意力机制称为自注意力机制.
- 注意力机制在网络中实现的图形表示:
- 注意力计算规则的代码分析:
1 | def attention(query, key, value, mask=None, dropout=None): |
- tensor.masked_fill演示:
1 | >>> input = Variable(torch.randn(5, 5)) |
- 输入参数:
1 | # 我们令输入的query, key, value都相同, 位置编码的输出 |
- 调用:
1 | attn, p_attn = attention(query, key, value) |
- 输出效果:
1 | # 将得到两个结果 |
- 带有mask的输入参数:
1 | query = key = value = pe_result |
- 调用:
1 | attn, p_attn = attention(query, key, value, mask=mask) |
- 带有mask的输出效果:
1 | # query的注意力表示: |
2.3.2 注意力机制总结:
- 学习了什么是注意力:
- 我们观察事物时,之所以能够快速判断一种事物(当然允许判断是错误的), 是因为我们大脑能够很快把注意力放在事物最具有辨识度的部分从而作出判断,而并非是从头到尾的观察一遍事物后,才能有判断结果. 正是基于这样的理论,就产生了注意力机制.
- 什么是注意力计算规则:
- 它需要三个指定的输入Q(query), K(key), V(value), 然后通过公式得到注意力的计算结果, 这个结果代表query在key和value作用下的表示. 而这个具体的计算规则有很多种, 我这里只介绍我们用到的这一种.
- 学习了Q, K, V的比喻解释:
- Q是一段准备被概括的文本; K是给出的提示; V是大脑中的对提示K的延伸.
- 当Q=K=V时, 称作自注意力机制.
- 什么是注意力机制:
- 注意力机制是注意力计算规则能够应用的深度学习网络的载体, 除了注意力计算规则外, 还包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使用自注意力计算规则的注意力机制称为自注意力机制.
- 学习并实现了注意力计算规则的函数: attention
- 它的输入就是Q,K,V以及mask和dropout, mask用于掩码, dropout用于随机置0.
- 它的输出有两个, query的注意力表示以及注意力张量.
- 学习了什么是注意力:
2.3.3 多头注意力机制
- 学习目标:
- 了解多头注意力机制的作用.
- 掌握多头注意力机制的实现过程.
- 什么是多头注意力机制:
- 从多头注意力的结构图中,貌似这个所谓的多个头就是指多组线性变换层,其实并不是,我只有使用了一组线性变化层,即三个变换张量对Q,K,V分别进行线性变换,这些变换不会改变原有张量的尺寸,因此每个变换矩阵都是方阵,得到输出结果后,多头的作用才开始显现,每个头开始从词义层面分割输出的张量,也就是每个头都想获得一组Q,K,V进行注意力机制的计算,但是句子中的每个词的表示只获得一部分,也就是只分割了最后一维的词嵌入向量. 这就是所谓的多头,将每个头的获得的输入送到注意力机制中, 就形成多头注意力机制.
- 多头注意力机制结构图:
- 多头注意力机制的作用:
- 这种结构设计能让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表达,实验表明可以从而提升模型效果.
- 多头注意力机制的代码实现:
1 | # 用于深度拷贝的copy工具包 |
- tensor.view演示:
1 | x = torch.randn(4, 4) |
- torch.transpose演示:
1 | >>> x = torch.randn(2, 3) |
- 实例化参数:
1 | # 头数head |
- 输入参数:
1 | # 假设输入的Q,K,V仍然相等 |
- 调用:
1 | mha = MultiHeadedAttention(head, embedding_dim, dropout) |
- 输出效果:
1 | tensor([[[-0.3075, 1.5687, -2.5693, ..., -1.1098, 0.0878, -3.3609], |
2.3.3 多头注意力机制总结:
- 学习了什么是多头注意力机制:
- 每个头开始从词义层面分割输出的张量,也就是每个头都想获得一组Q,K,V进行注意力机制的计算,但是句子中的每个词的表示只获得一部分,也就是只分割了最后一维的词嵌入向量. 这就是所谓的多头.将每个头的获得的输入送到注意力机制中, 就形成了多头注意力机制.
- 学习了多头注意力机制的作用:
- 这种结构设计能让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表达,实验表明可以从而提升模型效果.
- 学习并实现了多头注意力机制的类: MultiHeadedAttention
- 因为多头注意力机制中需要使用多个相同的线性层, 首先实现了克隆函数clones.
- clones函数的输入是module,N,分别代表克隆的目标层,和克隆个数.
- clones函数的输出是装有N个克隆层的Module列表.
- 接着实现MultiHeadedAttention类, 它的初始化函数输入是h, d_model, dropout分别代表头数,词嵌入维度和置零比率.
- 它的实例化对象输入是Q, K, V以及掩码张量mask.
- 它的实例化对象输出是通过多头注意力机制处理的Q的注意力表示.
- 学习了什么是多头注意力机制:
2.3.4 前馈全连接层
- 学习目标:
- 了解什么是前馈全连接层及其它的作用.
- 掌握前馈全连接层的实现过程.
- 什么是前馈全连接层:
- 在Transformer中前馈全连接层就是具有两层线性层的全连接网络.
- 前馈全连接层的作用:
- 考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力.
- 前馈全连接层的代码分析:
1 | # 通过类PositionwiseFeedForward来实现前馈全连接层 |
- ReLU函数公式: ReLU(x)=max(0, x)
- ReLU函数图像:
- 实例化参数:
1 | d_model = 512 |
- 输入参数:
1 | # 输入参数x可以是多头注意力机制的输出 |
- 调用:
1 | ff = PositionwiseFeedForward(d_model, d_ff, dropout) |
- 输出效果:
1 | tensor([[[-1.9488e+00, -3.4060e-01, -1.1216e+00, ..., 1.8203e-01, |
2.3.4 前馈全连接层总结:
- 学习了什么是前馈全连接层:
- 在Transformer中前馈全连接层就是具有两层线性层的全连接网络.
- 学习了前馈全连接层的作用:
- 考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力.
- 学习并实现了前馈全连接层的类: PositionwiseFeedForward
- 它的实例化参数为d_model, d_ff, dropout, 分别代表词嵌入维度, 线性变换维度, 和置零比率.
- 它的输入参数x, 表示上层的输出.
- 它的输出是经过2层线性网络变换的特征表示.
- 学习了什么是前馈全连接层:
2.3.5 规范化层
- 学习目标:
- 了解规范化层的作用.
- 掌握规范化层的实现过程.
- 规范化层的作用:
- 它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内.
- 规范化层的代码实现:
1 | # 通过LayerNorm实现规范化层的类 |
- 实例化参数:
1 | features = d_model = 512 |
- 输入参数:
1 | # 输入x来自前馈全连接层的输出 |
- 调用:
1 | ln = LayerNorm(feature, eps) |
- 输出效果:
1 | tensor([[[ 2.2697, 1.3911, -0.4417, ..., 0.9937, 0.6589, -1.1902], |
2.3.5 规范化层总结:
- 学习了规范化层的作用:
- 它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内.
- 学习并实现了规范化层的类: LayerNorm
- 它的实例化参数有两个, features和eps,分别表示词嵌入特征大小,和一个足够小的数.
- 它的输入参数x代表来自上一层的输出.
- 它的输出就是经过规范化的特征表示.
- 学习了规范化层的作用:
2.3.6 子层连接结构
- 学习目标:
- 了解什么是子层连接结构.
- 掌握子层连接结构的实现过程.
- 什么是子层连接结构:
- 如图所示,输入到每个子层以及规范化层的过程中,还使用了残差链接(跳跃连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构),在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构.
- 子层连接结构图:
- 子层连接结构的代码分析:
1 | # 使用SublayerConnection来实现子层连接结构的类 |
- 实例化参数
1 | size = 512 |
- 输入参数:
1 | # 令x为位置编码器的输出 |
- 调用:
1 | sc = SublayerConnection(size, dropout) |
- 输出效果:
1 | tensor([[[ 14.8830, 22.4106, -31.4739, ..., 21.0882, -10.0338, -0.2588], |
2.3.6 子层连接结构总结:
- 什么是子层连接结构:
- 如图所示,输入到每个子层以及规范化层的过程中,还使用了残差链接(跳跃连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构), 在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构.
- 学习并实现了子层连接结构的类: SublayerConnection
- 类的初始化函数输入参数是size, dropout, 分别代表词嵌入大小和置零比率.
- 它的实例化对象输入参数是x, sublayer, 分别代表上一层输出以及子层的函数表示.
- 它的输出就是通过子层连接结构处理的输出.
- 什么是子层连接结构:
2.3.7 编码器层
- 学习目标:
- 了解编码器层的作用.
- 掌握编码器层的实现过程.
- 编码器层的作用:
- 作为编码器的组成单元, 每个编码器层完成一次对输入的特征提取过程, 即编码过程.
- 编码器层的构成图:
- 编码器层的代码分析:
1 | # 使用EncoderLayer类实现编码器层 |
- 实例化参数:
1 | size = 512 |
- 调用:
1 | el = EncoderLayer(size, self_attn, ff, dropout) |
- 输出效果:
1 | tensor([[[ 33.6988, -30.7224, 20.9575, ..., 5.2968, -48.5658, 20.0734], |
2.3.7 编码器层总结:
- 学习了编码器层的作用:
- 作为编码器的组成单元, 每个编码器层完成一次对输入的特征提取过程, 即编码过程.
- 学习并实现了编码器层的类: EncoderLayer
- 类的初始化函数共有4个, 别是size,其实就是我们词嵌入维度的大小. 第二个self_attn,之后我们将传入多头自注意力子层实例化对象, 并且是自注意力机制. 第三个是feed_froward, 之后我们将传入前馈全连接层实例化对象. 最后一个是置0比率dropout.
- 实例化对象的输入参数有2个,x代表来自上一层的输出, mask代表掩码张量.
- 它的输出代表经过整个编码层的特征表示.
- 学习了编码器层的作用:
2.3.8 编码器
- 学习目标:
- 了解编码器的作用.
- 掌握编码器的实现过程.
- 编码器的作用:
- 编码器用于对输入进行指定的特征提取过程, 也称为编码, 由N个编码器层堆叠而成.
- 编码器的结构图:
- 编码器的代码分析:
1 | # 使用Encoder类来实现编码器 |
- 实例化参数:
1 | # 第一个实例化参数layer, 它是一个编码器层的实例化对象, 因此需要传入编码器层的参数 |
- 调用:
1 | en = Encoder(layer, N) |
- 输出效果:
1 | tensor([[[-0.2081, -0.3586, -0.2353, ..., 2.5646, -0.2851, 0.0238], |
2.3.8 编码器总结:
- 学习了编码器的作用:
- 编码器用于对输入进行指定的特征提取过程, 也称为编码, 由N个编码器层堆叠而成.
- 学习并实现了编码器的类: Encoder
- 类的初始化函数参数有两个,分别是layer和N,代表编码器层和编码器层的个数.
- forward函数的输入参数也有两个, 和编码器层的forward相同, x代表上一层的输出, mask代码掩码张量.
- 编码器类的输出就是Transformer中编码器的特征提取表示, 它将成为解码器的输入的一部分.
- 学习了编码器的作用:
2.4 解码器部分实现
学习目标
- 了解解码器中各个组成部分的作用.
- 掌握解码器中各个组成部分的实现过程.
- 解码器部分:
- 由N个解码器层堆叠而成
- 每个解码器层由三个子层连接结构组成
- 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
- 第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
- 第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
- 说明:
- 解码器层中的各个部分,如,多头注意力机制,规范化层,前馈全连接网络,子层连接结构都与编码器中的实现相同. 因此这里可以直接拿来构建解码器层.
2.4.1 解码器层
- 学习目标:
- 了解解码器层的作用.
- 掌握解码器层的实现过程.
- 解码器层的作用:
- 作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程.
- 解码器层的代码实现:
1 | # 使用DecoderLayer的类实现解码器层 |
- 实例化参数:
1 | # 类的实例化参数与解码器层类似, 相比多出了src_attn, 但是和self_attn是同一个类. |
- 输入参数:
1 | # x是来自目标数据的词嵌入表示, 但形式和源数据的词嵌入表示相同, 这里使用per充当. |
- 调用:
1 | dl = DecoderLayer(size, self_attn, src_attn, ff, dropout) |
- 输出效果:
1 | tensor([[[ 1.9604e+00, 3.9288e+01, -5.2422e+01, ..., 2.1041e-01, |
2.4.1 解码器层总结:
- 学习了解码器层的作用:
- 作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程.
- 学习并实现了解码器层的类: DecoderLayer
- 类的初始化函数的参数有5个, 分别是size,代表词嵌入的维度大小, 同时也代表解码器层的尺寸,第二个是self_attn,多头自注意力对象,也就是说这个注意力机制需要Q=K=V,第三个是src_attn,多头注意力对象,这里Q!=K=V, 第四个是前馈全连接层对象,最后就是droupout置0比率.
- forward函数的参数有4个,分别是来自上一层的输入x,来自编码器层的语义存储变量mermory, 以及源数据掩码张量和目标数据掩码张量.
- 最终输出了由编码器输入和目标数据一同作用的特征提取结果.
- 学习了解码器层的作用:
2.4.2 解码器
- 学习目标:
- 了解解码器的作用.
- 掌握解码器的实现过程.
- 解码器的作用:
- 根据编码器的结果以及上一次预测的结果, 对下一次可能出现的’值’进行特征表示.
- 解码器的代码分析:
1 | # 使用类Decoder来实现解码器 |
- 实例化参数:
1 | # 分别是解码器层layer和解码器层的个数N |
- 输入参数:
1 | # 输入参数与解码器层的输入参数相同 |
- 调用:
1 | de = Decoder(layer, N) |
- 输出效果:
1 | tensor([[[ 0.9898, -0.3216, -1.2439, ..., 0.7427, -0.0717, -0.0814], |
2.4.2 解码器总结:
- 学习了解码器的作用:
- 根据编码器的结果以及上一次预测的结果, 对下一次可能出现的’值’进行特征表示.
- 学习并实现了解码器的类: Decoder
- 类的初始化函数的参数有两个,第一个就是解码器层layer,第二个是解码器层的个数N.
- forward函数中的参数有4个,x代表目标数据的嵌入表示,memory是编码器层的输出,src_mask, tgt_mask代表源数据和目标数据的掩码张量.
- 输出解码过程的最终特征表示.
- 学习了解码器的作用:
2.5 输出部分实现
学习目标
- 了解线性层和softmax的作用.
- 掌握线性层和softmax的实现过程.
- 输出部分包含:
- 线性层
- softmax层
线性层的作用
- 通过对上一步的线性变化得到指定维度的输出, 也就是转换维度的作用.
softmax层的作用
- 使最后一维的向量中的数字缩放到0-1的概率值域内, 并满足他们的和为1.
- 线性层和softmax层的代码分析:
1 | # nn.functional工具包装载了网络层中那些只进行计算, 而没有参数的层 |
- nn.Linear演示:
1 | m = nn.Linear(20, 30) |
- 实例化参数:
1 | # 词嵌入维度是512维 |
- 输入参数:
1 | # 输入x是上一层网络的输出, 我们使用来自解码器层的输出 |
- 调用:
1 | gen = Generator(d_model, vocab_size) |
- 输出效果:
1 | tensor([[[-7.8098, -7.5260, -6.9244, ..., -7.6340, -6.9026, -7.5232], |
小节总结
学习了输出部分包含:
- 线性层
- softmax层
线性层的作用:
- 通过对上一步的线性变化得到指定维度的输出, 也就是转换维度的作用.
softmax层的作用:
- 使最后一维的向量中的数字缩放到0-1的概率值域内, 并满足他们的和为1.
学习并实现了线性层和softmax层的类: Generator
- 初始化函数的输入参数有两个, d_model代表词嵌入维度, vocab_size代表词表大小.
- forward函数接受上一层的输出.
- 最终获得经过线性层和softmax层处理的结果.
2.6 模型构建
学习目标
- 掌握编码器-解码器结构的实现过程.
- 掌握Transformer模型的构建过程.
- 通过上面的小节, 我们已经完成了所有组成部分的实现, 接下来就来实现完整的编码器-解码器结构.
- Transformer总体架构图:
编码器-解码器结构的代码实现
1 | # 使用EncoderDecoder类来实现编码器-解码器结构 |
- 实例化参数
1 | vocab_size = 1000 |
- 输入参数:
1 | # 假设源数据与目标数据相同, 实际中并不相同 |
- 调用:
1 | ed = EncoderDecoder(encoder, decoder, source_embed, target_embed, generator) |
- 输出效果:
1 | tensor([[[ 0.2102, -0.0826, -0.0550, ..., 1.5555, 1.3025, -0.6296], |
- 接着将基于以上结构构建用于训练的模型.
Tansformer模型构建过程的代码分析
1 | def make_model(source_vocab, target_vocab, N=6, |
- nn.init.xavier_uniform演示:
1 | # 结果服从均匀分布U(-a, a) |
- 输入参数:
1 | source_vocab = 11 |
- 调用:
1 | if __name__ == '__main__': |
- 输出效果:
1 | # 根据Transformer结构图构建的最终模型结构 |
小节总结
学习并实现了编码器-解码器结构的类: EncoderDecoder
- 类的初始化函数传入5个参数, 分别是编码器对象, 解码器对象, 源数据嵌入函数, 目标数据嵌入函数, 以及输出部分的类别生成器对象.
- 类中共实现三个函数, forward, encode, decode
- forward是主要逻辑函数, 有四个参数, source代表源数据, target代表目标数据, source_mask和target_mask代表对应的掩码张量.
- encode是编码函数, 以source和source_mask为参数.
- decode是解码函数, 以memory即编码器的输出, source_mask, target, target_mask为参数
学习并实现了模型构建函数: make_model
- 有7个参数,分别是源数据特征(词汇)总数,目标数据特征(词汇)总数,编码器和解码器堆叠数,词向量映射维度,前馈全连接网络中变换矩阵的维度,多头注意力结构中的多头数,以及置零比率dropout.
- 该函数最后返回一个构建好的模型对象.
2.7 模型基本测试运行
学习目标
- 了解Transformer模型基本测试的copy任务.
- 掌握实现copy任务的四步曲.
- 我们将通过一个小的copy任务完成模型的基本测试工作.
- copy任务介绍:
- 任务描述: 针对数字序列进行学习, 学习的最终目标是使输出与输入的序列相同. 如输入[1, 5, 8, 9, 3], 输出也是[1, 5, 8, 9, 3].
- 任务意义: copy任务在模型基础测试中具有重要意义,因为copy操作对于模型来讲是一条明显规律, 因此模型能否在短时间内,小数据集中学会它,可以帮助我们断定模型所有过程是否正常,是否已具备基本学习能力.
使用copy任务进行模型基本测试的四步曲
- 第一步: 构建数据集生成器
- 第二步: 获得Transformer模型及其优化器和损失函数
- 第三步: 运行模型进行训练和评估
- 第四步: 使用模型进行贪婪解码
- 第一步: 构建数据集生成器
1 | # 导入工具包Batch, 它能够对原始样本数据生成对应批次的掩码张量 |
- 输入参数:
1 | # 将生成0-10的整数 |
- 调用:
1 | if __name__ == '__main__': |
- 输出效果:
1 | # 会得到一个数据生成器(生成器对象) |
- 第二步: 获得Transformer模型及其优化器和损失函数
1 | # 导入优化器工具包get_std_opt, 该工具用于获得标准的针对Transformer模型的优化器 |
- 标签平滑示例:
1 | from pyitcast.transformer_utils import LabelSmoothing |
- 标签平滑图像:
- 标签平滑图像分析:
- 我们目光集中在黄色小方块上, 它相对于横坐标横跨的值域就是标签平滑后的正向平滑值域, 我们可以看到大致是从0.5到2.5.
- 它相对于纵坐标横跨的值域就是标签平滑后的负向平滑值域, 我们可以看到大致是从-0.5到1.5, 总的值域空间由原来的[0, 2]变成了[-0.5, 2.5].
- 第三步: 运行模型进行训练和评估
1 | # 导入模型单轮训练工具包run_epoch, 该工具将对模型使用给定的损失函数计算方法进行单轮参数更新. |
- 输入参数:
1 | # 进行10轮训练 |
- 输出效果:
1 | Epoch Step: 1 Loss: 3.315704 Tokens per Sec: 309.740843 |
- 第四步: 使用模型进行贪婪解码
1 | # 导入贪婪解码工具包greedy_decode, 该工具将对最终结进行贪婪解码 |
- 输出效果:
1 | 1 3 2 5 4 6 7 8 9 10 |
小节总结
学习了copy任务的相关知识:
- 任务描述: 针对数字序列进行学习, 学习的最终目标是使输出与输入的序列相同. 如输入[1, 5, 8, 9, 3], 输出也是[1, 5, 8, 9, 3].
- 任务意义: copy任务在模型基础测试中具有重要意义,因为copy操作对于模型来讲是一条明显规律, 因此模型能否在短时间内,小数据集中学会它,可以帮助我们断定模型所有过程是否正常,是否已具备基本学习能力.
学习了使用copy任务进行模型基本测试的四步曲:
- 第一步: 构建数据集生成器
- 第二步: 获得Transformer模型及其优化器和损失函数
- 第三步: 运行模型进行训练和评估
- 第四步: 使用模型进行贪婪解码
学习并实现了构建数据集生成器函数: data_gen
- 它有三个输入参数, 分别是V: 随机生成数字的最大值+1, batch: 每次输送给模型更新一次参数的数据量, nbatches: 一共输送nbatches次完成一轮.
- 该函数最终得到一个生成器对象.
学习了获得Transformer模型及其优化器和损失函数:
- 通过导入优化器工具包get_std_opt, 获得标准优化器.
- 通过导入标签平滑工具包LabelSmoothing, 进行标签平滑.
- 通过导入损失计算工具包SimpleLossCompute, 计算损失.
学习并实现了运行模型进行训练和评估函数: run
- 在函数中导入模型单轮训练工具包run_epoch, 对模型进行单轮训练.
- 函数共有三个参数, model代表将要进行训练的模型, slc代表使用的损失计算方法, epochs代表模型训练的轮数.
- 函数最终打印了模型训练和评估两个过程的损失.
学习并实现了使用模型进行贪婪解码:
- 通过导入贪婪解码工具包greedy_decode, 根据输入得到最后输出, 完成了copy任务.