transformer

神经网络的介绍

神经网络的概念

ANN 简称NN 类神经网络,模仿生物神经网络的数学模型,用来对函数进行估计或近似

模拟生物的神经系统,对函数进行估计或者近似

什么是神经元

神经网络中的基础单元,相互连接,组成神经网络

在这里插入图片描述

在这里插入图片描述

一个神经元的功能是求得输入向量与权向量得内积后,经一个非线性传递函数得到一个标量结果。

单层神经网络

有限个神经元构成,所有得神经元得输入向量都是同一个向量。
由于每一个神经元都会产生一个标量结果,所以单层神经元得输出是一个向量
向量的维数等于神经元的数目
在这里插入图片描述

感知机

两层神经网络
输入层接受外界输入信号后传递给输出层
输出+1正例,-1返例
输出层就是M-P神经元
在这里插入图片描述
N+1 输出1

在这里插入图片描述

这里有3个感知机

作用
N维空间用一个超平面分割为两部分,得到结果看是哪个分类

输出的结果是处于一个平面上的,假如给一个0.4,再这个平面的什么方向,或者另一个什么方向。之后就可以使非常简单的二分类模型。
复杂情况
N个数目,三维空间一个平面分为两部分。
大的平面就是超平面

a、两层的神经元
b、简单的二分类的模型,给定阈值,判断数据属于那一部分。

多层神经网络

单层神经网络进行叠加。
输入层:接受大量非线性输入信息,输入的信息称为输入向量。
输出层:信息再神经元连接中传输、分析、权衡、形成输出的结果,输出的消息称为输出向量
隐藏层:隐层:输入层和输出层之间众多神经元和链接组成的各个层面。隐层可以有一层或多层。隐层的神经元数目不定,但数目越来越多神经网路的非线性越显著,从而神经网络的强健性更显著。
每一层的神经元的个数可以不确定

在这里插入图片描述

全连接层
两两之间都有连接。

N-1层有M个神经元
N层有N个神经元
N是全连接层的时候

在这里插入图片描述
在这里插入图片描述
全连接层进行的是y=Wx+b的操作

激活函数,作用

感知机模型可以画一条线
在这里插入图片描述
再多加一层
依然是一个直线的形式,和感知机一样是没有什么不同的。
也是一条直线。

这时候就需要非线性的激活函数,输出的结果就不在是一条直线。

在这里插入图片描述
在这里插入图片描述
激活函数不再是直线,而是一个曲线。

对多层使用激活函数,那么非线性的情况就越强一点

作用:增加模型的非线性分割能力

在这里插入图片描述
sigmoid:{0,1}
tanh{1,1}
relumax{0,x}
elu{x,a(e^x-1)}

最强的是ReLU
数据小于0的时候就是噪声,把噪声变为正常值
对于ELU,小于0的时候就是一个正常情况

**作用:
增加模型的非线性分割能力
提高模型鲁棒性(稳健性)===拟合效果更好,更加接近真实值
缓解梯度消失问题
加速模型收敛
**

张量Tensor

各种数值数据称为张量
在这里插入图片描述
10维数据,10个特征,每一列,
10个特征存储的数值中的每一列,
维度是矩阵中的每一列
用阶来表示
0阶张量:常数
1阶张量:向量
2阶张量:matrix

阶代表的是一个数组的个数,形状,
向量的shape就是一个数值,一阶的
矩阵的shape就是两个数值,二阶的

在pytorch里面创建tensor
1、使用python中的列表或者序列创建tensor
torch,tensor([[1,-1],[1,-1]])

在这里插入图片描述
2、使用numpy中的数组创建tensor
在这里插入图片描述
3、使用torchd的api创建tensor
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

tensor的使用方法

1、获取tensor中的数据当tensor中只有一个元素可用:tensor.item()

在这里插入图片描述

2、转化为numpy数值
在这里插入图片描述

3、获得形状:tensor.size()

在这里插入图片描述

tensor.size()
tensor.size(1):"获得第一个维度的形状

4、形状改变 view,类似于numpy的reshape,是一种拷贝,仅仅是形状发生改变
在这里插入图片描述
5、获取维数:tensor.dim()

在这里插入图片描述

6、获取最大值

tensor.max/min/std

7、转置
在这里插入图片描述
如何理解
(2,3,4)

(3,2,4)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

梯度下降和反向传播

梯度是什么

梯度:是一个向量,导数+变化量快的方向(学习的前进方向)

a、向量
b、学习(参数更新)的方向

梯度下降

a、算出梯度
b、w=w-a▽w

总结:
梯度就是多元函数参数的变化趋势(参数学习的方向),只有一个自变量时称为导数

反向传播

nn.module

__init__需要调用super方法,集成父类的属性和方法
forward方法必须是实现,用来定义我们的网络的前向计算的过程

y=wx+b的模型

from torch import nn
class Lr(nn.Moudule):
	def __init__(self):
		# 继承父类Init的参数
		super(Lr,self).__init__()
		# y=wx+b,第一个参数,输入的形状,第二个参数,输出的形状
		# 实例化linear这个对象
		# 得到了一个线性的实例
		self.linear = nn.Linear(1,1)
		
	def forward(self,x):
		# 如果有数值需要穿到linear里面计算,得到最终输出的时候,调用下linear传入x
		out = self.linear(X)
		return out
	
	# x = torch.rand([500,1])
	# 500条数据,一列,500行,一列,所以这个列就是维度,维度为1,特征是1

	# 实例化模型
	model = Lr()
	# 传入参数,计算结果
	predict = model(x)

nn.Linear是torch预定义好的线性模型,也被称为全链接层
传入的参数为输入的数量,输出的数量

优化器类

optimizer: torch为我们封装的用来进行更新参数的方法。
比如随机梯度下降
torch.optim提供的
torch.optim.SGD(参数,学习率)
torch.optim.Adam(参数,学习率)

参数可以使用model.parameters()来获取,获取模型中所有requires_Grad = True 的参数

优化类的使用方法
1、实例化
2、所有参数的梯度,将其位置为0
3、反向传播计算梯度
4、更新参数值

optimizer = optim.SGD(model.paraters(),lr=le-3)
optimizer.zero_grad()
loss.backward()
optimizer.step()

损失函数
均方误差:nn.MSELoss() 常用与分类问题
交叉熵损失:nn.CrossEntropyLoss(),常用语逻辑回归

model = Lr()
# 实例化模型
criterion = nn.MSELoss()
# 实例化损失函数
optimizer = optim.SGD(model.parameters(),lr=le-3)
# 实例化优化器
for i in range(100):
	y_predict = model(x_true)
	# 向前计算预测值
	loss = criterion(y_true,y_predict)
	#调用损失函数传入真实值和预测值,得到损失结果
	optimizer.zero_grad()
	# 当前循环参数检查为0
	loss.backward()
	# 计算梯度
	optimizer.step()
	# 更新参数的值
	

理解神经网络的思想

使用transformer模型架构处理从一个语言文本到另一种语言文本的翻译工作。
Embedding层 文本嵌入层
完成词嵌入
embedding层产生的张量称为词嵌入张量,他的最后一维将称作词向量。

======================================================================

输入部分

在这里插入图片描述
inputs 是一个源文本
embedding 文本的嵌入层
位置编码器的处理

文本嵌入层
位置编码器

文本嵌入层的作用
无论是源文本嵌入还是目标文本嵌入,都是为了将文本词汇的数字表示转变为向量表示,希望在这样的高维空间捕捉词汇间的关系

为什么变高维了?因为信息表示更多了

输出部分
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

编码器部分
在这里插入图片描述

N是一个超参数
一个编码器层由两个子层来连接
多头的注意力
add & norm
规范化层
残差连接:跨层向上残差连接

解码器:

在这里插入图片描述
每个解码器有3个
一个多头注意力机制,加了一个mask,规范化,残差
两个这样子
一个前馈全连接子层

============================================================================

输入部分实现

了解文本嵌入层和位置编码的作用
掌握文本嵌入层和位置编码的实现过程

文本嵌入层的作用
无论是源文本嵌入还是目标文本嵌入,都是为了将文本词汇的数字表示转变为向量表示,希望在这样的高维空间捕捉词汇间的关系

nn.Embedding演示

# nn.Embedding演示
# 一个参数是单词的总数,一个是嵌入的维度
embedding = nn.Embedding(10,3)
# 输入张量的形状是两行四列
input = torch.LongTensor([[1,2,4,5],[4,3,2,9]])
print(embedding(input))
# 输出的结果代表了43列,3这个维度展示在列这个方向,第一行共同代表了1
# 每一个数字都被映射成为了3维的列表

结果

tensor([[[ 0.7780, -0.6919,  1.5326],
         [-0.2926, -0.7014, -1.0193],
         [-0.8627, -0.0836, -1.4387],
         [-0.4911, -0.0372, -0.5403]],

        [[-0.8627, -0.0836, -1.4387],
         [-0.7705,  1.7145,  0.6959],
         [-0.2926, -0.7014, -1.0193],
         [ 0.8205,  0.4603,  0.3768]]], grad_fn=<EmbeddingBackward0>)

在这里插入图片描述

tensor([[ 0.0000,  0.0000,  0.0000],
        [-0.5076, -0.1903, -0.2580],
        [ 0.0000,  0.0000,  0.0000],
        [ 0.6851,  1.4131,  0.6518]], grad_fn=<EmbeddingBackward0>)

在这里插入图片描述

python多行注释 ctrl+/

import torch
import torch.nn as nn
import math
# 封装函数Variable
from torch.autograd import Variable

# 定义embedding类来实现文本嵌入层,这里的s说明代表两个一模一样

> 这里是引用

的嵌入层,他们共享参数
class Embeddings(nn.Module):
    # 类的初始化,有两个参数,d_model:指词嵌入的维度,vocab:指词表的大小
    # 如果嵌入的是英文文本,vocab是英文文本的大小,如果是目标文本就是法文文本的大小
    def __init__(self,d_model,vocab):
        # 接着就是使用super的方式指明继承nn.Module的初始化函数
        super(Embeddings, self).__init__()
        # 调用nn中于定义层embdding, 获得一个词嵌入对象self.lut
        # 两个参数是词表的总词数和词表的维度
        self.lut = nn.Embedding(vocab,d_model)
        # 最后将d_model传入类中
        self.d_model = d_model

    # 可以将其理解为该层的前向传播逻辑,所有层都会有此函数
    # 参数x,因为embedding层是首层,所以代表输入给模型的文本通过词汇映射后的张量
    # 只有一个嵌入层,所以直接扔到这里面,就可以
    # 乘以是一个缩放的作用,映射的作用
    def forward(self,x):
        # 将x传给self.lut并与根号下self.d_model相乘作为结果返回
        # x代表输入的文本通过词汇映射后的数字张量
        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]])
# print(embedding(input))
# # 输出的结果代表了43列,3这个维度展示在列这个方向,第一行共同代表了1
# # 每一个数字都被映射成为了3维的列表
#
# #
# embedding = nn.Embedding(10,3,padding_idx=0)
# input = torch.LongTensor([0,2,0,5])
# print(embedding(input))
# # 第一个和第三个都被映射成为了0
#
# embedding = nn.Embedding(10,3,padding_idx=5)
# input = torch.LongTensor([0,2,0,5])
# print(embedding(input))


# 实例化参数
# 词嵌入维度是512维
d_model = 512
# 词表的大小是1000
vocab = 1000

# 输入参数
# 输入x是一个使用variable封装的长整型张量,形状是2*4
x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))

# 实例化一个emb
emb = Embeddings(d_model,vocab)
# 将x传入到里面
embr = emb(x)
print("embr:",embr)
print(embr.shape)

# 输出2*4*512
# 100 被映射为一个列表,512
# 242个维 4个数 每个数有一行512
D:\soft\anaocanda\envs\ssj\python.exe D:/info/code/pythonProject1/trans.py
embr: tensor([[[-1.1043e+01,  2.7552e+01, -8.9714e+00,  ...,  7.5044e+00,
           4.7409e+00, -2.1803e+01],
         [ 2.1296e+01,  4.5528e+01, -2.1740e+01,  ...,  8.6224e+00,
           1.0894e+01,  2.2324e+01],
         [-1.5769e+01,  6.8512e+00,  4.2896e+01,  ...,  2.6830e+01,
          -3.7023e+01, -7.3919e+00],
         [ 4.2120e+01, -6.5864e+00,  4.7775e+01,  ..., -1.7166e+00,
           5.9007e+00,  1.9379e+01]],

        [[ 6.5395e-01, -3.2113e+00,  1.0046e+01,  ...,  1.5939e+01,
           1.8397e+01, -1.8000e+01],
         [-1.4095e+01, -1.6762e+01,  5.0975e+00,  ..., -8.4803e+00,
          -3.8557e+00,  6.8540e+00],
         [-5.2886e+00,  2.2801e+01,  5.7622e+00,  ...,  1.0209e+01,
           5.9754e+00, -4.0796e+00],
         [-1.7370e+01, -1.2281e+01, -4.2703e-02,  ..., -2.6487e+01,
           3.7915e+01, -1.5407e+01]]], grad_fn=<MulBackward0>)
torch.Size([2, 4, 512])

进程已结束,退出代码0

位置编码器

在transformer的编码器中,并没有对词汇位置信息的处理,因此需要在embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中,以弥补位置信息的缺失

产生的位置张量有更多的信息

让我们的信息更丰富,编码之后的张量有更多的信息

Dropout演示

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import torch
import torch.nn as nn
import math
# 封装函数Variable
from torch.autograd import Variable

# 定义embedding类来实现文本嵌入层,这里的s说明代表两个一模一样

class Embeddings(nn.Module):
    # 类的初始化,有两个参数,d_model:指词嵌入的维度,vocab:指词表的大小
    # 如果嵌入的是英文文本,vocab是英文文本的大小,如果是目标文本就是法文文本的大小
    def __init__(self,d_model,vocab):
        # 接着就是使用super的方式指明继承nn.Module的初始化函数
        super(Embeddings, self).__init__()
        # 调用nn中于定义层embdding, 获得一个词嵌入对象self.lut
        # 两个参数是词表的总词数和词表的维度
        self.lut = nn.Embedding(vocab,d_model)
        # 最后将d_model传入类中
        self.d_model = d_model

    # 可以将其理解为该层的前向传播逻辑,所有层都会有此函数
    # 参数x,因为embedding层是首层,所以代表输入给模型的文本通过词汇映射后的张量
    # 只有一个嵌入层,所以直接扔到这里面,就可以
    # 乘以是一个缩放的作用,映射的作用
    def forward(self,x):
        # 将x传给self.lut并与根号下self.d_model相乘作为结果返回
        # x代表输入的文本通过词汇映射后的数字张量
        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]])
# print(embedding(input))
# # 输出的结果代表了4行3列,3这个维度展示在列这个方向,第一行共同代表了1
# # 每一个数字都被映射成为了3维的列表
#
# #
# embedding = nn.Embedding(10,3,padding_idx=0)
# input = torch.LongTensor([0,2,0,5])
# print(embedding(input))
# # 第一个和第三个都被映射成为了0
#
# embedding = nn.Embedding(10,3,padding_idx=5)
# input = torch.LongTensor([0,2,0,5])
# print(embedding(input))


# 实例化参数
# 词嵌入维度是512维
d_model = 512
# 词表的大小是1000
vocab = 1000

# 输入参数
# 输入x是一个使用variable封装的长整型张量,形状是2*4
x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))

# 实例化一个emb
emb = Embeddings(d_model,vocab)
# 将x传入到里面
embr = emb(x)
print("embr:",embr)
print(embr.shape)

# 输出2*4*512
# 100 被映射为一个列表,512
# 2行4列 2个维 4个数 每个数有一行512个

# 定义位置编码器类,我们同样把它看作一个层,因此会继承nn.Moudle
class PositionalEncoding(nn.Module):
    def __init__(self,d_model,dropout,max_len=5000):
        # 位置编码器类的初试化函数,共有三个参数,分别时d_model:词嵌入的维度,dropout:置0比率,max_len每个句子的最大程度
        super(PositionalEncoding,self).__init__()

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

        # 初始化一个位置编码矩阵,它是一个0阵,矩阵的大小是max_len * d_model
        pe = torch.zeros(max_len,d_model)

        # 初始化一个绝对位置矩阵,在我们这里,词汇的绝对位置就是用它的索引去表示的
        # 所以我们首先使用arange方法获得一个连续自然数向量,然后再使用unsqueeze方法拓展向量维度
        # 又因为参数传的是1,代表矩阵拓展的位置,会使向量变成一个max_len * 1 的矩阵
        # 在第一个维度上面扩展 所以就是max_len * 1
        position = torch.arange(0,max_len).unsqueeze(1)

        # 绝对位置矩阵初始化之后,接下来就是考虑如何将这些位置信息加入到位置编码矩阵中
        # 最简单的思路就是先将max_len *1 的绝对局长,变化成max_len * d_model形状,然后覆盖原来的初始位置编码矩阵即可
        # 要做这种矩阵变换,就需要一个1 * d_model 形状的变换矩阵 div_term,我们对这个变换矩阵的要求除了形状外
        # 还希望它能够将自然数的绝对位置编码维数缩放成足够小的数字,有助于再之后的梯度下降过程中更快的进行收敛
        # 首先使用arange获得一个自然树矩阵,但是这里并没有按照举起的一样初始化
        # 而是有了一个跳跃,只初始化了一半即 1 * d_model/2 的矩阵,为甚是一半呢,这里并不是真正意义上的一半的矩阵
        # 我们可以把它看作是初始化了两次,而每次初始化的变换矩阵会做不同的处理,第一次初始化的变化矩阵分布再正弦波上,第二次分布再预先波上
        # 并把这两个矩阵分别填充再位置编码矩阵的偶数和奇数位置上,组成最终的位置编码矩阵

        # 定义一个变化矩阵div_term,跳跃式的初始化
        div_term = torch.exp(torch.arange(0,d_model/2) * -(math.log(1000.0) / d_model))
        pe[:,0::2] = torch.sin(position * div_term)
        # 偶数类正弦波,sin只赋值到左边的256个位置的编码
        pe[:,1::2] = torch.cos(position * div_term)

        # 这样我们就得到了位置编码矩阵pe,pe现在还只是一个二维矩阵,要想和embedding的输出(一个三维张量)相加
        # 就必须拓展一个维度,所以这里使用unsqueeze拓展维度
        pe = pe.unsqueeze(0)

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

        #这个buffer不是模型的中的参数,不随着优化器同步变化
        # 注册成Buffer后我们就可以在模型保存后重新加载的时候,将这个位置编码器和模型参数一同加载进来
        self.register_buffer('pe',pe)

        # pe的值是顶死的,d_model的维度确定了,pe矩阵的值是死的,因为只算sin,cos

    def forward(self,x):
        #forward函数的参数是x,表示文本序列的词嵌入表示
        #在相加之前我们对pe做一些适配工作,将这三维张量的第二维也就是句子最大长度的那一维将切片到与输入的x的第二维相同即x.size(1)
        #因为我们默认max_len为5000太大了,很难有一个句子包含5000个词汇,所以要重新进行与输入张量进行适配
        # 最后使用Variable进行封装,使其与x的样本相同,但是它是不需要进行模型求解的,因为把requ
        # 截取到x.size(1),位置编码不参与更新
        x = x + Variable(self.pe[:,:x.size(1)],requires_grad = False)
        # 最后使用self.dropout对象进行‘丢弃’操作,并返回结果
        return self.dropout(x)

d_model =  512
dropout = 0.1
max_len = 60

x = embr
pe = PositionalEncoding(d_model,dropout,max_len)
pe_result = pe(x)
print(pe_result)
print(pe_result.shape)



结果

D:\soft\Anaconda\envs\py3.9\python.exe D:/soft/pycharm/pythonProject2/main.py
embr: tensor([[[  1.9032,  26.9969,  29.6788,  ...,   8.7173,  58.1351, -41.6023],
         [ -9.0137,  -2.8836,  32.3906,  ...,   5.4485,  15.6861,   0.6282],
         [ 23.8943,   8.3358,  21.4642,  ...,  -7.6993, -39.8723,  14.1040],
         [-35.3380,   7.6910,  20.3110,  ...,  26.8116, -20.7676, -42.1631]],

        [[-68.3004,   6.0939, -47.7095,  ...,  18.7444,   4.5372, -10.3177],
         [ -2.3122,  -7.0605,   3.9860,  ...,  27.3861,   2.9266,  22.8562],
         [ 14.3036,  15.4481,  40.7551,  ...,  -5.7721,  28.4123,  47.1052],
         [-34.6770, -11.5442,   2.9279,  ..., -34.1835,  24.5586, -26.2388]]],
       grad_fn=<MulBackward0>)
torch.Size([2, 4, 512])
tensor([[[  2.1147,  31.1076,  32.9764,  ...,  10.7970,  64.5945, -45.1136],
         [ -9.0802,  -2.6037,  36.9164,  ...,   0.0000,   0.0000,   1.8085],
         [ 27.5596,   8.7997,  24.8715,  ...,  -7.4460, -44.2314,  16.7799],
         [-39.1076,   7.4455,  22.7687,  ...,  30.8965, -22.9685, -45.7419]],

        [[-75.8893,   7.8822, -53.0105,  ...,  21.9382,   0.0000, -10.3530],
         [ -0.0000,  -7.2446,   5.3558,  ...,   0.0000,   3.2874,  26.5064],
         [ 16.9032,  16.7022,  46.3058,  ...,  -5.3047,  31.6404,  53.4479],
         [-38.3732, -13.9269,   3.4541,  ..., -36.8758,   0.0000, -28.0482]]],
       grad_fn=<MulBackward0>)
torch.Size([2, 4, 512])

进程已结束,退出代码0

出现错误

OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
OMP: Hint This means that multiple copies of the OpenMP runtime have been linked into the program. That is dangerous, since it can degrade performance or cause incorrect results. The best thing to do is to ensure that only a single OpenMP runtime is linked into the process, e.g. by avoiding static linking of the OpenMP runtime in any library. As an unsafe, unsupported, undocumented workaround you can set the environment variable KMP_DUPLICATE_LIB_OK=TRUE to allow the program to continue to execute, but that may cause crashes or silently produce incorrect results. For more information, please see http://www.intel.com/software/products/support/.

在这里插入图片描述

import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'

import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'

import matplotlib.pyplot as plt
import numpy as np
import torch
import torch.nn as nn
import math
# 封装函数Variable
from torch.autograd import Variable


# 定义embedding类来实现文本嵌入层,这里的s说明代表两个一模一样

class Embeddings(nn.Module):
    # 类的初始化,有两个参数,d_model:指词嵌入的维度,vocab:指词表的大小
    # 如果嵌入的是英文文本,vocab是英文文本的大小,如果是目标文本就是法文文本的大小
    def __init__(self,d_model,vocab):
        # 接着就是使用super的方式指明继承nn.Module的初始化函数
        super(Embeddings, self).__init__()
        # 调用nn中于定义层embdding, 获得一个词嵌入对象self.lut
        # 两个参数是词表的总词数和词表的维度
        self.lut = nn.Embedding(vocab,d_model)
        # 最后将d_model传入类中
        self.d_model = d_model

    # 可以将其理解为该层的前向传播逻辑,所有层都会有此函数
    # 参数x,因为embedding层是首层,所以代表输入给模型的文本通过词汇映射后的张量
    # 只有一个嵌入层,所以直接扔到这里面,就可以
    # 乘以是一个缩放的作用,映射的作用
    def forward(self,x):
        # 将x传给self.lut并与根号下self.d_model相乘作为结果返回
        # x代表输入的文本通过词汇映射后的数字张量
        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]])
# print(embedding(input))
# # 输出的结果代表了4行3列,3这个维度展示在列这个方向,第一行共同代表了1
# # 每一个数字都被映射成为了3维的列表
#
# #
# embedding = nn.Embedding(10,3,padding_idx=0)
# input = torch.LongTensor([0,2,0,5])
# print(embedding(input))
# # 第一个和第三个都被映射成为了0
#
# embedding = nn.Embedding(10,3,padding_idx=5)
# input = torch.LongTensor([0,2,0,5])
# print(embedding(input))


# 实例化参数
# 词嵌入维度是512维
d_model = 512
# 词表的大小是1000
vocab = 1000

# 输入参数
# 输入x是一个使用variable封装的长整型张量,形状是2*4
x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))

# 实例化一个emb
emb = Embeddings(d_model,vocab)
# 将x传入到里面
embr = emb(x)
print("embr:",embr)
print(embr.shape)

# 输出2*4*512
# 100 被映射为一个列表,512
# 2行4列 2个维 4个数 每个数有一行512个

# 定义位置编码器类,我们同样把它看作一个层,因此会继承nn.Moudle
class PositionalEncoding(nn.Module):
    def __init__(self,d_model,dropout,max_len=5000):
        # 位置编码器类的初试化函数,共有三个参数,分别时d_model:词嵌入的维度,dropout:置0比率,max_len每个句子的最大程度
        super(PositionalEncoding,self).__init__()

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

        # 初始化一个位置编码矩阵,它是一个0阵,矩阵的大小是max_len * d_model
        pe = torch.zeros(max_len,d_model)

        # 初始化一个绝对位置矩阵,在我们这里,词汇的绝对位置就是用它的索引去表示的
        # 所以我们首先使用arange方法获得一个连续自然数向量,然后再使用unsqueeze方法拓展向量维度
        # 又因为参数传的是1,代表矩阵拓展的位置,会使向量变成一个max_len * 1 的矩阵
        # 在第一个维度上面扩展 所以就是max_len * 1
        position = torch.arange(0,max_len).unsqueeze(1)

        # 绝对位置矩阵初始化之后,接下来就是考虑如何将这些位置信息加入到位置编码矩阵中
        # 最简单的思路就是先将max_len *1 的绝对局长,变化成max_len * d_model形状,然后覆盖原来的初始位置编码矩阵即可
        # 要做这种矩阵变换,就需要一个1 * d_model 形状的变换矩阵 div_term,我们对这个变换矩阵的要求除了形状外
        # 还希望它能够将自然数的绝对位置编码维数缩放成足够小的数字,有助于再之后的梯度下降过程中更快的进行收敛
        # 首先使用arange获得一个自然树矩阵,但是这里并没有按照举起的一样初始化
        # 而是有了一个跳跃,只初始化了一半即 1 * d_model/2 的矩阵,为甚是一半呢,这里并不是真正意义上的一半的矩阵
        # 我们可以把它看作是初始化了两次,而每次初始化的变换矩阵会做不同的处理,第一次初始化的变化矩阵分布再正弦波上,第二次分布再预先波上
        # 并把这两个矩阵分别填充再位置编码矩阵的偶数和奇数位置上,组成最终的位置编码矩阵

        # 定义一个变化矩阵div_term,跳跃式的初始化
        div_term = torch.exp(torch.arange(0,d_model/2) * -(math.log(1000.0) / d_model))
        pe[:,0::2] = torch.sin(position * div_term)
        # 偶数类正弦波,sin只赋值到左边的256个位置的编码
        pe[:,1::2] = torch.cos(position * div_term)

        # 这样我们就得到了位置编码矩阵pe,pe现在还只是一个二维矩阵,要想和embedding的输出(一个三维张量)相加
        # 就必须拓展一个维度,所以这里使用unsqueeze拓展维度
        pe = pe.unsqueeze(0)

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

        #这个buffer不是模型的中的参数,不随着优化器同步变化
        # 注册成Buffer后我们就可以在模型保存后重新加载的时候,将这个位置编码器和模型参数一同加载进来
        self.register_buffer('pe',pe)

        # pe的值是顶死的,d_model的维度确定了,pe矩阵的值是死的,因为只算sin,cos

    def forward(self,x):
        #forward函数的参数是x,表示文本序列的词嵌入表示
        #在相加之前我们对pe做一些适配工作,将这三维张量的第二维也就是句子最大长度的那一维将切片到与输入的x的第二维相同即x.size(1)
        #因为我们默认max_len为5000太大了,很难有一个句子包含5000个词汇,所以要重新进行与输入张量进行适配
        # 最后使用Variable进行封装,使其与x的样本相同,但是它是不需要进行模型求解的,因为把requ
        # 截取到x.size(1),位置编码不参与更新
        x = x + Variable(self.pe[:,:x.size(1)],requires_grad = False)
        # 最后使用self.dropout对象进行‘丢弃’操作,并返回结果
        return self.dropout(x)

d_model =  512
dropout = 0.1
max_len = 60

x = embr
pe = PositionalEncoding(d_model,dropout,max_len)
pe_result = pe(x)
print(pe_result)
print(pe_result.shape)

# 第一步设置一个画布
plt.figure(figsize=(15,5))

# 实例化PositionalEncoding类对象,
pe = PositionalEncoding(20,0)

# 向pe中传入一个全零初始化的x,相当于展示我们的Pe
y = pe(Variable(torch.zeros(1,100,20)))
# 这个y只是位置编码张量

# 画曲线
# pe代表三维的张量,: 所有的行,4到8列
plt.plot(np.arange(100),y[0,:, 4:8].data.numpy())

# 展示图像要有一个show
plt.show()
# 给一个维度信息
plt.legend(["dim %d"%p for p in [4,5,6,7]])


在这里插入图片描述

小结

学习了文本嵌入层的作用
无论是源文本嵌入还是目标文本嵌入,都是讲文本中词汇的数字表示转变为向量表示,希望在这样的高维空间捕捉词汇间的关系

学习并实现了文本嵌入层的类: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,这又很好的控制了嵌入数值的大小,有助于梯度的快速计算

编码器部分实现

了解编码器中各个组成部分的作用
掌握编码器中各个组成部分的实现过程

在这里插入图片描述

掩码张量

了解什么是掩码张量以及它的作用
掌握生成掩码张量的实现过程

什么是掩码张量

掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于0位置还是1位置被遮掩可以自定义,因此它的作用就是让另一个张量中的一些数值被遮掩,也可以说被替换,它的表现形式是一个张量。

掩码张量的作用

在transformer中,掩码张量的主要作用在应用attention,有一些生成的attention张量中的值计算有可能已知了未来信息得到的,未来信息被看到的是因为训练时会把整个输出结果都一次性进行embedding,但是理论上解码器的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用,所以,我们会进行遮掩。

有时候我读一篇文章,我读前半部分,已经直到后面的部分信息
解码器不是一次就产生的,而是一次次的产生的
因为未来的信息可能被提前利用
这时候我们才会被遮掩

生成掩码张量的代码分析

np.triu演示


# np.triu演示
# 移了一行,保证4,8,12的右上的值不变,左上就清零了
print(np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]],k=-1))

# 主对角线
print(np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]],k=0))

# 右上移了移
print(np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]],k=1))
D:\soft\Anaconda\envs\py3.9\python.exe D:/soft/pycharm/pythonProject2/main.py
[[ 1  2  3]
 [ 4  5  6]
 [ 0  8  9]
 [ 0  0 12]]
[[1 2 3]
 [0 5 6]
 [0 0 9]
 [0 0 0]]
[[0 2 3]
 [0 0 6]
 [0 0 0]
 [0 0 0]]

进程已结束,退出代码0

输入实例

# 输入实例
# 生成的掩码张量的最后两维的大小
size = 5

# 调用
sm = subsequent_mask(size)
print("sm:",sm)

# 输出效果
# 最后两维形成一个下三角阵
D:\soft\Anaconda\envs\py3.9\python.exe D:/soft/pycharm/pythonProject2/main.py
sm: tensor([[[1, 0, 0, 0, 0],
         [1, 1, 0, 0, 0],
         [1, 1, 1, 0, 0],
         [1, 1, 1, 1, 0],
         [1, 1, 1, 1, 1]]], dtype=torch.uint8)

进程已结束,退出代码0

掩码张量的可视化

# 掩码张量的可视化
# 给一个画布
plt.figure(figsize=(5,5))
# 20维把这个第0维取出来
plt.imshow(subsequent_mask(20)[0])
plt.show()
# 输出效果分析

在这里插入图片描述
在这里插入图片描述

# 输出效果分析

# 黄色的是1的部分,代表被遮掩,紫色代表没有被遮掩,横坐标代表目标词汇的位置,纵坐标表示可查看的位置
# 我们看到,在0的位置都被遮住了,1的位置还是黄色,第一次词还没有产生,从第二个位置看过去,就能看到位置1的词,其他位置看不到

掩码张量总结

学习了什么是掩码张量

掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于0位置还是1位置被遮掩可以自定义,因此它的作用就是让另一个张量中的一些数值被遮掩,也可以说被替换,它的表现形式是一个张量。

学习了掩码张量的作用
在transformer中,掩码张量的主要作用在应用attention,有一些生成的attention张量中的值计算有可能已知了未来信息得到的,未来信息被看到的是因为训练时会把整个输出结果都一次性进行embedding,但是理论上解码器的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用,所以,我们会进行遮掩。

有时候我读一篇文章,我读前半部分,已经直到后面的部分信息
解码器不是一次就产生的,而是一次次的产生的
因为未来的信息可能被提前利用
这时候我们才会被遮掩

学习并实现生成后遮掩的掩码张量函数:subsequent_mask
输入是size,代表掩码张量的大小
输出是一个最后两维形成1方针的下三角阵
最后对生成的掩码张量进行可视化分析,更深一步理解了它的用途

注意力机制

什么是注意力
我们观察事物时,之所以能够快速判断一种食物(当然允许判断是错误的),是因为我们大脑弄能够很快把注意力放在事物最具有辨识度的部分从而作出判断,而并非是从头到尾的观察一遍事物后,才能有判断结果,正式基于这样的理论,就产生了注意力机制。

什么是注意力计算规则
它需要三个指定的输入Q,K,V,通过公式得到注意力的结果,这个结果代表Q在K,V作用下的表示,而这个具体的计算规则有很多种

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

key和value一般情况下默认是相同的,与query不同,这种是我们一般的注意力输入形式
但有一种特殊情况,query k,v相同,自注意里机制。
一般注意力机制,使用不同于给定文本的关键词表示它,
自注意力机制,需要用给定文本自身来表达自己,也就是说你需要从给定文本中抽取关键词来表述它,相当于对文本自身的一次特征提取。

什么是注意力机制
注意力机制是注意力计算规则能够应用的深度学习网络的载体,除了注意力计算规则外,还包括一些必要的全连接层以及相关张量处理,使其与应用网络融为一体,使用自注意力规则的注意力机制称为自注意力机制

在这里插入图片描述

在这里插入图片描述
第一步输入
第二步:矩阵的乘法
第三步:scale 规范化层
第四步:mask 掩码层
第五步:softmax
最后得到的结果在进行一次惩罚

tensor.masked_fill演示

# tensor.masked_fill演示
input = Variable(torch.randn(5,5))
print(input)
mask = Variable(torch.zeros(5,5))
print(mask)
output = input.masked_fill(mask == 0,-1e9)
print(output)
D:\soft\Anaconda\envs\py3.9\python.exe D:/soft/pycharm/pythonProject2/main.py
tensor([[-0.2290,  1.7523,  0.5148,  0.7973,  0.1688],
        [ 1.0370, -0.6098,  0.0542, -2.0970,  1.4655],
        [-0.5541, -0.2751,  1.3962, -0.4723,  0.7442],
        [-0.3009,  0.8585, -0.2505, -0.9173, -0.2948],
        [-1.2695,  1.0996,  1.2564, -0.3282,  0.2156]])
tensor([[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.]])
tensor([[-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]])

进程已结束,退出代码0

# 注意力计算规则的代码分析
def attention(query,key,value,mask=None,dropout = None):
    # mask:掩码张量,dropout是nn.Dropout层的实例化对象,默认为None
    # 在函数中,首先取qeury的最后一维的大小,一般情况下等于我们的词嵌入维度,命名为d_k
    d_k = query.size(-1)

    # 按照注意力公式,将query与key的转置相乘,这里面key是将最后两个维度进行转置,再除以缩放系数
    # 得到注意力得分张量scores
    # query最后一个维度是词嵌入维度,K最后一个维度也是词嵌入维度,所以要转置,前面挪移个
    # torch里面固定的矩阵乘法matmul
    # transpose转置,哪两个维度呢,是-2和-1
    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注意力表示,同时返回query注意力表示注意力张量
    return torch.matmul(p_attn,value) , p_attn

# # tensor.masked_fill演示
# input = Variable(torch.randn(5,5))
# print(input)
# mask = Variable(torch.zeros(5,5))
# print(mask)
# # 所有对mask=0的位置,填充-1e9
# output = input.masked_fill(mask == 0,-1e9)
# print(output)

# 输入参数
# 我们令q,k,v都相同,位置编码的输出
# embeddings和位置编码作为query,k,v
query = key = value = pe_result

# 扔到attention里面
attn,p_attn = attention(query,key,value)
print("attn:",attn)
# 2*4*512
print(attn.shape)
print("p_attn:",p_attn)
# 2*4*4
print(p_attn.shape)

# 带有mask的输入参数
query = key = value = pe_result
# 令mask为一个2*4*4的零张量
mask = Variable(torch.zeros(2,4,4))
# 扔到attention里面
attn,p_attn = attention(query,key,value,mask=mask)
print("attn:",attn)
# 2*4*512
# 比较规范化
print(attn.shape)
print("p_attn:",p_attn)
# 2*4*4
print(p_attn.shape)
D:\soft\Anaconda\envs\py3.9\python.exe D:/soft/pycharm/pythonProject2/main.py
attn: tensor([[[  2.2906,  -3.1374,  40.3521,  ...,  30.8067,  15.9210,  39.8785],
         [ -3.8772,  -2.6398,  24.3268,  ..., -17.4089,   5.1689,   1.7967],
         [ 36.6114, -15.1562,  26.0079,  ...,   1.6383,  -9.0957,  -4.7327],
         [  0.0000, -17.5309,  26.4107,  ..., -25.2490, -42.9054,   5.5060]],

        [[ 18.5480,  10.3514,   0.0000,  ..., -40.6356,   0.0000,  26.8590],
         [  0.0000,  32.9057, -49.8491,  ..., -10.3907, -16.3110,  -6.7831],
         [-37.6501,  -1.4808,   0.0000,  ..., -49.6573,  -1.1568,  47.5696],
         [-42.2563, -67.4943,  35.4779,  ...,  18.9980, -13.9735,   0.0000]]],
       grad_fn=<UnsafeViewBackward0>)
torch.Size([2, 4, 512])
p_attn: tensor([[[1., 0., 0., 0.],
         [0., 1., 0., 0.],
         [0., 0., 1., 0.],
         [0., 0., 0., 1.]],

        [[1., 0., 0., 0.],
         [0., 1., 0., 0.],
         [0., 0., 1., 0.],
         [0., 0., 0., 1.]]], grad_fn=<SoftmaxBackward0>)
torch.Size([2, 4, 4])
attn: tensor([[[  8.7562,  -9.6161,  29.2743,  ...,  -2.5532,  -7.7278,  10.6121],
         [  8.7562,  -9.6161,  29.2743,  ...,  -2.5532,  -7.7278,  10.6121],
         [  8.7562,  -9.6161,  29.2743,  ...,  -2.5532,  -7.7278,  10.6121],
         [  8.7562,  -9.6161,  29.2743,  ...,  -2.5532,  -7.7278,  10.6121]],

        [[-15.3396,  -6.4295,  -3.5928,  ..., -20.4214,  -7.8603,  16.9114],
         [-15.3396,  -6.4295,  -3.5928,  ..., -20.4214,  -7.8603,  16.9114],
         [-15.3396,  -6.4295,  -3.5928,  ..., -20.4214,  -7.8603,  16.9114],
         [-15.3396,  -6.4295,  -3.5928,  ..., -20.4214,  -7.8603,  16.9114]]],
       grad_fn=<UnsafeViewBackward0>)
torch.Size([2, 4, 512])
p_attn: tensor([[[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]],

        [[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]]], grad_fn=<SoftmaxBackward0>)
torch.Size([2, 4, 4])

进程已结束,退出代码0

注意力机制总结

什么是注意力:
我们观察事物时,之所以能够快速判断一种食物(当然允许判断是错误的),是因为我们大脑弄能够很快把注意力放在事物最具有辨识度的部分从而作出判断,而并非是从头到尾的观察一遍事物后,才能有判断结果,正式基于这样的理论,就产生了注意力机制。

什么是注意力计算规则
它需要三个指定的输入Q,K,V,通过公式得到注意力的结果,这个结果代表Q在K,V作用下的表示,而这个具体的计算规则有很多种

学习了Q,K,V的比喻解释
Q是一段准备被概括的文本,K是给出的提示,V是大脑中对提示K的延申
当Q=K=V时,称为自注意力机制

什么是自主里机制
注意力机制是注意力计算规则能够应用的深度学习网络的载体,除了注意力计算规则外,还包括一些必要的全连接层以及相关张量处理,使其与应用网络融为一体,使用自注意力规则的注意力机制称为自注意力机制

学习并实现了注意力计算规则的函数attention
它的输入就是Q,K,V以及mask和dropout,mask用于掩码,dropout用于随机置0
它的输出有两个,query的注意力表示以及注意力张量

多头注意力机制

什么是多头注意力机制:

从多头注意力的结构图中,貌似多个头就是指多组线性变换层,其实不是,只是用了一组线性变换层,即三个变换张量Q,K,V分别进行线性变换,这些变换不会改变原有张量的尺寸,因此每个变换矩阵都是方阵,得到输出结果后,多头的作用才开始显现,每个头开始从词义层面分割输出的张量,每一个头都获得一组Q,K,V进行注意力机制的计算,但是句子中的每个词的表示只获取一部分,也就是只分割了最后一维的词嵌入向量,这就是多头,每个头获得的东西送到注意i机制当中,就形成了多头注意力机制

在这里插入图片描述
concat连接,linear线性层

多图注意力机制的作用
能够让每个注意力机制取优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表达,实验表明可以从而提升模型效果。

h是层层堆叠的。

多头注意力机制的代码实现

# 我们使用一个类来实现多头注意力机制的处理
class MultiHeadedAttention(nn.Module):
    def __init__(self,head,embedding_dim,dropout=0.1):
        """在类的初始化时,会传入三个参数,head代表头数,embedding_dim代表词嵌入的维度,dropout代表进行dropout操作时置0比率,默认是0.1"""
        super(MultiHeadedAttention,self).__init__()

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

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

        # 传入头数
        self.head = head

        # 然后获得线性层对象,通过nn的Linear实例化,它的内部变换矩阵是embedding_dim * embedding_dim,然后使用clones
        # 为什么是四个呢,这是因为在多头注意力中,W,KV个需要一个,最后拼接的矩阵还需要一个
        # 准备拷贝一个全连接的线性层,输入张量,输出张量,输入embedding_dim 多头注意力里面一定是一个方阵
        self.linears = clones(nn.Linear(embedding_dim,embedding_dim),4)

        # self.attn为none,它代表最后得到的注意力张量,现在还没有结果所以为None.
        self.attn - None

        # 最后一个就是self.dropout对象,它通过nn中droupout实例化而来,指令比率为传进来的参数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拓展维度,代表多头中的第N头
            mask = mask.unsqueeze(1)


        # 接着,我们获得一个batch_size的变量,它是query尺寸的第1个数字,代表有多少条样本
        batch_size = query.size(0)

        # 之后进入多头处理环节
        # 首先利用zip将输入QKV与三个线性层组成到一起,然后使用for循环,将输入QKV分别传到线性层中,self.linears代表连续拷贝的四个线性层,后面穿了三个值,zip以这个置表示,只用了前三个线性层,后面的不匹配
        # 每一次for循环,model方阵,x query key value
        # model(x)相当于我们一个输入扔到一个全连接线性层里面,view(batch_size,-1,self.head,self.d_k).transpose(1,2)
        # 我们把张量进行了一个划分,
        # 全部默认是batch_size,第二个是-1,
        # self.head * self.d_k表示embedding_dim
        # .transpose(1,2)自适应的维度和head的维度进行一个转置,第一个维度和第二个维度,就是-1,和self.head
        # 计算机会genuine这种变换自动计算这里的置,然后对第二维和第三位进行转置操作
        # 为了让代表句子长度维度和词向量维度能够相邻,这样注意力机制才能找到词义与句子位置的关系
        # 从attention函数中可以看到,利用的是原始维度的倒数和第二维,这样我们就得到了每个头的


        # 做完线性变换后,开始为每个头分割输入,这里使用view方法对线性变换的结果进行维度重塑,多
        # 这样就意味着每个头可以获得一部分词特征组成的句子,其中的-1代表自适应维度

        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))]
        # 一个ModeL,一个x都是从zip里面取出来的,self.linear都是从线性层里面取出来的,以元组形式传进去,传了三个值,
        # 以一个值传参只用到了self.inears里面
        # 每个for循环,拿出一个model们四个线性层,x后面就是输入的
        # model(x)一个输入扔到全连接线性层里面,变换完之后有一个输出,view就是对线性变化的结果进行维度重塑,进行了四个维度,-1代表姿势一维度
        # 全部默认是batch_size,最后一个维度我们一般是embedding_dim,我们拆分了两个,一个头,一个词嵌入维度,head,d_k,后两个维度保证了,第二个维度自适应
        # 所以我们把model(X)变味了四维的张量,之后在再

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

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

        # 最后使用线性层列表中的最后一个线性层对输入进行线性变换得到最终的多头注意力结构的输出。
        # 用第四个把x扔进去,就是最后一个linear
        return self.linears[-1](x)
    # tensor.view演示
    x = torch.randn(4,4)
    print(x.size())
    y = x.view(16)
    print(y.size())
    z = x.view(-1,8)
    print(z.size())

    a = torch.randn(1,2,3,4)
    print(a.size())
    b = a.transpose(1,2)
    print(b.size())
    c = a.view(1,3,2,4)
    print(c.size())
    print(torch.equal(b,c))

    # torch.transpose演示
    x = torch.randn(2,3)
    print(x)
    # 第0个维度和第1相互转换
    print(torch.transpose(x,0,1))

D:\soft\Anaconda\envs\py3.9\python.exe D:/soft/pycharm/pythonProject2/main.py
torch.Size([2, 4, 4])
torch.Size([4, 4])
torch.Size([16])
torch.Size([2, 8])
torch.Size([1, 2, 3, 4])
torch.Size([1, 3, 2, 4])
torch.Size([1, 3, 2, 4])
False
tensor([[-0.5042, -0.4165, -1.7043],
        [-0.4836, -0.5108, -1.4535]])
tensor([[-0.5042, -0.4836],
        [-0.4165, -0.5108],
        [-1.7043, -1.4535]])

进程已结束,退出代码0

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

向上Claire

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值