presentation: 如何白嫖模型性能,最近瞻仰了几篇dxh的CVPR paper, 使用多分支网络训练模型,再利用结构重参数,根据参数的等价转换实现对网络结构的等价转换,即实现了简单网络对性能的提高,也加速了模型的推理时间,对于模型压缩和移动端部署等都具有很高的参考价值。
实现了对CIFAR10数据集进行分类训练。
vgg16(): 13层卷积层+ 3层全连接层
整个网络有5个vgg-block块和5个maxpool层逐个相连,然后进入FC层,直到最后1000路softmax输出。
仅使用3x3卷积, 仅使用ReLU作为激活函数。
将20多层3x3卷积堆起来,分成5个stage,每个stage的第一层是stride=2的降采样,每个卷积层用ReLU作为激活函数。
RepVGG-A: 5个stage分别有[1, 2, 4, 14, 1]层
conv_bn: 每一个卷积层后都有一个BN层, 卷积层:bias = False
每个stage第一层:
self.nonlinearity = nn.ReLU()
rbr_dense
rbr_1x1
第二层 +:
(nonlinearity): ReLU()
rbr_identity
rbr_dense
rbr_1x1
RepVGG除了最后的池化层和分类层之外,都是清一色RepVGGBlock堆叠;
1x1卷积——相当于一个特殊的(用0padding)的3x3卷积;
构造出一个以单位矩阵为卷积核的1x1卷积即可;
identity作用:Elementwise(逐元素)相加,每个通道中每个元素对应相加。
to do: 将identity分支用一个卷积层表示,这样才有可能融合。
identity前后值不变,那么我会想到是用权重等于1的卷积核,并分开通道进行卷积,即1x1的,权重固定为1的Depthwise卷积(每一个通道对应一个卷积)。
继续走:新的问题产生了,我们的3x3和1x1分支都不是Depthwise卷积,现在也是不能融合进去的,我们需要把identity的Depthwise卷积以普通卷积的形式表达出来。
Depthwise卷积——>普通卷积:
普通卷积输出是将各个通道结果相加,那么自然而然想到,我们只要将当前通道对应的卷积权重设置为1,而其他通道权重设置为0,不就等价Depthwise卷积。
重要性质:卷积核都展开, 展开来得到的是一个单位矩阵。
assert isinstance(branch, nn.BatchNorm2d) # 当branch是 identity,也即只有BN时候返回以上数据
if not hasattr(self, 'id_tensor'):
input_dim = self.in_channels // self.groups # 用groups(卷积核个数)计算了下卷积核对应的输入通道
kernel_value = np.zeros((self.in_channels, input_dim, 3, 3), dtype=np.float32)
#for循环给卷积核赋值1。为每一个通道设置对应的卷积核(将当前通道对应的卷积权重设置为1,而其他通道权重设置为0)
#这里就是用到了前面推导Identity分支,其卷积核等价于单位矩阵的这个特性,对第(i, i)个元素赋值为1。
#后续则是将Identity分支上的BN参数给赋值过去。
for i in range(self.in_channels):
kernel_value[i, i % input_dim, 1, 1] = 1
self.id_tensor = torch.from_numpy(kernel_value).to(branch.weight.device)
kernel = self.id_tensor
running_mean = branch.running_mean #平均值
running_var = branch.running_var #方差
gamma = branch.weight # BN γ
beta = branch.bias # BN β
eps = branch.eps #eps
return kernel * t, beta - running_mean * gamma / std #根据公式推出
参考:
郑泽康:https://www.zhihu.com/people/mardino
dxh: https://zhuanlan.zhihu.com/p/344324470