IT评测·应用市场-qidao123.com

标题: 【深度学习与NLP】——Transformer架构解析 [打印本页]

作者: 勿忘初心做自己    时间: 2024-8-30 21:49
标题: 【深度学习与NLP】——Transformer架构解析
目次
第一章:Transformer背景介绍
1.1 Transformer的诞生
1.2 Transformer的优势
1.3 Transformer的市场
第二章:Transformer架构解析
2.1 熟悉Transformer架构
2.1.1 Transformer模子的作用
2.1.2 Transformer总体架构图
2.2 输入部分实现
2.2.1 文本嵌入层的作用
2.2.2 位置编码器的作用
2.3 编码器部分实现
2.3.1 掩码张量
2.3.2 注意力机制
2.3.3 多头注意力机制
2.3.4 前馈全毗连层
2.3.5 规范化层
2.3.6 子层毗连结构
2.3.7 编码器层
2.3.8 编码器
2.4 解码器部分实现
学习目标
2.4.1 解码器层
2.4.2 解码器
2.5 输出部分实现
2.5.1 线性层的作用
2.5.2 softmax层的作用
2.6 模子构建
2.6.1 编码器-解码器结构的代码实现
2.6.2 Tansformer模子构建过程的代码分析

第一章:Transformer背景介绍

1.1 Transformer的诞生


2018年10月,Google发出一篇论文《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》, BERT模子横空出世, 并横扫NLP范畴11项任务的最佳成绩!

论文地址: https://arxiv.org/pdf/1810.04805.pdf

而在BERT中发挥重要作用的结构就是Transformer, 之后又相继出现XLNET,roBERT等模子击败了BERT,但是他们的核心没有变,仍旧是:Transformer.

1.2 Transformer的优势


相比之前占领市场的LSTM和GRU模子,Transformer有两个显著的优势:
  1. 1, Transformer能够利用分布式GPU进行并行训练,提升模型训练效率.   
  2. 2, 在分析预测更长的文本时, 捕捉间隔较长的语义关联效果更好.   
复制代码

下面是一张在测评比较图:


1.3 Transformer的市场


在著名的SOTA机器翻译榜单上, 几乎所有排名靠前的模子都使用Transformer,

其基本上可以看作是工业界的风向标, 市场空间自然不必多说!
第二章:Transformer架构解析

2.1 熟悉Transformer架构


学习目标


2.1.1 Transformer模子的作用





2.1.2 Transformer总体架构图

















小节总结




2.2 输入部分实现


学习目标




2.2.1 文本嵌入层的作用




  1. # 使用pip安装的工具包包括pytorch-0.3.0, numpy, matplotlib, seaborn
  2. pip install http://download.pytorch.org/whl/cu80/torch-0.3.0.post4-cp36-cp36m-linux_x86_64.whl numpy matplotlib seaborn
  3. # MAC系统安装, python版本<=3.6
  4. pip install torch==0.3.0.post4 numpy matplotlib seaborn
复制代码


  1. # 导入必备的工具包
  2. import torch
  3. # 预定义的网络层torch.nn, 工具开发者已经帮助我们开发好的一些常用层,
  4. # 比如,卷积层, lstm层, embedding层等, 不需要我们再重新造轮子.
  5. import torch.nn as nn
  6. # 数学计算工具包
  7. import math
  8. # torch中变量封装函数Variable.
  9. from torch.autograd import Variable
  10. # 定义Embeddings类来实现文本嵌入层,这里s说明代表两个一模一样的嵌入层, 他们共享参数.
  11. # 该类继承nn.Module, 这样就有标准层的一些功能, 这里我们也可以理解为一种模式, 我们自己实现的所有层都会这样去写.
  12. class Embeddings(nn.Module):
  13.     def __init__(self, d_model, vocab):
  14.         """类的初始化函数, 有两个参数, d_model: 指词嵌入的维度, vocab: 指词表的大小."""
  15.         # 接着就是使用super的方式指明继承nn.Module的初始化函数, 我们自己实现的所有层都会这样去写.
  16.         super(Embeddings, self).__init__()
  17.         # 之后就是调用nn中的预定义层Embedding, 获得一个词嵌入对象self.lut
  18.         self.lut = nn.Embedding(vocab, d_model)
  19.         # 最后就是将d_model传入类中
  20.         self.d_model = d_model
  21.     def forward(self, x):
  22.         """可以将其理解为该层的前向传播逻辑,所有层中都会有此函数
  23.            当传给该类的实例化对象参数时, 自动调用该类函数
  24.            参数x: 因为Embedding层是首层, 所以代表输入给模型的文本通过词汇映射后的张量"""
  25.         # 将x传给self.lut并与根号下self.d_model相乘作为结果返回
  26.         return self.lut(x) * math.sqrt(self.d_model)
复制代码

  1. >>> embedding = nn.Embedding(10, 3)
  2. >>> input = torch.LongTensor([[1,2,4,5],[4,3,2,9]])
  3. >>> embedding(input)
  4. tensor([[[-0.0251, -1.6902,  0.7172],
  5.          [-0.6431,  0.0748,  0.6969],
  6.          [ 1.4970,  1.3448, -0.9685],
  7.          [-0.3677, -2.7265, -0.1685]],
  8.         [[ 1.4970,  1.3448, -0.9685],
  9.          [ 0.4362, -0.4004,  0.9400],
  10.          [-0.6431,  0.0748,  0.6969],
  11.          [ 0.9124, -2.3616,  1.1151]]])
  12. >>> embedding = nn.Embedding(10, 3, padding_idx=0)
  13. >>> input = torch.LongTensor([[0,2,0,5]])
  14. >>> embedding(input)
  15. tensor([[[ 0.0000,  0.0000,  0.0000],
  16.          [ 0.1535, -2.0309,  0.9315],
  17.          [ 0.0000,  0.0000,  0.0000],
  18.          [-0.1655,  0.9897,  0.0635]]])
复制代码

   
  1. # 词嵌入维度是512维
  2. d_model = 512
  3. # 词表大小是1000
  4. vocab = 1000
复制代码
  
  1. # 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
  2. x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
复制代码

   
  1. emb = Embeddings(d_model, vocab)
  2. embr = emb(x)
  3. print("embr:", embr)
复制代码

   
  1. embr: Variable containing:
  2. ( 0 ,.,.) =
  3.   35.9321   3.2582 -17.7301  ...    3.4109  13.8832  39.0272
  4.    8.5410  -3.5790 -12.0460  ...   40.1880  36.6009  34.7141
  5. -17.0650  -1.8705 -20.1807  ...  -12.5556 -34.0739  35.6536
  6.   20.6105   4.4314  14.9912  ...   -0.1342  -9.9270  28.6771
  7. ( 1 ,.,.) =
  8.   27.7016  16.7183  46.6900  ...   17.9840  17.2525  -3.9709
  9.    3.0645  -5.5105  10.8802  ...  -13.0069  30.8834 -38.3209
  10.   33.1378 -32.1435  -3.9369  ...   15.6094 -29.7063  40.1361
  11. -31.5056   3.3648   1.4726  ...    2.8047  -9.6514 -23.4909
  12. [torch.FloatTensor of size 2x4x512]
复制代码

2.2.2 位置编码器的作用




  1. # 定义位置编码器类, 我们同样把它看做一个层, 因此会继承nn.Module   
  2. class PositionalEncoding(nn.Module):
  3.     def __init__(self, d_model, dropout, max_len=5000):
  4.         """位置编码器类的初始化函数, 共有三个参数, 分别是d_model: 词嵌入维度,
  5.            dropout: 置0比率, max_len: 每个句子的最大长度"""
  6.         super(PositionalEncoding, self).__init__()
  7.         # 实例化nn中预定义的Dropout层, 并将dropout传入其中, 获得对象self.dropout
  8.         self.dropout = nn.Dropout(p=dropout)
  9.         # 初始化一个位置编码矩阵, 它是一个0阵,矩阵的大小是max_len x d_model.
  10.         pe = torch.zeros(max_len, d_model)
  11.         # 初始化一个绝对位置矩阵, 在我们这里,词汇的绝对位置就是用它的索引去表示.
  12.         # 所以我们首先使用arange方法获得一个连续自然数向量,然后再使用unsqueeze方法拓展向量维度使其成为矩阵,
  13.         # 又因为参数传的是1,代表矩阵拓展的位置,会使向量变成一个max_len x 1 的矩阵,
  14.         position = torch.arange(0, max_len).unsqueeze(1)
  15.         # 绝对位置矩阵初始化之后,接下来就是考虑如何将这些位置信息加入到位置编码矩阵中,
  16.         # 最简单思路就是先将max_len x 1的绝对位置矩阵, 变换成max_len x d_model形状,然后覆盖原来的初始位置编码矩阵即可,
  17.         # 要做这种矩阵变换,就需要一个1xd_model形状的变换矩阵div_term,我们对这个变换矩阵的要求除了形状外,
  18.         # 还希望它能够将自然数的绝对位置编码缩放成足够小的数字,有助于在之后的梯度下降过程中更快的收敛.  这样我们就可以开始初始化这个变换矩阵了.
  19.         # 首先使用arange获得一个自然数矩阵, 但是细心的同学们会发现, 我们这里并没有按照预计的一样初始化一个1xd_model的矩阵,
  20.         # 而是有了一个跳跃,只初始化了一半即1xd_model/2 的矩阵。 为什么是一半呢,其实这里并不是真正意义上的初始化了一半的矩阵,
  21.         # 我们可以把它看作是初始化了两次,而每次初始化的变换矩阵会做不同的处理,第一次初始化的变换矩阵分布在正弦波上, 第二次初始化的变换矩阵分布在余弦波上,
  22.         # 并把这两个矩阵分别填充在位置编码矩阵的偶数和奇数位置上,组成最终的位置编码矩阵.
  23.         div_term = torch.exp(torch.arange(0, d_model, 2) *
  24.                              -(math.log(10000.0) / d_model))
  25.         pe[:, 0::2] = torch.sin(position * div_term)
  26.         pe[:, 1::2] = torch.cos(position * div_term)
  27.         # 这样我们就得到了位置编码矩阵pe, pe现在还只是一个二维矩阵,要想和embedding的输出(一个三维张量)相加,
  28.         # 就必须拓展一个维度,所以这里使用unsqueeze拓展维度.
  29.         pe = pe.unsqueeze(0)
  30.         # 最后把pe位置编码矩阵注册成模型的buffer,什么是buffer呢,
  31.         # 我们把它认为是对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不需要随着优化步骤进行更新的增益对象.
  32.         # 注册之后我们就可以在模型保存后重加载时和模型结构与参数一同被加载.
  33.         self.register_buffer('pe', pe)
  34.     def forward(self, x):
  35.         """forward函数的参数是x, 表示文本序列的词嵌入表示"""
  36.         # 在相加之前我们对pe做一些适配工作, 将这个三维张量的第二维也就是句子最大长度的那一维将切片到与输入的x的第二维相同即x.size(1),
  37.         # 因为我们默认max_len为5000一般来讲实在太大了,很难有一条句子包含5000个词汇,所以要进行与输入张量的适配.
  38.         # 最后使用Variable进行封装,使其与x的样式相同,但是它是不需要进行梯度求解的,因此把requires_grad设置成false.
  39.         x = x + Variable(self.pe[:, :x.size(1)],
  40.                          requires_grad=False)
  41.         # 最后使用self.dropout对象进行'丢弃'操作, 并返回结果.
  42.         return self.dropout(x)
复制代码

  1. >>> m = nn.Dropout(p=0.2)
  2. >>> input = torch.randn(4, 5)
  3. >>> output = m(input)
  4. >>> output
  5. Variable containing:
  6. 0.0000 -0.5856 -1.4094  0.0000 -1.0290
  7. 2.0591 -1.3400 -1.7247 -0.9885  0.1286
  8. 0.5099  1.3715  0.0000  2.2079 -0.5497
  9. -0.0000 -0.7839 -1.2434 -0.1222  1.2815
  10. [torch.FloatTensor of size 4x5]
复制代码

  1. >>> x = torch.tensor([1, 2, 3, 4])
  2. >>> torch.unsqueeze(x, 0)
  3. tensor([[ 1,  2,  3,  4]])
  4. >>> torch.unsqueeze(x, 1)
  5. tensor([[ 1],
  6.         [ 2],
  7.         [ 3],
  8.         [ 4]])
复制代码

   
  1. # 词嵌入维度是512维
  2. d_model = 512
  3. # 置0比率为0.1
  4. dropout = 0.1
  5. # 句子最大长度
  6. max_len=60
复制代码

   
  1. # 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
  2. x = embr
  3. Variable containing:
  4. ( 0 ,.,.) =
  5.   35.9321   3.2582 -17.7301  ...    3.4109  13.8832  39.0272
  6.    8.5410  -3.5790 -12.0460  ...   40.1880  36.6009  34.7141
  7. -17.0650  -1.8705 -20.1807  ...  -12.5556 -34.0739  35.6536
  8.   20.6105   4.4314  14.9912  ...   -0.1342  -9.9270  28.6771
  9. ( 1 ,.,.) =
  10.   27.7016  16.7183  46.6900  ...   17.9840  17.2525  -3.9709
  11.    3.0645  -5.5105  10.8802  ...  -13.0069  30.8834 -38.3209
  12.   33.1378 -32.1435  -3.9369  ...   15.6094 -29.7063  40.1361
  13. -31.5056   3.3648   1.4726  ...    2.8047  -9.6514 -23.4909
  14. [torch.FloatTensor of size 2x4x512]
复制代码

   
  1. pe = PositionalEncoding(d_model, dropout, max_len)
  2. pe_result = pe(x)
  3. print("pe_result:", pe_result)
复制代码

   
  1. pe_result: Variable containing:
  2. ( 0 ,.,.) =
  3. -19.7050   0.0000   0.0000  ...  -11.7557  -0.0000  23.4553
  4.   -1.4668 -62.2510  -2.4012  ...   66.5860 -24.4578 -37.7469
  5.    9.8642 -41.6497 -11.4968  ...  -21.1293 -42.0945  50.7943
  6.    0.0000  34.1785 -33.0712  ...   48.5520   3.2540  54.1348
  7. ( 1 ,.,.) =
  8.    7.7598 -21.0359  15.0595  ...  -35.6061  -0.0000   4.1772
  9. -38.7230   8.6578  34.2935  ...  -43.3556  26.6052   4.3084
  10.   24.6962  37.3626 -26.9271  ...   49.8989   0.0000  44.9158
  11. -28.8435 -48.5963  -0.9892  ...  -52.5447  -4.1475  -3.0450
  12. [torch.FloatTensor of size 2x4x512]
复制代码


  1. import matplotlib.pyplot as plt
  2. # 创建一张15 x 5大小的画布
  3. plt.figure(figsize=(15, 5))
  4. # 实例化PositionalEncoding类得到pe对象, 输入参数是20和0
  5. pe = PositionalEncoding(20, 0)
  6. # 然后向pe传入被Variable封装的tensor, 这样pe会直接执行forward函数,
  7. # 且这个tensor里的数值都是0, 被处理后相当于位置编码张量
  8. y = pe(Variable(torch.zeros(1, 100, 20)))
  9. # 然后定义画布的横纵坐标, 横坐标到100的长度, 纵坐标是某一个词汇中的某维特征在不同长度下对应的值
  10. # 因为总共有20维之多, 我们这里只查看4,5,6,7维的值.
  11. plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())
  12. # 在画布上填写维度提示信息
  13. plt.legend(["dim %d"%p for p in [4,5,6,7]])
复制代码
  
  

   
  
小节总结




2.3 编码器部分实现


学习目标




2.3.1 掩码张量










  1. def subsequent_mask(size):
  2.     """生成向后遮掩的掩码张量, 参数size是掩码张量最后两个维度的大小, 它的最后两维形成一个方阵"""
  3.     # 在函数中, 首先定义掩码张量的形状
  4.     attn_shape = (1, size, size)
  5.     # 然后使用np.ones方法向这个形状中添加1元素,形成上三角阵, 最后为了节约空间,
  6.     # 再使其中的数据类型变为无符号8位整形unit8
  7.     subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')
  8.     # 最后将numpy类型转化为torch中的tensor, 内部做一个1 - 的操作,
  9.     # 在这个其实是做了一个三角阵的反转, subsequent_mask中的每个元素都会被1减,
  10.     # 如果是0, subsequent_mask中的该位置由0变成1
  11.     # 如果是1, subsequent_mask中的该位置由1变成0
  12.     return torch.from_numpy(1 - subsequent_mask)
复制代码


  1. >>> np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]], k=-1)
  2. array([[ 1,  2,  3],
  3.        [ 4,  5,  6],
  4.        [ 0,  8,  9],
  5.        [ 0,  0, 12]])
  6. >>> np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]], k=0)
  7. array([[ 1,  2,  3],
  8.        [ 0,  5,  6],
  9.        [ 0,  0,  9],
  10.        [ 0,  0, 0]])
  11. >>> np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]], k=1)
  12. array([[ 0,  2,  3],
  13.        [ 0,  0,  6],
  14.        [ 0,  0,  0],
  15.        [ 0,  0, 0]])
复制代码

   
  1. # 生成的掩码张量的最后两维的大小
  2. size = 5
复制代码

   
  1. sm = subsequent_mask(size)
  2. print("sm:", sm)
复制代码

   
  1. # 最后两维形成一个下三角阵
  2. sm: (0 ,.,.) =
  3.   1  0  0  0  0
  4.   1  1  0  0  0
  5.   1  1  1  0  0
  6.   1  1  1  1  0
  7.   1  1  1  1  1
  8. [torch.ByteTensor of size 1x5x5]
复制代码


  1. plt.figure(figsize=(5,5))
  2. plt.imshow(subsequent_mask(20)[0])
复制代码

   
  


   
  



2.3.2 注意力机制












  1. 假如我们有一个问题: 给出一段文本,使用一些关键词对它进行描述!
  2. 为了方便统一正确答案,这道题可能预先已经给大家写出了一些关键词作为提示.其中这些给出的提示就可以看作是key,
  3. 而整个的文本信息就相当于是query,value的含义则更抽象,可以比作是你看到这段文本信息后,脑子里浮现的答案信息,
  4. 这里我们又假设大家最开始都不是很聪明,第一次看到这段文本后脑子里基本上浮现的信息就只有提示这些信息,
  5. 因此key与value基本是相同的,但是随着我们对这个问题的深入理解,通过我们的思考脑子里想起来的东西原来越多,
  6. 并且能够开始对我们query也就是这段文本,提取关键信息进行表示.  这就是注意力作用的过程, 通过这个过程,
  7. 我们最终脑子里的value发生了变化,
  8. 根据提示key生成了query的关键词表示方法,也就是另外一种特征表示方法.
  9. 刚刚我们说到key和value一般情况下默认是相同,与query是不同的,这种是我们一般的注意力输入形式,
  10. 但有一种特殊情况,就是我们query与key和value相同,这种情况我们称为自注意力机制,就如同我们的刚刚的例子,
  11. 使用一般注意力机制,是使用不同于给定文本的关键词表示它. 而自注意力机制,
  12. 需要用给定文本自身来表达自己,也就是说你需要从给定文本中抽取关键词来表述它, 相当于对文本自身的一次特征提取.
复制代码







  1. def attention(query, key, value, mask=None, dropout=None):
  2.     """注意力机制的实现, 输入分别是query, key, value, mask: 掩码张量,
  3.        dropout是nn.Dropout层的实例化对象, 默认为None"""
  4.     # 在函数中, 首先取query的最后一维的大小, 一般情况下就等同于我们的词嵌入维度, 命名为d_k
  5.     d_k = query.size(-1)
  6.     # 按照注意力公式, 将query与key的转置相乘, 这里面key是将最后两个维度进行转置, 再除以缩放系数根号下d_k, 这种计算方法也称为缩放点积注意力计算.
  7.     # 得到注意力得分张量scores
  8.     scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
  9.     # 接着判断是否使用掩码张量
  10.     if mask is not None:
  11.         # 使用tensor的masked_fill方法, 将掩码张量和scores张量每个位置一一比较, 如果掩码张量处为0
  12.         # 则对应的scores张量用-1e9这个值来替换, 如下演示
  13.         scores = scores.masked_fill(mask == 0, -1e9)
  14.     # 对scores的最后一维进行softmax操作, 使用F.softmax方法, 第一个参数是softmax对象, 第二个是目标维度.
  15.     # 这样获得最终的注意力张量
  16.     p_attn = F.softmax(scores, dim = -1)
  17.     # 之后判断是否使用dropout进行随机置0
  18.     if dropout is not None:
  19.         # 将p_attn传入dropout对象中进行'丢弃'处理
  20.         p_attn = dropout(p_attn)
  21.     # 最后, 根据公式将p_attn与value张量相乘获得最终的query注意力表示, 同时返回注意力张量
  22.     return torch.matmul(p_attn, value), p_attn
复制代码

  1. >>> input = Variable(torch.randn(5, 5))
  2. >>> input
  3. Variable containing:
  4. 2.0344 -0.5450  0.3365 -0.1888 -2.1803
  5. 1.5221 -0.3823  0.8414  0.7836 -0.8481
  6. -0.0345 -0.8643  0.6476 -0.2713  1.5645
  7. 0.8788 -2.2142  0.4022  0.1997  0.1474
  8. 2.9109  0.6006 -0.6745 -1.7262  0.6977
  9. [torch.FloatTensor of size 5x5]
  10. >>> mask = Variable(torch.zeros(5, 5))
  11. >>> mask
  12. Variable containing:
  13. 0  0  0  0  0
  14. 0  0  0  0  0
  15. 0  0  0  0  0
  16. 0  0  0  0  0
  17. 0  0  0  0  0
  18. [torch.FloatTensor of size 5x5]
  19. >>> input.masked_fill(mask == 0, -1e9)
  20. Variable containing:
  21. -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
  22. -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
  23. -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
  24. -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
  25. -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
  26. [torch.FloatTensor of size 5x5]
复制代码

   
  1. # 我们令输入的query, key, value都相同, 位置编码的输出
  2. query = key = value = pe_result
  3. Variable containing:
  4. ( 0 ,.,.) =
  5.   46.5196  16.2057 -41.5581  ...  -16.0242 -17.8929 -43.0405
  6. -32.6040  16.1096 -29.5228  ...    4.2721  20.6034  -1.2747
  7. -18.6235  14.5076  -2.0105  ...   15.6462 -24.6081 -30.3391
  8.    0.0000 -66.1486 -11.5123  ...   20.1519  -4.6823   0.4916
  9. ( 1 ,.,.) =
  10. -24.8681   7.5495  -5.0765  ...   -7.5992 -26.6630  40.9517
  11.   13.1581  -3.1918 -30.9001  ...   25.1187 -26.4621   2.9542
  12. -49.7690 -42.5019   8.0198  ...   -5.4809  25.9403 -27.4931
  13. -52.2775  10.4006   0.0000  ...   -1.9985   7.0106  -0.5189
  14. [torch.FloatTensor of size 2x4x512]
复制代码


  1. attn, p_attn = attention(query, key, value)
  2. print("attn:", attn)
  3. print("p_attn:", p_attn)
复制代码

   
  1. # 将得到两个结果
  2. # query的注意力表示:
  3. attn: Variable containing:
  4. ( 0 ,.,.) =
  5.    12.8269    7.7403   41.2225  ...     1.4603   27.8559  -12.2600
  6.    12.4904    0.0000   24.1575  ...     0.0000    2.5838   18.0647
  7.   -32.5959   -4.6252  -29.1050  ...     0.0000  -22.6409  -11.8341
  8.     8.9921  -33.0114   -0.7393  ...     4.7871   -5.7735    8.3374
  9. ( 1 ,.,.) =
  10.   -25.6705   -4.0860  -36.8226  ...    37.2346  -27.3576    2.5497
  11.   -16.6674   73.9788  -33.3296  ...    28.5028   -5.5488  -13.7564
  12.     0.0000  -29.9039   -3.0405  ...     0.0000   14.4408   14.8579
  13.    30.7819    0.0000   21.3908  ...   -29.0746    0.0000   -5.8475
  14. [torch.FloatTensor of size 2x4x512]
  15. # 注意力张量:
  16. p_attn: Variable containing:
  17. (0 ,.,.) =
  18.   1  0  0  0
  19.   0  1  0  0
  20.   0  0  1  0
  21.   0  0  0  1
  22. (1 ,.,.) =
  23.   1  0  0  0
  24.   0  1  0  0
  25.   0  0  1  0
  26.   0  0  0  1
  27. [torch.FloatTensor of size 2x4x4]
复制代码

  1. query = key = value = pe_result
  2. # 令mask为一个2x4x4的零张量
  3. mask = Variable(torch.zeros(2, 4, 4))
复制代码

   
  1. attn, p_attn = attention(query, key, value, mask=mask)
  2. print("attn:", attn)
  3. print("p_attn:", p_attn)
复制代码

   
  1. # query的注意力表示:
  2. attn: Variable containing:
  3. ( 0 ,.,.) =
  4.    0.4284  -7.4741   8.8839  ...    1.5618   0.5063   0.5770
  5.    0.4284  -7.4741   8.8839  ...    1.5618   0.5063   0.5770
  6.    0.4284  -7.4741   8.8839  ...    1.5618   0.5063   0.5770
  7.    0.4284  -7.4741   8.8839  ...    1.5618   0.5063   0.5770
  8. ( 1 ,.,.) =
  9.   -2.8890   9.9972 -12.9505  ...    9.1657  -4.6164  -0.5491
  10.   -2.8890   9.9972 -12.9505  ...    9.1657  -4.6164  -0.5491
  11.   -2.8890   9.9972 -12.9505  ...    9.1657  -4.6164  -0.5491
  12.   -2.8890   9.9972 -12.9505  ...    9.1657  -4.6164  -0.5491
  13. [torch.FloatTensor of size 2x4x512]
  14. # 注意力张量:
  15. p_attn: Variable containing:
  16. (0 ,.,.) =
  17.   0.2500  0.2500  0.2500  0.2500
  18.   0.2500  0.2500  0.2500  0.2500
  19.   0.2500  0.2500  0.2500  0.2500
  20.   0.2500  0.2500  0.2500  0.2500
  21. (1 ,.,.) =
  22.   0.2500  0.2500  0.2500  0.2500
  23.   0.2500  0.2500  0.2500  0.2500
  24.   0.2500  0.2500  0.2500  0.2500
  25.   0.2500  0.2500  0.2500  0.2500
  26. [torch.FloatTensor of size 2x4x4]
复制代码




2.3.3 多头注意力机制












  1. # 用于深度拷贝的copy工具包
  2. import copy
  3. # 首先需要定义克隆函数, 因为在多头注意力机制的实现中, 用到多个结构相同的线性层.
  4. # 我们将使用clone函数将他们一同初始化在一个网络层列表对象中. 之后的结构中也会用到该函数.
  5. def clones(module, N):
  6.     """用于生成相同网络层的克隆函数, 它的参数module表示要克隆的目标网络层, N代表需要克隆的数量"""
  7.     # 在函数中, 我们通过for循环对module进行N次深度拷贝, 使其每个module成为独立的层,
  8.     # 然后将其放在nn.ModuleList类型的列表中存放.
  9.     return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
  10. # 我们使用一个类来实现多头注意力机制的处理
  11. class MultiHeadedAttention(nn.Module):
  12.     def __init__(self, head, embedding_dim, dropout=0.1):
  13.         """在类的初始化时, 会传入三个参数,head代表头数,embedding_dim代表词嵌入的维度,
  14.            dropout代表进行dropout操作时置0比率,默认是0.1."""
  15.         super(MultiHeadedAttention, self).__init__()
  16.         # 在函数中,首先使用了一个测试中常用的assert语句,判断h是否能被d_model整除,
  17.         # 这是因为我们之后要给每个头分配等量的词特征.也就是embedding_dim/head个.
  18.         assert embedding_dim % head == 0
  19.         # 得到每个头获得的分割词向量维度d_k
  20.         self.d_k = embedding_dim // head
  21.         # 传入头数h
  22.         self.head = head
  23.         # 然后获得线性层对象,通过nn的Linear实例化,它的内部变换矩阵是embedding_dim x embedding_dim,然后使用clones函数克隆四个,
  24.         # 为什么是四个呢,这是因为在多头注意力中,Q,K,V各需要一个,最后拼接的矩阵还需要一个,因此一共是四个.
  25.         self.linears = clones(nn.Linear(embedding_dim, embedding_dim), 4)
  26.         # self.attn为None,它代表最后得到的注意力张量,现在还没有结果所以为None.
  27.         self.attn = None
  28.         # 最后就是一个self.dropout对象,它通过nn中的Dropout实例化而来,置0比率为传进来的参数dropout.
  29.         self.dropout = nn.Dropout(p=dropout)
  30.     def forward(self, query, key, value, mask=None):
  31.         """前向逻辑函数, 它的输入参数有四个,前三个就是注意力机制需要的Q, K, V,
  32.            最后一个是注意力机制中可能需要的mask掩码张量,默认是None. """
  33.         # 如果存在掩码张量mask
  34.         if mask is not None:
  35.             # 使用unsqueeze拓展维度
  36.             mask = mask.unsqueeze(0)
  37.         # 接着,我们获得一个batch_size的变量,他是query尺寸的第1个数字,代表有多少条样本.
  38.         batch_size = query.size(0)
  39.         # 之后就进入多头处理环节
  40.         # 首先利用zip将输入QKV与三个线性层组到一起,然后使用for循环,将输入QKV分别传到线性层中,
  41.         # 做完线性变换后,开始为每个头分割输入,这里使用view方法对线性变换的结果进行维度重塑,多加了一个维度h,代表头数,
  42.         # 这样就意味着每个头可以获得一部分词特征组成的句子,其中的-1代表自适应维度,
  43.         # 计算机会根据这种变换自动计算这里的值.然后对第二维和第三维进行转置操作,
  44.         # 为了让代表句子长度维度和词向量维度能够相邻,这样注意力机制才能找到词义与句子位置的关系,
  45.         # 从attention函数中可以看到,利用的是原始输入的倒数第一和第二维.这样我们就得到了每个头的输入.
  46.         query, key, value = \
  47.            [model(x).view(batch_size, -1, self.head, self.d_k).transpose(1, 2)
  48.             for model, x in zip(self.linears, (query, key, value))]
  49.         # 得到每个头的输入后,接下来就是将他们传入到attention中,
  50.         # 这里直接调用我们之前实现的attention函数.同时也将mask和dropout传入其中.
  51.         x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout)
  52.         # 通过多头注意力计算后,我们就得到了每个头计算结果组成的4维张量,我们需要将其转换为输入的形状以方便后续的计算,
  53.         # 因此这里开始进行第一步处理环节的逆操作,先对第二和第三维进行转置,然后使用contiguous方法,
  54.         # 这个方法的作用就是能够让转置后的张量应用view方法,否则将无法直接使用,
  55.         # 所以,下一步就是使用view重塑形状,变成和输入形状相同.
  56.         x = x.transpose(1, 2).contiguous().view(batch_size, -1, self.head * self.d_k)
  57.         # 最后使用线性层列表中的最后一个线性层对输入进行线性变换得到最终的多头注意力结构的输出.
  58.         return self.linears[-1](x)
复制代码


  1. >>> x = torch.randn(4, 4)
  2. >>> x.size()
  3. torch.Size([4, 4])
  4. >>> y = x.view(16)
  5. >>> y.size()
  6. torch.Size([16])
  7. >>> z = x.view(-1, 8)  # the size -1 is inferred from other dimensions
  8. >>> z.size()
  9. torch.Size([2, 8])
  10. >>> a = torch.randn(1, 2, 3, 4)
  11. >>> a.size()
  12. torch.Size([1, 2, 3, 4])
  13. >>> b = a.transpose(1, 2)  # Swaps 2nd and 3rd dimension
  14. >>> b.size()
  15. torch.Size([1, 3, 2, 4])
  16. >>> c = a.view(1, 3, 2, 4)  # Does not change tensor layout in memory
  17. >>> c.size()
  18. torch.Size([1, 3, 2, 4])
  19. >>> torch.equal(b, c)
  20. False
复制代码


  1. >>> x = torch.randn(2, 3)
  2. >>> x
  3. tensor([[ 1.0028, -0.9893,  0.5809],
  4.         [-0.1669,  0.7299,  0.4942]])
  5. >>> torch.transpose(x, 0, 1)
  6. tensor([[ 1.0028, -0.1669],
  7.         [-0.9893,  0.7299],
  8.         [ 0.5809,  0.4942]])
复制代码

   
  1. # 头数head
  2. head = 8
  3. # 词嵌入维度embedding_dim
  4. embedding_dim = 512
  5. # 置零比率dropout
  6. dropout = 0.2
复制代码

   
  1. # 假设输入的Q,K,V仍然相等
  2. query = value = key = pe_result
  3. # 输入的掩码张量mask
  4. mask = Variable(torch.zeros(8, 4, 4))
复制代码

   
  1. mha = MultiHeadedAttention(head, embedding_dim, dropout)
  2. mha_result = mha(query, key, value, mask)
  3. print(mha_result)
复制代码

   
  1. tensor([[[-0.3075,  1.5687, -2.5693,  ..., -1.1098,  0.0878, -3.3609],
  2.          [ 3.8065, -2.4538, -0.3708,  ..., -1.5205, -1.1488, -1.3984],
  3.          [ 2.4190,  0.5376, -2.8475,  ...,  1.4218, -0.4488, -0.2984],
  4.          [ 2.9356,  0.3620, -3.8722,  ..., -0.7996,  0.1468,  1.0345]],
  5.         [[ 1.1423,  0.6038,  0.0954,  ...,  2.2679, -5.7749,  1.4132],
  6.          [ 2.4066, -0.2777,  2.8102,  ...,  0.1137, -3.9517, -2.9246],
  7.          [ 5.8201,  1.1534, -1.9191,  ...,  0.1410, -7.6110,  1.0046],
  8.          [ 3.1209,  1.0008, -0.5317,  ...,  2.8619, -6.3204, -1.3435]]],
  9.        grad_fn=<AddBackward0>)
  10. torch.Size([2, 4, 512])
复制代码




2.3.4 前馈全毗连层









  1. # 通过类PositionwiseFeedForward来实现前馈全连接层
  2. class PositionwiseFeedForward(nn.Module):
  3.     def __init__(self, d_model, d_ff, dropout=0.1):
  4.         """初始化函数有三个输入参数分别是d_model, d_ff,和dropout=0.1,第一个是线性层的输入维度也是第二个线性层的输出维度,
  5.            因为我们希望输入通过前馈全连接层后输入和输出的维度不变. 第二个参数d_ff就是第二个线性层的输入维度和第一个线性层的输出维度.
  6.            最后一个是dropout置0比率."""
  7.         super(PositionwiseFeedForward, self).__init__()
  8.         # 首先按照我们预期使用nn实例化了两个线性层对象,self.w1和self.w2
  9.         # 它们的参数分别是d_model, d_ff和d_ff, d_model
  10.         self.w1 = nn.Linear(d_model, d_ff)
  11.         self.w2 = nn.Linear(d_ff, d_model)
  12.         # 然后使用nn的Dropout实例化了对象self.dropout
  13.         self.dropout = nn.Dropout(dropout)
  14.     def forward(self, x):
  15.         """输入参数为x,代表来自上一层的输出"""
  16.         # 首先经过第一个线性层,然后使用Funtional中relu函数进行激活,
  17.         # 之后再使用dropout进行随机置0,最后通过第二个线性层w2,返回最终结果.
  18.         return self.w2(self.dropout(F.relu(self.w1(x))))
复制代码






   
  1. d_model = 512
  2. # 线性变化的维度
  3. d_ff = 64
  4. dropout = 0.2
复制代码

   
  1. # 输入参数x可以是多头注意力机制的输出x = mha_resulttensor([[[-0.3075,  1.5687, -2.5693,  ..., -1.1098,  0.0878, -3.3609],
  2.          [ 3.8065, -2.4538, -0.3708,  ..., -1.5205, -1.1488, -1.3984],
  3.          [ 2.4190,  0.5376, -2.8475,  ...,  1.4218, -0.4488, -0.2984],
  4.          [ 2.9356,  0.3620, -3.8722,  ..., -0.7996,  0.1468,  1.0345]],
  5.         [[ 1.1423,  0.6038,  0.0954,  ...,  2.2679, -5.7749,  1.4132],
  6.          [ 2.4066, -0.2777,  2.8102,  ...,  0.1137, -3.9517, -2.9246],
  7.          [ 5.8201,  1.1534, -1.9191,  ...,  0.1410, -7.6110,  1.0046],
  8.          [ 3.1209,  1.0008, -0.5317,  ...,  2.8619, -6.3204, -1.3435]]],
  9.        grad_fn=<AddBackward0>)
  10. torch.Size([2, 4, 512])
复制代码

   
  1. ff = PositionwiseFeedForward(d_model, d_ff, dropout)
  2. ff_result = ff(x)
  3. print(ff_result)
复制代码

   
  1. tensor([[[-1.9488e+00, -3.4060e-01, -1.1216e+00,  ...,  1.8203e-01,
  2.           -2.6336e+00,  2.0917e-03],
  3.          [-2.5875e-02,  1.1523e-01, -9.5437e-01,  ..., -2.6257e-01,
  4.           -5.7620e-01, -1.9225e-01],
  5.          [-8.7508e-01,  1.0092e+00, -1.6515e+00,  ...,  3.4446e-02,
  6.           -1.5933e+00, -3.1760e-01],
  7.          [-2.7507e-01,  4.7225e-01, -2.0318e-01,  ...,  1.0530e+00,
  8.           -3.7910e-01, -9.7730e-01]],
  9.         [[-2.2575e+00, -2.0904e+00,  2.9427e+00,  ...,  9.6574e-01,
  10.           -1.9754e+00,  1.2797e+00],
  11.          [-1.5114e+00, -4.7963e-01,  1.2881e+00,  ..., -2.4882e-02,
  12.           -1.5896e+00, -1.0350e+00],
  13.          [ 1.7416e-01, -4.0688e-01,  1.9289e+00,  ..., -4.9754e-01,
  14.           -1.6320e+00, -1.5217e+00],
  15.          [-1.0874e-01, -3.3842e-01,  2.9379e-01,  ..., -5.1276e-01,
  16.           -1.6150e+00, -1.1295e+00]]], grad_fn=<AddBackward0>)
  17. torch.Size([2, 4, 512])
复制代码




2.3.5 规范化层







  1. # 通过LayerNorm实现规范化层的类
  2. class LayerNorm(nn.Module):
  3.     def __init__(self, features, eps=1e-6):
  4.         """初始化函数有两个参数, 一个是features, 表示词嵌入的维度,
  5.            另一个是eps它是一个足够小的数, 在规范化公式的分母中出现,
  6.            防止分母为0.默认是1e-6."""
  7.         super(LayerNorm, self).__init__()
  8.         # 根据features的形状初始化两个参数张量a2,和b2,第一个初始化为1张量,
  9.         # 也就是里面的元素都是1,第二个初始化为0张量,也就是里面的元素都是0,这两个张量就是规范化层的参数,
  10.         # 因为直接对上一层得到的结果做规范化公式计算,将改变结果的正常表征,因此就需要有参数作为调节因子,
  11.         # 使其即能满足规范化要求,又能不改变针对目标的表征.最后使用nn.parameter封装,代表他们是模型的参数。
  12.         self.a2 = nn.Parameter(torch.ones(features))
  13.         self.b2 = nn.Parameter(torch.zeros(features))
  14.         # 把eps传到类中
  15.         self.eps = eps
  16.     def forward(self, x):
  17.         """输入参数x代表来自上一层的输出"""
  18.         # 在函数中,首先对输入变量x求其最后一个维度的均值,并保持输出维度与输入维度一致.
  19.         # 接着再求最后一个维度的标准差,然后就是根据规范化公式,用x减去均值除以标准差获得规范化的结果,
  20.         # 最后对结果乘以我们的缩放参数,即a2,*号代表同型点乘,即对应位置进行乘法操作,加上位移参数b2.返回即可.
  21.         mean = x.mean(-1, keepdim=True)
  22.         std = x.std(-1, keepdim=True)
  23.         return self.a2 * (x - mean) / (std + self.eps) + self.b2
复制代码

   
  1. features = d_model = 512
  2. eps = 1e-6
复制代码

   
  1. # 输入x来自前馈全毗连层的输出x = ff_resulttensor([[[-1.9488e+00, -3.4060e-01, -1.1216e+00,  ...,  1.8203e-01,
  2.           -2.6336e+00,  2.0917e-03],
  3.          [-2.5875e-02,  1.1523e-01, -9.5437e-01,  ..., -2.6257e-01,
  4.           -5.7620e-01, -1.9225e-01],
  5.          [-8.7508e-01,  1.0092e+00, -1.6515e+00,  ...,  3.4446e-02,
  6.           -1.5933e+00, -3.1760e-01],
  7.          [-2.7507e-01,  4.7225e-01, -2.0318e-01,  ...,  1.0530e+00,
  8.           -3.7910e-01, -9.7730e-01]],
  9.         [[-2.2575e+00, -2.0904e+00,  2.9427e+00,  ...,  9.6574e-01,
  10.           -1.9754e+00,  1.2797e+00],
  11.          [-1.5114e+00, -4.7963e-01,  1.2881e+00,  ..., -2.4882e-02,
  12.           -1.5896e+00, -1.0350e+00],
  13.          [ 1.7416e-01, -4.0688e-01,  1.9289e+00,  ..., -4.9754e-01,
  14.           -1.6320e+00, -1.5217e+00],
  15.          [-1.0874e-01, -3.3842e-01,  2.9379e-01,  ..., -5.1276e-01,
  16.           -1.6150e+00, -1.1295e+00]]], grad_fn=<AddBackward0>)
  17. torch.Size([2, 4, 512])
复制代码

   
  1. ln = LayerNorm(feature, eps)
  2. ln_result = ln(x)
  3. print(ln_result)
复制代码

   
  1. tensor([[[ 2.2697,  1.3911, -0.4417,  ...,  0.9937,  0.6589, -1.1902],
  2.          [ 1.5876,  0.5182,  0.6220,  ...,  0.9836,  0.0338, -1.3393],
  3.          [ 1.8261,  2.0161,  0.2272,  ...,  0.3004,  0.5660, -0.9044],
  4.          [ 1.5429,  1.3221, -0.2933,  ...,  0.0406,  1.0603,  1.4666]],
  5.         [[ 0.2378,  0.9952,  1.2621,  ..., -0.4334, -1.1644,  1.2082],
  6.          [-1.0209,  0.6435,  0.4235,  ..., -0.3448, -1.0560,  1.2347],
  7.          [-0.8158,  0.7118,  0.4110,  ...,  0.0990, -1.4833,  1.9434],
  8.          [ 0.9857,  2.3924,  0.3819,  ...,  0.0157, -1.6300,  1.2251]]],
  9.        grad_fn=<AddBackward0>)
  10. torch.Size([2, 4, 512])
复制代码




2.3.6 子层毗连结构










  1. # 使用SublayerConnection来实现子层连接结构的类
  2. class SublayerConnection(nn.Module):
  3.     def __init__(self, size, dropout=0.1):
  4.         """它输入参数有两个, size以及dropout, size一般是都是词嵌入维度的大小,
  5.            dropout本身是对模型结构中的节点数进行随机抑制的比率,
  6.            又因为节点被抑制等效就是该节点的输出都是0,因此也可以把dropout看作是对输出矩阵的随机置0的比率.
  7.         """
  8.         super(SublayerConnection, self).__init__()
  9.         # 实例化了规范化对象self.norm
  10.         self.norm = LayerNorm(size)
  11.         # 又使用nn中预定义的droupout实例化一个self.dropout对象.
  12.         self.dropout = nn.Dropout(p=dropout)
  13.     def forward(self, x, sublayer):
  14.         """前向逻辑函数中, 接收上一个层或者子层的输入作为第一个参数,
  15.            将该子层连接中的子层函数作为第二个参数"""
  16.         # 我们首先对输出进行规范化,然后将结果传给子层处理,之后再对子层进行dropout操作,
  17.         # 随机停止一些网络中神经元的作用,来防止过拟合. 最后还有一个add操作,
  18.         # 因为存在跳跃连接,所以是将输入x与dropout后的子层输出结果相加作为最终的子层连接输出.
  19.         return x + self.dropout(sublayer(self.norm(x)))
复制代码

   
  1. size = 512
  2. dropout = 0.2
  3. head = 8
  4. d_model = 512
复制代码

   
  1. # 令x为位置编码器的输出
  2. x = pe_result
  3. mask = Variable(torch.zeros(8, 4, 4))
  4. # 假设子层中装的是多头注意力层, 实例化这个类
  5. self_attn =  MultiHeadedAttention(head, d_model)
  6. # 使用lambda获得一个函数类型的子层
  7. sublayer = lambda x: self_attn(x, x, x, mask)
复制代码

   
  1. sc = SublayerConnection(size, dropout)
  2. sc_result = sc(x, sublayer)
  3. print(sc_result)
  4. print(sc_result.shape)
复制代码

   
  1. tensor([[[ 14.8830,  22.4106, -31.4739,  ...,  21.0882, -10.0338,  -0.2588],
  2.          [-25.1435,   2.9246, -16.1235,  ...,  10.5069,  -7.1007,  -3.7396],
  3.          [  0.1374,  32.6438,  12.3680,  ..., -12.0251, -40.5829,   2.2297],
  4.          [-13.3123,  55.4689,   9.5420,  ..., -12.6622,  23.4496,  21.1531]],
  5.         [[ 13.3533,  17.5674, -13.3354,  ...,  29.1366,  -6.4898,  35.8614],
  6.          [-35.2286,  18.7378, -31.4337,  ...,  11.1726,  20.6372,  29.8689],
  7.          [-30.7627,   0.0000, -57.0587,  ...,  15.0724, -10.7196, -18.6290],
  8.          [ -2.7757, -19.6408,   0.0000,  ...,  12.7660,  21.6843, -35.4784]]],
  9.        grad_fn=<AddBackward0>)
  10. torch.Size([2, 4, 512])
复制代码




2.3.7 编码器层










  1. # 使用EncoderLayer类实现编码器层
  2. class EncoderLayer(nn.Module):
  3.     def __init__(self, size, self_attn, feed_forward, dropout):
  4.         """它的初始化函数参数有四个,分别是size,其实就是我们词嵌入维度的大小,它也将作为我们编码器层的大小,
  5.            第二个self_attn,之后我们将传入多头自注意力子层实例化对象, 并且是自注意力机制,
  6.            第三个是feed_froward, 之后我们将传入前馈全连接层实例化对象, 最后一个是置0比率dropout."""
  7.         super(EncoderLayer, self).__init__()
  8.         # 首先将self_attn和feed_forward传入其中.
  9.         self.self_attn = self_attn
  10.         self.feed_forward = feed_forward
  11.         # 如图所示, 编码器层中有两个子层连接结构, 所以使用clones函数进行克隆
  12.         self.sublayer = clones(SublayerConnection(size, dropout), 2)
  13.         # 把size传入其中
  14.         self.size = size
  15.     def forward(self, x, mask):
  16.         """forward函数中有两个输入参数,x和mask,分别代表上一层的输出,和掩码张量mask."""
  17.         # 里面就是按照结构图左侧的流程. 首先通过第一个子层连接结构,其中包含多头自注意力子层,
  18.         # 然后通过第二个子层连接结构,其中包含前馈全连接子层. 最后返回结果.
  19.         x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
  20.         return self.sublayer[1](x, self.feed_forward)
复制代码

   
  1. size = 512
  2. head = 8
  3. d_model = 512
  4. d_ff = 64
  5. x = pe_result
  6. dropout = 0.2
  7. self_attn = MultiHeadedAttention(head, d_model)
  8. ff = PositionwiseFeedForward(d_model, d_ff, dropout)
  9. mask = Variable(torch.zeros(8, 4, 4))
复制代码

   
  1. el = EncoderLayer(size, self_attn, ff, dropout)
  2. el_result = el(x, mask)
  3. print(el_result)
  4. print(el_result.shape)
复制代码

   
  1. tensor([[[ 33.6988, -30.7224,  20.9575,  ...,   5.2968, -48.5658,  20.0734],
  2.          [-18.1999,  34.2358,  40.3094,  ...,  10.1102,  58.3381,  58.4962],
  3.          [ 32.1243,  16.7921,  -6.8024,  ...,  23.0022, -18.1463, -17.1263],
  4.          [ -9.3475,  -3.3605, -55.3494,  ...,  43.6333,  -0.1900,   0.1625]],
  5.         [[ 32.8937, -46.2808,   8.5047,  ...,  29.1837,  22.5962, -14.4349],
  6.          [ 21.3379,  20.0657, -31.7256,  ..., -13.4079, -44.0706,  -9.9504],
  7.          [ 19.7478,  -1.0848,  11.8884,  ...,  -9.5794,   0.0675,  -4.7123],
  8.          [ -6.8023, -16.1176,  20.9476,  ...,  -6.5469,  34.8391, -14.9798]]],
  9.        grad_fn=<AddBackward0>)
  10. torch.Size([2, 4, 512])
复制代码




2.3.8 编码器







  1. # 使用Encoder类来实现编码器
  2. class Encoder(nn.Module):
  3.     def __init__(self, layer, N):
  4.         """初始化函数的两个参数分别代表编码器层和编码器层的个数"""
  5.         super(Encoder, self).__init__()
  6.         # 首先使用clones函数克隆N个编码器层放在self.layers中
  7.         self.layers = clones(layer, N)
  8.         # 再初始化一个规范化层, 它将用在编码器的最后面.
  9.         self.norm = LayerNorm(layer.size)
  10.     def forward(self, x, mask):
  11.         """forward函数的输入和编码器层相同, x代表上一层的输出, mask代表掩码张量"""
  12.         # 首先就是对我们克隆的编码器层进行循环,每次都会得到一个新的x,
  13.         # 这个循环的过程,就相当于输出的x经过了N个编码器层的处理.
  14.         # 最后再通过规范化层的对象self.norm进行处理,最后返回结果.
  15.         for layer in self.layers:
  16.             x = layer(x, mask)
  17.         return self.norm(x)
复制代码
  
  1. # 第一个实例化参数layer, 它是一个编码器层的实例化对象, 因此需要传入编码器层的参数
  2. # 又因为编码器层中的子层是不共享的, 因此需要使用深度拷贝各个对象.
  3. size = 512
  4. head = 8
  5. d_model = 512
  6. d_ff = 64
  7. c = copy.deepcopy
  8. attn = MultiHeadedAttention(head, d_model)
  9. ff = PositionwiseFeedForward(d_model, d_ff, dropout)
  10. dropout = 0.2
  11. layer = EncoderLayer(size, c(attn), c(ff), dropout)
  12. # 编码器中编码器层的个数N
  13. N = 8
  14. mask = Variable(torch.zeros(8, 4, 4))
复制代码

   
  1. en = Encoder(layer, N)
  2. en_result = en(x, mask)
  3. print(en_result)
  4. print(en_result.shape)
复制代码

   
  1. tensor([[[-0.2081, -0.3586, -0.2353,  ...,  2.5646, -0.2851,  0.0238],
  2.          [ 0.7957, -0.5481,  1.2443,  ...,  0.7927,  0.6404, -0.0484],
  3.          [-0.1212,  0.4320, -0.5644,  ...,  1.3287, -0.0935, -0.6861],
  4.          [-0.3937, -0.6150,  2.2394,  ..., -1.5354,  0.7981,  1.7907]],
  5.         [[-2.3005,  0.3757,  1.0360,  ...,  1.4019,  0.6493, -0.1467],
  6.          [ 0.5653,  0.1569,  0.4075,  ..., -0.3205,  1.4774, -0.5856],
  7.          [-1.0555,  0.0061, -1.8165,  ..., -0.4339, -1.8780,  0.2467],
  8.          [-2.1617, -1.5532, -1.4330,  ..., -0.9433, -0.5304, -1.7022]]],
  9.        grad_fn=<AddBackward0>)
  10. torch.Size([2, 4, 512])
复制代码





2.4 解码器部分实现


学习目标








2.4.1 解码器层







  1. # 使用DecoderLayer的类实现解码器层
  2. class DecoderLayer(nn.Module):
  3.     def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
  4.         """初始化函数的参数有5个, 分别是size,代表词嵌入的维度大小, 同时也代表解码器层的尺寸,
  5.             第二个是self_attn,多头自注意力对象,也就是说这个注意力机制需要Q=K=V,
  6.             第三个是src_attn,多头注意力对象,这里Q!=K=V, 第四个是前馈全连接层对象,最后就是droupout置0比率.
  7.         """
  8.         super(DecoderLayer, self).__init__()
  9.         # 在初始化函数中, 主要就是将这些输入传到类中
  10.         self.size = size
  11.         self.self_attn = self_attn
  12.         self.src_attn = src_attn
  13.         self.feed_forward = feed_forward
  14.         # 按照结构图使用clones函数克隆三个子层连接对象.
  15.         self.sublayer = clones(SublayerConnection(size, dropout), 3)
  16.     def forward(self, x, memory, source_mask, target_mask):
  17.         """forward函数中的参数有4个,分别是来自上一层的输入x,
  18.            来自编码器层的语义存储变量mermory, 以及源数据掩码张量和目标数据掩码张量.
  19.         """
  20.         # 将memory表示成m方便之后使用
  21.         m = memory
  22.         # 将x传入第一个子层结构,第一个子层结构的输入分别是x和self-attn函数,因为是自注意力机制,所以Q,K,V都是x,
  23.         # 最后一个参数是目标数据掩码张量,这时要对目标数据进行遮掩,因为此时模型可能还没有生成任何目标数据,
  24.         # 比如在解码器准备生成第一个字符或词汇时,我们其实已经传入了第一个字符以便计算损失,
  25.         # 但是我们不希望在生成第一个字符时模型能利用这个信息,因此我们会将其遮掩,同样生成第二个字符或词汇时,
  26.         # 模型只能使用第一个字符或词汇信息,第二个字符以及之后的信息都不允许被模型使用.
  27.         x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, target_mask))
  28.         # 接着进入第二个子层,这个子层中常规的注意力机制,q是输入x; k,v是编码层输出memory,
  29.         # 同样也传入source_mask,但是进行源数据遮掩的原因并非是抑制信息泄漏,而是遮蔽掉对结果没有意义的字符而产生的注意力值,
  30.         # 以此提升模型效果和训练速度. 这样就完成了第二个子层的处理.
  31.         x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, source_mask))
  32.         # 最后一个子层就是前馈全连接子层,经过它的处理后就可以返回结果.这就是我们的解码器层结构.
  33.         return self.sublayer[2](x, self.feed_forward)
复制代码
  
  1. # 类的实例化参数与解码器层类似, 相比多出了src_attn, 但是和self_attn是同一个类.
  2. head = 8
  3. size = 512
  4. d_model = 512
  5. d_ff = 64
  6. dropout = 0.2
  7. self_attn = src_attn = MultiHeadedAttention(head, d_model, dropout)
  8. # 前馈全连接层也和之前相同
  9. ff = PositionwiseFeedForward(d_model, d_ff, dropout)
复制代码

   
  1. # x是来自目标数据的词嵌入表示, 但形式和源数据的词嵌入表示相同, 这里使用per充当.
  2. x = pe_result
  3. # memory是来自编码器的输出
  4. memory = en_result
  5. # 实际中source_mask和target_mask并不相同, 这里为了方便计算使他们都为mask
  6. mask = Variable(torch.zeros(8, 4, 4))
  7. source_mask = target_mask = mask
复制代码

   
  1. dl = DecoderLayer(size, self_attn, src_attn, ff, dropout)
  2. dl_result = dl(x, memory, source_mask, target_mask)
  3. print(dl_result)
  4. print(dl_result.shape)
复制代码

   
  1. tensor([[[ 1.9604e+00,  3.9288e+01, -5.2422e+01,  ...,  2.1041e-01,
  2.           -5.5063e+01,  1.5233e-01],
  3.          [ 1.0135e-01, -3.7779e-01,  6.5491e+01,  ...,  2.8062e+01,
  4.           -3.7780e+01, -3.9577e+01],
  5.          [ 1.9526e+01, -2.5741e+01,  2.6926e-01,  ..., -1.5316e+01,
  6.            1.4543e+00,  2.7714e+00],
  7.          [-2.1528e+01,  2.0141e+01,  2.1999e+01,  ...,  2.2099e+00,
  8.           -1.7267e+01, -1.6687e+01]],
  9.         [[ 6.7259e+00, -2.6918e+01,  1.1807e+01,  ..., -3.6453e+01,
  10.           -2.9231e+01,  1.1288e+01],
  11.          [ 7.7484e+01, -5.0572e-01, -1.3096e+01,  ...,  3.6302e-01,
  12.            1.9907e+01, -1.2160e+00],
  13.          [ 2.6703e+01,  4.4737e+01, -3.1590e+01,  ...,  4.1540e-03,
  14.            5.2587e+00,  5.2382e+00],
  15.          [ 4.7435e+01, -3.7599e-01,  5.0898e+01,  ...,  5.6361e+00,
  16.            3.5891e+01,  1.5697e+01]]], grad_fn=<AddBackward0>)
  17. torch.Size([2, 4, 512])
复制代码




2.4.2 解码器







  1. # 使用类Decoder来实现解码器
  2. class Decoder(nn.Module):
  3.     def __init__(self, layer, N):
  4.         """初始化函数的参数有两个,第一个就是解码器层layer,第二个是解码器层的个数N."""
  5.         super(Decoder, self).__init__()
  6.         # 首先使用clones方法克隆了N个layer,然后实例化了一个规范化层.
  7.         # 因为数据走过了所有的解码器层后最后要做规范化处理.
  8.         self.layers = clones(layer, N)
  9.         self.norm = LayerNorm(layer.size)
  10.     def forward(self, x, memory, source_mask, target_mask):
  11.         """forward函数中的参数有4个,x代表目标数据的嵌入表示,memory是编码器层的输出,
  12.            source_mask, target_mask代表源数据和目标数据的掩码张量"""
  13.         # 然后就是对每个层进行循环,当然这个循环就是变量x通过每一个层的处理,
  14.         # 得出最后的结果,再进行一次规范化返回即可.
  15.         for layer in self.layers:
  16.             x = layer(x, memory, source_mask, target_mask)
  17.         return self.norm(x)
复制代码
  
  1. # 分别是解码器层layer和解码器层的个数N
  2. size = 512
  3. d_model = 512
  4. head = 8
  5. d_ff = 64
  6. dropout = 0.2
  7. c = copy.deepcopy
  8. attn = MultiHeadedAttention(head, d_model)
  9. ff = PositionwiseFeedForward(d_model, d_ff, dropout)
  10. layer = DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
  11. N = 8
复制代码

   
  1. # 输入参数与解码器层的输入参数相同
  2. x = pe_result
  3. memory = en_result
  4. mask = Variable(torch.zeros(8, 4, 4))
  5. source_mask = target_mask = mask
复制代码

   
  1. de = Decoder(layer, N)
  2. de_result = de(x, memory, source_mask, target_mask)
  3. print(de_result)
  4. print(de_result.shape)
复制代码

   
  1. tensor([[[ 0.9898, -0.3216, -1.2439,  ...,  0.7427, -0.0717, -0.0814],
  2.          [-0.7432,  0.6985,  1.5551,  ...,  0.5232, -0.5685,  1.3387],
  3.          [ 0.2149,  0.5274, -1.6414,  ...,  0.7476,  0.5082, -3.0132],
  4.          [ 0.4408,  0.9416,  0.4522,  ..., -0.1506,  1.5591, -0.6453]],
  5.         [[-0.9027,  0.5874,  0.6981,  ...,  2.2899,  0.2933, -0.7508],
  6.          [ 1.2246, -1.0856, -0.2497,  ..., -1.2377,  0.0847, -0.0221],
  7.          [ 3.4012, -0.4181, -2.0968,  ..., -1.5427,  0.1090, -0.3882],
  8.          [-0.1050, -0.5140, -0.6494,  ..., -0.4358, -1.2173,  0.4161]]],
  9.        grad_fn=<AddBackward0>)
  10. torch.Size([2, 4, 512])
复制代码





2.5 输出部分实现


学习目标




2.5.1 线性层的作用



2.5.2 softmax层的作用




  1. # nn.functional工具包装载了网络层中那些只进行计算, 而没有参数的层
  2. import torch.nn.functional as F
  3. # 将线性层和softmax计算层一起实现, 因为二者的共同目标是生成最后的结构
  4. # 因此把类的名字叫做Generator, 生成器类
  5. class Generator(nn.Module):
  6.     def __init__(self, d_model, vocab_size):
  7.         """初始化函数的输入参数有两个, d_model代表词嵌入维度, vocab_size代表词表大小."""
  8.         super(Generator, self).__init__()
  9.         # 首先就是使用nn中的预定义线性层进行实例化, 得到一个对象self.project等待使用,
  10.         # 这个线性层的参数有两个, 就是初始化函数传进来的两个参数: d_model, vocab_size
  11.         self.project = nn.Linear(d_model, vocab_size)
  12.     def forward(self, x):
  13.         """前向逻辑函数中输入是上一层的输出张量x"""
  14.         # 在函数中, 首先使用上一步得到的self.project对x进行线性变化,
  15.         # 然后使用F中已经实现的log_softmax进行的softmax处理.
  16.         # 在这里之所以使用log_softmax是因为和我们这个pytorch版本的损失函数实现有关, 在其他版本中将修复.
  17.         # log_softmax就是对softmax的结果又取了对数, 因为对数函数是单调递增函数,
  18.         # 因此对最终我们取最大的概率值没有影响. 最后返回结果即可.
  19.         return F.log_softmax(self.project(x), dim=-1)
复制代码


  1. >>> m = nn.Linear(20, 30)
  2. >>> input = torch.randn(128, 20)
  3. >>> output = m(input)
  4. >>> print(output.size())
  5. torch.Size([128, 30])
复制代码

   
  1. # 词嵌入维度是512维
  2. d_model = 512
  3. # 词表大小是1000
  4. vocab_size = 1000
复制代码

   
  1. # 输入x是上一层网络的输出, 我们使用来自解码器层的输出
  2. x = de_result
复制代码

   
  1. gen = Generator(d_model, vocab_size)
  2. gen_result = gen(x)
  3. print(gen_result)
  4. print(gen_result.shape)
复制代码

   
  1. tensor([[[-7.8098, -7.5260, -6.9244,  ..., -7.6340, -6.9026, -7.5232],
  2.          [-6.9093, -7.3295, -7.2972,  ..., -6.6221, -7.2268, -7.0772],
  3.          [-7.0263, -7.2229, -7.8533,  ..., -6.7307, -6.9294, -7.3042],
  4.          [-6.5045, -6.0504, -6.6241,  ..., -5.9063, -6.5361, -7.1484]],
  5.         [[-7.1651, -6.0224, -7.4931,  ..., -7.9565, -8.0460, -6.6490],
  6.          [-6.3779, -7.6133, -8.3572,  ..., -6.6565, -7.1867, -6.5112],
  7.          [-6.4914, -6.9289, -6.2634,  ..., -6.2471, -7.5348, -6.8541],
  8.          [-6.8651, -7.0460, -7.6239,  ..., -7.1411, -6.5496, -7.3749]]],
  9.        grad_fn=<LogSoftmaxBackward>)
  10. torch.Size([2, 4, 1000])
复制代码

小节总结




2.6 模子构建


学习目标




2.6.1 编码器-解码器结构的代码实现

  1. # 使用EncoderDecoder类来实现编码器-解码器结构
  2. class EncoderDecoder(nn.Module):
  3.     def __init__(self, encoder, decoder, source_embed, target_embed, generator):
  4.         """初始化函数中有5个参数, 分别是编码器对象, 解码器对象,
  5.            源数据嵌入函数, 目标数据嵌入函数,  以及输出部分的类别生成器对象
  6.         """
  7.         super(EncoderDecoder, self).__init__()
  8.         # 将参数传入到类中
  9.         self.encoder = encoder
  10.         self.decoder = decoder
  11.         self.src_embed = source_embed
  12.         self.tgt_embed = target_embed
  13.         self.generator = generator
  14.     def forward(self, source, target, source_mask, target_mask):
  15.         """在forward函数中,有四个参数, source代表源数据, target代表目标数据,
  16.            source_mask和target_mask代表对应的掩码张量"""
  17.         # 在函数中, 将source, source_mask传入编码函数, 得到结果后,
  18.         # 与source_mask,target,和target_mask一同传给解码函数.
  19.         return self.decode(self.encode(source, source_mask), source_mask,
  20.                             target, target_mask)
  21.     def encode(self, source, source_mask):
  22.         """编码函数, 以source和source_mask为参数"""
  23.         # 使用src_embed对source做处理, 然后和source_mask一起传给self.encoder
  24.         return self.encoder(self.src_embed(source), source_mask)
  25.     def decode(self, memory, source_mask, target, target_mask):
  26.         """解码函数, 以memory即编码器的输出, source_mask, target, target_mask为参数"""
  27.         # 使用tgt_embed对target做处理, 然后和source_mask, target_mask, memory一起传给self.decoder
  28.         return self.decoder(self.tgt_embed(target), memory, source_mask, target_mask)
复制代码

   
  1. vocab_size = 1000
  2. d_model = 512
  3. encoder = en
  4. decoder = de
  5. source_embed = nn.Embedding(vocab_size, d_model)
  6. target_embed = nn.Embedding(vocab_size, d_model)
  7. generator = gen
复制代码

   
  1. # 假设源数据与目标数据相同, 实际中并不相同
  2. source = target = Variable(torch.LongTensor([[100, 2, 421, 508], [491, 998, 1, 221]]))
  3. # 假设src_mask与tgt_mask相同,实际中并不相同
  4. source_mask = target_mask = Variable(torch.zeros(8, 4, 4))
复制代码

   
  1. ed = EncoderDecoder(encoder, decoder, source_embed, target_embed, generator)
  2. ed_result = ed(source, target, source_mask, target_mask)
  3. print(ed_result)
  4. print(ed_result.shape)
复制代码

   
  1. tensor([[[ 0.2102, -0.0826, -0.0550,  ...,  1.5555,  1.3025, -0.6296],
  2.          [ 0.8270, -0.5372, -0.9559,  ...,  0.3665,  0.4338, -0.7505],
  3.          [ 0.4956, -0.5133, -0.9323,  ...,  1.0773,  1.1913, -0.6240],
  4.          [ 0.5770, -0.6258, -0.4833,  ...,  0.1171,  1.0069, -1.9030]],
  5.         [[-0.4355, -1.7115, -1.5685,  ..., -0.6941, -0.1878, -0.1137],
  6.          [-0.8867, -1.2207, -1.4151,  ..., -0.9618,  0.1722, -0.9562],
  7.          [-0.0946, -0.9012, -1.6388,  ..., -0.2604, -0.3357, -0.6436],
  8.          [-1.1204, -1.4481, -1.5888,  ..., -0.8816, -0.6497,  0.0606]]],
  9.        grad_fn=<AddBackward0>)
  10. torch.Size([2, 4, 512])
复制代码



2.6.2 Tansformer模子构建过程的代码分析

  1. def make_model(source_vocab, target_vocab, N=6,
  2.                d_model=512, d_ff=2048, head=8, dropout=0.1):
  3.     """该函数用来构建模型, 有7个参数,分别是源数据特征(词汇)总数,目标数据特征(词汇)总数,
  4.        编码器和解码器堆叠数,词向量映射维度,前馈全连接网络中变换矩阵的维度,
  5.        多头注意力结构中的多头数,以及置零比率dropout."""
  6.     # 首先得到一个深度拷贝命令,接下来很多结构都需要进行深度拷贝,
  7.     # 来保证他们彼此之间相互独立,不受干扰.
  8.     c = copy.deepcopy
  9.     # 实例化了多头注意力类,得到对象attn
  10.     attn = MultiHeadedAttention(head, d_model)
  11.     # 然后实例化前馈全连接类,得到对象ff
  12.     ff = PositionwiseFeedForward(d_model, d_ff, dropout)
  13.     # 实例化位置编码类,得到对象position
  14.     position = PositionalEncoding(d_model, dropout)
  15.     # 根据结构图, 最外层是EncoderDecoder,在EncoderDecoder中,
  16.     # 分别是编码器层,解码器层,源数据Embedding层和位置编码组成的有序结构,
  17.     # 目标数据Embedding层和位置编码组成的有序结构,以及类别生成器层.
  18.     # 在编码器层中有attention子层以及前馈全连接子层,
  19.     # 在解码器层中有两个attention子层以及前馈全连接层.
  20.     model = EncoderDecoder(
  21.         Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N),
  22.         Decoder(DecoderLayer(d_model, c(attn), c(attn),
  23.                              c(ff), dropout), N),
  24.         nn.Sequential(Embeddings(d_model, source_vocab), c(position)),
  25.         nn.Sequential(Embeddings(d_model, target_vocab), c(position)),
  26.         Generator(d_model, target_vocab))
  27.     # 模型结构完成后,接下来就是初始化模型中的参数,比如线性层中的变换矩阵
  28.     # 这里一但判断参数的维度大于1,则会将其初始化成一个服从均匀分布的矩阵,
  29.     for p in model.parameters():
  30.         if p.dim() > 1:
  31.             nn.init.xavier_uniform(p)
  32.     return model
复制代码


  1. # 结果服从均匀分布U(-a, a)
  2. >>> w = torch.empty(3, 5)
  3. >>> w = nn.init.xavier_uniform_(w, gain=nn.init.calculate_gain('relu'))
  4. >>> w
  5. tensor([[-0.7742,  0.5413,  0.5478, -0.4806, -0.2555],
  6.         [-0.8358,  0.4673,  0.3012,  0.3882, -0.6375],
  7.         [ 0.4622, -0.0794,  0.1851,  0.8462, -0.3591]])
复制代码

   
  1. source_vocab = 11
  2. target_vocab = 11
  3. N = 6
  4. # 其他参数都使用默认值
复制代码

   
  1. if __name__ == '__main__':
  2.     res = make_model(source_vocab, target_vocab, N)
  3.     print(res)
复制代码

   
  1. # 根据Transformer结构图构建的最终模型结构
  2. EncoderDecoder(
  3.   (encoder): Encoder(
  4.     (layers): ModuleList(
  5.       (0): EncoderLayer(
  6.         (self_attn): MultiHeadedAttention(
  7.           (linears): ModuleList(
  8.             (0): Linear(in_features=512, out_features=512)
  9.             (1): Linear(in_features=512, out_features=512)
  10.             (2): Linear(in_features=512, out_features=512)
  11.             (3): Linear(in_features=512, out_features=512)
  12.           )
  13.           (dropout): Dropout(p=0.1)
  14.         )
  15.         (feed_forward): PositionwiseFeedForward(
  16.           (w_1): Linear(in_features=512, out_features=2048)
  17.           (w_2): Linear(in_features=2048, out_features=512)
  18.           (dropout): Dropout(p=0.1)
  19.         )
  20.         (sublayer): ModuleList(
  21.           (0): SublayerConnection(
  22.             (norm): LayerNorm(
  23.             )
  24.             (dropout): Dropout(p=0.1)
  25.           )
  26.           (1): SublayerConnection(
  27.             (norm): LayerNorm(
  28.             )
  29.             (dropout): Dropout(p=0.1)
  30.           )
  31.         )
  32.       )
  33.       (1): EncoderLayer(
  34.         (self_attn): MultiHeadedAttention(
  35.           (linears): ModuleList(
  36.             (0): Linear(in_features=512, out_features=512)
  37.             (1): Linear(in_features=512, out_features=512)
  38.             (2): Linear(in_features=512, out_features=512)
  39.             (3): Linear(in_features=512, out_features=512)
  40.           )
  41.           (dropout): Dropout(p=0.1)
  42.         )
  43.         (feed_forward): PositionwiseFeedForward(
  44.           (w_1): Linear(in_features=512, out_features=2048)
  45.           (w_2): Linear(in_features=2048, out_features=512)
  46.           (dropout): Dropout(p=0.1)
  47.         )
  48.         (sublayer): ModuleList(
  49.           (0): SublayerConnection(
  50.             (norm): LayerNorm(
  51.             )
  52.             (dropout): Dropout(p=0.1)
  53.           )
  54.           (1): SublayerConnection(
  55.             (norm): LayerNorm(
  56.             )
  57.             (dropout): Dropout(p=0.1)
  58.           )
  59.         )
  60.       )
  61.     )
  62.     (norm): LayerNorm(
  63.     )
  64.   )
  65.   (decoder): Decoder(
  66.     (layers): ModuleList(
  67.       (0): DecoderLayer(
  68.         (self_attn): MultiHeadedAttention(
  69.           (linears): ModuleList(
  70.             (0): Linear(in_features=512, out_features=512)
  71.             (1): Linear(in_features=512, out_features=512)
  72.             (2): Linear(in_features=512, out_features=512)
  73.             (3): Linear(in_features=512, out_features=512)
  74.           )
  75.           (dropout): Dropout(p=0.1)
  76.         )
  77.         (src_attn): MultiHeadedAttention(
  78.           (linears): ModuleList(
  79.             (0): Linear(in_features=512, out_features=512)
  80.             (1): Linear(in_features=512, out_features=512)
  81.             (2): Linear(in_features=512, out_features=512)
  82.             (3): Linear(in_features=512, out_features=512)
  83.           )
  84.           (dropout): Dropout(p=0.1)
  85.         )
  86.         (feed_forward): PositionwiseFeedForward(
  87.           (w_1): Linear(in_features=512, out_features=2048)
  88.           (w_2): Linear(in_features=2048, out_features=512)
  89.           (dropout): Dropout(p=0.1)
  90.         )
  91.         (sublayer): ModuleList(
  92.           (0): SublayerConnection(
  93.             (norm): LayerNorm(
  94.             )
  95.             (dropout): Dropout(p=0.1)
  96.           )
  97.           (1): SublayerConnection(
  98.             (norm): LayerNorm(
  99.             )
  100.             (dropout): Dropout(p=0.1)
  101.           )
  102.           (2): SublayerConnection(
  103.             (norm): LayerNorm(
  104.             )
  105.             (dropout): Dropout(p=0.1)
  106.           )
  107.         )
  108.       )
  109.       (1): DecoderLayer(
  110.         (self_attn): MultiHeadedAttention(
  111.           (linears): ModuleList(
  112.             (0): Linear(in_features=512, out_features=512)
  113.             (1): Linear(in_features=512, out_features=512)
  114.             (2): Linear(in_features=512, out_features=512)
  115.             (3): Linear(in_features=512, out_features=512)
  116.           )
  117.           (dropout): Dropout(p=0.1)
  118.         )
  119.         (src_attn): MultiHeadedAttention(
  120.           (linears): ModuleList(
  121.             (0): Linear(in_features=512, out_features=512)
  122.             (1): Linear(in_features=512, out_features=512)
  123.             (2): Linear(in_features=512, out_features=512)
  124.             (3): Linear(in_features=512, out_features=512)
  125.           )
  126.           (dropout): Dropout(p=0.1)
  127.         )
  128.         (feed_forward): PositionwiseFeedForward(
  129.           (w_1): Linear(in_features=512, out_features=2048)
  130.           (w_2): Linear(in_features=2048, out_features=512)
  131.           (dropout): Dropout(p=0.1)
  132.         )
  133.         (sublayer): ModuleList(
  134.           (0): SublayerConnection(
  135.             (norm): LayerNorm(
  136.             )
  137.             (dropout): Dropout(p=0.1)
  138.           )
  139.           (1): SublayerConnection(
  140.             (norm): LayerNorm(
  141.             )
  142.             (dropout): Dropout(p=0.1)
  143.           )
  144.           (2): SublayerConnection(
  145.             (norm): LayerNorm(
  146.             )
  147.             (dropout): Dropout(p=0.1)
  148.           )
  149.         )
  150.       )
  151.     )
  152.     (norm): LayerNorm(
  153.     )
  154.   )
  155.   (src_embed): Sequential(
  156.     (0): Embeddings(
  157.       (lut): Embedding(11, 512)
  158.     )
  159.     (1): PositionalEncoding(
  160.       (dropout): Dropout(p=0.1)
  161.     )
  162.   )
  163.   (tgt_embed): Sequential(
  164.     (0): Embeddings(
  165.       (lut): Embedding(11, 512)
  166.     )
  167.     (1): PositionalEncoding(
  168.       (dropout): Dropout(p=0.1)
  169.     )
  170.   )
  171.   (generator): Generator(
  172.     (proj): Linear(in_features=512, out_features=11)
  173.   )
  174. )
复制代码

小节总结



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4