赞
踩
看到一些好的东西就忍不住想记录下来,方便学习和记忆。本文讲一下BatchNorm 训练和推理过程中的一些解读。
参考如下:
Batchnorm原理:https://blog.csdn.net/qq_25737169/article/details/79048516
上篇博客的归纳整理
推理时BN和Conv融合:https://mp.weixin.qq.com/s/P94ACKuoA0YapBKlrgZl3A
batchnorm顾名思义是对每batch个数据同时做一个norm,对一个神经元(或者一个卷积核)的输出减去一个batch统计得到的均值,除以标准差,然后乘以一个可学习的系数,再加上一个偏置,这个过程就完成了。
第一步:先求出此次批量数据x的均值,μβ=1m∑mi=1xi
第二步:求出此次批量数据的方差,σβ2=1m∑i=1m(xi−μβ)2
第三步:接下来就是对x做归一化,得到xi−
第四步:最重要的一步,引入缩放和平移变量γ和β ,计算归一化后的值,yi=γxi−+β
如果不加γ和β,直接归一化,是会打乱原有数据的分布,容易导致网络学不到任何东西,但是加入这两个参数后,事情就不一样了。先考虑特殊情况,假设γ是batch的方差,β是batch的均值,那么yi=γxi−+β得到的yi就是还原到了归一化之前的x,也就是缩放平移到了归一化前的分布,相当于batchnorm没有改变任何分布没有起作用。所以,加入了γ和β这两个参数后的batchnorm,保证了每一次数据归一化后还保留有之前学习来的特征分布,同时又能完成归一化的操作,加速训练。
在训练过程中,为保持稳定,一般使用滑动平均法更新均值和方差,滑动平均就是在更新当前值的时候,以一定比例保存之前的数值,以均值 为例,以一定比例 (例如这里0.99)保存之前的均值,当前只更新0.001倍的本Batch的均值,计算方法如下:
训练代码如下(示例):
def Batchnorm_simple_for_train(x, gamma, beta, bn_param): """ param:x : 输入数据,设shape(B,L) param:gama : 缩放因子 γ param:beta : 平移因子 β param:bn_param : batchnorm所需要的一些参数 eps : 接近0的数,防止分母出现0 momentum : 动量参数,一般为0.9, 0.99, 0.999 running_mean :滑动平均的方式计算新的均值,训练时计算,为测试数据做准备 running_var : 滑动平均的方式计算新的方差,训练时计算,为测试数据做准备 """ running_mean = bn_param['running_mean'] #shape = [B] running_var = bn_param['running_var'] #shape = [B] results = 0. # 建立一个新的变量 x_mean=x.mean(axis=0) # 计算x的均值 x_var=x.var(axis=0) # 计算方差 x_normalized=(x-x_mean)/np.sqrt(x_var+eps) # 归一化 results = gamma * x_normalized + beta # 缩放平移 running_mean = momentum * running_mean + (1 - momentum) * x_mean running_var = momentum * running_var + (1 - momentum) * x_var #记录新的值 bn_param['running_mean'] = running_mean bn_param['running_var'] = running_var return results , bn_param
在训练的时候事先计算好mean、var在测试的时候直接拿来用就行,不用计算均值和方差。
running_mean = momentum * running_mean + (1 - momentum) * x_mean
running_var = momentum * running_var + (1 - momentum) * x_var
测试代码如下(示例):
def Batchnorm_simple_for_test(x, gamma, beta, bn_param): """ param:x : 输入数据,设shape(B,L) param:gama : 缩放因子 γ param:beta : 平移因子 β param:bn_param : batchnorm所需要的一些参数 eps : 接近0的数,防止分母出现0 momentum : 动量参数,一般为0.9, 0.99, 0.999 running_mean :滑动平均的方式计算新的均值,训练时计算,为测试数据做准备 running_var : 滑动平均的方式计算新的方差,训练时计算,为测试数据做准备 """ running_mean = bn_param['running_mean'] #shape = [B] running_var = bn_param['running_var'] #shape = [B] results = 0. # 建立一个新的变量 x_normalized=(x-running_mean )/np.sqrt(running_var +eps) # 归一化 results = gamma * x_normalized + beta # 缩放平移 return results , bn_param
训练的时候,均值mean、方差var、γ 、β是一直在更新的,但是,在推理的时候,以上四个值都是固定了的,也就是推理的时候,均值和方差来自训练样本的数据分布。因此,在推理的时候,上面BN的计算公式可以变形为:
在均值mean、方差var、γ 、β都是固定值的时候,上面公式可以改写为:
推理的时候,Batch Norm层的4个参数是固定的常数,我们以一个三个神经元输入的全连接网络为例,如下图::
则全连接输出:
其中c 为偏置(这里为避免与上面的冲突,所以用 c 表示),那么全连接 + BN 一起,则是:
公式转换如下:
到这里大家应该清楚了,因为推理时,BN是一个线性的操作,也就是一个缩放+一个偏移,我们完全可以把这个线性操作叠加到前面的全连接层或者卷积层,只需要把全连接或者卷积层的权重乘以一个系数a (alpha),偏置从 c 变为 ac+b 就可以了了。完整的过程如下图:
在训练时候,在卷积层后面直接加BN层,训练完成后,我们只需要将网络中BN层去掉,读取原来的卷积层权重和偏置,以及BN层的四个参数(均值、方差、γ 、β),然后按照上面的计算方法替换卷积核的权重,更新偏置就可以了。
pytorch 官方实现https://github.com/pytorch/pytorch/blob/master/torch/nn/utils/fusion.py
ResNet18中一个卷积+BN层融合后代码如下(示例):
import torch import torchvision def fuse(conv, bn): fused = torch.nn.Conv2d( conv.in_channels, conv.out_channels, kernel_size=conv.kernel_size, stride=conv.stride, padding=conv.padding, bias=True ) # setting weights w_conv = conv.weight.clone().view(conv.out_channels, -1) w_bn = torch.diag(bn.weight.div(torch.sqrt(bn.eps+bn.running_var))) fused.weight.copy_( torch.mm(w_bn, w_conv).view(fused.weight.size()) ) # setting bias if conv.bias is not None: b_conv = conv.bias else: b_conv = torch.zeros( conv.weight.size(0) ) b_bn = bn.bias - bn.weight.mul(bn.running_mean).div( torch.sqrt(bn.running_var + bn.eps) ) fused.bias.copy_( b_conv + b_bn ) return fused # Testing # we need to turn off gradient calculation because we didn't write it torch.set_grad_enabled(False) x = torch.randn(16, 3, 256, 256) resnet18 = torchvision.models.resnet18(pretrained=True) # removing all learning variables, etc resnet18.eval() model = torch.nn.Sequential( resnet18.conv1, resnet18.bn1 ) f1 = model.forward(x) fused = fuse(model[0], model[1]) f2 = fused.forward(x) d = (f1 - f2).mean().item() print("error:",d)
Note: 融合BN仅限于Conv+BN或者是BN+Conv结构,中间不能加非线性层,例如Conv+Relu+BN那就不行了。当然,一般结构都是Conv+BN+Relu结构。
至此,本人对Batch Norm有了一个更深的理解,后续在部署时推理效率提升可以使用这个方法。tips:一般需将模型转换成caffe模型来merge卷积和BN层能避免更多坑。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。