简介:YOLOv5/v7/v8/v10算法网络结构图原型文件,是一份面向目标检测初学者、算法研究者和论文写作者的PPTX绘图素材,集中呈现YOLO系列四个主流版本的网络拓扑与模块组成。整套资源仅1个PPTX文件,压缩包约112KB,结构图以可编辑形状绘制,既能直接用于课堂汇报、组会展示,也可灵活修改模块名称与连接线,适配个人讲解习惯。已有516人学习浏览,常用于课程设计、算法对比和论文插图场景。内容清晰标出主干网络、Neck网络与输出端,并逐一标注YOLOv5的C3与SPPF、YOLOv7的ELAN与SPPCSPC、YOLOv8的C2f、YOLOv10的SCDown与PSA等关键模块,同时体现特征图尺寸变化(P1/2~P5/32)和检测层输出。尤其适合用来梳理从YOLOv5到YOLOv10的演进脉络,理解残差连接、多尺度特征融合、空间金字塔池化与注意力机制等核心设计,是一份可直接复用的算法结构速查与绘图底稿。
1. 结构图是最快理解 YOLO 家族演进的入口
大多数人在初次接触 YOLOv5、v7、v8、v10 时,第一反应是去读论文或者直接 clone 官方仓库跑训练。但真正上手后会发现,只看源码里的 yaml 配置文件很难建立对整体数据流的直觉——尤其是当你想修改 Backbone 层或者调整 Neck 的融合策略时,很容易因为搞不清某一层的输入输出而改出形状不匹配的 bug。这套 YOLOv5/v7/v8/v10 算法网络结构图原型文件的价值就在这里:它把每个版本的完整网络结构按主干网络、Neck 网络、输出端三个区域拆开,标注了每一层的卷积核尺寸、步长、特征图尺寸变化以及 Detect 头的输出尺寸,适合需要做模型结构对比、写论文示意图、做课程设计或者第一次接触 YOLO 系列源码的开发者。我自己的习惯是,先对着结构图把每一层的通道数和特征图分辨率算一遍,再回头看 yaml 配置文件,阅读效率会高不少。
2. 通道数与步长的设计逻辑:YOLOv5 和 v7 的主干拆解
YOLOv5s 和 YOLOv7 是两组风格差异明显的结构。YOLOv5s 走的是经典的卷积加 C3 模块堆叠路线,而 v7 引入了 ELAN 和 MP 结构。同为两阶段下采样策略,两者在特征复用方式上完全不同,值得逐层对照着看。
2.1 Focus 与早期下采样的取舍
YOLOv5s 的输入层到 P1 阶段采用的是 Conv[6,2],这是一个 6x6 卷积核配合 stride=2 的下采样卷积。在更早版本的 YOLOv5 中,这个位置使用的是 Focus 模块,通过切片操作把 640x640x3 的输入变成 320x320x12,再用 3x3 卷积调整通道数。后续版本把 Focus 替换成 6x6 卷积,本质上是把切片加卷积的操作融合成一个标准卷积层,减少了模块数量,同时保持了相同的下采样倍率和感受野扩展速度。
# 用 PyTorch 模拟 YOLOv5s 的 Stem 层设计逻辑 import torch.nn as nn class StemConv(nn.Module): def __init__(self, in_channels=3, out_channels=64, kernel_size=6, stride=2): super().__init__() self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding=kernel_size // 2, bias=False) self.bn = nn.BatchNorm2d(out_channels) self.act = nn.SiLU(inplace=True) def forward(self, x): return self.act(self.bn(self.conv(x))) # 输入 640x640x3,经过 StemConv 后输出 320x320x64 stem = StemConv()这段代码对应结构图中的Conv[6,2]标记。注意padding=kernel_size // 2这个细节——kernel_size 为 6 时 padding 取 3,保证输出特征图尺寸严格按照 640/2=320 进行减半。相比 3x3 卷积,6x6 卷积在步长 2 的情况下能用更少的层数覆盖更大的感受野,这也是 YOLOv5 后续版本作者反复调整的 Stem 设计点。
2.2 C3 模块中的 True 与 False 到底差在哪
结构图中 YOLOv5 的 C3 模块标记了C3 True和C3 False两种状态。True 表示该 C3 模块内部的 Bottleneck 分支带有残差连接,False 表示不带。残差连接的开关直接影响梯度的传播路径:在主干网络的深层位置使用 True,可以让梯度通过 shortcut 直接回传到浅层,缓解深层网络的梯度消失问题;而在 Neck 区域的特征融合阶段使用 False,是因为这里的主要任务是融合不同尺度的语义信息和位置信息,残差连接反而可能让特征过度平滑。
class Bottleneck(nn.Module): def __init__(self, in_channels, out_channels, shortcut=True): super().__init__() self.cv1 = nn.Conv2d(in_channels, out_channels, 1, 1, bias=False) self.cv2 = nn.Conv2d(out_channels, out_channels, 3, 1, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) self.bn2 = nn.BatchNorm2d(out_channels) self.act = nn.SiLU(inplace=True) self.shortcut = shortcut and in_channels == out_channels def forward(self, x): identity = x out = self.act(self.bn1(self.cv1(x))) out = self.bn2(self.cv2(out)) out = self.act(out) return out + identity if self.shortcut else out这段代码展示了C3 True和C3 False的唯一差别:shortcut参数是否为 True。当结构图中标注 C3 True 时,意味着该层输出等于out + identity;标注 C3 False 时则直接返回out。在修改 YOLOv5 结构做消融实验时,这个参数是最容易调整且影响最直观的实验变量之一。
2.3 YOLOv7 的 ELAN 与 MP 结构
YOLOv7 的主干网络一开始就采用Conv[3,1]和Conv[3,2]组合,不再使用 YOLOv5 中的 6x6 卷积。之后的 ELAN 模块设计思路是:输入先经过一个 1x1 卷积进行通道压缩,然后分出多条分支,每条分支用不同数量的 3x3 卷积提取特征,最后在通道维度上拼接。这样做的好处是每条分支的感受野不同,拼接后的特征图包含多尺度信息。
# ELAN 模块的核心分支逻辑 class ELAN(nn.Module): def __init__(self, in_channels, out_channels, num_branches=4): super().__init__() self.cv1 = nn.Conv2d(in_channels, out_channels // 2, 1, 1, bias=False) self.branches = nn.ModuleList() for i in range(num_branches): self.branches.append( nn.Sequential( nn.Conv2d(out_channels // 2, out_channels // 2, 3, 1, padding=1, bias=False), nn.BatchNorm2d(out_channels // 2), nn.SiLU(inplace=True) ) ) def forward(self, x): x = self.cv1(x) outputs = [x] for branch in self.branches: outputs.append(branch(outputs[-1])) return torch.cat(outputs, dim=1)注意这段代码是 ELAN 模块的简化示意,实际 YOLOv7 实现中分支数不是固定的 4,而是 2 到 3 条主分支配合不同的卷积深度。MP 模块则是在 ELAN 后面接了一个 maxpool 和一个 stride=2 的卷积,两者并行后拼接,实现特征图尺寸减半的同时保持通道数增长。结构图中你能看到 MP1、MP2 这样的标记,指的就是不同阶段的 MP 配置,实际上 MP 本身也是一个可学习的下采样模块,不单纯是池化操作。
3. C2f、SCDown 与 PSA:v8/v10 对特征复用方式的改造
YOLOv8 和 v10 在结构上继承了 v5/v7 的许多设计思想,但把 C3 换成了 C2f,把集中的检测头换成了解耦头,v10 还引入了 SCDown 做下采样。这一章重点说清这些模块的变化逻辑。
3.1 C2f 与 C3 的参数差异和梯度流动
YOLOv8 的 C2f 模块本质上是对 YOLOv7 中 ELAN 思路的进一步简化。C2f 的输入先经过 1x1 卷积扩展通道,然后分成两条路径:一条直接连接到输出,另一条经过多个 Bottleneck 串行提取特征,最后把中间所有 Bottleneck 的输出和第一条路径的结果全部在通道维度拼接。结构图中C2f [n=3],C2f [n=6]这类标记里的 n 就是 Bottleneck 的堆叠数量。
class C2f(nn.Module): def __init__(self, in_channels, out_channels, n=1, shortcut=True): super().__init__() self.cv1 = nn.Conv2d(in_channels, out_channels * 2, 1, 1, bias=False) self.cv2 = nn.Conv2d((n + 2) * out_channels // 2, out_channels, 1, 1, bias=False) self.m = nn.ModuleList( [Bottleneck(out_channels // 2, out_channels // 2, shortcut) for _ in range(n)] ) def forward(self, x): y = list(self.cv1(x).chunk(2, 1)) # 切分为两条路径 y.extend(m(y[-1]) for m in self.m) # 每个 Bottleneck 的输出都保留 return self.cv2(torch.cat(y, 1))这段代码对应结构图中C2f [n=3]的展开。y.extend(m(y[-1]) for m in self.m)这一行的含义是,每个 Bottleneck 的输出都会拼接到最终结果中,这使得梯度可以直接从后往前的每一层流动,类似 DenseNet 的连接方式,但实现成本比 DenseNet 低很多。这也是 v8 相比 v5 在相近参数量下精度更高的原因之一。
在修改 YOLOv8 网络结构时,调整 n 是控制计算量的最直接手段。结构图中C2f [512]和C2f [256]这类标记指的是输出通道数,而C2f [n=3]指的是 Bottleneck 个数。两者在 yaml 文件中的位置不同,效果也不同——前者影响特征宽度,后者影响网络深度。
3.2 SCDown:YOLOv10 对 stride=2 卷积的重新包装
YOLOv10 结构图中多次出现SCDown[k3,s2],这是对普通 stride=2 卷积的改进。普通下采样卷积直接对整张特征图做卷积,计算量随通道数线性增长。SCDown 的思路是先用一个 1x1 卷积把通道数降下来,再对降维后的特征执行 stride=2 的 3x3 卷积,最后用 1x1 卷积把通道数恢复回去。
class SCDown(nn.Module): def __init__(self, in_channels, out_channels, kernel_size=3, stride=2): super().__init__() self.cv1 = nn.Conv2d(in_channels, out_channels // 2, 1, 1, bias=False) self.cv2 = nn.Conv2d(out_channels // 2, out_channels, kernel_size, stride, padding=kernel_size // 2, bias=False) self.bn = nn.BatchNorm2d(out_channels) self.act = nn.SiLU(inplace=True) def forward(self, x): x = self.act(self.bn(self.cv2(self.cv1(x)))) return x为什么结构图里 v10 主干的 P3、P4 阶段都使用 SCDown 而不是直接让 C2f 模块负责下采样?直接原因是解耦:C2f 专注于特征提取和融合,SCDown 专注于空间尺寸缩减,这种职责分离让每一层的行为更易预测,也方便对某一模块单独做剪枝或者量化。从 FLOPs 的角度看,SCDown 的第一层 1x1 卷积把通道数减半后,第二层 3x3 卷积的乘加运算量也减半,整体计算开销相比普通 stride=2 卷积降低约 25%。
3.3 CIB 与 PSA:v10 检测头前的注意力机制
结构图输出端附近有两个值得注意的模块:CIB 和 PSA。CIB 是一个紧凑的倒残差卷积块,结构图上标注为C2fCIB True,它用于在 v10Detect 之前对融合后的特征做最后一轮提炼。PSA 则是位置敏感注意力模块,结构图中能看到它在 P3/P4/P5 三层都出现,紧跟 SPPF 之后。
PSA 的工作流程是:输入特征经过 1x1 卷积后分成两路,一路直接保留,另一路经过自注意力计算位置权重,最后两路拼接并由 1x1 卷积融合。这样做的目标是让模型对目标位置更敏感,尤其是在遮挡和小目标场景下,效果比纯通道注意力更明显。如果你在训练 YOLOv10 时发现小目标的召回率偏低,优先排查 PSA 模块是否被误删或通道数设置过小。
下面这张表汇总了四个版本的核心模块差异,适合初学时做快速对照:
| 版本 | 下采样方式 | 特征提取模块 | 注意力机制 | 检测头类型 |
|---|---|---|---|---|
| YOLOv5s | Conv[6,2] | C3(Bottleneck) | 无 | 耦合头(Conv2d×3) |
| YOLOv7 | Conv[3,2] | ELAN / ELAN-H | 无 | 耦合头(RepConv) |
| YOLOv8 | Conv[k3,s2] | C2f(Bottleneck×n) | 无 | 解耦头(分类+回归) |
| YOLOv10 | SCDown[k3,s2] | C2f + CIB | PSA | v10Detect(解耦+one-to-many/one-to-one) |
3.4 输出端的通道数映射
YOLOv5 的结构图输出端标记了三个 Detect 层,分别输出 20x20、40x40、80x80 的特征图。每个 Detect 头内部是 Conv2d 加 reshape 操作,输出维度为(num_anchors, num_classes + 5, H, W)。v8 的解耦头把分类和回归分开,输出维度分别为(num_classes, H, W)和(4 * reg_max, H, W)——这里的reg_max是 Distribution Focal Loss 中使用的离散化区间个数,默认是 16。v10 在 v8 的基础上增加了 one-to-many 和 one-to-one 两组输出,前者用于训练,后者用于推理,这也是 v10 结构图中v10Detect与 v8 的 Head 在通道数上明显不同的原因。
4. 把结构图变成训练依据:yaml 文件与 FLOPs 核查
光能看懂结构图还不够,关键是如何把图里的信息映射到实际工程的 yaml 配置中,以及如何验证自己画的图没有算错。这一章给出具体的对照方法和验证脚本。
4.1 从结构图逆向生成 yaml 配置的模板
YOLOv8 的模型配置文件采用嵌套列表定义结构,每个列表的第一个元素是模块名,第二个是参数字典。对照结构图修改配置是一门基本功。以C2f [512]为例,在 yaml 中对应[-1, 512, C2f, 3]或类似形式,含义是输入来自上一层(-1 表示上一层的输出),输出通道数为 512,模块类型为 C2f,重复次数为 3。
每次改完结构图或者 yaml 配置,我都建议用下面这段脚本验证模型能否正常构建,并且打印每层的输出形状:
# 验证修改后的 yaml 配置是否合法 import torch from ultralytics import YOLO model = YOLO("yolov8n.yaml") dummy = torch.randn(1, 3, 640, 640) outputs = model.model(dummy) for i, out in enumerate(outputs): # 训练模式下返回三尺度特征 if isinstance(out, list): print(f"Detect head {i}:", [tuple(t.shape) for t in out]) else: print(f"Layer {i}:", tuple(out.shape))这段代码用来验证你配置的通道数是否匹配。注意如果输出的是一个长度为 3 的列表,并且每个元素都是[1, 4 + num_classes, 8400]这样的形状,说明 Neck 部分的上采样和 Concat 操作没有把通道数配错,模型能正常前向传播。
4.2 用结构图核对 FLOPs 与参数量是否合理
结构图中每个模块的通道数和层数都已知,可以手动估算每一层的 FLOPs。以 YOLOv5s 的Conv[3,2]为例,卷积核大小为 3x3,输入通道 64,输出通道 128,输入特征图尺寸 160x160,则单层 FLOPs 约为3x3x64x128x160x160,即约 1.88 GFLOPs,再加上 BN 和激活函数的开销,最终和实际值会有一点偏差,但数量级不会错。
yolo model= yolov8n.yaml verbose=True运行上述命令可以在终端看到每个模块的参数量和每秒浮点运算次数(FLOPs)。如果输出的 Total FLOPs 和你在结构图上手动计算的结果相差超过 15%,基本可以判断是某个模块的 stride 或通道数配错了。常见的错误情况是,在某个下采样阶段多写了一个 stride=2 的卷积,导致特征图分辨率降得过早,后续 Concat 操作直接报出形状不匹配的错误。
4.3 从图到代码:一个结构修改的完整示例
比如我想给 YOLOv8n 的 C2f 模块加一个通道注意力分支,结构图在原有 P4 位置的C2f [512]后面插入一个新的 SE 模块。操作步骤是:先在结构图上画出新的虚线框和输入输出线,然后在 yaml 的对应位置插入一行[-1, 512, SE, 16],最后在项目源码里定义一个 SE 模块。
class SE(nn.Module): def __init__(self, channels, reduction=16): super().__init__() self.pool = nn.AdaptiveAvgPool2d(1) self.fc = nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplace=True), nn.Linear(channels // reduction, channels), nn.Sigmoid() ) def forward(self, x): b, c, _, _ = x.size() scale = self.fc(self.pool(x).view(b, c)).view(b, c, 1, 1) return x * scale.expand_as(x)插入后跑一次前向传播验证形状。这里的reduction=16是通道压缩比例,如果通道数设置过小,SE 模块的参数量甚至可以忽略不计,但收敛速度可能会变化。每次对结构做调整,都建议先在小型数据集(比如 VOC 子集)上训练 10 到 20 个 epoch,观察损失曲线是否正常下降,再决定是否值得全量训练。
5. 用原型文件做结构验证:番外排查方法
结构图原型文件除了充当学习资料,直接拿来排查训练异常也很有用。这里给出我在 YOLO 系列训练和部署中经常使用的三个排查技巧,都是基于结构图或结构数据反推的。
5.1 根据特征图分辨率快速定位 Neck 层通道失配
如果训练时在某个 epoch 突然报出Shape mismatch错误,先别急着看堆栈信息,直接打开结构图,检查报错的位置对应的那条 Concat 分支。YOLOv5 到 v10 的 Neck 结构基本都是上采样加 Concat 模式,Concat 要求两个分支的通道数不一致时会被 1x1 卷积调整,但空间尺寸必须一致。结构图里每一行标出了特征图尺寸(20x20、40x40、80x80),你只需要检查 Concat 的两个输入是否来自同一尺寸的特征图层。
5.2 根据结构图算出每层的感受野,验证小目标检测能力
感受野的计算公式是RF_{n} = RF_{n-1} + (k-1) \* stride_{n}(从后往前迭代)。结构图中只要有每层的卷积核尺寸和步长,就能手算每一层的感受野。以 YOLOv8 为例,输入层到 P3 输出的累计步长为 8,P3 输出的感受野实际上是(4+1) \* 8 - 3左右,这个数值决定了模型对多大尺寸的目标最敏感。
如果结构图中 P3 分支的感受野仍然偏大,说明当前模型在极小目标上存在先天劣势,此时期望通过调参解决是不现实的,正确做法是给主干网络增加一个 stride=4 的预测分支,或者修改 P2 层的输出通道数,让模型保留更高分辨率的特征图。
5.3 用扁平化结构图来辅助剪枝和量化
YOLOv8/yolov5 的剪枝通常基于 BN 层的 gamma 系数,但如何选择剪枝层则需要参考结构图。具体做法是:把结构图中的每一层编号和当前模型中各层一一对应,然后统计每个 C2f 内部 Bottleneck 的 gamma 均值。剪掉 gamma 均值为0.1以下的通道,保留大于0.5的通道,按结构图逐层操作,能最大程度减少精度损失。
结构图中从 0 到 23 的编号可以作为剪枝脚本的索引依据,避免误删关键层。大部分开源剪枝代码都需要传入一个exclude_layers参数,把结构图中的 Detect 头和 SPPF 模块编入排除名单,这两个位置一旦剪枝,精度下降会非常显著且难以恢复。
本文还有配套的精品资源,点击获取