查看原文
其他

必看必会的模型推理加速技巧:融合BN和Conv层

AI小将 机器学习算法工程师 2021-12-31

编辑:我是小将  

很多深度模型采用BN层(Batch Normalization)被很多深度模型来提升泛化能力。在模型推理时,BN层要从训练状态切换到测试状态,此时采用模型训练中近似的均值和方差。BN层最酷的地方是它可以用一个1x1卷积等效替换,更进一步地,我们可以将BN层合并到前面的卷积层中

Batch Normalization

这里假定是网络的某一激活层特征,我们要对其进行归一化。若模型训练batch中共有个样例,其特征分别是 ,我们采用下列公式进行归一化:

这里为这个batch上计算得到的均值和方差(在B,H,W维度上计算,每个channel单独计算),而  防止除零所设置的一个极小值, 是放缩系数,而是平移系数。在训练过程中, and 在当前batch上计算:


而参数是和其它模型参数一起通过梯度下降方法训练得到。在测试阶段,我们不太会对一个batch图像进行预测,一般是对单张图像测试。因此,通过前面公式计算 and 就不可能。BN的解决方案是采用训练过程中指数移动平均值

目前,BN层大部分用于CNN网络中,此时上面的4个参数就是针对特征图各个通道的参数,这里我们记以及为第个通道的参数。

融合方案

首先我们将测试阶段的BN层(一般称为frozen BN)等效替换为一个1x1卷积层:

对于一个形状为的特征图,归一化后的结果,可以如下计算:

这里的是特征图的各个空间位置,我们可以看到上述计算就是形式,其可以看成是卷积,这里W的行向量就是每个对应输出通道的卷积核()。由于BN层常常在Conv层之后,我们可以进行两个操作的合并。

然后我们将BN层和Conv层融合:这里我们记:

  • 为BN的参数,
  • 是BN前面Conv层的参数,
  • 为Conv层的输入,
  • 为输入层的channel数,
  • 是Conv层的卷积核大小

我们将的每个卷积部分reshape为一个维度为 的向量,因此Conv层加BN层的操作为:

显然,我们可以将Conv层和BN层合并成一个新的卷积层,其参数为:

  • filter weights: 
  • bias: 

最后我们在PyTorch中实现这个融合操作:nn.Conv2d参数:

  • filter weights,  : conv.weight;
  • bias, : conv.bias;

nn.BatchNorm2d参数:

  • scaling,  : bn.weight;
  • shift, : bn.bias;
  • mean estimate,  : bn.running_mean;
  • variance estimate,  : bn.running_var;
  • (为了数值稳定性): bn.eps.

具体的实现代码如下(Google Colab, https://colab.research.google.com/drive/1mRyq_LlJW4u_rArzzhEe_T6tmEWoNN1K):

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)

运行代码,会发现融合BN和Conv层之后推理结果是一样,所以是等效替换。另外也可以对比前后推理时间的差异,会发现融合后推理时间会减少。

原文链接:Speeding up model with fusing batch normalization and convolution (http://learnml.today/speeding-up-model-with-fusing-batch-normalization-and-convolution-3)


推荐阅读

堪比Focal Loss!解决目标检测中样本不平衡的无采样方法

超越BN和GN!谷歌提出新的归一化层:FRN

另辟蹊径!斯坦福大学提出边界框回归任务新Loss:GIoU

人人必须要知道的语义分割模型:DeepLabv3+



机器学习算法工程师


                            一个用心的公众号

长按,识别,加关注

进群,学习,得帮助

你的关注,我们的热度,

我们一定给你学习最大的帮助


                                                                                                    


: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存