effidehead_distill_ns.py
yolov6\models\heads\effidehead_distill_ns.py
YOLOv6中的EffideHead模块主要有三种变体: EffideHead_Distill_NS 、 EffideHead_FuseAB 和 EffideHead_Lite ,它们在功能和设计上有所不同。
EffideHead_Distill_NS :
EffideHead_Distill_NS是YOLOv6中用于知识蒸馏的检测头。
它通过使用无锚框(anchor-free)的方式进行目标检测,并且结合了SimOTA标签分配策略和SIOU边界框回归损失,以提高检测的准确性和效率。
此外,它还采用了自蒸馏技术,通过教师模型和学生模型的协同训练,进一步提升模型的性能。
EffideHead_FuseAB :
EffideHead_FuseAB是另一种检测头变体,它结合了特征融合(Feature Fusion)和锚框(Anchor-based)的方法。
这种方法通过融合不同层次的特征图来提升检测性能,适用于需要高精度检测的场景。
与EffideHead_Distill_NS相比,EffideHead_FuseAB更注重特征融合和锚框的使用,以适应不同的检测需求。
EffideHead_Lite :
EffideHead_Lite是YOLOv6中轻量级的检测头变体,旨在提供更高的推理速度和更低的计算成本。
它通过优化网络结构和减少参数来达到这一目标,特别适合资源受限的设备上运行。
EffideHead_Lite通过简化网络结构和减少计算量,使得模型更加高效,适合实时检测应用23。
目录
effidehead_distill_ns.py
1.所需的库和模块
2.class Detect(nn.Module):
3.def build_effidehead_layer(channels_list, num_anchors, num_classes, reg_max=16):
1.所需的库和模块
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
from yolov6.layers.common import *
from yolov6.assigners.anchor_generator import generate_anchors
from yolov6.utils.general import dist2bbox
2.class Detect(nn.Module):
class Detect(nn.Module):# 它是一个高效的解耦头,用于无成本蒸馏,适用于 Nano/Small 模型。'''Efficient Decoupled Head for Cost-free Distillation.(FOR NANO/SMALL MODEL)'''# 1.num_classes :目标检测任务中的类别数。# 2.num_layers :检测头的层数。# 3.inplace :是否使用 inplace 操作以减少内存使用。# 4.head_layers :检测头的层结构,这是一个包含多个模块的列表。# 5.use_dfl :是否使用分布式焦点损失(Distributed Focal Loss)。# 6.reg_max :回归的最大值,用于目标检测中的回归任务。def __init__(self, num_classes=80, num_layers=3, inplace=True, head_layers=None, use_dfl=True, reg_max=16): # detection layer 检测层super().__init__()# 确保 head_layers 参数不为空。assert head_layers is not Noneself.nc = num_classes # number of classes 类别数量# 每个锚点的输出数量,包括类别和边界框坐标。self.no = num_classes + 5 # number of outputs per anchor 每个锚点的输出数量self.nl = num_layers # number of detection layers 检测层数# 一个包含零张量的列表,用于存储每个检测层的网格信息。self.grid = [torch.zeros(1)] * num_layers # [tensor([0.]), tensor([0.]), tensor([0.])]# 锚点的先验概率。self.prior_prob = 1e-2# 是否使用 inplace 操作。self.inplace = inplace# 一个包含步长的张量,这些步长在构建模型时计算。stride = [8, 16, 32] # strides computed during build 构建期间计算的步长self.stride = torch.tensor(stride) # tensor([ 8, 16, 32])# 是否使用分布式焦点损失。self.use_dfl = use_dfl# 回归的最大值。self.reg_max = reg_max# 一个卷积层,用于投影。self.proj_conv = nn.Conv2d(self.reg_max + 1, 1, 1, bias=False)# 这个属性表示网格单元的偏移量。在目标检测中,网格单元用于预测边界框的位置。 grid_cell_offset 通常设置为 0.5,这意味着网格单元的中心位于单元格的中点。self.grid_cell_offset = 0.5# 这个属性表示网格单元的大小。在目标检测中,网格单元的大小决定了每个单元格可以预测的边界框的大小范围。 grid_cell_size 通常设置为 5.0,这意味着每个网格单元覆盖的区域是 5x5 像素。self.grid_cell_size = 5.0# 这些属性在目标检测模型中用于计算边界框的预测位置。具体来说,它们用于确定每个网格单元的中心位置以及每个单元格可以预测的边界框的大小范围。通过这种方式,模型可以准确地预测目标的位置和大小。# 例如,如果一个网格单元的中心位于图像的 (x, y) 位置,那么该单元格预测的边界框的中心也将位于 (x, y)。边界框的大小将根据 grid_cell_size 和模型预测的偏移量来确定。# 这些属性的设置对于模型的性能至关重要,因为它们直接影响到边界框预测的准确性。通过调整这些参数,可以优化模型在不同尺度和不同分辨率的图像上的性能。# Init decouple head 初始化解耦头# 通过使用 nn.ModuleList , Detect 类可以确保这些层在模型的参数更新和模块迭代中被正确处理。 nn.ModuleList 继承自 nn.Module ,可以自动注册其包含的模块为子模块,并且它们的参数也会被自动添加到模型的参数列表中,这样在模型训练时这些参数就会被优化。# nn.ModuleList()# nn.ModuleList 是 PyTorch 中的一个容器类,它继承自 nn.Module ,用于存储一个有序的模块列表。 nn.ModuleList 的主要作用是保持子模块的有序性,并且确保在模型的参数更新时,这些子模块的参数也会被包括在内。# class ModuleList(nn.Module): def __init__(self, modules=None):# module_list = nn.ModuleList() module_list.append(nn.Linear(2, 3)) module_list.append(nn.ReLU())# nn.ModuleList 的关键特性是它会自动注册其包含的模块为子模块,这意味着当你调用 model.parameters() 或 model.named_parameters() 时, ModuleList 中的模块参数也会被包括在内,从而在模型训练时会被优化。# 此外, nn.ModuleList 支持索引、切片、长度查询等操作,使得管理模块集合变得非常方便。# 这个列表用于存储每个检测层的“茎”(stem)部分,通常是一些卷积层,用于处理从特征图到检测头的输入。self.stems = nn.ModuleList()# 这个列表用于存储类别预测卷积层。这些层通常跟在茎部分后面,用于进一步提取特征,然后用于类别预测。self.cls_convs = nn.ModuleList()# 这个列表用于存储边界框回归卷积层。这些层与类别预测卷积层类似,但专门用于提取边界框位置的特征。self.reg_convs = nn.ModuleList()# 这个列表用于存储类别预测层。这些层通常位于网络的末端,将类别预测卷积层的输出转换为最终的类别预测结果。self.cls_preds = nn.ModuleList()# 这个列表用于存储分布式边界框回归预测层。如果模型使用分布式焦点损失(Distributed Focal Loss),这些层将用于输出边界框回归的预测结果。self.reg_preds_dist = nn.ModuleList()# 这个列表用于存储边界框回归预测层。这些层将边界框回归卷积层的输出转换为最终的边界框回归预测结果。self.reg_preds = nn.ModuleList()# Efficient decoupled head layers 高效解耦的头部层# 根据传入的 head_layers 列表来初始化检测头中的各个组件。# 这个循环会迭代 num_layers 次,每次迭代代表一个检测层。for i in range(num_layers):# 由于每个检测层有6个相关的模块,这个计算会为每个检测层生成一个基础索引。例如,第一个检测层的基础索引是0,第二个是6,第三个是12,以此类推。idx = i*6 # idx -> 0,6,12# 将 head_layers 列表中对应索引的茎(stem)模块添加到 self.stems 列表中。self.stems.append(head_layers[idx])# 将 类别预测卷积层 添加到 self.cls_convs 列表中。self.cls_convs.append(head_layers[idx+1])# 将 边界框回归卷积层 添加到 self.reg_convs 列表中。self.reg_convs.append(head_layers[idx+2])# 将 类别预测层 添加到 self.cls_preds 列表中。self.cls_preds.append(head_layers[idx+3])# 如果使用分布式焦点损失,将 分布式边界框回归预测层 添加到 self.reg_preds_dist 列表中。self.reg_preds_dist.append(head_layers[idx+4])# 将 边界框回归预测层 添加到 self.reg_preds 列表中。self.reg_preds.append(head_layers[idx+5])# 这段代码定义了一个名为 initialize_biases 的方法,它用于初始化 Detect 类中类别预测层的偏置(bias)。这个方法确保了类别预测层的偏置被设置为一个特定的值,这个值通常是基于目标检测中锚点(anchor)的先验概率。def initialize_biases(self):# 这个循环遍历 self.cls_preds 中的所有卷积层,这些层是用于类别预测的输出层。for conv in self.cls_preds:# 获取当前卷积层的偏置,并将其视作一维张量。b = conv.bias.view(-1, )# torch.fill_(value)# 在PyTorch中,可以使用 torch.fill_() 方法对张量进行填充操作。这个方法会将张量中的所有元素值填充为指定的数值。# math.log(x[, base])# x :必需,数字。如果 x 不是一个数字,返回 TypeError 。如果值为 0 或 负数 ,则返回 ValueError 。# base :可选,底数,默认为 e 。# 返回一个整数浮点数 float,表示一个数字的自然对数。# 使用目标检测中常用的方法来初始化偏置。这里的 self.prior_prob 是锚点的先验概率,通常设置为 0.01。偏置被初始化为 -log((1 - prior_prob) / prior_prob) ,这相当于将二分类问题的交叉熵损失中的背景类的概率设置为 1 - prior_prob 。b.data.fill_(-math.log((1 - self.prior_prob) / self.prior_prob))# torch.nn.Parameter()# 对于 nn.Parameter() 是pytorch中定义可学习参数的一种方法,因为我们在搭建网络时,网络中会存在一些矩阵,这些矩阵内部的参数是可学习的,也就是可梯度求导的。# torch.nn.Parameter 继承 torch.Tensor ,其作用将一个不可训练的类型为 Tensor 的参数转化为可训练的类型为 parameter 的参数,# 并将这个参数绑定到 module 里面,成为 module 中可训练的参数。# 将初始化后的偏置重新设置为卷积层的偏置,并确保它是可训练的参数。conv.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)# 获取当前卷积层的权重。w = conv.weight# 将权重初始化为0。w.data.fill_(0.)# 将初始化后的权重重新设置为卷积层的权重,并确保它是可训练的参数。conv.weight = torch.nn.Parameter(w, requires_grad=True)# 这个循环遍历 self.reg_preds_dist 中的所有卷积层,这些层是用于分布式边界框回归的输出层。for conv in self.reg_preds_dist:# 获取当前卷积层的偏置,并将其视作一维张量。b = conv.bias.view(-1, )# 将偏置初始化为1.0。这种初始化通常用于边界框回归任务,因为通常我们希望初始的边界框预测接近于0(即锚点的位置),而1.0的偏置可以帮助模型在训练开始时就有一个合适的起点。b.data.fill_(1.0)# 将初始化后的偏置重新设置为卷积层的偏置,并确保它是可训练的参数。conv.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)# 获取当前卷积层的权重。w = conv.weight# 将权重初始化为0。这种初始化可以防止模型在训练开始时就对边界框的位置做出过大的预测。w.data.fill_(0.)# 将初始化后的权重重新设置为卷积层的权重,并确保它是可训练的参数。conv.weight = torch.nn.Parameter(w, requires_grad=True)# 这个循环遍历 self.reg_preds 中的所有卷积层,这些层是用于边界框回归的输出层。for conv in self.reg_preds:# 获取当前卷积层的偏置,并将其视作一维张量。b = conv.bias.view(-1, )# 将偏置初始化为1.0。这种初始化通常用于边界框回归任务,因为通常我们希望初始的边界框预测接近于锚点的位置,而1.0的偏置可以帮助模型在训练开始时就有一个合适的起点。b.data.fill_(1.0)# 将初始化后的偏置重新设置为卷积层的偏置,并确保它是可训练的参数。conv.bias = torch.nn.Parameter(b.view(-1), requires_grad=True)# 获取当前卷积层的权重。w = conv.weight# 将权重初始化为0。这种初始化可以防止模型在训练开始时就对边界框的位置做出过大的预测。w.data.fill_(0.)# 将初始化后的权重重新设置为卷积层的权重,并确保它是可训练的参数。conv.weight = torch.nn.Parameter(w, requires_grad=True)# torch.linspace(start, end, steps=100, dtype=None, layout=torch.strided, device=None, requires_grad=False)# 返回一个一维的 tensor(张量),这个张量包含了从 start 到 end ,平均分(线性)成 steps 个线段得到的向量。# start :序列的起始值。# end :序列的结束值。# steps :生成的数值数量,默认为 100。# dtype :生成的张量的数据类型,默认为 None,表示采用默认的数据类型。# layout :生成的张量的布局,默认为 torch.strided。# device :生成的张量所在的设备,默认为 None,表示使用默认设备。# requires_grad :是否需要梯度,默认为 False。# 初始化 self.proj 参数。# 这个函数生成一个一维张量,包含从0到 self.reg_max (包括 self.reg_max )的等间隔值,总共有 self.reg_max + 1 个值。# nn.Parameter :将这个张量转换为一个参数,这样它就可以被包含在模型的参数列表中,但在优化过程中不会被更新,因为 requires_grad=False 。# 这个参数可能用于在回归任务中定义一个投影或者映射,例如,它可以用于将预测的边界框坐标映射到一个特定的范围内。self.proj = nn.Parameter(torch.linspace(0, self.reg_max, self.reg_max + 1), requires_grad=False)# nn.Parameter()# 在 PyTorch 中, nn.Parameter 是一个特殊的类,它用于定义一个可以被优化(即可以通过梯度下降等方法更新)的参数。 nn.Parameter 通常用于定义模型中的权重和偏置,这些参数在训练过程中会被优化算法调整以最小化损失函数。# class Parameter(torch.Tensor): def __init__(self, data, requires_grad=True):# param = nn.Parameter(torch.randn(2, 3), requires_grad=True)# data :要转换为参数的数据,通常是一个 torch.Tensor 对象。# requires_grad :一个布尔值,指示是否需要计算这个参数的梯度。如果设置为 True ,则在反向传播时会计算这个参数的梯度,并在优化步骤中更新它。# nn.Parameter 与普通的 torch.Tensor 的主要区别在于, nn.Parameter 对象会自动注册到包含它的 nn.Module (模型)中,使得它们在模型的参数列表中可见,并且在模型的参数更新(例如,使用梯度下降)时会被考虑。# 此外, nn.Parameter 对象会在模型的 state_dict 和加载模型时的 load_state_dict 方法中被特别处理。# 初始化 self.proj_conv.weight 权重。# self.proj.view([1, self.reg_max + 1, 1, 1]) :将 self.proj 参数重塑为一个四维张量,形状为 [1, self.reg_max + 1, 1, 1] ,这通常是为了匹配卷积层权重的形状。# .clone() :创建 self.proj 的一个副本,以避免直接修改原始参数。# .detach() :从计算图中分离出这个张量,这样它就不会在梯度计算中被考虑。# nn.Parameter :将这个张量转换为一个参数,这样它就可以被包含在模型的参数列表中,但在优化过程中不会被更新,因为 requires_grad=False 。# self.proj_conv.weight :这个权重被设置为上述张量,用于 self.proj_conv 卷积层。由于 requires_grad=False ,这个权重在训练过程中不会被更新。self.proj_conv.weight = nn.Parameter(self.proj.view([1, self.reg_max + 1, 1, 1]).clone().detach(),requires_grad=False)# 这段代码定义了 Detect 类的 forward 方法,它是 PyTorch 中所有神经网络模型的核心方法,定义了模型在进行前向传播时的行为。def forward(self, x):# 这个条件检查模型是否处于训练模式。在训练和推理(inference)模式下,模型的行为可能会有所不同。if self.training:# 这个列表用于存储每个检测层的 类别预测分数 。在目标检测中,每个锚点(anchor)通常会预测多个类别的分数,这些分数表示锚点包含特定类别目标的概率。cls_score_list = []# 这个列表用于存储每个检测层的 分布式边界框回归结果 。分布式回归是一种技术,它将边界框回归任务分解为多个独立的回归任务,例如,分别回归目标的宽度、高度和偏移量。reg_distri_list = []# 这个列表用于存储每个检测层的 局部边界框回归结果 ,通常表示为边界框的左、上、右、下(left, right, top, bottom)坐标或者中心点坐标加上宽度和高度(center-x, center-y, width, height)。reg_lrtb_list = []# 循环遍历每个检测层, self.nl 是检测层的数量。for i in range(self.nl):# 将输入 x 通过第 i 个茎(stem)模块进行处理。x[i] = self.stems[i](x[i])# 存储类别预测的特征图。cls_x = x[i]# 存储边界框回归的特征图。reg_x = x[i]# 通过类别预测卷积层进一步处理特征图。cls_feat = self.cls_convs[i](cls_x)# 通过类别预测层得到最终的类别预测结果。cls_output = self.cls_preds[i](cls_feat)# 通过边界框回归卷积层进一步处理特征图。reg_feat = self.reg_convs[i](reg_x)# 通过分布式边界框回归预测层得到分布式回归结果。reg_output = self.reg_preds_dist[i](reg_feat)# 通过边界框回归预测层得到局部回归结果。reg_output_lrtb = self.reg_preds[i](reg_feat)# torch.sigmoid(input, *, out=None) → Tensor# input (Tensor) :输入张量。# out (Tensor, optional) :输出张量。如果提供,则函数的结果将写入此张量中,并且函数将返回这个张量。# 函数返回一个新的张量,其中包含了输入张量 input 每个元素的 Sigmoid 函数值,它可以将任何实数映射到0和1之间。# 如果提供了 out 参数,则结果将直接写入这个张量中,并且返回这个张量。# 对类别预测结果应用 Sigmoid 激活函数,将输出转换为概率值。cls_output = torch.sigmoid(cls_output)# 将 类别预测结果 展平并置换维度,添加到列表中。cls_score_list.append(cls_output.flatten(2).permute((0, 2, 1)))# 将 分布式边界框回归结果 展平并置换维度,添加到列表中。reg_distri_list.append(reg_output.flatten(2).permute((0, 2, 1)))# 将 局部边界框回归结果 展平并置换维度,添加到列表中。reg_lrtb_list.append(reg_output_lrtb.flatten(2).permute((0, 2, 1)))# 沿着第二维度(axis=1)拼接类别预测结果。cls_score_list = torch.cat(cls_score_list, axis=1)# 沿着第二维度拼接分布式边界框回归结果。reg_distri_list = torch.cat(reg_distri_list, axis=1)# 沿着第二维度拼接局部边界框回归结果。reg_lrtb_list = torch.cat(reg_lrtb_list, axis=1)# 返回 处理后的特征图 x 、类别预测结果列表、边界框回归结果列表、局部边界框回归列表。return x, cls_score_list, reg_distri_list, reg_lrtb_list# forward 方法中处理推理(evaluation)模式的部分。在推理模式下,模型使用前向传播得到的预测结果来生成目标的类别分数和边界框。else:# 这个列表用于存储每个检测层的 类别预测分数 。在目标检测中,每个锚点(anchor)通常会预测多个类别的分数,这些分数表示锚点包含特定类别目标的概率。cls_score_list = []# 这个列表用于存储每个检测层的 局部边界框回归结果 ,通常表示为边界框的左、上、右、下(left, right, top, bottom)坐标或者中心点坐标加上宽度和高度(center-x, center-y, width, height)。reg_lrtb_list = []# def generate_anchors(feats, fpn_strides, grid_cell_size=5.0, grid_cell_offset=0.5, device='cpu', is_eval=False, mode='af'):# -> 根据特征生成锚点。# -> return anchor_points, stride_tensor / return anchors, anchor_points, num_anchors_list, stride_tensor# 函数用于生成每个特征图上的锚点(anchor points),这些锚点是用于目标检测的参考框。# 1.x :当前层的特征图。# 2.self.stride :模型的步长,决定了锚点的间隔。# 3.self.grid_cell_size 和 4.self.grid_cell_offset :网格单元的大小和偏移量。# 5.device :特征图所在的设备(CPU或GPU)。# 6.is_eval=True :表示当前处于推理模式。# 7.mode='af' :锚点生成的模式,'af' 可能指的是 'anchor free',即不依赖于预定义的锚点。anchor_points, stride_tensor = generate_anchors(x, self.stride, self.grid_cell_size, self.grid_cell_offset, device=x[0].device, is_eval=True, mode='af')# 循环遍历每个检测层, self.nl 是检测层的数量。for i in range(self.nl):# 获取当前特征图的批次大小、通道数、高度和宽度。b, _, h, w = x[i].shape# 计算当前特征图的总单元格数。l = h * w# 将输入 x 通过第 i 个茎(stem)模块进行处理。x[i] = self.stems[i](x[i])# 存储类别预测的特征图。cls_x = x[i]# 存储边界框回归的特征图。reg_x = x[i]# 通过类别预测卷积层进一步处理特征图。cls_feat = self.cls_convs[i](cls_x)# 通过类别预测层得到最终的类别预测结果。cls_output = self.cls_preds[i](cls_feat)# 通过边界框回归卷积层进一步处理特征图。reg_feat = self.reg_convs[i](reg_x)# 通过边界框回归预测层得到局部回归结果。reg_output_lrtb = self.reg_preds[i](reg_feat)# 对类别预测结果应用 Sigmoid 激活函数,将输出转换为概率值。cls_output = torch.sigmoid(cls_output)# 将类别预测结果 cls_output 重塑为一个新的张量,其形状为 [b, self.nc, l] 。# b 是批次大小。# self.nc 是类别数量。# l 是当前特征图的总单元格数(即高度乘以宽度)。# cls_score_list.append(...) :将重塑后的类别预测结果添加到 cls_score_list 列表中。cls_score_list.append(cls_output.reshape([b, self.nc, l]))# 将边界框回归结果 reg_output_lrtb 重塑为一个新的张量,其形状为 [b, 4, l] 。# b 是批次大小。# 4 表示边界框的四个坐标(左、上、右、下)。# l 是当前特征图的总单元格数。# reg_lrtb_list.append(...) :将重塑后的边界框回归结果添加到 reg_lrtb_list 列表中。reg_lrtb_list.append(reg_output_lrtb.reshape([b, 4, l]))# 沿着最后一个维度拼接类别预测结果,并置换维度。cls_score_list = torch.cat(cls_score_list, axis=-1).permute(0, 2, 1)# 沿着最后一个维度拼接边界框回归结果,并置换维度。reg_lrtb_list = torch.cat(reg_lrtb_list, axis=-1).permute(0, 2, 1)# def dist2bbox(distance, anchor_points, box_format='xyxy'): -> 将距离(ltrb)转换为盒子(xywh或xyxy)。 -> return bbox# dist2bbox 函数用于将边界框回归结果转换为边界框的坐标。# 将回归结果和锚点转换为边界框。# anchor_points 是与特征图上每个位置对应的锚点坐标。pred_bboxes = dist2bbox(reg_lrtb_list, anchor_points, box_format='xywh')# 将预测的边界框坐标 pred_bboxes 乘以步长 stride_tensor ,以将它们从特征图空间转换到原始图像空间。# stride_tensor 包含了每个检测层的步长,步长决定了特征图上一个单元格对应原始图像上的大小。# 这个缩放操作是必要的,因为边界框回归通常是在特征图上进行的,而特征图是原始图像下采样后的结果。通过乘以步长,可以将边界框坐标恢复到原始图像的尺度。pred_bboxes *= stride_tensor# 返回最终的预测结果,包含了 边界框 、 置信度 和 类别分数 。return torch.cat([pred_bboxes,torch.ones((b, pred_bboxes.shape[1], 1), device=pred_bboxes.device, dtype=pred_bboxes.dtype),cls_score_list],axis=-1)
3.def build_effidehead_layer(channels_list, num_anchors, num_classes, reg_max=16):
def build_effidehead_layer(channels_list, num_anchors, num_classes, reg_max=16):head_layers = nn.Sequential(# stem0ConvBNSiLU(in_channels=channels_list[6],out_channels=channels_list[6],kernel_size=1,stride=1),# cls_conv0ConvBNSiLU(in_channels=channels_list[6],out_channels=channels_list[6],kernel_size=3,stride=1),# reg_conv0ConvBNSiLU(in_channels=channels_list[6],out_channels=channels_list[6],kernel_size=3,stride=1),# cls_pred0nn.Conv2d(in_channels=channels_list[6],out_channels=num_classes * num_anchors,kernel_size=1),# reg_pred0nn.Conv2d(in_channels=channels_list[6],out_channels=4 * (reg_max + num_anchors),kernel_size=1),# reg_pred0_1nn.Conv2d(in_channels=channels_list[6],out_channels=4 * (num_anchors),kernel_size=1),# stem1ConvBNSiLU(in_channels=channels_list[8],out_channels=channels_list[8],kernel_size=1,stride=1),# cls_conv1ConvBNSiLU(in_channels=channels_list[8],out_channels=channels_list[8],kernel_size=3,stride=1),# reg_conv1ConvBNSiLU(in_channels=channels_list[8],out_channels=channels_list[8],kernel_size=3,stride=1),# cls_pred1nn.Conv2d(in_channels=channels_list[8],out_channels=num_classes * num_anchors,kernel_size=1),# reg_pred1nn.Conv2d(in_channels=channels_list[8],out_channels=4 * (reg_max + num_anchors),kernel_size=1),# reg_pred1_1nn.Conv2d(in_channels=channels_list[8],out_channels=4 * (num_anchors),kernel_size=1),# stem2ConvBNSiLU(in_channels=channels_list[10],out_channels=channels_list[10],kernel_size=1,stride=1),# cls_conv2ConvBNSiLU(in_channels=channels_list[10],out_channels=channels_list[10],kernel_size=3,stride=1),# reg_conv2ConvBNSiLU(in_channels=channels_list[10],out_channels=channels_list[10],kernel_size=3,stride=1),# cls_pred2nn.Conv2d(in_channels=channels_list[10],out_channels=num_classes * num_anchors,kernel_size=1),# reg_pred2nn.Conv2d(in_channels=channels_list[10],out_channels=4 * (reg_max + num_anchors),kernel_size=1),# reg_pred2_1nn.Conv2d(in_channels=channels_list[10],out_channels=4 * (num_anchors),kernel_size=1))return head_layers