目录
训练阶段: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)
功能:对输入的四维数组进行批量标准化处理,具体计算公式如下:
对于所有的batch中样本的同一个channel的数据元素进行标准化处理,即如果有C个通道,无论batch中有多少个样本,都会在通道维度上进行标准化处理,一共进行C次。
注解:这里重点表明了标准化处理的计算公式,即
这里是标准化处理之后的
。
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中所有样本的均值和方差
再逐个正则化所有样本
模型存储的均值(running_mean)和方差(running_var)的更新:
其中的m代表的是momentum,和
表示模型中存储的均值和方差,
和
表示一个batch中的所有样本X的对应每个通道的均值和方差(局部特性)。
和
描述全局数据的统计特性。
测试阶段:training = False,track_running_stats = True
一个batch的样本处理:
模型存储的均值(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不改变。