You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何TransformerEncoder需实例化两个LayerNormalization层?

为什么Transformer编码器要实例化两个相同的LayerNormalization层?

以下代码来自Francois Chollet所著《Python深度学习(第二版)》,是Transformer编码器自定义Layer子类的实现。我注意到代码里专门实例化了两个LayerNormalization层,从定义看二者完全一致,想了解作者这么实现的原因。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

class TransformerEncoder(layers.Layer):
   # 这是继承自keras.layers.Layer的自定义层,用于实现Transformer模型的编码器部分,采用多头注意力机制。

   def __init__(self, embed_dim, dense_dim, num_heads, **kwargs):
       super().__init__(**kwargs)
       self.embed_dim = embed_dim  # 输入嵌入的维度
       self.dense_dim = dense_dim  # 前馈网络中全连接层的维度
       self.num_heads = num_heads  # 多头注意力机制中的头数
       self.attention = layers.MultiHeadAttention(
           num_heads=num_heads, key_dim=embed_dim)  # 用于计算注意力权重的MultiHeadAttention实例
       self.dense_proj = keras.Sequential(
           [layers.Dense(dense_dim, activation="relu"),  # 输出维度为dense_dim,输入维度自动匹配
            layers.Dense(embed_dim),]
       )  # 包含两个全连接层的Sequential模型,用于处理注意力输出
       self.layernorm_1 = layers.LayerNormalization()  # 用于归一化输入数据的LayerNormalization层
       self.layernorm_2 = layers.LayerNormalization()  # 用于归一化前馈网络输出的另一个LayerNormalization层

   def call(self, inputs, mask=None):
       # 该方法接受两个参数:inputs(输入数据)和mask(可选的掩码,通常用于标记序列中需忽略的位置,比如填充序列)

       if mask is not None:
           mask = mask[:, tf.newaxis, :]  # 将掩码张量的形状从(batch_size, sequence_length)扩展为(batch_size, 1, sequence_length)

       attention_output = self.attention(inputs, inputs, attention_mask=mask)  # 使用MultiHeadAttention层计算注意力权重

       proj_input = self.layernorm_1(inputs + attention_output)  # 使用LayerNormalization层归一化输入与注意力输出的和

       proj_output = self.dense_proj(proj_input)  # 通过前馈网络处理归一化后的输入

       return self.layernorm_2(proj_input + proj_output)  # 使用另一个LayerNormalization层归一化proj_input与proj_output的和

   def get_config(self):
       # 该方法返回包含层配置的字典,包括层的类型、参数、权重等。当使用model.save()保存模型时,Keras会用此配置重建模型结构。

       config = super().get_config()  # 先调用父类的get_config方法,获取包含父类配置的字典

       config.update({
           "embed_dim": self.embed_dim,
           "num_heads": self.num_heads,
           "dense_dim": self.dense_dim,
       })
       return config  # 然后用自定义层的特定配置(embed_dim、num_heads、dense_dim)更新字典,确保保存模型时自定义层的配置被保存

核心原因解析:

  • LayerNormalization是带可学习参数的层:每个LayerNormalization实例都有独立的缩放参数(gamma)和偏移参数(beta),这些参数会在训练中根据所在位置的输入分布独立优化。两个LN层处理的输入数据分布完全不同:第一个处理的是「原始输入+注意力输出」的残差和,第二个处理的是「前馈输入+前馈输出」的残差和,独立的LN层能针对性适配不同分布,提升模型表达能力。
  • 遵循Transformer原始设计:Transformer论文中,每个残差模块后的层归一化都是独立组件,各自维护参数。如果复用同一个LN层,相当于强制共享gamma和beta,会限制模型拟合能力——两个位置的输入统计特征(均值、方差)差异很大,共享参数无法适配这种差异。
  • 避免训练时的参数冲突:Keras的Layer实例是有状态的,复用同一个LN层会导致训练时参数更新同时影响两个位置的归一化操作,引发梯度更新混乱,导致模型收敛变慢甚至不收敛。

内容的提问来源于stack exchange,提问作者li zijing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 07:07:53