这里我们解读其核心部分。
核心思想
其核心思想就是做一个算子融合,比如常见的conv + bn算子融合。只是这里做了更彻底,更好,将三个分支,融合成一个分支
1. 重定义模型
训练的时候,模型主要由如下的模块组成。一个stage中有3个分支。
推理的时候,主要流程是:
- stride != 1 的时候
- conv3*3 + bn 融合 得到w1,b1
kernel = branch.conv.weight
running_mean = branch.bn.running_mean
running_var = branch.bn.running_var
gamma = branch.bn.weight
beta = branch.bn.bias
eps = branch.bn.eps
std = (running_var + eps).sqrt()
t = (gamma / std).reshape(-1, 1, 1, 1)
return kernel * t, beta - running_mean * gamma / std
- conv1*1 + bn 融合 得到w2, b2。同上
- 然后将上面两个 layer 转换成一个 conv layer ,这里的conv layer是有偏置的
if deploy:
self.rbr_reparam = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride,
padding=padding, dilation=dilation, groups=groups, bias=True, padding_mode=padding_mode)
else:
self.rbr_identity = nn.BatchNorm2d(num_features=in_channels) if out_channels == in_channels and stride == 1 else None
self.rbr_dense = conv_bn(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=padding, groups=groups)
self.rbr_1x1 = conv_bn(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=stride, padding=padding_11, groups=groups)
print('RepVGG Block, identity = ', self.rbr_identity)
注意: conv3,conv1需要保持同样的stride
kernel3x3, bias3x3 = self._fuse_bn_tensor(self.rbr_dense)
kernel1x1, bias1x1 = self._fuse_bn_tensor(self.rbr_1x1)
kernelid, biasid = self._fuse_bn_tensor(self.rbr_identity)
- stride == 1
- conv3*3 + bn 融合 得到w1,b1
- conv1*1 + bn 融合 得到w2, b2
- bn 可以转换成一个中心点=1的conv3卷积,得到 w3,b3
- 最后就是3个卷积核,偏置相加得到一个卷积核和偏置
kernel3x3 + self._pad_1x1_to_3x3_tensor(kernel1x1) + kernelid, bias3x3 + bias1x1 + biasid
2. 总结
这篇文章是算子融合 + 效果好。值得推荐