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