nn.BatchNorm1d 与 nn.BatchNorm2d 使用方法与原理

在PyTorch库中,nn.BatchNorm1d是一个专为一维数据设计的批量归一化层。而nn.BatchNorm2d与之非常类似,不过是输入数据的形状要求稍有不同。下面通过一些实例代码和其输出结果,让我们理解nn.BatchNorm1dnn.BatchNorm2d的工作机制。

nn.BatchNorm1d使用示例

import torch
import torch.nn as nn

torch.manual_seed(42)  # 固定随机种子

# 创建一个BatchNorm1d层,通道数为3
m = nn.BatchNorm1d(3)  # d=3
print('m.weight:\n', m.weight)  # (d,)
print('m.bias:\n', m.bias)  # (d,)

# 准备一个批次大小为4,通道数为3的数据输入
x = torch.randn(4, 3)  # (bs, d)
print('x:\n', x)

# 首先,我们计算原始输入数据每个通道的均值和方差
x_mean = x.mean(dim=0)  # (d,)
x_var = x.var(dim=0, unbiased=False)  # (d,)
print('x_mean:\n', x_mean)
print('x_var:\n', x_var)

# 将输入数据传递给BatchNorm1d层进行归一化处理
y = m(x)  # (bs, d)
print('y:\n', y)

# 计算经过BatchNorm1d层后的输出数据在每个通道上的均值和方差
y_mean = y.mean(dim=0) # (d,)
y_var = y.var(dim=0, unbiased=False)  # (d,)
print('y_mean:\n', y_mean)
print('y_var:\n', y_var)

运行这段代码后,我们得到以下输出:

m.weight:
 Parameter containing:
tensor([1., 1., 1.], requires_grad=True)
m.bias:
 Parameter containing:
tensor([0., 0., 0.], requires_grad=True)
x:
 tensor([[ 0.3367,  0.1288,  0.2345],
        [ 0.2303, -1.1229, -0.1863],
        [ 2.2082, -0.6380,  0.4617],
        [ 0.2674,  0.5349,  0.8094]])
x_mean:
 tensor([ 0.7606, -0.2743,  0.3298])
x_var:
 tensor([0.6999, 0.4174, 0.1307])
y:
 tensor([[-0.5067,  0.6239, -0.2637],
        [-0.6339, -1.3134, -1.4275],
        [ 1.7302, -0.5630,  0.3647],
        [-0.5896,  1.2525,  1.3264]], grad_fn=<NativeBatchNormBackward0>)
y_mean:
 tensor([2.9802e-08, 0.0000e+00, 0.0000e+00], grad_fn=<MeanBackward1>)
y_var:
 tensor([1.0000, 1.0000, 0.9999], grad_fn=<VarBackward0>)

从上述输出我们可以观察到:

  1. nn.BatchNorm1d初始化时,weight(缩放)参数默认为全1向量,bias(平移)参数默认为全0向量,并且它们都是可训练的(requires_grad=True)。

  2. 输入数据是一批具有4个样本(bs)和3个通道(d)的数据点。

  3. 在未经处理的输入数据上,我们分别计算了各个通道的均值和方差。

  4. 当我们将输入数据送入nn.BatchNorm1d层后,得到的输出数据经过了归一化处理,使得每个通道的均值接近于0(实际上由于浮点数精度问题,显示为接近于0的很小的数值),且方差近似为1。

nn.BatchNorm1d计算原理

import torch
import torch.nn as nn

def batch_norm_with_nn(x, num_features):
    """
    使用 nn.BatchNorm1d 实现的批量归一化
    x: (bs, d)
    num_features: d
    """
    m = nn.BatchNorm1d(num_features)
    return m(x)

def batch_norm_manual(x, num_features):
    """
    手动实现的批量归一化
    x: (bs, d)
    num_features: d
    """
    weight = torch.ones(num_features)  # (d,) 全1
    bias = torch.zeros(num_features)  # (d,) 全0
    x_mean = x.mean(dim=0)  # (d,) 均值
    x_var = x.var(dim=0, unbiased=False)  # (d,) 方差
    normalized = (x - x_mean) / torch.sqrt(x_var + 1e-5)  # (bs, d) 归一化
    y_manual = weight * normalized + bias  # (bs, d) 缩放和平移
    # (d,)与(bs, d)计算时,(d,)会被广播为(bs, d)
    return y_manual

# 设置随机种子
torch.manual_seed(42)

# 创建输入数据
batch_size = 4
num_features = 3
x = torch.randn(batch_size, num_features)  # (bs, d)

y_nn = batch_norm_with_nn(x, num_features)  # (bs, d) 直接调用 nn.BatchNorm1d
y_manual = batch_norm_manual(x, num_features)  # (bs, d) 手动实现

print("Output using nn.BatchNorm1d:\n", y_nn)
print("Output using manual implementation:\n", y_manual)

运行这段代码后,我们得到以下输出:

Output using nn.BatchNorm1d:
 tensor([[-0.5067,  0.6239, -0.2637],
        [-0.6339, -1.3134, -1.4275],
        [ 1.7302, -0.5630,  0.3647],
        [-0.5896,  1.2525,  1.3264]], grad_fn=<NativeBatchNormBackward0>)
Output using manual implementation:
 tensor([[-0.5067,  0.6239, -0.2637],
        [-0.6339, -1.3134, -1.4275],
        [ 1.7302, -0.5630,  0.3647],
        [-0.5896,  1.2525,  1.3264]])

观察上述输出,我们可以看到,无论是使用nn.BatchNorm1d还是手动实现批量归一化,两者对同一输入数据处理后得到的结果完全一致。这是因为它们遵循了相同的批量归一化计算流程:先计算每个通道的均值和方差,然后对原始数据进行标准化(减去均值并除以标准差),最后通过可学习的权重和偏置参数进行缩放和平移变换。

更多地,如果在上面的手动实现中, y_manual 直接等于 normalized 而不进行缩放(乘以权重)和平移(加上偏置),那么 y_manual 就是输入数据 x 经过零均值和单位方差的标准化处理。这意味着 y_manual(即 normalized)的每个通道将具有大致为 0 的均值和大致为 1 的方差。这是因为标准化过程是通过减去均值并除以方差的平方根来实现的。

nn.BatchNorm2d计算原理

import torch
import torch.nn as nn

def batch_norm_2d_with_nn(x, num_features):
    """
    使用 nn.BatchNorm2d 实现的批量归一化
    x: (N, C, H, W)
    num_features: C (通道数)
    """
    m = nn.BatchNorm2d(num_features)
    print('m.weight:\n', m.weight)
    print('m.bias:\n', m.bias)
    return m(x)

def batch_norm_2d_manual(x, num_features):
    """
    手动实现的批量归一化(适用于2D数据)
    x: (N, C, H, W)
    num_features: C (通道数)
    """
    weight = torch.ones(num_features)[None, :, None, None]  # (1, C, 1, 1) 全1
    bias = torch.zeros(num_features)[None, :, None, None]   # (1, C, 1, 1) 全0
    x_mean = x.mean(dim=(0, 2, 3), keepdim=True)  # (1, C, 1, 1) 均值
    x_var = x.var(dim=(0, 2, 3), keepdim=True, unbiased=False)  # (1, C, 1, 1) 方差
    normalized = (x - x_mean) / torch.sqrt(x_var + 1e-5)  # (N, C, H, W) 归一化
    y_manual = weight * normalized + bias  # (N, C, H, W) 缩放和平移
    return y_manual

def batch_norm_2dto1d_manual(x, num_features):
    """
    将 BatchNorm2d 转换为 BatchNorm1d 形式处理
    x: (N, C, H, W)
    num_features: C (通道数)
    """
    N, C, H, W = x.shape
    x_reshaped = x.permute(0, 2, 3, 1)  # (N, H, W, C)
    x_reshaped = x_reshaped.reshape(-1, C)  # (N*H*W, C), 即(bs, d)

    m = nn.BatchNorm1d(C)
    y_reshaped = m(x_reshaped)  # (N*H*W, C)

    y = y_reshaped.reshape(N, H, W, C)  # (N, H, W, C)
    y = y.permute(0, 3, 1, 2)  # (N, C, H, W)
    return y

# 设置随机种子
torch.manual_seed(42)

# 创建输入数据
batch_size = 4
num_channels = 3
height = 2
width = 2
x = torch.randn(batch_size, num_channels, height, width)  # (N, C, H, W)

y_nn = batch_norm_2d_with_nn(x, num_channels)  # 使用 nn.BatchNorm2d
y_manual = batch_norm_2d_manual(x, num_channels)  # 手动实现 BatchNorm2d
y_manual_2d_to_1d = batch_norm_2dto1d_manual(x, num_channels)  # 将 BatchNorm2d 转换为 BatchNorm1d 处理
y_nn_y_manual = torch.allclose(y_nn, y_manual, rtol=1e-03, atol=1e-05)  # 判断两个输出是否相等
y_nn_y_manual_2d_to_1d = torch.allclose(y_nn, y_manual_2d_to_1d, rtol=1e-03, atol=1e-05)  # 判断两个输出是否相等

print("is y_nn equal to y_manual?", y_nn_y_manual)
print("is y_nn equal to y_manual_2d_to_1d?", y_nn_y_manual_2d_to_1d)

运行这段代码后,我们得到以下输出:

m.weight:
 Parameter containing:
tensor([1., 1., 1.], requires_grad=True)
m.bias:
 Parameter containing:
tensor([0., 0., 0.], requires_grad=True)
is y_nn equal to y_manual? True
is y_nn equal to y_manual_2d_to_1d? True

上面的代码演示了如何使用PyTorch中的nn.BatchNorm2d模块进行批量归一化处理,以及如何手动实现针对2D数据的批量归一化操作。同时,它还将2D批量归一化转换为1D形式进行处理,并验证了三种方法得到的结果一致。可以看到,batch_norm_2dto1d_manual 函数将 BatchNorm2d 转换为 BatchNorm1d 形式处理,并且得到的结果与使用 BatchNorm2d 直接实现的结果一致。

nn.BatchNorm1d和nn.BatchNorm2d总结

nn.BatchNorm1dnn.BatchNorm2d两者处理的数据维度不同,但它们的核心参数和工作原理是一致的。它们的相同点在于 d (或着说,c)始终是独立的维度,即每个特征维度(d)被独立地归一化。在两种情况下,d 维度都是独立的,这意味着每个特征通道(无论是一维还是二维数据)都有其自己的归一化参数(均值和方差)。所以,不论是一维还是二维批量归一化层,它们的总参数量均为 2 * num_features。而nn.BatchNorm2d不过是将bs(批大小)、h(高度)、和h(宽度)的维度在操作中混合在一起,当作BatchNorm1d中的bs维度处理,正如batch_norm_2dto1d_manual函数所演示的。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值