一、Batch-Normalization
Batch Normalization的作用是将一个批次(Batch)的特征矩阵的每一个channels 计算为均值为0,方差为1的分布 规律。
一般而言,在一个神经网络输入图像之前,会将图像进行预处理,这个预处理可能是 标准化处理等手段,由于输入数据满足某一分布规律,所以会加速网络的收敛。这样 在输入第一次卷积的时候满足某一分布规律,但是在输入第二次卷积时,就不一定满 足某一分布规律了,再往后的卷积的输入就更不满足了,那么就需要一个中间商,让 上一层的输出经过它之后能够某一分布规律,Batch Normalization就是这个中间 商,它可以让输入的特征矩阵的每一个channels满足均值为0,方差为1的分布规 律。


python
import numpy as np
def batch_normalization(BN_input, epsilon=1e-5):
"""
对输入数据进行批量归一化(Batch Normalization)处理
参数:
BN_input: 输入数据,形状为(batch_size, channels, height, width)的4D张量
epsilon: 用于数值稳定的小常数,防止除以零
返回:
经过批量归一化处理后的数据
"""
# 1. 计算均值和方差
# 沿batch、height、width维度计算每个channel的均值和方差
# keepdims=True保持维度不变,便于后续广播操作
mean = np.mean(BN_input, axis=(0, 2, 3), keepdims=True) # 形状变为(1, C, 1, 1)
variance = np.var(BN_input, axis=(0, 2, 3), keepdims=True) # 形状变为(1, C, 1, 1)
# 2. 归一化处理
# 使用公式:(x - μ) / √(σ² + ε)
normalized_data = (BN_input - mean) / np.sqrt(variance + epsilon)
# 3. 缩放和平移
# 实际应用中,gamma和beta是可学习的参数
# 这里简化实现,gamma初始化为1,beta初始化为0
gamma = np.ones_like(BN_input) # 缩放参数
beta = np.zeros_like(BN_input) # 平移参数
# 应用缩放和平移:y = γ * x_norm + β
output_data = gamma * normalized_data + beta
return output_data
# 测试数据
# 模拟卷积层输出:2个样本,2个通道,2x2的特征图
conv_output = [
[ # 第一个样本
[[1, 1], [1, 2]], # 通道1
[[-1, 1], [0, 1]], # 通道2
],
[ # 第二个样本
[[0, -1], [2, 2]], # 通道1
[[0, -1], [3, 1]], # 通道2
]
]
# 将列表转换为numpy数组
conv_output = np.array(conv_output)
# 进行批量归一化
normalized_output = batch_normalization(conv_output)
print("归一化后的输出:")
print(normalized_output)
python
import torch
import torch.nn as nn
# 假设卷积后的结果如下:
# 卷积层的输出是(batch_size, channels, height, width)的四维张量
conv_output = [
[ # 第一个样本
[[1, 1], [1, 2]], # 通道1
[[-1, 1], [0, 1]], # 通道2
],
[ # 第二个样本
[[0, -1], [2, 2]], # 通道1
[[0, -1], [3, 1]], # 通道2
]
]
# 将列表转换为PyTorch张量,并指定数据类型为float32
embd_output = torch.tensor(conv_output, dtype=torch.float)
# 打印原始输入数据
print("Original Input:")
print(embd_output)
# 将最后三个维度(channels, height, width)合并为一个维度
# 这里使用view()进行形状变换,保持通道数不变
# -1表示自动计算batch_size
reshaped_embd_output = embd_output.view(
-1, # 自动计算的batch_size
embd_output.size(-3), # 通道数
embd_output.size(-2), # 高度
embd_output.size(-1) # 宽度
)
# 在Batch维度上进行Batch Normalization
# 创建BatchNorm2d层,参数:
# - num_features: 输入的特征图通道数
# - affine: 是否学习缩放(gamma)和平移(beta)参数
batch_norm = nn.BatchNorm2d(
reshaped_embd_output.size(1), # 输入通道数
affine=True # 启用可学习的gamma和beta参数
)
# 应用批量归一化
normalized_output = batch_norm(reshaped_embd_output)
# 将输出重新调整为原始形状
normalized_output = normalized_output.view(embd_output.size())
# 打印归一化后的输出
print("\nBN Normalized Output:")
print(normalized_output)
# 打印BN层的参数(训练过程中会学习这些参数)
print("\nBN层参数:")
print("gamma (weight):", batch_norm.weight)
print("beta (bias):", batch_norm.bias)
print("running_mean:", batch_norm.running_mean)
print("running_var:", batch_norm.running_var)
二、Layer Normalization
Layer Normalization由Ba, Kiros, 和Hinton于2016年提出。它主要用于解决深度神经网络训练过程中的内部协变量偏移问题。不同于Batch Normalization(批归一 化),Layer Normalization是独立于批次大小的,这使得它在处理小批次数据或循 环神经网络(RNN)时特别有用。

使用PyTorch实现图像任务的LN层
pythonimport torch import torch.nn as nn # 假设卷积后的结果如下: # 卷积层的输出是(batch_size, channels, height, width)的四维张量 conv_output = [ [ # 第一个样本 (样本1) [[1, 1], [1, 2]], # 通道1的2x2特征图 [[-1, 1], [0, 1]], # 通道2的2x2特征图 ], [ # 第二个样本 (样本2) [[0, -1], [2, 2]], # 通道1的2x2特征图 [[0, -1], [3, 1]], # 通道2的2x2特征图 ] ] # 将Python列表转换为PyTorch张量,并指定为float32类型 embd_output = torch.tensor(conv_output, dtype=torch.float) # 打印原始输入数据 print("Original Input:") print(embd_output) print("输入形状:", embd_output.shape) # 输出形状应为[2, 2, 2, 2] (batch, channel, height, width) # 创建LayerNorm层 # 参数说明: # normalized_shape: 需要规范化的维度(从最后一个维度开始) # 这里我们传入embd_output.shape[1:4],即对每个样本的(channels, height, width)进行归一化 # 相当于对每个空间位置的特征向量进行独立归一化 layer_norm = nn.LayerNorm(embd_output.shape[1:4]) # 对每个样本的[channel, height, width]做归一化 # 应用层归一化 normalized_output = layer_norm(embd_output) # 打印归一化后的输出 print("\nLN Normalized Output:") print(normalized_output) print("输出形状:", normalized_output.shape) # 应与输入形状相同 # 打印LN层的参数(可学习的缩放和平移参数) print("\nLayerNorm层参数:") print("gamma (weight):", layer_norm.weight.shape) # 形状应与normalized_shape一致 print("beta (bias):", layer_norm.bias.shape) # 形状应与normalized_shape一致

对于输入,有样本1和样本2,即Sample1和Sample2,每个样本有四个字,即4个 token,或者叫sequence_len,每个字的Embedding有自己的维度,即embedding dimension。那么LN规则化其实就是对每个样本的每个token的embedding dimension求均值和方差,然后运算。图中相同颜色的个子就是做LN规则化的。
使用PyTorch实现NLP任务的LN层
pythonimport torch import torch.nn as nn # 假设文字的Embedding编码如下: # 形状为 (batch_size, sequence_length, embedding_dimension) # 示例数据包含2个样本,每个样本4个token,每个token用2维向量表示 embd_output = [ [ # 第一个样本 (batch=0) [1, -1], # token 0 [1, 1], # token 1 [1, 0], # token 2 [2, 1], # token 3 ], [ # 第二个样本 (batch=1) [0, 0], # token 0 [-1, -1], # token 1 [2, 3], # token 2 [2, 1], # token 3 ] ] # 将Python列表转换为PyTorch张量,并指定为float32类型 embd_output = torch.tensor(embd_output, dtype=torch.float) # 打印原始输入数据 print("Original Input:") print(embd_output) print("输入形状:", embd_output.shape) # 预期输出: torch.Size([2, 4, 2]) # 创建LayerNorm层 # 参数说明: # normalized_shape: 需要规范化的维度(从最后一个维度开始) # 这里传入embd_output.size(-1)即embedding_dimension=2, # 表示对每个token的特征向量进行独立归一化 # eps: 防止除零的小常数(默认1e-5) # elementwise_affine: 是否使用可学习的缩放和平移参数(默认True) layer_norm = nn.LayerNorm( normalized_shape=embd_output.size(-1), # 对embedding_dimension进行归一化 eps=1e-5, # 数值稳定系数 elementwise_affine=True # 启用可学习的gamma和beta参数 ) # 应用层归一化 # LayerNorm的计算过程: # 1. 对每个token的特征向量计算均值和方差 # 2. 使用 (x - mean) / sqrt(var + eps) 进行归一化 # 3. 应用缩放gamma和平移beta:y = gamma * x + beta normalized_output = layer_norm(embd_output) # 打印归一化后的输出 print("\nLN Normalized Output:") print(normalized_output) print("输出形状:", normalized_output.shape) # 应与输入形状相同 # 打印LN层的可学习参数 print("\nLayerNorm层参数:") print("gamma (缩放权重):", layer_norm.weight) # 形状为[embedding_dimension] print("beta (平移偏置):", layer_norm.bias) # 形状为[embedding_dimension] print("参数形状:", layer_norm.weight.shape) # 预期输出: torch.Size([2])
三、Post-LN和Pre-LN
3.1、Post-LN(后层归一化):
Post-LN 是原始的 Transformer 架构中使用的层归一化方式。在 Post-LN 中,层归 一化是在每个子层的残差连接之后进行的。然而,Post-LN 在深度 Transformer(例 如,十层或更多层)的训练中经常变得不稳定,导致模型无法使用。

3.2、Pre-LN(前层归一化):
由于 Post-LN 的训练不稳定性,研究者们提出了 Pre-LN。在 Pre-LN 中,层归一化 是在每个子层的输入上进行的。这使得 Pre-LN 在深度 Transformer 的训练中更为稳 定。

3.3、Post-LN和Pre-LN比较:

