资讯详情

11111111111111111111

111111111111111111111111111111111111111111111111111111

跳转至 logo Transformer 第二章:Transformer架构解析 logo Transformer 第一章:Transformer背景介绍 第二章:Transformer架构解析 目录 2.1 认识Transformer架构 学习目标 Transformer模型的作用 Transformer总体架构图 小节总结 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 实现输出部分 学习目标 线性层的作用 softmax层的作用 小节总结 2.6 模型构建 学习目标 编码器-解码器结构的代码实现 Tansformer代码分析模型构建过程 小节总结 第二章:Transformer架构解析 2.1 认识Transformer架构 学习目标 了解Transformer模型的作用. 了解Transformer整体架构图中各部分的名称. Transformer模型的作用 基于seq2seq架构的transformer模型可以完成NLP典型的领域研究任务, 如机器翻译, 文本生成等. 同时,可以为不同任务的迁移学习构建预训练语言模型. 声明: 在下一步的架构分析中, 假设我们使用它Transformer从一种语言文本到另一种语言文本的模型架构处理, 因此,许多命名方法遵循NLP中的规则. 比如: Embeddding层将称为文本嵌入层, Embedding层产生的张量称为词嵌入张量, 它的最后一维将被称为词向量等. Transformer总体架构图 avatar Transformer总体架构可分为四个部分: 输入部分 输出部分 编码器部分 解码器部分 输入部分包括: 源文本嵌入层及编码器的位置 目标文本嵌入层及其位置编码器 avatar 输出部分包括: 线性层 softmax层 avatar 编码器部分: 由N个编码器组成 每个编码器层由两个子层连接结构组成 第一个子层连接结构包括多头自注子层和标准化层 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接 avatar 解码器部分: 由N个解码器组成 每个解码器层由三个子层连接结构组成 第一个子层连接结构包括多头自注子层和标准化层 第二个子层连接结构包括多头注意力子层和标准化层 第三个子层连接结构包括前馈全连接子层和标准化层 avatar 小节总结 学习了Transformer模型的作用:

基于seq2seq架构的transformer模型可以完成NLP典型的领域研究任务, 如机器翻译, 文本生成等. 同时,可以为不同任务的迁移学习构建预训练语言模型. Transformer总体结构可分为四部分:

输入部分 输出部分 编码器部分 解码器部分 输入部分包括:

源文本嵌入层及编码器的位置 目标文本嵌入层及其位置编码器 输出部分包括:

线性层 softmax处理器 编码器部分:

由N个编码器组成 每个编码器层由两个子层连接结构组成 第一个子层连接结构包括多头自注子层和标准化层 第二个子层连接结构包括前馈全连接子层和标准化层 解码器部分:

由N个解码器组成 每个解码器层由三个子层连接结构组成 第一个子层连接结构包括多头自注子层和标准化层 第二个子层连接结构包括多头注意力子层和标准化层 第三个子层连接结构包括前馈全连接子层和标准化层 2.2 实现输入部分 学习目标 了解文本嵌入层和位置编码的作用. 掌握文本嵌入层和位置编码的实现过程. 输入部分包括: 源文本嵌入层及编码器的位置 目标文本嵌入层及其位置编码器 avatar 文本嵌入层的作用 无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示, 我希望在这样的高维空间中捕捉词汇之间的关系. pytorch 0.3.0及其必要工具包的安装:

使用pip包括安装工具pytorch-0.3.0, numpy, matplotlib, seaborn

pip install http://download.pytorch.org/whl/cu80/torch-0.3.0.post4-cp36-cp36m-linux_x86_64.whl numpy matplotlib seaborn

MAC系统安装, python版本<=3.6

pip install torch==0.3.0.post4 numpy matplotlib seaborn 文本嵌入层的代码分析:

导入必要的工具包

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 (self, d_model, vocab): 有两个参数, d_model: 指词嵌入的维度, vocab: 指词表的大小.”"" # 然后是使用super说明继承的方式nn.Module初始化函数, 我们实现的所有层都会这样写. super(Embeddings, self).() # 之后是调用nn中间的预定义层Embedding, 获得一个单词嵌入对象self.lut self.lut = nn.Embedding(vocab, d_model) # 最后就是将d_model传入类中 self.d_model = d_model

def forward(self, x):     """这个函数可以理解为该层的前向传播逻辑,在所有层中都有        当传递给此类实例对象参数时, 此类函数自动调用        参数x: 因为Embedding层是首层, 因此,通过词汇映射输入给模型的文本的张量"""      # 将x传给self.lut并与根号下self.d_model相乘作为结果返回     return self.lut(x) * math.sqrt(self.d_model) 

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]]]) 实例化参数:

词嵌入维度是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) 输出效果:

embr: 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] 位置编码器的作用 因为在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失. 位置编码器的代码分析:

定义位置编码器类, 我们同样把它看做一个层, 因此会继承nn.Module

class PositionalEncoding(nn.Module): def (self, d_model, dropout, max_len=5000): “”“位置编码器类的初始化函数, 共有三个参数, 分别是d_model: 词嵌入维度, dropout: 置0比率, max_len: 每个句子的最大长度”"" super(PositionalEncoding, self).()

    # 实例化nn中预定义的Dropout层, 并将dropout传入其中, 获得对象self.dropout
    self.dropout = nn.Dropout(p=dropout)

    # 初始化一个位置编码矩阵, 它是一个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)

    # 绝对位置矩阵初始化之后,接下来就是考虑如何将这些位置信息加入到位置编码矩阵中,
    # 最简单思路就是先将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)

    # 最后把pe位置编码矩阵注册成模型的buffer,什么是buffer呢,
    # 我们把它认为是对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不需要随着优化步骤进行更新的增益对象. 
    # 注册之后我们就可以在模型保存后重加载时和模型结构与参数一同被加载.
    self.register_buffer('pe', pe)

def forward(self, x):
    """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)

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]]) 实例化参数:

词嵌入维度是512维

d_model = 512

置0比率为0.1

dropout = 0.1

句子最大长度

max_len=60 输入参数:

输入x是Embedding层的输出的张量, 形状是2 x 4 x 512

x = embr 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] 调用:

pe = PositionalEncoding(d_model, dropout, max_len) pe_result = pe(x) print(“pe_result:”, pe_result) 输出效果:

pe_result: Variable containing: ( 0 ,.,.) = -19.7050 0.0000 0.0000 … -11.7557 -0.0000 23.4553 -1.4668 -62.2510 -2.4012 … 66.5860 -24.4578 -37.7469 9.8642 -41.6497 -11.4968 … -21.1293 -42.0945 50.7943 0.0000 34.1785 -33.0712 … 48.5520 3.2540 54.1348

( 1 ,.,.) = 7.7598 -21.0359 15.0595 … -35.6061 -0.0000 4.1772 -38.7230 8.6578 34.2935 … -43.3556 26.6052 4.3084 24.6962 37.3626 -26.9271 … 49.8989 0.0000 44.9158 -28.8435 -48.5963 -0.9892 … -52.5447 -4.1475 -3.0450 [torch.FloatTensor of size 2x4x512] 绘制词汇向量中特征的分布曲线:

import matplotlib.pyplot as plt

创建一张15 x 5大小的画布

plt.figure(figsize=(15, 5))

实例化PositionalEncoding类得到pe对象, 输入参数是20和0

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.legend([“dim %d”%p for p in [4,5,6,7]]) 输出效果: avatar 效果分析: 每条颜色的曲线代表某一个词汇中的特征在不同位置的含义. 保证同一词汇随着所在位置不同它对应位置嵌入向量会发生变化. 正弦波和余弦波的值域范围都是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个编码器层堆叠而成 每个编码器层由两个子层连接结构组成 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接 avatar 2.3.1 掩码张量 学习目标: 了解什么是掩码张量以及它的作用. 掌握生成掩码张量的实现过程. 什么是掩码张量: 掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩,也可以说被替换, 它的表现形式是一个张量. 掩码张量的作用: 在transformer中, 掩码张量的主要作用在应用attention(将在下一小节讲解)时,有一些生成的attention张量中的值计算有可能已知了未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩. 关于解码器的有关知识将在后面的章节中讲解. 生成掩码张量的代码分析:

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)

np.triu演示:

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]]) 输入实例:

生成的掩码张量的最后两维的大小

size = 5 调用:

sm = subsequent_mask(size) print(“sm:”, sm) 输出效果:

最后两维形成一个下三角阵

sm: (0 ,.,.) = 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 [torch.ByteTensor of size 1x5x5] 掩码张量的可视化:

plt.figure(figsize=(5,5)) plt.imshow(subsequent_mask(20)[0]) 输出效果: avatar 效果分析: 通过观察可视化方阵, 黄色是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作用下的表示. 而这个具体的计算规则有很多种, 我这里只介绍我们用到的这一种. 我们这里使用的注意力的计算规则: avatar Q, K, V的比喻解释:

假如我们有一个问题: 给出一段文本,使用一些关键词对它进行描述! 为了方便统一正确答案,这道题可能预先已经给大家写出了一些关键词作为提示.其中这些给出的提示就可以看作是key, 而整个的文本信息就相当于是query,value的含义则更抽象,可以比作是你看到这段文本信息后,脑子里浮现的答案信息, 这里我们又假设大家最开始都不是很聪明,第一次看到这段文本后脑子里基本上浮现的信息就只有提示这些信息, 因此key与value基本是相同的,但是随着我们对这个问题的深入理解,通过我们的思考脑子里想起来的东西原来越多, 并且能够开始对我们query也就是这段文本,提取关键信息进行表示. 这就是注意力作用的过程, 通过这个过程, 我们最终脑子里的value发生了变化, 根据提示key生成了query的关键词表示方法,也就是另外一种特征表示方法.

刚刚我们说到key和value一般情况下默认是相同,与query是不同的,这种是我们一般的注意力输入形式, 但有一种特殊情况,就是我们query与key和value相同,这种情况我们称为自注意力机制,就如同我们的刚刚的例子, 使用一般注意力机制,是使用不同于给定文本的关键词表示它. 而自注意力机制, 需要用给定文本自身来表达自己,也就是说你需要从给定文本中抽取关键词来表述它, 相当于对文本自身的一次特征提取. 什么是注意力机制: 注意力机制是注意力计算规则能够应用的深度学习网络的载体, 除了注意力计算规则外, 还包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使用自注意力计算规则的注意力机制称为自注意力机制. 注意力机制在网络中实现的图形表示: avatar 注意力计算规则的代码分析:

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) # 按照注意力公式, 将query与key的转置相乘, 这里面key是将最后两个维度进行转置, 再除以缩放系数根号下d_k, 这种计算方法也称为缩放点积注意力计算. # 得到注意力得分张量scores scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)

# 接着判断是否使用掩码张量
if mask is not None:
    # 使用tensor的masked_fill方法, 将掩码张量和scores张量每个位置一一比较, 如果掩码张量处为0
    # 则对应的scores张量用-1e9这个值来替换, 如下演示
    scores = scores.masked_fill(mask == 0, -1e9)

# 对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

tensor.masked_fill演示:

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] 输入参数:

我们令输入的query, key, value都相同, 位置编码的输出

query = key = value = pe_result Variable containing: ( 0 ,.,.) = 46.5196 16.2057 -41.5581 … -16.0242 -17.8929 -43.0405 -32.6040 16.1096 -29.5228 … 4.2721 20.6034 -1.2747 -18.6235 14.5076 -2.0105 … 15.6462 -24.6081 -30.3391 0.0000 -66.1486 -11.5123 … 20.1519 -4.6823 0.4916

( 1 ,.,.) = -24.8681 7.5495 -5.0765 … -7.5992 -26.6630 40.9517 13.1581 -3.1918 -30.9001 … 25.1187 -26.4621 2.9542 -49.7690 -42.5019 8.0198 … -5.4809 25.9403 -27.4931 -52.2775 10.4006 0.0000 … -1.9985 7.0106 -0.5189 [torch.FloatTensor of size 2x4x512] 调用:

attn, p_attn = attention(query, key, value) print(“attn:”, attn) print(“p_attn:”, p_attn) 输出效果:

将得到两个结果

query的注意力表示:

attn: Variable containing: ( 0 ,.,.) = 12.8269 7.7403 41.2225 … 1.4603 27.8559 -12.2600 12.4904 0.0000 24.1575 … 0.0000 2.5838 18.0647 -32.5959 -4.6252 -29.1050 … 0.0000 -22.6409 -11.8341 8.9921 -33.0114 -0.7393 … 4.7871 -5.7735 8.3374

( 1 ,.,.) = -25.6705 -4.0860 -36.8226 … 37.2346 -27.3576 2.5497 -16.6674 73.9788 -33.3296 … 28.5028 -5.5488 -13.7564 0.0000 -29.9039 -3.0405 … 0.0000 14.4408 14.8579 30.7819 0.0000 21.3908 … -29.0746 0.0000 -5.8475 [torch.FloatTensor of size 2x4x512]

注意力张量:

p_attn: Variable containing: (0 ,.,.) = 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1

(1 ,.,.) = 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 [torch.FloatTensor of size 2x4x4] 带有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) 带有mask的输出效果:

query的注意力表示:

attn: Variable containing: ( 0 ,.,.) = 0.4284 -7.4741 8.8839 … 1.5618 0.5063 0.5770 0.4284 -7.4741 8.8839 … 1.5618 0.5063 0.5770 0.4284 -7.4741 8.8839 … 1.5618 0.5063 0.5770 0.4284 -7.4741 8.8839 … 1.5618 0.5063 0.5770

( 1 ,.,.) = -2.8890 9.9972 -12.9505 … 9.1657 -4.6164 -0.5491 -2.8890 9.9972 -12.9505 … 9.1657 -4.6164 -0.5491 -2.8890 9.9972 -12.9505 … 9.1657 -4.6164 -0.5491 -2.8890 9.9972 -12.9505 … 9.1657 -4.6164 -0.5491 [torch.FloatTensor of size 2x4x512]

注意力张量:

p_attn: Variable containing: (0 ,.,.) = 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500

(1 ,.,.) = 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 0.2500 [torch.FloatTensor of size 2x4x4] 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进行注意力机制的计算,但是句子中的每个词的表示只获得一部分,也就是只分割了最后一维的词嵌入向量. 这就是所谓的多头,将每个头的获得的输入送到注意力机制中, 就形成多头注意力机制. 多头注意力机制结构图: avatar 多头注意力机制的作用: 这种结构设计能让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表达,实验表明可以从而提升模型效果. 多头注意力机制的代码实现:

用于深度拷贝的copy工具包

import copy

首先需要定义克隆函数, 因为在多头注意力机制的实现中, 用到多个结构相同的线性层.

我们将使用clone函数将他们一同初始化在一个网络层列表对象中. 之后的结构中也会用到该函数.

def clones(module, N): “”“用于生成相同网络层的克隆函数, 它的参数module表示要克隆的目标网络层, N代表需要克隆的数量”"" # 在函数中, 我们通过for循环对module进行N次深度拷贝, 使其每个module成为独立的层, # 然后将其放在nn.ModuleList类型的列表中存放. return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])

我们使用一个类来实现多头注意力机制的处理

class MultiHeadedAttention(nn.Module): def (self, head, embedding_dim, dropout=0.1): “”“在类的初始化时, 会传入三个参数,head代表头数,embedding_dim代表词嵌入的维度, dropout代表进行dropout操作时置0比率,默认是0.1.”"" super(MultiHeadedAttention, self).()

    # 在函数中,首先使用了一个测试中常用的assert语句,判断h是否能被d_model整除,
    # 这是因为我们之后要给每个头分配等量的词特征.也就是embedding_dim/head个.
    assert embedding_dim % head == 0

    # 得到每个头获得的分割词向量维度d_k
    self.d_k = embedding_dim // head

    # 传入头数h
    self.head = head

    # 然后获得线性层对象,通过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)

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))]

    # 得到每个头的输入后,接下来就是将他们传入到attention中,
    # 这里直接调用我们之前实现的attention函数.同时也将mask和dropout传入其中.
    x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout)

    # 通过多头注意力计算后,我们就得到了每个头计算结果组成的4维张量,我们需要将其转换为输入的形状以方便后续的计算,
    # 因此这里开始进行第一步处理环节的逆操作,先对第二和第三维进行转置,然后使用contiguous方法,
    # 这个方法的作用就是能够让转置后的张量应用view方法,否则将无法直接使用,
    # 所以,下一步就是使用view重塑形状,变成和输入形状相同.
    x = x.transpose(1, 2).contiguous().view(batch_size, -1, self.head * self.d_k)

    # 最后使用线性层列表中的最后一个线性层对输入进行线性变换得到最终的多头注意力结构的输出.
    return self.linears[-1](x)

tensor.view演示:

x = torch.randn(4, 4) x.size() torch.Size([4, 4])

y = x.view(16) y.size() torch.Size([16])

z = x.view(-1, 8) # the size -1 is inferred from other dimensions z.size() torch.Size([2, 8])

a = torch.randn(1, 2, 3, 4) a.size() torch.Size([1, 2, 3, 4])

b = a.transpose(1, 2) # Swaps 2nd and 3rd dimension b.size() torch.Size([1, 3, 2, 4])

c = a.view(1, 3, 2, 4) # Does not change tensor layout in memory c.size() torch.Size([1, 3, 2, 4])

torch.equal(b, c) False torch.transpose演示:

x = torch.randn(2, 3) x tensor([[ 1.0028, -0.9893, 0.5809], [-0.1669, 0.7299, 0.4942]])

torch.transpose(x, 0, 1) tensor([[ 1.0028, -0.1669], [-0.9893, 0.7299], [ 0.5809, 0.4942]]) 实例化参数:

头数head

head = 8

词嵌入维度embedding_dim

embedding_dim = 512

置零比率dropout

dropout = 0.2 输入参数:

假设输入的Q,K,V仍然相等

query = value = key = pe_result

输入的掩码张量mask

mask = Variable(torch.zeros(8, 4, 4)) 调用:

mha = MultiHeadedAttention(head, embedding_dim, dropout) mha_result = mha(query, key, value, mask) print(mha_result) 输出效果:

tensor([[[-0.3075, 1.5687, -2.5693, …, -1.1098, 0.0878, -3.3609], [ 3.8065, -2.4538, -0.3708, …, -1.5205, -1.1488, -1.3984], [ 2.4190, 0.5376, -2.8475, …, 1.4218, -0.4488, -0.2984], [ 2.9356, 0.3620, -3.8722, …, -0.7996, 0.1468, 1.0345]],

    [[ 1.1423,  0.6038,  0.0954,  ...,  2.2679, -5.7749,  1.4132],
     [ 2.4066, -0.2777,  2.8102,  ...,  0.1137, -3.9517, -2.9246],
     [ 5.8201,  1.1534, -1.9191,  ...,  0.1410, -7.6110,  1.0046],
     [ 3.1209,  1.0008, -0.5317,  ...,  2.8619, -6.3204, -1.3435]]],
   grad_fn=<AddBackward0>)

torch.Size([2, 4, 512]) 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中前馈全连接层就是具有两层线性层的全连接网络. 前馈全连接层的作用: 考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力. 前馈全连接层的代码分析:

通过类PositionwiseFeedForward来实现前馈全连接层

class PositionwiseFeedForward(nn.Module): def (self, d_model, d_ff, dropout=0.1): “”“初始化函数有三个输入参数分别是d_model, d_ff,和dropout=0.1,第一个是线性层的输入维度也是第二个线性层的输出维度, 因为我们希望输入通过前馈全连接层后输入和输出的维度不变. 第二个参数d_ff就是第二个线性层的输入维度和第一个线性层的输出维度. 最后一个是dropout置0比率.”"" super(PositionwiseFeedForward, self).()

    # 首先按照我们预期使用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))))

ReLU函数公式: ReLU(x)=max(0, x) ReLU函数图像: avatar 实例化参数:

d_model = 512

线性变化的维度

d_ff = 64

dropout = 0.2 输入参数:

输入参数x可以是多头注意力机制的输出

x = mha_result tensor([[[-0.3075, 1.5687, -2.5693, …, -1.1098, 0.0878, -3.3609], [ 3.8065, -2.4538, -0.3708, …, -1.5205, -1.1488, -1.3984], [ 2.4190, 0.5376, -2.8475, …, 1.4218, -0.4488, -0.2984], [ 2.9356, 0.3620, -3.8722, …, -0.7996, 0.1468, 1.0345]],

    [[ 1.1423,  0.6038,  0.0954,  ...,  2.2679, -5.7749,  1.4132],
     [ 2.4066, -0.2777,  2.8102,  ...,  0.1137, -3.9517, -2.9246],
     [ 5.8201,  1.1534, -1.9191,  ...,  0.1410, -7.6110,  1.0046],
     [ 3.1209,  1.0008, -0.5317,  ...,  2.8619, -6.3204, -1.3435]]],
   grad_fn=<AddBackward0>)

torch.Size([2, 4, 512]) 调用:

ff = PositionwiseFeedForward(d_model, d_ff, dropout) ff_result = ff(x) print(ff_result) 输出效果:

tensor([[[-1.9488e+00, -3.4060e-01, -1.1216e+00, …, 1.8203e-01, -2.6336e+00, 2.0917e-03], [-2.5875e-02, 1.1523e-01, -9.5437e-01, …, -2.6257e-01, -5.7620e-01, -1.9225e-01], [-8.7508e-01, 1.0092e+00, -1.6515e+00, …, 3.4446e-02, -1.5933e+00, -3.1760e-01], [-2.7507e-01, 4.7225e-01, -2.0318e-01, …, 1.0530e+00, -3.7910e-01, -9.7730e-01]],

    [[-2.2575e+00, -2.0904e+00,  2.9427e+00,  ...,  9.6574e-01,
      -1.9754e+00,  1.2797e+00],
     [-1.5114e+00, -4.7963e-01,  1.2881e+00,  ..., -2.4882e-02,
      -1.5896e+00, -1.0350e+00],
     [ 1.7416e-01, -4.0688e-01,  1.9289e+00,  ..., -4.9754e-01,
      -1.6320e+00, -1.5217e+00],
     [-1.0874e-01, -3.3842e-01,  2.9379e-01,  ..., -5.1276e-01,
      -1.6150e+00, -1.1295e+00]]], grad_fn=<AddBackward0>)

torch.Size([2, 4, 512]) 2.3.4 前馈全连接层总结:

学习了什么是前馈全连接层: 在Transformer中前馈全连接层就是具有两层线性层的全连接网络. 学习了前馈全连接层的作用: 考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力. 学习并实现了前馈全连接层的类: PositionwiseFeedForward 它的实例化参数为d_model, d_ff, dropout, 分别代表词嵌入维度, 线性变换维度, 和置零比率. 它的输入参数x, 表示上层的输出. 它的输出是经过2层线性网络变换的特征表示. 2.3.5 规范化层 学习目标: 了解规范化层的作用. 掌握规范化层的实现过程. 规范化层的作用: 它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内. 规范化层的代码实现:

通过LayerNorm实现规范化层的类

class LayerNorm(nn.Module): def (self, features, eps=1e-6): “”“初始化函数有两个参数, 一个是features, 表示词嵌入的维度, 另一个是eps它是一个足够小的数, 在规范化公式的分母中出现, 防止分母为0.默认是1e-6.”"" super(LayerNorm, self).()

    # 根据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

实例化参数:

features = d_model = 512 eps = 1e-6 输入参数:

输入x来自前馈全连接层的输出

x = ff_result tensor([[[-1.9488e+00, -3.4060e-01, -1.1216e+00, …, 1.8203e-01, -2.6336e+00, 2.0917e-03], [-2.5875e-02, 1.1523e-01, -9.5437e-01, …, -2.6257e-01, -5.7620e-01, -1.9225e-01], [-8.7508e-01, 1.0092e+00, -1.6515e+00, …, 3.4446e-02, -1.5933e+00, -3.1760e-01], [-2.7507e-01, 4.7225e-01, -2.0318e-01, …, 1.0530e+00, -3.7910e-01, -9.7730e-01]],

    [[-2.2575e+00, -2.0904e+00,  2.9427e+00,  ...,  9.6574e-01,
      -1.9754e+00,  1.2797e+00],
     [-1.5114e+00, -4.7963e-01,  1.2881e+00,  ..., -2.4882e-02,
      -1.5896e+00, -1.0350e+00],
     [ 1.7416e-01, -4.0688e-01,  1.9289e+00,  ..., -4.9754e-01,
      -1.6320e+00, -1.5217e+00],
     [-1.0874e-01, -3.3842e-01,  2.9379e-01,  ..., -5.1276e-01,
      -1.6150e+00, -1.1295e+00]]], grad_fn=<AddBackward0>)

torch.Size([2, 4, 512]) 调用:

ln = LayerNorm(feature, eps) ln_result = ln(x) print(ln_result) 输出效果:

tensor([[[ 2.2697, 1.3911, -0.4417, …, 0.9937, 0.6589, -1.1902], [ 1.5876, 0.5182, 0.6220, …, 0.9836, 0.0338, -1.3393], [ 1.8261, 2.0161, 0.2272, …, 0.3004, 0.5660, -0.9044], [ 1.5429, 1.3221, -0.2933, …, 0.0406, 1.0603, 1.4666]],

    [[ 0.2378,  0.9952,  1.2621,  ..., -0.4334, -1.1644,  1.2082],
     [-1.0209,  0.6435,  0.4235,  ..., -0.3448, -1.0560,  1.2347],
     [-0.8158,  0.7118,  0.4110,  ...,  0.0990, -1.4833,  1.9434],
     [ 0.9857,  2.3924,  0.3819,  ...,  0.0157, -1.6300,  1.2251]]],
   grad_fn=<AddBackward0>)

torch.Size([2, 4, 512]) 2.3.5 规范化层总结:

学习了规范化层的作用: 它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内. 学习并实现了规范化层的类: LayerNorm 它的实例化参数有两个, features和eps,分别表示词嵌入特征大小,和一个足够小的数. 它的输入参数x代表来自上一层的输出. 它的输出就是经过规范化的特征表示. 2.3.6 子层连接结构 学习目标: 了解什么是子层连接结构. 掌握子层连接结构的实现过程. 什么是子层连接结构: 如图所示,输入到每个子层以及规范化层的过程中,还使用了残差链接(跳跃连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构),在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构. 子层连接结构图: avatar avatar 子层连接结构的代码分析:

使用SublayerConnection来实现子层连接结构的类

class SublayerConnection(nn.Module): def (self, size, dropout=0.1): “”“它输入参数有两个, size以及dropout, size一般是都是词嵌入维度的大小, dropout本身是对模型结构中的节点数进行随机抑制的比率, 又因为节点被抑制等效就是该节点的输出都是0,因此也可以把dropout看作是对输出矩阵的随机置0的比率. “”” super(SublayerConnection, self).() # 实例化了规范化对象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)))

实例化参数

size = 512 dropout = 0.2 head = 8 d_model = 512 输入参数:

令x为位置编码器的输出

x = pe_result mask = Variable(torch.zeros(8, 4, 4))

假设子层中装的是多头注意力层, 实例化这个类

self_attn = MultiHeadedAttention(head, d_model)

使用lambda获得一个函数类型的子层

sublayer = lambda x: self_attn(x, x, x, mask) 调用:

sc = SublayerConnection(size, dropout) sc_result = sc(x, sublayer) print(sc_result) print(sc_result.shape) 输出效果:

tensor([[[ 14.8830, 22.4106, -31.4739, …, 21.0882, -10.0338, -0.2588], [-25.1435, 2.9246, -16.1235, …, 10.5069, -7.1007, -3.7396], [ 0.1374, 32.6438, 12.3680, …, -12.0251, -40.5829, 2.2297], [-13.3123, 55.4689, 9.5420, …, -12.6622, 23.4496, 21.1531]],

    [[ 13.3533,  17.5674, -13.3354,  ...,  29.1366,  -6.4898,  35.8614],
     [-35.2286,  18.7378, -31.4337,  ...,  11.1726,  20.6372,  29.8689],
     [-30.7627,   0.0000, -57.0587,  ...,  15.0724, -10.7196, -18.6290],
     [ -2.7757, -19.6408,   0.0000,  ...,  12.7660,  21.6843, -35.4784]]],
   grad_fn=<AddBackward0>)

torch.Size([2, 4, 512]) 2.3.6 子层连接结构总结:

什么是子层连接结构: 如图所示,输入到每个子层以及规范化层的过程中,还使用了残差链接(跳跃连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构), 在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构. 学习并实现了子层连接结构的类: SublayerConnection 类的初始化函数输入参数是size, dropout, 分别代表词嵌入大小和置零比率. 它的实例化对象输入参数是x, sublayer, 分别代表上一层输出以及子层的函数表示. 它的输出就是通过子层连接结构处理的输出. 2.3.7 编码器层 学习目标: 了解编码器层的作用. 掌握编码器层的实现过程. 编码器层的作用: 作为编码器的组成单元, 每个编码器层完成一次对输入的特征提取过程, 即编码过程. 编码器层的构成图: avatar 编码器层的代码分析:

使用EncoderLayer类实现编码器层

class EncoderLayer(nn.Module): def (self, size, self_attn, feed_forward, dropout): “”“它的初始化函数参数有四个,分别是size,其实就是我们词嵌入维度的大小,它也将作为我们编码器层的大小, 第二个self_attn,之后我们将传入多头自注意力子层实例化对象, 并且是自注意力机制, 第三个是feed_froward, 之后我们将传入前馈全连接层实例化对象, 最后一个是置0比率dropout.”"" super(EncoderLayer, self).()

    # 首先将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)

实例化参数:

size = 512 head = 8 d_model = 512 d_ff = 64 x = pe_result dropout = 0.2 self_attn = MultiHeadedAttention(head, d_model) ff = PositionwiseFeedForward(d_model, d_ff, dropout) mask = Variable(torch.zeros(8, 4, 4)) 调用:

el = EncoderLayer(size, self_attn, ff, dropout) el_result = el(x, mask) print(el_result) print(el_result.shape) 输出效果:

tensor([[[ 33.6988, -30.7224, 20.9575, …, 5.2968, -48.5658, 20.0734], [-18.1999, 34.2358, 40.3094, …, 10.1102, 58.3381, 58.4962], [ 32.1243, 16.7921, -6.8024, …, 23.0022, -18.1463, -17.1263], [ -9.3475, -3.3605, -55.3494, …, 43.6333, -0.1900, 0.1625]],

    [[ 32.8937, -46.2808,   8.5047,  ...,  29.1837,  22.5962, -14.4349],
     [ 21.3379,  20.0657, -31.7256,  ..., -13.4079, -44.0706,  -9.9504],
     [ 19.7478,  -1.0848,  11.8884,  ...,  -9.5794,   0.0675,  -4.7123],
     [ -6.8023, -16.1176,  20.9476,  ...,  -6.5469,  34.8391, -14.9798]]],
   grad_fn=<AddBackward0>)

torch.Size([2, 4, 512]) 2.3.7 编码器层总结:

学习了编码器层的作用: 作为编码器的组成

标签: 0228连接器mtl温度变送器mtl55736431连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台