pytorch BatchNorm 实验

pytorch BatchNorm 实验

百度了一圈,也没有找到pytorch BatchNorm详细解释能让自己十分明白的,没办法自己做一下实验记录下吧,然后结合百度的进行理解

BatchNorm2d

一般用于一次前向运算的batch size比较多的情况(100~200) , 但是当batch size较小时(小于16时),效果会变差,这时使用group norm可能得到的效果会更好

它的公式可以表示为

y = x − E [ x ] V a r [ x ] + ϵ ∗ γ + β y = \frac{x - \mathrm{E}[x]}{ \sqrt{\mathrm{Var}[x] + \epsilon}} * \gamma + \beta y=Var[x]+ϵ xE[x]γ+β
当输入为 Batch x Channel x Height x Width BatchNorm2d的num_features第于channel

说白了就是对CWH(例如对于channel=0 取出所有batch中channel=0的输入) 求平均mean(E[x]表示求平均) 求方差var
然后对每一个输入元素应用上面的公式

其中 γ , β \gamma ,\beta γβ 是BatchNorm2d.weight 和 BatchNorm2d.bias 参数 有多少个channel 就有多少个,而affine是控制这两个参数是否可以学习
如果affine=False 则weight bias 分别为常量1 和 0

eps:是防止除零出错 而加的一个小数

momentum: BatchNorm2d其实内部还有 running_mean 和 running_var 内部变量(初始值为0和1),当每一次计算Norm结果时,这两个内部变量就会进行更新,更新的计算公式是
新值 = 上一次的值*(1-momentum) + 本次计算的值*momentum。
其实这样做的作用是在训练结束预测时,平均值和方差值 与整个训练数据集相关,而与本次输入的平均值和方差无关

track_running_stats: 这个是设置在是否在预测模式时使用训练时得到的running_mean 和 running_var 作为BatchNorm2d计算公式的平均值和方差值,如果等于False,则预测时每次都用输入数据重新计算平均值和方差值

训练或预测模式: 可以通过train()或 eval()函数改变它的状态,在训练状态时,BatchNorm2d计算 running_mean 和 running_var是不会被使用到的,而在预测状态时track_running_stats=False时 每次BatchNorm2d计算都会用输入数据计算平均值和方差;track_running_stats=True时 每次BatchNorm2d计算都会用running_mean, running_var作为平均值和方差,不用当前输入数据计算平均值和方差。

import torch
import torch.nn as nn

tt = torch.randn(2,4,3,3)
bn = nn.BatchNorm2d(4,track_running_stats=True)

bn.train(True)
ot = bn(tt)

mean = torch.mean(tt[:,0])
var = torch.var(tt[:,0],False)

#用公式计算bn后的第一个元素的值
bn0000 = bn.bias[0] + bn.weight[0]*(tt[0][0][0][0]-mean)/torch.pow(var+bn.eps,0.5)

print("可以看到ot[0][0][0][0] 与 我们模拟计算的bn0000是相等的")
print("ot[0][0][0][0]=",ot[0][0][0][0],"bn0000=",bn0000)

#用公式计算bn后的第一个元素的值
bn0000 = bn.bias[0] + bn.weight[0]*(tt[0][0][0][0]-mean)/torch.pow(var+bn.eps,0.5)

print("可以看到ot[0][0][0][0] 与 我们模拟计算的bn0000是相等的")
print("ot[0][0][0][0]=",ot[0][0][0][0],"bn0000=",bn0000)

#但是如果 设置预测模式时 ot[0][0][0][0] 与 我们模拟计算的bn0000就不再相等了
bn.train(False)
tt = torch.randn(2,4,3,3)
ot = bn(tt)

mean = torch.mean(tt[:,0])
var = torch.var(tt[:,0],False)
bn0000 = bn.bias[0] + bn.weight[0]*(tt[0][0][0][0]-mean)/torch.pow(var+bn.eps,0.5)
print("可以看到ot[0][0][0][0] 与 我们模拟计算的bn0000不再相等了")
print("ot[0][0][0][0]=",ot[0][0][0][0],"bn0000=",bn0000)
#这时候换成 bn的running_mean running_var 它们就相等了
bn0000 = bn.bias[0] + bn.weight[0]*(tt[0][0][0][0]-bn.running_mean[0])/torch.pow(bn.running_var[0]+bn.eps,0.5)
print("可以看到ot[0][0][0][0] 与 我们模拟计算的bn0000又相等了!")
print("ot[0][0][0][0]=",ot[0][0][0][0],"bn0000=",bn0000)

#预测模式 禁止使用running_mean running_var,每次都计算输入数平均值和方差
bn.track_running_stats=False
ot = bn(tt)
mean = torch.mean(tt[:,0])
var = torch.var(tt[:,0],False)
bn0000 = bn.bias[0] + bn.weight[0]*(tt[0][0][0][0]-mean)/torch.pow(var+bn.eps,0.5)
print("track_running_stats=False时 可以看到ot[0][0][0][0] 结果 是跟输入数据平均值和方差有关")
print("ot[0][0][0][0]=",ot[0][0][0][0],"bn0000=",bn0000)

可以看到ot[0][0][0][0] 与 我们模拟计算的bn0000是相等的
ot[0][0][0][0]= tensor(0.4617, grad_fn=SelectBackward>) bn0000= tensor(0.4617, grad_fn=AddBackward0>)
可以看到ot[0][0][0][0] 与 我们模拟计算的bn0000是相等的
ot[0][0][0][0]= tensor(0.4617, grad_fn=) bn0000= tensor(0.4617, grad_fn=AddBackward0>)
可以看到ot[0][0][0][0] 与 我们模拟计算的bn0000不再相等了
ot[0][0][0][0]= tensor(0.0335, grad_fn=) bn0000= tensor(-0.0962, grad_fn=AddBackward0>)
可以看到ot[0][0][0][0] 与 我们模拟计算的bn0000又相等了!
ot[0][0][0][0]= tensor(0.0335, grad_fn=SelectBackward>) bn0000= tensor(0.0335, grad_fn=AddBackward0>)
track_running_stats=False时 可以看到ot[0][0][0][0] 结果 是跟输入数据平均值和方差有关
ot[0][0][0][0]= tensor(-0.0962, grad_fn=SelectBackward>) bn0000= tensor(-0.0962, grad_fn=AddBackward0>)

InstanceNorm2d

当mini-batch时使用 一次前向运算batch size比较小时

InstanceNorm2d 其实与 BatchNorm2d是类似的,它只是对所有Batch所有Channel求 Height x Width数据的均值和方差,然后再应用公式
y = x − E [ x ] V a r [ x ] + ϵ ∗ γ + β y = \frac{x - \mathrm{E}[x]}{ \sqrt{\mathrm{Var}[x] + \epsilon}} * \gamma + \beta y=Var[x]+ϵ xE[x]γ+β

tt = torch.randn(2,4,3,3)
insn = nn.InstanceNorm2d(4,affine=True)
ot = insn(tt)
mean = torch.mean(tt[0][0])
var = torch.var(tt[0][0],False)
#用公式计算insn后的第一个元素的值
insn0000 = insn.bias[0] + insn.weight[0]*(tt[0][0][0][0]-mean)/torch.pow(var+insn.eps,0.5)
print("可以看到ot[0][0][0][0] 与 我们模拟计算的insn0000是相等的")
print("ot[0][0][0][0]=",ot[0][0][0][0],"insn0000=",insn0000)

可以看到ot[0][0][0][0] 与 我们模拟计算的insn0000是相等的
ot[0][0][0][0]= tensor(0.1109, grad_fn=SelectBackward>) insn0000= tensor(0.1109, grad_fn=AddBackward0>)

# 一步到位函数
def MyInstanceNorm2d(t,eps=1e-5):
    t:torch.Tensor
    mean=torch.mean(t,[2,3])
    var=torch.var(t,[2,3],False)
    mean.unsqueeze_(2)
    mean.unsqueeze_(2)
    var.unsqueeze_(2)
    var.unsqueeze_(2)
    return (t-mean)/torch.pow(var+eps,0.5)

print("可以看到 也是差不多相等的:\n")
print(insn(tt)/MyInstanceNorm2d(tt))

可以看到 也是差不多相等的:

tensor([[[[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000]],
[[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000]],
[[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000]],
[[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000]]],
[[[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000]],
[[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000]],
[[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000]],
[[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000]]]], grad_fn=DivBackward0>)

LayerNorm

当mini-batch时使用 一次前向运算batch size比较小时 通常应用于整个样本,并且通常用于NLP(自然语言处理)任务

LayerNorm也是与上面的两个运算相似,不同的地方是它对CHW求均值和方差,也就是对不同的Batch 计算不同的均值和方差,而面它的weight 和 bias对于每个CHW维度都有对应的值(对所有输入数据每个元素都有对应的不同的 w*x+b仿射变换)

tt = torch.randn(2,4,3,3)
ln = nn.LayerNorm(normalized_shape=[4,3,3],elementwise_affine=True)
print("每个输入元素都有以有不同的变换","weight.shape=",ln.weight.shape,"bias.shape=",ln.bias.shape)
ot = ln(tt)
mean = torch.mean(tt[0])
var = torch.var(tt[0],False)
ln0000 = ln.bias[0][0][0] + ln.weight[0][0][0]*(tt[0][0][0][0]-mean)/torch.pow(var+ln.eps,0.5)
print("可以看到ot[0][0][0][0] 与 我们模拟计算的ln0000是相等的")
print("ot[0][0][0][0]=",ot[0][0][0][0],"ln0000=",ln0000)

每个输入元素都有以有不同的变换 weight.shape= torch.Size([4, 3, 3]) bias.shape= torch.Size([4, 3, 3])
可以看到ot[0][0][0][0] 与 我们模拟计算的ln0000是相等的
ot[0][0][0][0]= tensor(1.3358, grad_fn=SelectBackward>) ln0000= tensor(1.3358, grad_fn=AddBackward0>)

GroupNorm

GroupNorm将channel分组;即是将batch中的单个样本的G层特征图抽出来一起求mean和variance,与batch size无关 当batch size较小时(小于16时),使用该normalization方法效果更好 输入通道被分成num_groups组,每个组包含num_channels / num_groups个通道。每组的均值和标准差分开计算。如果affine=True,则γ和β这两个可学习的通道仿射变换参数向量的大小为num_channels。

num_groups(int): 将通道分成的组的数量

num_channels(int):输入期待的通道数

tt = torch.randn(2,4,3,3)
gn = nn.GroupNorm(num_groups=2,num_channels=4)

print("四个channel对应四种不同的变换","weight=",gn.weight,"bias=",gn.bias)
print("")
ot = gn(tt)

mean = torch.mean(tt[0,0:2])
var = torch.var(tt[0,0:2],False)
gn0000 = gn.bias[0] + gn.weight[0]*(tt[0][0][0][0]-mean)/torch.pow(var+gn.eps,0.5)
print("可以看到ot[0][0][0][0] 与 我们模拟计算的gn0000是相等的")
print("ot[0][0][0][0]=",ot[0][0][0][0],"gn0000=",gn0000)

mean = torch.mean(tt[0,2:4])
var = torch.var(tt[0,2:4],False)
gn0200 = gn.bias[2] + gn.weight[2]*(tt[0][2][0][0]-mean)/torch.pow(var+gn.eps,0.5)
print("可以看到ot[0][2][0][0] 与 我们模拟计算的gn0200是相等的")
print("ot[0][0][0][0]=",ot[0][2][0][0],"gn0000=",gn0200)

四个channel对应四种不同的变换 weight= Parameter containing:
tensor([1., 1., 1., 1.], requires_grad=True) bias= Parameter containing:
tensor([0., 0., 0., 0.], requires_grad=True)

可以看到ot[0][0][0][0] 与 我们模拟计算的gn0000是相等的
ot[0][0][0][0]= tensor(-0.0843, grad_fn=SelectBackward>) gn0000= tensor(-0.0843, grad_fn=AddBackward0>)
可以看到ot[0][2][0][0] 与 我们模拟计算的gn0200是相等的
ot[0][0][0][0]= tensor(-1.7789, grad_fn=SelectBackward>) gn0000= tensor(-1.7789, grad_fn=AddBackward0>)

你可能感兴趣的:(dnn,pytorch)