详细解读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——批量标准化操作

发表评论