1 前馈全连接层

前馈全连接层 (Multi-Head Attention) 概念:Transformer中前馈全连接层就是具有两层线性层的全连接网络

为什么需要前馈全连接层?

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

class PositionwiseFeedForward(nn.Module):
	# 传入输入的特征维度,输出的特征维度
	 def __init__(self, d_model, d_ff, dropout=0.1):
        super(PositionwiseFeedForward, self).__init__()
		# 定义线性层1
        self.w1 = nn.Linear(d_model, d_ff)

        # 定义线性层2
        self.w2 = nn.Linear(d_ff, d_model)

        # dropout层
        self.dropout = nn.Dropout(p=dropout)

	def foward(self,x):
		x = self.w2(self.dropout(F.relu(self.w1(x1))))
		return x

def dm04_test_PositionwiseFeedForward():

    # 1 实例化PositionwiseFeedForward
    d_model, d_ff = 512, 1024
    my_positionwisefeedforward =  PositionwiseFeedForward(d_model, d_ff)
    print('my_positionwisefeedforward-->', my_positionwisefeedforward)

    # 2 给模型喂数据
    pe_result  = torch.randn(2, 4, 512)
    ff_result =  my_positionwisefeedforward(pe_result)
    print('ff_result--->', ff_result.shape, ff_result)

在这里插入图片描述

2 规范化层

规范化层(Normalization Layer)概念

  • 是一种用于对输入数据进行归一化的重要组件。它通常紧随着每个子层的输出,以确保网络在处理数据时保持稳定的分布
  • 规范化层实际上是一种归一化的操作,旨在使输入的均值保持接近0,标准差保持接近1

规范化层的作用:

  • 防止梯度消失和梯度爆炸:在深度神经网络中,随着层数的增加,梯度很容易变得非常小(梯度消失)或非常大(梯度爆炸)。
  • 规范化层通过将输入数据归一化,可以缓解这一问题,使得梯度的传播更加稳定。

计算方式:

  • 计算每个位置的所有特征维度上的均值和方差
  • 然后对该位置的所有特征维度进行线性变换,以保证均值为0,方差为1
  • 最后再进行缩放和平移操作,引入了可学习的参数(缩放因子和平移因子)。

在这里插入图片描述

# 规范化层 LayerNorm 实现思路分析
# 1 init函数  (self, features, eps=1e-6):
   # 定义线性层self.a2 self.b2, nn.Parameter(torch.ones(features))
# 2 forward(self, x) 返回标准化后的结果
    # 对数据求均值 保持形状不变 x.mean(-1, keepdims=True)
    # 对数据求方差 保持形状不变 x.std(-1, keepdims=True)
    # 对数据进行标准化变换 反向传播可学习参数a2 b2
    # eg self.a2 * (x-mean)/(std + self.eps) + self.b2

# 1 按照批次,学习所有批次数据的均值和方差, 最终训练出来一个均值和方差,来更好的适应数据
# 2 到预测阶段,就要让这个值不要再变化,设置模型为:
    # 2-1 model.eval()模式,在评估阶段 不让a2 b2这样的数据不发生变化
    # 2-2 with torch.no_grad(): 在评估阶段,不进行梯度更新
class LayerNorm(nn.Module):

    def __init__(self, features, eps=1e-6):

        super(LayerNorm, self).__init__()
        # features指的是特征数,每个指都需要进行变换
        # 定义线性层参数 a2 相当于y = k x + b 中的k
        self.a2 = nn.Parameter(torch.ones(features))

        # 定义线性层参数 b2  相当于y = k x + b 中的b
        self.b2 = nn.Parameter(torch.zeros(features))

        self.eps = eps

    def forward(self, x):

        # 对数据求均值 保持形状不变 x.mean(-1, keepdims=True) # [2,4,512] --->[2,4,1]
        mean = x.mean(-1, keepdims = True)

        # 对数据求方差 保持形状不变 x.std(-1, keepdims=True) # [2,4,512] --->[2,4,1]
        std = x.std(-1, keepdims = True)

        # 对数据进行标准化变换后,在不同批次之间学习参数a2 b2 让模型对所有数据有更好的适应性
        # 反向传播可学习参数a2 b2
        y = self.a2 * (x-mean)/(std+self.eps) + self.b2
        return y


def dm05_test_LayerNorm():

    # 实例化标准化层
    mylayernorm =  LayerNorm(512)
    print('mylayernorm--->', mylayernorm)

    # 给模型喂数据
    pe_result = torch.randn(2, 4, 512)
    layernorm_result =  mylayernorm(pe_result)
    print('layernorm_result--->', layernorm_result, layernorm_result.shape)

在这里插入图片描述

在这里插入图片描述

  • a2 和 b2 是额外增加的权重参数,为了让模型更好适配不同批次的数据,学习不同批次之间的数据特征,能让模型有更好的泛化能力
  • 原始数据调整为均值为0,方差为1的数据之后,再次经过线性变换
    y = a2*x + b2 更好的适配不同批次的数据
  • a2 和 b2是模型参数,会随着反向传播,进行更新
  • 按照批次,学习所有数据的均值和方差, 最终训练出来一个均值和方差,来更好的适应数据
  • 到预测阶段,就要让这个值不要再变化,设置模型为:
    • model.eval()模式,在评估阶段 不让a2 b2这样的数据不发生变
    • with torch.no_grad(): 在评估阶段,不进行梯度更新

LayerNorm和BatchNorm的区别
在这里插入图片描述

3 子层连接结构

子层连接结构组成

  • 子层(多头注意力子层或者 前馈全连接层)
  • 规范化层
  • 残差连接

在这里插入图片描述
子层连接结构的设计思想:

  • 功能上实现x+x2
  • 设计思想:函数的入口地址做函数参

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

# 子层连接结构 子层(前馈全连接层 或者 注意力机制层)+ norm层 + 残差连接
# SublayerConnection实现思路分析
# 1 init函数  (self, size, dropout=0.1):
   # 定义self.norm层 self.dropout层, 其中LayerNorm(size)
# 2 forward(self, x, sublayer) 返回+以后的结果
    # 数据self.norm() -> sublayer()->self.dropout() + x
class SublayerConnection(nn.Module):

    def __init__(self, size, dropput=0.1):
        
        super(SublayerConnection, self).__init__()

        # 实例化规范化层对象
        self.norm = LayerNorm(size)

        # 定义dropout层
        self.dropput = nn.Dropout(p = dropput)

    # self.feed_forward
    def forward(self, x, sublayer):
        '''x -数据  sublayer回调函数的入口地址'''
        # ====》self.feed_forward(x)
        # self.self_attn(x) , self.self_attn(x,x,x,mask)
        # 问题是:为什么  回调函数的入口地址为什么 self.feed_forward可以这样写
        # 而 注意力机制的回调函数入口不能这样写self.self_attn(x)
        # 答:注意力机制的回调函数入口这样写,掉不起来 。因为注意力机制的forward需要有4个参数
        # x2 = self.norm(x)
        # x2 = sublayer(x2)
        # x2 = self.dropput(x2)
        # x = x + x2

        # 参数 x代表数据
        # sublayer 函数入口地址 子层函数(前馈全连接层 或者 注意力机制层函数的入口地址)
        # 方式1 如果采用前norm,编码部分最后要保证 再多加一个norm运行
        x = x + self.dropput(sublayer(self.norm(x)))
        # # 方式2
        # x = x + self.dropput(self.norm(sublayer(x)) )
        # # 方式3
        # x = self.norm (x + self.dropput(sublayer(x)))
        return x


def dm06_test_SublayerConnection():
    size = 512
    # 实例化子层连接结构对象
    my_sublayerconnection = SublayerConnection(size)
    print('my_sublayerconnection--->', my_sublayerconnection)

    # 给模型喂数据
    # 1-1 准备数据
    x = torch.randn(2, 4, 512)

    # 1-2 准备函数的入口地址
    # 实例化多头注意力机制对象
    mask = Variable(torch.zeros(8, 4, 4))
    my_mha = MultiHeadedAttention(8, 512, 0.1)
    # 构建多头注意力机制对象的forward函数的 入口地址
    sublayer = lambda x:my_mha(x, x, x, mask)

    # my_sublayerconnection为数据
    sublayerconnection_result = my_sublayerconnection(x, sublayer)
    print('sublayerconnection_result--->', sublayerconnection_result.shape, sublayerconnection_result)

在这里插入图片描述

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐