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

nn.BatchNorm2d——批量标准化操作

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

功能:对输入的四维数组进行批量标准化处理,具体计算公式如下:
y = x − m e a n [ x ] V a r [ x ] + e p s ∗ g a m m a + b e t a y=\frac{x-mean[x]}{\sqrt{Var[x]+eps}}*gamma+beta y=Var[x]+eps xmean[x]gamma+beta

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

训练阶段的均值和方差计算方法相同,将所有batch相同通道的值取出来,一块计算均值和方差,即计算当前观测值的均值和方差。

测试阶段的均值和方差有两种计算方法:
①估计所有图片的均值和方差,即做全局计算,具体计算方法如下:
模型分别储存各个通道(通道数需要预先定义)的均值和方差数据(初始为0和1),在每次训练过程中,每标准化一组数据,都利用计算得到的局部观测值的均值和方差对储存的数据做更新测试阶段利用模型存储的两个数据做标准化处理,更新公式如下:
X n e w = ( 1 − m o m e n t u m ) × X o l d + m o m e n t u m × X t 其中, X n e w 是模型的新参数, X o l d 是模型原来的参数, X t 是当前观测值的参数 X_{new}=(1-momentum)\times X_{old} + momentum\times X_t\\ 其中,X_{new}是模型的新参数,X_{old}是模型原来的参数,X_t是当前观测值的参数 Xnew=(1momentum)×Xold+momentum×Xt其中,Xnew是模型的新参数,Xold是模型原来的参数,Xt是当前观测值的参数
②采用和训练阶段相同的计算方法,即只计算当前输入数据的均值和方差

输入:

  • num_features:输入图像的通道数量。
  • eps:稳定系数,防止分母出现0。
  • momentum:模型均值和方差更新时的参数,见上述公式。
  • affine:代表gamma,beta是否可学。如果设为True,代表两个参数是通过学习得到的;如果设为False,代表两个参数是固定值,默认情况下,gamma是1,beta是0。
  • track_running_stats:代表训练阶段是否更新模型存储的均值和方差,即测试阶段的均值与方差的计算方法采用第一种方法还是第二种方法。如果设为True,则代表训练阶段每次迭代都会更新模型存储的均值和方差(计算全局数据),测试过程中利用存储的均值和方差对各个通道进行标准化处理;如果设为False,则模型不会存储均值和方差,训练过程中也不会更新均值和方差的数据,测试过程中只计算当前输入图像的均值和方差数据(局部数据)。具体区别见代码案例。

注意:

  • 训练阶段的标准化过程中,均值和方差来源途径只有一种方式,即利用当前输入的数据进行计算。
  • 测试阶段的标准化过程中,均值和方差来源途径有两种方式,一是来源于全局的数据,即模型本身存储一组均值和方差数据,在训练过程中,不断更新它们,使其具有描述全局数据的统计特性;二是来源于当前的输入数据,即和训练阶段计算方法一样,但这样会在测试过程中带来统计特性偏移的弊端,一般track_running_stats设置为True,即采用第一种来源途径。
  • 换句话说,就是训练阶段和测试阶段所承载的任务不同,训练阶段主要是通过已知的数据去优化模型,而测试阶段主要是利用已知的模型去预测未知的数据。

用途:

  • 训练过程中遇到收敛速度很慢的问题时,可以通过引入BN层来加快网络模型的收敛速度
  • 遇到梯度消失或者梯度爆炸的问题时,可以考虑引入BN层来解决
  • 一般情况下,还可以通过引入BN层来加快网络的训练速度

批量标准化的具体原理请参考论文:Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift

代码案例

一般用法

import torch
from torch import nn
# 在(0-1)范围内随机生成数据
img=torch.rand(2,2,2,3)
bn=nn.BatchNorm2d(2)
img_2=bn(img)
print(img)
print(img_2)

输出

# 标准化前
tensor([[[[0.5330, 0.7753, 0.6192],
          [0.9190, 0.1657, 0.5841]],

         [[0.7766, 0.7864, 0.2004],
          [0.9379, 0.3253, 0.1964]]],


        [[[0.7448, 0.9222, 0.1860],
          [0.3829, 0.8812, 0.2508]],

         [[0.0130, 0.0405, 0.2205],
          [0.8997, 0.5143, 0.9414]]]])
# 标准化后
tensor([[[[-0.1764,  0.7257,  0.1446],
          [ 1.2605, -1.5434,  0.0140]],

         [[ 0.8332,  0.8615, -0.8287],
          [ 1.2987, -0.4685, -0.8403]]],


        [[[ 0.6121,  1.2726, -1.4678],
          [-0.7350,  1.1199, -1.2269]],

         [[-1.3693, -1.2899, -0.7707],
          [ 1.1883,  0.0769,  1.3088]]]], grad_fn=<NativeBatchNormBackward>)

标准化过程是以通道为维度计算的,即所有batch下,相同通道(channel)下的数据合并到一块,做标准化处理。若有C个通道,无论batch是多少,都会有C次标准化。

import torch
from torch import nn
img=torch.rand(2,2,2,3)
# 取出两个batch下第一个维度的数据
a=torch.cat((img[0,0,:,:],img[1,0,:,:]),dim=0)
# 转化为numpy格式,便于计算均值方差
b=a.numpy()
import numpy as np
mean=np.mean(b)
std=np.std(b)+1e-5
# 手动标准化
img_2=(b-mean)/std
bn=nn.BatchNorm2d(2)
# 利用BatchNorm2d标准化
img_3=bn(img)
print(img_2)
print(img_3)

输出

手动标准化得到的数据,前两行代表第一个batch下第一个通道标准化后的数据,与利用BatchNorm2d的前两行数据相等;后两行代表第二个batch下第一个通道标准化后的数据,与利用BatchNorm2d的前五六行数据相等。

# 手动标准化
[[-0.8814389  -1.3535967   0.05035681]
 [-0.5180839  -1.396645    1.8198812 ]
 [ 0.9151892   0.9469903  -0.7903797 ]
 [ 0.35690263  1.135288   -0.28446582]]
# 利用BatchNorm2d标准化
tensor([[[[-0.8814, -1.3535,  0.0504],
          [-0.5181, -1.3966,  1.8198]],

         [[-1.5779, -0.5996, -1.0233],
          [-0.3919, -0.6692,  0.6693]]],


        [[[ 0.9151,  0.9469, -0.7903],
          [ 0.3569,  1.1352, -0.2844]],

         [[ 0.5829, -0.7664,  1.1329],
          [ 1.4469, -0.4100,  1.6063]]]], grad_fn=<NativeBatchNormBackward>)
track_running_stats设为TrueFasle的区别

训练过程

import torch
from torch import nn
img=torch.rand(2,2,2,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,'var:',bn_t.running_var)
print('bn_f,mean:',bn_f.running_mean,'var:',bn_f.running_var)
# 转化为训练阶段
bn_t.train()
bn_f.train()
img_t=bn_t(img)
img_f=bn_f(img)
print(img_t)
print(img_f)
print('一次迭代更新bn_t,mean:',bn_t.running_mean,'var:',bn_t.running_var)
img_t=bn_t(img)
print('两次迭代更新bn_t,mean:',bn_t.running_mean,'var:',bn_t.running_var)

输出

# 初始过程,track_running_stats设为True时,模型存储全局均值与方差
# 初始化为0和1,两个值对应两个通道
bn_t,mean: tensor([0., 0.]) var: tensor([1., 1.])
# track_running_stats设为False时,模型不存储均值与方差
bn_f,mean: None var: None
# 由下面的结果易知,track_running_stats设为True与False,对训练过程的标准化结果无影响
tensor([[[[-1.0599,  0.9532, -0.2647],
          [ 0.8146,  0.2971, -1.7099]],

         [[ 1.0554,  0.9239,  1.9331],
          [ 0.0334, -1.3058, -0.0804]]],


        [[[ 1.0146,  0.7528, -0.1986],
          [ 1.3564, -1.6232, -0.3325]],

         [[-1.6591, -0.7690, -0.3045],
          [ 0.7691,  0.1344, -0.7306]]]], grad_fn=<NativeBatchNormBackward>)
tensor([[[[-1.0599,  0.9532, -0.2647],
          [ 0.8146,  0.2971, -1.7099]],

         [[ 1.0554,  0.9239,  1.9331],
          [ 0.0334, -1.3058, -0.0804]]],


        [[[ 1.0146,  0.7528, -0.1986],
          [ 1.3564, -1.6232, -0.3325]],

         [[-1.6591, -0.7690, -0.3045],
          [ 0.7691,  0.1344, -0.7306]]]], grad_fn=<NativeBatchNormBackward>)
# track_running_stats设为Ture时,模型每标准化一组数据,都会更新自己存储的数据一次
一次迭代更新bn_t,mean: tensor([0.0562, 0.0586]) var: tensor([0.9092, 0.9043])
两次迭代更新bn_t,mean: tensor([0.1068, 0.1114]) var: tensor([0.8275, 0.8183])

测试过程

import torch
from torch import nn
# img和上个训练过程的数据一样,为了便于做比较
bn_t=nn.BatchNorm2d(2,track_running_stats=True)
bn_f=nn.BatchNorm2d(2,track_running_stats=False)
# 输出初始的模型存储值,测试过程中,bn_t利用该值进行标准化
print('bn_t,mean:',bn_t.running_mean,'var:',bn_t.running_var)
# 转化为测试过程
bn_t.eval()
bn_f.eval()
img_t=bn_t(img)
img_f=bn_f(img)
print(img)
print(img_t)
print(img_f)
bn_t.train()
img_t=bn_t(img)
bn_t.eval()
img_t=bn_t(img)
print('更新后bn_t,mean:',bn_t.running_mean,'var:',bn_t.running_var)
print(img_t)

输出

# track_running_stats设为Ture时
# 在测试过程中利用running_mean和running_var做标准化计算
bn_t,mean: tensor([0., 0.]) var: tensor([1., 1.])
# 如果不进行训练,则默认初始值为0和1,用这两个数做标准化时的结果与输入相同
# 输入数据
tensor([[[[0.2542, 0.8395, 0.4854],
          [0.7992, 0.6488, 0.0652]],

         [[0.7970, 0.7707, 0.9722],
          [0.5929, 0.3256, 0.5702]]],


        [[[0.8574, 0.7813, 0.5046],
          [0.9568, 0.0904, 0.4657]],

         [[0.2550, 0.4327, 0.5255],
          [0.7398, 0.6131, 0.4404]]]])
# track_running_stats设为Ture时,拿初始化数据进行标准化计算结果
# 可见与上述结果相同
tensor([[[[0.2542, 0.8395, 0.4854],
          [0.7992, 0.6488, 0.0652]],

         [[0.7970, 0.7707, 0.9722],
          [0.5929, 0.3256, 0.5702]]],


        [[[0.8574, 0.7813, 0.5046],
          [0.9568, 0.0904, 0.4657]],

         [[0.2550, 0.4327, 0.5255],
          [0.7398, 0.6131, 0.4404]]]], grad_fn=<NativeBatchNormBackward>)
# track_running_stats设为Fasle时,标准化计算结果和训练过程一样,因此结果相同
# 这里的输入数据和上一个案例一样,可以和上个过程的结果做比较。
tensor([[[[-1.0599,  0.9532, -0.2647],
          [ 0.8146,  0.2971, -1.7099]],

         [[ 1.0554,  0.9239,  1.9331],
          [ 0.0334, -1.3058, -0.0804]]],


        [[[ 1.0146,  0.7528, -0.1986],
          [ 1.3564, -1.6232, -0.3325]],

         [[-1.6591, -0.7690, -0.3045],
          [ 0.7691,  0.1344, -0.7306]]]], grad_fn=<NativeBatchNormBackward>)
# 经过一次训练过程,running_mean与running_var都有所改变
更新后bn_t,mean: tensor([0.0562, 0.0586]) var: tensor([0.9092, 0.9043])
# 再进行测试时,用新的running_mean和running_var做标准化计算
tensor([[[[0.2076, 0.8215, 0.4501],
          [0.7792, 0.6214, 0.0094]],

         [[0.7764, 0.7488, 0.9607],
          [0.5619, 0.2807, 0.5380]]],


        [[[0.8402, 0.7604, 0.4702],
          [0.9444, 0.0358, 0.4294]],

         [[0.2065, 0.3934, 0.4909],
          [0.7163, 0.5831, 0.4015]]]], grad_fn=<NativeBatchNormBackward>)

官方文档

nn.BatchNorm2d:https://pytorch.org/docs/stable/generated/torch.nn.BatchNorm2d.html?highlight=norm2d#torch.nn.BatchNorm2d

可以将生成器改为以下结构,使其可以匹配edge-connect中的修补模式的预训练模型键值的结构: ``` class Generator(nn.Module): def __init__(self): super(Generator, self).__init__() self.encoder = nn.Sequential( nn.Conv2d(3, 64, 3, stride=2, padding=1), nn.BatchNorm2d(64), nn.LeakyReLU(0.2), nn.Conv2d(64, 128, 3, stride=2, padding=1), nn.BatchNorm2d(128), nn.LeakyReLU(0.2), nn.Conv2d(128, 256, 3, stride=2, padding=1), nn.BatchNorm2d(256), nn.LeakyReLU(0.2), nn.Conv2d(256, 512, 3, stride=2, padding=1), nn.BatchNorm2d(512), nn.LeakyReLU(0.2), nn.Conv2d(512, 4000, 1), nn.BatchNorm2d(4000), nn.LeakyReLU(0.2) ) self.decoder = nn.Sequential( nn.ConvTranspose2d(4000, 512, 3, stride=2, padding=1, output_padding=1), nn.BatchNorm2d(512), nn.LeakyReLU(0.2), nn.ConvTranspose2d(512, 256, 3, stride=2, padding=1, output_padding=1), nn.BatchNorm2d(256), nn.LeakyReLU(0.2), nn.ConvTranspose2d(256, 128, 3, stride=2, padding=1, output_padding=1), nn.BatchNorm2d(128), nn.LeakyReLU(0.2), nn.ConvTranspose2d(128, 64, 3, stride=2, padding=1, output_padding=1), nn.BatchNorm2d(64), nn.LeakyReLU(0.2), nn.ConvTranspose2d(64, 3, 3, stride=1, padding=1), nn.Tanh() ) self.edge_connect_keys = nn.ModuleList([nn.Linear(4000, 4000) for _ in range(4)]) self.edge_connect_values = nn.ModuleList([nn.Linear(4000, 4000) for _ in range(4)]) def forward(self, x, mask=None): x = self.encoder(x) if mask is not None: for i in range(4): x = self.edge_connect_keys[i](x[mask == i]) x = x[mask == i] x = self.edge_connect_values[i](x) x = x.view(x.size(0), -1, 1, 1) x = self.decoder(x) return x ``` 其中,增加了 `edge_connect_keys` 和 `edge_connect_values` 两个模块列表,用于存储4个方向的修补模式的预训练模型键值。在 `forward` 方法中,如果有 mask,就根据 mask 的值将 x 分别传入 edge_connect_keys 和 edge_connect_values 中进行处理,并将结果合并。最后再将结果传入 decoder 中进行解码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

视觉萌新、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值