BN批量标准化操作解读

  num_features: :math:`C` from an expected input of size
            :math:`(N, C, H, W)`
        eps: a value added to the denominator for numerical stability.
            Default: 1e-5
        momentum: the value used for the running_mean and running_var
            computation. Can be set to ``None`` for cumulative moving average
            (i.e. simple average). Default: 0.1
        affine: a boolean value that when set to ``True``, this module has
            learnable affine parameters. Default: ``True``
        track_running_stats: a boolean value that when set to ``True``, this
            module tracks the running mean and variance, and when set to ``False``,
            this module does not track such statistics, and initializes statistics
            buffers :attr:`running_mean` and :attr:`running_var` as ``None``.
            When these buffers are ``None``, this module always uses batch statistics.
            in both training and eval modes. Default: ``True``

    Shape:
        - Input: :math:`(N, C, H, W)`
        - Output: :math:`(N, C, H, W)` (same shape as input)

    Examples::

        >>> # With Learnable Parameters
        >>> m = nn.BatchNorm2d(100)
        >>> # Without Learnable Parameters
        >>> m = nn.BatchNorm2d(100, affine=False)
        >>> input = torch.randn(20, 100, 35, 45)
        >>> output = m(input)

BN层的状态包含4个参数:

weight,即缩放操作的\gamma
bias,缩放操作的\beta
running_mean,训练阶段在全训练数据上统计的均值,测试阶段会用到
running_var,训练阶段在全训练数据上统计的方差,测试阶段会用到
weight和bias这两个参数需要训练,而running_mean、running_val不需要训练,它们只是训练阶段的统计值。
训练时,均值、方差分别是该批次内数据相应维度的均值与方差;
推理时,均值、方差是基于所有批次的期望计算所得
 

对于所有的batch中的同⼀个channel的数据元素进⾏标准化处理,即如果有C个通道,⽆论有多少个batch,都会在通道维度上进⾏标准化处理,⼀共进⾏C次。
训练阶段的均值和⽅差计算⽅法相同,将所有batch相同通道的值取出来,⼀块计算均值和⽅差,即计算当前观测值的均值和⽅差。
测试阶段的均值和⽅差有两种计算⽅法:
①估计所有图⽚的均值和⽅差,即做全局计算,具体计算⽅法如下:
模型分别储存各个通道(通道数需要预先定义)的均值和⽅差数据(初始为0和1),在每次训练过程中,每标准化⼀组数据,都利⽤计算得到的局部观测值的均值和⽅差对储存的数据做更新,测试阶段利⽤模型存储的两个数据做标准化处理,更新公式如下:

②采⽤和训练阶段相同的计算⽅法,即只计算当前输⼊数据的均值和⽅差

import os
import numpy as np
import random
def seed_torch(seed=42):
    random.seed(seed) # python seed
    os.environ['PYTHONHASHSEED'] = str(seed) # 设置python哈希种子,for certain hash-based operations (e.g., the item order in a set or a dict)。seed为0的时候表示不用这个feature,也可以设置为整数。 有时候需要在终端执行,到脚本实行可能就迟了。
    np.random.seed(seed) # If you or any of the libraries you are using rely on NumPy, 比如Sampling,或者一些augmentation。 哪些是例外可以看https://pytorch.org/docs/stable/notes/randomness.html
    torch.manual_seed(seed) # 为当前CPU设置随机种子。 pytorch官网倒是说(both CPU and CUDA)
    torch.cuda.manual_seed(seed) # 为当前GPU设置随机种子
    # torch.cuda.manual_seed_all(seed) # 使用多块GPU时,均设置随机种子
    torch.backends.cudnn.deterministic = True
    # torch.backends.cudnn.benchmark = True # 设置为True时,cuDNN使用非确定性算法寻找最高效算法
    # torch.backends.cudnn.enabled = True # pytorch使用CUDANN加速,即使用GPU加速
seed_torch(seed=42)
if __name__ == '__main__':
    seed_torch()
    x = torch.rand(2, 2, 3, 3)
    bn = nn.BatchNorm2d(2)
    out = bn(x)
    # 取出两个batch第一个维度的数据,做拼接
    a = torch.cat((x[0, 0, :, :], x[1, 0, :, :]), dim=0)
    b = a.numpy()
    mean = np.mean(b)
    std = np.std(b)+1e-5
    # 手动标准化
    out1 = (b - mean) / std
    print(x)
    print(out)
    print(out1)
    print("******************************")

标准化过程是以通道为维度计算的,即所有batch下,相同通道(channel)下的数据合并到⼀块,做标准化处理。若有C个通道,⽆论batch是多少,都会有C次标准化。


⼿动标准化得到的数据,前两⾏代表第⼀个batch下第⼀个通道标准化后的数据,与利⽤BatchNorm2d的前两⾏数据相等;后两⾏代表第⼆个batch下第⼀个通道标准化后的数据,与利⽤BatchNorm2d的前五六⾏数据相等。

x = 
tensor([[[[0.8823, 0.9150, 0.3829],
          [0.9593, 0.3904, 0.6009],
          [0.2566, 0.7936, 0.9408]],

         [[0.1332, 0.9346, 0.5936],
          [0.8694, 0.5677, 0.7411],
          [0.4294, 0.8854, 0.5739]]],


        [[[0.2666, 0.6274, 0.2696],
          [0.4414, 0.2969, 0.8317],
          [0.1053, 0.2695, 0.3588]],

         [[0.1994, 0.5472, 0.0062],
          [0.9516, 0.0753, 0.8860],
          [0.5832, 0.3376, 0.8090]]]])
out = 
tensor([[[[ 1.2564,  1.3741, -0.5387],
          [ 1.5333, -0.5114,  0.2450],
          [-0.9926,  0.9378,  1.4667]],

         [[-1.4313,  1.2410,  0.1039],
          [ 1.0236,  0.0176,  0.5957],
          [-0.4436,  1.0771,  0.0383]]],


        [[[-0.9566,  0.3405, -0.9457],
          [-0.3284, -0.8476,  1.0746],
          [-1.5363, -0.9461, -0.6251]],

         [[-1.2106, -0.0508, -1.8548],
          [ 1.2975, -1.6244,  1.0790],
          [ 0.0693, -0.7495,  0.8221]]]], grad_fn=<NativeBatchNormBackward0>)

# 手动标准化:
[[ 1.2564411   1.3741059  -0.5386709 ]
 [ 1.5333482  -0.51140857  0.24504317]
 [-0.9926246   0.9378682   1.4667271 ]

 [-0.95665246  0.3404908  -0.94568336]
 [-0.32839343 -0.84759253  1.0746179 ]
 [-1.5363196  -0.9461752  -0.6251226 ]]

track_running_stats设置为 True 和 False的区别

    x = torch.rand(2, 2, 3, 3)
    bn_t = nn.BatchNorm2d(2, track_running_stats= True)
    bn_f = nn.BatchNorm2d(2, track_running_stats= False)
    # 输出初始的模型存储值
    print('bn_t, mean:', bn_t.running_mean, 'bn_t, val:', bn_t.running_var)
    print('bn_f, mean:', bn_f.running_mean, 'bn_f, val:', bn_f.running_var)
    #  转换为训练阶段
    bn_t.train()
    bn_f.train()
    out0 = bn_t(x)
    out1 = bn_f(x)
    print('第一次迭代——bn_t, mean:', bn_t.running_mean, 'bn_t, val:', bn_t.running_var)
    print('第一次迭代——bn_f, mean:', bn_f.running_mean, 'bn_f, val:', bn_f.running_var)
    y = torch.rand(2, 2, 3, 3)
    out00 = bn_t(y)
    out11 = bn_f(y)
    print('第二次迭代——bn_t, mean:', bn_t.running_mean, 'bn_t, val:', bn_t.running_var)
    print('第二次迭代——bn_f, mean:', bn_f.running_mean, 'bn_f, val:', bn_f.running_var)

track_running_stats设置为 True时,每次都会更新running_mean和running_val

第一次迭代——bn_t, mean: tensor([0.0533, 0.0562]) bn_t, val: tensor([0.9082, 0.9095])
第一次迭代——bn_f, mean: None bn_f, val: None
第二次迭代——bn_t, mean: tensor([0.1012, 0.1069]) bn_t, val: tensor([0.8256, 0.8281])
第二次迭代——bn_f, mean: None bn_f, val: None

    x = torch.rand(2, 2, 3, 3)
    y = torch.rand(2, 2, 3, 3)
    bn_t = nn.BatchNorm2d(2, track_running_stats= True)
    bn_f = nn.BatchNorm2d(2, track_running_stats= False)
    # 输出初始的模型存储值
    print('bn_t, mean:', bn_t.running_mean, 'bn_t, val:', bn_t.running_var)
    print('bn_f, mean:', bn_f.running_mean, 'bn_f, val:', bn_f.running_var)
    #  转换为训练阶段
    bn_t.train()
    bn_f.train()
    out0train = bn_t(x)
    out1train = bn_f(x)
    print('train第一次迭代——bn_t, mean:', bn_t.running_mean, 'bn_t, val:', bn_t.running_var)
    print('train第一次迭代——bn_f, mean:', bn_f.running_mean, 'bn_f, val:', bn_f.running_var)

    out00train = bn_t(y)
    out11train = bn_f(y)
    print('train第二次迭代——bn_t, mean:', bn_t.running_mean, 'bn_t, val:', bn_t.running_var)
    print('train第二次迭代——bn_f, mean:', bn_f.running_mean, 'bn_f, val:', bn_f.running_var)

    bn_t.eval()
    bn_f.eval()
    out0eval = bn_t(x)
    out1eval = bn_f(x)
    print('eval第一次迭代——bn_t, mean:', bn_t.running_mean, 'bn_t, val:', bn_t.running_var)
    print('eval第一次迭代——bn_f, mean:', bn_f.running_mean, 'bn_f, val:', bn_f.running_var)
    y = torch.rand(2, 2, 3, 3)
    out00eval = bn_t(y)
    out11eval = bn_f(y)
    print('eval第二次迭代——bn_t, mean:', bn_t.running_mean, 'bn_t, val:', bn_t.running_var)
    print('eval第二次迭代——bn_f, mean:', bn_f.running_mean, 'bn_f, val:', bn_f.running_var)
    # out = bn(x)

track_running_stats设置为 True时,每次都会更新running_mean和running_val,在val时,running_mean和running_val保持train最后一次的running_mean和running_val不变。

bn_t, mean: tensor([0., 0.]) bn_t, val: tensor([1., 1.])
bn_f, mean: None bn_f, val: None
train第一次迭代——bn_t, mean: tensor([0.0533, 0.0562]) bn_t, val: tensor([0.9082, 0.9095])
train第一次迭代——bn_f, mean: None bn_f, val: None
train第二次迭代——bn_t, mean: tensor([0.1074, 0.0976]) bn_t, val: tensor([0.8227, 0.8280])
train第二次迭代——bn_f, mean: None bn_f, val: None
eval第一次迭代——bn_t, mean: tensor([0.1074, 0.0976]) bn_t, val: tensor([0.8227, 0.8280])
eval第一次迭代——bn_f, mean: None bn_f, val: None
eval第二次迭代——bn_t, mean: tensor([0.1074, 0.0976]) bn_t, val: tensor([0.8227, 0.8280])
eval第二次迭代——bn_f, mean: None bn_f, val: None

参考链接:nn.BatchNorm2d——批量标准化操作解读 - 百度文库 (baidu.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在PyTorch中,冻结Batch Normalization(BN)层的常见做法是将其设置为eval模式,并将其track_running_stats属性设置为False。这样做可以防止BN层参与训练过程中的梯度更新。以下是几种常见的冻结BN层的方法: 方法一: 在加载预训练模型时,需要使用以下代码来冻结BN层: ```python def freeze_bn(m): classname = m.__class__.__name__ if classname.find('BatchNorm') != -1: m.eval() model.apply(freeze_bn) ``` 这段代码会将模型中所有的BN层设置为eval模式,从而冻结它们的参数。 方法二: 如果在自己定义的模型中需要冻结特征提取层(pretrain layer)的BN层,可以按如下方式修改train函数: ```python def train(self, mode=True): super(fintuneNet, self).train(mode) if self.args.freeze_bn and mode==True: self.branch_cnn.apply(self.fix_bn) return self def fix_bn(self, m): classname = m.__class__.__name__ if classname.find('BatchNorm') != -1: m.eval() m.track_running_stats = False for name, p in m.named_parameters(): p.requires_grad = False ``` 这段代码会将模型中特征提取层的BN层设置为eval模式,并将其track_running_stats属性设置为False,同时将参数的requires_grad属性设置为False,从而冻结这些层的参数。 另外,可以阅读一篇名为"Pytorch BN(BatchNormal)计算过程与源码分析和train与eval的区别"的文章,该文章对PyTorch中BN层的计算过程以及train和eval模式的区别进行了详细分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值