基于位置的前馈神经网络

news/2024/7/24 12:14:59 标签: 神经网络, 人工智能, 深度学习

目录

1、什么是前馈全连接层

2、前馈全连接层的作用

3、代码实现FFN


1、什么是前馈全连接层

在Transformers中前馈全连接层就是具有两层线性层的全连接网络

2、前馈全连接层的作用

考虑注意力机制可能对复杂过程的拟合程度不够,通过增加家两层网络来增强模型的能力

3、代码实现FFN

import torch.nn as nn
import numpy as np
from torch.autograd import Variable
from torch.autograd import Variable
import copy
from torch import tensor, softmax
import math
# 构建Embedding类来实现文本嵌入层
class Embeddings(nn.Module):
    def __init__(self,vocab,d_model):
        """
        :param vocab: 词表的大小
        :param d_model: 词嵌入的维度
        """
        super(Embeddings,self).__init__()
        self.lut = nn.Embedding(vocab,d_model)
        self.d_model = d_model
    def forward(self,x):
        """
        :param x: 因为Embedding层是首层,所以代表输入给模型的文本通过词汇映射后的张量
        :return:
        """
        return self.lut(x) * math.sqrt(self.d_model)
class PositionalEncoding(nn.Module):
    def __init__(self,d_model,dropout,max_len=5000):
        """
        :param d_model: 词嵌入的维度
        :param dropout: 随机失活,置0比率
        :param max_len: 每个句子的最大长度,也就是每个句子中单词的最大个数
        """
        super(PositionalEncoding,self).__init__()
        self.dropout = nn.Dropout(p=dropout)
        pe = torch.zeros(max_len,d_model) # 初始化一个位置编码器矩阵,它是一个0矩阵,矩阵的大小是max_len * d_model
        position = torch.arange(0,max_len).unsqueeze(1) # 初始一个绝对位置矩阵 max_len * 1
        div_term = torch.exp(torch.arange(0,d_model,2)*-(math.log(1000.0)/d_model)) # 定义一个变换矩阵,跳跃式的初始化
        # 将前面定义的变换矩阵进行奇数、偶数的分别赋值
        pe[:,0::2] = torch.sin(position*div_term)
        pe[:,1::2] = torch.cos(position*div_term)
        pe = pe.unsqueeze(0)  # 将二维矩阵扩展为三维和embedding的输出(一个三维向量)相加
        self.register_buffer('pe',pe) # 把pe位置编码矩阵注册成模型的buffer,对模型是有帮助的,但是却不是模型结构中的超参数或者参数,不需要随着优化步骤进行更新的增益对象。注册之后我们就可以在模型保存后重加载时,将这个位置编码与模型参数一同加载进来

    def forward(self, x):
        """
        :param x: 表示文本序列的词嵌入表示
        :return: 最后使用self.dropout(x)对对象进行“丢弃”操作,并返回结果
        """
        x = x + Variable(self.pe[:, :x.size(1)],requires_grad = False)   # 不需要梯度求导,而且使用切片操作,因为我们默认的max_len为5000,但是很难一个句子有5000个词汇,所以要根据传递过来的实际单词的个数对创建的位置编码矩阵进行切片操作
        return self.dropout(x)

def subsequent_mask(size):
    """
    :param size: 生成向后遮掩的掩码张量,参数 size 是掩码张量的最后两个维度大小,它的最后两个维度形成一个方阵
    :return:
    """
    attn_shape = (1,size,size) # 定义掩码张量的形状
    subsequent_mask = np.triu(np.ones(attn_shape),k = 1).astype('uint8') # 定义一个上三角矩阵,元素为1,再使用其中的数据类型变为无符号8位整形
    return torch.from_numpy(1 - subsequent_mask) # 先将numpy 类型转化为 tensor,再做三角的翻转,将位置为 0 的地方变为 1,将位置为 1 的方变为 0

def attention(query, key, value, mask = None, dropout = None):
    """
    :param query: 三个张量输入
    :param key: 三个张量输入
    :param value: 三个张量输入
    :param mask: 掩码张量
    :param dropout: 传入的 dropout 实例化对象
    :return:
    """
    d_model = query.size(-1)  # 得到词嵌入的维度,取 query 的最后一维大小
    scores = torch.matmul(query,key.transpose(-2,-1)) / math.sqrt(d_model)     # 按照注意力公式,将 query 和 key 的转置相乘,这里是将 key 的最后两个维度进行转置,再除以缩放系数,得到注意力得分张量 scores
    if mask is not None:
        scores = torch.masked_fill(scores,mask == 0,-1e9)  # 使用 tensor 的 mask_fill 方法,将掩码张量和 scores 张量中每一个位置进行一一比较,如果掩码张量处为 0 ,则使用 -1e9 替换
        # scores = scores.masked_fill(mask == 0,-1e9)
    p_attn = softmax(scores, dim = -1) # 对 scores 的最后一维进行 softmax 操作,使用 F.softmax 方法,第一个参数是 softmax 对象,第二个参数是最后一个维度,得到注意力矩阵
    print('scores.shape ',scores.shape)
    if dropout is not None:
        p_attn = dropout(p_attn)
    return torch.matmul(p_attn,value),p_attn  # 返回注意力表示
class MultiHeadAttention(nn.Module):
    def __init__(self, head, embedding_dim , dropout=0.1):
        """
        :param head: 代表几个头的参数
        :param embedding_dim: 词向量维度
        :param dropout: 置零比率
        """
        super(MultiHeadAttention, self).__init__()
        assert embedding_dim % head == 0     # 确认一下多头的数量可以整除词嵌入的维度 embedding_dim
        self.d_k = embedding_dim // head  # 每个头获得词向量的维度
        self.head = head
        self.linears = nn.ModuleList([copy.deepcopy(nn.Linear(embedding_dim, embedding_dim)) for _ in range(4)])   # 深层拷贝4个线性层,每一个层都是独立的,保证内存地址是独立的,分别是 Q、K、V以及最终的输出线性层
        self.attn = None   # 初始化注意力张量
        self.dropout = nn.Dropout(p=dropout)
    def forward(self, query, key, value, mask=None):
        """
        :param query: 查询query [batch size, sentence length, d_model]
        :param key: 待查询key [batch size, sentence length, d_model]
        :param value: 待查询value [batch size, sentence length, d_model]
        :param mask: 计算相似度得分时的掩码(设置哪些输入不计算到score中)[batch size, 1, sentence length]
        :return:
        """
        if mask is not None:
            mask = mask.unsqueeze(1) # 将掩码张量进行维度扩充,代表多头中的第 n 个头
        batch_size = query.size(0)
        query, key, value = [l(x).view(batch_size, -1, self.head, self.d_k).transpose(1, 2) for l, x in zip(self.linears, (query, key, value))]  # 将1、2维度进行调换,目的是让句子长度维度和词向量维度靠近,这样注意力机制才能找到词义与句子之间的关系
        # 将每个头传递到注意力层
        x, self.attn = attention(query, key, value, mask=mask,
                                      dropout=self.dropout)
        # 得到每个头的计算结果是 4 维的张量,需要形状的转换
        # 前面已经将1,2两个维度进行转置了,所以这里要重新转置回来
        # 前面已经经历了transpose,所以要使用contiguous()方法,不然无法使用 view 方法
        x = x.transpose(1, 2).contiguous() \
            .view(batch_size, -1, self.head * self.d_k)
        return self.linears[-1](x)  # 在最后一个线性层中进行处理,得到最终的多头注意力结构输出
import torch.nn as nn
import torch
class PositionFeedForward(nn.Module):
    def __init__(self, d_model, d_ff, dropout=0.1):
        """
        :param d_model: 输入维度,词嵌入的维度
        :param d_ff: 第一个的输出连接第二个的输入
        :param dropout: 置零比率
        """
        super(PositionFeedForward, self).__init__()
        self.linear1 = nn.Linear(d_model, d_ff)
        self.linear2 = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(p = dropout)
    def forward(self, x):
        return self.linear2(self.dropout(torch.relu(self.linear1(x))))
# 实例化参数
d_model = 512
dropout = 0.1
max_len = 60  # 句子最大长度
#-----------------------------------------词嵌入层
# 输入 x 是 Embedding层输出的张量,形状为 2 * 4 * 512
x = Variable(torch.LongTensor([[100,2,42,508],[491,998,1,221]]))
emb = Embeddings(1000,512)   # 嵌入层
embr = emb(x)
#-----------------------------------------位置编码
pe = PositionalEncoding(d_model, dropout,max_len)  # 位置编码
pe_result = pe(embr)
#-----------------------------------------多头注意力机制
head = 8
embedding_dim = 512
dropout = 0.2
query = key = value = pe_result
mask = torch.zeros(2,4,4)
mha = MultiHeadAttention(head,embedding_dim,dropout)
mha_result = mha(query,key,value,mask)
#-----------------------------------------FFN
d_model = 512
d_ff = 62
dropout = 0.2
ff = PositionFeedForward(d_model,d_ff,dropout)
x = mha_result
ff_result = ff(x)
print(ff_result)
print(ff_result.shape)

scores.shape  torch.Size([2, 8, 4, 4])
tensor([[[ 0.4427, -1.4212, -0.3038,  ..., -0.0478, -0.9644,  1.0128],
         [-0.1761, -1.7056,  0.2831,  ..., -0.2049, -1.4458,  0.6031],
         [-1.0499, -0.6499, -0.6668,  ..., -0.4854, -1.1480,  0.2326],
         [-0.4020, -1.5800,  0.0879,  ...,  0.4629, -0.1393,  0.6648]],

        [[ 0.9657, -1.0271, -0.8961,  ..., -2.3648, -0.1479, -0.0632],
         [ 1.1462, -0.6441, -0.6195,  ..., -1.3566, -0.8735, -0.9672],
         [ 0.7842, -0.7735, -0.1261,  ..., -0.8950, -0.1457, -0.5313],
         [ 0.2848, -0.3641,  0.3231,  ..., -3.7226,  1.0617, -0.1215]]],
       grad_fn=<AddBackward0>)
torch.Size([2, 4, 512])


http://www.niftyadmin.cn/n/5078590.html

相关文章

arc 166 a

#include<bits/stdc.h> using namespace std; using VI vector<int>; using ll long long; const int mod 998244353; //当只有ab的时候&#xff0c;看作把a可以向右移动 //1 - x 是a 1 - y a //x中的 a 的 下标 < y 中 a 的下标 //这样就可以通过位移得到 …

TCP习题总结

1、在采用TCP连接的数据传输阶段&#xff0c;如果发送端的发送窗口值由1000变为2000&#xff0c;那么发送端在收到一个确认之前可以发送&#xff08;&#xff09;。 A. 2000个TCP报文段 B. 2000B C. 1000B D. 1000 个 TCP 报文 这道题考察的是TCP的基本…

Python各文件类型对比: .py、.ipynb、.pyi、.pyc、.pyd

在Python中&#xff0c;各文件类型的作用如下&#xff1a; .py文件&#xff1a;是Python代码文件的标准扩展名&#xff0c;其中包含可执行的Python代码。 .ipynb文件&#xff1a;是Jupyter Notebook的文件扩展名&#xff0c;其中包含交互式代码、文本、图像、公式等多种元素&am…

【学习笔记】[ARC145F] Modulo Sum of Increasing Sequences

单位根反演好题。 提示&#xff1a;是照搬的 这篇题解 的做法&#xff0c;只是加了一点小小的解释。 首先&#xff0c;做等价变换&#xff1a;给第 i i i个位置加上 i − 1 i-1 i−1&#xff0c;问题转化为了求单调递增序列&#xff0c;即从 [ 0 , N M − 1 ] [0,NM-1] [0,N…

rustlings本地开发环境配置

克隆自己的仓库 首先我们要在github上找到自己仓库并把它克隆到本地 git clone https://github.com/LearningOS/rust-rustlings-2023-autumn-******.git下载插件 rust-analyzer和Git Graph一个可以用来解析rust代码&#xff0c;另一个可以可视化管理git代码库 下载rustling…

4.springcloudalibaba sentinel v1.8.6版本服务搭建

文章目录 前言一、sentinel服务端安装1.1 服务端下载1.2 启动sentinel服务 二、客户端使用sentinel2.1.pom增加sentinel包2.2 增加配置2.3 启动服务 三、验证3.1 给hello接口增加流控规则3.2 测试结果如下 总结 前言 前面完成了gateway项目部署并且测试&#xff0c;现在部署搭…

Python 五人分鱼

A、B、C、D、E 五人在某天夜里合伙去捕鱼&#xff0c;到第二天凌晨时都疲惫不堪&#xff0c;于是各自找地方睡觉。 日上三杆&#xff0c;A 第一个醒来&#xff0c;他将鱼分为五份&#xff0c;把多余的一条鱼扔掉&#xff0c;拿走自己的一份。 B 第二个醒来&#xff0c;也将鱼分…

程序员不写注释的原因

程序员不写注释的原因 时间压力&#xff1a;在开发过程中&#xff0c;程序员常常面临紧迫的时间限制&#xff0c;注释可能被视为额外的工作&#xff0c;被忽略或推迟。自我理解&#xff1a;有些程序员可能认为他们编写的代码非常清晰易懂&#xff0c;不需要注释来解释代码的意…