深度学习CNN网络推理时Batchnorm层和卷积层的融合,以提升推理速度。

深度学习系列文章目录

文章目录

  • 深度学习系列文章目录
  • 前言
  • 一、Batchnorm
  • 二、推理时BatchNorm和Conv融合
  • 总结


前言

看到一些好的东西就忍不住想记录下来,方便学习和记忆。本文讲一下BatchNorm 训练和推理过程中的一些解读。
参考如下:
Batchnorm原理:https://blog.csdn.net/qq_25737169/article/details/79048516
上篇博客的归纳整理
推理时BN和Conv融合:https://mp.weixin.qq.com/s/P94ACKuoA0YapBKlrgZl3A

一、Batchnorm

batchnorm顾名思义是对每batch个数据同时做一个norm,对一个神经元(或者一个卷积核)的输出减去一个batch统计得到的均值,除以标准差,然后乘以一个可学习的系数,再加上一个偏置,这个过程就完成了。
BN 公式

深度学习CNN网络推理时Batchnorm层和卷积层的融合,以提升推理速度。_第1张图片
第一步:先求出此次批量数据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.90.990.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.90.990.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

二、推理时BatchNorm和Conv融合

训练的时候,均值mean、方差var、γ 、β是一直在更新的,但是,在推理的时候,以上四个值都是固定了的,也就是推理的时候,均值和方差来自训练样本的数据分布。因此,在推理的时候,上面BN的计算公式可以变形为:
在这里插入图片描述
在均值mean、方差var、γ 、β都是固定值的时候,上面公式可以改写为:
在这里插入图片描述
推理的时候,Batch Norm层的4个参数是固定的常数,我们以一个三个神经元输入的全连接网络为例,如下图::
在这里插入图片描述

深度学习CNN网络推理时Batchnorm层和卷积层的融合,以提升推理速度。_第2张图片
则全连接输出:
在这里插入图片描述
其中c 为偏置(这里为避免与上面的冲突,所以用 c 表示),那么全连接 + BN 一起,则是:
在这里插入图片描述
公式转换如下:在这里插入图片描述
到这里大家应该清楚了,因为推理时,BN是一个线性的操作,也就是一个缩放+一个偏移,我们完全可以把这个线性操作叠加到前面的全连接层或者卷积层,只需要把全连接或者卷积层的权重乘以一个系数a (alpha),偏置从 c 变为 ac+b 就可以了了。完整的过程如下图:
深度学习CNN网络推理时Batchnorm层和卷积层的融合,以提升推理速度。_第3张图片
在训练时候,在卷积层后面直接加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层能避免更多坑。

你可能感兴趣的:(BatchNorm,深度学习)