Yolov8 模型解析函数 parse_model 讲解

本文详细介绍了如何在对Yolov8模型进行结构修改时,正确解析和处理配置文件,包括类别数、缩放模块、通道数和激活函数的设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


        进行Yolov8模型结构修改的时候,模型的配置文件如何被解析对我们来说十分重要,下面将对此进行讲解。

def parse_model(d, ch, verbose=True):  # model_dict, input_channels(3)
    # Parse a YOLO model.yaml dictionary
    if verbose:
        LOGGER.info(f"\n{'':>3}{'from':>20}{'n':>3}{'params':>10}  {'module':<45}{'arguments':<30}")
    # 模型的配置以字典的形式传入,分别获取类别数,缩放模块重复次数,缩放模型通道数,以及模型的激活函数。
    # d.get('activation') 如果没有activation键值返回None
    nc, gd, gw, act = d['nc'], d['depth_multiple'], d['width_multiple'], d.get('activation')
    if act:
        # 修改模型的激活函数
        # eval()函数常被称为评估函数,它的功能是去掉参数最外侧引号,变成python可执行的语句,并执行语句的函数。
        Conv.default_act = eval(act)  # redefine default activation, i.e. Conv.default_act = nn.SiLU()
        if verbose:
            LOGGER.info(f"{colorstr('activation:')} {act}")  # print
    # ch 存储每个layer的输入通道数 
    ch = [ch] 
    # layers将每个layer存储进列表,save存储当前layer用到前面layer的index,c2每个layer的输出通道数
    layers, save, c2 = [], [], ch[-1]  # layers, savelist, ch out
    # 遍历backbone和head组成的列表生成网络
    for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']):  # from, number, module, args
        # 首先判断layer的名字是否为字符串,如果是字符串则通过eval()转换为函数
        m = eval(m) if isinstance(m, str) else m  # eval strings
        # 遍历layer的参数
        for j, a in enumerate(args):
            with contextlib.suppress(NameError):
                # 如果参数类型为'int'则通过eval()转换为int;如果是nc,配置文件定义在前面定义nc:80,则将其转换为80;其他参数去掉最外侧引号
                args[j] = eval(a) if isinstance(a, str) else a  # eval strings
        # 如果重复次数大于1则缩放模块重复次数,并且重复次数最小为1;如果重复系数为1,不操作
        n = n_ = max(round(n * gd), 1) if n > 1 else n  # depth gain
        if m in {
                Classify, Conv, ConvTranspose, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, Focus,
                BottleneckCSP, C1, C2, C2f, C3, C3TR, C3Ghost, nn.ConvTranspose2d, DWConvTranspose2d, C3x}:
            # c1为layer的输入通道数,c2为layer的输出通道数
            c1, c2 = ch[f], args[0]
            # 判断layer的输出通道数是否为类别数,如果是类别数则不进行缩放;如果不是,则进行缩放
            if c2 != nc:  # if c2 not equal to number of classes (i.e. for Classify() output)
                c2 = make_divisible(c2 * gw, 8)
                '''
                # 返回最近的可被除数整除的x
                def make_divisible(x, divisor):
                    if isinstance(divisor, torch.Tensor):
                        divisor = int(divisor.max())  # to int
                    return math.ceil(x / divisor) * divisor
                '''
            # 将layer需要的所有参数组成一个列表,将在后面将列表作为layer的参数传出layer
            args = [c1, c2, *args[1:]]
            if m in {BottleneckCSP, C1, C2, C2f, C3, C3TR, C3Ghost, C3x}:
                args.insert(2, n)  # number of repeats
                n = 1
        elif m is nn.BatchNorm2d:
            args = [ch[f]]
        elif m is Concat:
            # Concat操作的输出通道数为输入通道数之和
            c2 = sum(ch[x] for x in f)
        elif m in {Detect, Segment}:
            # 检测和分割头的关于通道的参数是将传入特征图的通道数组成列表
            args.append([ch[x] for x in f])
            if m is Segment:
                args[2] = make_divisible(args[2] * gw, 8)
        else:
            c2 = ch[f]
        # 如果重复次数大于1,则将重复的操作组成一个Sequential
        m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)  # module
        # 获取模块的类型(class),如models.commom.Conv
        t = str(m)[8:-2].replace('__main__.', '')  # module type
        # 获取模块的参数量
        m.np = sum(x.numel() for x in m_.parameters())  # number params
        # m_.i:当前layer的索引, m_.f:当前layer的输入来自于那些layer的索引,m_.type:当前layer的类型
        m_.i, m_.f, m_.type = i, f, t  # attach index, 'from' index, type
        if verbose:
            LOGGER.info(f'{i:>3}{str(f):>20}{n_:>3}{m.np:10.0f}  {t:<45}{str(args):<30}')  # print
        # 将当前layer用到的前面layer的index进行存储
        save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1)  # append to savelist
        # 将当前layer加入layers
        layers.append(m_)
        if i == 0:
            ch = []
        ch.append(c2)
    return nn.Sequential(*layers), sorted(save)
### 修改 YOLOv8 的 `parse_model` 函数 在 Ultralytics 提供的 YOLOv8 源码中,`parse_model` 是用于解析模型架构的核心函数之一。它主要负责从给定的配置文件(通常是 `.yaml` 文件)中读取网络层定义并构建相应的 PyTorch 层结构[^1]。 如果需要自定义或修改 `parse_model` 行为以实现特定功能,则可以按照以下方式操作: #### 1. 定位源代码中的 `parse_model` `parse_model` 函数通常位于 `ultralytics/nn/modules.py` 或类似的模块中。可以通过查找该函数来了解其默认行为。以下是原始版本的一个简化示例: ```python def parse_model(d, ch): # model_dict, input_channels (ch) """ Parse a Yaml model dictionary into a list of layers. Args: d (dict): Model configuration as a dict or loaded yaml file. ch (int): Input channels to the first layer. Returns: List[Tuple[str, nn.Module]]: A list of tuples containing module names and their corresponding modules. """ import torch.nn as nn anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple'] na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors no = na * (nc + 5) layers, save, c2 = [], [], ch[-1] for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): ... ``` 此部分逻辑会逐层遍历模型配置,并根据每一层的名称动态创建对应的 PyTorch 模块。 --- #### 2. 自定义扩展 `parse_model` 为了实现特定的功能需求,可以在原有基础上进行扩展。例如,在某些情况下可能希望增加新的层类型或者调整现有层的行为。具体方法如下: ##### 方法一:覆盖原函数 通过重新定义整个 `parse_model` 函数来自定义其行为。假设我们想支持一种新类型的层 `CustomLayer`,则可以这样写: ```python from ultralytics.nn.modules import Conv, BottleneckCSP, SPPF import torch.nn as nn def custom_parse_model(d, ch): """ Customized version of parse_model with additional support for 'CustomLayer'. """ anchors, nc, gd, gw = d.get('anchors'), d.get('nc'), d.get('depth_multiple', 1), d.get('width_multiple', 1) na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors no = na * (nc + 5) layers, save, c2 = [], [], ch[-1] for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): m_str = m.strip().lower() if m_str == 'conv': # Standard convolutional layer conv_layer = Conv(c1=c2, c2=args[0], k=args[1], s=args[2]) layers.append((m, conv_layer)) c2 = args[0] elif m_str == 'bottleneckcsp': bottleneck_csp = BottleneckCSP(c1=c2, c2=args[0], n=n, shortcut=False) layers.append((m, bottleneck_csp)) c2 = args[0] elif m_str == 'customlayer': # New custom-defined layer type custom_layer = CustomLayer(input_dim=c2, output_dim=args[0]) layers.append(('CustomLayer', custom_layer)) c2 = args[0] else: raise NotImplementedError(f'Unsupported layer type {m}') return layers ``` 在此例子中新增了一个名为 `CustomLayer` 的假想类作为额外的支持选项。 ##### 方法二:继承与重载 另一种更优雅的方式是利用面向对象编程的思想,通过对已有类进行子类化从而仅替换所需的部分而不完全重构全局逻辑: ```python class ExtendedModelParser(UltralyticsBaseClass): @staticmethod def parse_custom_layers(m, c2, args): if m.lower() == 'customlayer': return CustomLayer(input_dim=c2, output_dim=args[0]), args[0] else: return super().default_parsing_logic(m, c2, args) ``` 这种方式更加灵活且易于维护,同时也保留了框架原有的设计模式。 --- #### 3. 替换默认调用路径 完成上述更改之后还需要确保程序能够实际加载我们的定制版 `parse_model` 而不是原来的那个。这一步取决于具体的项目集成情况;一般而言有两种途径可选: - **直接编辑官方库源码**:虽然简单粗暴但不推荐长期使用因为每次更新都需要重复改动; - **Monkey Patching 技术**:即运行时动态替换成自己的实现版本而无需触碰第三方包内部细节。 示例代码片段展示后者做法如下所示: ```python from ultralytics.nn.modules import parse_model as original_parse_model def patched_parse_model(*args, **kwargs): print("Using customized parse_model function.") result = custom_parse_model(*args, **kwargs) return result # Apply monkey patching here original_module.parse_model = patched_parse_model ``` 以上步骤完成后即可让系统无缝切换至增强后的解析流程。 --- ### 总结 综上所述,要修改 YOLOv8 中的 `parse_model` 函数需先找到其实现位置再决定采用何种策略对其进行改造—无论是整体替代还是局部扩充均各有优劣适配不同场景需求。最终记得测试验证所有预期变化均已生效并无副作用残留。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值