详细解读nn.BatchNorm2d——批量标准化操作

目录

一、参数解读

1、BatchNorm2d的作用

2、BatchNorm2d的参数

二、解释模型存储的均值和方差是如何更新

1、文字描述

2、程序验证

训练阶段:trainning = True,track_running_stats = True

测试阶段:training = False,track_running_stats = True

参考链接:
完全解读BatchNorm2d归一化算法原理_机器学习算法那些事的博客-CSDN博客nn.BatchNorm2d——批量标准化操作解读_视觉萌新、的博客-CSDN博客_batchnormal2dnn.BatchNorm2d——批量标准化操作torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)功能:对输入的四维数组进行批量标准化处理,具体计算公式如下:y=x−mean[x]Var[x]+eps∗gamma+betay=\frac{x-mean[x]}{\sqrt{Var[x]+eps}}*gammahttps://blog.csdn.net/qq_50001789/article/details/120507768

写着一篇博客的目的是为了彻底弄清楚里面具体是怎么计算的,同时也是因为有了太多的博客并没有深入理解,所以特地写一篇博客记录一下。也为了防止后来者脑壳疼。

如果有用的话,请记得点赞+关注哦

一、参数解读

1、BatchNorm2d的作用

torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)

功能:对输入的四维数组进行批量标准化处理,具体计算公式如下:

y=\frac{x-\operatorname{mean}[x]}{\sqrt{\operatorname{Var}[x]+e p s}} * \gamma+\beta

        对于所有的batch中样本的同一个channel的数据元素进行标准化处理,即如果有C个通道,无论batch中有多少个样本,都会在通道维度上进行标准化处理,一共进行C次。

注解:这里重点表明了标准化处理x的计算公式,即

\hat x=\frac{x-\operatorname{mean}[x]}{\sqrt{\operatorname{Var}[x]+e p s}} * \gamma+\beta

这里\hat x是标准化处理之后的x

2、BatchNorm2d的参数

输入:

  • num_features:输入图像的通道数量-C。
  • eps:稳定系数,防止分母出现0。
  • momentum:BatchNorm2d里面存储均值(running_mean)和方差(running_var)更新时的参数。
  •  为BatchNorm2d里面的均值(running_mean)和方差(running_var),为当前观测值(样本)的均值或方差,为更新后的均值或方差(最后需要重新存储到BatchNorm2d中),momentum为更新参数。

  • affine:代表gamma,beta是否可学。如果设为True,代表两个参数是通过学习得到的;如果设为False,代表两个参数是固定值,默认情况下,gamma是1,beta是0。
  • track_running_stats:BatchNorm2d中存储的的均值和方差是否需要更新,若为True,表示需要更新;反之不需要更新。更新公式参考momentum参数介绍 。
  • 二、解释模型存储的均值和方差是如何更新

    1、文字描述

    上面说了说了这么多,其实都是铺垫。

    我们的目标有两个:

    在训练阶段和测试阶段:样本如何标准化处理。

    在训练阶段和测试阶段:模型BatchNorm2d中自身存储的均值(running_mean)和方差(running_var)如何更新。

    模型参数是否需要更新,需要结合参数布尔型参数trainning和track_running_states来看,模型归一化的结果也因这两种参数的不同而不同。

    根据模型处于训练阶段或测试阶段,参数trainning和track_running_states有4种组合方式。

    1、trainning = True,track_running_states = True:模型处于训练阶段,表示每作一次归一化,模型都需要更新参数均值和方差,即更新参数 running_mean 和 running_var 。

            模型分别储存各个通道(通道数需要预先定义)的均值和方差数据(初始为0和1),在每次训练过程中,每标准化一个batch的数据,都利用计算得到的局部观测值(batch里面的样本)的均值和方差对储存的数据做更新,使其具有描述全局数据的统计特性。

    2、trainning = True,track_running_stats = False:模型处于训练阶段,表示对新的训练数据进行归一化时,不更新模型的均值和方差,这种设置是错误的,因为不能很好的描述全局的数据统计特性。

    3、trainning = False,track_running_stats = True:模型处于测试阶段,表示模型在归一化测试数据时,需要考虑模型的均值和方差,但是不更新模型的均值和方差。

            测试阶段利用模型存储的两个数据做标准化处理

    4、trainning = False,track_running_stats = False:模型处于测试阶段,表示模型在归一化测试数据时,不考虑模型的均值和方差,这种设置是错误的,归一化的结果会造成统计特性的偏移。

    还记得我们的目标吗?我现在就针对两个目标来表达出来:

    由上面4种组合参数的介绍,正确的参数设置应为:

    训练阶段:trainning = True,track_running_stats = True

            一个batch的样本处理:

    先计算batch中所有样本的均值和方差

    再逐个正则化所有样本

    \hat x=\frac{x-\mu}{\sqrt{​{\sigma}^2}+\epsilon}* \gamma+\beta

            模型存储的均值(running_mean)和方差(running_var)的更新:

    {running\_mean}_{new}=(1-m) * {running\_mean}_{old}+ m * Mean[X]

    {running\_var}_{new}=(1-m) * {running\_var}_{old}+ m * Var[X]

    其中的m代表的是momentum,{running\_mean}_{old}{running\_var}_{old}表示模型中存储的均值和方差,Mean[X]Var[X]表示一个batch中的所有样本X的对应每个通道的均值和方差(局部特性)。{running\_mean}_{new}{running\_var}_{new}描述全局数据的统计特性。

    测试阶段:training = False,track_running_stats = True

            一个batch的样本处理:

    \hat x=\frac{x-running\_mean}{\sqrt{running\_var+e p s}} * \gamma+\beta

            模型存储的均值(running_mean)和方差(running_var)的不更新。

    2、程序验证

    训练阶段:trainning = True,track_running_stats = True

    # https://blog.csdn.net/algorithmPro/article/details/103982466
    # 在训练阶段
    import torch.nn as nn
    import torch
    import copy
    m3 = nn.BatchNorm2d(3, eps=0, momentum=0.5, affine=True, track_running_stats=True).cuda()
    # 为了方便验证,设置模型参数的值
    m3.running_mean = (torch.ones([3]) * 4).cuda()  # 设置模型的均值是4
    m3.running_var = (torch.ones([3]) * 2).cuda()  # 设置模型的方差是2
    
    # 查看模型参数的值
    print('trainning:', m3.training)
    print('running_mean:', m3.running_mean)
    print('running_var:', m3.running_var)
    # gamma对应模型的weight,默认值是1
    print('weight:', m3.weight)
    # gamma对应模型的bias,默认值是0
    print('bias:', m3.bias)
    
    ex_old = copy.deepcopy(m3.running_mean)
    var_old = copy.deepcopy(m3.running_var)
    # 计算更新后的均值和方差
    momentum = m3.momentum  # 更新参数
    # >
    # trainning: True
    # running_mean: tensor([4., 4., 4.], device='cuda:0')
    # running_var: tensor([2., 2., 2.], device='cuda:0')
    # weight: Parameter
    # containing:
    # tensor([1., 1., 1.], device='cuda:0', requires_grad=True)
    # bias: Parameter
    # containing:
    # tensor([0., 0., 0.], device='cuda:0', requires_grad=True)
    
    # 生成通道3,416行416列的输入数据
    torch.manual_seed(21)
    input3 = torch.randn(2, 3, 416, 416).cuda()
    # 输出第一个通道的数据
    # input3[0][0]
    # 数据归一化
    output3 = m3(input3)
    # 输出归一化后的第一个通道的数据
    # output3[0][0]
    print('*'*30)
    print('程序计算的新的均值ex_new:', m3.running_mean)
    print('程序计算的新的方差var_new:', m3.running_var)
    print("程序计算的输出bn:")
    print(output3[0])
    
    
    
    # 输入数据的均值
    # input3[0][i].mean()单个样本单个通道的均值
    # (input3[0][i].mean()+input3[1][i].mean())/2 所有样本单个通道的均值(但是这里只有2个样本)
    obser_mean = torch.Tensor([(input3[0][i].mean()+input3[1][i].mean())/2 for i in range(3)]).cuda()
    # 输入数据的方差
    obser_var = torch.Tensor([(input3[0][i].var()+input3[1][i].var())/2 for i in range(3)]).cuda()
    
    
    
    # 更新均值
    ex_new = (1 - momentum) * ex_old + momentum * obser_mean
    # 更新方差
    var_new = (1 - momentum) * var_old + momentum * obser_var
    # 打印
    print('*'*30)
    print('手动计算的新的均值ex_new:', ex_new)
    print('手动计算的新的方差var_new:', var_new)
    
    # # >
    # ex_new: tensor([2.0024, 2.0015, 2.0007], device='cuda:0')
    
    # var_new: tensor([1.5024, 1.4949, 1.5012], device='cuda:0')
    
    output3_calcu = torch.zeros_like(input3)
    for channel in range(input3.shape[1]):
        output3_calcu[0][channel] = (input3[0][channel] - obser_mean[channel]) / (pow(obser_var[channel] + m3.eps, 0.5))
    # 编码归一化
    # output3_channel_1 = (input3[0][0] - obser_mean[0]) / (pow(obser_var[0] + m3.eps, 0.5))
    # output3_channel_2 = (input3[0][1] - obser_mean[1]) / (pow(obser_var[1] + m3.eps, 0.5))
    # output3_channel_3 = (input3[0][2] - obser_mean[2]) / (pow(obser_var[2] + m3.eps, 0.5))
    # output3_source
    print("手动计算的输出bn:")
    print(output3_calcu[0])
    

    测试阶段:training = False,track_running_stats = True

    # https://blog.csdn.net/algorithmPro/article/details/103982466
    # 在测试阶段
    # 初始化模型,并设置模型处于测试阶段
    import torch
    import torch.nn as nn
    import copy
    m3 = nn.BatchNorm2d(3, eps=0, momentum=0.5, affine=True, track_running_stats=True).cuda()
    # 测试阶段
    m3.eval()
    # 为了方便验证,设置模型参数的值
    m3.running_mean = (torch.ones([3]) * 4).cuda()  # 设置模型的均值是4
    m3.running_var = (torch.ones([3]) * 2).cuda()  # 设置模型的方差是2
    
    # 查看模型参数的值
    print('trainning:', m3.training)
    print('running_mean:', m3.running_mean)
    print('running_var:', m3.running_var)
    # gamma对应模型的weight,默认值是1
    print('weight:', m3.weight)
    # gamma对应模型的bias,默认值是0
    print('bias:', m3.bias)
    
    # # >
    # trainning: False
    # running_mean: tensor([4., 4., 4.], device='cuda:0')
    # running_var: tensor([2., 2., 2.], device='cuda:0')
    # weight: Parameter
    # containing:
    # tensor([1., 1., 1.], device='cuda:0', requires_grad=True)
    # bias: Parameter
    # containing:
    # tensor([0., 0., 0.], device='cuda:0', requires_grad=True)
    
    # 初始化输入数据,并计算输入数据的均值和方差
    # 生成通道3,416行416列的输入数据
    torch.manual_seed(21)
    input3 = torch.randn(1, 3, 416, 416).cuda()
    # 输入数据的均值
    obser_mean = torch.Tensor([input3[0][i].mean() for i in range(3)]).cuda()
    # 输入数据的方差
    obser_var = torch.Tensor([input3[0][i].var() for i in range(3)]).cuda()
    # 打印
    print('obser_mean:', obser_mean)
    print('obser_var:', obser_var)
    
    # >
    # obser_mean: tensor([0.0047, 0.0029, 0.0014], device='cuda:0')
    # obser_var: tensor([1.0048, 0.9898, 1.0024], device='cuda:0')
    
    ex_old = copy.deepcopy(m3.running_mean)
    var_old = copy.deepcopy(m3.running_var)
    print('*'*30)
    print('程序计算bn前的均值ex_new:', ex_old)
    print('程序计算bn前的方差var_new:', var_old)
    
    # 数据归一化
    output3 = m3(input3)
    # 输出归一化后的第一个通道的数据
    
    
    
    print('*'*30)
    print('程序计算bn后的均值ex_new:', m3.running_mean)
    print('程序计算bn后的方差var_new:', m3.running_var)
    
    # 归一化函数实现
    output3_calcu = torch.zeros_like(input3)
    for channel in range(input3.shape[1]):
        output3_calcu[0][channel] = (input3[0][channel] - m3.running_mean[channel]) / (pow(m3.running_var[channel] + m3.eps, 0.5))
    
    
    print("程序计算的输出bn:")
    print(output3)
    print("手动计算的输出bn:")
    print(output3_calcu)
    
    # 由结果可知,执行测试阶段的froward函数后,模型的running_mean和running_var不改变。

    来源:ChaoFeiLi

    物联沃分享整理
    物联沃-IOTWORD物联网 » 详细解读nn.BatchNorm2d——批量标准化操作

    发表评论