深度学习(DBBNet重参数化)

DBBNet重参数化是ACNet的升级版本,又可以叫做ACNetV2。

结构如下,训练时将多个结构并联,推理时整合为一个卷积层:

8ba9b38738bfa50dd5c6c612c073e155

我实现时发现两个卷积做串联再重参数无法保证输入输出张量一样大,最后参考了原始作者代码发现他将BN层改造了一下,从而保证张量大小一致。 

串联两个卷积再做重参数可以参考这篇文章:PyTorch 卷积的工作原理或如何将两个卷积折叠为一个 |迈向数据科学

下面代码参考了原始开源工程:https://github.com/DingXiaoH/DiverseBranchBlock

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

def transI_fusebn(kernel, bn):
    gamma = bn.weight
    std = (bn.running_var + bn.eps).sqrt()
    return kernel * ((gamma / std).reshape(-1, 1, 1, 1)), bn.bias - bn.running_mean * gamma / std

def transIII_1x1_kxk(k1, b1, k2, b2, groups):

    if groups == 1:
        k = F.conv2d(k2, k1.permute(1, 0, 2, 3))      #
        b_hat = (k2 * b1.reshape(1, -1, 1, 1)).sum((1, 2, 3))
    else:
        k_slices = []
        b_slices = []
        k1_T = k1.permute(1, 0, 2, 3)
        k1_group_width = k1.size(0) // groups
        k2_group_width = k2.size(0) // groups
        for g in range(groups):
            k1_T_slice = k1_T[:, g*k1_group_width:(g+1)*k1_group_width, :, :]
            k2_slice = k2[g*k2_group_width:(g+1)*k2_group_width, :, :, :]
            k_slices.append(F.conv2d(k2_slice, k1_T_slice))
            b_slices.append((k2_slice * b1[g*k1_group_width:(g+1)*k1_group_width].reshape(1, -1, 1, 1)).sum((1, 2, 3)))
        k, b_hat = transIV_depthconcat(k_slices, b_slices)
    return k, b_hat + b2

def transIV_depthconcat(kernels, biases):
    return torch.cat(kernels, dim=0), torch.cat(biases)

def transV_avg(channels, kernel_size, groups):
    input_dim = channels // groups
    k = torch.zeros((channels, input_dim, kernel_size, kernel_size))
    k[np.arange(channels), np.tile(np.arange(input_dim), groups), :, :] = 1.0 / kernel_size ** 2
    return k

def transVI_multiscale(kernel, target_kernel_size):
    H_pixels_to_pad = (target_kernel_size - kernel.size(2)) // 2
    W_pixels_to_pad = (target_kernel_size - kernel.size(3)) // 2
    return F.pad(kernel, [H_pixels_to_pad, H_pixels_to_pad, W_pixels_to_pad, W_pixels_to_pad])

class BNAndPadLayer(nn.Module):
    def __init__(self,
                 pad_pixels,
                 num_features,
                 eps=1e-5,
                 momentum=0.1,
                 affine=True,
                 track_running_stats=True):
        super(BNAndPadLayer, self).__init__()
        self.bn = nn.BatchNorm2d(num_features, eps, momentum, affine, track_running_stats)
        self.pad_pixels = pad_pixels

    def forward(self, input):
        output = self.bn(input)

        if self.pad_pixels > 0:
            if self.bn.affine:
                pad_values = self.bn.bias.detach() - self.bn.running_mean * self.bn.weight.detach() / torch.sqrt(self.bn.running_var + self.bn.eps)
            else:
                pad_values = - self.bn.running_mean / torch.sqrt(self.bn.running_var + self.bn.eps)
            output = F.pad(output, [self.pad_pixels] * 4)

            pad_values = pad_values.view(1, -1, 1, 1)
            output[:, :, 0:self.pad_pixels, :] = pad_values
            output[:, :, -self.pad_pixels:, :] = pad_values
            output[:, :, :, 0:self.pad_pixels] = pad_values
            output[:, :, :, -self.pad_pixels:] = pad_values

        return output

    @property
    def weight(self):
        return self.bn.weight

    @property
    def bias(self):
        return self.bn.bias

    @property
    def running_mean(self):
        return self.bn.running_mean

    @property
    def running_var(self):
        return self.bn.running_var

    @property
    def eps(self):
        return self.bn.eps


class DBBNet(nn.Module):

    def __init__(self, in_channels, out_channels, kernel_size,
                 stride=1, padding=0, dilation=1, groups=1,
                 deploy=False):
        super(DBBNet, self).__init__()
        self.deploy = deploy

        self.kernel_size = kernel_size
        self.out_channels = out_channels
        self.groups = groups


        self.conv_fusion = nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride,
                                      padding=padding, dilation=dilation, groups=groups, bias=True)

        self.dbb_1x1= nn.Sequential()
        self.dbb_1x1.add_module('conv', nn.Conv2d(in_channels, out_channels, 1, stride=stride, padding=0, groups=groups, bias=False))
        self.dbb_1x1.add_module('bn', nn.BatchNorm2d(out_channels))

        self.dbb_1x1_kxk = nn.Sequential()
        self.dbb_1x1_kxk.add_module('conv1', nn.Conv2d(in_channels, in_channels,1, stride=1, padding=0, groups=groups, bias=False))
        self.dbb_1x1_kxk.add_module('bn1', BNAndPadLayer(pad_pixels=padding, num_features=in_channels, affine=True))
        self.dbb_1x1_kxk.add_module('conv2', nn.Conv2d(in_channels, out_channels,kernel_size, stride=stride, padding=0, groups=groups, bias=False))
        self.dbb_1x1_kxk.add_module('bn2', nn.BatchNorm2d(out_channels))

        self.dbb_1x1_avg = nn.Sequential()
        self.dbb_1x1_avg.add_module('conv', nn.Conv2d(in_channels, out_channels,1, stride=stride, padding=0, groups=groups, bias=False))
        self.dbb_1x1_avg.add_module('bn', BNAndPadLayer(pad_pixels=padding, num_features=in_channels, affine=True))
        self.dbb_1x1_avg.add_module('avgpool', nn.AvgPool2d(kernel_size=kernel_size, stride=stride, padding=0))
        self.dbb_1x1_avg.add_module('avgbn', nn.BatchNorm2d(out_channels))

        self.dbb_kxk = nn.Sequential()
        self.dbb_kxk.add_module('conv', nn.Conv2d(in_channels, out_channels,kernel_size, stride=stride, padding=padding, groups=groups, bias=False))    
        self.dbb_kxk.add_module('bn', nn.BatchNorm2d(out_channels))


    def reparam(self):

        self.deploy = True 

        # fus 1x1
        k_1x1,b_1x1 = transI_fusebn(self.dbb_1x1.conv.weight, self.dbb_1x1.bn)
        k_1x1 = transVI_multiscale(k_1x1, self.kernel_size)

        # fus 1x1_kxk
        k_1x1_kxk_first, b_1x1_kxk_first = transI_fusebn(self.dbb_1x1_kxk.conv1.weight, self.dbb_1x1_kxk.bn1)
        k_1x1_kxk_second, b_1x1_kxk_second = transI_fusebn(self.dbb_1x1_kxk.conv2.weight, self.dbb_1x1_kxk.bn2)
        k_1x1_kxk_merged, b_1x1_kxk_merged = transIII_1x1_kxk(k_1x1_kxk_first, b_1x1_kxk_first, k_1x1_kxk_second, b_1x1_kxk_second, groups=self.groups)
       
        # fus 1x1_avg
        k_avg = transV_avg(self.out_channels, self.kernel_size, self.groups)
        k_1x1_avg_first, b_1x1_avg_first = transI_fusebn(self.dbb_1x1_avg.conv.weight, self.dbb_1x1_avg.bn)
        k_1x1_avg_second, b_1x1_avg_second = transI_fusebn(k_avg, self.dbb_1x1_avg.avgbn)
        k_1x1_avg_merged, b_1x1_avg_merged = transIII_1x1_kxk(k_1x1_avg_first, b_1x1_avg_first, k_1x1_avg_second, b_1x1_avg_second, groups=self.groups)

        # fus kxk
        k_kxk, b_kxk = transI_fusebn(self.dbb_kxk.conv.weight, self.dbb_kxk.bn)

        self.conv_fusion.weight.data = k_1x1 + k_1x1_kxk_merged+ k_kxk + k_1x1_avg_merged 
        self.conv_fusion.bias.data = b_1x1 + b_1x1_kxk_merged  + b_kxk+ b_1x1_avg_merged


    def forward(self, inputs):

        if self.deploy:
            return self.conv_fusion(inputs)
        else:
            return self.dbb_1x1(inputs) + self.dbb_1x1_kxk(inputs)  + self.dbb_kxk(inputs)+ self.dbb_1x1_avg(inputs)


x = torch.randn(1, 20, 224, 224)

net1 = DBBNet(20,20,3,padding=1,deploy=False)
torch.save(net1.state_dict(), "dbb.pth")
net1.eval()
y1 = net1(x)

net2 = DBBNet(20,20,3,padding=1,deploy=False)
net2.load_state_dict(torch.load("dbb.pth"))
net2.reparam()
net2.eval()
y2 = net2(x)

print(y1.shape,y2.shape)
print(torch.allclose(y1, y2, atol=1e-4))

torch.onnx.export(net1, x, "dbb.onnx", input_names=['input'], output_names=['output'])
torch.onnx.export(net2, x, "dbb_deploy.onnx", input_names=['input'], output_names=['output'])
posted @ 2025-09-20 19:59  Dsp Tian  阅读(20)  评论(0)    收藏  举报