YOLOv5(v7.0)网络修改实践二:把单分支head改为YOLOX的双分支解耦合head(DecoupleHead)

这篇具有很好参考价值的文章主要介绍了YOLOv5(v7.0)网络修改实践二:把单分支head改为YOLOX的双分支解耦合head(DecoupleHead)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前面研究了一下YOLOX的网络结构,在YOLOv5(tag7.0)集成了yolox的骨干网络,现在继续下一步集成YOLOX的head模块。YOLOX的head模块是双分支解耦合网络,把目标置信度的预测和目标的位置预测分成两条支路,并验证双分支解耦合头性能要优于单分支耦合头。

1、关于双分支解耦合头decouplehead

主要是个人理解。这里的双分支解耦合和单分支耦合是根据网络的结构和承担的任务来分配的,具体地讲,就是head模块是几条之路,如果是一条支路,那目标的类别和位置预测任务就都在这一条支路上进行,类别分类和位置预测使用同一套权重进行预测,对于这两个任务来说,这种单分支网络结构是耦合的,我理解的就是绑定在一块,经过同一个结构得到不同任务的结果。

基于任务特点,一个是关注类别信息,一个是关注位置信息,在不同的支路上完成这两个任务,这样的网络就可以针对不同任务独享各自的权重,但这两个任务都是为了表达同一个目标的信息,是什么,在哪里,所以这种双分支的结构对于同一个目标来说是解耦合的,decouple.

了解上述区别后,而只能使用单分支的yolov5已经性能显著了。那加上双分支是不是效果会更好呢,这非常让人期待,于是实践操作一下就可以进行验证亲自感受一下。而且yolov8使用的也是解耦合头和anchor free方式取得了更好的效果,这也让我们看到了做这件事的意义。

在实践操作的过程中,我发现直接在yolov5中复现yolox的head有点繁琐,因为需要直接把 单分支、anchor-based 改为 双分支、anchoe-free,所以我先把任务拆分,先实现 双分支、anchor-based.

2、YOLOX的head结构

这个可以直接看yolox的官方代码,找到YOLOXHead的定义代码。 其中__init__ 定义了head模块的基础结构,forward则是定义网络结构连接的方式,用以约束数据运算。

class YOLOXHead(nn.Module):
    def __init__(
        self,
        num_classes,
        width=1.0,
        strides=[8, 16, 32],
        in_channels=[256, 512, 1024],
        act="silu",
        depthwise=False,
    ):
        """
        Args:
            act (str): activation type of conv. Defalut value: "silu".
            depthwise (bool): whether apply depthwise conv in conv branch. Defalut value: False.
        """
        super().__init__()

        self.num_classes = num_classes
        self.decode_in_inference = True  # for deploy, set to False

        self.cls_convs = nn.ModuleList()
        self.reg_convs = nn.ModuleList()
        self.cls_preds = nn.ModuleList()
        self.reg_preds = nn.ModuleList()
        self.obj_preds = nn.ModuleList()
        self.stems = nn.ModuleList()
        Conv = DWConv if depthwise else BaseConv

        for i in range(len(in_channels)):
            self.stems.append(
                BaseConv(
                    in_channels=int(in_channels[i] * width),
                    out_channels=int(256 * width),
                    ksize=1,
                    stride=1,
                    act=act,
                )
            )
            self.cls_convs.append(
                nn.Sequential(
                    *[
                        Conv(
                            in_channels=int(256 * width),
                            out_channels=int(256 * width),
                            ksize=3,
                            stride=1,
                            act=act,
                        ),
                        Conv(
                            in_channels=int(256 * width),
                            out_channels=int(256 * width),
                            ksize=3,
                            stride=1,
                            act=act,
                        ),
                    ]
                )
            )
            self.reg_convs.append(
                nn.Sequential(
                    *[
                        Conv(
                            in_channels=int(256 * width),
                            out_channels=int(256 * width),
                            ksize=3,
                            stride=1,
                            act=act,
                        ),
                        Conv(
                            in_channels=int(256 * width),
                            out_channels=int(256 * width),
                            ksize=3,
                            stride=1,
                            act=act,
                        ),
                    ]
                )
            )
            self.cls_preds.append(
                nn.Conv2d(
                    in_channels=int(256 * width),
                    out_channels=self.num_classes,
                    kernel_size=1,
                    stride=1,
                    padding=0,
                )
            )
            self.reg_preds.append(
                nn.Conv2d(
                    in_channels=int(256 * width),
                    out_channels=4,
                    kernel_size=1,
                    stride=1,
                    padding=0,
                )
            )
            self.obj_preds.append(
                nn.Conv2d(
                    in_channels=int(256 * width),
                    out_channels=1,
                    kernel_size=1,
                    stride=1,
                    padding=0,
                )
            )

        self.use_l1 = False
        self.l1_loss = nn.L1Loss(reduction="none")
        self.bcewithlog_loss = nn.BCEWithLogitsLoss(reduction="none")
        self.iou_loss = IOUloss(reduction="none")
        self.strides = strides
        self.grids = [torch.zeros(1)] * len(in_channels)

    def initialize_biases(self, prior_prob):
        for conv in self.cls_preds:
            b = conv.bias.view(1, -1)
            b.data.fill_(-math.log((1 - prior_prob) / prior_prob))
            conv.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)

        for conv in self.obj_preds:
            b = conv.bias.view(1, -1)
            b.data.fill_(-math.log((1 - prior_prob) / prior_prob))
            conv.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)

    def forward(self, xin, labels=None, imgs=None):
        outputs = []
        origin_preds = []
        x_shifts = []
        y_shifts = []
        expanded_strides = []

        for k, (cls_conv, reg_conv, stride_this_level, x) in enumerate(
            zip(self.cls_convs, self.reg_convs, self.strides, xin)
        ):
            x = self.stems[k](x)
            cls_x = x
            reg_x = x

            cls_feat = cls_conv(cls_x)
            cls_output = self.cls_preds[k](cls_feat)

            reg_feat = reg_conv(reg_x)
            reg_output = self.reg_preds[k](reg_feat)
            obj_output = self.obj_preds[k](reg_feat)

从上述代码可知,head模块根据前面输出的三个维度的特征图数据进行网络构建,根据数据的维度具体定义网络的结构,具体方法是通过一个 for 循环来构建基础的结构:self.stem, self.cls_convs, self.reg_convs, self.cls_preds, self.reg_preds和self.obj_preds.通过看forward函数可以了解这些模块是怎么衔接的,进而可以按照自己的方式灵活的复现出一样的网络结构。

单独实例化YOLOXHead()得到的网络结构:

"""YOLOXHead(
  (cls_convs): ModuleList(
    (0): Sequential(
      (0): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
    (1): Sequential(
      (0): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
    (2): Sequential(
      (0): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
  )
  (reg_convs): ModuleList(
    (0): Sequential(
      (0): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
    (1): Sequential(
      (0): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
    (2): Sequential(
      (0): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
  )
  (cls_preds): ModuleList(
    (0): Conv2d(256, 80, kernel_size=(1, 1), stride=(1, 1))
    (1): Conv2d(256, 80, kernel_size=(1, 1), stride=(1, 1))
    (2): Conv2d(256, 80, kernel_size=(1, 1), stride=(1, 1))
  )
  (reg_preds): ModuleList(
    (0): Conv2d(256, 4, kernel_size=(1, 1), stride=(1, 1))
    (1): Conv2d(256, 4, kernel_size=(1, 1), stride=(1, 1))
    (2): Conv2d(256, 4, kernel_size=(1, 1), stride=(1, 1))
  )
  (obj_preds): ModuleList(
    (0): Conv2d(256, 1, kernel_size=(1, 1), stride=(1, 1))
    (1): Conv2d(256, 1, kernel_size=(1, 1), stride=(1, 1))
    (2): Conv2d(256, 1, kernel_size=(1, 1), stride=(1, 1))
  )
  (stems): ModuleList(
    (0): BaseConv(
      (conv): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (act): SiLU(inplace=True)
    )
    (1): BaseConv(
      (conv): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (act): SiLU(inplace=True)
    )
    (2): BaseConv(
      (conv): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (act): SiLU(inplace=True)
    )
  )
  (l1_loss): L1Loss()
  (bcewithlog_loss): BCEWithLogitsLoss()
  (iou_loss): IOUloss()
)
"""

3、重构YOLOX的DecoupledHead

基于yolov5的网络构建方式和自己的理解,重新根据网络的结构写了一下。我写的结构里 由于涉及到锚框,所以这里还不是完全体的yolox的head结构,只写了一条线的数据处理结构,因为yolov5的框架head模块要加入锚框,还会再定义。

class DecoupledHead(nn.Module):
    def __init__(
        self,
        in_channels=[256, 512, 1024],
        num_classes=80,
        width=0.5,
        anchors=(),
        act="silu",
        depthwise=False,
        prior_prob=1e-2,
    ):
        super().__init__()
        self.num_classes = num_classes
        # self.nl = len(anchors)
        self.na = len(anchors[0]) // 2
        # self.in_channels = in_channels
        # import ipdb;ipdb.set_trace()
        Conv = DWConv if depthwise else BaseConv
        self.stems=BaseConv(in_channels=int(in_channels * width),
                            out_channels=int(256 * width),
                            ksize=1,stride=1,act=act,)
        
        self.cls_convs=nn.Sequential(
                        Conv(in_channels=int(256 * width),
                            out_channels=int(256 * width),
                            ksize=3,stride=1,act=act,),
                        Conv(in_channels=int(256 * width),
                            out_channels=int(256 * width),
                            ksize=3,stride=1,act=act,),)
        self.cls_preds=nn.Conv2d(in_channels=int(256 * width),
                                out_channels=self.num_classes * self.na,
                                kernel_size=1,stride=1,padding=0,)
        
        self.reg_convs=nn.Sequential(
                        Conv(in_channels=int(256 * width),
                            out_channels=int(256 * width),
                            ksize=3,stride=1,act=act,),
                        Conv(in_channels=int(256 * width),
                            out_channels=int(256 * width),
                            ksize=3,stride=1,act=act,),)
        self.reg_preds=nn.Conv2d(in_channels=int(256 * width),
                                out_channels=4 * self.na,
                                kernel_size=1,stride=1,padding=0,)
        self.obj_preds=nn.Conv2d(in_channels=int(256 * width),
                                out_channels=1 * self.na,
                                kernel_size=1,stride=1,padding=0,)
    #没用上初始化函数
    def initialize_biases(self):
        prior_prob = self.prior_prob
        for conv in self.cls_preds:
            b = conv.bias.view(1, -1)
            b.data.fill_(-math.log((1 - prior_prob) / prior_prob))
            conv.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)

        for conv in self.obj_preds:
            b = conv.bias.view(1, -1)
            b.data.fill_(-math.log((1 - prior_prob) / prior_prob))
            conv.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)
            
    def forward(self,x):
        # import ipdb;ipdb.set_trace()
        x = self.stems(x)
        cls_x = x
        reg_x = x
        
        cls_feat = self.cls_convs(cls_x)
        cls_output = self.cls_preds(cls_feat)
        
        reg_feat = self.reg_convs(reg_x)
        reg_output = self.reg_preds(reg_feat)
        obj_output = self.obj_preds(reg_feat)
        
        # out = torch.cat([cls_output,reg_output,obj_output], 1)
        out = torch.cat([reg_output,obj_output,cls_output], 1)
        return out
        

可以看到YOLOXHead的 init 和 forward() 都包含两个for循环,分别用来构建head结构和处理数据,为了避免麻烦和匹配yolov5的框架,我写了单流程结构。通过后面再在yolo.py进一步实现yoloxhead的双分支结构。

yolo.py下再定义的头部结构代码:

class DetectDcoupleHead(nn.Module):
    stride = None  # strides computed during build
    dynamic = False  # force grid reconstruction
    export = False  # export mode
    def __init__(self, nc=80, anchors=(), width=1.0, ch=(), inplace=True):
        super().__init__()
        self.prior_prob = 1e-2
        self.in_ch = [256, 512, 1024]
        self.nc = nc  # number of classes
        self.width = width
        self.no = nc + 5  # number of outputs per anchor
        self.nl = len(anchors)  # number of detection layers
        self.na = len(anchors[0]) // 2  # number of anchors
        self.grid = [torch.empty(0) for _ in range(self.nl)]  # init grid
        self.anchor_grid = [torch.empty(0) for _ in range(self.nl)]  # init anchor grid
        self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2))  # shape(nl,na,2)
        # self.DecoupledHead = DecoupledHead()
        self.m = nn.ModuleList(DecoupledHead(x, self.nc, self.width, anchors) for x in self.in_ch)  # output conv
        self.inplace = inplace  # use inplace ops (e.g. slice assignment)
    
    def forward(self, x):
        z = []  # inference output
        # import ipdb;ipdb.set_trace()
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            # import ipdb;ipdb.set_trace()
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()

            # 
            if not self.training:  # inference
                if self.dynamic or self.grid[i].shape[2:4] != x[i].shape[2:4]:
                    self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)

                if isinstance(self, Segment):  # (boxes + masks)
                    xy, wh, conf, mask = x[i].split((2, 2, self.nc + 1, self.no - self.nc - 5), 4)
                    xy = (xy.sigmoid() * 2 + self.grid[i]) * self.stride[i]  # xy
                    wh = (wh.sigmoid() * 2) ** 2 * self.anchor_grid[i]  # wh
                    y = torch.cat((xy, wh, conf.sigmoid(), mask), 4)
                else:  # Detect (boxes only)
                    xy, wh, conf = x[i].sigmoid().split((2, 2, self.nc + 1), 4)
                    xy = (xy * 2 + self.grid[i]) * self.stride[i]  # xy
                    wh = (wh * 2) ** 2 * self.anchor_grid[i]  # wh
                    y = torch.cat((xy, wh, conf), 4)
                z.append(y.view(bs, self.na * nx * ny, self.no))

        return x if self.training else (torch.cat(z, 1),) if self.export else (torch.cat(z, 1), x)

    def _make_grid(self, nx=20, ny=20, i=0, torch_1_10=check_version(torch.__version__, '1.10.0')):
        d = self.anchors[i].device
        t = self.anchors[i].dtype
        shape = 1, self.na, ny, nx, 2  # grid shape
        y, x = torch.arange(ny, device=d, dtype=t), torch.arange(nx, device=d, dtype=t)
        yv, xv = torch.meshgrid(y, x, indexing='ij') if torch_1_10 else torch.meshgrid(y, x)  # torch>=0.7 compatibility
        grid = torch.stack((xv, yv), 2).expand(shape) - 0.5  # add grid offset, i.e. y = 2.0 * x - 0.5
        anchor_grid = (self.anchors[i] * self.stride[i]).view((1, self.na, 1, 1, 2)).expand(shape)
        return grid, anchor_grid
    
    def initialize_biases(self):
        prior_prob = self.prior_prob
        for i in range(self.nl):
            conv_cls = self.m[i].cls_preds
            b = conv_cls.bias.view(1, -1)
            b.data.fill_(-math.log((1 - prior_prob) / prior_prob))
            conv_cls.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)

            conv_obj = self.m[i].obj_preds
            b_obj = conv_obj.bias.view(1, -1)
            b_obj.data.fill_(-math.log((1 - prior_prob) / prior_prob))
            conv_obj.bias = torch.nn.Parameter(b_obj.view(-1), requires_grad=True)

在DetectDcoupleHead的定义中可以发现,self.m是最终的yolox的双分支head结构,它通过输入数据的维度分别重新构建了三个decoupledhead的结构,效果完全与yoloxhead一致,即下面这行代码

self.m = nn.ModuleList(DecoupledHead(x, self.nc, self.width, anchors) for x in self.in_ch)

另外,定义DetectDcoupleHead可以更好的解决锚框铺设问题,并且使得该头部模块可以在yaml配置文件中进行参数配置,最终实现yoloxhead的双分支、anchor-based结构

想要可以训练,还要再完善一些细节,主要是yolo.py中的网络构建代码

"""parse_model中添加:"""
elif m in {DetectDcoupleHead}:
	 """args是yaml配置文件的字典中每行的列表里模块后的参数"""
    # import ipdb;ipdb.set_trace()
    args.append([ch[x] for x in f])#append导致输入维度参数在最后一个位置
    if isinstance(args[1], int):  # 锚框 number of anchors
        args[1] = [list(range(args[1] * 2))] * len(f)

"""DetectionModel中添加:"""
if isinstance(m, DetectDcoupleHead):
    s = 256  # 2x min stride
    m.inplace = self.inplace
    forward = lambda x: self.forward(x)[0] if isinstance(m, Segment) else self.forward(x)
    m.stride = torch.tensor([s / x.shape[-2] for x in forward(torch.zeros(1, ch, s, s))])  # forward
    check_anchor_order(m)
    m.anchors /= m.stride.view(-1, 1, 1)
    self.stride = m.stride
    m.initialize_biases()

做了上面的这些修改工作,基本上就是把yolox的head结构复现到yolov5上了。如下是复现构建后的head模块的结构,可以发现self.m与yoloxhead的结构是一致的。

"""
self.m
ModuleList(
  (0): DecoupledHead(
    (stems): BaseConv(
      (conv): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (act): SiLU(inplace=True)
    )
    (cls_convs): Sequential(
      (0): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
    (cls_preds): Conv2d(128, 3, kernel_size=(1, 1), stride=(1, 1))
    (reg_convs): Sequential(
      (0): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
    (reg_preds): Conv2d(128, 12, kernel_size=(1, 1), stride=(1, 1))
    (obj_preds): Conv2d(128, 3, kernel_size=(1, 1), stride=(1, 1))
  )
  (1): DecoupledHead(
    (stems): BaseConv(
      (conv): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (act): SiLU(inplace=True)
    )
    (cls_convs): Sequential(
      (0): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
    (cls_preds): Conv2d(128, 3, kernel_size=(1, 1), stride=(1, 1))
    (reg_convs): Sequential(
      (0): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
    (reg_preds): Conv2d(128, 12, kernel_size=(1, 1), stride=(1, 1))
    (obj_preds): Conv2d(128, 3, kernel_size=(1, 1), stride=(1, 1))
  )
  (2): DecoupledHead(
    (stems): BaseConv(
      (conv): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (act): SiLU(inplace=True)
    )
    (cls_convs): Sequential(
      (0): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
    (cls_preds): Conv2d(128, 3, kernel_size=(1, 1), stride=(1, 1))
    (reg_convs): Sequential(
      (0): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
      (1): BaseConv(
        (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU(inplace=True)
      )
    )
    (reg_preds): Conv2d(128, 12, kernel_size=(1, 1), stride=(1, 1))
    (obj_preds): Conv2d(128, 3, kernel_size=(1, 1), stride=(1, 1))
  )
)

"""

4、总结

其实做完这些工作回过头来,发现事情其实很简单,我们如果自己想要弄一个双分支的head结构,直接自己写一个,或者在原来的基础直接改就可以了,把一些数据维度上,让网络正常跑起来就行。这样做的话主要是一个学习理解的过程。
如下是本文复现工作的训练截图:
YOLOv5(v7.0)网络修改实践二:把单分支head改为YOLOX的双分支解耦合head(DecoupleHead),yolov5(v7.0)目标检测代码解读及扩充,yolov5,yolox,python,目标检测,DecoupledHead
训练结果还是有一定优势的,双分支比单分支效果更好一些,有高几个点。

后续我打算把双分支、anchor-free也实现一下。目前已经做了一点工作,已经基本上跑起来了,anchor-free其实目前的代码没什么特殊的,预测的数据都基本上一样,不过训练的是一种位置解码方式,用一种方式来表达位置信息,同时去掉生成锚框的操作,但是会基于每个特征图的特征点进行预测,本质和锚框也差不多。文章来源地址https://www.toymoban.com/news/detail-603264.html

到了这里,关于YOLOv5(v7.0)网络修改实践二:把单分支head改为YOLOX的双分支解耦合head(DecoupleHead)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《YOLOv5/v7进阶实战专栏》专栏介绍 & 专栏目录

    本专栏包含超多YOLO算法进阶使用教程;我会用最简练的语言让你用最低的时间成本掌握下面的内容,使用过程中有任何问题都可以与本人联系 ~ 2024年1-2月会对整个专栏全面重构,增加更多的改进,更多实战内容,修改好的代码包,敬请期待~ 专栏地址:点击跳转 专栏重构中

    2024年02月15日
    浏览(52)
  • yolov5 backbone 更改为 mobilevit(即改即用)

    在大佬的博客补充了一些小问题,按照如下修改,你的代码就能跑起来了 使用MobileViT替换YOLOv5主干网络 收费教程:YOLOv5更换骨干网络之 MobileViT-S / MobileViT-XS / MobileViT-XXS MobileViT模型简介 MobileViT、MobileViTv2、MobileViTv3学习笔记(自用) MobileViTv1、MobileViTv2、MobileViTv3网络详解 我

    2024年02月09日
    浏览(44)
  • YOLOV5的backbone改为shuffleNet,并进行效果对比

    近期,想尝试将YOLOV5的backbone改为ShuffleNetv2这类的轻量级网络,想和yolov5s进行对比,话不多少,正文开始 拉取YOLOV5的最新代码,代码链接如下:YOLOV5 2.1数据集下载 这里我们准备VOC数据集,如果不想提现下载也没关系,训练时会自动下载,但是这里还是建议提前准备好,下载

    2024年02月06日
    浏览(39)
  • YOLOv5的head详解

    YOLOv5的head详解 在前两篇文章中我们对YOLO的backbone和neck进行了详尽的解读,如果有小伙伴没看这里贴一下传送门: YOLOv5的Backbone设计 YOLOv5的Neck端设计 在这篇文章中,我们将针对YOLOv5的head进行解读,head虽然在网络中占比最少,但这却是YOLO最核心的内容,话不多说,进入正题

    2023年04月16日
    浏览(30)
  • yolov5修改骨干网络-使用自己搭建的网络-以efficientnetv2为例

    yolov5修改骨干网络–原网络说明 yolov5修改骨干网络-使用pytorch自带的网络-以Mobilenet和efficientnet为例 yolov5修改骨干网络-使用自己搭建的网络-以efficientnetv2为例 增加网络的深度depth能够得到更加丰富、复杂的特征并且能够很好的应用到其它任务中。但网络的深度过深会面临梯度

    2024年01月25日
    浏览(35)
  • YOLOv5/v7 应用轻量级通用上采样算子CARAFE

    特征上采样是现代卷积神经网络架构中的关键操作,例如特征金字塔。其设计对于密集预测任务,如目标检测和语义/实例分割至关重要。在本研究中,我们提出了一种称为内容感知特征重组(CARAFE)的通用、轻量级且高效的操作符,以实现这一目标。CARAFE具有以下几个优点:

    2024年02月08日
    浏览(47)
  • YOLOv5、v7改进之三十一:CrissCrossAttention注意力机制

     前 言: 作为当前先进的深度学习目标检测算法YOLOv7,已经集合了大量的trick,但是还是有提高和改进的空间,针对具体应用场景下的检测难点,可以不同的改进方法。此后的系列文章,将重点对YOLOv7的如何改进进行详细的介绍,目的是为了给那些搞科研的同学需要创新点或

    2024年02月10日
    浏览(51)
  • 改进YOLO系列 | YOLOv5/v7 引入谷歌 Lion 优化器

    论文地址:https://arxiv.org/pdf/2302.06675.pdf 代码地址:https://github.com/google/automl/tree/master/lion 我们提出了一种将算法发现作为程序搜索的方法,并将其应用于发现用于深度神经网络训练的优化算法。我们利用高效的搜索技术来探索一个无限且稀疏的程序空间。为了弥补代理任务和

    2024年02月09日
    浏览(37)
  • YOLOv5/v7 引入 最新 BiFusion Neck | 附详细结构图

    YOLO 社区自前两次发布以来一直情绪高涨!随着中国农历新年2023兔年的到来,美团对YOLOv6进行了许多新的网络架构和训练方案改进。此版本标识为 YOLOv6 v3.0。对于性能,YOLOv6-N在COCO数据集上的AP为37.5%,通过NVIDIA Tesla T4 GPU测试的吞吐量为1187 FPS。YOLOv6-S以484 FPS的速度得到了超过

    2024年02月05日
    浏览(44)
  • 基于YOLOv5、v7、v8的竹签计数系统的设计与实现

    该系统是一个综合型的应用,基于PyTorch框架的YOLOv5、YOLOv7和YOLOv8,结合了Django后端和Vue3前端,为竹签生成工厂和串串香店铺提供了一套全面而强大的实时监测与分析解决方案。系统主要特色在于实时目标检测和位置追踪,支持用户通过上传图片、视频或摄像头进行推理,实

    2024年01月23日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包