Ultralytics:解读Conv2模块

前言
- 由于本人水平有限,难免出现错漏,敬请批评改正。
- 更多精彩内容,可点击进入Python日常小操作专栏、OpenCV-Python小应用专栏、YOLO系列专栏、自然语言处理专栏、人工智能混合编程实践专栏或我的个人主页查看
- YOLOs-CPP:一个免费开源的YOLO全系列C++推理库(以YOLO26为例)
- PaddleOCR:Win10上安装使用PPOCRLabel标注工具
- 目标检测:使用自己的数据集微调DEIMv2进行物体检测
- 图像分割:PyTorch从零开始实现SegFormer语义分割
- 图像超分:使用自己的数据集微调Real-ESRGAN-x4plus进行超分重建
- 图像生成:PyTorch从零开始实现一个简单的扩散模型
- Stable Diffusion:使用自己的数据集微调 Stable Diffusion 3.5 LoRA 文生图模型
- 图像超分:使用自己的数据集微调Real-ESRGAN-x2plus进行超分重建
- Anomalib:使用Anomalib 2.1.0训练自己的数据集进行异常检测
- Anomalib:在Linux服务器上安装使用Anomalib 2.1.0
- 人工智能混合编程实践:C++调用封装好的DLL进行异常检测推理
- 人工智能混合编程实践:C++调用封装好的DLL进行FP16图像超分重建(v3.0)
- 隔离系统Python:源码编译3.11.8到自定义目录(含PGO性能优化)
- 在线机的Python环境迁移到离线机上
- Nuitka 将 Python 脚本封装为 .pyd 或 .so 文件
- Ultralytics:使用 YOLO11 进行速度估计
- Ultralytics:使用 YOLO11 进行物体追踪
- Ultralytics:使用 YOLO11 进行物体计数
- Ultralytics:使用 YOLO11 进行目标打码
- 人工智能混合编程实践:C++调用Python ONNX进行YOLOv8推理
- 人工智能混合编程实践:C++调用封装好的DLL进行YOLOv8实例分割
- 人工智能混合编程实践:C++调用Python ONNX进行图像超分重建
- 人工智能混合编程实践:C++调用Python AgentOCR进行文本识别
- 通过计算实例简单地理解PatchCore异常检测
- Python将YOLO格式实例分割数据集转换为COCO格式实例分割数据集
- YOLOv8 Ultralytics:使用Ultralytics框架训练RT-DETR实时目标检测模型
- 基于DETR的人脸伪装检测
- YOLOv7训练自己的数据集(口罩检测)
- YOLOv8训练自己的数据集(足球检测)
- YOLOv5:TensorRT加速YOLOv5模型推理
- YOLOv5:IoU、GIoU、DIoU、CIoU、EIoU
- 玩转Jetson Nano(五):TensorRT加速YOLOv5目标检测
- YOLOv5:添加SE、CBAM、CoordAtt、ECA注意力机制
- YOLOv5:yolov5s.yaml配置文件解读、增加小目标检测层
- Python将COCO格式实例分割数据集转换为YOLO格式实例分割数据集
- YOLOv5:使用7.0版本训练自己的实例分割模型(车辆、行人、路标、车道线等实例分割)
- 使用Kaggle GPU资源免费体验Stable Diffusion开源项目
- Stable Diffusion:在服务器上部署使用Stable Diffusion WebUI进行AI绘图(v2.0)
- Stable Diffusion:使用自己的数据集微调训练LoRA模型(v2.0)
相关介绍
Ultralytics 简介
Ultralytics 基于多年的计算机视觉和人工智能基础研究,创建了最先进的 (SOTA) YOLO 模型。我们的模型不断更新性能和灵活性,快速、准确且易于使用。他们擅长对象检测、跟踪、实例分割、语义分割、图像分类和姿势估计任务。
前提条件
- 熟悉Python、Pytorch
实验环境
Package Version
------------------------ ------------
Python 3.11.8
absl-py 2.4.0
accelerate 1.13.0
annotated-doc 0.0.4
anyio 4.13.0
calflops 0.3.2
certifi 2026.4.22
charset-normalizer 3.4.7
click 8.3.3
colorama 0.4.6
contourpy 1.3.3
cycler 0.12.1
filelock 3.29.0
flatbuffers 25.12.19
fonttools 4.62.1
fsspec 2026.4.0
grpcio 1.80.0
h11 0.16.0
hf-xet 1.5.0
httpcore 1.0.9
httpx 0.28.1
huggingface_hub 1.14.0
idna 3.15
Jinja2 3.1.6
kiwisolver 1.5.0
Markdown 3.10.2
markdown-it-py 4.2.0
MarkupSafe 3.0.3
matplotlib 3.10.9
mdurl 0.1.2
ml_dtypes 0.5.0
mpmath 1.3.0
networkx 3.6.1
numpy 1.26.4
nvidia-cublas-cu12 12.8.3.14
nvidia-cuda-cupti-cu12 12.8.57
nvidia-cuda-nvrtc-cu12 12.8.61
nvidia-cuda-runtime-cu12 12.8.57
nvidia-cudnn-cu12 9.7.1.26
nvidia-cufft-cu12 11.3.3.41
nvidia-cufile-cu12 1.13.0.11
nvidia-curand-cu12 10.3.9.55
nvidia-cusolver-cu12 11.7.2.55
nvidia-cusparse-cu12 12.5.7.53
nvidia-cusparselt-cu12 0.6.3
nvidia-nccl-cu12 2.26.2
nvidia-nvjitlink-cu12 12.8.61
nvidia-nvtx-cu12 12.8.55
onnx 1.19.0
onnxruntime-gpu 1.26.0
onnxslim 0.1.94
opencv-python 4.6.0.66
packaging 26.2
pillow 12.2.0
pip 24.0
polars 1.40.1
polars-runtime-32 1.40.1
protobuf 7.34.1
psutil 7.2.2
pycocotools 2.0.11
Pygments 2.20.0
pyparsing 3.3.2
python-dateutil 2.9.0.post0
PyYAML 6.0.3
regex 2026.5.9
requests 2.34.1
rich 15.0.0
safetensors 0.7.0
scipy 1.16.0
setuptools 65.5.0
shellingham 1.5.4
six 1.17.0
sympy 1.14.0
tabulate 0.10.0
tensorboard 2.20.0
tensorboard-data-server 0.7.2
tokenizers 0.22.2
torch 2.7.1+cu128
torchaudio 2.7.1+cu128
torchvision 0.22.1+cu128
tqdm 4.67.3
transformers 5.8.1
triton 3.3.1
typer 0.25.1
typing_extensions 4.15.0
ultralytics 8.4.58
ultralytics-thop 2.0.19
urllib3 2.7.0
Werkzeug 3.1.8
Conv2
import cv2
import math
import torch
import numpy as np
import matplotlib.pyplot as plt
from torch import nn
def autopad(k, p=None, d=1): # kernel, padding, dilation
"""Pad to 'same' shape outputs."""
if d > 1:
k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
return p
class Conv(nn.Module):
"""Standard convolution module with batch normalization and activation.
Attributes:
conv (nn.Conv2d): Convolutional layer.
bn (nn.BatchNorm2d): Batch normalization layer.
act (nn.Module): Activation function layer.
default_act (nn.Module): Default activation function (SiLU).
"""
default_act = nn.SiLU() # default activation
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
"""Initialize Conv layer with given parameters.
Args:
c1 (int): Number of input channels.
c2 (int): Number of output channels.
k (int): Kernel size.
s (int): Stride.
p (int, optional): Padding.
g (int): Groups.
d (int): Dilation.
act (bool | nn.Module): Activation function.
"""
super().__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
self.bn = nn.BatchNorm2d(c2)
self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()
def forward(self, x):
"""Apply convolution, batch normalization and activation to input tensor.
Args:
x (torch.Tensor): Input tensor.
Returns:
(torch.Tensor): Output tensor.
"""
return self.act(self.bn(self.conv(x)))
def forward_fuse(self, x):
"""Apply convolution and activation without batch normalization.
Args:
x (torch.Tensor): Input tensor.
Returns:
(torch.Tensor): Output tensor.
"""
return self.act(self.conv(x))
class Conv2(Conv):
"""Simplified RepConv module with Conv fusing.
Attributes:
conv (nn.Conv2d): Main 3x3 convolutional layer.
cv2 (nn.Conv2d): Additional 1x1 convolutional layer.
bn (nn.BatchNorm2d): Batch normalization layer.
act (nn.Module): Activation function layer.
"""
def __init__(self, c1, c2, k=3, s=1, p=None, g=1, d=1, act=True):
"""Initialize Conv2 layer with given parameters.
Args:
c1 (int): Number of input channels.
c2 (int): Number of output channels.
k (int): Kernel size.
s (int): Stride.
p (int, optional): Padding.
g (int): Groups.
d (int): Dilation.
act (bool | nn.Module): Activation function.
"""
super().__init__(c1, c2, k, s, p, g=g, d=d, act=act)
self.cv2 = nn.Conv2d(c1, c2, 1, s, autopad(1, p, d), groups=g, dilation=d, bias=False) # add 1x1 conv
def forward(self, x):
"""Apply convolution, batch normalization and activation to input tensor.
Args:
x (torch.Tensor): Input tensor.
Returns:
(torch.Tensor): Output tensor.
"""
return self.act(self.bn(self.conv(x) + self.cv2(x)))
def forward_fuse(self, x):
"""Apply fused convolution, batch normalization and activation to input tensor.
Args:
x (torch.Tensor): Input tensor.
Returns:
(torch.Tensor): Output tensor.
"""
return self.act(self.bn(self.conv(x)))
def fuse_convs(self):
"""Fuse parallel convolutions."""
w = torch.zeros_like(self.conv.weight.data)
i = [x // 2 for x in w.shape[2:]]
w[:, :, i[0] : i[0] + 1, i[1] : i[1] + 1] = self.cv2.weight.data.clone()
self.conv.weight.data += w
self.__delattr__("cv2")
self.forward = self.forward_fuse
使用示例

if __name__ == '__main__':
# 1. 读取图像(请将路径改为您自己的图片)
img_path = "cat_640x640.png" # 或者使用绝对路径
img_bgr = cv2.imread(img_path)
if img_bgr is None:
raise FileNotFoundError(f"图片 {img_path} 不存在!请检查路径。")
# 2. 转换为 RGB,并转为 PyTorch 张量 (H,W,C) -> (C,H,W) -> (1,C,H,W)
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
img_tensor = torch.from_numpy(img_rgb).float() # (H, W, C)
img_tensor = img_tensor.permute(2, 0, 1).unsqueeze(0) # (1, C, H, W)
# 3. 创建 Conv2 层:输入3通道,输出16通道,主核3×3,步长2(自动same padding)
conv2_layer = Conv2(c1=3, c2=16, k=3, s=2)
# 4. 前向传播(未融合,双分支)
with torch.no_grad():
out_before = conv2_layer(img_tensor)
print("未融合输出形状:", out_before.shape) # torch.Size([1, 16, 320, 320])
# 5. 可视化未融合的特征图(第一个通道)
feat_map_before = out_before[0, 0, :, :].cpu().numpy()
feat_map_before = (feat_map_before - feat_map_before.min()) / (feat_map_before.max() - feat_map_before.min() + 1e-8)
feat_map_before = (feat_map_before * 255).astype(np.uint8)
# 6. 融合分支(将1x1卷积分支合并到3x3主卷积)
conv2_layer.fuse_convs()
print("融合后,cv2 属性已删除,forward 已切换为 forward_fuse")
# 7. 融合后的前向传播(单分支)
with torch.no_grad():
out_after = conv2_layer(img_tensor)
print("融合后输出形状:", out_after.shape) # 同样为 torch.Size([1, 16, 320, 320])
# 8. 验证融合前后输出是否一致(数值误差应接近于0)
diff = torch.abs(out_before - out_after).max().item()
print(f"融合前后最大绝对差异: {diff:.6f}") # 通常 < 1e-6
# 9. 可视化融合后的特征图(第一个通道),并与融合前对比
feat_map_after = out_after[0, 0, :, :].cpu().numpy()
feat_map_after = (feat_map_after - feat_map_after.min()) / (feat_map_after.max() - feat_map_after.min() + 1e-8)
feat_map_after = (feat_map_after * 255).astype(np.uint8)
# 10. 绘制对比图(原图、融合前特征图、融合后特征图、差异图)
plt.figure(figsize=(15, 5))
plt.subplot(1, 4, 1)
plt.imshow(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))
plt.title("Original Image")
plt.axis("off")
plt.subplot(1, 4, 2)
plt.imshow(feat_map_before, cmap='gray')
plt.title("Before Fuse (Ch0)")
plt.axis("off")
plt.subplot(1, 4, 3)
plt.imshow(feat_map_after, cmap='gray')
plt.title("After Fuse (Ch0)")
plt.axis("off")
# 差异图(放大显示差异)
diff_map = np.abs(feat_map_before.astype(np.float32) - feat_map_after.astype(np.float32))
plt.subplot(1, 4, 4)
plt.imshow(diff_map, cmap='hot')
plt.title("Difference (×10)")
plt.axis("off")
plt.tight_layout()
plt.savefig("conv2_output_visualization.png", dpi=150)
# 若有图形界面,可取消下面一行的注释以显示窗口
# plt.show()
print("可视化图片已保存为 conv2_output_visualization.png")

未融合输出形状: torch.Size([1, 16, 320, 320])
融合后,cv2 属性已删除,forward 已切换为 forward_fuse
融合后输出形状: torch.Size([1, 16, 320, 320])
融合前后最大绝对差异: 0.000006
可视化图片已保存为 conv2_output_visualization.png
流程示意图

代码解读
Conv2(简化版 RepConv)
Conv2 是 Conv 的增强版本,它在标准 3×3 卷积的基础上,并行添加了一个 1×1 卷积分支,并将两者输出相加后再过 BN 和激活函数。这种设计借鉴了 RepVGG 的思想,能在训练时提高模型表达能力,而在推理时通过“融合”将两个分支合并为单个 3×3 卷积,从而不增加额外计算量。
功能
- 训练阶段:采用 3×3 卷积 + 1×1 卷积 并行结构,两者输出相加后通过 BN 和激活函数,增强特征表达能力。
- 推理阶段:通过
fuse_convs()将 1×1 分支的权重叠加到 3×3 卷积核的中心位置,从而将双分支合并为单个 3×3 卷积,且无需增加额外计算量,实现无损加速。
初始化参数
与 Conv 完全相同,但额外创建了一个 self.cv2(1×1 卷积),其参数(输入/输出通道、步长、分组、膨胀等)与主卷积保持一致。
| 参数 | 说明 |
|---|---|
c1 | 输入通道数 |
c2 | 输出通道数 |
k | 主卷积核大小(默认 3) |
s | 步长(两个分支共用) |
p | 填充(若为 None 自动计算) |
g | 分组数(两个分支共用) |
d | 膨胀率(两个分支共用) |
act | 激活函数(与 Conv 一致) |
关键属性
self.conv:继承自父类的 3×3(或指定大小)主卷积。self.cv2:新增的 1×1 卷积分支(无 BN,因为它与主分支共享同一个 BN)。self.bn:共享的批归一化层(对两个分支的和进行归一化)。self.act:共享的激活函数。
前向方法
| 方法 | 流程 | 用途 |
|---|---|---|
forward(x) | act(bn(conv(x) + cv2(x))) | 训练或未融合推理(双分支) |
forward_fuse(x) | act(bn(conv(x))) | 融合后仅使用主卷积(已剥离 cv2) |
融合机制(fuse_convs 详解)
数学原理
设主卷积核为 W 3 ∈ R C o u t × C i n × 3 × 3 W_3 \in \mathbb{R}^{C_{out} \times C_{in} \times 3 \times 3} W3∈RCout×Cin×3×3,1×1 卷积核为 W 1 ∈ R C o u t × C i n × 1 × 1 W_1 \in \mathbb{R}^{C_{out} \times C_{in} \times 1 \times 1} W1∈RCout×Cin×1×1。
由于 1×1 卷积等价于 3×3 卷积核中心位置的权重,其余位置为 0,因此可将 W 1 W_1 W1 填充到 3×3 尺寸:
W
f
u
s
e
d
=
W
3
+
pad_to_3x3
(
W
1
)
W_{fused} = W_3 + \text{pad\_to\_3x3}(W_1)
Wfused=W3+pad_to_3x3(W1)
其中填充操作将
W
1
W_1
W1 放在 3×3 核的正中央(索引 [1,1] 处)。
实现步骤
- 创建一个与主卷积权重形状相同的零张量
w。 - 计算中心位置索引
i = [kh//2, kw//2](假设核大小为 3,则i=[1,1])。 - 将
self.cv2.weight克隆后赋值到w[:, :, i[0]:i[0]+1, i[1]:i[1]+1]。 - 将
w加到主卷积权重上:self.conv.weight.data += w。 - 删除
self.cv2属性(释放内存)。 - 将
forward方法重定向为forward_fuse,使后续前向只走单分支。
注意:由于两个分支共享同一个 BN,融合后 BN 参数不变(因为卷积层输出相加后归一化等价于融合后卷积再归一化,两者数学上完全等价)。
设计意图与优势
- 训练增益:多分支结构提供更多梯度路径,有助于优化,提升模型精度。
- 推理无损:融合后等价于单卷积,推理速度与
Conv相同,无额外开销。 - 简化实现:相比完整的 RepVGG(包含 3×3、1×1、identity 三支),
Conv2去除了 identity 分支(等价于 1×1 的特殊情况),实现更简洁,但仍具备核心的融合能力。
注意事项
- 膨胀卷积的支持:若
d > 1,1×1 卷积的膨胀率设置为d虽然无实际意义(因为核大小为1),但为保持一致性,代码仍传入了d,不影响结果。 - 分组卷积:
g参数同时作用于两个分支,分组数必须能整除c1和c2,否则会报错。 - 融合时机:应在推理前调用
fuse_convs(),且不要再进行训练,因为融合后cv2被删除,无法反向传播更新其梯度。 - 与父类
Conv的关系:Conv2在初始化时调用了super().__init__(...),因此继承了conv、bn、act等属性,并额外添加cv2。
在 YOLOv8 中的应用
Conv2 并非 Ultralytics 官方 YOLOv8 的默认组件(官方使用标准 Conv),但它可用于自定义网络结构,例如在骨干网络中加入 RepVGG 风格模块,以提升精度-速度权衡。您可以将其替换 YAML 配置中的 Conv 项,或作为独立模块插入到 C2f 等容器中。
优缺点
优点
-
训练时表达能力强
并行使用 3×3 和 1×1 双分支,梯度流更丰富,相当于隐式集成,有助于提升模型精度,可视为一种结构重参数化(RepVGG 思想)。 -
推理无额外开销
通过fuse_convs()可将两个分支无损融合为单个 3×3 卷积,推理时速度与标准卷积完全一致,不增加任何计算或显存成本。 -
灵活性高
可随时按需融合或保持双分支,方便在训练和部署之间切换,适应不同场景(如训练时用多分支,推理时用单分支)。 -
兼容性强
继承自Conv,可以无缝替换原有模块,无需改动网络结构,即插即用。
缺点
-
训练时显存和计算翻倍
前向和反向传播需同时计算两个卷积,训练时 FLOPs 约增加 1.1 倍(3×3 + 1×1),显存占用也相应增加,对资源有一定要求。 -
实现复杂度较高
融合逻辑涉及权重拷贝和__delattr__操作,可能引发代码维护难度;若操作不当(如在融合后继续训练)会导致梯度计算错误。 -
仅适用于特定场合
当步长s>1或采用膨胀卷积时,1×1 分支的“对齐”方式需额外注意,融合公式略有不同(但代码已考虑),但实际应用时需验证等效性。 -
对 BN 层共享依赖
两个分支共用同一个 BN,融合后 BN 统计量需保持不变,这种设计虽然简洁,但限制了分支数(如不能轻易加入 identity 分支)。
参考文献
[1] https://docs.ultralytics.com/
[2] https://github.com/ultralytics/ultralytics.git
- 由于本人水平有限,难免出现错漏,敬请批评改正。
- 更多精彩内容,可点击进入Python日常小操作专栏、OpenCV-Python小应用专栏、YOLO系列专栏、自然语言处理专栏、人工智能混合编程实践专栏或我的个人主页查看
- YOLOs-CPP:一个免费开源的YOLO全系列C++推理库(以YOLO26为例)
- PaddleOCR:Win10上安装使用PPOCRLabel标注工具
- 目标检测:使用自己的数据集微调DEIMv2进行物体检测
- 图像分割:PyTorch从零开始实现SegFormer语义分割
- 图像超分:使用自己的数据集微调Real-ESRGAN-x4plus进行超分重建
- 图像生成:PyTorch从零开始实现一个简单的扩散模型
- Stable Diffusion:使用自己的数据集微调 Stable Diffusion 3.5 LoRA 文生图模型
- 图像超分:使用自己的数据集微调Real-ESRGAN-x2plus进行超分重建
- Anomalib:使用Anomalib 2.1.0训练自己的数据集进行异常检测
- Anomalib:在Linux服务器上安装使用Anomalib 2.1.0
- 人工智能混合编程实践:C++调用封装好的DLL进行异常检测推理
- 人工智能混合编程实践:C++调用封装好的DLL进行FP16图像超分重建(v3.0)
- 隔离系统Python:源码编译3.11.8到自定义目录(含PGO性能优化)
- 在线机的Python环境迁移到离线机上
- Nuitka 将 Python 脚本封装为 .pyd 或 .so 文件
- Ultralytics:使用 YOLO11 进行速度估计
- Ultralytics:使用 YOLO11 进行物体追踪
- Ultralytics:使用 YOLO11 进行物体计数
- Ultralytics:使用 YOLO11 进行目标打码
- 人工智能混合编程实践:C++调用Python ONNX进行YOLOv8推理
- 人工智能混合编程实践:C++调用封装好的DLL进行YOLOv8实例分割
- 人工智能混合编程实践:C++调用Python ONNX进行图像超分重建
- 人工智能混合编程实践:C++调用Python AgentOCR进行文本识别
- 通过计算实例简单地理解PatchCore异常检测
- Python将YOLO格式实例分割数据集转换为COCO格式实例分割数据集
- YOLOv8 Ultralytics:使用Ultralytics框架训练RT-DETR实时目标检测模型
- 基于DETR的人脸伪装检测
- YOLOv7训练自己的数据集(口罩检测)
- YOLOv8训练自己的数据集(足球检测)
- YOLOv5:TensorRT加速YOLOv5模型推理
- YOLOv5:IoU、GIoU、DIoU、CIoU、EIoU
- 玩转Jetson Nano(五):TensorRT加速YOLOv5目标检测
- YOLOv5:添加SE、CBAM、CoordAtt、ECA注意力机制
- YOLOv5:yolov5s.yaml配置文件解读、增加小目标检测层
- Python将COCO格式实例分割数据集转换为YOLO格式实例分割数据集
- YOLOv5:使用7.0版本训练自己的实例分割模型(车辆、行人、路标、车道线等实例分割)
- 使用Kaggle GPU资源免费体验Stable Diffusion开源项目
- Stable Diffusion:在服务器上部署使用Stable Diffusion WebUI进行AI绘图(v2.0)
- Stable Diffusion:使用自己的数据集微调训练LoRA模型(v2.0)


1116

被折叠的 条评论
为什么被折叠?



