YOLO模型魔改指南:從原理到實戰,替換Backbone、Neck和Head(戰損版)


前言

Hello,大家好,我是GISer Liu😁,一名熱愛AI技術的GIS開發者。本系列是作者參加DataWhale 2025年6月份Yolo原理組隊學習的技術筆記文檔,這里整理為博客,希望能幫助Yolo的開發者少走彎路!

🚀 歡迎來到YOLO進階系列教程的核心,也是最后一篇文章——模型“魔改”!在目標檢測領域,YOLO系列憑借其卓越的速效平衡成為了標桿。然而,無論是為了發表學術論文,還是應對復雜多變的業務場景,僅僅滿足于使用官方模型、調整參數是遠遠不夠的。我們需要的,是突破“調參工程師”的局限,真正深入模型內部,進行結構級的創新。

這正是Datawhale YOLO Master項目的初衷。它提供了一套即插即用的先進模塊和一套系統性的魔改方法論,旨在幫助開發者:

  1. 系統性理解YOLO架構:拆解模型為Backbone、Neck、Head等核心組件。
  2. 掌握模塊化創新:像搭樂高一樣,將前沿的模塊(如SwinTransformer, CBAM等)無縫集成到YOLOv8/v10/v11中。
  3. 提升工程與科研能力:從源碼層面理解并改造SOTA模型,為自己的項目或研究注入創新力。

本教程將手把手帶你走完從環境準備到模型改造、訓練的全過程。無論你是希望在CV領域深造的大學生,還是尋求技術突破的開發者,相信本教程都能為你提供堅實的起點。OK,讓我們開始“造”自己的YOLO吧!


更新記錄:(本文隨時更新)

20250709:本文當前只是理論的堆砌,閱讀感覺并不好;不夠直觀;后續我作者通過一個具體的需求,例如遙感影像目標識別去魔改我們的yolo模型結構;測試性能的變化;


一、YOLO“魔改”:從“調參”到“改結構”

1. 為什么要“魔改”YOLO?

標準的YOLO模型雖然強大,但在特定任務上未必是最優解。例如,在遙感影像中檢測微小目標,或是在工業流水線上識別密集物體,都對模型的特征提取能力、多尺度融合等方面提出了更高的要求。此時,僅僅調整學習率、優化器等超參數,帶來的性能提升是有限的。

真正的突破來自于對網絡結構的創新——也就是我們常說的“魔改”。這好比我們不是簡單地調整一輛車的懸掛軟硬(調參),而是給它換上一臺更強勁的發動機或者更先進的空氣動力學套件(魔改)。

模型魔改是網絡結構上的修改和替換,而非簡單調參;這需要開發者對模型組成和原理有深刻的理解🤔

2. “魔改”的哲學:像搭樂高一樣構建網絡

YOLO Master項目的核心思想是將復雜的神經網絡解構成一系列可插拔的、標準化的“積木塊”。YOLO模型經典的三段式結構(主干、頸部、頭部)為這種模塊化改造提供了完美的框架。

mermaid

  • 主干網絡 (Backbone):負責從輸入圖像中提取基礎特征,是模型的“地基”。我們可以將其替換為更先進的結構,如SwinTransformerConvNeXtV2等,以獲取更強的特征表達能力。
  • 頸部網絡 (Neck):負責融合主干網絡在不同階段提取出的特征圖,增強模型對不同尺寸目標的感知能力。可以引入GFPN等結構進行優化。
  • 檢測頭 (Head):根據融合后的特征進行最終的邊界框回歸和類別預測。我們可以嘗試DyHead等動態頭部來提升檢測性能。
  • 注意力機制 (Attention):像“插件”一樣,可以插入到網絡中的任何位置,讓模型“關注”到最重要的特征區域。CBAMSE是常用的選擇。

二、準備工作:搭建你的“魔改”基礎環境

在開始之前,我們需要準備好兩個核心的代碼庫:ultralytics官方庫和yolo-master魔改項目庫。

1. 克隆項目倉庫

我們提供三種下載方式,推薦使用git clone,如果遇到網絡問題,可以嘗試國內的GitCode鏡像。

① 下載 ultralytics 源碼
# 方法一:從GitHub直接克隆 (推薦)
git clone https://github.com/ultralytics/ultralytics.git# 方法二:從國內鏡像GitCode克隆
git clone https://gitcode.com/gh_mirrors/ul/ultralytics.git

file_catalog

其中,我們要對ultralytics文檔目錄結構有個相對完整的了解:

ultralytics/????
?assets:靜態資源:測試圖像、預訓練模型等示例文件
cfg:配置文件中心??
- datasets/:數據集定義(路徑、類別、預處理)
- models/:模型架構配置(YOLOv8n/v8s/v8m等)
- trackers/:跟蹤算法參數
- default.yaml:全局默認配置(訓練/推理/導出參數)
?data/?:數據預處理與增強邏輯
engine/?:核心功能引擎
- exporter.py:模型導出(ONNX/TensorRT等)
- model.py:模型生命周期管理
- predictor.py:推理接口
- trainer.py:訓練流程控制
- validator.py:驗證指標計算
?hub/??:PyTorch Hub 集成接口
models/??:模型架構實現
- yolo/YOLO系列主代碼
- detect/:檢測任務
- segment/:分割任務
- pose/:姿態估計
- classify/:分類任務
- model.py:模型構建核心
- rtdetr/:實時DETR架構
- nas/:神經架構搜索
- sam/SAM優化策略
- fastsam/:快速分割模型
?nn/??:神經網絡組件:自定義層/激活函數等
?solutions/??:高級應用模塊:線計數/熱力圖生成等場景化解決方案
trackers/??:多目標跟蹤(MOT)算法實現
utils/??:工具庫
- callbacks/:訓練回調函數
- metrics.py:性能評估指標
- plotting.py:可視化工具
- torch_utils.py:PyTorch擴展功能
- downloads.py:資源下載管理
- ops.py:張量操作擴展
?init.py?:包初始化入口(版本/API暴露)
② 下載 yolo-master 魔改教程源碼
git clone https://github.com/datawhalechina/yolo-master.git

2. 環境配置

進入yolo-master目錄,其中包含了requirements.txt文件,我們可以使用pip進行安裝。為了加速下載,建議使用國內的清華鏡像。

# 進入yolo-master項目目錄
cd yolo-master# 安裝依賴,-e . 表示以可編輯模式安裝ultralytics
# 假設你的ultralytics目錄與yolo-master在同一級
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install -e ../ultralytics

pip install -e .-e 代表 “editable”(可編輯)。這種方式安裝后,你對 ultralytics 源碼的任何修改(比如我們后續的“魔改”操作)會立刻生效,而無需重新安裝。這對于模型開發和調試至關重要。

config


三、主干(BackBone)的替換

要實現真正的“即插即用”,我們需要對ultralytics的源碼進行一些通用性的改造,讓它能夠識別并正確處理我們添加的自定義模塊,特別是復雜的主干網絡。修改的核心位于ultralytics/nn/tasks.py文件中,這個文件負責解析YAML配置文件并構建整個神經網絡模型。

1. 魔改的挑戰與思路

挑戰ultralytics原生的parse_model函數設計時,主要考慮的是構建由一系列“標準”層(如ConvC2fConcat)組成的網絡。這些層有一個共同點:輸入一個張量,輸出一個張量。而我們想替換的先進主干網絡(如RepViT, SwinTransformer等)通常是作為一個整體模塊,它輸入一個圖像張量,一次性輸出多個不同尺度的特征圖(例如,同時輸出P3, P4, P5三個層級的特征)。原生的解析和前向傳播邏輯無法直接處理這種“一對多”的復雜模塊。

解決思路:我們的魔改思路可以分為兩步,就像進行一次精密的“外科手術”:

  1. 改造parse_model函數(模型構建階段):讓它在解析YAML時,能夠“識別”出我們自定義的、作為整體的主干網絡。識別后,它需要特殊處理:不再將它看作一個普通層,而是作為一個特殊的“多輸出模塊”,并正確記錄下它所有輸出頭的通道信息。
  2. 改造_predict_once函數(模型推理階段):讓它在前向傳播時,如果遇到這個被標記過的特殊主干網絡,就執行特殊的傳播邏輯。這個邏輯會一次性接收主干網絡輸出的多個特征圖,并將它們正確地存放到一個列表中(y列表),以供后續的Neck網絡層使用。

2. 實戰:一步步改造YOLOv8主干

主干網絡是決定模型性能的基石。YOLO Master提供了大量先進的Backbone供我們選擇。

可選主干網絡核心思想
RepViT融合CNN的效率和ViT的性能
StarNet輕量級、高效的星狀結構
EfficientViT高效的Vision Transformer變體
FasterNet極速推理,專注于硬件友好
ConvNeXtV2現代化的純卷積網絡,性能媲美Transformer
SwinTransformer經典的層級化窗口注意力Transformer
VanillaNet極簡主義設計,返璞歸真但效果強大
(還有更多)

讓我們以RepViT為例,演示完整的替換步驟。
接下來,我們將以RepViT為例,完整地展示如何實現這一“手術”。這個方法具有普適性,適用于本文中介紹的所有主干網絡。

① 準備模塊代碼和配置文件
  • 第一步:安放模塊代碼
    ultralytics/ultralytics/nn/目錄下,創建一個新文件夾new_modules。然后,將yolo-master項目中的Backbone_RepViT.py文件復制到這個新文件夾中,并重命名為repvit.py
    在這里插入圖片描述
    在這里插入圖片描述

    • 目的:將我們自定義的模塊代碼與ultralytics的官方代碼分離開,便于管理和維護。
  • 第二步:安放YAML文件
    yolo-master中的RepViT-P345.yaml文件復制到ultralytics/cfg/models/v8/(或者你指定的其他版本目錄)下。這個YAML文件描述了使用RepViT作為主干的網絡結構。
    在這里插入圖片描述

    • 目的:讓YOLO的訓練引擎能夠找到并加載我們的新模型定義。
② 引用新模塊

打開ultralytics/nn/tasks.py文件,在文件的開頭部分,找到導入模塊的區域,添加以下代碼:

# ultralytics/nn/tasks.py# ... 其他 import 語句 ...
from ultralytics.utils.torch_utils import (fuse_conv_and_bn, fuse_deconv_and_bn, initialize_weights, intersect_dicts,make_divisible, model_info, scale_img, time_sync)# ==================== 在這里添加我們的新模塊導入 ====================
from .new_modules.repvit import *
from .new_modules.starnet import * # 為后續其他模塊預留
from .new_modules.efficientvit import * # 為后續其他模塊預留
# ... 你可以繼續添加其他自定義模塊的導入 ...
# ===================================================================#LOGGER = logging.getLogger(__name__)
# ... 文件后續內容 ...

import

  • 目的:將repvit.py中定義的所有類(如repvit_m2_3)導入到tasks.py的全局命名空間中,這樣parse_model函數在解析YAML時才能通過字符串名字找到并實例化它們。
③ 核心改造一:parse_model函數

tasks.py中,使用Ctrl+F找到parse_model函數。這是整個魔改工程的核心。我們將分三部分進行修改。

i) 修改讀取模型參數部分(增強解析器的靈活性)

這部分修改的目的是讓解析器更加健壯和靈活,能夠處理更復雜的參數和模塊名,為后續所有類型的魔改(包括主干、頸部等)打下基礎。
原代碼:

 for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']):  # from, number, module, argsm = getattr(torch.nn, m[3:]) if 'nn.' in m else globals()[m]  # get modulefor j, a in enumerate(args):if isinstance(a, str):with contextlib.suppress(ValueError):args[j] = locals()[a] if a in locals() else ast.literal_eval(a)```* **修改后代碼**:```pythonis_backbone = Falsefor i, (f, n, m, args) in enumerate(d['backbone'] + d['head']):  # from, number, module, args# ==================== 增強的模塊獲取邏輯 ====================# 原理:使用 try-except 塊來優雅地處理模塊查找。# 原生代碼直接使用 globals()[m] 查找,如果m不是一個已知的模塊名,程序會報錯退出。# 修改后,我們嘗試獲取模塊,如果失敗(比如m是一個我們后續要特殊處理的字符串),# 就暫時跳過,給予后續代碼處理它的機會。try:if m == 'node_mode':  # 為更復雜的頸部(如GFPN)預留的邏輯m = d[m]if len(args) > 0:if args[0] == 'head_channel':args[0] = int(d[args[0]])t = m # 臨時保存模塊名字符串,用于打印日志m = getattr(torch.nn, m[3:]) if 'nn.' in m else globals()[m]  # get moduleexcept:pass # 如果在globals()中找不到模塊名,暫時忽略# ==========================================================# ==================== 增強的參數解析邏輯 ====================# 原理:同樣使用 try-except 塊增強魯棒性。# 有些參數可能是字符串(如路徑),ast.literal_eval 會解析失敗。# 修改后,如果解析失敗,就保持其原始的字符串類型。for j, a in enumerate(args):if isinstance(a, str):with contextlib.suppress(ValueError):try:args[j] = locals()[a] if a in locals() else ast.literal_eval(a)except:args[j] = a # 解析失敗時,保留為字符串# ==========================================================
ii) 添加自定義主干的參數接收邏輯

這是識別我們自定義主干的“秘密握手”。

  • 實現思路:我們在parse_model函數的循環內部,計算每個模塊的輸出通道數c2之后,添加一個判斷。如果m是我們自定義的主干網絡類(如repvit_m2_3),我們就調用它特有的一個方法(我們約定所有自定義主干都實現一個名為channel的屬性或方法)來獲取它所有輸出層的通道列表。

c1, c2 = ch[f], args[0]這行代碼之后,添加如下邏輯:

# ... 在 parse_model 函數的循環內 ...if m in (Classify, Detect, RTDETR, Segment):# ... 省略 ...elif m is nn.BatchNorm2d:# ... 省略 ...else:c2 = ch[f] if c2 == -1 else c2# ==================== 自定義主干接收參數部分 ====================# 原理:這是識別自定義主干的核心。# 我們約定,所有即插即用的主干網絡模塊,都會有一個名為 `channel` 的屬性,# 這個屬性返回一個列表,包含了它所有輸出特征圖的通道數。# 通過檢查模塊 m 是否有 'channel' 屬性,我們就能識別出它。if hasattr(globals()[t], 'channel'): # 使用臨時變量t(模塊名字符串)來檢查# 實例化主干網絡。注意,這里的m是模塊的類本身。m = m()# 獲取輸出通道列表,例如 [128, 256, 512]c2 = m.channel# ==============================================================m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)  # module# ... 后續代碼 ...
iii) 修改模型實例化部分

這部分是整個改造的“執行”階段。在這里,我們將真正地區分處理標準層和我們的自定義主干。

原代碼:

    # 原代碼部分一m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)  # modulet = str(m)[8:-2].replace('__main__.', '')  # module typem_.np = sum(x.numel() for x in m_.parameters())  # number paramsm_.i, m_.f, m_.type = i, f, t  # attach index, 'from' index, typeif verbose:LOGGER.info(f'{i:>3}{str(f):>20}{n_:>3}{m_.np:10.0f}  {t:<45}{str(args):<30}')  # printsave.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1)  # append to savelist# 原代碼部分二if i == 0:ch = []ch.append(c2)

修改后代碼 (整合了ii和iii的邏輯)

    # ... 在 c1, c2 = ch[f], args[0] 之后 ...# 這是更完整的邏輯,取代了ii)中的簡單判斷if m in (Conv, ConvTranspose, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, Focus,BottleneckCSP, C1, C2, C2f, C3, C3TR, C3Ghost, nn.Conv2d, DWConvTranspose, C3x, RepC3):c2 = m.get_nc(ch, f, args)  # get c2 output channels# ... (省略其他elif)# ==================== 修改后的模型實例化 ====================# --- 步驟 1: 實例化與識別 ---# 巧妙的識別方法:我們先按常規方式實例化模塊。# 如果 m 是我們的自定義主干,它沒有輸入參數,直接 m() 即可。# 然后,我們檢查它的 `channel` 屬性,得到 `c2`。# 如果 `c2` 是一個列表,就說明這是一個自定義主干!if isinstance(c2, list):# 是自定義主干,is_backbone標志位設為Trueis_backbone = True# m_ 直接就是我們實例化的主干對象 m (在之前的步驟中已經 m=m() )m_ = m# 給模塊實例動態添加一個 'backbone' 屬性,值為 True。# 目的:這是一個“標記”,為了在后續的前向傳播 `_predict_once` 函數中識別它。m_.backbone = Trueelse:# 是標準層,按原邏輯構建m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)  # modulet = str(m)[8:-2].replace('__main__.', '')  # module type# --- 步驟 2: 計算參數并附加信息 ---# 計算參數量m_.np = sum(x.numel() for x in m_.parameters())  # number params# 附加索引信息。注意這里的 `i+4`# 原理:我們的自定義主干會輸出多個特征圖,為了給這些特征圖在內部留出索引位置(0,1,2,3),# 我們將主干模塊本身的索引號人為地增加,例如 `0 -> 4`。# 這樣,后續Neck部分的層索引就不會與Backbone的輸出索引沖突。# 4是一個經驗值,通常主干輸出P2-P5四層特征,但只要比主干輸出層數多即可。m_.i, m_.f, m_.type = i + 4 if is_backbone else i, f, t  # attach index, 'from' index, typeif verbose:LOGGER.info(f'{i:>3}{str(f):>20}{n_:>3}{m_.np:10.0f}  {t:<45}{str(args):<30}')  # print# 將需要保存的層的索引添加到 savelist。同樣,對主干索引進行偏移。save.extend(x % (i + 4 if is_backbone else i) for x in ([f] if isinstance(f, int) else f) if x != -1)# --- 步驟 3: 追蹤通道數 ---if i == 0:ch = []# 關鍵修改!if isinstance(c2, list):# 如果 c2 是列表 (我們的主干),則用 extend 將所有輸出通道加入 ch 列表ch.extend(c2)# 補位操作:確保 ch 列表的長度至少為5。# 目的:為了與 `_predict_once` 中的邏輯對齊,方便通過索引訪問不同尺度的特征。# 即使某個主干不輸出P1, P2層,也用0占位,避免索引錯誤。for _ in range(5 - len(ch)):ch.insert(0, 0)else:# 如果是標準層,按原邏輯 append 單個輸出通道ch.append(c2)# ==============================================================
④ 核心改造二:_predict_once函數

這個函數負責執行模型的前向傳播。我們需要修改它,以便能正確處理我們標記了backbone=True的特殊模塊。

原代碼:

    def _predict_once(self, x, profile=False, visualize=False, embed=None):y, dt, embeddings = [], [], []  # outputsfor m in self.model:if m.f != -1:  # if not from previous layerx = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]  # from earlier layersif profile:self._profile_one_layer(m, x, dt)x = m(x)  # runy.append(x if m.i in self.save else None)  # save output# ... 省略 visualize 和 embed 的代碼 ...return x

修改后代碼

    def _predict_once(self, x, profile=False, visualize=False, embed=None):y, dt, embeddings = [], [], []  # outputsfor m in self.model:if m.f != -1:  # if not from previous layerx = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]  # from earlier layersif profile:self._profile_one_layer(m, x, dt)# ==================== 自定義主干前向傳播邏輯 ====================# 原理:檢查在 parse_model 中添加的 'backbone' 標記。if hasattr(m, 'backbone'):# 如果是主干模塊,直接調用它,它會返回一個特征圖列表x = m(x)# 補位操作,與 parse_model 中的邏輯對應。# 目的:確保輸出列表 x 的長度固定,即使主干輸出的特征圖數量不同,# 后續的層可以通過固定的索引(如 y[4])來獲取特征。for _ in range(5 - len(x)):x.insert(0, None) # 用 None 填充不存在的低層特征# 遍歷主干輸出的每一層特征圖for i_idx, i in enumerate(x):# 根據 savelist 判斷這一層是否需要保存給后續層使用if i_idx in self.save:y.append(i)else:y.append(None) # 如果不需要,用 None 占位# 將主干的最后一層輸出作為下一個模塊的輸入x = x[-1]else:# 如果是標準層,執行原有的邏輯x = m(x)  # runy.append(x if m.i in self.save else None)  # save output# ==============================================================if visualize:feature_visualization(x, m.type, m.i, save_dir=visualize)if embed and m.i in embed:# ... (省略)return x

如此修改后,模型的網絡結構就會發生變化:
new_networkl


四、頸部(Neck)的替換

我們已經成功地改造了tasks.py,建立了一個強大的、可兼容自定義主干的框架。現在,我們將利用這個框架,對模型的“頸部”進行替換。

1-GFPN (GiraffeDet FPN)

GFPN(長頸鹿特征金字塔網絡)通過其獨特的、類似長頸鹿脖子的交錯連接方式,高效地融合深層語義信息和淺層空間信息。我們將用它來替換YOLOv8原生的PANet結構。

① 文件準備與引用(此步驟與之前一致)
  1. 代碼Neck-GFPN.py -> new_modules/GFPN.py
  2. 配置GFPN-P345.yaml -> cfg/models/v8/
  3. 引用:在 tasks.py 中添加 from .new_modules.GFPN import *
② GFPN的YAML配置與parse_model的聯動

思考:GFPN是如何被我們的新parse_model函數解析的?

讓我們深入GFPN-P345.yamltasks.py的代碼;。

  • 第一步:分析GFPN-P345.yaml的配置

    打開GFPN-P345.yaml,你會發現它巧妙地使用變量來定義網絡結構,這是一種非常優雅的工程實踐。

    # ultralytics/cfg/models/v8/GFPN-P345.yaml (內容示例)# ------------------ YAML 頂層參數定義 ------------------
    # 定義了頸部和頭部的默認通道數
    widen_factor: 1.0
    head_channel: 256# 核心:定義了頸部中重復使用的核心模塊的名稱
    # 這樣做的好處是,如果想把所有 CSPStage 換成其他模塊,
    # 只需修改下面這一行,無需改動 head 中的每一處。
    node_mode: CSPStage# ... (nc, depth_multiple, width_multiple 等定義)# ------------------ Backbone (主干) ------------------
    backbone:- [-1, 1, Conv, [64, 3, 2]]              # 0-P1/2# ... (主干網絡定義,這里可能是標準的YOLOv8主干,也可能是我們之前替換的RepViT等)# 假設主干的第4、6、9層分別輸出P3, P4, P5 特征# ------------------ Head (頸部 + 頭部) ------------------
    head:# in_channels: [256, 512, 1024] # 來自Backbone的 P3, P4, P5# out_channels: [256, 512, 1024]# --- GFPN 頸部結構 ---- [-1, 1, Conv, [256, 1, 1]]              # 10- [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 11- [[-1, 6], 1, Concat, [1]]               # 12 (Concat P4)- [-1, 3, node_mode, [head_channel, 3]]   # 13 <--- 關鍵!使用了node_mode- [-1, 1, Conv, [256, 1, 1]]              # 14- [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 15- [[-1, 4], 1, Concat, [1]]               # 16 (Concat P3)- [-1, 3, node_mode, [head_channel, 3]]   # 17 <--- 關鍵!使用了node_mode- [-1, 1, Conv, [256, 3, 2]]              # 18- [[-1, 14], 1, Concat, [1]]              # 19- [-1, 3, node_mode, [head_channel, 3]]   # 20 <--- 關鍵!使用了node_mode- [-1, 1, Conv, [256, 3, 2]]              # 21- [[-1, 10], 1, Concat, [1]]              # 22- [-1, 3, node_mode, [head_channel, 3]]   # 23 <--- 關鍵!使用了node_mode# --- Detect Head ---- [[17, 20, 23], 1, Detect, [nc]]        # Detect(P3, P4, P5)
    
  • 第二步:追蹤parse_model的執行流程

    parse_model函數解析到第13層 [-1, 3, node_mode, [head_channel, 3]] 時,我們之前修改的代碼開始發揮作用:

    # 在 ultralytics/nn/tasks.py 的 parse_model 中# 此時,循環變量的值為:
    # i = 13, f = -1, n = 3, m = 'node_mode', args = ['head_channel', 3]try:# 1. 檢查到 m == 'node_mode',條件成立if m == 'node_mode':# 2. 將 m 的值從字符串 'node_mode' 替換為 YAML 頂層定義的實際值# m = d['node_mode']  -->  m 變成了 'CSPStage'm = d[m]# 3. 檢查 args 列表if len(args) > 0: # len(['head_channel', 3]) > 0, 成立# 4. 檢查第一個參數是否為 'head_channel'if args[0] == 'head_channel': # 成立# 5. 將 args[0] 從字符串 'head_channel' 替換為 YAML 頂層定義的實際值# args[0] = int(d['head_channel']) --> args[0] 變成了整數 256args[0] = int(d[args[0]])# 經過處理后,模塊定義從 'node_mode', ['head_channel', 3]# 變成了實際的 'CSPStage', [256, 3]t = m # t 被賦值為 'CSPStage'# 6. 最后,通過 globals()['CSPStage'] 找到我們導入的 CSPStage 類m = getattr(torch.nn, m[3:]) if 'nn.' in m else globals()[m]
    except:pass
    # ... 后續代碼將使用 m = CSPStage 類, args = [256, 3] 來實例化模塊
    

    結論:我們為parse_model添加的try-exceptif m == 'node_mode'邏輯,本質上是創建了一個“宏替換”機制。它使得YAML的編寫者可以像定義宏一樣預設模塊名和參數,極大地增強了配置文件的靈活性和復用性。

③ 魔改前后模型參數對比

我們可以通過打印模型結構來直觀地看到變化。

魔改前模型結構 (標準 YOLOv8 Neck)

    # yolov8.yaml summary: 225 layers, 3157200 parameters, 3157184 gradients, 8.9 GFLOPs... (backbone)10  -1  1  ultralytics.nn.modules.conv.Conv         [512, 1, 1]11  -1  1  torch.nn.modules.upsampling.Upsample     [None, 2, 'nearest']12  [-1, 6]  1  ultralytics.nn.modules.container.Concat  [1]13  -1  3  ultralytics.nn.modules.block.C2f         [512, True]14  -1  1  ultralytics.nn.modules.conv.Conv         [256, 1, 1]15  -1  1  torch.nn.modules.upsampling.Upsample     [None, 2, 'nearest']16  [-1, 4]  1  ultralytics.nn.modules.container.Concat  [1]17  -1  3  ultralytics.nn.modules.block.C2f         [256, True]18  -1  1  ultralytics.nn.modules.conv.Conv         [256, 3, 2]19  [-1, 14] 1  ultralytics.nn.modules.container.Concat  [1]20  -1  3  ultralytics.nn.modules.block.C2f         [512, True]21  -1  1  ultralytics.nn.modules.conv.Conv         [512, 3, 2]22  [-1, 10] 1  ultralytics.nn.modules.container.Concat  [1]23  -1  3  ultralytics.nn.modules.block.C2f         [1024, True]24  [17, 20, 23] 1 ultralytics.nn.modules.head.Detect [80]

魔改后模型結構 (GFPN-P345 Neck)

    # GFPN-P345.yaml summary: 237 layers, 3348644 parameters, 3348628 gradients, 9.2 GFLOPs... (backbone)10  -1  1  ultralytics.nn.modules.conv.Conv         [256, 1, 1]11  -1  1  torch.nn.modules.upsampling.Upsample     [None, 2, 'nearest']12  [-1, 6]  1  ultralytics.nn.modules.container.Concat  [1]13  -1  3  new_modules.GFPN.CSPStage                [256, 3]  # <-- 核心模塊被替換14  -1  1  ultralytics.nn.modules.conv.Conv         [256, 1, 1]15  -1  1  torch.nn.modules.upsampling.Upsample     [None, 2, 'nearest']16  [-1, 4]  1  ultralytics.nn.modules.container.Concat  [1]17  -1  3  new_modules.GFPN.CSPStage                [256, 3]  # <-- 核心模塊被替換18  -1  1  ultralytics.nn.modules.conv.Conv         [256, 3, 2]19  [-1, 14] 1  ultralytics.nn.modules.container.Concat  [1]20  -1  3  new_modules.GFPN.CSPStage                [512, 3]  # <-- 核心模塊被替換21  -1  1  ultralytics.nn.modules.conv.Conv         [512, 3, 2]22  [-1, 10] 1  ultralytics.nn.modules.container.Concat  [1]23  -1  3  new_modules.GFPN.CSPStage                [1024, 3] # <-- 核心模塊被替換24  [17, 20, 23] 1 ultralytics.nn.modules.head.Detect [80]

通過對比可以清晰地看到,原生的C2f模塊被我們自定義的CSPStage(來自GFPN.py)所取代,這證明我們的魔改成功了。


五、頭部(Head)的革新

1-DyHead (Dynamic Head)

DyHead通過統一的注意力機制,動態地選擇最重要的特征,極大地提升了檢測頭的表征能力。

① 文件準備與引用(同上)
  1. 代碼: Head-DyHead.py -> new_modules/DyHead.py
  2. 配置: DyHead-P345.yaml -> cfg/models/v8/
  3. 引用: tasks.py 中添加 from .new_modules.DyHead import *
② 核心解析:DyHead的“無縫”集成

思考:DyHead為什么不需要對tasks.py做任何新的修改?

答案在于,DyHead的設計模式與YOLO原生Detect頭高度兼容,并且我們之前對parse_model的通用化改造已經能夠處理它。

第一步:分析DyHead-P345.yaml的結構
DyHead模塊將替換掉原Detect層以及之前的一些卷積層。

    # DyHead-P345.yaml (head部分示例)head:# ... (頸部融合層)# 假設頸部輸出三層特征分別在索引 17, 20, 23# 原生YOLOv8中,這里會有一系列解耦的卷積層,最后連接Detect層# 使用DyHead后,直接將三層特征輸入給DyHead模塊- [[17, 20, 23], 1, DyHead, [nc]] # <--- 直接替換

第二步:追蹤_predict_once的執行流程
當模型前向傳播到DyHead層時:

    # 在 ultralytics/nn/tasks.py 的 _predict_once 中# 此時,m 是實例化的 DyHead 對象,m.f 是 [17, 20, 23]# 1. 進入獲取輸入的邏輯if m.f != -1:  # 成立# 2. m.f 是一個列表, 執行 else 分支#    這行代碼會從 y 列表中,根據索引 17, 20, 23,#    取出對應的三層特征圖張量,并打包成一個新的列表 `x`#    x = [y[17], y[20], y[23]]x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]# 3. 檢查 'backbone' 屬性, DyHead沒有這個標記, 跳過if hasattr(m, 'backbone'):...else:# 4. 執行常規的前向傳播#    將包含三個特征圖的列表 x, 整體傳遞給 DyHead 模塊#    x = m(x)  等價于  outputs = dyhead_instance([feature_p3, feature_p4, feature_p5])x = m(x)y.append(x if m.i in self.save else None)

結論DyHead模塊本身被設計為接收一個特征圖列表作為輸入。而YOLO的_predict_once函數中,處理多輸入的from(如[-1, 6][17, 20, 23])的邏輯,天然地就會將多個來源的特征圖打包成一個列表。兩者一拍即合,實現了無縫對接。我們不需要為DyHead編寫任何特殊的解析或執行代碼。


六、注意力機制(Attention)的融合

歡迎來到“魔改”系列中最靈活、最有趣的部分——集成注意力機制。

核心思想與類比:想象一下,當您在一張雜亂的桌面上尋找鑰匙時,您的大腦并不會平均地掃描每一個平方厘米。相反,您的目光會自動聚焦于桌面上的高亮區域,比如金屬反光處、顏色鮮艷的物體旁。注意力機制(Attention Mechanism)賦予了神經網絡類似的能力。它讓模型在處理海量信息時,能夠智能地“聚焦”于最關鍵的特征,并“忽略”次要或無關的背景,從而用有限的計算資源做出更精準的判斷。

本節,我們將以CBAM (Convolutional Block Attention Module) 為例,進行一次完整、詳盡的“即插即用”式集成。我們將一起分析其代碼原理,選擇合適的插入位置,完成一次無死角的YAML文件修改,并最終驗證我們的工作。

1. 深入理解CBAM模塊

在動手之前,我們先快速理解CBAM的工作原理,這將有助于我們決定將它放在網絡中的哪個位置。

CBAM由兩個串聯的子模塊組成:

  1. 通道注意力模塊 (Channel Attention):它回答的問題是“什么特征更重要?”。比如,在一個人像識別任務中,包含“眼睛”、“鼻子”等信息的特征通道,其重要性就應該高于包含“背景墻壁”信息的通道。該模塊會學習一個權重,對各個通道進行加權,增強重要特征,抑制次要特征。
  2. 空間注意力模塊 (Spatial Attention):它回答的問題是“特征圖的哪個位置更重要?”。在識別出一張人臉后,其五官所在的位置顯然比背景區域更關鍵。該模塊會生成一個空間“熱力圖”,告訴網絡應該重點關注特征圖上的哪些像素區域。

最重要的特性:CBAM模塊的forward函數接收一個張量x,經過內部一系列計算后,輸出一個與x 尺寸完全相同 的張量。這正是它能被“即插即用”的關鍵。

2. 集成CBAM

① 準備工作
  1. 代碼: 將 yolo_master/.../Attention-CBAM.py 復制到 ultralytics/ultralytics/nn/new_modules/ 并重命名為 CBAM.py
  2. 引用: 在 ultralytics/nn/tasks.py 的頂部添加 from .new_modules.CBAM import *
② CBAM應該放在哪里?

這是一個開放性問題,但有一些常用的策略:

  • 放在特征提取之后:通常將注意力模塊放置在核心特征提取塊(如C2f)之后。這樣做的好處是,C2f已經產生了豐富的特征組合,此時使用CBAM可以立刻對這些新特征進行“精煉”和“篩選”,讓最有用的信息傳遞給下一層。
  • 放在下采樣之前:在網絡通過步進卷積(Conv)進行下采樣、縮小特征圖尺寸之前,使用CBAM可以確保在信息被壓縮前,關鍵特征已經被充分“關注”,減少重要信息的丟失。

本教程決策:我們將遵循以上策略,選擇在Backbone的第4個模塊(一個C2f層)之后,第5個模塊(一個Conv下采樣層)之前插入CBAM。這個位置非常理想,它能精煉P3級別的特征,再將其傳遞給更深的網絡。

③ YAML修改

這是最關鍵的一步。我們將以yolov8n.yaml為藍本,創建yolov8n-CBAM.yaml

  • 第一步:復制并重命名yolov8n.yamlyolov8n-CBAM.yaml

  • 第二步:進行修改。 下面是完整的修改前后對比,所有改動都用注釋明確標出。

yolov8n.yaml (原始文件)

# ultralytics/cfg/models/v8/yolov8n.yamlnc: 80  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.25  # layer channel multiple# anchors
anchors:- [10,13, 16,30, 33,23]  # P3/8- [30,61, 62,45, 59,119]  # P4/16- [116,90, 156,198, 373,326]  # P5/32# YOLOv8.0n backbone
backbone:# [from, number, module, args]- [-1, 1, Conv, [64, 3, 2]]  # 0-P1/2- [-1, 1, Conv, [128, 3, 2]]  # 1-P2/4- [-1, 3, C2f, [128, True]]  # 2- [-1, 1, Conv, [256, 3, 2]]  # 3-P3/8- [-1, 6, C2f, [256, True]]  # 4- [-1, 1, Conv, [512, 3, 2]]  # 5-P4/16- [-1, 6, C2f, [512, True]]  # 6- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32- [-1, 3, C2f, [1024, True]] # 8- [-1, 1, SPPF, [1024, 5]]   # 9# YOLOv8.0n head
head:- [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 10- [[-1, 6], 1, Concat, [1]]  # 11- [-1, 3, C2f, [512, False]] # 12- [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 13- [[-1, 4], 1, Concat, [1]]  # 14- [-1, 3, C2f, [256, False]] # 15- [-1, 1, Conv, [256, 3, 2]] # 16- [[-1, 12], 1, Concat, [1]] # 17- [-1, 3, C2f, [512, False]] # 18- [-1, 1, Conv, [512, 3, 2]] # 19- [[-1, 9], 1, Concat, [1]]  # 20- [-1, 3, C2f, [1024, False]]# 21- [[15, 18, 21], 1, Detect, [nc]]  # Detect(P3, P4, P5)

yolov8n-CBAM.yaml (修改后的文件)

# ultralytics/cfg/models/v8/yolov8n-CBAM.yaml# ... (nc, depth_multiple, width_multiple, anchors 定義與上面完全相同) ...# YOLOv8.0n backbone with CBAM
backbone:# [from, number, module, args]- [-1, 1, Conv, [64, 3, 2]]      # 0- [-1, 1, Conv, [128, 3, 2]]     # 1- [-1, 3, C2f, [128, True]]      # 2- [-1, 1, Conv, [256, 3, 2]]     # 3- [-1, 6, C2f, [256, True]]      # 4- [-1, 1, CBAM, [256]]           # 5  <--- 新增CBAM層. 它接收第4層的256通道輸出- [-1, 1, Conv, [512, 3, 2]]     # 6 (原索引為5)- [-1, 6, C2f, [512, True]]      # 7 (原索引為6)- [-1, 1, Conv, [1024, 3, 2]]    # 8 (原索引為7)- [-1, 3, C2f, [1024, True]]     # 9 (原索引為8)- [-1, 1, SPPF, [1024, 5]]       # 10 (原索引為9)# YOLOv8.0n head with updated indices
head:- [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 11# Concat 融合 Neck P5 和 Backbone P4. Backbone P4 現在是第7層- [[-1, 7], 1, Concat, [1]]      # 12 (原為[-1, 6]) <--- 索引更新!- [-1, 3, C2f, [512, False]]     # 13- [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 14# Concat 融合 Neck P4 和 Backbone P3. Backbone P3 的輸出現在經過了第4層的C2f和第5層的CBAM,所以我們從第5層引出- [[-1, 5], 1, Concat, [1]]      # 15 (原為[-1, 4]) <--- 索引更新!- [-1, 3, C2f, [256, False]]     # 16- [-1, 1, Conv, [256, 3, 2]]     # 17# Concat 融合 Neck P3 和 Neck P4(第13層)- [[-1, 13], 1, Concat, [1]]     # 18 (原為[-1, 12]) <--- 索引更新!- [-1, 3, C2f, [512, False]]     # 19- [-1, 1, Conv, [512, 3, 2]]     # 20# Concat 融合 Neck P4 和 Backbone P5(第10層)- [[-1, 10], 1, Concat, [1]]     # 21 (原為[-1, 9]) <--- 索引更新!- [-1, 3, C2f, [1024, False]]    # 22# Detect 層的輸入來自第16, 19, 22層- [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5) (原為[15, 18, 21]) <--- 索引更新!
(4) 追蹤parse_model如何處理CBAM

讓我們看看parse_model解析新增的第5層[-1, 1, CBAM, [256]]時,發生了什么。

  1. 確定輸入通道 c1from-1,所以 c1 來自上一層(第4層)的輸出。我們知道第4層C2f的輸出通道是256,所以 c1 = 256
  2. 確定模塊 mm 是我們導入的 CBAM 類。
  3. 確定參數 argsargs 是列表 [256]
  4. 實例化模塊 m_:對于CBAM這樣的通用模塊,parse_model會執行 m(c1, *args) 來實例化。這會調用 CBAM(256, 256)
  5. 確定輸出通道 c2:對于通用模塊,parse_model會默認將args[0]作為輸出通道數,即c2 = 256
  6. 更新通道列表 ch:執行 ch.append(c2),將256添加到通道列表中。

結論:由于CBAM輸入和輸出通道數相同(c1=c2=256),它完美地融入了網絡的數據流,對后續層的通道數計算沒有任何影響。唯一的、也是最容易出錯的復雜性,在于手動更新所有后續層(尤其是ConcatDetect層)的from索引。

(5) 可視化對比:魔改前后的模型結構

下面是模擬model.info()命令輸出的文本,可以清晰地看到變化。
魔改前模型結構 (yolov8n.yaml)

      # ... (層 0-3)4  -1  6       ultralytics.nn.modules.block.C2f  119296    (256, 128, 6, True, False, 1, 0.5)5  -1  1       ultralytics.nn.modules.conv.Conv  147968    (512, 256, 3, 2)6  -1  6       ultralytics.nn.modules.block.C2f  476160    (512, 256, 6, True, False, 1, 0.5)# ...11  [-1, 6]  1  ultralytics.nn.modules.container.Concat  0         (1)# ...14  [-1, 4]  1  ultralytics.nn.modules.container.Concat  0         (1)# ...22  [15, 18, 21]  1  ultralytics.nn.modules.head.Detect  649920    (80, (256, 512, 1024))

魔改后模型結構 (yolov8n-CBAM.yaml)

      # ... (層 0-3)4  -1  6       ultralytics.nn.modules.block.C2f  119296    (256, 128, 6, True, False, 1, 0.5)+  5  -1  1            new_modules.CBAM.CBAM  704       (256, None) # <--- 新增層,參數量極小6  -1  1       ultralytics.nn.modules.conv.Conv  147968    (512, 256, 3, 2)7  -1  6       ultralytics.nn.modules.block.C2f  476160    (512, 256, 6, True, False, 1, 0.5)# ...- 11  [-1, 6]  1  ultralytics.nn.modules.container.Concat  0         (1)+ 12  [-1, 7]  1  ultralytics.nn.modules.container.Concat  0         (1) # <--- 索引更新# ...- 14  [-1, 4]  1  ultralytics.nn.modules.container.Concat  0         (1)+ 15  [-1, 5]  1  ultralytics.nn.modules.container.Concat  0         (1) # <--- 索引更新# ...- 22  [15, 18, 21]  1  ultralytics.nn.modules.head.Detect  649920    (80, (256, 512, 1024))+ 23  [16, 19, 22]  1  ultralytics.nn.modules.head.Detect  649920    (80, (256, 512, 1024)) # <--- 索引更新

通過這個對比,我們可以百分之百地確認,我們的CBAM模塊已成功插入,并且整個模型的后續連接也已正確更新。對于SE模塊的集成,過程與此完全相同,不再贅述。


七、核心組件的優化

在掌握了對Backbone、Neck、Head三大件的“大刀闊斧”式改造后,我們再來學習如何對網絡中的基礎“零件”——如上下采樣和卷積模塊——進行“精雕細琢”的單元。這要求我們更深入地理解YOLO的parse_model函數是如何處理標準模塊的。

1. 上下采樣模塊:EUCB (Efficient Up-sampling with Channel Balancing)

背景與目的:在特征金字塔網絡(FPN)中,上采樣負責將高層(小尺寸、強語義)的特征圖放大,以便與低層(大尺寸、強細節)的特征圖融合。YOLOv8默認使用的nn.Upsample(配合mode='nearest')雖然速度極快,但它是一種固定的、非學習性的插值方法,僅僅是簡單地復制像素,可能會在放大過程中產生偽影或丟失細節。

EUCB (Efficient Up-sampling with Channel Balancing) 旨在解決這個問題。它是一種可學習的上采樣模塊。這意味著網絡可以通過反向傳播,學習到如何以最優的方式從低分辨率特征中“生成”高分辨率特征,同時還能調整通道數量,從而可能帶來更平滑、更有效的特征融合效果。

① 集成步驟
  1. 代碼: 將 yolo_master/.../Upsample-EUCB.py 復制到 ultralytics/ultralytics/nn/new_modules/ 并重命名為 EUCB.py
  2. 引用: 在tasks.py的頂部添加 from .new_modules.EUCB import *
② 核心解析:EUCB的參數與parse_model的自動適配

思考:nn.UpsampleEUCB 在參數上有何不同?parse_model如何處理這種不同?

  • 第一步:分析模塊的__init__簽名

    • torch.nn.Upsample的定義很簡單,它只關心縮放因子和模式,不改變通道數。其YAML中的args[None, 2, 'nearest']None代表輸出尺寸(由scale_factor=2決定),通道數保持不變。

    • 我們打開new_modules/EUCB.py文件(或根據其用法推斷),可以發現EUCB模塊的定義更像一個卷積層。一個合理的__init__簽名應該是:def __init__(self, c1, c2, scale_factor=2):

      • c1: 輸入通道數。
      • c2: 輸出通道數。這是與nn.Upsample最大的不同。
      • scale_factor: 縮放因子。
  • 第二步:修改YAML文件并追蹤解析過程

    現在,我們在yolov8.yamlhead部分進行替換。

    # yolov8-EUCB.yaml (head 部分示例)head:# ...# 假設解析到第14層,其輸入來自第13層(C2f),輸出通道數為512# ch = [..., 512]# 第15層: 上采樣層# --- 原代碼 ---# - [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 15# --- 修改后 ---# 我們希望上采樣后,通道數從512變為256- [-1, 1, EUCB, [256, 2]] # 15. args為[輸出通道數, 縮放因子]# 第16層: Concat層# 它的輸入來自上一層(第15層)和主干的第4層 (假設通道數為256)- [[-1, 4], 1, Concat, [1]] # 16# 第17層: C2f層# 它的輸入來自第16層的Concat。Concat后的通道數 = 256(來自EUCB) + 256(來自Backbone) = 512- [-1, 3, C2f, [256, False]] # 17. 該C2f層輸出通道數為256# ...
    

    parse_model解析到我們修改的第15層 [-1, 1, EUCB, [256, 2]] 時,其標準模塊處理邏輯會執行以下操作:

    # 在 ultralytics/nn/tasks.py 的 parse_model 中# 此時: m = EUCB 類, args = [256, 2]
    # 假設上一層的輸出通道數 ch[-1] 是 512# 1. 獲取輸入通道數 c1
    # c1 = ch[f] --> c1 = ch[-1] --> c1 = 512
    c1 = ch[f]# 2. 獲取輸出通道數 c2
    # c2 = args[0] if isinstance(args[0], int) else ...
    # 這里的 args[0] 是 256, 是整數。所以 c2 被賦值為 256
    c2 = args[0] # 3. 實例化模塊
    # m_ = m(*args) 等價于 m_ = EUCB(512, 256, 2)
    # 注意!這里的`*args`展開會把所有參數傳進去,所以我們的模塊定義要與之匹配
    # 一個更嚴謹的寫法是在YAML中只寫輸出通道數,讓模塊內部處理
    # 假設YAML為 [-1, 1, EUCB, [256]]
    # 那么實例化將是 m_ = EUCB(c1, *args) --> EUCB(512, 256)
    m_ = nn.Sequential(...) if n > 1 else m(c1, *args) # 注意這里隱式的 c1 參數# 4. 更新通道列表 ch
    # ch.append(c2) --> ch.append(256)
    # 現在 ch 列表的最后一個元素是 256,供下一層使用
    ch.append(c2)
    

    結論:我們不需要為EUCB編寫任何特殊解析代碼。只要一個模塊遵循“接收輸入通道c1,并通過args接收其他參數(包括輸出通道c2)”這一標準模式,parse_model的通用邏輯就能自動完成輸入/輸出通道的推斷和模塊的正確實例化。

③ 魔改前后模型結構對比

魔改前 (使用 nn.Upsample)

    # ...# 假設第14層輸出512通道14 ... [..., 512, True]15  -1  1  torch.nn.modules.upsampling.Upsample     [None, 2, 'nearest']# 第15層輸出通道仍為512# ...

魔改后 (使用 EUCB)

    # ...# 第14層輸出512通道14 ... [..., 512, True]15  -1  1  new_modules.EUCB.EUCB                    [512, 256, 2]# 第15層輸出通道變為256,參數量增加,因為它有可學習的權重# ...

2. 卷積模塊:C2f_CMUNeXtBlock

這是對YOLOv8中最重要的特征提取單元C2f的直接替換。CMUNeXtBlock可能借鑒了ConvNeXt的設計,例如使用更大的卷積核、深度可分離卷積等,旨在用相似的參數量換取更強的特征表達能力。

① 集成步驟(同上)
  1. 代碼: C2f_CMUNeXtBlock.py -> new_modules/C2f_CMUNeXtBlock.py
  2. 引用: 在 tasks.py 中添加 from .new_modules.C2f_CMUNeXtBlock import *
② 實現“無痛”替換

思考:為什么C2f_CMUNeXtBlock可以如此輕易地替換C2f

答案在于接口兼容性C2f_CMUNeXtBlock在設計時,刻意模仿了C2f__init__參數簽名,使得它可以直接使用C2f在YAML文件中的參數定義。

  • 第一步:對比__init__簽名

    • C2f的簽名(簡化后):__init__(self, c1, c2, n=1, shortcut=False, ...)
    • C2f_CMUNeXtBlock的簽名(推斷):__init__(self, c1, c2, n=1, shortcut=False, ...)

    只要兩者都接收相同的核心參數(輸入通道c1,輸出通道c2,重復次數n,快捷連接shortcut),它們在YAML層面就是可互換的。

  • 第二步:修改YAML
    這個修改是最簡單的,只需更換模塊名即可。

    # yolov8-CMUNeXt.yaml (backbone 部分示例)backbone:- [-1, 1, Conv, [64, 3, 2]]  # 0- [-1, 1, Conv, [128, 3, 2]]  # 1# --- 原代碼 ---# - [-1, 3, C2f, [128, True]]  # 2# --- 修改后 ---- [-1, 3, C2f_CMUNeXtBlock, [128, True]] # 2. 參數完全相同!
    
  • 第三步:追蹤parse_model的執行流程
    這個流程與解析C2f時完全一樣。

    1. parse_model獲取模塊名C2f_CMUNeXtBlock
    2. 它從ch列表獲取輸入通道c1
    3. 它從args[128, True])中獲取輸出通道c2=128以及其他參數。
    4. 它根據n=3,循環3次來實例化模塊。
    5. 它將輸出通道c2=128添加到ch列表中。
      整個過程行云流水,因為C2f_CMUNeXtBlock完美地扮演了C2f的角色。

結論:對于想要替換現有標準模塊的自定義模塊,最佳實踐就是讓新模塊的參數接口與舊模塊保持高度一致。這樣就能實現“無痛”替換,將修改成本降至最低,僅需更改YAML中的一個字符串。


OK,今天我們就學習到這里🏆🎉👌!


文章參考

  • YOLO系列官方論文
    • YOLOv8 by Ultralytics
    • YOLOv1: You Only Look Once: Unified, Real-Time Object Detection
  • 核心項目與代碼
    • YOLO Master GitHub by DataWhale
    • Ultralytics YOLOv8 Documentation

拓展閱讀

  • Ultralytics GitHub 倉庫 (YOLOv3/v5/v8 的主流實現)
  • 作者的算法專欄 (包含更多YOLO技術文章)

💖 感謝您的耐心閱讀!

如果您覺得本文對您理解和實踐YOLO模型改造有所幫助,請考慮點贊、收藏或分享給更多有需要的朋友。您的支持是我持續創作優質內容的動力!歡迎在評論區交流討論,共同進步。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/88341.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/88341.shtml
英文地址,請注明出處:http://en.pswp.cn/web/88341.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Swift 圖論實戰:DFS 算法解鎖 LeetCode 323 連通分量個數

文章目錄摘要描述示例題解答案DFS 遍歷每個連通區域Union-Find&#xff08;并查集&#xff09;題解代碼分析&#xff08;Swift 實現&#xff1a;DFS&#xff09;題解代碼詳解構建鄰接表DFS 深度優先搜索遍歷所有節點示例測試及結果示例 1示例 2示例 3時間復雜度分析空間復雜度分…

【劍指offer】棧 隊列

&#x1f4c1; JZ9 用兩個棧實現隊列一個棧in用作進元素&#xff0c;一個棧out用于出元素。當棧out沒有元素時&#xff0c;從in棧獲取數據&#xff0c;根據棧的特性&#xff0c;棧out的top元素一定是先進入的元素&#xff0c;因此當棧out使用pop操作時&#xff0c;一定時滿足隊…

GoView 低代碼數據可視化

純前端 分支&#xff1a; master &#x1f47b; 攜帶 后端 請求分支: master-fetch &#x1f4da; GoView 文檔 地址&#xff1a;https://www.mtruning.club/ 項目純前端-Demo 地址&#xff1a;https://vue.mtruning.club/ 項目帶后端-Demo 地址&#xff1a;https://demo.mtrun…

Spring Boot返回前端Long型丟失精度 后兩位 變成00

文章目錄一、前言二、問題描述2.1、問題背景2.2、問題示例三、解決方法3.1、將ID轉換為字符串3.2、使用JsonSerialize注解3.3、使用JsonFormat注解一、前言 在后端開發中&#xff0c;我們經常會遇到需要將ID作為標識符傳遞給前端的情況。當ID為long類型時&#xff0c;如果該ID…

計算機網絡實驗——無線局域網安全實驗

實驗1. WEP和WPA2-PSK實驗一、實驗目的驗證AP和終端與實現WEP安全機制相關的參數的配置過程。驗證AP和終端與實現WPA2-PSK安全機制相關的參數的配置過程。驗證終端與AP之間建立關聯的過程。驗證關閉端口的重新開啟過程。驗證屬于不同BSS的終端之間的數據傳輸過程。二、實驗任務…

【從零開始學Dify】大模型應用開發平臺Dify本地化部署

目錄Dify一、本地化部署1、安裝docker2、安裝Dify&#xff08;1&#xff09;拉取代碼到本地&#xff08;2&#xff09;docker部署&#xff08;3&#xff09;查看服務狀態&#xff08;4&#xff09;web端部署&#xff08;5&#xff09;登錄二、可能會出現的問題&#xff08;1&am…

LVGL應用和部署(和物理按鍵交互)

【 聲明&#xff1a;版權所有&#xff0c;歡迎轉載&#xff0c;請勿用于商業用途。 聯系信箱&#xff1a;feixiaoxing 163.com】屏幕除了顯示部分&#xff0c;還要去和其他外設進行交互&#xff0c;這是非常重要的一個處理方法。我們知道&#xff0c;不管是mcu&#xff0c;還是…

限流式保護器如何筑牢無人駕駛汽車充電站的安全防線

摘要&#xff1a; 隨著新能源汽車&#xff0c;尤其是無人駕駛車隊的快速發展&#xff0c;充電設施的安全可靠性至關重要。交流充電樁&#xff08;俗稱“慢充樁”&#xff09;作為重要的充電基礎設施&#xff0c;其末端回路的安全保護需滿足國家標準GB51348-2019的嚴格要求&…

專題:2025母嬰行業洞察報告|附60+份報告PDF匯總下載

原文鏈接&#xff1a;https://tecdat.cn/?p42908 全球母嬰市場正經歷結構性增長&#xff0c;一面是歐美成熟市場的品質消費升級&#xff0c;一面是東南亞、中東等新興市場的人口紅利釋放。2020至2026年&#xff0c;全球母嬰市場規模將從1859億美元增至3084億美元&#xff0c;年…

從零搭建多商戶商城系統源碼:技術棧、數據庫設計與接口規劃詳解

如今&#xff0c;多商戶商城系統已成為傳統零售轉型與新型電商平臺構建的關鍵利器。無論是打造像某寶、某東這樣的綜合型平臺&#xff0c;還是服務于垂直行業的獨立電商&#xff0c;一套高效、可擴展的多商戶商城系統源碼&#xff0c;往往決定著平臺的成敗。 今天&#xff0c;小…

在Docker中運行macOS的超方便體驗!

在數字化和開發人員快速迭代的今日&#xff0c;擁有一個便捷、高效的開發環境成為每個開發者夢寐以求的事情。特別是在需要操作多個系統、開發跨平臺應用時&#xff0c;調試和測試的便利性顯得尤為重要。今天為大家介紹的這款開源項目&#xff0c;正是一個解決此類問題的利器—…

Kettle導入Excel文件進數據庫時,數值發生錯誤的一種原因

1、問題描述及原因 在使用kettle讀取Excel文件、并導入數據庫時&#xff0c;需要讀取Excel中的數值、日期(或日期時間、時間)、文本這三種類型的列進來&#xff0c;發現讀取其中的數值時&#xff0c;讀取的數字就不對。 經調查&#xff0c;原因是&#xff0c;在“導出數據為E…

Windows安裝DevEco Studio

1. 概述 DevEco Studio是華為基于IDEA Community開源工具開發的一站式HarmonyOS應用及元服務開發平臺&#xff0c;為開發者提供代碼開發、編譯構建以及調測等功能 2. 運行環境要求 操作系統&#xff1a;Windows10 64位、Windows11 64位 內存&#xff1a;16GB及以上 硬盤&…

PLC框架-1.3.2 報文750控制匯川伺服的轉矩上下限

本文介紹1200PLC如何使用750報文設定伺服轉矩的上下限。 750號報文 PLC---->伺服 (控制) 伺服--->PLC (狀態) PZD1

Redis知識集合---思維導圖(持續更新中)

一、Redis中常見的數據類型有哪些&#xff1f;二、Redis為什么這么快&#xff1f;三、為什么Redis設計為單線程&#xff1f;6.0版本為何引入多線程&#xff1f;四、

mac m1安裝大模型工具vllm

1 更新系統環境 參考vllm官網文檔&#xff0c;vllm對apple m1平臺mac os, xcoder, clang有如下要求 OS: macOS Sonoma or later SDK: XCode 15.4 or later with Command Line Tools Compiler: Apple Clang > 15.0.0 在App Store更新macOS和XCoder&#xff0c;依據XCoder版本…

解鎖localtime:使用技巧與避坑指南

目錄 一、引言 1.1 背景與目的 1.2 localtime 函數簡介 二、localtime 函數詳解 2.1 函數原型與參數 2.2 返回值與 tm 結構體 2.3 基本使用示例 三、localtime 函數的缺陷剖析 3.1 多次調用同一共享區間導致錯誤 3.1.1 問題現象展示 3.1.2 原因深入分析 3.1.3 實際影…

鄭州機械設計研究所 -PHM產品序列概覽

1.設備狀態監測系統 動態信號監測很像是三個獨立通道&#xff0c;振動&#xff0c;轉速&#xff0c;然后高頻的某個頻帶。或者是同一個振動信號做的低頻和高頻兩個帶通&#xff0c;時域和頻域組圖。實時檢測&#xff0c;很明顯是24個時 -頻指標。 動態分析看起來像趨勢圖。 2.…

《棒壘球知道》奧運會的吉祥物是什么·棒球1號位

Olympic Mascots & Baseball/Softball Games History ?&#xff08;奧運吉祥物與棒壘球賽事全科普&#xff09;1984洛杉磯奧運會 / Los Angeles 1984Mascot: Sam the Eagle&#xff08;山姆鷹&#xff09;美國精神象征&#xff0c;紅白藍配色超吸睛&#xff01;Baseball/S…

【提高篇-基礎知識與編程環境:1、Linux系統終端中常用的文件與目錄操作命令】

Linux終端提供了豐富的命令來操作文件和目錄&#xff0c;以下簡單介紹一些常用的命令&#xff1a; 一、目錄操作命令 pwd - 顯示當前工作目錄 pwd #輸出當前所在目錄的絕對路徑 cd - 切換目錄 cd /path/to/directory # 切換到指定目錄 cd … # …