[{"content":"","date":"2026年05月27日","externalUrl":null,"permalink":"/Owen-Studio/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"2026年05月27日","externalUrl":null,"permalink":"/Owen-Studio/","section":"Owen Studio","summary":"","title":"Owen Studio","type":"page"},{"content":"","date":"2026年05月27日","externalUrl":null,"permalink":"/Owen-Studio/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"2026年05月27日","externalUrl":null,"permalink":"/Owen-Studio/tags/resnet/","section":"Tags","summary":"","title":"ResNet","type":"tags"},{"content":" 前言 # 如果说卷积神经网络（CNN）在 2012 年因为 AlexNet 而真正走进大众视野，那么 2015 年的 ResNet，就是把\u0026quot;深度学习\u0026quot;这四个字里的\u0026quot;深度\u0026quot;二字真正立住了的那一块基石。在它出现之前，神经网络做到二十几层已经被认为是相当深的结构；在它之后，上百层的网络也能够平稳的训练。而ResNet提出的\u0026quot;残差连接\u0026quot;也从一个具体的工程技巧，逐渐演化为现代深度学习里几乎无处不在的默认配置。从 Transformer 到 Diffusion，从 LLM 到多模态，你都能在结构图里找到它的身影。\n这一篇博客我将分三个部分展开叙述：第一部分追溯它的诞生背景，看看当时整个 CV 界的“深度瓶颈”，ResNet 又是用什么方式把这个问题解决的；第二部分用 PyTorch 实现一个最简单的 ResNet，并做一个小消融实验来对“残差连接”这一结构进行验证；第三部分则将会跳出 ResNet 本身，聊聊\u0026quot;残差思想\u0026quot;是怎样突破传统CV、最终成为整个深度学习领域共用的结构语言。\nResNet 的诞生 # 背景 # ResNet 由微软亚洲研究院的 He Kaiming、Zhang Xiangyu、Ren Shaoqing 和 Sun Jian 于 2015 年提出，论文为《Deep Residual Learning for Image Recognition》，并最终斩获 CVPR 2016 的最佳论文奖（Best Paper Award）。同年，凭借 ResNet 这一武器，作者团队在 ILSVRC 2015 图像分类、检测、定位以及 COCO 检测、分割五个赛道上全部夺冠，ImageNet 分类 top-5 错误率被压到了 3.57%，第一次低于人类水平（约 5.1%）。\n要理解 ResNet 解决了什么问题，首先得把镜头拉回 2014 年。彼时 AlexNet（2012，8 层）已经过去近两年，VGG（2014，最深 19 层）证明了\u0026quot;加深网络可以带来精度提升\u0026quot;，GoogLeNet（2014，22 层）则通过 Inception 模块在控制参数量的同时进一步做深。整个社区当时几乎形成了一个共识：只要能把网络做得更深，精度就还能继续往上升。\n但很快人们就发现事情没有那么简单。研究者尝试把 VGG 风格的网络从 19 层继续叠到 30 层、50 层时，模型的表现不仅没有变好，反而开始变差，而且变差的不只是测试集上的精度，连训练集上的 loss 都比浅层网络更高。这个现象在论文中被称作退化问题（Degradation Problem）。\nFigure 1：56 层网络在 CIFAR-10 上的训练误差和测试误差都高于 20 层网络。\n这里存在一个很重要的观点：退化问题 ≠ 梯度消失/爆炸。在 ResNet 出现之前，深网络难训练这件事本身大家都知道，但传统理解都归因于梯度消失或梯度爆炸。既然如此，只要用 BatchNorm 把每一层激活值的分布拉回 N(0,1) 附近，让梯度能够稳定回传不就好了吗？事实上 He Kaiming 团队在论文中明确指出，他们的所有实验都使用了 BN，初始化也都用了当时最先进的 He Initialization，梯度的范数在反向传播过程中是正常的，并没有出现消失或爆炸。换句话说，深层网络的\u0026quot;退化\u0026quot;，不是因为梯度算不出来，而是因为优化器本身，就无法在如此高维的空间找到空间低谷。现实世界中三维的山峰你能很轻易地分辨它的高峰低谷，但进入到一个数百维甚至千维的空间里，几乎没办法用反向误差传播去找到“最快下滑路径”，而 ResNet 就是要解决这一问题。\n这就是退化问题反直觉的地方。从数学上讲，一个 56 层的网络其表达能力一定 ≥ 20 层的网络，你完全可以让后面那 36 层全部学成恒等映射（identity mapping），这样 56 层的效果至少应该等同于 20 层。但实验证明，让一组卷积层去学习恒等映射，这件事本身就很难。优化器倾向于把每一层都学成\u0026quot;非零\u0026quot;的某种变换，恒等映射对它来说反而是一个非常特殊、不容易稳定到达的点。\nResNet 的解法：把\u0026quot;学习目标\u0026quot;换掉 # He Kaiming 团队的切入点非常巧妙：既然让网络学习恒等映射这件事很难，那能不能重新定义网络的学习目标，让\u0026quot;什么都不做\u0026quot;反而成为最容易达成的状态？\n假设我们希望某几层堆叠的卷积最终学到的映射是 $H(x)$。传统做法是直接让这几层去拟合 $H(x)$。ResNet 的思路是：与其让这堆卷积层直接拟合 $H(x)$，不如让它去拟合一个残差函数（Residual Function）：\n$$F(x) := H(x) - x$$那么原本要学习的 $H(x)$ 就可以重写为：\n$$H(x) = F(x) + x$$在网络结构上，这一改动只需要从输入直接拉一条跨层捷径（Shortcut Connection）到几层卷积的输出，再把两者逐元素相加：\nFigure 2：一个最基本的残差块（Residual Block）。\n这个看起来无比简单的改动，却从根本上改变了网络的学习行为：\n恒等映射变得更容易学习：如果当前这一组卷积层\u0026quot;什么都不学\u0026quot;（即 $F(x) \\to 0$），整个模块的输出就自动等于输入 $x$。优化器要让一层变成恒等映射，只需要把那几个卷积的权重压向零即可，这件事对优化器来说远比从头学一个恒等映射容易。 梯度有了一条\u0026quot;高速公路\u0026quot;：反向传播时，梯度沿着 shortcut 直接回流到浅层，不需要逐层穿越所有卷积。即便堆叠到 152 层、1000 层，浅层依然能拿到足够强的梯度信号。 学习目标本身更\u0026quot;小\u0026quot;：相比直接拟合 $H(x)$，残差 $F(x)$ 通常是一个幅度更小、变化更平缓的函数——它只需要表达\u0026quot;在 $x$ 的基础上还需要做哪些修正\u0026quot;，而不必从零生成整张特征图。优化器在这种\u0026quot;小目标\u0026quot;上更容易找到下降方向。 这三点合在一起，就是 ResNet 能够把网络做到 152 层（论文）甚至 1202 层（CIFAR 实验）而依然能稳定收敛的核心原因。\n论文里那些值得一提的细节 # 除了残差块这一核心改动，ResNet 的论文中还有几处工程设计，深刻影响了后续几乎所有的深度网络。\n1. Bottleneck 结构\n当网络深度从 34 层向 50 层以上扩展时，直接堆 3×3 卷积的参数量和 FLOPs 都会爆炸。ResNet 在 ResNet-50/101/152 中改用了 Bottleneck（瓶颈） 结构：\n$$1\\times1 \\text{（降维）} \\to 3\\times3 \\text{（特征提取）} \\to 1\\times1 \\text{（升维）}$$通过先用 1×1 卷积把通道数压低（比如从 256 压到 64），在低维空间做完 3×3 卷积之后，再用 1×1 卷积升回原来的通道数，最后与 shortcut 相加。这种设计在大幅减少计算量的同时，保持了模型的表达能力，直接成为后来 ResNeXt、MobileNet v2、Transformer FFN 等结构的设计模板。\n2. Shortcut 的两种形式\n当残差块的输入输出通道数一致、空间分辨率也一致时，shortcut 就是一条纯粹的恒等连接（Identity Shortcut），不带任何参数；而当通道数或分辨率发生变化时（例如经过下采样 stage 的入口），shortcut 上会插入一个 1×1 卷积做维度对齐（Projection Shortcut）。论文的消融实验表明，Identity Shortcut 在效果上略优于 Projection Shortcut，且因为没有引入参数，工程实现上也更干净。这条经验在后续几乎所有使用残差的网络中都被沿用。\n3. 全局平均池化代替全连接\nResNet 沿用了 GoogLeNet 的做法，用 Global Average Pooling（GAP） 替代了 VGG 末端那个庞大的全连接层。这在显著减少参数量的同时，也让网络对输入分辨率不再敏感——这一点在后来的检测、分割任务中尤其重要。\n总结 # ResNet 真正改变的，并不是\u0026quot;我们能不能把网络做得更深\u0026quot;这件事本身，而是让\u0026quot;加深\u0026quot;这件事终于成为一个可控的工程操作。在它之前，加深网络是一场带有赌博性质的实验；在它之后，加深网络变成了一种几乎线性的精度提升手段。\n从更宏观的角度看，ResNet 的价值还远不止 CV 领域。残差连接这一简单优雅的结构，后来在 Transformer 的每一个 Block、Diffusion 模型的 U-Net、几乎所有现代 LLM 的 Residual Stream 中反复出现，已然成为深度学习里事实上的\u0026quot;默认结构\u0026quot;。我们将在后续章节中再回过头来聊它的这条破圈之路。\n下一章，我们将不再停留在论文层面，而是用 PyTorch 亲手实现一个最小的 ResNet，并预留几处可消融的位置，把这一章里讲过的\u0026quot;为什么\u0026quot;在代码层面展现并且验证。\n用 PyTorch 手撕一个最小的 ResNet # 讲了这么多原理，最直接的方式还是动手把它写出来跑一遍。这一章我们用 PyTorch 实现一个最小 ResNet，在 CIFAR-10 上做分类，并在关键位置留几个\u0026quot;可拆装\u0026quot;的开关，方便后面做消融。\n请注意，本章的目的不是写一个性能最优的 ResNet，原因很简单，torchvision 里早就有现成的 resnet18 可以直接拿来用。本章更重要的目的是把上一章讲过的\u0026quot;残差连接到底起到了什么作用\u0026quot;这件事，从代码层面展现一遍。\n残差块（BasicBlock） # 我们直接从最核心的残差块开始。论文中的 BasicBlock 结构非常简单，两个 3×3 卷积 + BN + ReLU，再加一条 shortcut。\npythonimport torch import torch.nn as nn import torch.nn.functional as F class BasicBlock(nn.Module): expansion = 1 def __init__(self, in_channels, out_channels, stride=1, use_shortcut=True): super().__init__() self.use_shortcut = use_shortcut self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channels) # 当输入输出维度不一致时，shortcut 需要一个 1x1 卷积做对齐（Projection Shortcut） # 否则就是一条纯粹的恒等连接（Identity Shortcut） if stride != 1 or in_channels != out_channels: self.shortcut = nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(out_channels) ) else: self.shortcut = nn.Identity() def forward(self, x): # 主路：conv -\u0026gt; bn -\u0026gt; relu -\u0026gt; conv -\u0026gt; bn out = self.conv1(x) out = self.bn1(out) out = F.relu(out) out = self.conv2(out) out = self.bn2(out) # 残差相加（消融时可关闭） if self.use_shortcut: identity = self.shortcut(x) out = out + identity # 相加之后再过一次 ReLU out = F.relu(out) return out 这里有两个细节需要展开说一下。\n第一个是 use_shortcut 这个开关，这是故意留出来的消融位——把它设为 False，整个块就退化成一个普通的\u0026quot;两层卷积堆叠\u0026quot;，和 VGG 风格的卷积模块几乎没有区别。后面我们会用它来对比 带 shortcut 和不带 shortcut 在深层网络下的训练表现。\n第二个是 shortcut 的两种形式。当 stride=1 且输入输出通道一致时，shortcut 是 nn.Identity()，没有任何参数；当下采样发生时（stride=2）或者通道数需要扩张时，才插入一个 1×1 卷积做维度对齐。这一点和上一章里讲到的论文细节完全一致。\n组装 ResNet # 有了 BasicBlock，组装 ResNet 就是堆叠罢了。我们做一个 CIFAR-10 适配版的 ResNet，结构上和原论文的 CIFAR 实验保持一致：入口是一个 3×3 卷积（不是 ImageNet 版的 7×7），然后是三个 stage，每个 stage 内部由若干个 BasicBlock 串起来，stage 之间通过 stride=2 做下采样。\npythonclass ResNet(nn.Module): def __init__(self, num_blocks_per_stage, num_classes=10, use_shortcut=True): super().__init__() self.in_channels = 16 self.use_shortcut = use_shortcut # CIFAR-10 入口：3x3 卷积，不做下采样 self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(16) # 三个 stage，通道数依次为 16/32/64，stage 间 stride=2 self.stage1 = self._make_stage(16, num_blocks_per_stage, stride=1) self.stage2 = self._make_stage(32, num_blocks_per_stage, stride=2) self.stage3 = self._make_stage(64, num_blocks_per_stage, stride=2) self.avgpool = nn.AdaptiveAvgPool2d(1) # Global Average Pooling self.fc = nn.Linear(64, num_classes) def _make_stage(self, out_channels, num_blocks, stride): strides = [stride] + [1] * (num_blocks - 1) layers = [] for s in strides: layers.append(BasicBlock(self.in_channels, out_channels, stride=s, use_shortcut=self.use_shortcut)) self.in_channels = out_channels return nn.Sequential(*layers) def forward(self, x): # 入口 out = self.conv1(x) out = self.bn1(out) out = F.relu(out) # 三个 stage 串行 out = self.stage1(out) out = self.stage2(out) out = self.stage3(out) # GAP + 分类头 out = self.avgpool(out) out = torch.flatten(out, 1) out = self.fc(out) return out def resnet20(use_shortcut=True): return ResNet(num_blocks_per_stage=3, use_shortcut=use_shortcut) def resnet56(use_shortcut=True): return ResNet(num_blocks_per_stage=9, use_shortcut=use_shortcut) 这里专门提供了 resnet20 和 resnet56 两个工厂函数，原因相信你已经猜到了，上一章那张论文 Figure 1 里对比的就是 20 层和 56 层网络，我们待会儿就要把这个对比亲手复现一遍。\n每个 stage 的 BasicBlock 数量由 num_blocks_per_stage 控制：3 个 block × 3 个 stage × 每个 block 2 层卷积 + 入口 1 层 + 末尾 fc 1 层 ≈ 20 层；9 个 block 同理算下来就是 56 层。\n最后注意到末尾用的是 AdaptiveAvgPool2d(1)，即 GAP，把任意空间尺寸的特征图压成一个 1×1 向量，这一点也和上一章讲过的论文细节对应。\n训练代码 # 训练部分用的是 PyTorch 最标准的写法：\npythonimport torch import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms device = \u0026#34;cuda\u0026#34; if torch.cuda.is_available() else \u0026#34;cpu\u0026#34; # CIFAR-10 标准数据增强 train_tf = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)), ]) test_tf = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)), ]) train_set = datasets.CIFAR10(root=\u0026#34;./data\u0026#34;, train=True, download=True, transform=train_tf) test_set = datasets.CIFAR10(root=\u0026#34;./data\u0026#34;, train=False, download=True, transform=test_tf) train_loader = DataLoader(train_set, batch_size=128, shuffle=True, num_workers=2) test_loader = DataLoader(test_set, batch_size=256, shuffle=False, num_workers=2) def train_one(model, epochs=50, lr=0.1): model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=lr, momentum=0.9, weight_decay=5e-4) scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs) for epoch in range(epochs): model.train() train_loss, train_correct, train_total = 0.0, 0, 0 for x, y in train_loader: x, y = x.to(device), y.to(device) optimizer.zero_grad() out = model(x) loss = criterion(out, y) loss.backward() optimizer.step() train_loss += loss.item() * x.size(0) train_correct += (out.argmax(1) == y).sum().item() train_total += x.size(0) scheduler.step() # 测试集评估 model.eval() test_correct, test_total = 0, 0 with torch.no_grad(): for x, y in test_loader: x, y = x.to(device), y.to(device) out = model(x) test_correct += (out.argmax(1) == y).sum().item() test_total += x.size(0) print(f\u0026#34;[Epoch {epoch+1:3d}] \u0026#34; f\u0026#34;train_loss={train_loss/train_total:.4f} \u0026#34; f\u0026#34;train_acc={train_correct/train_total:.4f} \u0026#34; f\u0026#34;test_acc={test_correct/test_total:.4f}\u0026#34;) 这里有意识地把训练 loss 也打印出来，不只是 test acc。因为这里可以展现退化问题最经典的现象：去掉 shortcut 的深层网络，连训练 loss 都降不下去。如果只看 test acc，你会以为是过拟合或者别的什么问题；只有同时看着 train loss 一起，才能确认这不是过拟合，是优化器降不下去了。\n留给读者的几个消融实验 # 铺垫到这里，下面给出三组消融实验。希望有心的读者能自己做一次实验，再回到上一章的\u0026quot;为什么\u0026quot;对照阅读。\n消融 1：shortcut 的作用（核心）\n这是最能直接验证残差思想的实验。同时训练以下四个模型，对比它们的 train loss 曲线和 test acc：\npythonm1 = resnet20(use_shortcut=True) # 20 层 + shortcut m2 = resnet20(use_shortcut=False) # 20 层 - shortcut m3 = resnet56(use_shortcut=True) # 56 层 + shortcut m4 = resnet56(use_shortcut=False) # 56 层 - shortcut for m in [m1, m2, m3, m4]: train_one(m, epochs=50) 预期能观察到的现象有两个层次：\n浅层（20 层）：有无 shortcut 差距不大，因为 20 层本身还没进入\u0026quot;退化\u0026quot;的高发区。 深层（56 层）：有 shortcut 的训练正常收敛；去掉 shortcut 的不仅 test acc 更差，连 train loss 都比 20 层版本更高。这一现象如果你能亲眼看到，那就是你独立复现了论文 Figure 1 中那张著名的退化曲线。 消融 2：进一步把网络加深\n有了 num_blocks_per_stage 这个旋钮，你可以非常方便地把网络做得更深，比如 num_blocks_per_stage=18（约等于 110 层）：\npythonm_deep_with = ResNet(num_blocks_per_stage=18, use_shortcut=True) m_deep_without = ResNet(num_blocks_per_stage=18, use_shortcut=False) 注意训练去掉 shortcut 的 110 层网络时，你大概率会观察到 loss 直接卡死、几乎不下降的现象。这正是上一章中提到的\u0026quot;优化器在高维空间里找不到下降方向\u0026quot;的真实表现。\n消融 3：BN 是不是就能解决问题？\n这一组用来回应上一章里那段强调过的观点：退化问题 ≠ 梯度消失，BN 解决不了它。你可以在 BasicBlock 里把 BN 全部去掉（或者干脆替换成 nn.Identity()），再分别训练有 shortcut 和无 shortcut 的版本：\npython# 改造 BasicBlock：把 self.bn1 / self.bn2 换成 nn.Identity() # （改造代码留给读者自己完成） 如果 BN 真的就能\u0026quot;治好\u0026quot;深层网络，那么无 shortcut + 有 BN 应该和带 shortcut 的版本差距不大。但实际跑下来你会发现，就算由BN层的存在，没有 shortcut 的深层网络该训不动还是训不动。这就是 He Kaiming 在论文中强调的那一点：残差连接解决的，是优化层面的根本问题，BN 只能让数值稳定，无法替代它。\n小结 # 这一节我们用代码复现了 ResNet 原论文中做的一部分实验，能够让你更加直观的感受到“残差连接”这一结构在深度学习领域的开创性。\n下一章我们会再次跳出 ResNet 本身，去探索一个更抽象的话题：残差这种结构，如何从一个简单 CNN 结构走向如今动辄几百B的大语言模型。\n残差思想的外溢 # 前面两章里，\u0026ldquo;残差连接\u0026quot;一直都待在 CV 这个圈子里。但你有没有想过，今天那些动辄几十上百层的大模型，深度早就远远超过了当年的 ResNet，它们靠的是什么？答案其实不意外，正是同一条 shortcut。\n2017 年 Transformer 横空出世，把深度学习带进了一个全新的阶段。而在它的每一个 Encoder、Decoder Block 里面，残差连接依然存在，并发挥着至关重要的作用。\nCV 领域内的延续：U-Net 与各种\u0026quot;skip\u0026rdquo; # 残差思想最早溢出的地方，其实就在 ResNet 隔壁的 CV 子领域，分割。\n2015 年同年发布的 U-Net 是医学图像分割里几乎人手一份的网络。它的结构很直观，一条编码器把分辨率从高到低逐步下采样，再一条解码器把分辨率从低到高逐步上采样。U-Net 真正出名的地方，是它在编码器和解码器对应层级之间拉的那一组 skip connection，浅层的高分辨率特征图被直接 concat 到深层上采样后的特征图上。\n这里的 skip 和 ResNet 的 shortcut 形式上不太一样，ResNet 用 add，U-Net 用 concat，但要解决的问题是同一个：让浅层的精细信息有一条\u0026quot;不被下采样吃掉\u0026quot;的通路，直接抵达深层的预测头。后来的 FPN、PANet、HRNet 这些多尺度结构，做的也是同一件事，给信息流多开几条绕过主路的通道。\n整个 CV 界在 2015 年之后基本默认了一件事：只要网络要堆得深，就得给信息流一条 shortcut，不管它叫 residual、skip、还是 lateral。\n进入 Transformer：每一个 Block 都是一个残差块 # 让残差连接真正\u0026quot;破圈\u0026quot;的，是 2017 年那篇 《Attention Is All You Need》。\n如果你把 Transformer 的 Encoder Block 拆开看，每一层其实就是两个残差块串起来：\n$$x' = \\text{LayerNorm}(x + \\text{Attention}(x))$$\n$$x'' = \\text{LayerNorm}(x' + \\text{FFN}(x'))$$注意力子层和 FFN 子层外面，各自都套了一层 $x + \\text{Sublayer}(x)$ 的结构，也就是我们前面说过的残差公式。\n为什么 Transformer 也需要残差？答案和 ResNet 当年的回答几乎是同一句话：没有残差，深层 Transformer 训不起来。早期的 Transformer 也就 6 层 Encoder + 6 层 Decoder，但后来要堆到 24 层、48 层、96 层甚至更深，没有残差连接，梯度回不到浅层。GPT 系列从 12 层一路堆到 96 层、BERT-Large 的 24 层、再到现在动辄上百层的 LLM Decoder，每一层里都老老实实保留着 He Kaiming 当年那条 shortcut。\n后来的 Pre-LN 和 Post-LN 之争（讨论的是 LayerNorm 应该放在残差里面还是外面），讨论的也是\u0026quot;残差连接在 Transformer 里怎么用最稳定\u0026quot;这件事。没有任何一个版本敢去掉残差本身。\n在 LLM 里被命名：Residual Stream # 到了大语言模型这一阶段，残差连接甚至有了一个专门的名字：Residual Stream（残差流）。\n这个概念由 Anthropic 的可解释性研究正式提出。在他们的视角里，一个 Transformer LLM 在做的事情，不是\u0026quot;每一层逐步变换输入\u0026quot;，而是\u0026quot;每一层都在往同一条贯穿全网的残差流上读写信息\u0026quot;。\n具体来说，每个 token 在网络里都有一条贯穿所有层的\u0026quot;信息总线\u0026quot;，这就是 residual stream。每一层的 Attention 和 FFN 都从这条流上读取信息（通过它们的输入投影），处理之后再把结果加回到这条流上（通过残差相加）。从入口的 embedding 一路到末端的 unembedding 头，residual stream 始终是同一个高维向量，内容会随着层数推进被一层层修订，但它的\u0026quot;身份\u0026quot;不曾中断。\n我个人觉得这个视角挺有意思的：残差连接到这里已经不再只是\u0026quot;为了防止训练垮掉的补救措施\u0026quot;，它本身就是 LLM 的核心计算范式。LLM 的每一层做的事情，差不多就是一次对 residual stream 的\u0026quot;局部读—加工—写回\u0026quot;。\n回头看 ResNet 当年提出的 $H(x) = F(x) + x$，这里的 $x$ 此时已经不只是某一组卷积的输入，而是从模型入口一路流到模型出口的那条主线。\nDiffusion 里也是同一个故事 # 顺带一提，现在火热的 Diffusion 模型也没逃过残差的影响。Stable Diffusion 这类模型的核心去噪网络就是一个 U-Net，而这个 U-Net 内部的每一个 block 都是带残差的卷积块（很多实现直接复用了 ResNet 风格的 BasicBlock）；外面再套上 U-Net 自己的 skip connection。一个 Diffusion U-Net 里，残差连接其实出现了两层：宏观上的 skip，微观上的 residual block。\n文本到图像、视频生成、3D 生成这些今天看起来很前沿的方向，骨子里跑的还是当年 ResNet 那一套结构语言。/\n一点点总结 # 回过头看，残差连接能成为现在的\u0026quot;默认结构\u0026quot;，我个人觉得有几个原因：\n它在优化层面的好处是普适的。不管 CNN 还是 Transformer，只要网络要堆得深，\u0026ldquo;让恒等映射变成默认行为、让梯度有条高速通路\u0026quot;这件事就有价值，和数据模态、和具体子任务关系都不大。 它对架构本身几乎没有强假设。残差只要求\u0026quot;输入和输出能加起来\u0026rdquo;，不在意你中间做的是卷积、是注意力还是别的什么，所以它能塞进几乎任何模块。 它在工程上几乎是零成本。一条 shortcut，多一次 add 操作而已，但换来的稳定性和可训练性的提升远远超过这点开销。 从 2015 年的 ResNet 出发，残差连接走过了 CV、NLP、Diffusion、LLM 这一路，被反复使用、被重新命名（skip、residual stream），被各种结构装进自己的设计里。每次有新架构出来，不管是 Vision Transformer、Swin、ConvNeXt、Mamba、还是各种新的 LLM，你只要去看它的结构图，几乎都能在某一层、某一段找到那条熟悉的\u0026quot;绕过主路的箭头\u0026quot;。\n这大概也是 He Kaiming 当年那篇论文影响最远的地方：它不只解决了\u0026quot;网络做不深\u0026quot;这个具体问题，还给整个深度学习社区贡献了一种思考网络结构的方式，任何复杂的变换，都可以被拆解成\u0026quot;一条主路 + 一条捷径\u0026quot;。这件事十年后回头看，确实变成了几乎所有现代深度模型默认遵循的设计原则。\n写在最后 # 这篇博客从 2015 年 He Kaiming 那篇 CVPR 最佳论文写起，先聊了 ResNet 当年是为了解决什么问题，又用 PyTorch 把核心结构和最小可跑的网络敲了一遍，还留了几组简单的消融实验；最后一章再把视角拉远，看着残差连接如何走出 CV，进入 Transformer、Diffusion、LLM，成为今天几乎人手一份的默认配置。\n下次你再在某张模型结构图里看到那条熟悉的\u0026quot;高速通路\u0026quot;，希望已经不再陌生。那一条小小的 shortcut，撑起了现在我们能用到的几乎所有强推理、强特征提取能力的模型。ResNet 留给后人的，远不止一个 backbone，而是一种被时间反复验证过的思考方式。\n","date":"2026年05月27日","externalUrl":null,"permalink":"/Owen-Studio/posts/resnet-introduction/","section":"Posts","summary":"","title":"ResNet详解——残差结构的思考","type":"posts"},{"content":"","date":"2026年05月27日","externalUrl":null,"permalink":"/Owen-Studio/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"2026年05月27日","externalUrl":null,"permalink":"/Owen-Studio/categories/2d-vision/","section":"Categories","summary":"","title":"传统二维视觉","type":"categories"},{"content":"","date":"2026年05月27日","externalUrl":null,"permalink":"/Owen-Studio/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/","section":"Tags","summary":"","title":"深度学习","type":"tags"},{"content":"","date":"2026年05月23日","externalUrl":null,"permalink":"/Owen-Studio/tags/lora/","section":"Tags","summary":"","title":"LoRA","type":"tags"},{"content":"","date":"2026年05月23日","externalUrl":null,"permalink":"/Owen-Studio/tags/vlm/","section":"Tags","summary":"","title":"VLM","type":"tags"},{"content":" 前言 # 本文旨在让即便没有基础的读者再阅读后，也能快速的使用 Google Colab 计算平台，使用国内 Qwen 视觉-语言大模型进行小规模的 LoRA 微调，本文将给出完整的代码和使用示例。\n首先让我们来了解一下完成模型微调所需要的基础知识。\nGoogle Colab 是什么？ # Google Colab 是由谷歌推出的在线计算平台，Colab 是 calculate lab 的简称，他提供了免费的 GPU 算力(Tesla T4)供用户使用，虽然平台也有A100、H100这样的高端显卡，但那都是需要付费或进行美国大学生学生认证后才能使用。本教程还是使用免费的 GPU 和内存环境进行示范，如果各位读者有需求可以按照自己的需求订阅Google Colab的会员。\n在浏览器搜索Google Colab 并用自己的谷歌账号登录就会进入如下的界面：\n这里界面左侧有一个目录，里面包含了一些基础入门的知识，强烈建议在正式使用前先完整阅读。包括如何使用，以及Google Colab能完成哪些事，在后面进行示例讲解的时候心里有数。\n这时我们可以创建一个新的工作空间：在左上角的“文件”处，选择“在云端硬盘中新建笔记本”，就会自动跳转到如下图所示的工作空间了。因为刚开始创建时，工作空间的名字还是默认状态，建议各位读者先对空间名字进行自定义修改，防止下次使用时找不到原工作空间。\n到这一步，Google Colab的基础准备就已经完成了，下一步就是进行GPU的连接：\n注意工作空间右上角“连接”选项，点击后需要耐心等待一会，系统会为你分配运行时和基础环境。当显示已连接，且旁边出现了“RAM”，“磁盘”的占用条说明工作空间已经完成初始化。这时我们要去链接Google Colab分配的GPU。\n点击右上角的“下三角形🔻”，选择“更改运行时类型”，就会看到下面的界面。\n注意这里，只需要修改“硬件加速器”下的选项，把“CPU”改成“T4 GPU”即可保存。 等待系统再次为你分配运行时，当右下角出现 “T4(Python3)”字样时，GPU设置就成功啦。\n什么是LoRA？ # 这是只要接触过大模型微调就一定绕不开的问题。LoRA，全称 Low-Rank Adaptation，由微软在 2021 年提出，已经成为今天大模型微调事实上的标准做法。\n为什么需要 LoRA？\n我们先想一下传统的\u0026quot;全参数微调\u0026quot;是怎么做的。假设我们手上有一个 Qwen-VL 7B 模型，想让它在某个特定任务上表现更好，最直接的做法就是把所有 70 亿参数都拿出来，用新数据继续训练。听上去很合理，但问题在于：\n显存爆炸：训练时不仅要保存参数本身，还要保存梯度、优化器状态(Adam 需要保存一阶和二阶动量)。一个 7B 的模型在 FP16 下大概要 14GB 显存放参数，加上梯度和优化器状态，没有 80GB 的 A100 根本跑不起来。 存储灾难：每微调一个任务就要存一份完整的 7B 权重，10 个任务就是 140GB。如果你想给不同客户提供定制化服务，硬盘很快就被吃光。 过拟合风险：下游任务的数据集往往只有几百到几千条，让 70 亿参数全部参与训练，模型很容易\u0026quot;记住\u0026quot;训练样本，丢掉预训练时学到的通用能力。 那有没有一种办法，既能让模型适配下游任务，又不用动那么多参数呢？这就是 LoRA 想要解决的问题。\nLoRA 的核心思想\nLoRA 的洞察来自一个非常重要的假设——大模型在适配下游任务时，参数的变化量 $\\Delta W$ 是低秩的。\n什么意思呢？我们把微调过程写成数学形式。原始权重是 $W_0 \\in \\mathbb{R}^{d \\times k}$，微调后变成 $W_0 + \\Delta W$。传统全参微调中，$\\Delta W$ 是一个完整的 $d \\times k$ 矩阵，参数量和 $W_0$ 一样多。LoRA 假设 $\\Delta W$ 其实没有那么\u0026quot;复杂\u0026quot;，它可以用两个小矩阵相乘来近似：\n$$\\Delta W = BA, \\quad B \\in \\mathbb{R}^{d \\times r}, \\quad A \\in \\mathbb{R}^{r \\times k}$$其中 $r$ 是远小于 $d$ 和 $k$ 的\u0026quot;秩\u0026quot;(rank)，比如 $d=k=4096$，而 $r$ 可能只取 8 或 16。\n这样一来，原本 $d \\times k = 16{,}777{,}216$ 个参数的 $\\Delta W$，被压缩成了 $(d + k) \\times r = 65{,}536$ 个参数，整整缩小了 256 倍。我们冻结原始的 $W_0$，只训练 $A$ 和 $B$ 这两个小矩阵，前向传播就变成了：\n$$h = W_0 x + \\Delta W x = W_0 x + B A x$$ ┌───────────┐ x → │ W_0 │ → 冻结，不参与梯度更新 └───────────┘ │ ↓ ┌──────┐ ┌──────┐ x → │ A │ → │ B │ → α/r 缩放后加到原输出上 └──────┘ └──────┘ 训练 训练 初始化的时候有个小细节：$A$ 用高斯分布随机初始化，$B$ 初始化为零矩阵。这样训练刚开始时 $BA = 0$，模型行为和原始模型完全一致，避免一上来就把预训练知识破坏掉。\n几个关键超参数\n实际使用 LoRA 的时候，我们需要关心几个参数：\n参数 含义 经验值 r (rank) 低秩矩阵的秩，决定容量和参数量 4、8、16、32 alpha 缩放系数，最终结果会乘以 $\\alpha/r$ 一般取 $r$ 的 1～2 倍 target_modules 在哪些线性层上插入 LoRA q_proj, v_proj 是最常见的组合 dropout LoRA 路径上的 dropout 比例 0.05～0.1 r 越大，LoRA 的表达能力越强，但参数量也越多，越容易过拟合。对于视觉-语言模型这种已经过充分预训练的大模型，r=8 或 r=16 在大多数任务上已经够用。\nalpha 是一个常常被忽略但很关键的参数。LoRA 论文里给出的实际计算是 $\\Delta W = (\\alpha / r) \\cdot BA$，引入它的目的是让我们调整 r 的时候不用重新调整学习率。简单来说：固定 alpha，改变 r 时，模型的\u0026quot;等效学习率\u0026quot;基本不变。\ntarget_modules 决定了我们要让哪些权重\u0026quot;可训练\u0026quot;。Transformer 的每一层 Attention 都有 $W_q, W_k, W_v, W_o$ 四个投影矩阵，FFN 还有两个。原论文实验发现，只在 $W_q$ 和 $W_v$ 上加 LoRA 效果就已经很好；如果想榨干一点性能，可以把所有线性层都加上，代价是参数量翻几倍。\n为什么 LoRA 有效？\n回到那个核心假设——$\\Delta W$ 真的是低秩的吗？\nLoRA 论文做了一组实验：他们把全参微调得到的 $\\Delta W$ 拿过来做 SVD 分解，发现绝大部分能量都集中在前几个奇异值上，剩下的奇异值几乎为零。换句话说，模型在适配下游任务时，本质上只是在一个非常低维的子空间里\u0026quot;挪动\u0026quot;参数，剩下的高维方向上几乎没什么变化。\n这背后其实是一个更深的现象：大型预训练模型的\u0026quot;内在维度\u0026quot;(intrinsic dimension)远小于其名义参数量。这个观察在 BERT、GPT、LLaMA 上都被反复验证过，是 LoRA 能成立的根本原因。\nLoRA 与全参微调的对比\n维度 全参微调 LoRA 显存占用 高，需要为所有参数保存梯度和优化器状态 低，只为 $A, B$ 保存 训练参数量 100% 通常 0.1% ～ 1% 存储开销 每个任务一份完整模型 每个任务只存几 MB 的 LoRA 权重 训练速度 慢 快 下游性能 上限略高 在大多数任务上能达到 95% 以上 多任务切换 需要加载完整模型 切换 LoRA 权重即可 对于个人开发者和小团队来说，LoRA 几乎是唯一能在消费级 GPU 上微调大模型的可行路径。这也是本教程选择 LoRA 的原因——一张免费的 T4 显卡，16GB 显存，正好够跑一次 Qwen-VL 的 LoRA 微调。\n推理时怎么用？\nLoRA 训练好之后，推理时有两种用法：\n合并(merge)：把 $BA$ 直接加回 $W_0$，得到一份新的权重 $W' = W_0 + (\\alpha/r) BA$。这样推理时和原始模型完全一样，没有任何额外开销，但失去了\u0026quot;热插拔\u0026quot;的灵活性。 不合并：保留 $W_0$ 和 LoRA 权重分开存储，前向时同时跑两条路径。代价是每层多一次小矩阵乘法，但好处是可以在同一个基础模型上加载多份 LoRA 权重，按需切换。 如果是部署到生产环境、且任务固定，建议直接 merge；如果是多任务、多用户场景，保留分离形式会更灵活。HuggingFace 的 peft 库对这两种用法都有现成的接口，我们后面会看到具体怎么调用。\n正式进入Google Colab进行微调 # 理论铺垫够了，下面我们就一格一格地把整个微调流程跑通。Colab 的工作方式是按\u0026quot;单元格(cell)\u0026ldquo;组织代码的，每个单元格可以独立运行，运行结果会直接显示在格子下面。建议各位读者跟着把每一格代码新建一个 cell 复制进去，边跑边看输出。\n第一步：确认 GPU 已连接\n打开新建的笔记本后，第一件事永远是看看 GPU 到底分到了没有。新建一个 cell，输入：\npython!nvidia-smi ! 是 Colab 的语法糖，表示这一行是 shell 命令而不是 Python 代码。运行后会输出一张 GPU 信息表，关键看两件事：显卡型号是不是 Tesla T4，显存是不是 15360MiB(约 15GB)。如果输出报错说找不到 nvidia-smi，那说明运行时还没切到 GPU，回到上面\u0026quot;更改运行时类型\u0026quot;那一步重做一次。\n紧接着再跑一段 Python 代码，从 PyTorch 的角度二次确认：\npythonimport torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0)) print(f\u0026#34;显存: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB\u0026#34;) 输出：\nTrue Tesla T4 显存: 14.6 GB True + Tesla T4 就说明 PyTorch 已经能正常调用 GPU 了。注意这里显存显示 14.6GB 而不是 15GB，是因为有一小部分被显卡驱动和系统保留了，正常现象。\n第二步：安装依赖并加载 Qwen2-VL-2B 模型\nColab 自带的环境里 transformers 版本可能偏老，VLM 相关的几个库也不齐，先统一装一遍：\npython!pip install transformers peft datasets accelerate bitsandbytes qwen-vl-utils -q -q 是 quiet 模式，输出会少很多。这里有个坑要提醒一下：装完 bitsandbytes 之后，Colab 有时候需要重启运行时(顶部菜单 \u0026ldquo;代码执行程序 → 重新启动会话\u0026rdquo;)才能让新版本生效，否则下一格加载 4bit 量化模型时会报 ImportError: Using bitsandbytes 4-bit quantization requires bitsandbytes。如果遇到了，重启完从这一格之后接着跑即可。\n然后正式加载模型：\npythonfrom transformers import Qwen2VLForConditionalGeneration, AutoProcessor, BitsAndBytesConfig import torch bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_compute_dtype=torch.float16 ) model = Qwen2VLForConditionalGeneration.from_pretrained( \u0026#34;Qwen/Qwen2-VL-2B-Instruct\u0026#34;, quantization_config=bnb_config, device_map=\u0026#34;auto\u0026#34; ) processor = AutoProcessor.from_pretrained(\u0026#34;Qwen/Qwen2-VL-2B-Instruct\u0026#34;) print(\u0026#34;模型加载成功\u0026#34;) 这里有两个细节值得展开：\nBitsAndBytesConfig(load_in_4bit=True) 是关键，它会把原本 FP16 的权重压成 4bit 存储。Qwen2-VL-2B 的全精度大小约 4.5GB，量化后只需要 1.5GB 左右，T4 的 15GB 显存就绰绰有余了。bnb_4bit_compute_dtype=torch.float16 表示存储是 4bit 但计算时再升回 FP16，精度损失会小很多。 device_map=\u0026quot;auto\u0026quot; 让 transformers 自动把模型分片放到可用设备上，对单卡 T4 而言就是全部塞进 GPU。 第一次运行时会从 HuggingFace 下载约 4GB 的权重文件，T4 上一般要等 1-2 分钟。看到 模型加载成功 就算过关。\n第三步：先做一次推理，验证模型能用\n微调之前先确认模型本身没问题，免得后面训练出问题时分不清是数据问题还是模型问题。\npythonfrom PIL import Image import requests url = \u0026#34;https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg\u0026#34; image = Image.open(requests.get(url, stream=True).raw).convert(\u0026#34;RGB\u0026#34;) messages = [ { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: [ {\u0026#34;type\u0026#34;: \u0026#34;image\u0026#34;, \u0026#34;image\u0026#34;: image}, {\u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;这张图片里有什么？\u0026#34;} ] } ] text = processor.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) inputs = processor( text=[text], images=[image], return_tensors=\u0026#34;pt\u0026#34; ).to(\u0026#34;cuda\u0026#34;) with torch.no_grad(): output = model.generate(**inputs, max_new_tokens=100) response = processor.decode(output[0], skip_special_tokens=True) print(response) 这里的 messages 结构是 Qwen 系列的标准对话格式：一条用户消息里可以同时塞图片和文字。apply_chat_template 会按 Qwen 的训练模板把它拼成一段带特殊 token 的字符串，processor 再把图片和文字一起编码成模型能吃的张量。\n输出：\n这张图片里有一辆绿色的复古汽车停在人行道上，背景是一堵黄色的墙和一扇棕色的门。 模型把图片内容描述得相当准确，说明推理链路是通的。\n第四步：准备图文训练数据\nVLM 的训练数据本质上是\u0026quot;图片 + 问题 + 答案\u0026quot;三元组。为了把流程跑通，我们先手工构造几条围绕同一张图片的问答：\npythonimport requests from io import BytesIO from PIL import Image def load_image_from_url(url): response = requests.get(url) return Image.open(BytesIO(response.content)).convert(\u0026#34;RGB\u0026#34;) raw_data = [ { \u0026#34;image_url\u0026#34;: \u0026#34;https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg\u0026#34;, \u0026#34;question\u0026#34;: \u0026#34;图片中的车是什么颜色？\u0026#34;, \u0026#34;answer\u0026#34;: \u0026#34;图片中的车是复古绿色的。\u0026#34; }, { \u0026#34;image_url\u0026#34;: \u0026#34;https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg\u0026#34;, \u0026#34;question\u0026#34;: \u0026#34;这辆车停在什么地方？\u0026#34;, \u0026#34;answer\u0026#34;: \u0026#34;这辆车停在路边的停车位上。\u0026#34; }, { \u0026#34;image_url\u0026#34;: \u0026#34;https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg\u0026#34;, \u0026#34;question\u0026#34;: \u0026#34;图片中除了车还有什么？\u0026#34;, \u0026#34;answer\u0026#34;: \u0026#34;图片中除了车还有道路和建筑物背景。\u0026#34; }, ] print(\u0026#34;加载训练图片...\u0026#34;) train_data = [] for item in raw_data: image = load_image_from_url(item[\u0026#34;image_url\u0026#34;]) train_data.append({ \u0026#34;image\u0026#34;: image, \u0026#34;question\u0026#34;: item[\u0026#34;question\u0026#34;], \u0026#34;answer\u0026#34;: item[\u0026#34;answer\u0026#34;] }) print(f\u0026#34;加载完成: {item[\u0026#39;question\u0026#39;]}\u0026#34;) print(f\u0026#34;\\n共 {len(train_data)} 条训练数据\u0026#34;) 这里只用了三条数据，纯粹是为了演示完整链路。真实项目里至少要几百到几千条，而且每条最好对应不同的图片。注意第二条数据的\u0026quot;答案\u0026quot;其实和第三步推理时模型自己给的描述(人行道)不一致——这是故意为之，后面我们要看看 LoRA 能不能成功把模型的认知转到我们想要的答案上来。\n第五步：配置 LoRA\n终于到了 LoRA 登场的环节。前面理论部分提到的几个超参数，在这里全都要落到具体配置上：\npythonfrom peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training model = prepare_model_for_kbit_training(model) lora_config = LoraConfig( r=8, lora_alpha=16, target_modules=[\u0026#34;q_proj\u0026#34;, \u0026#34;v_proj\u0026#34;, \u0026#34;k_proj\u0026#34;, \u0026#34;o_proj\u0026#34;], lora_dropout=0.05, bias=\u0026#34;none\u0026#34;, task_type=\u0026#34;CAUSAL_LM\u0026#34; ) model = get_peft_model(model, lora_config) model.print_trainable_parameters() prepare_model_for_kbit_training 是一个必要的预处理步骤：对 4bit 量化的模型来说，它会把所有非 LoRA 层冻结、把 LayerNorm 转成 FP32、再开启 gradient checkpointing 节省显存。少了这一步训练会有几率崩溃。\nLoraConfig 里的几个参数就是前面理论章节讲过的那些：r=8 是低秩矩阵的秩，lora_alpha=16 即 $\\alpha/r = 2$ 的缩放系数，target_modules 这次比常见的 q_proj + v_proj 多带上了 k_proj 和 o_proj，是为了让 LoRA 在注意力层有更充分的表达能力——多花的参数量也很有限。task_type=\u0026quot;CAUSAL_LM\u0026quot; 告诉 PEFT 我们是做\u0026quot;自回归生成\u0026quot;任务。\n输出：\ntrainable params: 2,179,072 || all params: 2,211,164,672 || trainable%: 0.0985 22 亿参数里，只有 218 万参数(约 0.1%)会参与训练，这就是 LoRA 的威力。\n第六步：自定义数据整理器\nVLM 的数据处理比纯文本麻烦不少，因为除了 input_ids，还得带上图片张量 pixel_values 和 Qwen2-VL 特有的 image_grid_thw(图片在时间-高-宽三个维度上的网格划分)。我们用 torch.utils.data.Dataset 包一层：\npythonfrom torch.utils.data import Dataset from qwen_vl_utils import process_vision_info class VLMDataset(Dataset): def __init__(self, data, processor): self.data = data self.processor = processor def __len__(self): return len(self.data) def __getitem__(self, idx): item = self.data[idx] messages = [ { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: [ {\u0026#34;type\u0026#34;: \u0026#34;image\u0026#34;, \u0026#34;image\u0026#34;: item[\u0026#34;image\u0026#34;]}, {\u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;text\u0026#34;: item[\u0026#34;question\u0026#34;]} ] }, { \u0026#34;role\u0026#34;: \u0026#34;assistant\u0026#34;, \u0026#34;content\u0026#34;: [ {\u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;text\u0026#34;: item[\u0026#34;answer\u0026#34;]} ] } ] text = self.processor.apply_chat_template( messages, tokenize=False, add_generation_prompt=False ) image_inputs, video_inputs = process_vision_info(messages) inputs = self.processor( text=[text], images=image_inputs, videos=video_inputs, return_tensors=\u0026#34;pt\u0026#34;, padding=\u0026#34;max_length\u0026#34;, max_length=512, truncation=True ) result = { \u0026#34;input_ids\u0026#34;: inputs[\u0026#34;input_ids\u0026#34;].squeeze(0), \u0026#34;attention_mask\u0026#34;: inputs[\u0026#34;attention_mask\u0026#34;].squeeze(0), \u0026#34;labels\u0026#34;: inputs[\u0026#34;input_ids\u0026#34;].squeeze(0).clone() } if \u0026#34;pixel_values\u0026#34; in inputs: result[\u0026#34;pixel_values\u0026#34;] = inputs[\u0026#34;pixel_values\u0026#34;] if \u0026#34;image_grid_thw\u0026#34; in inputs: result[\u0026#34;image_grid_thw\u0026#34;] = inputs[\u0026#34;image_grid_thw\u0026#34;] return result dataset = VLMDataset(train_data, processor) sample = dataset[0] print(\u0026#34;数据字段：\u0026#34;, sample.keys()) 注意三个点：\n和推理时不同，这里 apply_chat_template 的 add_generation_prompt=False，因为训练时 assistant 的回答已经在 messages 里了，不需要再加生成提示符。 labels = input_ids.clone()：这是因果语言模型的标准做法，让模型学着预测下一个 token。严格来讲我们只应该对 assistant 部分的 token 计算 loss，但 PEFT + HF Trainer 这套组合在简单场景下用全 token loss 也能学起来。 process_vision_info 是 qwen_vl_utils 提供的工具函数，会从 messages 里把图片字段抽出来，转成 processor 能吃的格式，同时生成 image_grid_thw。自己手搓很容易出错，直接用现成的就好。 第七步：开始训练\n这一步把 TrainingArguments、Trainer、collate_fn 凑齐就能跑了：\npythonfrom transformers import TrainingArguments, Trainer training_args = TrainingArguments( output_dir=\u0026#34;./qwen_vl_lora_ckpt\u0026#34;, num_train_epochs=3, per_device_train_batch_size=1, gradient_accumulation_steps=4, learning_rate=1e-4, logging_steps=1, save_strategy=\u0026#34;no\u0026#34;, fp16=True, remove_unused_columns=False, report_to=\u0026#34;none\u0026#34;, ) def collate_fn(batch): input_ids = torch.stack([item[\u0026#34;input_ids\u0026#34;] for item in batch]) attention_mask = torch.stack([item[\u0026#34;attention_mask\u0026#34;] for item in batch]) labels = torch.stack([item[\u0026#34;labels\u0026#34;] for item in batch]) result = { \u0026#34;input_ids\u0026#34;: input_ids, \u0026#34;attention_mask\u0026#34;: attention_mask, \u0026#34;labels\u0026#34;: labels, } if \u0026#34;pixel_values\u0026#34; in batch[0]: result[\u0026#34;pixel_values\u0026#34;] = torch.cat( [item[\u0026#34;pixel_values\u0026#34;] for item in batch], dim=0 ) if \u0026#34;image_grid_thw\u0026#34; in batch[0]: result[\u0026#34;image_grid_thw\u0026#34;] = torch.cat( [item[\u0026#34;image_grid_thw\u0026#34;] for item in batch], dim=0 ) return result trainer = Trainer( model=model, args=training_args, train_dataset=dataset, data_collator=collate_fn, ) print(\u0026#34;开始训练...\u0026#34;) trainer.train() print(\u0026#34;训练完成！\u0026#34;) TrainingArguments 里有一个必须强调的参数：remove_unused_columns=False。HF Trainer 默认会把它不认识的字段从 batch 里删掉，对 VLM 来说 pixel_values 和 image_grid_thw 都会被误删，训练时就会报\u0026quot;模型收不到图片\u0026quot;的错。这是 VLM 微调踩得最多的一个坑，如果忘了关这个开关，整套流程都会失败。\n其余几个超参数的选择逻辑：\nper_device_train_batch_size=1，因为 T4 显存有限，batch 大于 1 容易 OOM。 gradient_accumulation_steps=4，等价于每 4 个 step 才更新一次参数，相当于把 batch size 放大到 4，让梯度更平稳。 learning_rate=1e-4，LoRA 比全参微调能承受更大的学习率，1e-4 是常用经验值。 fp16=True，混合精度训练，进一步节省显存和加速。 跑起来之后会看到 loss 从 3 点几一路下降，3 个 epoch 后基本能降到 1 以下。整个训练在 T4 上大概 1-2 分钟，非常快。\n第八步：测试微调效果\n训练完了得验证一下到底有没有\u0026quot;学进去\u0026rdquo;。我们专门拿第四步里那个\u0026quot;答案不一致\u0026quot;的问题来问：\npythonmodel.eval() test_image = load_image_from_url( \u0026#34;https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/car.jpg\u0026#34; ) messages = [ { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: [ {\u0026#34;type\u0026#34;: \u0026#34;image\u0026#34;, \u0026#34;image\u0026#34;: test_image}, {\u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;这辆车停在什么地方？\u0026#34;} ] } ] text = processor.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) inputs = processor( text=[text], images=[test_image], return_tensors=\u0026#34;pt\u0026#34; ).to(\u0026#34;cuda\u0026#34;) with torch.no_grad(): output = model.generate(**inputs, max_new_tokens=100) response = processor.decode(output[0], skip_special_tokens=True) print(\u0026#34;微调后回答：\u0026#34;) print(response.split(\u0026#34;assistant\u0026#34;)[-1].strip()) 输出：\n微调后回答： 这辆车停在人行道上。 回想第三步未微调时，模型描述里也提到了\u0026quot;人行道\u0026quot;，而我们训练数据里给的答案是\u0026quot;路边的停车位\u0026quot;。从结果看，3 个 epoch、3 条数据的小规模 LoRA 还没把模型的认知完全扭转过来，这其实很正常，数据量太少。如果换成几百条结构一致的训练样本，模型基本上会被你训练数据里的答案\u0026quot;带跑\u0026quot;。这一步如果想感受 LoRA 的威力，可以试着把同一个问题的训练样本扩成 20 条，再跑 5 个 epoch 看看变化。\n第九步：保存 LoRA 权重到云端硬盘\nColab 的运行时是临时的，会话一断所有文件都会消失。要想下次还能用，必须把权重保存到 Google Drive：\npythonfrom google.colab import drive drive.mount(\u0026#39;/content/drive\u0026#39;) save_path = \u0026#34;/content/drive/MyDrive/qwen_vl_lora_weights\u0026#34; model.save_pretrained(save_path) processor.save_pretrained(save_path) print(f\u0026#34;已保存到 {save_path}\u0026#34;) 第一次执行 drive.mount 会弹出一个授权窗口，需要登录 Google 账号并允许 Colab 访问云端硬盘。授权之后，/content/drive/MyDrive/ 就映射到了你的云盘根目录。\n保存的内容只有 LoRA 的 $A, B$ 两个小矩阵，加上一份 adapter_config.json，总共也就十几兆——这就是前面 LoRA 章节里说的\u0026quot;存储开销小\u0026quot;的实际体现。下次想恢复时，重新加载基础 Qwen2-VL-2B，再用 PeftModel.from_pretrained(model, save_path) 把 LoRA 权重套上去就行。\n到这里，一次完整的 Qwen2-VL LoRA 微调就跑完了。从启动 Colab、连接 GPU、配置环境，到模型加载、数据准备、LoRA 注入、训练、推理验证、权重持久化，整个链路都在一台免费的 T4 上完成。\n进一步深化 # 上面的流程跑通之后，真正要把它用在自己的项目里，还有三件事可以继续深入。这里只点到为止，给各位读者一个继续探索的方向。\n换成自己的数据集\n教程里示例的三条数据只是 demo。真实场景下，数据通常以 .json 或 .jsonl 的形式存在云盘里，每行一条 {\u0026quot;image\u0026quot;: \u0026quot;xxx.jpg\u0026quot;, \u0026quot;question\u0026quot;: \u0026quot;...\u0026quot;, \u0026quot;answer\u0026quot;: \u0026quot;...\u0026quot;}。把第四步的 raw_data 换成从文件读取就行：图片可以放在 Drive 同目录下，用 PIL.Image.open 加载本地路径；如果是公开 URL，沿用 load_image_from_url 也可以。\n经验上，几百条到一两千条针对性的样本就能让模型在特定任务(比如商品描述、医学影像问答、表格识别)上有肉眼可见的提升。数据质量远比数量重要：宁可花一周时间精修 500 条，也别一股脑塞 5000 条噪声进去。\n调整 LoRA 超参数\n教程里用的是比较保守的一组：r=8, alpha=16, dropout=0.05。如果发现训练 loss 降不下来、模型学不动，可以试着把 r 提到 16 或 32，让 LoRA 容量更大；如果发现训练 loss 很低但推理时表现没改善，多半是过拟合了，可以把 dropout 提到 0.1，或者减少 epoch 数。\ntarget_modules 也值得多试试。教程里加了四个注意力投影矩阵，如果还想让模型在视觉理解上有更大变化，可以把 FFN 层(gate_proj, up_proj, down_proj)也加进去；如果显存吃紧，反过来只留 q_proj, v_proj 也能跑。learning_rate 一般在 5e-5 ~ 2e-4 之间挑，loss 震荡得厉害就调小，下降得太慢就调大。\n推理时合并 LoRA 权重以减少延迟\n前面 LoRA 章节提过两种推理方式，教程里默认用的是\u0026quot;不合并\u0026quot;——get_peft_model 包出来的模型每次前向都要跑两条路径(原始 $W_0$ + LoRA 旁路)，每层多一次小矩阵乘法。\n如果任务已经固定下来要部署到生产环境，最好的做法是把 LoRA 直接 merge 回基础权重：\npythonmerged_model = model.merge_and_unload() merged_model.save_pretrained(\u0026#34;./qwen_vl_merged\u0026#34;) merge_and_unload 会就地把 $BA$ 加到 $W_0$ 上，然后把 LoRA 那几层\u0026quot;卸载\u0026quot;掉，得到的就是一份和原始模型结构完全一样的新权重。推理时不再有额外的矩阵乘法，延迟和原始 Qwen2-VL-2B 完全一致。代价是合并之后就回不到\u0026quot;热插拔多份 LoRA\u0026quot;的状态了，所以多任务场景下还是保留分离形式更好。\n写在最后 # 整篇博客回头看，做的事情其实很简单的：用一张免费的 T4，让一个 22 亿参数的视觉-语言模型在我们自己的数据上学会一点新东西。\n为了做到这件事，我们把它拆成了三块完成：先讲清楚 Google Colab 这个白嫖算力的平台怎么用、GPU 怎么连；再花了大半篇幅讲 LoRA 的原理，从\u0026quot;为什么需要\u0026quot;、\u0026ldquo;核心思想\u0026rdquo;、\u0026ldquo;关键超参数\u0026quot;一路讲到\u0026quot;LoRA为什么有效\u0026rdquo;；最后用 9 个 cell 把整个微调流程跑完，连同 bitsandbytes 重启、remove_unused_columns=False 这些新手经常出问题的地方也提了出来。\n如果你是第一次接触大模型微调，能跟着把这套流程跑通，那你已经走过了万事开头难的第一步。掌握基本流程再配合你手边的各种AI和Agent，你可以很轻松的将这套简单的demo搬到你公司或实验室的服务器上进行进一步训练微调，剩下的就是确定base model、换数据、调参、不断试错的工程问题了。微调本身从来不是终点，它只是让通用大模型贴合你具体场景的一种手段。真正决定效果的，还是你能不能想清楚\u0026quot;我要让模型学什么\u0026quot;。\n如果你正在迷茫如何入门LLM、VLM、vLLM，希望这篇博客能帮到屏幕前的你。如果跑通了，欢迎把你的微调成果分享出来，如果遇到了奇怪的问题报错，也欢迎在评论区留言，让我们共同讨论。\nipynb源代码分享：(https://colab.research.google.com/drive/14REy0PyFJetMb5j1fmP6zYI8C7z2rx6o?usp=sharing)\n","date":"2026年05月23日","externalUrl":null,"permalink":"/Owen-Studio/posts/how-to-configure-vlm-using-google-colab/","section":"Posts","summary":"","title":"如何使用Google Colab进行视觉-语言模型微调","type":"posts"},{"content":"","date":"2026年05月23日","externalUrl":null,"permalink":"/Owen-Studio/categories/multimodal/","section":"Categories","summary":"","title":"视觉多模态","type":"categories"},{"content":"","date":"2026年05月12日","externalUrl":null,"permalink":"/Owen-Studio/tags/rf-detr/","section":"Tags","summary":"","title":"RF-DETR","type":"tags"},{"content":" 前言 # 在上一期中我们着重介绍了 RT-DETR 这一模型，并且解释了什么是 DETR 的问题。今天我们继续来看 DETR 的变种模型——RF-DETR。\nRF-DETR于2025年11月发表于论文《RF-DETR: Neural Architecture Search for Real-Time Detection Transformers》，由Roboflow公司和CMU联合提出，被 ICLR 2026所收录，据官方口径，RF-DETR基于 DINOv2 视觉 Transformer 骨干网络，在 COCO 和 RF100-VL 上实现了最先进的精度-延迟权衡。是截至目前（2026.5），实时检测领域的 SOTA 方法。\n按照惯例，我们今天仍然将介绍了 RF-DETR 的backbone、neck、Encoder/Decoder以及 RF-DETR 的独创之处——NAS（神经架构搜索）。不了解 DETR 的读者请见上一篇博客《详解RT-DETR》。另外由于DINOv2自监督模型的特殊性，本博客还会涉及到一些强化学习和自监督学习的基础知识。\n详解 RF-DETR # 背景与定位 # 模型名字中的“RF”就是Roboflow的缩写，是Roboflow公司开发的实时目标检测模型。其设计目的主要是解决开放词表检测器（YOLO-World）和专用检测器（YOLO26、RT-DETR）精度和推理速度之间的矛盾。\n网络结构 # Backbone：DINOv2——自监督预训练ViT # DINO的名字来源于 Self-DIstillation with NO labels，其核心思路是“不用label，用蒸馏来预训练”。 DINOv2 是 Meta AI 2023 年发布的自监督 Vision Transformer 模型（结构几乎与 ViT 一致），从 1.42 亿张无标签图像里学到了一套泛用性和鲁棒性都很强的视觉特征，大部分下游任务不微调也能直接用。\n论文《DINOv2: Learning Robust Visual Features without Supervision》\n为什么要自监督？\n在讲 DINOv2 怎么训练之前，我们先想想：为什么 RF-DETR 不像 RT-DETR 那样选用一个在 ImageNet 上有监督预训练的 ResNet/HGNetv2，而要选一个“没人告诉它图里是什么”的模型？\n有监督预训练有一个绕不开的问题——标签会限制模型看到的世界。在 ImageNet 上做 1000 类分类，模型最终学到的特征会高度偏向“可分类”这件事，对类别相关的纹理、轮廓非常敏感，但对“类别外”的几何关系、空间结构、细粒度部位的差异往往一笔带过。检测任务恰恰需要后者：模型既要分清楚是什么，更要知道在哪儿、长什么样、和周围怎么挨着的。\n而自监督训练的训练思路完全不同，它不告诉模型“这是猫”，而是让模型去学习两个问题：同一张图被裁剪、抖动、模糊后，你还认不认得它是同一个东西？图里某一块被遮住了，你能不能根据上下文把它脑补出来？ 当一个模型能稳定地回答这两个问题，它必然学到了一组语义+几何+上下文都很扎实的通用特征。这正是检测器最想要的 backbone。\n自蒸馏的核心思想\nDINO/DINOv2 的训练范式叫做自蒸馏（Self-Distillation）。蒸馏这个词大家应该不陌生，传统的知识蒸馏需要一个已经训好的大模型当老师，让小学生模仿老师的输出。但 DINO 这里玩了个花招：老师和学生是同一个模型，只不过老师的参数是学生参数的“滑动平均”版本。\n我们准备两个结构完全一样的网络，记作 Student $f_{\\theta_s}$ 和 Teacher $f_{\\theta_t}$。Student 走标准反向传播更新参数；Teacher 不接收梯度，它的参数 $\\theta_t$ 由 Student 参数 $\\theta_s$ 的指数移动平均（EMA） 得到：\n$$\\theta_t \\leftarrow \\lambda \\cdot \\theta_t + (1 - \\lambda) \\cdot \\theta_s$$其中 $\\lambda$ 是接近 1 的常数（DINOv2 中通常从 0.994 余弦上升到 1.0）。这样做的好处很直观：Teacher 始终是 Student 的滑动平均版本，输出更平滑，给 Student 提供了一个比自己当前状态更靠谱的拟合目标。学生对着老师学习，损失不断下降。\nMulti-crop：同一张图，两种视角\n有了师生结构，下一个问题是：给 Student 和 Teacher 看什么？学什么？\nDINOv2 采用了一种叫 Multi-crop 的输入策略。对同一张图，随机生成两类视图：\nGlobal crops：2 个大视角，分辨率 $224 \\times 224$，覆盖图像 50% 以上的区域，保留了完整的语义结构。 Local crops：若干个小视角（默认 8 个），分辨率 $96 \\times 96$，只覆盖图像 5%–50% 的局部区域，可能只是一只眼睛、一片叶子。 关键规则：所有视图（大+小）都喂给 Student，但只有 global crops 喂给 Teacher。换句话说，Teacher 看到的是“一棵完整的树”，Student 看到的是“一片叶子”，然后我们让 Student 回答：你这片叶子的特征，和 Teacher 看到的整棵树，是不是一致的？\n这一不对称设计强迫模型学到局部—全局一致性（local-to-global correspondence）——也就是无论我看到的是整体还是局部，我应该都能映射到同一个语义。这种性质对检测尤其重要，因为检测器面对的是图中各种尺度、各种位置的局部目标，建立起局部全局的一致性能大大降低误检漏检的几率。\n两个训练目标：DINO Loss + iBOT Loss\nDINOv2 在 DINOv1 的基础上，把两条自监督路线合并了起来：一条管图像级语义（DINO loss），一条管 patch 级细节（iBOT loss）。\n1. 图像级目标——DINO Loss\n对每一个视图，网络末端接一个投影头（projection head），输出一个 $K$ 维的向量（默认 $K = 65536$），再过 softmax 得到一个概率分布。我们把 Student 在视图 $x$ 上的输出记作 $P_s(x)$，Teacher 在视图 $x'$ 上的输出记作 $P_t(x')$，则 DINO loss 是两者之间的交叉熵：\n$$\\mathcal{L}_{\\text{DINO}} = -\\sum_{x' \\in \\mathcal{V}_g}\\ \\sum_{x \\in \\mathcal{V},\\ x \\neq x'}\\ P_t(x') \\cdot \\log P_s(x)$$其中 $\\mathcal{V}_g$ 是 global crops 集合，$\\mathcal{V}$ 是所有视图集合。直观理解就是：Teacher 给一个 global view 输出一个软分布，Student 看到的任何其他视图（无论大小）都要预测出同样的软分布。\n这里有一个非常关键的工程细节，叫做centering + sharpening，用来防止训练坍缩（collapse），输出一个常数分布或者全部塌缩到某一维。\nCentering：对 Teacher 的输出做一个滑动平均的减项，把分布从“被某一维主导”往“均匀分布”拉。 Sharpening：对 Teacher 输出的 softmax 用一个非常低的温度（$\\tau_t \\approx 0.04$–$0.07$，正常是 $1$），让分布变尖锐，避免被 centering 拉成完全均匀。 两者一拉一推，使 Teacher 的输出始终保持在“既有区分性又不过度集中”的状态，训练得以稳定进行。\n2. Patch 级目标——iBOT Loss\nDINO loss 只关心整张图的 [CLS] token 分类表示，对 ViT 输出的众多 patch token 没有直接监督。DINOv2 引入 iBOT 的思路来补上这一块：\n具体做法是 Masked Image Modeling（MIM），类似 BERT 的“完形填空”。把 Student 输入图像中的部分 patch 随机 mask 掉，让 Student 仅根据可见的 patch 去预测被遮住位置的特征；而 Teacher 看到的是未被 mask 的完整图像，它对应位置的 patch 输出就作为软标签。\n记被 mask 的 patch 位置集合为 $\\mathcal{M}$，则 iBOT loss 写作：\n$$\\mathcal{L}_{\\text{iBOT}} = -\\sum_{i \\in \\mathcal{M}}\\ P_t^{(i)}(x)\\cdot \\log P_s^{(i)}(\\tilde{x})$$其中 $\\tilde{x}$ 是被 mask 后的图像，$P^{(i)}$ 表示第 $i$ 个 patch 位置的输出分布。这样一来，模型不仅要在整体语义上对齐，每一个 patch 自己也得对得上，这迫使 backbone 学到位置敏感、细粒度的视觉表征——正好是检测器需要在 patch 网格上做密集预测的能力。\n最终的总损失就是两者加权求和：\n$$\\mathcal{L}_{\\text{total}} = \\mathcal{L}_{\\text{DINO}} + \\mathcal{L}_{\\text{iBOT}}$$数据：LVD-142M 的“筛子” 有了好的损失函数还不够，自监督最看重的另一件事是数据。DINOv2 团队没有简单地把网上爬来的图片一股脑塞进去，而是构建了一个叫 LVD-142M 的精选数据集。\n它的构建逻辑大致是：从一个约 25 亿张图片的原始池子出发，先用一组 curated 数据集（如 ImageNet-22k、Google Landmarks 等）作为“种子”，然后用一个预训练 ViT 提取所有图片的特征，对原始池子里的图片做最近邻检索 + 去重 + 类别均衡聚类，最后筛出 1.42 亿张分布均匀、质量较高、与种子语义接近的图片。\n这一过程很像“用筛子从泥沙里挑金子”，结果是：相比直接用未筛选的网络爬虫数据，LVD-142M 训练出来的 DINOv2 在下游任务上的表现明显更好，且完全不依赖任何人工标注。\n伪影问题与 Register Tokens\n把视角从训练机制拉回到 backbone 的输出本身——直接拿 DINOv2 出来一用，你会发现一个非常诡异的现象：在最后一层的 patch token 里，某些位置的特征向量范数（norm）异常巨大，比正常 patch 高出十倍甚至上百倍，可视化在 attention map 上就是几块刺眼的“亮斑”，被称为 ViT 的伪影（artifacts）。\n更怪的是，这些 high-norm 的异常 token 几乎全部出现在图像中信息量最低的背景区域——比如一面纯色墙、一片天空、一块桌面。本该最“无聊”的地方反而获得了最强的激活。\nMeta AI 在 2023 年的论文《Vision Transformers Need Registers》中对此给出了一个非常反直觉的解释：模型在训练过程中，自发地把这些它认为没用的背景 token 征用成了自己的“草稿纸”——用来存储一些全局性的、与具体位置无关的信息（比如全局上下文摘要、给 CLS 辅助使用的中间量等）。这是 ViT 在没有显式 scratchpad 机制下被迫做出的“自适应涌现”。\n这个行为对分类任务影响不大（CLS token 该怎样还怎样），但对密集预测任务（检测、分割、深度估计）就是灾难：因为 patch token 是直接被下游用作位置特征的，而这些位置的特征已经被模型挪作他用，相当于交付给检测头的地图里，被偷偷塞进了几块不属于地图本身的便签纸。RF-DETR 这种以每个 patch 为基本单元做预测的检测器，恰恰是受此影响最严重的一类。\n怎么修？ 答案出奇地简单——既然模型需要 scratchpad，那就显式地给它几张草稿纸。具体做法是：在 patch token 序列之外，再额外拼接 $N$ 个可学习的 Register Tokens（DINOv2 with Registers 默认 $N=4$）：\n$$\\text{Tokens} = [\\,\\text{CLS};\\ \\text{REG}_1,\\ \\ldots,\\ \\text{REG}_N;\\ \\text{patch}_1,\\ \\ldots,\\ \\text{patch}_{HW}\\,]$$这些 register tokens 和 patch tokens 一样参与所有的 Self-Attention 计算，也接收梯度更新，但在输出端被完全丢弃——既不参与下游的分类，也不送给检测头做密集预测。它们用完即弃，唯一的职责就是充当模型“想存什么就往里塞”的中转站。\n效果非常直接：\npatch token 上的 high-norm 伪影几乎完全消失，attention map 重新变得干净； 模型的“草稿”信息被引流到了 register tokens 上，patch token 重新专注于自己位置的语义； 在检测、分割等密集预测任务上，下游精度有显著且稳定的提升。 这也是为什么 RF-DETR 默认采用带 Registers 版本的 DINOv2，并在 NAS 搜索空间里专门留了一条“是否启用 register tokens”的开关——它确实是一个会显著影响下游精度、却几乎不增加延迟的关键设计。\n为什么 RF-DETR 选 DINOv2\n回过头来看 RF-DETR 的选择，就一目了然了：\n强通用特征：DINOv2 backbone 在分类、分割、深度估计、检测等任务上几乎无需微调即可达到接近 SOTA 的性能，迁移到检测任务的“起点”非常高。 patch 级表征丰富：iBOT 路线让每个 patch 的 token 都有清晰的语义和定位信息，正好契合 DETR 风格检测器以 patch 序列为 Encoder/Decoder 输入的范式。 训练成本边际效益高：检测数据集（COCO、RF100-VL）规模有限，从一个见过 1.42 亿张图的 backbone 开始 fine-tune，比从 ImageNet 的 1.28M 起步要划算得多。 至于 DINOv2 如何被 RF-DETR 进一步剪裁、改造以满足实时性约束，我们将在 NAS 一节中详细展开。\nNeck：Projector # 这是RF-DETR的关键设计，用于将ViT单尺度($40\\times40\\times768$)的输出魔改为多尺度特征，同时兼顾实时推理的轻量化。\n从示意图的原理上看，Projector 远比FPN、PAnet等传统跨尺度融合模式要简单得多。只是对同一张图做了三种分辨率变换，能够这样操作的原因是DINOv2每一层都已经有了全局感受野，天然混合有细节和语义，并不需要FPN那样的跨层信息传递。\nEncoder：窗口注意力 + 全局注意力交替 # 窗口注意力：在我前几期博客中的Swin Transformer讲解中有详细讲解，这里简单的带过。\n把特征图切成 $W\\times W$的小窗口（用NAS搜索出来，一般为两个窗口），每个token只和窗口内的token做 Self-Attention。\n全局注意力层：完整的全局注意力计算，让跨窗口的远距离token建立联系，弥补窗口注意力的盲区。\n两者在Encoder中交替叠加，局部感知能力和全局语义能力兼得，同时可以减少使用纯全局注意力的计算量，更好地进行实时推理。\nDecoder：标准结构 + 可剪枝层 # 看到这一节标题先别紧张——RF-DETR 的 Decoder 其实和上一篇博客《详解 RT-DETR》中的 Decoder 几乎完全一致，这里我们把基础部分快速过一遍，重点讲 RF-DETR 在这块多出来的一处独到设计：Decoder Layer Pruning（解码器层级剪枝）。\n与 RT-DETR 一致的部分\nQuery 来源：依旧是 IoU-aware Query Selection——从 Encoder 输出的全部特征里，按 \u0026ldquo;分类分数 × IoU 预测\u0026rdquo; 综合排序，挑出 Top-K（默认 300）个作为 object queries 的内容初始值，对应空间坐标作为初始参考框。 Decoder Layer 结构：标准三件套——Self-Attention（query 之间互相算特征）、Cross-Attention（query 去看 Encoder 输出的多尺度特征）、FFN（非线性增强）。 训练匹配：每次前向用匈牙利算法做一对一匹配，正样本算分类+L1+GIoU，负样本只算分类（背景类）。 彻底摆脱 NMS：一对一匹配让每个 query 学会\u0026quot;我只负责一个目标\u0026quot;的 concept，Decoder 里的 Self-Attention 又让 query 之间互相回避——推理时直接取置信度最高的若干预测即可。 这些点上一期已经从头到尾讲透了，不再赘述。不熟悉的读者请回头翻一下 RT-DETR 那一章的损失函数小节。\nRF-DETR 的新东西：Decoder Layer Pruning\nRT-DETR 的 Decoder 固定 6 层，训练时每一层都接独立的预测头 + 独立的匈牙利匹配 + 独立的损失，但推理时只用最后一层的输出，前 5 层的预测头被全部丢弃。这是经典 DETR 的辅助损失（auxiliary loss）做法——它让中间层也能拿到直接的梯度监督，加速收敛、稳定训练，但中间层的预测头在部署时纯属\u0026quot;训练阶段的脚手架\u0026quot;。\nRF-DETR 在这一基础上多走了一步，既然每一层都被监督过、都能独立输出一组合法的检测结果，那为什么推理时一定要走完所有层？\n答案就是 Decoder Layer Pruning：推理时可以在任意一层提前\u0026quot;出口\u0026quot;，丢掉后续所有层。\n设训练时 Decoder 总共有 $L$ 层，每一层 $l$ 都有自己的预测头 $\\text{Head}_l$ 与对应损失 $\\mathcal{L}^{(l)}$，总损失：\n$$\\mathcal{L}_{\\text{decoder}} = \\sum_{l=1}^{L} \\mathcal{L}^{(l)}$$部署时则只走前 $l^\\star$ 层（$l^\\star \\leq L$），用 $\\text{Head}_{l^\\star}$ 的输出做最终预测，后续 $L - l^\\star$ 层连同它们的预测头一起直接砍掉：\n$$\\text{Output} = \\text{Head}_{l^\\star}(\\,z^{(l^\\star)}\\,)$$这相当于训练时\u0026quot;养着一棵 6 层的树\u0026quot;，部署时\u0026quot;想截到哪一节就截到哪一节\u0026quot;。在 NAS 搜索阶段，$l^\\star$ 也是一个可搜的维度——延迟预算紧的部署场景（Nano、Small）会自动选较小的 $l^\\star$，精度优先的场景（Large）则保留满层。\n为什么这招能 work？\n关键在于辅助损失把每一层都训练成了独立可用的预测器。如果没有辅助损失，中间层的输出根本不是\u0026quot;完整的检测结果\u0026quot;，砍掉后面就会引发混乱。而 RF-DETR 在训练时让每一层都走完整套匈牙利匹配和损失计算，等于把 $L$ 个不同精度档位的 Decoder 同时塞进了一份权重里。\n这背后其实是和 NAS 那一节同源的思想——一次训练，多档输出。Backbone 那边用 weight-sharing supernet 实现\u0026quot;训一次切多种尺寸\u0026quot;，Decoder 这边用辅助损失 + layer pruning 实现\u0026quot;训一次切多种深度\u0026quot;，两者拼到一起，就成了 RF-DETR 灵活到几乎离谱的部署能力。\nNAS：让模型自动搜索最优配置 # 聊到这里，RF-DETR 的“肉身”已经搭得差不多了：DINOv2 当 backbone、Projector 把单尺度拆成多尺度、Encoder 里窗口与全局注意力交替……但你有没有发现一件事——这些模块里到处都是“调参点”：backbone 用前几层？Projector 输出几个尺度？Encoder 堆多少层？窗口开多大？输入分辨率多少？这些问题在我们上面介绍的时候完全没有提及，因为这并不是由训练人决定的，而是用过模型内在的自动优化机制。\n任何一个数字往上下挪一点，精度和延迟都会跟着变。手动一个个试，是 RT-DETR 时代的做法（人工实验搞出来 -R18/-R34/-R50/-L/-X 一堆变体），但这一套放到 2026 年实在过于原始。RF-DETR 的回答是：把这件事交给搜索算法——也就是 NAS（Neural Architecture Search，神经架构搜索）。\nNAS 的“三件套” 要理解 RF-DETR 做了什么，得先知道一个标准 NAS 系统通常长什么样。它由三个部分组成：\n搜索空间（Search Space）：定义“候选架构长什么样”。比如某层的通道数能从 $\\{64, 128, 256\\}$ 中选、深度能在 $[6, 12]$ 之间取。 搜索策略（Search Strategy）：从这个庞大的空间里挑出有希望的架构。早期的 NAS（如 Zoph \u0026amp; Le 2017 的 NASNet）用强化学习——把架构选择当作一个序列决策问题，让一个 RNN 控制器以策略梯度（REINFORCE）的方式逐 token 输出架构描述，把验证集精度当作 reward 回传给控制器更新策略。后续也出现了进化算法、贝叶斯优化、可微分 NAS（DARTS）等多种路线。 性能评估（Performance Estimation）：给每个候选架构一个分数。最朴素的做法是把它从头训完，但那样代价是天文数字（NASNet 用了 800 块 GPU 跑了一个月）。 RF-DETR 走的是一条“一次训练，到处采样”的路子，不再像经典 RL-NAS 那样每个架构独立训练，但思路里仍然保留了“search 空间 + 搜索器 + 评估”这三件套的骨架。\nRF-DETR 的核心思路：Weight-sharing Supernet 为什么经典 NAS 那么慢？原因很简单——每评估一个架构，都要把它从零训一遍。假设搜索空间里有 $10^5$ 个候选架构，每个都得训大量时间，这么多候选架构不可能训的完。\nRF-DETR 借鉴了 权重共享超网（Weight-sharing Supernet） 的思想（源自 BigNAS、Once-for-All 等工作）：\n设计一个最大版本的网络（Supernet），它包含搜索空间里所有候选结构的“并集”。比如 Encoder 最深可以是 12 层，那 Supernet 就有 12 层；Backbone 最宽 hidden_dim 是 768，那 Supernet 也就把它做到 768。 训练 Supernet 时，每一步随机采样一个子网络（subnet） 来做前向和反向传播；不同 subnet 之间共享对应位置的参数。 训练完成后，Supernet 就成了一个“万能仓库”——从里面任意切出一个 subnet，它都已经具备较好的精度，不需要重新训练。 打个比方：你不再是为每件衣服单独织一匹布，而是先织一大块预留了所有剪裁线的通用布，需要哪种尺寸的衣服时，照着尺寸直接裁下来就能穿。\n这一招把“训练成本”和“评估候选数”彻底解耦——训练一次，评估十万次都行。这才让真正大规模的架构搜索成为可能。\n搜索空间：RF-DETR 到底在搜什么？\nRF-DETR 的搜索维度横跨从输入到输出的整条链路，可以粗略分成四类：\n模块 可搜的维度 备注 输入 图像分辨率（如 $448$, $512$, $576$, $640$） 分辨率每加 64，token 数平方增长 Backbone（DINOv2） 使用的 ViT 层数（深度子集）、是否使用 register tokens、patch size 不改 hidden_dim，避免破坏预训练权重 Projector 每个尺度的输出通道数、下采样的具体配置 控制 Neck 的轻重 Encoder 总层数、窗口/全局注意力的交替模式、窗口大小 $W$、FFN 扩展比等 决定推理延迟的大头 Decoder 的 query 数量、层数也在搜索范围之内，但权重影响相对较小。\nbackbone 不轻易改宽度这一条单独说一下。原因前面 DINOv2 一节已经讲过——hidden_dim 和投影头的预训练权重是高度耦合的，改宽度等于把 DINOv2 的 1.42 亿张图“白练了”。所以 RF-DETR 在 backbone 这一侧只敢“变浅”不敢“变窄”：用前 $k$ 层（$k \\in \\{6, 8, 10, 12\\}$）当作 backbone 即可，权重直接复用 DINOv2 的对应层。\nSupernet 的训练：怎么让一块布能裁出所有衣服？ 最大的难点在于：如果只随机采一个 subnet 来训，那些不被采到的“极端配置”（比如最深+最大窗口）就永远学不好。RF-DETR 采用了一种叫做 Sandwich Rule（三明治采样） 的策略（来自 BigNAS）——每一个 batch 内，同时训练以下几类 subnet：\n最大 subnet：Supernet 本身，确保上界被充分训练。 最小 subnet：搜索空间里最浅最窄的那个，确保下界也练到位。 随机 2 个中间 subnet：覆盖搜索空间的“腰部”。 四者共享同一份输入图像，前向各自走自己的路径，损失加起来一起反向传播：\n$$\\mathcal{L}_{\\text{supernet}} = \\mathcal{L}(\\text{max}) + \\mathcal{L}(\\text{min}) + \\sum_{i=1}^{2} \\mathcal{L}(\\text{rand}_i)$$这个写法保证了搜索空间任何一个点都不会被遗忘——最大和最小是“天花板和地板”，中间随机覆盖中间地带。\n此外还有两个工程细节：\nIn-place Distillation（原地蒸馏）：把最大 subnet 当作 Teacher，让较小的 subnet 在训练时不仅学 ground truth，还要学最大 subnet 的输出分布。等于把 DINOv2 学到的“自蒸馏”思想又复用了一遍——大网络给小网络当老师，让小网络的精度上限提升。 BN 重校准：因为不同 subnet 走的路径不同，每个 subnet 的 BatchNorm 统计量也不一样。Supernet 训完后，部署任何一个 subnet 前都要用一小批数据重新跑一次 BN 统计（实际上 RF-DETR 用的是 LayerNorm，在层层之间已经完整了Normalization，不需要这一步）。 搜索阶段：延迟感知的 Pareto 寻优\nSupernet 训练完成后，就到了真正的“搜索”环节。RF-DETR 关心的不只是“哪个架构精度最高”，而是 “在给定延迟预算下，哪个架构精度最高”——这是一个多目标优化问题。\n形式化地写，我们要在搜索空间 $\\mathcal{A}$ 中找出 Pareto 前沿：\n$$\\mathcal{P} = \\big\\{\\ a \\in \\mathcal{A}\\ \\big|\\ \\nexists\\ a' \\in \\mathcal{A}:\\ \\text{mAP}(a') \\geq \\text{mAP}(a)\\ \\wedge\\ \\text{Latency}(a') \u003c \\text{Latency}(a)\\ \\big\\}$$Pareto 前沿上的每一个点都是“在这个延迟下精度无法被压榨更高”的最优配置。\nRF-DETR 采用 进化算法（Evolutionary Search） 搜索 Pareto 前沿：\n初始化：随机采样数百个 subnet，组成初代种群。 评估：对每个 subnet，在 Supernet 上直接取权重，在验证集上跑一次得到 mAP；在目标硬件（如 T4 GPU、Jetson Orin）上跑一次得到真实延迟。注意这里没有任何“重训练”——评估一个架构的成本从“几天”降到了“几分钟”。 筛选：按 Pareto 支配关系挑出当前的 Pareto 前沿，作为下一代的父代。 变异 / 交叉：在父代的基础上小幅修改（改一层深度、改一次窗口大小），或者把两个父代的配置拼起来产生子代。 迭代：重复 2–4 若干轮，前沿会逐步“外推”——同样的延迟下精度更高，或同样的精度下延迟更低。 想象你站在一片散落着大大小小石头的海滩上，每块石头是一个 subnet，石头的高度代表它的精度，石头横向的位置代表它的延迟。Pareto 前沿就是从左往右扫视时，你看到的石头顶端构成的\u0026quot;天际线\u0026quot;——只有那些没有被其他更高更左的石头挡住的，才在天际线上。\n进化算法的每一次迭代，就像潮水悄悄上涨——一些原本露出的石头被淹没（被新的、更优的 subnet 支配），新的更高的石头从远处浮现（变异/交叉产生的新候选）。天际线随每一轮迭代被往上、往外推一段，但水位本身只升不降。\n顺带跟强化学习版的 NAS 对比一下：两者目标相同、策略不同。RL 用一个参数化策略 $\\pi_\\phi$ 来输出架构序列、用 REINFORCE 优化 $\\mathbb{E}_{a \\sim \\pi_\\phi}[R(a)]$；而进化算法不维护显式策略，靠种群和选择算子隐式地“爬山”。在权重共享的设定下，每次评估代价非常低廉，进化算法的 sample-efficiency 反而更友好——这也是近几年 NAS 圈子从 RL 主流回归到进化/随机搜索的根本原因。\n为何论文中提到必须在真实场景测试延迟？ NAS 论文里一个经常被忽视的坑是：FLOPs 不等于 Latency。两个 FLOPs 相同的架构，在同一块 GPU 上可能跑出 1.5 倍的延迟差，原因来自 memory bandwidth、kernel launch overhead、算子的 fusion 友好度等等。\nRF-DETR 直接走最朴实但最有效的路线——在目标硬件上实测延迟。具体做法是：\n提前枚举搜索空间里每个算子级配置的真实延迟（这一步可以离线一次性完成），构建一个 LUT（Look-Up Table）。 搜索时把整体延迟近似为 LUT 的加和： $$\\text{Latency}(a) \\approx \\sum_{l \\in a} \\text{LUT}[l]$$ Pareto 前沿上的候选最后再过一次端到端实测校准。 这样既保证了搜索过程中评估延迟的速度（毫秒级），又保证了最终选出的架构效率在边缘上不会华而不实。\n输出：一族而非一个\n最终 RF-DETR 不是产出一个固定的模型，而是沿着 Pareto 前沿切出一族模型：\n模型 适配延迟档位 典型部署 RF-DETR-Nano 最低延迟 移动端、Jetson Nano RF-DETR-Small 中低 Jetson Orin、边缘服务器 RF-DETR-Base 中等 T4 / A10 RF-DETR-Medium 中高 A10 / A100 半负载 RF-DETR-Large 最高精度 A100 满负载 / H100 这是 RF-DETR 相对 RT-DETR 最大的范式差异：RT-DETR 是“一个网络结构，配几个不同大小的 backbone”，RF-DETR 是“一次性给你一族沿 Pareto 前沿分布的架构，每个都是该延迟档位下的最优解”。换句话说，RT-DETR 给的是手工挑出的几件成衣，RF-DETR 给的是一整条裁缝流水线，照着你要的延迟尺码当场出活。\n小结：NAS 给 RF-DETR 带来了什么\n回到最初的问题——RF-DETR 凭什么号称“实时检测领域 SOTA”？这一节其实就是答案的核心：\nDINOv2 提供了强 backbone（特征质量天花板高）。 Projector + 窗口/全局交替 Encoder 提供了灵活的骨架（架构有大量可调维度）。 NAS 把这些维度自动调到了帕累托最优（在每个延迟档位上把性能压榨到极致）。 三者环环相扣：没有 1 的强 backbone，再调架构精度也上不去；没有 2 的灵活骨架，NAS 没东西可搜；没有 3 的自动搜索，2 的灵活性也只会变成调参噩梦。\n如何在 Roboflow 库中简单调用 RF-DETR 模型进行微调或推理 # 上一章节的 RT-DETR 我们借助的是 Ultralytics 这一“万能模型平台”，到了 RF-DETR 这边，Roboflow 自己也给出了一个非常干净的官方 Python 库——直接就叫 rfdetr。和 Ultralytics 一样，整套接口收敛到了 加载 → 推理 → 训练 → 导出 四个环节上，几乎零学习成本。\n安装 # bashpip install rfdetr 一行命令完事，模型权重下载、可视化、ONNX/TensorRT 导出工具链全部就位。如果要做 GPU 训练，确保 CUDA 版本与 PyTorch 匹配即可（这一点跟所有 PyTorch 项目都一样，不再赘述）。\n可用模型 # Roboflow 官方目前主要发布了 NAS 搜出来的几个 Pareto 前沿点位，对应的类名一目了然：\n模型类 参数量 COCO mAP50-95 适用场景 RFDETRNano ~14M 48.4 移动端、Jetson Nano RFDETRSmall ~20M 53.0 Jetson Orin、边缘 RFDETRBase ~29M 54.7 T4 / A10，默认首选 RFDETRMedium ~44M 56.3 A10 / A100 半负载 RFDETRLarge ~128M 60.5 A100 满负载 / H100 不知道选哪个的话，直接从 RFDETRBase 开始——它是精度与速度的甜点位，绝大部分通用场景下都不会让你失望。\nPython API：加载、推理、训练、导出 # pythonfrom rfdetr import RFDETRBase # 1. 加载预训练模型（首次运行会自动从官方下载权重） model = RFDETRBase() # 也可换成 RFDETRNano/Small/Medium/Large # 2. 推理：图片、视频、文件夹、URL、PIL.Image、numpy.ndarray 都支持 detections = model.predict(\u0026#39;your_image.jpg\u0026#39;, threshold=0.5) # detections 是一个 supervision 库的 Detections 对象， # 包含 xyxy 坐标、置信度、类别 id，可以直接用 supervision 做可视化 import supervision as sv image = sv.cv2_to_pillow(sv.cv2.imread(\u0026#39;your_image.jpg\u0026#39;)) annotated = sv.BoxAnnotator().annotate(image.copy(), detections) annotated.save(\u0026#39;output.jpg\u0026#39;) # 3. 训练：换上自己的数据集（COCO 格式） model.train( dataset_dir=\u0026#39;path/to/your_dataset\u0026#39;, epochs=100, batch_size=32, grad_accum_steps=1, lr=1e-4, output_dir=\u0026#39;runs/my_rfdetr\u0026#39;, ) # 4. 导出（部署阶段必用） model.export(format=\u0026#39;onnx\u0026#39;) # 也支持 \u0026#39;tensorrt\u0026#39;、\u0026#39;coreml\u0026#39; 数据集格式 # 和 Ultralytics 用 YOLO txt 格式不同，rfdetr 默认是 COCO 格式——一个根目录下三个子目录加各自的 _annotations.coco.json：\nyour_dataset/ ├── train/ │ ├── _annotations.coco.json │ └── *.jpg ├── valid/ │ ├── _annotations.coco.json │ └── *.jpg └── test/ ├── _annotations.coco.json └── *.jpg 如果你的数据集本来是 YOLO 格式或 Pascal VOC 格式，最简单的做法是直接上 Roboflow 网站建个项目，把数据传上去，下载的时候选 \u0026ldquo;COCO 格式\u0026rdquo; 就行了——Roboflow 本身就是干数据集这件事起家的，转换、清洗、标注、版本管理一条龙，对自家的 rfdetr 库是无缝转接了属于。\n一些训练上的小坑 # 学习率不要照搬 YOLO 经验：RF-DETR 的 backbone 是 DINOv2 预训练 ViT，对学习率比 ResNet/HGNetv2 更敏感。官方推荐 lr=1e-4、backbone 部分再额外降一档（库内默认配置已经处理好，自己改的时候要心里有数）。 batch size 不够就用梯度累积：DETR 系模型对 batch size 要求很高，换言之就是需要大显存显卡，笔者用的RTX4060 8GB完全没法用，用 grad_accum_steps 把\u0026quot;一次大batch的反向传播\u0026quot;拆成\u0026quot;N 次小batch的反向传播。官方建议的batch_size=32，那么你的batch_size与grad_accum_steps相乘等于32即可。 小目标场景把 query 数量调大：默认 300 个 query 在密集小目标（航拍、人群）上可能不够，库里有 num_queries 参数可调。 预训练权重很重要：和 RT-DETR 一样，DETR 系从头训成本极高，小数据集上务必从官方权重 fine-tune，别轻易尝试从零开始。 推理部署：直接 ONNX / TensorRT # model.export(format='onnx') 出来的 ONNX 已经包含了完整的后处理（无需手动写NMS——RF-DETR 本身就不要 NMS），可以直接喂给 ONNX Runtime 或 TensorRT。Jetson 边缘设备上推荐走TensorRT路线，能直接吃满 NAS 当初为该硬件挤出来的延迟优化收益。\nbash# CLI 也有等价命令 rfdetr export --model base --format onnx --output rfdetr-base.onnx 注意：以上内容具有时效性，rfdetr 库迭代较快，参数名和默认值请以官方文档为准。\nRF-DETR 实际应用中的 workflow # 讲到这里，前面分模块、分技术点讲了一大堆——DINOv2、Projector、Encoder、Decoder、NAS——但一定有读者读完后心里其实还抱有疑问：\nDINOv2 不是无标签自监督训练的吗？那我们用 COCO 这种带 GT 框的数据集微调，到底是怎么训的？ NAS 那一套又重又复杂，我每次微调自己的数据集时，也得跑一遍 NAS 吗？ 这两个问题问得都非常好，也是把 RF-DETR 真正用起来时绕不开的两个 conceptual blocker。这一节我们就把它们说清楚，顺便把整条 workflow 从 Meta 到 Roboflow 再到你的个人计算机的模式画出来。\n困惑的来源：两套训练范式的\u0026quot;叠加\u0026quot; # RF-DETR 的训练并不是\u0026quot;自监督\u0026quot;和\u0026quot;NAS\u0026quot;两件事杂糅在一起同时发生的，而是好几个独立阶段串联起来，每个阶段干自己的事，用自己的损失函数，互不打扰。用户拿到模型时，前面所有阶段都已经被前人替你跑完了，你做的只是流水线最后一步。\n要把这条链路搞清楚，我们顺着时间线从最上游开始讲起。\n完整五阶段流水线 # ┌──────────────────────────────────────────────────────────────┐ │ 阶段 1：DINOv2 自监督预训练 │ │ 执行者：Meta AI（2023） │ │ 数据：LVD-142M（1.42 亿张无标签图像） │ │ 损失：DINO Loss + iBOT Loss（自蒸馏，无 GT） │ │ 产物：会\u0026#34;看图\u0026#34;的 ViT 主干权重 │ └──────────────────────────────────────────────────────────────┘ ▼ 加载 ViT 权重（丢弃投影头/teacher） ┌──────────────────────────────────────────────────────────────┐ │ 阶段 2：NAS Supernet 训练 │ │ 执行者：Roboflow（2025） │ │ 数据：Objects365 / COCO 等大规模检测数据集 │ │ 损失：检测损失（匈牙利匹配 + 分类 + L1 + GIoU） │ │ 产物：一个\u0026#34;万能仓库\u0026#34;超网，每个 subnet 都已被训过 │ └──────────────────────────────────────────────────────────────┘ ▼ 超网上跑进化算法 ┌──────────────────────────────────────────────────────────────┐ │ 阶段 3：NAS 搜索（Pareto 前沿） │ │ 执行者：Roboflow │ │ 产物：5 个固定架构 → Nano / Small / Base / Medium / Large │ └──────────────────────────────────────────────────────────────┘ ▼ 每个架构再做完整训练 ┌──────────────────────────────────────────────────────────────┐ │ 阶段 4：5 个架构各自完整训练 │ │ 执行者：Roboflow │ │ 数据：COCO │ │ 产物：5 个 .pt 预训练权重文件（pip install 后下载的就是这个） │ └──────────────────────────────────────────────────────────────┘ ▼ 发布 ───────────────────────────── 分界线 ───────────────────────── ▼ ┌──────────────────────────────────────────────────────────────┐ │ 阶段 5：用户微调（你做的事） │ │ 执行者：你 │ │ 数据：你自己的 COCO 格式数据集 │ │ 损失：检测损失（和阶段 4 完全相同） │ │ 学习了什么：只动权重，架构是固定的 │ └──────────────────────────────────────────────────────────────┘ 仔细看上面的workflow，你会发现\u0026quot;自监督\u0026quot; 只发生在阶段 1，\u0026ldquo;NAS\u0026rdquo; 只发生在阶段 2-3，到了阶段 5 你做的事情就是纯粹的有监督微调，和训练 RT-DETR、YOLO 在数学上没有任何本质差异。前两件事都是 Meta 和 Roboflow 替你做的\u0026quot;重活\u0026quot;，你拿到的是已经被打磨成形的最终成品（权重文件）。\n第一个问题：自监督预训练 → 有监督微调，到底怎么衔接？ # DINOv2 的自监督训练和用 COCO 微调，是两个完全分开的阶段，用两套完全不同的损失函数。\n我们把镜头拉到 RF-DETR 的完整结构上看看权重的来源：\n图片 │ ▼ [ DINOv2 ViT Backbone ] ←── 权重从 DINOv2 预训练加载（阶段 1 的产物） │ ▼ patch tokens [ Projector ] ←── 权重在阶段 2 的 NAS 训练中得到 │ ▼ 多尺度特征 [ Encoder ] ←── 同上 │ ▼ [ Decoder + Heads ] ←── 同上 │ ▼ 预测框 + 类别 Backbone：从 DINOv2 加载，自监督学到的特征提取能力全部体现在这里。 其他所有模块（Projector、Encoder、Decoder、分类/回归头）：在阶段 2-4 的 NAS 检测训练中初始化并训练好——这些组件DINOv2 backbone训练时根本不存在。 到了阶段 5 你微调时，损失函数就是标准的检测损失，具体可以参考 RT-DETR 那一篇博客给过的公式：\n$$\\mathcal{L}_{\\text{finetune}} = \\sum_{j=1}^{M}\\left[\\mathcal{L}_{\\text{cls}}(c_j, \\hat{p}_{\\hat\\sigma(j)}) + \\mathbb{1}_{\\{c_j \\neq \\varnothing\\}} \\cdot \\mathcal{L}_{\\text{box}}(b_j, \\hat{b}_{\\hat\\sigma(j)})\\right]$$完全用上了 COCO 的 GT 框和类别标签。没有任何 \u0026ldquo;DINO Loss\u0026rdquo; 或 \u0026ldquo;iBOT Loss\u0026rdquo; 的影子——那两个损失函数在阶段 1 用完就退休了。\n第二个问题：NAS 也参与微调阶段吗？ # 答案是不会的。NAS 在阶段 2-3 跑完后，架构就被固定下来了。当你写下：\npythonfrom rfdetr import RFDETRBase model = RFDETRBase() 这一行构造函数里所有的层数、通道数、窗口大小、是否带 register tokens、Decoder 截到第几层……全是 Roboflow 在阶段 3 NAS 搜出来后固化在源码里的。你看到的 Python 类只是这些\u0026quot;出厂配置\u0026quot;的一个 “展示橱窗”。\n打开 rfdetr 库的训练接口，你能配的参数只有这些：\npythonmodel.train( dataset_dir=..., epochs=..., batch_size=..., lr=..., grad_accum_steps=..., ... ) 全是训练超参（hyperparameters），仅影响\u0026quot;权重怎么更新\u0026quot;。\n而 NAS 影响的是架构超参（architectural hyperparameters）——影响\u0026quot;模型长什么样\u0026quot;。两类参数在概念上是完全分离的，微调只动前者，不动后者。\n为什么不让用户跑 NAS？\n理论上可以，但成本完全不现实：\nSupernet 训练：要在 Objects365 这种数千万张图的检测数据集上训，至少几十张 A100 跑一两周。比训整个 RF-DETR-Large 还要贵。 延迟测量需要目标硬件：评估 subnet 延迟时要在最终部署的硬件上实测。想为 Jetson Orin 优化你就得有 Jetson Orin，想为 A100 优化就得有 A100。 你的数据集太小：用户自定义数据集通常只有几千到几万张图，撑不起 NAS 那种\u0026quot;训超网 + 评估上万 subnet\u0026quot;的统计需求——NAS 搜出来的架构没有统计显著性。 所以 Roboflow 的设计哲学非常务实：NAS 这种重型计算搜索一次就好，把搜索到的不同架构开源出来供所有用户自用。\n那如果 5 档都不满意呢？\n有两种办法：\n介于两档之间：比如 Base 不够快、Small 又不够准。直接用 Base 微调，部署时启用 Decoder Layer Pruning——前面 Decoder 那节讲过，推理时截到第 4 层而不是第 6 层，相当于在 Base 和 Small 之间动态滑一档。这是 RF-DETR 唯一让用户在部署时还能\u0026quot;切架构\u0026quot;的口子。 真要自定义架构：跳出 rfdetr 高层 API，去官方 GitHub 仓库找 supernet 训练脚本。这条路是开放的，但你得准备好 Objects365 数据集 + 一个昂贵的训练服务器群——大多数情况下不值得。 99.99% 的用户都会停在选项 1，Layer Pruning 给出来的中间档位已经覆盖了绝大多数延迟需求。\n实战 workflow 总结 # 这条 workflow 想清楚以后，你就能理解为什么 RF-DETR 既\u0026quot;复杂\u0026quot;（论文里讲一堆 DINOv2、NAS、supernet）又\u0026quot;简单\u0026quot;（用起来 pip install 加几行代码），复杂的部分已经被meta和Roboflow帮你用庞大的计算集群计算完了，你所要做的只是在原有权重上进行传统的微调。\n总结 # 我们从四个角度详细拆解了 RF-DETR：DINOv2 自监督预训练给出了强 backbone 与 Register Tokens 这样的细节修复，Projector + 窗口/全局交替 Encoder给出了灵活的骨架，Decoder Layer Pruning让一份权重能跑出多档延迟，NAS 自动搜索把这些组件在 Pareto 前沿上调到了极致。四块拼起来，就是当下实时检测领域的 SOTA。\n从 RF-DETR 身上也能看见现代 CV 的一个趋势：CNN 在精度上早就不占优势，连过去引以为豪的推理延迟一项，也在 RF-DETR 这样的模型面前被追平甚至反超了。这条路的上限我们似乎还远没摸到。\nDETR 系列模型可能暂时就介绍到这里了，其他 DETR 变种你只要弄清楚 RT-DETR 和 RF-DETR，基本都能轻松理解。如果各位读者有什么问题，欢迎在 GitHub Discussions 中留言。\n","date":"2026年05月12日","externalUrl":null,"permalink":"/Owen-Studio/posts/rf-detr-introduce/","section":"Posts","summary":"","title":"详解 RF-DETR","type":"posts"},{"content":"","date":"2026年05月11日","externalUrl":null,"permalink":"/Owen-Studio/tags/rt-detr/","section":"Tags","summary":"","title":"RT-DETR","type":"tags"},{"content":" 什么是DETR？ # DETR的全称是Detection Transformer，顾名思义，也就是Transformer风格的检测器。对比我们熟知的CNN风格检测器，如Fast R-CNN、YOLO，最大的区别是使用了Transformer中的Encoder、Decoder对图片进行分类和回归的预测。\nDETR首次发表于2020年的论文《End-to-End Object Detection with Transformers》，由Nicolas Carion团队发表于CVPR。论文中详细阐述了DETR作为目标检测器的优势和短板，其中最大的优势就是直接抛弃了NMS，做到了真正的\u0026quot;端到端\u0026quot;检测。但 DETR 并不是我们今天的主角，因其对多个尺度同时做全局注意力特征计算的特性，模型训练慢，推理极慢，完全不可能用于实时检测领域，感兴趣的读者可以去阅读原论文了解为什么会这样，我们今天将继续模型介绍的这个系列，要介绍的是 DETR 最出名的变种模型之一——RT-DETR。\n阅前杂谈 # 请注意，本篇博客需要有一定深度学习基础，需要了解Transformer、原始DETR才能更好的理解消化全篇内容，如果你并不是很了解上述几点，建议先去学习它们，上述几点将作为本篇基础，不会再博客中详细介绍。\nRT-DETR详解 # 背景与定位 # RT-DETR 是 DETR 的一个变种，2023 年由百度人工智能研究院提出，发表于 CVPR 2024。论文标题《DETRs Beat YOLOs on Real-time Object Detection》，从标题就能看出，RT 前缀代表 Real-time，矛头直接对准 YOLO 系列在实时检测中的地位。论文要解决的问题就一句话：在原生不依赖 NMS 的前提下，让 DETR 这一类模型跑到 YOLO 那个量级的实时速度。下面详细看看它是怎么做到的。\n网络结构 # 1.Backbone # RT-DETR 在 backbone 上的选择比较保守，没去蹭当时正火的 ViT 风格，主力还是用了一直被验证好用的轻量 CNN——ResNet。ResNet 各位读者想必已经很熟悉了，现在几乎是各路模型默认的 baseline，结构优势就不再赘述。\n除去ResNet，在RT-DETR-L上选用了更大的 HGNetv2 作为backbone，而其他的 RT-DETR-R18/R34/R50 分别使用了 ResNet18/34/50。\n2.Hybrid Encoder（核心） # 在原始DETR中，这一部分本来是 Transformer Encoder，这里变成Hybrid(高效的)，那么这个Encoder高效在哪？我们一步步解析。\nAIFI模块（Intra-scale Feature Interaction，尺度内特征交互）\n了解AIFI模块，我们先来想想Transformer Encoder 为什么这么慢？\n原始 DETR 中 Transformer Encoder 的输入是把backbone输出的三个不同尺度的特征图（S3 + S4 + S5）全部展平，将其拼接在一起，送入 Transformer Encoder 做全局自注意力。这里已经埋下了一个隐患，Self-Attention的复杂度是 $O(N^2)$，N是token数量，假设输入 640x640 分辨率图片，我们来大概的计算一下。\n在全局自注意力下，对于一张尺寸为 $H \\times W$ 的特征图，展平后的token数就是 $N = H \\times W$，那么S3特征图（stride=8）的输出尺寸是 $80 \\times 80 = 6400$ 个token；S4（stride=16）为 $40 \\times 40 = 1600$ 个token；S5（stride=32）为 $20 \\times 20 = 400$ 个token。三者全部拼接后，总token数为：\n$$N = 6400 + 1600 + 400 = 8400$$全局自注意力的计算量正比于 $N^2$：\n$$N^2 = 8400^2 = 70{,}560{,}000 \\approx 7056 \\text{ 万}$$这个数字已经相当惊人了，而且这还只是单张图片、一次前向传播的开销。\nRT-DETR 这篇论文里，作者抓住了这样一点：\n多尺度特征之间，并不是所有交换都同等重要。高层语义空间（S5）之间的交互有价值，应该用Transformer进行全局注意力计算，而低层语义空间（S3/S4）之间的交互并没有什么意义，因为其缺乏深层语义信息，强行进行注意力计算反而会引入噪声。\n那么问题就好解决多了，我们只需要对 S5 输出做注意力计算，相应的计算复杂度也大大降低： S5 只有 $20 \\times 20 = 400$ 个token，计算量变为：\n$$N_{S5}^2 = 400^2 = 160{,}000 \\approx 16 \\text{ 万}$$对比一下，计算量压缩了约：\n$$\\frac{8400^2}{400^2} = \\frac{70{,}560{,}000}{160{,}000} = 441 \\text{ 倍}$$计算量被压下来一大截，模型训练也更稳了。\nCCFM模块（Cross-scale Feature-fusion Module，跨尺度特征融合模块） 刚刚的AIFI模块，我们只用到了 S5 的输出，那S3/S4的输出怎么办？难道直接丢弃吗？当然不是，RT-DETR给出的回答是 CCFM 模块。\n跨尺度特征融合，这个名字很难让人不想到YOLO中引入FPN、PANet、BiFPN等 Neck 部位的多尺度特征融合模式，实际上 CCFM 也是这一思想的延续。\n如图所示，CCFM主要有三条数据流路线：\n第一条：S5：从AIFI模块里处理完后S5，形状仍然保持，因为其已经同时包含语义信息和位置信息，他将直通输出 P5，不与浅层信息融合。\n第二条：S4：来自中层的输出，他将于来自AIFI处理过后的S5输出融合，首先 S5 需要经过一次2×双线性上采样，将自身分辨率从 20×20 对齐至 S4 的 40×40，然后在 channel 维度与 S4 进行concat操作，进行信息的初步融合；然后使用RepBlock(重参数卷积)精炼，将刚刚融合的通道数压回标准维度；最终得到输出 P4。这里的P4既有中层的纹理信息，也有从 S5 流下的深层全局语义。\n第三条：S3：具体操作与第二条类似，只不过将第二条中的 S5 换成 P4，将 S4 换成 S3。\n三条路线串行走完后，将得到的P5，P3，P4展平拼接，将他们送入到 Transformer Decoder 中的K，V进行计算\n3.Transformer Decoder # 3.1 IoU-aware Query Selection（基于IoU感知的查询选择）\n刚刚的 Encoder 仅对 Decoder 输出了K和V，那么Q在哪？别急，你的Q(iang)来了。\nIoU-aware Query Selection 从编码器输出的全部特征中，按置信度选取固定数量（默认 300 个）的图像特征，作为解码器的初始 object queries。\n怎么选？ 对编码器输出的每个位置特征，预测一个分类分数；取分类分数最高的 Top-300 个位置，其对应的特征向量就成为 object queries 的内容初始值，其对应的空间坐标则成为 Decoder 迭代精炼时的初始参考框。\n为什么叫 IoU-aware？ 原始 DETR 只看分类分数排序，但分类高不代表框就准。RT-DETR 在训练时让模型对每个 query 同时预测分类分数和预测框与真值的 IoU，选取时拿两者乘积排序——这样选出来的 query 既类别相关、框也大致定位得上，Decoder 的起点就比随机初始化好得多。\n3.2 Decoder\n先明确 Decoder 的输入和输出：\nQ（Object Queries）：来自 IoU-aware Query Selection 选出的 300 个初始查询向量（内容嵌入 + 参考框位置） K、V：来自 CCFM 输出的多尺度特征（P3、P4、P5 展平后拼接，共 $80^2 + 40^2 + 20^2 = 7200$ 个位置） 输出：每个 query 对应的类别得分与边界框 RT-DETR 使用标准 Transformer Decoder，共 6 层，每层包含三个子模块：\nSelf-Attention（自注意力）：所有 object query 之间互相做注意力，让不同 query 感知彼此的存在，协商分工，避免多个 query 同时锁定同一目标。 Cross-Attention（交叉注意力）：object queries（Q）与多尺度图像特征（K、V）做注意力计算，从图像中提取目标的位置与外观信息。 FFN（前馈网络）：对 cross-attention 的输出做非线性变换，增强特征表达能力。 每个 Decoder Layer 之后都配有独立的分类头和回归头，输出当前层的预测结果。关于多层预测如何参与训练、以及 RT-DETR 如何彻底摆脱 NMS，我们将在损失函数一章中展开。\n损失函数 # 在讲损失之前，先想一个问题：模型输出了 300 个预测框，图里只有 3 个真实目标，该怎么计算损失？\n传统检测器的答案是：提前把每个 Anchor 或网格位置和某个真实框绑定好，谁 IoU 最高归谁负责——这叫预定义分配，分配策略在训练开始前就固定了。简单，但也是 NMS 存在的根源：既然多个候选框都在\u0026quot;认领\u0026quot;同一个目标，推理时就必须靠 NMS 事后去重。\nRT-DETR 的回答截然不同：每次前向传播后，动态地把 300 个预测框和真实框做最优匹配，强制每个真实框只被一个预测框认领，其余框预测背景。这就彻底切断了 NMS 的需求，代价是需要一个聪明的匹配算法——这就是匈牙利算法（Hungarian Algorithm）。\n匈牙利匹配（Hungarian Matching） # 匈牙利算法解决的是一个经典的二分图最优匹配问题：给定 $N$ 个预测框和 $M$ 个真实框（$N \\gg M$，通常 $N=300$），找到一个匹配方案 $\\hat{\\sigma}$，使每个真实框恰好被一个预测框配对，且总匹配代价最小：\n$$\\hat{\\sigma} = \\underset{\\sigma \\in \\mathfrak{S}_N}{\\arg\\min} \\sum_{j=1}^{M} \\mathcal{L}_{\\text{match}}\\left(y_j,\\ \\hat{y}_{\\sigma(j)}\\right)$$每对匹配的代价 $\\mathcal{L}_{\\text{match}}$ 由三项加权求和构成，各司其职，既要类别对，也要位置准：\n$$\\mathcal{L}_{\\text{match}} = \\lambda_{\\text{cls}} \\cdot \\mathcal{L}_{\\text{cls}}\\ +\\ \\lambda_{\\text{bbox}} \\cdot \\mathcal{L}_{\\text{bbox}}\\ +\\ \\lambda_{\\text{giou}} \\cdot \\mathcal{L}_{\\text{giou}}$$ $\\mathcal{L}_{\\text{cls}}$（分类代价）：预测框对真实类别 $c_j$ 的预测概率（取负），概率越高代价越低。匹配阶段直接用概率值，不用 Focal Loss——因为 Focal Loss 的调制因子依赖预测本身，放进匹配会引入不稳定性。 $\\mathcal{L}_{\\text{bbox}}$（L1 定位代价）：预测框与真实框坐标的 L1 距离，衡量位置的绝对误差。 $\\mathcal{L}_{\\text{giou}}$（GIoU 代价）：衡量两个框的几何重叠质量，比 IoU 更适合不重叠情况，也可微。 三项缺一不可：只看分类，位置差的框也能匹配上；只看 bbox/GIoU，类别不对的框也会被强行配对。三者综合，才能选出\u0026quot;类别对、位置也准\u0026quot;的最优匹配。\n$\\lambda$ 是超参数，论文默认 $\\lambda_{\\text{cls}}=2$，$\\lambda_{\\text{bbox}}=5$，$\\lambda_{\\text{giou}}=2$，定位项权重明显更高——匹配阶段更看重框的位置准不准。\n匈牙利算法保证找到全局最优的一对一匹配，而不是贪心地逐个配对。这个匹配在每次前向传播时动态执行，不同 batch、不同 epoch 的匹配结果都可能不同，模型需要从这种动态分配中学会稳定的检测行为。\n训练损失 # 匹配完成后，对应关系确定，损失计算就直接了：\n$$\\mathcal{L} = \\sum_{j=1}^{M} \\left[\\ \\mathcal{L}_{\\text{cls}}\\!\\left(c_j,\\ \\hat{p}_{\\hat{\\sigma}(j)}\\right)\\ +\\ \\mathbb{1}_{\\{c_j \\neq \\varnothing\\}} \\cdot \\mathcal{L}_{\\text{box}}\\!\\left(b_j,\\ \\hat{b}_{\\hat{\\sigma}(j)}\\right)\\ \\right]$$ 分类损失 $\\mathcal{L}_{\\text{cls}}$：使用 Focal Loss。300 个预测框里真实目标只有寥寥几个，正负样本严重失衡，Focal Loss 对难分样本加大权重，避免背景类把梯度淹没 定位损失 $\\mathcal{L}_{\\text{box}}$：L1 Loss 约束绝对坐标误差，GIoU Loss 约束框的重叠质量，两者互补： $$\\mathcal{L}_{\\text{box}} = \\lambda_{\\text{iou}} \\cdot \\mathcal{L}_{\\text{GIoU}}\\!\\left(b_j,\\ \\hat{b}\\right)\\ +\\ \\lambda_{\\text{L1}} \\cdot \\left\\| b_j - \\hat{b} \\right\\|_1$$ 迭代框精炼与辅助损失 # 回到 Decoder 的结构——每个 Decoder Layer 之后都有独立的预测头，这意味着 6 层各自都会输出一组 300 个预测。训练时，每一层都独立地走一遍匈牙利匹配和损失计算，6 层损失加总后一起反向传播：\n$$\\mathcal{L}_{\\text{total}} = \\sum_{l=1}^{6} \\mathcal{L}^{(l)}$$同时，每一层的预测框会作为下一层的参考位置，逐层修正——这与 Cascade RCNN 的级联思路如出一辙，让每一层都站在上一层的肩膀上，从粗到细地逼近目标位置。\n两件事叠加下来：模型在 6 个不同精度的预测层上都有直接的梯度监督，既收敛得更快，小目标和遮挡目标也跟着改善。推理时只取最后一层的输出，前 5 层的预测头全部丢弃。\n为什么不需要 NMS？ # 现在我们可以完整回答这个问题了。\nNMS 的存在，根因是一对多分配——同一个目标被多个候选框认领，推理时它们都有高置信度，必须靠后处理手动去重。RT-DETR 从训练时就用匈牙利算法执行一对一分配，强制模型学会\u0026quot;每个 query 负责且只负责一个目标\u0026quot;。经过数万次迭代，这一分工行为被内化进了权重；加上 Decoder 中的 Self-Attention 让各 query 彼此感知、主动回避重复，推理时的输出天然就没有重叠。\n直接取置信度最高的若干个预测框即可，不需要 NMS。这也是端到端检测的真正含义。\n如何使用被集成进Ultralytics的RT-DETR # 聊完原理，我们来看看怎么真正地用起来。\n讲到目标检测的工程化落地，第一个想到的就是 Ultralytics——最早因 YOLOv5、YOLOv8 一战成名，提供了一套写起来很顺手的模型接口。同一套 API，能跑 YOLO 系列、能跑 SAM，也能跑我们今天的主角 RT-DETR。如果你之前用过 YOLO，那 RT-DETR 你已经会用了，几乎零学习成本。\n安装 # bashpip install ultralytics 安装Ultralytics官方仓库，你也可以从GitHub克隆下来在本地安装。\nPython API：加载、推理、训练、导出 # Ultralytics 把模型使用流程收敛到了加载 → 推理 → 训练 → 导出这四件事，全部通过统一的 RTDETR 类完成：\npythonfrom ultralytics import RTDETR # 1. 加载预训练模型（首次运行会自动从官方下载权重） model = RTDETR(\u0026#39;rtdetr-l.pt\u0026#39;) # 也可换成 rtdetr-x.pt，更大、更准、稍慢 # 2. 推理：图片、视频、文件夹、URL、摄像头流都支持 results = model(\u0026#39;your_image.jpg\u0026#39;) results[0].show() # 弹窗显示 results[0].save(\u0026#39;output.jpg\u0026#39;) # 保存可视化结果 # 3. 训练：换上自己的数据集 model.train( data=\u0026#39;your_dataset.yaml\u0026#39;, epochs=100, imgsz=640, batch=16, device=0, # GPU 编号，CPU 训练改为 \u0026#39;cpu\u0026#39; ) # 4. 验证 / 导出 metrics = model.val() # 在 yaml 配置的验证集上跑评估 model.export(format=\u0026#39;onnx\u0026#39;) # 也可以 \u0026#39;engine\u0026#39;(TensorRT)、\u0026#39;openvino\u0026#39;、\u0026#39;coreml\u0026#39; 等 CLI：一行命令搞定 # 如果你嫌写 Python 麻烦，Ultralytics 还提供了完全等价的命令行接口，参数命名与 Python API 一致：\nbash# 推理 yolo predict model=rtdetr-l.pt source=\u0026#39;your_image.jpg\u0026#39; # 训练 yolo train model=rtdetr-l.pt data=\u0026#39;your_dataset.yaml\u0026#39; epochs=100 imgsz=640 # 验证 yolo val model=rtdetr-l.pt data=\u0026#39;your_dataset.yaml\u0026#39; # 导出为 ONNX（部署到 TensorRT/ORT 推理） yolo export model=rtdetr-l.pt format=onnx 数据集格式 # RT-DETR 在 Ultralytics 里沿用了 YOLO 风格的数据集格式——一个 .yaml 描述文件，配合 YOLO 格式的标注（每行 class_id cx cy w h，坐标均为相对值）：\nyaml# your_dataset.yaml path: /path/to/dataset # 数据集根目录 train: images/train # 训练图片相对路径 val: images/val # 验证图片相对路径 names: 0: person 1: car 2: dog 对应的目录结构：\ndataset/ ├── images/ │ ├── train/ # xxx.jpg │ └── val/ └── labels/ ├── train/ # xxx.txt （与图片同名） └── val/ 如果你做过 YOLO，这套组织方式应该再熟悉不过。\n注意：详细的训练设置，如lr、lrf等请查阅Ultralytic官方文档。上述所有内容具有时效性，一切请以官方文档为准。\n可用模型 # Ultralytics 目前主要集成了 RT-DETR 的两个 HGNetv2 版本：\n模型 参数量 COCO mAP50-95 适用场景 rtdetr-l.pt ~32M 53.0 默认首选，精度与速度的平衡点 rtdetr-x.pt ~67M 54.8 追求精度上限，速度可接受时使用 如果你想跑 RT-DETR-R18/R34/R50 这些 ResNet 变体，需要去官方 PaddlePaddle 仓库或 lyuwenyu/RT-DETR 的 PyTorch 复现版。\n几个小坑 # 输入分辨率必须是 32 的倍数：backbone 经过 5 次下采样，stride 累计到 32，如果你想改 imgsz，记得选 320、416、640、800 这种值，否则会报错。 Query 数量默认 300：对密集小目标场景（航拍、人群、昆虫），300 可能不够用，需要去模型配置里把 num_queries 调大。 预训练权重很重要：RT-DETR 训练成本比 YOLO 高，从头训练在小数据集上效果常常不如直接 finetune 官方预训练权重。 总结 # 到这里我们已经把 RT-DETR 的网络结构和训练机制基本拆完了：AIFI 只对 S5 做注意力把计算量砍掉一大截，CCFM 再把浅层信息融回来，IoU-aware Query Selection 给 Decoder 一个更高的起点，匈牙利匹配 + 一对一分配从训练阶段就把 NMS 的根因切掉。后面简单介绍了 Ultralytics 的接口，没用过的人也能很快上手。\n如果你想再深入一层，强烈建议直接读论文结合官方 lyuwenyu/RT-DETR 的源码进行学习。\n","date":"2026年05月11日","externalUrl":null,"permalink":"/Owen-Studio/posts/rt-detr-introduce/","section":"Posts","summary":"","title":"详解 RT-DETR","type":"posts"},{"content":"","date":"2026年05月10日","externalUrl":null,"permalink":"/Owen-Studio/tags/yolo/","section":"Tags","summary":"","title":"YOLO","type":"tags"},{"content":" 前言 # 我们已经学习聊过了从 YOLOv1 到 YOLOv7 的发展，了解了 YOLO 从一个想法发展到日益成熟且不断精进的单阶段检测器。 而今天我们将介绍从 YOLOv8 一直到 YOLO26 的发展史。\n因为篇幅限制，本篇注定只能大概的对模型发展进行梳理， 特别是考虑这一篇的主角——Ultralytics（后简称U社），在同一大型号内部在不断进行小版本的迭代，这其中的改进，不论是工程上还是网络上的，都不可能详细展开说明，希望各位读者理解，如若有感兴趣的地方，诸位可以自行在网上寻找资料。\nYOLOv8 # 背景 # 在很多人心中，YOLOv8 是最能代表 YOLO 系列的模型，因为它实在是太出名了，无数人用它做完了毕设，水了顶刊顶会。 在Google Scholar上搜索关键词 YOLOv8，会出现大量关于v8的论文。这一点我们上一篇提到的U社功不可没。\nYOLOv8 由U社正式发布于2023年，U社出品的新一代 YOLO 模型，并首次将模型集成到 Ultralytics 库中， 可以使用库中丰富的功能，简单、快捷进行模型训练/调优/推理/配置。放到今天，哪怕你对Python一窍不通， 也能用当下流行的AI coding软件训练自己的模型，哪怕你没有高性能显卡，依然可以使用CPU进行轻量模型的训练。\n虽然本系列博客的介绍一直集中在网络架构这一块，但不得不说 YOLOv8 在工程上的贡献绝对要超过网络架构上的改进——实在是太方便太好用了。\n开源地址：github.com/ultralytics/ultralytics\n整体结构 # YOLOv8 整体延续了 Backbone → Neck → Head 的检测框架，骨架沿用 YOLOv5，但三个部分都做了更新。最大的结构改动是把 YOLOv5 的 C3 模块全部换成新设计的 C2f；检测头直接扔掉 Anchor，改成 Anchor-Free 的解耦头。\n需要特别说明的是：YOLOv8 最初并没有随之发布论文，只有一个 GitHub 仓库和官方文档，属于工程先行的发布方式，这在 YOLO 系列里颇为罕见。\n1. Backbone：C2f 模块（替代 C3） # YOLOv5 的 C3 模块将输入分为两路，一路经过若干 Bottleneck 堆叠，另一路直接跳连，最后 Concat 合并。C3 的局限在于梯度路径较单一——深层 Bottleneck 的梯度必须经过所有浅层才能回传。\n图示：YOLOv8整体网络结构 YOLOv8 用 C2f（Cross-Stage Partial with 2 outputs and more feature reuse） 取而代之，其设计理念直接受到 YOLOv7 ELAN 梯度路径哲学的启发：\n将输入投影为 $2c$ 维后均分为两个分支 $y_0, y_1 \\in \\mathbb{R}^c$，随后 $y_1$ 依次经过 $n$ 个 Bottleneck 得到 $b_1, b_2, \\ldots, b_n$，将所有中间输出全部拼接后经过一个 $1\\times1$ 卷积输出：\n$$\\text{C2f}(x) = \\text{Conv}_{1\\times1}\\!\\left(\\text{Concat}(y_0,\\; y_1,\\; b_1,\\; b_2,\\; \\ldots,\\; b_n)\\right)$$与 C3 相比，C2f 多出了 $n$ 条直接通往输出的捷径：每个 Bottleneck 的输出都参与最终 Concat，梯度可以绕过后续层直接回传，有效缓解深层梯度消失。这正是 ELAN 所倡导的让不同深度的特征都直接贡献到最终输出的思路在 YOLOv8 中的落地。\n2. Neck：同样替换为 C2f # Neck 沿用 PANet 的双向特征融合路径（P3/P4/P5 三尺度，自顶向下语义增强 + 自底向上位置增强），将原有的 C3 模块统一替换为 C2f，保持与 Backbone 的设计一致性。\n3. Head：Anchor-Free + 解耦头 + 移除 Objectness # YOLOv8 沿用了 YOLOv6 已验证的无锚框设计和解耦检测头，并额外移除了 Objectness（目标性）分支，这是U社首次在自己的模型中引入Anchor-free模式。\n在 YOLOv5 中，每个检测位置输出为 $[t_x, t_y, t_w, t_h, \\text{obj}, \\text{cls}_1, \\ldots, \\text{cls}_C]$，其中 $\\text{obj}$ 独立预测“此处是否存在目标”。YOLOv8 认为在无锚框设计下 $\\text{obj}$ 与分类分支存在语义重叠，因此直接删除，输出简化为两条独立分支：\n分类分支：$C$ 维向量，sigmoid 激活，预测各类别置信度 回归分支：$4 \\times \\text{reg\\_max}$ 维向量，预测边界框的 ltrb 距离分布（详见 DFL 损失） 正样本匹配：TAL（沿用 YOLOv6） # YOLOv8 继续沿用了来自 TOOD 的 TAL 正样本匹配策略，公式与流程和 YOLOv6 完全一致：\n$$t = s^{\\alpha} \\cdot u^{\\beta}$$用分类得分 $s$ 与预测框-GT IoU 的乘积对每个候选位置打分，取 Top-k 作为正样本。可参见上一篇 YOLOv6 章节中的详细介绍，此处不再赘述。\n损失函数设计 # YOLOv8 回归损失的核心亮点是引入了 DFL（Distribution Focal Loss），来自 Generalized Focal Loss（GFL, 2020）。\n分类损失：BCE # 分类分支使用标准的 Binary Cross-Entropy（BCE），配合 TAL 的 Top-k 软正样本标签。\n回归损失：CIoU + DFL # DFL 改变了传统回归头对坐标的建模方式——不再直接输出一个确定性的距离值，而是输出一个在离散区间 $[0, 1, \\ldots, n]$（默认 $n=16$）上的概率分布 $P(y_i)$，最终坐标通过加权期望得到：\n$$\\hat{d} = \\sum_{i=0}^{n} i \\cdot P(y_i)$$监督信号是以真实距离值 $y$ 为中心的软标签：将权重集中在 $y$ 两侧的相邻整数 $\\lfloor y \\rfloor$ 和 $\\lceil y \\rceil$ 上，权重由 $y$ 与各整数端点的距离决定，然后用交叉熵拟合这个软分布：\n$$\\mathcal{L}_{DFL} = -\\left( (\\lceil y \\rceil - y)\\log P(\\lfloor y \\rfloor) + (y - \\lfloor y \\rfloor)\\log P(\\lceil y \\rceil) \\right)$$DFL 的直觉在于：传统回归默认坐标的最优答案是一个确定点，但目标边缘模糊、遮挡这些情况下，预测本身就有不确定性。DFL 让模型把这种不确定性显式画出来——分布越尖锐，模型对这个位置越有把握，大部分情况下收敛会更稳，训练信号也更聚焦。和 TAL 用联合得分筛正样本的思路其实是一路的，都在让模型对高质量预测给出更高的置信度。\n最终回归损失为 CIoU 与 DFL 的加权求和：\n$$\\mathcal{L}_{reg} = \\mathcal{L}_{CIoU} + \\mathcal{L}_{DFL}$$ 模型系列 # YOLOv8 提供 N、S、M、L、X 五个尺寸，以下数据来自 Ultralytics 官方文档（COCO val2017，A100 GPU）：\n版本 参数量 mAP50-95（val） 推理速度（ms, A100） YOLOv8n 3.2M 37.3% 0.99 YOLOv8s 11.2M 44.9% 1.20 YOLOv8m 25.9M 50.2% 1.83 YOLOv8l 43.7M 52.9% 2.39 YOLOv8x 68.2M 53.9% 3.53 数据来源：Ultralytics 官方文档，COCO val2017，A100 TensorRT10\n新一代Ultralytics库 # 一句 pip 就能装上 Ultralytics 库，训练深度学习模型的工作量被砍掉了一大截。 你不用再从零写 dataset loader、训练循环、tensorboard 接入、训练结束后的评估图（F1 confidence、混淆矩阵那一套），这些东西都在库里，几行命令就能跑通。\n还有 Roboflow 这种标注平台。以前标数据集只能本地装个 labelme 自己点，Roboflow 直接把数据集版本、团队协作、数据增强方案、各种格式导出都做成了 SaaS，还能直接下别人处理好的开源数据集。U 社做的不只是一个训练框架，它把一个对新手来说及其友好的深度学习AI社区给搭建起来了。\n后来几乎所有的深度学习训练平台都或多或少在抄这套范式：完备的文档 + 活跃的社区 + 一行命令跑通，新手能少走非常多的弯路。\n总结 # YOLOv8 的网络改动其实并不惊艳——C2f、解耦头、Anchor-Free、DFL 都不是它首创的，分别来自 ELAN、YOLOv6、GFL。它真正做的事情是把这些散落各处的好实践揉成了一套人人都能上手的工具。\n另一件值得一提的是，YOLOv8 在 Ultralytics 框架下第一次把多种视觉任务统一起来：目标检测、图像分类、实例分割、姿态估计，都通过换检测头就能跑，训练流程是同一套。\n学术角度看，它更像\u0026quot;最佳实践的工程整合\u0026quot;；落到工程上看，它把目标检测的门槛拉到了历史新低。无数工程师和研究生的第一个 YOLO 就是在 Ultralytics 库上跑出来的，这种影响力，是顶会论文难以媲美的。\nYOLOv8 也是 Ultralytics 正式接过 YOLO 主导权的那个节点。之后的 v9、v10、YOLO11 都在这条线上延续。无论外界如何质疑，Ultralytics 的工程化路线已经把 YOLO 推成了目标检测领域事实上的\u0026quot;工业标准\u0026quot;。\nYOLOv9 # 背景 # YOLOv9 由 **王建尧（Chien-Yao Wang）、叶宜豪（I-Hau Yeh）和廖弘源（Hong-Yuan Mark Liao）**于 2024 年 2 月发布，作者正是 YOLOv7 的原班人马，论文标题为 《YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information》。\n开源地址：github.com/WongKinYiu/yolov9\n如果说 YOLOv7 的主张是\u0026quot;可训练的 Bag of Freebies\u0026quot;，那 YOLOv9 想讲的故事是：深度网络在前向传播时本来就会丢信息，得从信息论角度重新看看梯度是怎么传过去的。围绕这件事，YOLOv9 给出了两块东西：GELAN（架构）和 PGI（训练机制）。\n整体结构 # YOLOv9 的架构依然遵循 Backbone → Neck → Head 的框架，主干网络使用 GELAN 替代了 YOLOv7 的 ELAN；训练阶段引入 PGI 提供高质量梯度信号；推理时 PGI 相关结构完全丢弃，保持与 GELAN 一致的轻量推理图。\n1. 信息瓶颈问题（PGI 的出发点） # YOLOv9 在文章开篇引用了信息论中经典的信息瓶颈原理（Information Bottleneck Principle）：数据经过深层网络逐层变换时，原始输入的信息会被不可避免地压缩和丢失。层数越深，到达该层的梯度信号所携带的“与原始输入相关的信息”就越少，网络深层事实上是在用一个退化的信息副本来更新参数。\n这一问题在轻量模型上尤为严重——参数量受限时，网络不得不做更激进的信息压缩，梯度质量更差，训练更难收敛。\n2. PGI：Programmable Gradient Information（训练机制） # PGI 是 YOLOv9 的核心创新，其思路是：在训练时引入一条辅助可逆分支（Auxiliary Reversible Branch），让梯度信号始终能追溯到原始输入，从而对抗深层网络的信息损耗。\nPGI 由三个部分组成：\n主推理分支（Main Branch）：正常的 GELAN 推理路径，推理时唯一保留的部分 辅助可逆分支（Auxiliary Reversible Branch）：一条与主分支并行运行的浅层可逆网络，满足“给定输出可完全还原输入”的可逆性约束，保证信息不丢失。这条分支仅在训练时存在，推理时完全丢弃，零额外推理开销。 多级辅助信息（Multi-level Auxiliary Information, MLAI）：将辅助分支的特征与主分支的多尺度特征对齐后提供监督，使梯度信号在主分支的多个层级都能获得来自原始输入的高质量反馈 PGI 与 YOLOv7 的辅助检测头有一个本质区别：YOLOv7 的辅助头只在输出层加一个额外的检测监督，属于输出级增强；PGI 的辅助分支在特征级别对信息完整性做保障，梯度质量的提升发生在更靠近主干的层级。\n3. GELAN：Generalized Efficient Layer Aggregation Network（架构） # GELAN 是 YOLOv7 ELAN 的推广版本。ELAN 的核心思想是多层特征全部直接参与最终输出，但 ELAN 固定使用卷积作为基本计算单元。GELAN 将这一思路泛化到任意计算块：\n$$\\text{GELAN}(x) = \\text{Conv}_{1\\times1}\\!\\left(\\text{Concat}(y_0,\\; y_1,\\; f_1(y_1),\\; f_2(f_1(y_1)),\\; \\ldots)\\right)$$其中 $f_i$ 可以是任意计算模块（标准卷积、RepConv、C2f、注意力模块等），只要保证梯度路径的多样性和特征复用即可。在 YOLOv9 的具体实现中，$f_i$ 采用了类似 CSP 的 RepConvN 堆叠，延续了 YOLOv7 的设计风格。\nGELAN 的意义在于它提供了一个统一的框架：ELAN（YOLOv7）、C2f（YOLOv8）从某种意义上都是 GELAN 的特例，区别仅在于选用的计算块不同。\n正样本匹配与损失函数 # YOLOv9 在正样本匹配上沿用了 TAL，损失函数延续了 YOLOv8 的 BCE + CIoU + DFL 组合，并将这套损失同时施加在主分支和辅助分支的预测头上。辅助分支的损失仅用于梯度回传，不参与最终评估指标。\n模型系列 # YOLOv9 提供 T/S/M/C/E 五个尺寸，以下数据来自 YOLOv9 论文（arXiv 2402.13616）：\n版本 参数量 mAP50-95（COCO val） 吞吐量（img/s, V100） YOLOv9-T 2.0M 38.3% — YOLOv9-S 7.2M 46.8% — YOLOv9-M 20.1M 51.4% — YOLOv9-C 25.3M 53.0% 232 YOLOv9-E 57.3M 55.6% 100 数据来源：YOLOv9 原论文，COCO val2017，NVIDIA V100\n总结 # YOLOv9 大概是 YOLO 系列里理论最扎实的一代。它用信息瓶颈原理解释了深网络梯度为什么会退化，PGI 给了一个有理论支撑的解法，而且推理时 PGI 那条辅助分支整个被砍掉，沿用了 YOLOv7 那一套训练复杂、推理简单的做法。GELAN 则把 ELAN 抽象成了一个更通用的框架，后面的人可以往里塞任意计算块。\n但这里面的理论分析实在是太晦涩硬核，笔者能力有限，原论文涉及信息论和离散数学的部分得系统性学习过的人才能真正读懂，这一段就不展开了。\nYOLOv10 # 背景 # YOLOv10 由清华大学的王翱等人于 2024 年 5 月发布，与v9的发布间隔仅两个月，论文标题为 《YOLOv10: Real-Time End-to-End Object Detection》。这是一篇来自学术界（而非 Ultralytics）的工作，但因为紧贴 YOLO 命名体系且切中了一个真实痛点，很快引起了广泛关注。\n开源地址：github.com/THU-MIG/yolov10\nYOLOv10 想解决的问题就一句话：所有 YOLO 变体推理时都靠 NMS 后处理，而 NMS 本身是个无法并行的串行算法，成了实时推理速度的瓶颈之一。那能不能把 NMS 去掉，又不掉精度？\nNMS 的问题 # 回顾一下 NMS 的工作方式：模型对同一目标会产生若干重叠的候选框，NMS 通过逐个比较 IoU 来筛选“最好的那一个”，本质上是一个 $O(n^2)$ 的串行过程。它的问题有两个：\n速度：无法被 GPU 高度并行化，尤其在候选框数量多时延迟明显 超参数敏感：IoU 阈值、置信度阈值需要针对不同数据集手动调参，泛化性差 基于 Transformer 的 RT-DETR 用匈牙利匹配实现了端到端的无 NMS 检测，但代价是极高的计算成本。YOLOv10 的目标是在轻量卷积框架内实现 NMS-Free。\n整体结构 # YOLOv10 在 Backbone 和 Neck 上延续了 YOLOv8 的整体设计风格（C2f 为主），并针对效率做了若干局部优化；最核心的创新集中在**检测头的双标签分配（Dual Label Assignment）**机制上。\n1. 架构效率优化 # YOLOv10 对不同位置的模块做了差异化的效率改造：\nCIB（Compact Inverted Block）：在计算成本较低的位置（如 Neck 深层）将标准卷积替换为类似 MobileNetV2 的倒残差结构（depthwise separable + inverted residual），在参数量和计算量上都更精简\n大核深度可分离卷积：在网络浅层引入 $7\\times7$ 深度可分离卷积扩大感受野，用深度可分离结构将大核的计算成本控制在可接受范围内——浅层通道数少，开销可控。\n这一设计背后有一条贯穿 YOLO 史的脉络——如何在网络最浅层以低代价获得大感受野，是每一代都在思考的问题：YOLOv5 的 Focus 层用的是“空间折叠”技巧，将 $H\\times W\\times C$ 的输入按 $2\\times2$ 间隔采样后重排为 $\\frac{H}{2}\\times\\frac{W}{2}\\times 4C$，再接 $3\\times3$ 卷积；该卷积实际覆盖原图 $6\\times6$ 范围，无需真正的大核。YOLOv8 直接把 Focus 替换成 $6\\times6$、步长为 2 的标准卷积，效果相近但更简洁。YOLOv10 继续沿这个方向，改用可学习的 $7\\times7$ 深度可分离卷积，感受野更大，参数量与 $3\\times3$ 相当。三者解法不同，动机相同。\nPSA（Partial Self-Attention）：放置在 Backbone 最深层（P5，约 $20\\times20$ 的特征图）的轻量全局注意力模块，具体流程如下：\n将输入 $x \\in \\mathbb{R}^{B\\times C\\times H\\times W}$ 沿通道均分为两半：$x_1, x_2 \\in \\mathbb{R}^{B\\times \\frac{C}{2}\\times H\\times W}$ 将 $x_2$ 展平空间维度后送入多头自注意力（MHSA）：$Q=x_2 W_Q,\\ K=x_2 W_K,\\ V=x_2 W_V$，计算 $\\text{Attn}=\\text{softmax}\\!\\left(\\frac{QK^\\top}{\\sqrt{d_k}}\\right)V$，再经 FFN 得到 $x_2'$ $x_1$ 不经任何注意力，直接保留（保留局部卷积特征） 将 $x_1$ 与 $x_2'$ 拼接后过 $1\\times1$ 卷积混合通道，输出与输入同维\n设计要点有两个：其一，只对一半通道做注意力，计算量约为全局 MHSA 的一半，避免了自注意力 $O((HW)^2)$ 的全量代价；其二，PSA 放在 P5（特征图最小，$HW$ 最小），此时 $20\\times20=400$ 个位置的全局注意力已完全可承受。$x_1$ 负责保留局部空间细节，$x_2'$ 负责建立长程依赖，最后 $1\\times1$ 卷积让两路信息相互融合——这是“局部+全局双流”的经典思路，代价却极低。而这种设计在YOLO11中得到了保留，并被集成进C2PSA模块中。这是注意力机制在单对单检测器中完美融合的典范。\n2. 双标签分配：NMS-Free 的实现方式 # 这是 YOLOv10 的核心贡献。\n传统 YOLO（v6/v7/v8/v9）的标签分配都是一对多（One-to-Many）：TAL 为每个 GT 分配 Top-k 个正样本，同一目标会被多个预测框学习，训练时梯度信号充足，但推理时必须用 NMS 去除重复框。\n如果把标签分配改成一对一（One-to-One）——每个 GT 只分配一个正样本——那推理时就不会出现重复框，NMS 自然不需要了。但问题是一对一分配大幅减少了正样本数量，梯度信号稀疏，模型很难训练好。\nYOLOv10 的解法是同时保留两套头，各司其职：\n分支 标签分配 用途 一对多头（O2M Head） TAL Top-k（与 v8 相同） 仅用于训练，提供丰富梯度信号 一对一头（O2O Head） TAL Top-1 训练 + 推理，输出无重复预测 两个头共享同一套 Backbone 和 Neck，只有最终的检测头不同。训练时两套损失同时反传，O2M 头的丰富监督信号通过共享主干“辅导”了 O2O 头的特征表达；推理时只跑 O2O 头，每个 GT 只有一个预测框，完全不需要 NMS。\n为了保证两套分配的一致性，YOLOv10 在 O2O 的 Top-1 选取时，直接从 O2M 的 Top-k 候选中挑分数最高的一个，形成一种“老师-学生”式的对齐。\n损失函数 # 延续 YOLOv8 的标准组合：分类用 BCE，回归用 CIoU + DFL。O2M 头和 O2O 头分别独立计算损失后加权求和。\n模型系列 # YOLOv10 提供 N/S/M/B/L/X 六个尺寸（新增了介于 M 和 L 之间的 B 档），以下数据来自 YOLOv10 论文（arXiv 2405.14458）：\n版本 参数量 mAP50-95（COCO val） 延迟（ms, T4） YOLOv10-N 2.3M 38.5% 1.84 YOLOv10-S 7.2M 46.3% 2.49 YOLOv10-M 15.4M 51.1% 4.74 YOLOv10-B 19.1M 52.5% 5.74 YOLOv10-L 24.4M 53.2% 7.28 YOLOv10-X 29.5M 54.4% 10.70 数据来源：YOLOv10 原论文，COCO val2017，NVIDIA T4，TensorRT\n总结 # YOLOv10 的思路很清楚：抓住 YOLO 一个长期忍着的痛点（NMS），并在卷积框架里给了一个能落地的解法（双标签分配）。\n这套双头设计很巧妙，没有放弃一对多分配的训练好处，而是让两套机制各管一摊：一对多负责把梯度喂饱，一对一负责推理时出干净结果。和 YOLOv7 RepConvN 那种训练多分支、推理单分支的套路，思路上其实是一类东西。\n当然 YOLOv10 也有自己的尴尬：NMS 在现代 GPU 上的延迟并不总是瓶颈，双头反而把训练复杂度提上去了；精度对比同时期的 YOLOv9 和 RT-DETR 也并没有特别明显的优势。它更大的价值是指了一个方向：端到端检测不一定非得 Transformer，卷积一样能做。\nYOLO11 # 背景 # 2024 年 9 月，Ultralytics 在 YOLO Vision 2024 大会上推出了 YOLO11。这次官方特地把名字里的 \u0026ldquo;v\u0026rdquo; 去掉了——不是 \u0026ldquo;YOLOv11\u0026rdquo;，而是 \u0026ldquo;YOLO11\u0026rdquo;。一个字母的事，背后其实想说两件事：一是 Ultralytics 要把 \u0026ldquo;YOLO\u0026rdquo; 这块招牌的话语权攥得更紧（毕竟 v9/v10 都不是它出的），二是这个系列从这里开始正式进入工程驱动、而不是靠论文更新迭代的阶段。\n与 YOLOv8 一样，YOLO11 没有发布正式论文，所有技术细节通过代码、官方博客和文档公开。它在 v8 的基础上做了进一步的模块替换与效率优化，并继续保持了一个仓库支持多任务的生态优势。\n开源地址：github.com/ultralytics/ultralytics（与 YOLOv8 共用同一个仓库）\nYOLO11 想干的事情概括起来很简单明确：在 YOLOv8 的工程框架上，把 v9/v10 里被验证有效的东西吸过来，再做一轮整合，让相同精度下参数量和延迟都再往下掉一些。\n整体结构 # YOLO11 的整体范式与 YOLOv8 完全一致：CSP 风格的 Backbone + PAN-FPN 颈部 + 解耦 Anchor-Free 检测头。真正变化的是其中两个关键模块。\n1. C3k2 模块：C2f 与 C3 的嵌套 # YOLO11 在 Backbone 和 Neck 中将 YOLOv8 的 C2f 替换为 C3k2。要看懂 C3k2，需要先理解 C3k——C3k 是 C3 的“可调内核版本”，而 C3k2 则是把 C3k 嵌进 C2f 外壳里得到的双层 CSP 结构。\n第一步：C3k——C3 的可调版本\nC3k 直接继承自 YOLOv5 的 C3，结构基本一致：输入经两条 $1\\times1$ 卷积分支拆成两路，主分支 $y_a$ 串联 $n$ 个 Bottleneck，旁支 $y_b$ 直通，最后 Concat 后再过一个 $1\\times1$ 卷积输出：\n$$\\text{C3k}(x) = \\text{Conv}_{1\\times1}\\!\\left(\\text{Concat}\\!\\left(\\underbrace{\\text{Bottleneck}^{(n)}(\\text{Conv}_{1\\times1}(x))}_{y_a},\\ \\underbrace{\\text{Conv}_{1\\times1}(x)}_{y_b}\\right)\\right)$$唯一差别在于：C3 里 Bottleneck 内部固定使用 $3\\times3$ 卷积，而 C3k 把 Bottleneck 内部的卷积核大小作为可配置参数 $k$（实现中默认 $k=3$，也允许更小的核）。当 $k=3$ 时 C3k 与 C3 结构等价——可以把 C3k 理解为“参数化版本的 C3”。\n第二步：C3k2——把 C3k 装进 C2f 的外壳\nC3k2 在外层完全沿用 YOLOv8 C2f 的拆分-堆叠-多路 Concat 结构：输入经 $1\\times1$ 卷积投影到 $2c$ 后均分为 $y_0,y_1$，$y_1$ 依次过 $n$ 个内部 Block 得到 $b_1,\\ldots,b_n$，所有中间特征拼接后再过一个 $1\\times1$ 卷积。C3k2 的关键改动是把 C2f 中固定的 Bottleneck 替换为一个可配置的内部 Block——这个 Block 既可以是标准 Bottleneck，也可以是上一步的 C3k 模块：\n$$\\text{C3k2}(x) = \\text{Conv}_{1\\times1}\\!\\left(\\text{Concat}(y_0,\\ y_1,\\ \\mathcal{B}(y_1),\\ \\mathcal{B}^{(2)}(y_1),\\ \\ldots,\\ \\mathcal{B}^{(n)}(y_1))\\right)$$其中 $\\mathcal{B} \\in \\{\\text{Bottleneck},\\ \\text{C3k}\\}$ 由配置开关 c3k 决定。当 c3k=False 时，C3k2 退化为完全等价于 C2f 的结构；当 c3k=True 时，每个内部 Block 自身就是一个小型的 CSP 模块，整个 C3k2 形成 “外层 CSP（C2f 风格） + 内层 CSP（C3k 风格）” 的双层嵌套。\n第三步：和已有模块的对照\n模块 出处 外层结构 内部 Block 输出端 Concat 路径数 C3 YOLOv5 单层 CSP（2 分支） 标准 Bottleneck（核固定 $3\\times3$） 2 C3k YOLO11 单层 CSP（2 分支） 标准 Bottleneck（核可配置 $k$） 2 C2f YOLOv8 多分支梯度（ELAN 风格） 标准 Bottleneck $n+2$ C3k2 YOLO11 多分支梯度（与 C2f 同构） Bottleneck 或 C3k 二选一 $n+2$ C3k2 的设计意图很务实：外层保留 C2f 已经被验证有效的多路梯度结构，内层用 C3k 替换标准 Bottleneck，把每个 Block 的内部计算压缩得更紧。配合 c3k 开关，工程师可以按层灵活调整——浅层通道少时用普通 Bottleneck（等价于 C2f），深层通道多、计算成本高时切到 C3k 形成嵌套 CSP，这也是 YOLO11 在相同精度下能进一步降低参数量的关键。\n2. C2PSA 模块：把 PSA 装进 CSP 框架 # YOLO11 在 Backbone 最深层（P5 之后）插入了一个 C2PSA 模块——这是 YOLOv10 中 PSA 的工程化版本。其结构可概括为：\n$$ \\text{C2PSA} = \\text{CSP 分支结构} + \\text{若干个 PSA Block} $$具体做法是：将输入沿通道一分为二，一支保留原样作 shortcut，另一支依次过若干个 PSA Block（每个 Block 内部做“通道折半 → 一半走 MHSA、一半保留 → 拼接 → $1\\times1$ 融合”，与 YOLOv10 的 PSA 完全一致），最后两支拼接并经 $1\\times1$ 卷积输出。\nC2PSA 的意义在于：它把 YOLOv10 中作为单一模块出现的 PSA，按 CSP 模式做了堆叠和包装，从而能够像 C2f / C3k2 那样作为一个标准的“特征提取单元”嵌入网络。\n3. 检测头的细节优化 # YOLO11 沿用了 YOLOv8 的解耦 Anchor-Free 头结构（分类分支 + 回归分支 + DFL），但在分类分支中将部分标准卷积替换为深度可分离卷积，进一步压缩参数。回归分支保持不变。\n需要指出的是：YOLO11 回到了标准的一对多匹配 + NMS 路线，没有采用 YOLOv10 的双头 NMS-Free 设计。Ultralytics 的判断是——双头带来的训练复杂度与一致性维护成本，在大多数工程场景下并不划算。\n正样本匹配 # 完全沿用 YOLOv8 的 TAL（Task-Aligned Assigner）：根据分类置信度与回归 IoU 的联合得分 $t = s^\\alpha \\cdot u^\\beta$ 选取 Top-k 正样本，详见 v6章节。\n损失函数 # 与 YOLOv8 完全相同的三项组合：\n$$ \\mathcal{L} = \\lambda_{\\text{cls}}\\,\\text{BCE}_{\\text{cls}} + \\lambda_{\\text{box}}\\,\\text{CIoU} + \\lambda_{\\text{dfl}}\\,\\text{DFL} $$ 模型系列 # YOLO11 提供 N/S/M/L/X 五个尺寸，多任务（检测 / 实例分割 / 分类 / 姿态 / OBB）共用同一组主干，只更换任务头。\n版本 参数量 mAP50-95（COCO val） YOLOv8 同档参数量 YOLOv8 同档 mAP YOLO11-N 2.6M 39.5% 3.2M 37.3% YOLO11-S 9.4M 47.0% 11.2M 44.9% YOLO11-M 20.1M 51.5% 25.9M 50.2% YOLO11-L 25.3M 53.4% 43.7M 52.9% YOLO11-X 56.9M 54.7% 68.2M 53.9% 数据来源：Ultralytics 官方文档，COCO val2017。\n可以看到一个清晰的趋势：相同档位下，YOLO11 普遍以更少参数取得了更高 mAP——这正是这一代主打的卖点。\n总结 # YOLO11 没有像 v9 的 PGI、v10 的 NMS-Free 那种亮眼的理论创新，它的价值更多在工程整合：把 v8 的稳定底盘、v10 里被验证有效的 PSA 注意力、再加上深度可分离结构的进一步使用，全都揉到同一套代码里，做出一个参数更少、精度更高、生态更完整的版本。\n更值得说的是名字的变化。从 \u0026ldquo;YOLOv8\u0026rdquo; 到 \u0026ldquo;YOLO11\u0026rdquo;，少掉的那个 \u0026ldquo;v\u0026rdquo;，是 Ultralytics 在把 YOLO 从一条开放的研究脉络，慢慢收拢成一条自家主导的工程产品线。v9/v10 由学术团队带的那段时间过去之后，Ultralytics 用 YOLO11 把工程生态上的主导权拿了回来。\nv4-v7 是学者百花齐放的时代，v8-v10 是学术和工程交替带的时代，到了 YOLO11，更像是一次\u0026quot;收尾\u0026quot;——把过去几年冒出来的有用设计沉淀到同一套可维护、可商用、能做多任务的框架里。\nYOLO26 # 背景 # 2025 年 9 月，Ultralytics 在 YOLO Vision 2025 大会上发布了 YOLO26——直接从 YOLO11 跳号到 26，命名上对齐“2026 年款”的产品节奏，也再次强化了 Ultralytics 把 YOLO 当作产品线来运营的态度。\n开源地址：github.com/ultralytics/ultralytics（仍是同一个仓库）\n如果说 YOLO11 是\u0026quot;内部模块的精炼整合\u0026quot;，那 YOLO26 的关键词只有一个：端侧优先（Edge-first）。Ultralytics 在大会上把话挑明了，这一代不再卷 COCO 上的 mAP 极限，而是要让模型在 CPU、移动端、嵌入式这些资源受限的平台上跑得更快、部署更省心。为此他们做了几项相当大胆的取舍，甚至把从 YOLOv8 一路用过来的 DFL 头给砍了。\n需要提前说明：YOLO26 截至本文撰写时仍处于发布早期，部分实现细节可能随着后续迭代而调整，本节内容以官方发布会与公开文档披露的核心改动为准。\n整体结构 # YOLO26 在外层骨架上延续 YOLO11 的范式（C3k2 + C2PSA + 解耦头），真正的变化都集中在减法上——简化结构、简化输出、简化部署。\n1. 移除 DFL：回归头重新变“瘦” # 从 YOLOv8 开始，回归头一直输出一个 reg_max 维度的离散概率分布，再通过加权期望得到坐标，对应的训练监督是 DFL。这套设计精度上确有收益，但代价不小：\n输出张量更厚（每个边长 4 → $4 \\times \\text{reg\\_max}$，默认 $\\text{reg\\_max}=16$ 即 64 维） 部署侧需要额外的“分布积分”计算，对一些不支持复杂算子融合的 NPU、ONNX runtime、CoreML 等不友好 推理后处理更繁琐，导出到边缘格式时常常成为性能瓶颈 YOLO26 的判断是：对绝大多数工程场景来说，DFL 带来的那点精度收益，不够抵消它在端侧带来的额外开销。所以回归头改回直接预测 $(l,t,r,b)$ 四个标量距离，输出维度从 $4\\times16=64$ 直接降到 4。这一改看着像在开倒车，但配合下面要讲的 ProgLoss，精度并没有明显掉，导出图和端侧推理却简化了一大截。\n2. ProgLoss：渐进式回归损失 # 没有了 DFL 的分布监督，回归任务需要新的训练机制来弥补。YOLO26 引入 ProgLoss（Progressive Loss）：在训练早期，损失更宽容地容许大的回归误差，让模型先学到大致位置；随着训练推进，损失逐渐收紧，对预测框的几何精度提出更高要求。\n这种由粗到精的课程式监督，与 DFL 的显式概率分布是两条不同的精度补偿路径——DFL 通过建模不确定性间接提升回归质量，ProgLoss 则通过训练动态调整来达成类似目标，但保留了简单标量回归的部署优势。\n3. STAL：小目标感知的标签分配 # 从 YOLOv6 开始，TAL 一直是 YOLO 系列的标准正样本分配策略。但 TAL 在小目标上有一个长期被诟病的问题：联合得分 $t=s^\\alpha u^\\beta$ 中，IoU 项 $u$ 对小目标天然不友好——同样的像素偏移，小目标的 IoU 损失要远大于大目标，导致小目标更难被选为正样本，正样本数量不足，训练不充分。\nYOLO26 提出 STAL（Small Target-Aware Label assignment），在 TAL 的基础上对小目标候选给予额外的分配权重，让小目标在 Top-k 选择时更容易胜出。这是对 TAL 的延伸而非取代——大、中目标的分配逻辑保持不变。\n4. 端到端推理：NMS-Free 成为默认选项 # YOLOv10 当年提出的双标签分配头被 YOLO26 重新拾起并做了简化：训练时仍以一对多分配为主、保证梯度信号；导出推理时可以选择启用一对一头，直接输出无重复预测，不再需要 NMS。\n与 YOLOv10 不同的是，YOLO26 把 NMS-Free 做成了导出时的可选模式而不是默认结构——研究端依然可以用一对多 + NMS 的传统流程训练评估，部署端只需在导出时切换一个开关，就能得到一个原生端到端的推理图。这种训练不变、部署可选的设计大大降低了 NMS-Free 的使用门槛。\n5. MuSGD：新的优化器 # 训练侧，YOLO26 引入了名为 MuSGD 的优化器——它结合了 SGD 的稳定收敛特性和 Muon 系优化器的二阶矩信息，旨在对小模型给出更稳定的训练曲线。在小尺寸（N、S）模型上效果尤为明显，缓解了过去 YOLO 小模型训练对学习率与权重衰减极为敏感的问题。\n正样本匹配 # TAL + STAL 组合：基础逻辑沿用 YOLOv8 的 Task-Aligned Assigner，对小目标候选叠加 STAL 提供的额外权重。\n损失函数 # 相比 v8/v10/v11 的三项组合（BCE + CIoU + DFL），YOLO26 的损失被简化为两项：\n$$ \\mathcal{L} = \\lambda_{\\text{cls}}\\,\\text{BCE}_{\\text{cls}} + \\lambda_{\\text{box}}\\,\\text{ProgLoss}(\\text{IoU}) $$其中 ProgLoss 包裹的是 IoU 系列损失（CIoU/DIoU 等），通过训练阶段调整其严格程度来实现由粗到精的渐进式监督。DFL 项被完全移除。\n多任务统一与 SAM 生态融合 # YOLO26 在任务覆盖上做了一次显著扩展——它原生支持目标检测、实例分割、语义分割、图像分类、姿态估计、OBB（旋转目标检测）六大基础视觉任务。其中语义分割是 Ultralytics 系列首次纳入官方支持的任务类型，这也补上了过去 YOLOv8/YOLO11 一直缺位的能力。\n1. 一套主干如何同时承担六种任务 # 能做到换头不换骨，靠的是 Ultralytics 从 YOLOv8 起就坚持的统一 Backbone + 统一 Neck + 任务专属 Head 范式。所有任务共享主干网络，差异完全收敛到最后一层 Head：\n任务 Head 设计要点 目标检测 每个网格输出 $C$ 维分类向量 + 4 维距离回归 实例分割 检测 Head 基础上额外输出 mask 系数，配合一组 prototype masks 通过线性组合重建实例掩膜 语义分割（新增） 将 Neck 多尺度特征上采样回输入分辨率 $H\\times W$，最后用 $1\\times1$ 卷积输出每像素的 $C$ 维类别分布，逐像素 softmax 即可 姿态估计 检测 Head + 每框 $K$ 个关键点的 $(x,y,\\text{conf})$ 输出 图像分类 直接在最深特征上接全局池化 + FC OBB 检测 Head 基础上额外回归一个旋转角度 $\\theta$ 语义分割与实例分割看似相近，实现路径却完全不同——实例分割是逐目标输出掩膜（先检测再分割），语义分割是逐像素分类（不区分实例，只关心类别）。前者复用检测头、后者直接做密集预测。Ultralytics 把两套头都封装进同一框架，用户只需要修改一行任务配置，相同的训练脚本就能切换。\n这种范式的工程价值是：所有任务共用同一份预训练主干，迁移成本极低；社区贡献的预训练权重在六个任务之间可以互相 Warm-start，进一步降低了非检测任务的入门门槛。\n2. 与 Meta SAM 的协作 # YOLO26 文档首次把 Ultralytics 与 Meta SAM（Segment Anything Model） 及 SAM 2 的深度集成列为正式的生态卖点。这一合作的关键在于——YOLO 与 SAM 的能力是互补的，而不是替代的：\nYOLO：擅长大规模、高速度的目标识别与定位，能给出稠密的 bounding box 与类别 SAM：擅长任意目标的像素级掩膜生成，但本身不知道这个目标是什么，需要外部 prompt（点、框、文字）告诉它分割哪里 把这两件事拼起来，自然就是YOLO 给框 → SAM 出掩膜的流水线。Ultralytics 把这条链路工程化成了三个层面：\n同框架直接调用：SAM、FastSAM、MobileSAM、SAM 2 已全部被纳入 ultralytics Python 包。可以在同一份脚本中混用 YOLO 检测器和 SAM 分割器，模型加载、推理、可视化使用一致的 API 自动标注（Auto-Annotation）：给一批未标注图像，框架自动跑 YOLO 出预测框 → 把框作为 prompt 传给 SAM 出掩膜 → 直接导出为 COCO/YOLO 格式的实例分割训练数据。原本需要数周人力的标注工作能在几小时内完成 训练数据增强：通过 SAM 对训练数据生成像素级掩膜，可以反过来作为 YOLO26 实例分割/语义分割任务的伪标签来源，形成 YOLO 给 SAM 提供prompt，SAM反过来告诉YOLO 如何分割的闭环 这套整合背后的逻辑十分清楚：YOLO 出识别效率，SAM 出分割精度，两个拼起来正好覆盖了从识别到分割、从粗到精的完整链路。Ultralytics 不再把 YOLO 当成唯一答案，而是把它当成一个生态枢纽——这大概才是 YOLO 系列把 \u0026ldquo;v\u0026rdquo; 去掉、跳号到 26 真正想说的事：YOLO 不是一个模型，是一整套围绕实时视觉任务的开源工具集。\n模型系列 # YOLO26 提供 N/S/M/L/X 五档尺寸，多任务支持继续保留。这一代主推的卖点不是 mAP，而是端侧延迟——尤其在 CPU 与 ARM 平台上的提速极为显著。官方公布的关键对比是：YOLO26-N 在与 YOLO11-N 相当 mAP 的前提下，CPU 推理延迟可降低 ~40%。\n版本 参数量 mAP50-95（COCO val） 关键特征 YOLO26-N ~2.4M ≈ YOLO11-N 主打 CPU/边缘端，延迟显著下降 YOLO26-S ~9M ≈ YOLO11-S 移动端首选 YOLO26-M ~20M 略优于 YOLO11-M 平衡型 YOLO26-L ~25M 略优于 YOLO11-L 服务端主力 YOLO26-X ~57M 略优于 YOLO11-X 精度上限 数据来源：Ultralytics YOLO Vision 2025 发布会与官方文档，具体数值随后续 release 可能调整。\n总结 # YOLO26 是 Ultralytics 把工程产品化这条路推到底的一代。它的几项重要改动——移除 DFL、ProgLoss、STAL、可选的 NMS-Free 导出、MuSGD、原生语义分割、跟 SAM 的深度集成——基本指向两件事：让 YOLO 在云端以外的世界也能跑得又快又稳；让 YOLO 不再只是一个模型，而是一整套覆盖识别到分割的开源工具集。\n里面比较值得说的是这种取舍：DFL 这种从 YOLOv8 用到现在、被无数后续工作引用的设计，也不是不能去掉的，只要找到合适的替代品（ProgLoss）和明确的取舍场景（端侧部署）就行。放到产品工程化语境下，这就是一次很正常的权衡。Ultralytics 这次显然挑了一条更贴合现代工程需要的路。\n从 YOLOv1 一路看到 YOLO26，这条发展线越来越清楚了：早期是 Joseph Redmon 一个人的热情驱动，中期被各路学术团队推着往理论纵深走（v4 的 BoF/BoS、v7 的 ELAN、v9 的信息瓶颈），近几年又被 Ultralytics 拽回工程主线，朝着\u0026quot;任何人、任何硬件、任何场景都能用\u0026quot;的方向走。这条路未必是最浪漫的，但它实打实地把目标检测从论文课题变成了人人都能上手的工具——光这一件事，就已经足够伟大了。\n最终总结 # YOLO系列单阶段检测器自诞生起已经过了十一年，见证了CV界了繁荣到因为大语言模型冲击下的没落。笔者在2023年入坑CV界，而那时候YOLO已经在CV界已经占据了举足轻重的地位。\nYOLO系列的发展是无数个人和学术组织交替努力的成果，从最开始Redmon博士将一个“单阶段思想”落地成真正可以实现的项目，到Ultralytics将整个YOLO工程化、规模化。YOLO系列从学术贡献中走来，投入到工程实际中去，最后又化作无数科研人员和大学生的参考文献。这样的故事，放在互联网上的学术圈子和开源社区里实在是让人津津乐道，我也时常和朋友聊起这11年间YOLO的种种往事，而今天总算将这些经过完整的书写下来，也算是给自己一个交代。\nYOLO系列是否已经到了尽头？我也不知道，但是我对YOLO仍然包有期待，作为个人使用者，我并不希望从此以后U社的一家独大，而是希望更多从业者能够有更加开创性的发现，让我们能使用更加轻量、更加强大的模型，但现实情况是，大部分从事深度学习研究的人都跑去搞LLM和多模态了，CV界的没落已成为必然。\n不过话虽如此，今年9月，或许U社又会发布自己新的模型，届时让我们一起共同期待吧，我个人的小小心愿，现在的Ultralytics平台已经相当强大，真心希望U社能在网络结构或训练范式有新的规范和创新。（别再是集成SAM和DETR）\n","date":"2026年05月10日","externalUrl":null,"permalink":"/Owen-Studio/posts/what-is-yolo3/","section":"Posts","summary":"","title":"YOLO的前世今生（下）","type":"posts"},{"content":"","date":"2026年05月10日","externalUrl":null,"permalink":"/Owen-Studio/categories/notes/","section":"Categories","summary":"","title":"学习笔记 / 心得","type":"categories"},{"content":" 前言 # 在上一期我详细介绍了YOLO的开端，以及从YOLOv1到YOLOv3的发展史，这一期我们将着重介绍从YOLOv4到YOLOv7的发展史。如果说v1到v3是YOLO（单阶段检测器）的探索期，那么本期YOLO在大的架构方面将会逐步稳定下来。“YOLO之父”Redmon在完成YOLOv3后就退出了YOLO系列的开发，但无数CV届的有志之士将不断将单阶段检测器推向新的高度。\nYOLOv4 # 背景 # YOLOv4发表于2020年的论文《YOLOv4: Optimal Speed and Accuracy of Object Detection》。主要工作由Alexey Bochkovskiy主导，这位也是CV领域大神，他后续还参与了YOLOv7的工作。\n首先不得不说的是，YOLOv4的研究恐怕是YOLO系列工作量最大、工程量最大的一个模型，论文原文中不仅对YOLO进行了大量创新，而且对每一个优化点都做了大量的工程试验以得到量化数据。我相信有过深度学习经验或发表过有关于深度学习论文的读者一定都知道我在说什么，其中大量的工程环节和消融实验让我光是看论文都觉得累，在这里由于篇幅和笔者能力限制不可能全部一一展开说明，只能大致介绍，如果有不理解的点各位可以查看原论文或开源仓库，感谢各位的理解。\n整体架构 # YOLOv4整体架构上延续了YOLOv3中FPN多尺度融合的思想，并最终提出了 Backbone -\u0026gt; Neck -\u0026gt; Head的架构，这标志着单阶段检测器整体架构的稳定，后来所有的单阶段检测器几乎都遵循这一整体架构。\n1.CSPDarknet53（Backbone）\nYOLOv4的backbone总体仍然使用YOLOv3中发明的Darknet-53，但在其中加入了CSP（Cross Stage Partial）连接模块，用来替换了原始网络中的“残差快”，将下采样后得到的特征图（每个Stage的入口处）从通道维度一分为二（各自用一个1x1的卷积压缩到C/2通道数），形成Part1和Part2两条通道。\nPart1（短路）：不经过残差快，直接横跨整个Stage。\nPart2（主路）：正常经过原来的N个残差快。\n两条路都走到Stage末尾后，做一次 Concat操作，再用一个1x1卷积将其中提炼的信息融合，把最终结果输出到下一个stage。\n这张图片非常清晰的展示CSP结构的数据流向，那么作者为什么要加入CSP连接呢？\n加入CSP连接的主要诉求，就是减少梯度冗余，这也是CSPNet原论文中提到的。在原网络中，梯度路径仅有唯一的一条，流经了所有的残差快，相邻层的梯度高度相关，大量的梯度冗余；加入了CSP后网络拥有了两条独立路径，梯度的来源不同，也降低了相邻层之间的梯度冗余。\n让人惊喜的是CSP带来的好处远不止这些，作者在实验中表明，CSP的加入减少了约20%的计算量，参数两也从原来的41M减少到27.6M；感受野从425x425（CSPResNeXt50 参照）提高到了725x725。\n这是一个非常重要的探索，接着往后学习，你会发现，后面的YOLO模型逐渐模块化，而像C3、C3K等等模块其实也使用CSP的思想，我们再接下来的章节也会讲到。\n2.SPP模块（Neck附加块）\n示意图非常详细的展示了SPP模块的位置以及数据流向，简单来说就是把最后一个stage输出的深层特征图通过不同格式MaxPool进行感受野的放大，最后拼接回去并进行信息融合，这样原始的stage输出就携带了不同感受野的信息。\n需要强调的是SPP 不是插在 Backbone 的每个 Stage，而是只插在 Backbone 最深层的输出之后，紧接着进入PANet Neck之前。原论文说这样「significantly increases the receptive field, separates out the most significant context features and causes almost no reduction of the network operation speed」。\n在我们的上篇提到过MaxPool本身没有可学习参数，只是机械的计算窗口最大值，计算开销极小，新增的代价只有 Concat 后通道翻 4 倍带来的那一个 1×1 整合卷积，整体额外 FLOPs 不到 0.5%。这又是一个在不增加多少计算量的条件下提升网络性能的创新。\n3.PANet（Neck路径聚合）\nPANet对比YOLOv3中的FPN，做出的最大改进用一句话就可以说清楚。把单行的自深层语义流向浅层位置信息的信息高速通路改为了双向的信息流动。\n原来的FPN浅层（56x56大感受野）的stage输出能够接收到来自深层的（13x13）带来的深层语义信息，但深层的stage输出却很难得到完整的浅层位置信息，这就是因为FPN的单向流动导致的。PANet在保留自深层到浅层的信息通路上加入了自浅层到深层的信息通路，将浅层的位置信息也能与深层的语义信息融合，保证输入到Head中的三个尺度都能含有位置信息和语义信息，这提升了模型整体的表达能力，并最终影响到了检测的精度。\n同时示意图中也提到了，原版PANet在两路特征融合时用的是逐元素相加（Add）。两路必须通道数相同，相加后信息直接叠加混合，两路特征互相干扰，区分度降低。YOLOv4把Add全部换成了Concat，然后接一个 1×1 卷积重新缩回需要的通道数，这样两路特征在通道维度上并排存放，各自独立，后续卷积可以自由选择利用哪一路的信息，表达能力更强。\n4.YOLOv3 Head（检测头，沿用）\nYOLOv4 的检测头直接沿用 YOLOv3 的设计：Neck 输出三个尺度的特征图（52×52、26×26、13×13），各自经过若干 1×1 + 3×3 卷积后，输出预测张量，每个网格预测 3 个 Anchor 框，每个框输出 $(t_x, t_y, t_w, t_h, t_{obj})$ 加 $C$ 个类别置信度，共 $3 \\times (5 + C)$ 个通道，三个尺度分别负责小、中、大目标的检测。YOLOv4 对 Head 本身未做改动，全部改进集中在 Backbone、Neck 和训练策略上。\nBag of Freebies(BoF) # “免费的午餐”，这是原论文中提出的概念，意味着下面的这些方法只在训练阶段发挥作用，推理阶段模型结构不变，在优化模型训练效果的同时不产生额外的使用开销。\n数据增强: 像素级增强 遮挡模拟 多图混合增强\nYOLO系列早期版本虽也使用了基础的数据增强，但 YOLOv4 是第一次系统化、大规模地将多种先进数据增强策略集成到训练流程中的版本。\n像素级增强包括了光度失真（Photometric Distortion）和几何变换（Geometric Distortion）。前者随机调整图像的亮度、对比度、色调、饱和度和噪声；后者随机缩放、裁剪、翻转、旋转。\n遮挡模拟包括了 ** Random Erase / CutOut**：在图像中随机选取矩形区域，填充随机值或零。GridMask：有规律地遮挡多个矩形区域。 DropBlock：在特征图层面做区域性随机丢弃，比 Dropout 更适合 CNN（Dropout 最初为全连接层设计）。\n多图混合增强包括了MixUp：将两张图像按不同比例加权叠加，标签也按比例混合。 CutMix：将一张图的裁剪区域覆盖到另一张图上，标签按面积比例调整。 Mosaic（YOLOv4 新提出）：将 4 张图像拼接成一张，使模型同时学习 4 种场景，有助于检测更小的目标，且降低了对 large mini-batch 的依赖。\n语义分布偏差处理: 标签平滑 自对抗训练\n在视觉任务中，不同类别的样本数量、特征分布可能严重不均衡，导致模型在训练时出现偏差，YOLOv4在训练时做了以下的处理。\nLabel Smoothing（标签平滑）：将 one-hot (0,1)硬标签转换为软标签(0.1,0.9)，提升模型鲁棒性，防止过拟合。Self-Adversarial Training（SAT，自对抗训练）：第一阶段：对输入图像执行对抗攻击，生成欺骗网络的对抗样本。第二阶段：用这些对抗样本正常训练网络。作用：提升模型对各种输入扰动的鲁棒性。\n边界框回归损失函数:\nYOLOv3使用的传统方法用MSE对 $(x,y,w,h)$各点独立回归，忽略了边界框的整体性，且损失值随目标尺度增大而增大。原论文对IoU系列损失做出了详细的梳理和实验，最终选用了CIoU Loss：\n损失函数 考虑因素 不足 MSE 各坐标点独立回归 {x,y,w,h} 忽视边界框整体性，损失随目标尺度增大而增大 IoU Loss 预测框与真值框的交并比（整体性） 无重叠时梯度为零，无法引导框靠近真值 GIoU Loss IoU + 最小外接框惩罚（形状与方向） 水平/垂直对齐时退化为 IoU，收敛慢 DIoU Loss IoU + 中心点距离 不考虑宽高比，形状匹配能力弱 CIoU Loss（最终选用） IoU + 中心点距离 + 宽高比一致性 — 在实验中，CIoU同时考虑了重叠面积、中心点距离、宽高比，收敛速度和精度均优于前面几种 归一化方法:\n在YOLOv2中就引入了BN层（归一化的概念），而不同BN层存在的问题是单 GPU 训练时 batch size 很小（比如 4），用 4 张图算出的 μ 和 σ 噪声极大，归一化效果差。CBN 的解法是把过去 k 个 iteration 的统计量一起拿来用，相当于把「有效 batch size」扩大了 k 倍。但这里有一个麻烦：第 $t-3$ 次 iteration 时网络权重是 $\\theta_{t-3}$，算出来的 μ 和 σ 是「在那组权重下」的激活值统计，到了第 $t$ 次 iteration 权重已经变成 $\\theta_t$ 了，这两组统计量不在同一个坐标系下，不能直接平均。CBN 用泰勒多项式对旧统计量做补偿校正，把它们折算到「当前权重」下再合并，这个补偿计算是有额外开销的。\nCmBN 将「跨 iteration」改成「跨 mini-batch within batch」，YOLOv4 训练时把一个大 batch 拆成 4 个 mini-batch 依次做前向，等 4 个 mini-batch 都跑完、权重还没有更新之前，把这 4 份统计量合并起来做一次归一化。这 4 个 mini-batch 是在同一组权重下跑的，统计量天然可比、无需任何校正，直接平均就是准确的。这样就既扩大了统计样本量，又完全规避了 CBN 里昂贵的泰勒补偿计算，实现简单、适合单 GPU 训练。\n总结一下，CmBN 改的不是 BN 层里的计算逻辑，改变的是 均值和方差从哪里收集 这一件事。CBN 收集的信息跨越的是时间（不同 iteration，权重不同）；而 CmBN 跨越的是空间（同一 iteration 内的不同 mini-batch，权重相同），所以 CmBN 不需要再用泰勒多项式对旧统计量做补偿校正，减少了计算开销，这也解决了单 GPU 训练下 batch size 较小时导致BN 统计不稳定的问题，让 YOLO 系列真正可以在个人计算机上稳定训练。\n学习率调度\n使用Cosine Annealing LR（余弦退火学习率），让学习率按照余弦曲线平滑衰减，避免训练末期振荡，这也成为后续所有YOLO模型里默认的退火策略。\nBag of Specials(BoS) # 这类方法不同于“免费午餐”，他们在推理阶段也会生效，会增加少许的计算量，但带来的精度收益远高于付出的代价。\n增大感受野\nSPP（Spatial Pyramid Pooling）：如前所述，放在 Neck 中使用。\n值得一提的是ASPP / RFB也在论文中做过对比试验，最终YOLOv4选用了SPP。\n注意力机制\nYOLOv4第一次引入了彼时火热的注意力机制模块——SAM（Spatial Attention Module，空间注意力）：并针对YOLOv4做了改进，将原本的卷积注意力改为逐点注意力（point-wise attention）。仅增加 0.1% 的额外计算量，却能提升模型对空间位置的聚焦能力。\n特征融合/路径聚合\n如前文所述，加入PANet作为Neck的路径聚合模块，双向特征融合，增强多尺度表征。\n激活函数\n替换了CSPDarknet-53 中的 Leaky ReLU激活函数，选择了Mish 激活函数： $$f(x)=x⋅tanh⁡(softplus(x))$$\n连续可微，在梯度传播上更平滑，有助于深层网络训练。\n后处理\nDIoU NMS：在传统 NMS 基础上，将预测框的中心点距离信息加入筛选过程，对遮挡场景下的重叠框处理更准确，替代原来基于 IoU 阈值的 Soft-NMS。\n总结 # 如原论文 Table 9 所示，YOLOv4 在 COCO test-dev 数据集上（输入分辨率 608×608，Tesla V100）取得了 43.5% AP / 65.7% AP₅₀，推理速度达 65 FPS；相比 YOLOv3（608×608）的 33.0% AP，精度提升约 10.5 个百分点，推理速度提升约 12%，在精度-速度曲线上全面超越了同期的 EfficientDet 系列。\nYOLOv4整合了v3-v4那两年里计算机视觉领域的新成果，并将其定制化地运用到了YOLOv4中来，这是一项极其繁杂、庞大的工作，要不断地筛选，不断实验，还需要去归纳和判断不同策略或不同改进点之间互相影响的结果，向这篇论文的研究团队致敬。\n本章中我们简单介绍了v4的发展历史和做出的改进，你会发现随着模型越来越靠后，内在的结构和整体框架也越来越熟悉，而YOLO系列也正是在前人探索的基础上一步一步优化而来的。YOLOv4起到了承上启下的作用，它进一步拓展了v3，保留整体的骨干，为后续YOLOv5和Ultralytics的出现奠定了坚实的基础。\nYOLOv5 # 背景 # 2020年，由Glenn Jocher和Ultralytics带着全新的YOLOv5模型登场了，这可能是一些人心中最经典的YOLO模型，它深刻影响了YOLO社区和YOLO的底层实现方式。尽管研究团队没有将YOLOv5的成果转化为论文，但它的发布仍然像是给整个CV界投下一颗重磅炸弹，也宣告了YOLO系列Ultralytics(后简称U社)时代的来临。\n值得一提的是，YOLOv5 的发布在社区中引发了一场不小的争议：部分研究者认为，在 Redmon 退出、原作者群体不知情的情况下，将一个没有论文支撑的工程项目命名为\u0026quot;YOLOv5\u0026quot;有蹭热度之嫌；也有声音认为这只是一个代号，评价模型本身才是重点。这场争论并没有定论，却在客观上埋下了后来 YOLO 系列群雄割据、各自为战的伏笔——从这里开始，\u0026ldquo;YOLOv×\u0026ldquo;的命名权不再归属于任何单一的学术机构。\n伟大的迁移 # 一提到v5，首先想到的一定是这一次伟大的迁移，YOLOv1~YOLOv4都是基于C语言写的Darknet框架，部署复杂、生态封闭，对于新手的上手难度高。YOLOv5则用Pytorch完全进行了重构，从底层完全重写了网络结构，并且接入了丰富的Pytorch、Python社区。\n完全的重构，再加上U社在工程上的精妙设计，训练/推理/模型/检测头代码更易读，更容易修改，并且得益于Pytorch丰富的工具库，YOLOv5天然支持 TorchScript、ONNX、TensorRT 等格式导出，并借助Python已有生态，实现了训练数据可视化、实验追踪、数据管道追踪，使得科研人员能实时追踪训练动态，更好的定位问题。而在github上将整个工程项目开源出来，社区贡献的门槛大幅降低，任何人都可能成为YOLO系列的Contributer。这彻底点燃了人们对于YOLO的热情，YOLO的名字也开始不局限于CV学术届，国内外的大公司都开始着手研究，甚至将YOLOv5布局在商业产出中。\nU社的这一系列操作，让不同领域的开发者接触到了YOLO，不论是对YOLO本身进行开发，还是将YOLO应用到自己的实际项目中，进行私有定制或边缘部署，这些都极大地打响了YOLO的知名度，U社的大手开始发力。\n📄 U社文档 https://docs.ultralytics.com/zh/models/yolov5/\nYOLOv5官方仓库 https://github.com/ultralytics/yolov5\n网络架构 # 1.C3模块（CSP的YOLOv5实现） # YOLOv5将YOLOv4里的CSP结构重新实现为C3模块（Cross Stage with 3 convolutions，名称来源于其中3个关键1×1卷积）。数据流上，输入经两个1×1卷积分为两路：短路直接横跨，主路经过N个Bottleneck堆叠，两路在末尾Concat后由第三个1×1卷积融合输出。与YOLOv4 CSP相比，C3在结构思想上完全一致（分路→Bottleneck堆叠→Concat→融合），Bottleneck内部的残差连接同样默认保留（shortcut=True）。本质区别在于这是一次用 PyTorch 对 Darknet CSP 的干净重写：代码更简洁、可读性更强，并天然获得 PyTorch 生态的所有工程红利。 C3是v5 Backbone的基本重复单元，后续v8中的C2f模块就是在C3的基础上演进而来的。\n2.Focus层 # 早期的YOLOv5在backbone的入口添加了一个Focus层，把输入的图像按像素间隔切片，将640x640x3变成320x320x12，再接一个3x3卷积。这样可以在不做池化的情况下实现两倍的下采样并保留所有像素信息。\n而在中后期的YOLOv5上，Focus层被替换成一个6x6，stride=2的卷积。因为有开发者实验发现二者效果相当，但后者更符合现代GPU计算，且对TensorRT量化更加友好。\n3.SPPF(Spatial Pyramid Pooling - Fast) # 顾名思义，这就是YOLOv4中的SPP的改进版。SPP是把stage5中的输出并行送入 $k=1/5/9/13$四路进行MaxPool再Concat。YOLOv5将其改为SPPF，把三个5x5的MaxPool串行排列，等效的模拟了扩大感受野的效果。串行的池化从感受眼上等效于SPP的 5/9/13（每经过一个5x5，感受野扩展一次），但计算量大幅度减少，速度经实测超过SPP两倍以上，同时输出结果完全相同。\n$$SPP: 输入 → [MaxPool(1), MaxPool(5), MaxPool(9), MaxPool(13)] → Concat$$\n$$SPPF: 输入 → MaxPool(5) → MaxPool(5) → MaxPool(5) → Concat(各层输出)$$ 4.模型缩放系列(n/s/m/l/x) # YOLOv5首次在YOLO系列中引入复合缩放(Compound Scaling),提供五个不同尺寸版本。\n版本 定位 参数量（约） mAP50-95（COCO val） FPS（V100） YOLOv5n(nano) 极轻量，移动端/嵌入式 1.9M 28.0% 45 YOLOv5s(small) 轻量，平衡速度 7.2M 37.4% 98 YOLOv5m(medium) 通用场景 21.2M 45.4% 63 YOLOv5l(large) 高精度 46.5M 49.0% 40 YOLOv5x(extra large) 最高精度 86.7M 50.7% 23 数据来源：YOLOv5 v6.1 官方 README，输入分辨率 640×640，Tesla V100，COCO val2017\n控制缩放的方式包括控制网络深度（C3模块重复次数）和宽度（通道数），通过depth_multiple 和 width_multiple两个参数共同控制，而这一种模式在U社的工程方案里不断优化，最终演化成了使用yaml低代码形式，完成在模型工厂中的构建。你不需要写任何python代码就可以构建不一样的模型结构。\n5.PANet Neck（沿用） # YOLOv5 的 Neck 直接沿用 YOLOv4 的 PANet 双向特征融合结构：Backbone 输出的 P3/P4/P5 三个尺度先经自顶向下的语义增强通路，再经自底向上的位置增强通路，保证每个尺度都同时携带浅层位置信息和深层语义信息后再送入检测头。与 YOLOv4 的区别在于，Neck 内的特征融合模块同样换成了 C3，代替原版 PANet 中的 CBL 堆叠，与 Backbone 保持统一的基本单元。\nBag of Freebies——数据增强 # YOLOv5在YOLOv4的基础上增加和完善了多种数据增强\n增强方式 说明 Mosaic（继承自 v4） 4 图拼接，扩大场景多样性 MixUp 两图按比例叠加 Copy-Paste 将目标实例随机复制粘贴到其他图像 Random Affine 随机旋转、缩放、平移、剪切 HSV 增强 随机调整色调/饱和度/亮度 Random Horizontal Flip 随机水平翻转 Albumentations 集成第三方增强库（可选） 损失函数的设计 # YOLOv5的总损失和YOLOv4几乎一致，主要由三部分组成：\n$$Loss = \\lambda_1 L_{cls} + \\lambda_2 L_{obj} + \\lambda_3 L_{loc}$$ 分项 使用的损失函数 说明 分类损失 $L_{cls}$ BCE Loss 多标签二元交叉熵 置信度损失 $L_{obj}$ BCE Loss 是否含有目标 定位损失 $L_{loc}$ CIoU Loss 继承自 YOLOv4 除此之外，YOLOv5对于不同尺度的输出，有不同的设计\n三个检测层（P3/P4/P5）的置信度损失权重不同。公式如下：\n$$L_{obj} = 4.0 \\cdot L_{obj}^{small} + 1.0 \\cdot L_{obj}^{medium} + 0.4 \\cdot L_{obj}^{large}$$\n对于小目标检测层（P3，52x52）权重最大（4.0），因为小目标样本较少，漏检代价高；大目标检测层（P5，13x13）权重最小（0.4）。 这是YOLOv5对不同尺度检测头差异化关注的体现。\n边界框解码公式的改进 # 这是一个冷门知识点，在YOLOv5中很容易被忽视但也很重要的一个细节。\nYOLOv2/v3 的公式：\n$$b_x = \\sigma(t_x) + c_x, \\quad b_y = \\sigma(t_y) + c_y$$$$b_w = p_w \\cdot e^{t_w}, \\quad b_h = p_h \\cdot e^{t_h}$$存在的问题：\n$\\sigma(t_x)$ 的值域是 $(0, 1)$，中心点偏移永远无法精确到 0 或 1（即网格边缘），需要极端的 $t_x$ 值才能接近边界，梯度极小，优化困难。这就是「网格敏感性」问题。\n$e^{t_w}$ 无上界，宽高预测可以爆炸性增长，容易引发梯度爆炸和 NaN loss。\nYOLOv5 的改进公式：\n$$b_x = (2\\sigma(t_x) - 0.5) + c_x, \\quad b_y = (2\\sigma(t_y) - 0.5) + c_y$$$$b_w = p_w \\cdot (2\\sigma(t_w))^2, \\quad b_h = p_h \\cdot (2\\sigma(t_h))^2$$改进效果：\n中心点偏移范围从 $(0, 1)$ 扩展到 $(-0.5, 1.5)$，可以轻松到达网格边缘甚至跨越到相邻网格，消除边界梯度消失问题。\n宽高缩放比被限制在 $(0, 4)$ 倍 Anchor 大小，彻底消除了 $e^{t_w}$ 无界带来的训练不稳定风险。\n正样本匹配策略改进（build target） # YOLOv5 改进了将 GT Box 分配给 Anchor 的策略：\n宽高比匹配： 计算 GT 框和 Anchor 模板的宽高比 $r_w = w_{gt}/w_{at}$，$r_h = h_{gt}/h_{at}$，取 $r^{max} = \\max(r_w^{max}, r_h^{max})$，若 $r^{max} \u003c 4$ 则认为匹配成功（阈值可配置）。\n多网格分配： 由于中心点偏移范围扩展到了 $(-0.5, 1.5)$，一个 GT Box 的中心点除了归属于其所在的网格外，还可以被分配给上/下/左/右相邻的网格参与预测，将正样本数量增加了约 3 倍，缓解了正负样本极度不均衡的问题，提升训练收敛速度。这进一步完善了YOLOv3中提出的Anchor-based策略，是Anchor的集大成者。\n优化器相关策略 # 得益于整体迁移到Pytorch上进行开发，YOLOv5有更丰富的Optimizer进行选择和调用，在训练阶段能够使模型更加稳定的收敛，并减轻计算负担。\n策略 说明 Warmup + Cosine LR 训练初期线性升温，之后余弦衰减，继承自 YOLOv4 EMA（指数移动平均） 对模型权重做滑动平均，推理时使用 EMA 权重，比 SGD 瞬时权重更稳定，泛化性更好 混合精度训练（AMP） FP16 + FP32 混合，显存减半，速度提升，无精度损失 超参数进化（Evolve） 用遗传算法自动搜索最优超参数组合（学习率、增强强度等），默认演化 300 代（轮数可配置） AutoAnchor 训练前用 k-means 对数据集 GT Box 聚类，再用遗传算法以 CIoU + Best Possible Recall 为适应度函数优化 Anchor，迭代 1000 代，使 Anchor 与自定义数据集匹配 这里的EMA、AMP、超参数进化等等，由于篇幅限制，在这篇博客中没法深入展开，因为这里面每个点几乎都可以单开一篇博客详谈， 如果各位读者对此感到好奇，可以到给种论坛或博客了解。\n总结 # YOLOv5做出的最大贡献就是把整个YOLO模型移植到了生态更丰富，对开发者更友好的Pytorch上，这其中其实也有相当不小的工作量，而且代码构建难度很高。事实证明这是个相当有远见的策略，它确实营造了现如今YOLO社区百花齐放的景象。而在当时，YOLOv5的仓库开源出来之后，也在内部不断迭代，这离不开GitHub上广大开源贡献者的贡献，所谓众人拾柴火焰高，每经过一个YOLOv5的小版本，背后都有人在对它不断做出改进和优化，向他们同样致以崇高敬意。\n而YOLOv5在模型优化和模型训练上也做出了大量的贡献，这其中除了U社的贡献，开源开发者做出的贡献也不容小觑。而他们共同把YOLOv5推向了市场，据我所知，现在仍然有许多目标检测、人脸识别等商业项目在沿用着YOLOv5，这也算得上是CV学术界将成果转化的标志性案例。\nU社也就此开启了它在CV界的传奇征程，顺便一提，笔者将在2026.5.14前往深圳参加U社组织的线下见面/问答会，如果读者有什么想问的问题说不定可以留言评论（？）直到今天U社在GitHub上面的仓库依然活跃，我们始终期待它能否给我们带来新的惊喜。\nYOLOv6 # 背景 # YOLOv6——一个比较小众，使用较少的版本，它由美团视觉智能部开发，并在2022年6月发表了论文《YOLOv6: A Single-Stage Object Detection Framework for Industrial Applications》。\n再上一个章节我们提到，YOLO形成了群雄割据的局面，它的命名权不属于任何一个公司或学术机构，而美团抢得了先机，抢先发布的按照自己需求优化的YOLO模型，而他们优化的方向更加考虑模型在边缘设备（硬件）上的部署效率。\n开源地址：github.com/meituan/YOLOv6\n整体结构 # YOLOv6 同样遵循 Backbone → Neck → Head 的整体框架，并在这三个组件上分别引入了全新的设计。整个网络最核心的亮点是**结构重参数化（Structural Re-parameterization）**技术的大规模运用，这让 YOLOv6 在训练和推理阶段拥有完全不同的网络结构，从而同时获得训练时的表达能力和推理时的部署效率。\n1. EfficientRep Backbone（结构重参数化） # YOLOv6 摈弃了 YOLOv5 的 C3 + CSP 路线，改用基于 RepConv（Reparameterizable Convolution）构建的 EfficientRep 作为 Backbone。\nRepConv 的核心思想来源于 RepVGG（2021），本质是\u0026quot;训练时多分支、推理时单分支\u0026rdquo;：\n训练阶段：每个 RepConv 块是三路并联结构——3×3 卷积、1×1 卷积、Identity 捷径（仅在输入输出通道数相同时存在），三路结果逐元素求和输出。多分支结构类似小型隐式集成，梯度路径更丰富，特征表达能力更强。 $$y_{train} = Conv_{3\\times3}(x) + Conv_{1\\times1}(x) + x$$ 推理阶段：利用卷积的线性可加性，将三路卷积在参数层面合并为一个等价的 3×3 卷积。1×1 卷积零填充边缘后等价为 3×3，Identity 等价于中心为 1、其余为 0 的 3×3 卷积核，吸收各自的 BN 参数后三者直接求和，推理时整个模块退化为一个简洁的 3×3 卷积。 $$y_{infer} = Conv_{3\\times3}^{merged}(x)$$这一设计带来了极大的工程价值：单一 3×3 卷积对现代 GPU / NPU / TensorRT 极度友好，没有残差加法、没有多路分支带来的额外显存占用，INT8 量化也更加稳定。这正好契合美团对边缘硬件部署效率的核心诉求。\n在具体实现上，YOLOv6 针对不同规模的模型采用了不同策略：小模型（N/S）使用纯 RepBlock（多个 RepConv 堆叠），大模型（M/L）则引入 CSP-RepBlock，融合了 CSP 分路思想与 RepConv，在参数量更大的模型上进一步压缩冗余计算。\n2. Rep-PAN Neck（沿用 PANet，替换基本单元） # Neck 结构沿用了 YOLOv4/v5 中经过验证的 PANet 双向特征融合路径（P3/P4/P5 三尺度，自顶向下语义增强 + 自底向上位置增强），但将 Neck 内部的特征融合模块全部替换为 RepBlock，形成 Rep-PAN。\n这与 YOLOv5 把 PANet 内的特征融合模块堆叠替换成 C3 的思路如出一辙，用当前版本的反复核心基本单元统一配置，保持 Backbone 与 Neck 的一致性，同时在 Neck 中也享受到结构重参数化带来的推理加速红利。\n3. 解耦检测头（Anchor-Free） # 这是YOLOv6在YOLO中首次引入的两项颠覆性改动。将检测头解耦深刻影响后面所有的YOLO模型，也使得YOLO未来在面对不同任务（目标检测、语义分割、实例分割等）有了更方便的使用空间。\nAnchor-Free（无锚框设计）\n回顾YOLOv2～v5，每个网格都需要预定义若干Anchor模板GT Box必须找到最接近的Anchor才能被匹配，训练前还需要AutoAnchor对数据集做聚类。这套机制在不同数据集和不同输入分辨率下都需要重新调整，部署灵活性差。\nYOLOv6采用了无锚框设计：每个网格直接预测目标中心点偏移量和框的宽高，不依赖任何预设Anchor模板，每个特征图上的每一个位置（网格点）都是一个潜在的检测候选点，不再绑定任何预设 Anchor 尺寸。消除了Anchor调参的工程负担，也让模型的泛化性更强。\n从Anchor-free到Anchor-based，再到回到Anchor-free，这本身就是研究团队再不断深入研究和试验后取得的成果。\n解耦检测头（Decoupled Head）\nYOLOv5 使用耦合检测头——分类分支和回归分支共享同一套卷积特征，最终输出层直接预测 $[t_x, t_y, t_w, t_h, obj, cls_1, \\ldots, cls_C]$。\nYOLOv6 将检测头拆分为两个独立的分支：\n分类分支：专注于语义特征，预测各类别置信度 回归分支：专注于几何/空间特征，预测边界框坐标 这种解耦设计的逻辑非常清晰，分类任务依赖语义特征，定位任务依赖几何特征，强行耦合会让两个任务相互干扰，各自分开优化后精度更高。\n正样本匹配：Task-Aligned Learning（TAL） # 去掉了 Anchor 之后，正样本匹配策略也需要随之重新设计。YOLOv6 采用了来自 TOOD（2021）的 TAL（Task-Aligned Learning）。\nYOLOv5 的 Anchor-based 匹配依赖宽高比阈值决定哪个 Anchor 对应哪个 GT，本质是纯几何匹配。TAL 换了一个视角：既然最终关心的是分类准不准和框回归准不准，那就直接用这两个任务的质量联合评分，分数最高的候选框才被认为是正样本。\nTAL为每个GT Box和候选预测框计算一个任务对齐分数：\n$$t = s^{\\alpha} \\cdot u^{\\beta}$$其中 $s$ 是分类预测得分，$u$ 是预测框与GT的IoU，$\\alpha$ 和 $\\beta$ 控制两个任务的权重比例。再从所有候选中按 $t$ 值取 Top-k 作为正样本。而这里的 $s$ 和 $u$ 分别代表模型在分类和定位两个子任务上的当前预测质量，直接来自前向传播的输出，无需额外计算。这种用预测质量本身来指导正样本选择的设计，比纯几何的 Anchor 匹配更贴近训练目标，在实验中效果也更好。\n这样的匹配策略让\u0026quot;分类质量高且定位精准\u0026quot;的预测框优先成为正样本，从训练策略层面就对分类和回归两个任务进行了对齐，避免了分类打分高但框偏差大的\u0026quot;噪声正样本\u0026quot;污染梯度。\n损失函数设计 # 分类损失：VariFocal Loss（VFL） # 普通的 Focal Loss 通过 $(1 - p)^\\gamma$ 降低易分样本的权重，正负样本使用相同的调制策略。VariFocal Loss 来自 VariFocalNet（2021），对正负样本进行非对称调制：\n$$VFL(p, q) = \\begin{cases} -q(q\\log p + (1-q)\\log(1-p)) \u0026 q \u003e 0 \\text{（正样本）} \\\\ -p^{\\gamma} \\log(1-p) \u0026 q = 0 \\text{（负样本）} \\end{cases}$$正样本的学习目标 $q$ 不再是硬标签 1，而是预测框与 GT 的 IoU 值（软标签），迫使模型学习预测\u0026quot;这个框有多好\u0026rdquo;，而不只是\u0026quot;这里有没有目标\u0026quot;。负样本仍使用Focal Loss的指数调制压低简单背景样本的权重。这与 TAL 中对分类-定位联合质量的建模思路形成了很好的呼应。\n回归损失：SIoU Loss # YOLOv6 在回归损失上引入了 SIoU Loss（来自 SIoU（2022）），在 CIoU 的基础上额外加入了角度对齐代价（Angle Cost）：\n损失函数 组成项 CIoU IoU + 中心点距离 + 宽高比 SIoU IoU + 中心点距离 + 宽高比 + 角度对齐 SIoU 先通过角度损失约束预测框中心与 GT 中心连线的方向，引导预测框沿水平或垂直方向收敛；方向对齐后再用距离损失缩小中心点偏差。这种分步引导的收敛路径比 CIoU 更短，在实验中收敛速度更快。\n模型系列 # YOLOv6 同样提供了多个尺寸版本以覆盖不同部署场景，以下数据来自 YOLOv6 v3.0 论文（arXiv 2301.05586）：\n版本 参数量（约） mAP50-95（COCO val） 吞吐量（img/s） YOLOv6-N 4.7M 37.5% 1187 YOLOv6-S 18.5M 45.0% 484 YOLOv6-M 34.9M 50.0% 226 YOLOv6-L 59.6M 52.8% 116 数据来源：YOLOv6 v3.0 arXiv 2301.05586，COCO val2017，NVIDIA T4，TensorRT7\nv3.0 是对初版的全面升级，在各尺寸上精度均有显著提升，感兴趣的读者建议以 v3.0 为基准进行横向比较。\n总结 # YOLOv6 是 YOLO 数字序列中首个完全拥抱 Anchor-Free + 解耦头 设计范式的版本，同时以结构重参数化为核心卖点，将\u0026quot;训练期表达力\u0026quot;和\u0026quot;推理期效率\u0026quot;这两个看似矛盾的目标在同一个模型里统一了起来。\n相比 YOLOv5，YOLOv6 在 mAP50-95 上有约 7～8 个百分点的提升（以Small规格横向比较：45.0% vs 37.4%），在吞吐量上同样具有竞争力。\n不过，YOLOv6在社区影响力上确实不如 YOLOv5，也没有像 U 社那样形成完整的工程生态，但却开创了企业孵化YOLO模型的先河。作为美团内部孵化并推向开源的工业化产品，它更多地被定位为特定硬件场景下的工程方案，而非通用的社区生态项目。但它引入的结构重参数化和Anchor-Free设计理念，已经在整个目标检测社区播下了种子——你会发现，这些思想在此后的 YOLOv8、RT-DETR 等模型中都有不同形式的延续。\nYOLOv7 # 背景 # 2022年7月，《YOLOv7: Trainable Bag-of-Freebies Sets New State-of-the-Art for Real-Time Object Detectors》正式发布。论文出自 Chien-Yao Wang、Alexey Bochkovskiy 和 Hong-Yuan Mark Liao 之手——YOLOv4 的主要贡献者 Bochkovskiy 时隔两年回归，与台湾中央研究院的 Wang 和 Liao 再度联手。\n值得一提的是，YOLOv7 和 YOLOv6 几乎在同一时间段发布，这正是当时 YOLO 系列群雄割据的缩影。两者走的是截然不同的路线：YOLOv6 专注于结构重参数化和部署效率，YOLOv7 则把重心放在了更高的检测精度和更系统化的训练策略上，在精度-速度曲线上打出了当时实时检测器的新高点。\n论文的核心主张借用了 YOLOv4 的框架语言——Trainable Bag-of-Freebies（可训练的免费午餐），你可以把它理解为Alexey Bochkovskiy对自己两年前YOLOv4模型的更进一步。所有改进都不改变推理结构，不增加推理开销，却能通过更好的训练设计大幅提升精度。这也是论文名称的由来——在不牺牲速度的前提下，把精度\u0026quot;白赚\u0026quot;回来。\n整体结构 # YOLOv7 同样遵循 Backbone → Neck → Head 框架。最核心的架构创新集中在 Backbone——用 E-ELAN 取代了此前的 CSPDarknet / EfficientRep 路线；Neck 沿用 PANet 双向融合并以 E-ELAN 块为基本单元；Head 延续 YOLOv5 风格的耦合锚框检测头。\n1. E-ELAN Backbone（高效层聚合网络） # YOLOv7 的 Backbone 核心是 E-ELAN（Extended Efficient Layer Aggregation Networks），这一设计来自同团队对梯度路径的系统性研究。\n理解 E-ELAN，需要先理解 ELAN 的设计哲学：\n梯度从输出层反向传播时，路径越短信息越直接但特征复用越少，路径越长特征越丰富但梯度容易消失。ELAN 的核心思路是同时维护\u0026quot;短路\u0026quot;和\u0026quot;长路\u0026quot;两类梯度通道：输入在经过一系列卷积堆叠的同时，将不同深度处的中间层输出全部保留，最后统一 Concat 聚合。这样浅层的位置信息和深层的语义信息都能流入最终特征，梯度路径的长短由设计者显式控制，既不会太浅（特征单一），也不会太深（梯度消失）。\nE-ELAN 在 ELAN 的基础上引入 expand-shuffle-merge（扩展-混洗-归并） 策略，使其能干净地扩展到更大规模的模型：\nExpand（扩展）：在每条并行路径内部使用分组卷积（Group Convolution），将通道切分为多个独立的组，在不显著增加 FLOPs 的前提下扩展特征多样性 Shuffle（混洗）：对分组后的通道进行重排（类似 ShuffleNet 的 Channel Shuffle），使不同组之间的信息能够流通，避免各组特征退化 Merge（归并）：重新 Concat 聚合，保持整体通道数的一致性。 这样做的好处在于：模型容量可以通过增加分组数来扩展，而不必改动梯度路径的网络结构本身，保证了训练稳定性和可扩展性。\n2. 针对拼接架构的复合缩放策略 # YOLOv5 引入了通过YOLOv5同款的 depth_multiple 和 width_multiple 控制的复合缩放，这套策略对序列卷积网络工作良好。但 E-ELAN 是基于 Concat 拼接的聚合架构，存在一个隐患：\n当你缩放深度（增加 ELAN 中的并行路径数量）时，最终 Concat 输出的通道数也随之增加。如果不同步调整后续 过渡层（Transition Layer） 的宽度，通道数就会不匹配，整网无法正常构建。\nYOLOv7 为此专门设计了拼接架构的复合缩放规则：\n每次缩放深度时，必须同步缩放所有与之 Concat 关联的过渡层宽度，保持整网通道一致性。\n这是一个容易被忽视但至关重要的工程细节，也是 YOLOv7 能够干净地扩展出 YOLOv7-X / W6 / E6 / D6 / E6E 等完整系列版本的基础。\n3. 重参数化规划（RepConvN） # 受 YOLOv6 的启发，YOLOv7 同样希望在 ELAN 的卷积层上使用结构重参数化来提升训练期的表达能力。\n然而，原版 RepConv 的三路并联包含一条 Identity 捷径，这条直连路径在残差结构（$y = F(x) + x$）中天然成立；但在 ELAN 的 Concat 拼接结构里，不同来源的特征通道被并排拼接，Identity 加法会让通道语义混淆，破坏聚合结构的逻辑。\nYOLOv7 的解决方案是使用 RepConvN（去掉 Identity 分支的 RepConv）：\n$$y_{train} = Conv_{3\\times3}(x) + Conv_{1\\times1}(x)$$推理时仍然合并为单个 3×3 卷积。这样既保留了多分支训练的优化收益，又与 ELAN 的 Concat 拼接结构兼容。可以把这理解为 YOLOv7 根据自身网络拓扑对重参数化方案做出的定制规划（Planned Re-parameterization）——并非照搬 YOLOv6 的设计，而是针对自身架构特点做了适配。\n训练创新：可训练的 Bag of Freebies # 以下改进全部只作用于训练阶段，推理时网络结构完全不变，是真正意义上\u0026quot;不花推理代价\u0026quot;的精度提升。\n辅助训练头（Auxiliary Head） # YOLOv7 在 Backbone 或 Neck 的中间位置额外接入辅助检测头（Auxiliary Head），与最终的主检测头（Lead Head）并行。辅助头只在训练阶段参与前向和反向传播，为中间层特征提供额外的梯度监督信号；推理时辅助头被完全移除，不引入任何额外开销。\nYOLOv7 在此基础上设计了 粗细结合（Coarse-to-Fine） 的分工：\nLead Head（主检测头）：负责精细预测，采用较严格的正样本匹配策略，产出最终检测结果 Auxiliary Head（辅助检测头）：负责粗粒度预测，采用较宽松的正样本匹配策略，产出更密集的监督信号 辅助头的宽松匹配能分配到更多正样本，为中间层特征提供密集的监督，引导中间层表示往检测友好的方向收敛；主检测头的严格匹配则保证最终输出的精准度。训练结束后辅助头清零，只留主检测头做推理。\nLead Head 引导的标签分配 # 辅助头的\u0026quot;宽松匹配\u0026quot;到底宽松到什么程度？YOLOv7 的答案是：由 Lead Head 的预测结果来决定。\n具体机制：先用主检测头当前的预测结果运行一次标签匹配，得到精细正样本集合 $S_{lead}$；再在此基础上放松约束，将更多候选框纳入辅助头的正样本集合 $S_{aux}$：\n$$S_{aux} \\supseteq S_{lead}$$这样，辅助头的学习目标始终与主检测头的优化方向保持一致，不会因为宽松匹配引入梯度噪声——两个头从训练第一步起就协同收敛。\n模型系列 # YOLOv7 提供从轻量到超大的完整系列，以下数据来自 YOLOv7 原始论文（arXiv 2207.02696），测试集 COCO test-dev，推理设备 Tesla V100：\n版本 输入分辨率 参数量（约） mAP50-95 FPS（V100，batch=1） YOLOv7 640×640 36.9M 51.4% 161 YOLOv7-X 640×640 71.3M 53.1% 114 YOLOv7-W6 1280×1280 70.4M 54.9% 84 YOLOv7-E6 1280×1280 97.2M 56.0% 56 YOLOv7-D6 1280×1280 154.7M 56.6% 44 YOLOv7-E6E 1280×1280 151.7M 56.8% 36 数据来源：YOLOv7 arXiv 2207.02696，COCO test-dev，Tesla V100，batch=1\n在 640×640 输入下，YOLOv7 发布时以 51.4% AP / 161 FPS 超越了同期所有实时检测器，包括 YOLOv5-X（50.7% AP）。W6 / E6 / D6 / E6E 系列则是面向高精度场景的大分辨率版本。\n总结 # YOLOv7 是本期介绍的最后一个YOLO版本，也是YOLO系列里最后一个以\u0026quot;论文驱动、精度优先\u0026quot;为核心的大版本——此后 U 社接管，以 YOLOv8 开启了工程化优先的新时代。Alexey Bochkovskiy团队还是如同当初研发v4时一样，论文中无不展示着他们庞大的工作量和复杂的实验设计。\n论文的最大亮点不是单一创新，而是将 E-ELAN、辅助训练头、Lead Head 引导标签分配、RepConvN 这一系列改动统一在\u0026quot;可训练 BoF\u0026quot;的框架下：所有训练期的复杂机制，在推理时都归零，用户拿到的是一个干净、快速的推理模型。\u0026ldquo;训练复杂、推理简单\u0026quot;的哲学在 YOLOv7 之后成为高性能检测器设计的重要范式。\n从 v4 的“系统化 BoF/BoS 实验”，到 v5 的“PyTorch 生态迁移”，再到 v6 的“结构重参数化 + Anchor-Free”，再到 v7 的“可训练BoF + 保留每一层的特征图进行融合”，每一代 YOLO 都在前人的肩膀上找到了自己的突破口。而这四年间的积累，也为 YOLOv8 和此后更多版本的诞生提供了深厚的土壤。\n","date":"2026年05月08日","externalUrl":null,"permalink":"/Owen-Studio/posts/what-is-yolo2/","section":"Posts","summary":"","title":"YOLO的前世今生（中）","type":"posts"},{"content":" 前言 # YOLO(You Only Look Once)恐怕是现今刚刚接触并学习CV的人最先接触到的深度学习视觉模型， 笔者在2023年开始钻研CV领域时，自己训练的第一个视觉模型就是彼时最热的YOLOv5。YOLO系列毫无疑问是计算机视觉领域的经典之作， 我将分三篇博客介绍YOLO的前世今生。 今天随着这篇文章，让我们以一个轻松的方式来回看YOLO的前世今生，历代YOLO模型的特点，再以个人的视角展望YOLO接下来的发展。\n但首先，我们必须先回答一个非常简单的问题——\n什么是YOLO？ # 1.目标检测 or 图像分类 # 从现在的视角，把这两个视觉任务分开来讲似乎有些奇怪了，我们接触的众多视觉框架都原生支持这两大任务， 但在十数年前，这其实是两个截然不同的任务。图像分类回答的问题是\u0026quot;图里有什么？\u0026quot;，而目标检测回答的问题不仅是这一点， 它还需要回答\u0026quot;它在图片上的哪里？\u0026quot;，它的输出并不只是一串经SoftMax处理过后的概率，而是一组边界框（Bounding Box）+ 类别标签 + 置信度， 这意味着\u0026quot;两个阶段\u0026quot;，那就是寻找\u0026quot;what\u0026quot;和\u0026quot;where\u0026quot;的两个阶段。\n2.传统两阶段检测器的思路 # 以最经典的R-CNN系列为代表，在执行目标检测任务时，它们的流程是 \u0026ldquo;先提名，再判断\u0026rdquo;\n第一阶段，用选择性搜索（Selective Search，原始 R-CNN/Fast R-CNN 使用）或后来的区域提议网络（RPN，Faster R-CNN 引入）在图像上扫出约2000个\u0026quot;可能存在目标的区域\u0026quot;， 我们称之为候选框（Region Proposal）。第二阶段，把这2000个候选框一个个裁下来，分别送入CNN模型中进行特征提取， 再用传统的SVM（支持向量机）和线性回归器精修坐标，最后用NMS算法一个个去掉重叠框。\n乍一看流程非常的优雅、清晰，但实际上这里隐藏了一个致命的问题。2000个候选框，意味着2000次CNN前向传播，其中还有大量的候选框其实是无效框。 R-CNN在一张图片推理中要花40s-60s，实时检测速率仅有0.02fps左右，完全无法实现实时检测。 当然，后续的Fast R-CNN、Faster R-CNN对此进行了大量优化，但两阶段的架构锁死了它的上限。\n3.YOLO的革命性思想 # 为了将上面的两阶段处理，变成单阶段处理，YOLO给出了革命性的方案。 在最初始的YOLOv1上，模型把整张图像分为7x7的网格，每个网格用来预测若干个边界框， 其中每个框包含：\n中心坐标(x,y) 宽高(w,h) 置信度分数 所属类别的概率分布 整张图片只经过一次神经网络前向传播，所有单元格的预测同步完成，而这也就是名字\u0026quot;You Only Look Once\u0026quot;的由来。 传统的两阶段方法对每个候选区域独立计算特征，特征计算量随着特征图数量线性增长， 而YOLO一张图仅进行一次完整的前向传播，所有格子共享同一次前向传播的结果。\nYOLOv1 # 诞生背景（八卦） # YOLOv1发表的标志是2015年发表在CVPR的论文《You Only Look Once: Unified, Real-Time Object Detection》， 不知道各位读者有没有感觉，单看这些深度学习领域大佬起的论文标题都相当的大气，甚至有些倨傲，不过他们确实有这个实力啊。 这篇论文还有个很值得细品的点，论文作者团队中的Ross Girshick正是R-CNN系列的作者之一，这位传统两阶段检测的奠基人， 亲手参与构建了它的竞争对手。当然这也算是一种自我革新的精神吧。\n网络结构 # 接下来我们来看看YOLOv1的网络结构: 阶段一：捕捉大轮廓\n用7x7大卷积扫描整张图片，在第一步建立大的感受野，让网络能感知边缘、颜色块、大尺度纹理等信息，接着MaxPool将空间尺寸对半砍， 减少计算量，保留最显著的特征。\n阶段二：建立局部特征基础\n用3x3卷积进一步提炼局部特征结构，扩展通道数，让神经网络开始学习更多样、深层的语义组合，再经一次MaxPool压缩空间。\n阶段三：反复提取和压缩\n从这里开始出现1x1和3x3卷积不断交替的模式。1x1先收缩通道减少计算量，3x3再展开特征提取，让网络在不暴涨计算成本的前提下， 持续加强图像理解能力。 阶段四：反复打磨\n将上一个阶段的模式重复四次，堆叠卷积以让网络有足够的深度去理解图片的各类语义，从纹理、边缘逐渐抽象到物体的局部结构，比如轮子、眼睛、车窗。 通道数达到1024。\n阶段五：收拢网络\n用步长为2的卷积把空间压缩到7x7，完成从\u0026quot;特征提取\u0026quot;到\u0026quot;空间预测\u0026quot;的过渡。 最终的预测格子就是 7×7，网络在这里把整张图的特征信息对齐到这个网格上。\n阶段六：整合语义\n空间不再缩小，两层3x3的卷积对输入的特征图做最后的语义整合，为后续的回归预测做准备。\n阶段七：预测\n把7x7x1024的特征图展平，经过两个全连接层直接回归出所有预测值。预测的输出是7x7x30，7x7很好理解，就是前面的图像上被分割7x7个网格，注意，实际上这些网格是“不存在的”，在单次前向推理的时候仍然是完整的一张图片。而后面的30则是2x5+20。\u0026ldquo;5\u0026quot;是 $(x,y,h,w,c)$，两个是因为作者的设计，每个网格只有两个框，而20则是检测物体类别的概率，这里的两个框共用一组概率输出，这也使得一个网格只能预测一种类别的物体——即便它输出两个框，这两个框也必须属于同一个类。\n坐标编码 # YOLOv1的坐标编码实际上奠定了YOLO全系列的基础，以 $(x,y,w,h)$ 为基础进行归一化， 而具体来说 $(x, y)$ 是目标中心相对于所在格子的偏移，归一化到 $[0, 1]$。比如中心恰好在格子正中间，$x=0.5$, $y=0.5$。 $(w, h)$ 是目标宽高相对于整张图的比例，同样归一化到 $[0, 1]$。预测时取的是 $√w$ 和 $√h$，而非 $w$、$h$ 本身——原因是平方根能放大小数值区间的梯度，让小目标的宽高误差获得更强的惩罚信号，否则一个小框偏差 5px 和大框偏差 5px 在 MSE 下会得到相同的惩罚，这显然不公平。\n损失函数 # 整个损失函数由五项MSE相加构成，分别负责坐标、置信度、分类三个指标。\n定位损失：惩罚预测框的 $x$ $y$ $\\sqrt{w}$ $\\sqrt{h}$ 与真实值的偏差，引入权重系数 $\\lambda_{\\text{coord}}=5$，因为坐标预测相比分类更难学习、更难收敛， 所以可以对权重进行放大。\n置信度损失：对于存在目标的格子来说，置信度目标值是预测框和真实框的 $\\text{IoU}$，而不是直接用1；对于不存在目标的格子来说，置信度目标值为0， 加入权重系数 $\\lambda_{\\text{noobj}}=0.5$，设置这么低的原因很简单，大部分格子里都没有目标，我们把它视为\u0026quot;背景\u0026rdquo;，如果背景和真样本平起平坐，训练会被背景误导。\n分类损失： 这个损失只对含有目标的格子计算，并且使用MSE而非交叉熵——分类问题的标准做法是用交叉熵，v1 这里用 MSE 显然不是最优解，这也是它被后续版本批评和改进的地方之一。\n以上五项分别涵盖了两个定位损失（xy一组，wh一组），两个置信度损失（有目标/无目标），以及分类损失。 计算的公式相当的简单：\n$$L_{\\text{total}} = 5\\times(①+②) + ③ + 0.5\\times④ + ⑤$$\n每个单独的一项全部都使用MSE来单独计算。\n总结 # 作为YOLO系列的开山鼻祖，v1确实提供了即便放到现在来看也十分新颖的思路，他的创新重点并不在于对CNN神经网络内部层级进行大范围优化， 而是通过网格化和自定义的坐标编码实现了单阶段目标检测，这的确是非常新颖且非常有应用前景的，YOLO系列至今仍有充足的生命力有力地证明了这一点。 但它仍有诸多不足，如每个格子只能预测一个类，对于多类别且密集的场景模型几乎失效；全连接层丢失了大部分精细的空间信息；MSE在处理大目标和小目标的泛用性不好等等。通过这些缺陷，我们似乎能隐约看到日后每一代YOLO版本在这些方向作出的努力——多尺度检测、Anchor-based和Anchor-free，解耦头等等。 总而言之，这依然是一次伟大的创新，笔者写到这里时，YOLOv1的原始论文在Google scholar上已经被引用了七万六千余次，这足以说明这一模型的成功和其开创性的地位。\nYOLOv2/YOLO9000 # 诞生背景 # YOLOv2首次发表于2016年的CVPR，论文《YOLO9000: Better, Faster, Stronger》，主要作者是Joseph Redmon \u0026amp; Ali Farhadi，仔细看论文标题，点明了v2相对于v1作出改进的三个方向——Better, Faster, Stronger。\n网络结构——Faster # 先来看看 YOLOv2 的整体结构。\nv2 摒弃了 v1 中偏重分类设计的 GoogLeNet 风格网络，换用了专为检测任务设计的 Darknet-19 作为 backbone。顾名思义，这个网络共有 19 个卷积层和 5 个最大池化层。以每个 MaxPool 作为大层之间的分界点，整个网络可以划分为六个阶段，每个阶段的职责和 v1 大体一致——逐步提取从低级到高级的特征，同时缩小空间尺寸。\n感兴趣的读者可以参考这个开源实现查看完整结构细节。\nConv → BN → LeakyReLU\n值得重点关注的是，Darknet-19 中每一个卷积单元不再是单纯的二维卷积，而是由卷积、批归一化（Batch Normalization）和 Leaky ReLU 激活函数串联而成的完整结构。这个组合在 ResNet 和此后的现代 YOLO 版本中随处可见，但这里是纯粹的串联结构，并不包含残差连接。\nBN 的加入是一个重大改进。在原论文的实验中，仅凭加入 Batch Normalization，mAP 就提升了约两个百分点，且几乎不增加推理开销。除了精度收益，BN 还能稳定梯度、加速收敛，并通过正则化效果抑制过拟合。\n去掉全连接层\nv2 的另一个重要结构变化是彻底去掉了全连接层。v1 中的全连接层将特征图强行展平，破坏了 CNN 天然具备的空间结构信息。v2 改为全卷积结构，检测直接在特征图上进行，空间信息得以完整保留。\n输出格式：13×13×125\nv2 的输出张量是 13×13×125，拆开来看：\n13×13：最终特征图的空间尺寸，对应图像被划分成的网格 125 = 25 × 5：每个格子预测 5 个 Anchor 框，每个框输出 25 个值 25 = 5 + 20：5 个基础预测值（x, y, w, h, 置信度 c）加上 20 个类别概率（默认在 PASCAL VOC 上训练，共 20 类） 这里的 5 个 Anchor 是预先在训练集上聚类得到的不同尺寸候选框，每个格子都会针对这 5 个 Anchor 分别进行预测，Anchor 的具体含义和来历将在下一节详细介绍。\n精度优化——Better # 1. Batch Normalization\n前文已提及，此处不再赘述。BN 层的优点已被深度学习领域广泛认可，是 v2 在训练稳定性上的重要基础。\n2. Anchor Box + K-Means 聚类\n这是 v2 最核心的架构变化，也是 YOLO 从 Anchor-free 转向 Anchor-based 的关键节点。\nv1 让网络从零开始直接回归坐标，学习难度大，收敛慢。v2 引入先验框（Anchor Box），网络只需预测相对于这些先验框的偏移量，任务难度大幅降低，召回率也随之提升约 7%。\n先验框的尺寸不是人工拍脑袋设定的，而是通过 K-Means 聚类在训练集上自动学习出来的。这里的距离度量也经过专门设计，用 $1 - \\text{IoU}(\\text{box}, \\text{centroid})$ 代替欧氏距离，使聚类结果中的框与真实框有更高的重叠度。实验表明，$K=5$ 是精度和复杂度之间最好的平衡点。\n你可能会问：为什么是 5 个 Anchor，而不是像 Faster R-CNN 那样用 9 个？论文中作者做了对比实验——RPN 用 3 种尺度 × 3 种长宽比手工设定 9 个 Anchor，平均 IoU 为 60.9；而 YOLOv2 用 K-Means 聚类得到的 5 个 Anchor，平均 IoU 就达到了 61.0，已经追平甚至略优于手工 9 个。在精度近似的情况下，5 个 Anchor 的计算开销显然更低，因此最终选定 K=5 作为 v2 的设计。\n3. 直接位置预测（Direct Location Prediction）\n这是和 Anchor 紧密配套的另一个改进。引入 Anchor 之后，作者很快发现一个新问题——训练初期模型不稳定，需要花很长时间才能收敛到合理范围。\n问题出在 Faster R-CNN 风格的坐标预测公式上：\n$$x = (t_x \\times w_a) + x_a, \\quad y = (t_y \\times h_a) + y_a$$这里 $t_x, t_y$ 是网络预测的偏移量。但 $t_x, t_y$ 完全没有约束——网络可以输出任意大小的值。这意味着一个挂在格子 (3, 3) 上的 Anchor，预测出的框中心可能跑到图像的任意位置。训练初期网络输出乱跳，框就在整张图上到处乱飞，自然难以收敛。\nYOLOv2 的解法是把 Anchor 中心强制锁死在它所属的那个网格内部：\n$$b_x = \\sigma(t_x) + c_x$$ $$b_y = \\sigma(t_y) + c_y$$ $$b_w = p_w \\cdot e^{t_w}$$ $$b_h = p_h \\cdot e^{t_h}$$其中 $(c_x, c_y)$ 是当前网格的左上角坐标；$\\sigma$ 是 sigmoid，把网络输出的偏移压到 $(0, 1)$ 之间——这样 $b_x$ 永远落在所属格子的范围内，无论网络输出什么值，框中心都不会跑到隔壁格子去。宽高方向用 $e^{t_w}$、$e^{t_h}$ 取指数，一来天然保证宽高永远是正数，二来在对数空间下，\u0026ldquo;放大 2 倍\u0026quot;和\u0026quot;缩小 1/2\u0026quot;是数值上对称的扰动，对网络的优化更友好。\n这一改动配合 K-Means Anchor 一起使用，相比单独引入 Anchor 又额外贡献了约 5% 的 mAP 提升，且训练曲线明显更稳定。这套坐标编码方式也一直被沿用到 YOLOv3、v4、v5，可以说是 Anchor-based YOLO 的\u0026quot;标配公式\u0026rdquo;。\n4. 高分辨率分类器预训练\nv1 的训练流程是先用 224×224 在 ImageNet 上训练分类器，再直接切换到 448×448 做检测微调。这个分辨率突变让模型需要花大量迭代重新适应新的输入尺寸。\nv2 在两者之间插入了一个过渡步骤：在正式进行检测训练之前，先用 448×448 在 ImageNet 上额外微调 10 个 epoch，让骨干网络充分适应高分辨率。这一步单独贡献了约 4% 的 mAP 提升。\n5. 细粒度特征直连（Passthrough Layer）\n检测头接收的是经过多次下采样后的低分辨率特征图，对小目标不够友好——目标太小时，到了 13×13 的特征图上可能只剩下一个格子甚至更少的响应。\nv2 的解法是引入一条\u0026quot;直通层（Passthrough Layer）\u0026quot;：将网络中段较高分辨率的特征图（26×26×512）直接拼接到最终检测层，让检测头同时看到高层语义特征和低层细节特征。到这里，其实我们已经可以看到类似FPN的思想了，将浅层高分辨率特征引入深层预测，增强特征表达\n拼接时有一个格式对齐的技巧：26×26 的特征图无法直接与 13×13 拼接，因此先通过**空间重排（Space-to-Depth）**将其变形为 13×13×2048，再与主路特征图做 concat，在不改变空间尺寸的前提下完成融合。\n6. 多尺度训练（Multi-Scale Training）\n由于 v2 已经去掉了全连接层、整个网络是全卷积结构，输入图片的尺寸不再被锁死——这成了多尺度训练的前提。\n具体做法很简单：每训练 10 个 batch，就从 $\\{320, 352, 384, ..., 608\\}$ 这十个尺度中随机选一个（都是 32 的倍数，对应 stride=32 后特征图尺寸为 10×10 到 19×19），作为接下来 10 个 batch 的输入分辨率。同一个网络在训练过程中要被迫适应各种不同的输入尺寸，于是天然学到了尺度鲁棒性。\n这样训练出来的模型有一个非常实用的特性：推理时可以自由切换输入分辨率，一个权重对应一条速度-精度曲线。想要更快？用 288×288 输入，速度极高但精度略降；想要更准？用 544×544 输入，速度慢一些但 mAP 显著更高。部署时可以根据硬件性能灵活选择，而不需要为不同场景训练多个模型。\n论文里给出的对比数据：416×416 输入下 67 FPS / 76.8 mAP，544×544 输入下 40 FPS / 78.6 mAP，同一套权重不同推理分辨率，自然形成了一条速度-精度的 Pareto 前沿。这种\u0026quot;训练一次、多种部署\u0026quot;的特性也是 v2 相比 v1 在工程层面的一个重要进步。\nYOLO9000如何检测9000类——Stronger # 这是论文里最有创意的部分，解决了一个数据集层面的根本矛盾：检测数据集（COCO）有标注框但类别少，分类数据集（ImageNet）类别多但没有位置标注，两者的类别体系还互不兼容——COCO 里的\u0026quot;狗\u0026quot;和 ImageNet 里的\u0026quot;德国牧羊犬\u0026quot;\u0026ldquo;柯基\u0026quot;并不互斥，不能用普通 Softmax 建模。\n于是论文中提出了 WordTree来解决这个问题，WordTree 的解法是把 ImageNet 和 COCO 的类别映射到 WordNet 的层级结构上，构造一棵树形分类体系。COCO 的标注是粗粒度的通用类别（如\u0026quot;飞机\u0026rdquo;），ImageNet 的标注是细粒度的（如\u0026quot;滑翔机\u0026quot;\u0026ldquo;喷气机\u0026quot;\u0026ldquo;空客\u0026rdquo;），WordTree 将这些细类作为\u0026quot;飞机\u0026quot;的子节点挂载。 把COCO 检测数据集和 ImageNet 前 9000 个类别通过 WordTree 合并后，最终的树形结构包含 9418 个节点论文标题《YOLO9000: Better, Faster, Stronger》里的 9000 就是这么来的。笔者在阅读这一部分的论文原文时，再一次回忆起了当初被“Tree”数据结构支配的恐惧，对于算法初学者来说，Tree数据结构中大量的递归绝对是最难以理解的算法之一，但在这里用一颗WordTree巧妙的解决了标签分类问题，实在是相当的高明。\n回到正题，v2在预测时也不是用一个简单的Softmax，而是在树的每一个节点做兄弟节点之间的Softmax，预测条件概率 $ P(德国牧羊犬 | 狗)$。最终某个类的绝对概率是从根节点到该类路径上所有条件概率的乘积。这简直可以考一道算法题\u0026hellip;\u0026hellip;\n联合训练时，COCO 检测图和 ImageNet 分类图混合输入：检测图反传完整损失（坐标 + 置信度 + 分类），分类图只反传分类损失；COCO 以 4:1 的比例过采样以平衡数据集规模差异。\n最终，YOLO9000 在 ImageNet 检测验证集上取得 19.7 mAP，其中对于 156 个从未见过检测标注的类别仍能达到 16.0 mAP，同时保持实时检测速度。\n总结 # v2继承了v1端到端检测的思想，在此基础上进行了全面的升级。换用更高效的backbone；引入BN层稳定训练；K-Means Anchor降低学习难度等等诸多改动，在论文中，每项改动都有可量化的收益体现，这也让笔者感叹在v1发布后的这一年里这篇论文的工作量之大。\n而最终的结果也没有让这些工作白费，论文中的结果也显示，YOLOv2在基准测试场景下（PASCAL VOC 2007 测试集，运行硬件是 Nvidia GeForce GTX Titan X） 输入416x416分辨率图片帧数 67 FPS ，达到 76.8 mAP，在图片输入544x544分辨率下帧率达到 40 FPS ，达到 78.6 mAP，同时超越了同期的 Faster R-CNN with ResNet 和 SSD，速度还比它们快得多。既更准，又更快，标题里的 \u0026ldquo;Better, Faster\u0026rdquo; 算是实 至名归。\n而YOLO9000的WordTree设计更是直到今天都让人耳目一新，这个联合训练的思路放在当年可以说是相当超前，用分类数据的规模弥补检测标注的稀缺，让模型的\u0026quot;词汇量\u0026quot;突破数据集的边界。这个方向后来在零样本检测、开放词汇检测等领域都有着深远的影响。\nYOLOv3 # 背景 # 来到YOLOv3，这篇论文发表于2018年——《YOLOv3: An Incremental Improvement》，标题已然表明，这只是“渐进性改进”，没有革命性突破。并且如果你真的有过阅读这篇论文，就会感觉这篇论文的文风是相当的随性，并且夹杂着作者大量的自嘲甚至是戏谑，Redmon真的是个很有趣的人，而这也是他自己在YOLO系列的最后一作，估计那时候的他不会想到，这系列居然能够薪火相传迭代至今。而Redmon本人，他在2020年以隐私和军事伦理为由退出了CV界，直到2026年又以共同作者的身份发了一篇《OlmoEarth: Stable Latent Image Modeling for Multimodal Earth Observation》，也是个相当传奇经历了。尽管如此。如果你有关注它的github主页 和 个人网站，其实他还是在继续着他感兴趣的工作的，至少他public出来的项目和网站，仍然在正常维护和更新。从网上对他不多的评价看，他是个相当有个性的人，也是我们熟知的“YOLO之父”。顺带一提，他似乎还是小马宝莉的粉丝(?)\nbackbone改进——Darknet-53 # 因为篇幅限制，我们依然不会详尽介绍这一网络结构。YOLOv2 的骨干是 Darknet-19，只有 19 层；YOLOv3 换成了 Darknet-53，共 53 层卷积，并在 ImageNet 上预训练后用于检测任务，检测网络在其基础上再叠加 53 层，整体共 106 层全卷积。\n既然不介绍详细网络结构，那么最关键的变化/改进在那里？最关键的无疑是引入了 残差连接 。每个残差块1×1 卷积（降通道）+ 3×3 卷积（提特征）+ shortcut（输入直连）构成，这里的设计显然借鉴了ResNet，解决了深层网络梯度消失的问题。论文的实验结果表示Darknet-53 在准确率上优于 ResNet-101，速度快 1.5 倍；精度与 ResNet-152 相当，速度快 2 倍。\n除了残差连接，Darknet-53 还有几个结构特点值得一提。Darknet-53完全去掉了 MaxPool，所有下采样都改用 stride=2 的 3×3 卷积——MaxPool 是固定的、不可学习的下采样方式，会硬性丢弃信息，而 stride 卷积参数可学，下采样过程本身也参与梯度更新，能保留更多有效特征，这也是现代 CNN 设计的主流做法。卷积单元继续沿用 v2 的 Conv → BN → Leaky ReLU 三件套，每个残差块进一步规范成 1×1 降通道 → 3×3 提特征 → shortcut 加回输入 的标准形式。\n除此之外，作者将整个网络分成 5 个 stage，每个 stage 入口用 stride=2 的 3×3 卷积把空间尺寸减半，随后堆叠若干残差块。从浅到深，五个 stage 的残差块数量分别是 1, 2, 8, 8, 4——绝大多数算力被堆在中间两段（8+8），这是因为中等抽象层级的特征（纹理→部件→物体）最值得反复提炼，而最浅层只是简单滤波、最深层只是高度抽象的语义表达，都不需要那么深。输入 416×416 时，五个 stage 的输出分辨率依次为 208 → 104 → 52 → 26 → 13，后三个尺度正好对应下一节 v3 多尺度检测的三个输入特征图——这并非巧合，Darknet-53 的 stage 设计本身就是为了配合后面的 FPN 多尺度预测安排的。而不论是本文的示例，还是原论文中的示例，都使用的416x416分辨率的图片作为参考，但实际上，得益于去除了全连接层，v3依然能够支持动态分辨率的输入，只需要保证输入的H和W是32的整数倍即可。\n多尺度预测——FPN 思想的引入 # 如果说 Darknet-53 解决的是\u0026quot;网络深了好不好训\u0026quot;的问题，那 v3 真正在检测能力上带来质变的，是多尺度预测。\nv1/v2 都只在单一尺度的特征图上做检测——v1 是 7×7、v2 是 13×13——对小目标天生不友好，目标稍微小一点在最深层特征图上就所剩无几，即使v2已经有了FPN思想的雏形，但并不完整，不过是把浅层的特征图下采样后再一次concat。v3 借鉴了同期 FPN（Feature Pyramid Network） 的思想，在三个不同分辨率的特征图上各做一次检测，输入 416×416 时三个检测尺度分别是：\n13×13（累积步长：stride=32）：负责检测大目标 26×26（stride=16）：负责检测中等目标 52×52（stride=8）：负责检测小目标 构造方式是FPN中经典的\u0026quot;自顶向下 + 横向连接\u0026rdquo;——深层 13×13 特征图先经过几层卷积后做检测，再上采样 2 倍得到 26×26，与 backbone 中段的同分辨率特征图做 concat；融合后的 26×26 再上采样到 52×52，与浅层特征图 concat。深层提供\u0026quot;是什么\u0026quot;的语义信息，浅层提供\u0026quot;在哪里\u0026quot;的精细位置信息，融合后输入各自的检测头。\n这一改动直击 v1/v2 的最大短板。在 COCO 上，v3 的小目标 AP 相比 v2 大幅跃升，作者本人在论文里也不无得意地写道一句\u0026quot;我家的小目标检测好多了（we get much better small object detection）\u0026quot;。更重要的是，这一设计为后面全部的YOLO基本模型定了基调，\u0026ldquo;多尺度 + FPN\u0026rdquo; 这套架构从此成为之后所有 YOLO 版本（乃至大量其他检测器）的标配。\nAnchor 与坐标编码 # Anchor 的设计在 v3 中变成了 9 个 Anchor 分到三个尺度——继续用 K-Means 聚类得到 9 个尺寸，按大小排序后大的分给 13×13、中等的给 26×26、小的给 52×52。这样每个格子在自己的尺度上只针对 3 个 Anchor 做预测，Anchor 形状和它要检测的目标尺度天然匹配，省去了\u0026quot;小目标硬挤大特征图\u0026quot;的尴尬。\n每个尺度上的输出张量为 $N \\times N \\times [3 \\times (4 + 1 + C)]$，COCO 80 类时就是 $N \\times N \\times 255$。三个尺度合起来，整张图共预测 $(13^2 + 26^2 + 52^2) \\times 3 = 10647$ 个候选框，再经过 NMS 过滤得到最终输出。\n坐标编码完全沿用 v2 的\u0026quot;直接位置预测\u0026quot;——sigmoid 锁中心、exp 缩放宽高，这套公式在 v3 这里已经成型并固化，被后续 v4、v5 一直沿用至今。\n分类头——从 Softmax 到独立 Sigmoid # v3 另一个值得一提的改动是把分类头从 Softmax 改成了独立 Sigmoid + 二元交叉熵（BCE）。\n原因在于像 Open Images 这样的数据集存在标签层级——一个对象既是\u0026quot;人\u0026quot;又是\u0026quot;女人\u0026quot;，标签之间不互斥。Softmax 强制所有类别概率之和为 1 的假设在这种场景下就不成立了。改成独立 Sigmoid 后，每个类别各自做一次二分类判断，互不干扰，多标签对象也能被正确处理。\n论文中提到这一改动在 COCO（标签互斥）上对最终精度几乎无影响，主要意义是让模型在多标签场景下具备了原生支持，扩展性更好。\n正负样本匹配——引入\u0026quot;忽略\u0026quot;机制 # v3 的样本匹配规则也比 v2 更精细，引入了一个新的样本类别——忽略样本（ignore）：\n正样本：与某个 GT 框 IoU 最大的 Anchor，承担坐标 + 置信度 + 分类的全部损失。每个 GT 只指定唯一一个正样本 Anchor。 忽略样本：与某个 GT 的 IoU 高于阈值（默认 0.5）但不是最大值的 Anchor，不计入任何损失——既不当正样本也不当负样本。 负样本：与所有 GT 的 IoU 都低于阈值的 Anchor，只参与置信度损失（目标值为 0）。 引入\u0026quot;忽略\u0026quot;这一类别的关键意义在于：那些\u0026quot;看起来像但不是最佳匹配\u0026quot;的 Anchor，如果硬性当成负样本来惩罚，会给模型传递相互矛盾的信号——明明也挺像，凭什么算错？把它们直接屏蔽掉，训练信号会更干净，模型收敛也更稳。而到了这一步，Anchor-based模式已经大成，这样的模式将持续到Ultralytics主导的YOLOv5当中。\n总结 # YOLOv3 标题里那句 \u0026ldquo;Incremental Improvement\u0026rdquo; 看似低调，实际上 v3 是 YOLO 系列在工业界用得最久、最广的一代。Darknet-53 + 多尺度 FPN + 独立 Sigmoid 分类头这三件事合起来，让 v3 在精度与速度的平衡上达到了相当成熟的水平——COCO 上 320×320 输入 22 ms / 28.2 mAP，608×608 输入 51 ms / 33.0 mAP，同等精度下速度比同期的 RetinaNet 快约 3 倍。\n经过三次迭代，YOLOv3将端到端检测真正做到了完全可用的地步，甚至有传言美国军队当中也在使用YOLOv3，而这也成为促使Redmon退出CV界的重要原因。也正是从 v3 开始，YOLO 真正成为了\u0026quot;目标检测的事实标准\u0026quot;，\u0026ldquo;You Only Look Once\u0026quot;的口号真正开始风靡整个CV界。后续的所有YOLO版本，从v4到最近的v26无一不是在v3的基础上进行的改造，你在它们身上一定能看到v3结构的影子，v1和v2也许是作者在端到端检测领域的探索，v3才是真正的YOLO系列奠基者，而它的后辈在此基础上不断换用更强的backbone，更轻量化的模块，更加强大的检测头，甚至到v8时代的Anchor-Free，YOLO系列仍然在不断创新。\n这是YOLO前世今生系列的第一篇，我们介绍了v1-v3的发展，从懵懂探索期逐渐成熟，每一个模型我们都花费了大量篇幅，因为这是理解YOLO系列的基础， 弄懂了这些，后面学习更强、更抽象的模型就不用再重复讲这些基础的架构，下一篇我将介绍从v4到v7的发展历史和每个模型的改进特点，敬请关注。\n","date":"2026年05月07日","externalUrl":null,"permalink":"/Owen-Studio/posts/what-is-yolo/","section":"Posts","summary":"","title":"YOLO的前世今生（上）","type":"posts"},{"content":"","date":"2026年05月06日","externalUrl":null,"permalink":"/Owen-Studio/tags/swin-transformer/","section":"Tags","summary":"","title":"Swin Transformer","type":"tags"},{"content":" 前言 # 在前面的博客中，我简单介绍了ViT这一初始模型的结构， 同时也提到了，ViT风格模型有着诸多变体，今天我将介绍由微软出品的Swin Transformer系列变体，介绍关于他的核心改进和算法原理，以及代码实现。\n如果你还没有阅读上一篇博客， 建议先阅读完再来阅读本篇，本篇博客将会重点聚焦算法原理，偏基础的ViT细节将不会在这篇文章中提及。 同时，这将会是一个新的系列，我将拆分讲解更多的模型结构，从CNN到Transformer架构，从经典的VGG16到最近的DETR，敬请期待。\nSwin Transformer核心算法原理 # Swin Transformer的核心改进总体上可以分成四个点：\n窗口注意力（W-MSA）—— 解决计算复杂度问题 Patch Merging —— Stage 之间的下采样，构建多尺度金字塔 移位窗口（SW-MSA）—— 解决窗口之间信息隔绝问题 相对位置编码 —— 替代 ViT 的绝对位置编码 接下来我将逐一介绍上述的改进。\n1.窗口注意力（W-MSA） # 首先让我们回顾一下ViT的核心学习机制——多头注意力机制，如果你有一定算法与数据结构基础，就能相当轻松地发现，整套算法的计算复杂度是$O(N^2)$， N是指的Token数量。如一张224x224分辨率的图片用16x16的Patch切，N = 196，此时的复杂度还能够勉强接受。但如果要输入更高分辨率的图像以换取对小目标物体的敏感度的话，复杂度将会暴涨。比如512×512，$N$就变成 1024，复杂度暴增 27 倍，根本跑不动。\n那么Swin Transformer提出的解法是怎样的呢，就是把注意力限制在固定大小的局部窗口（默认7x7=49个token）。 每个token只和同一窗口内的其他token做注意力计算，窗口之间完全并行处理。\n设图像被切成 $H \\times W$ 个 patch，窗口大小为 $M \\times M$， 全局注意力和窗口注意力的计算复杂度对比如下：\n$$\\Omega(\\text{MSA}) = 4HWC^2 + 2(HW)^2C$$$$\\Omega(\\text{W-MSA}) = 4HWC^2 + 2M^2HWC$$其中 $C$ 是每个 token 的 embedding 维度。 可以看到，全局注意力的复杂度包含 $(HW)^2$ 项，随分辨率平方增长； 而窗口注意力将这一项替换为 $M^2 \\cdot HW$， 由于窗口大小 $M$ 是固定常数（默认为 7），复杂度退化为关于图像尺寸的线性复杂度 $O(N)$， 这使得 Swin 可以处理任意分辨率的输入而不会出现显存爆炸。\n如果图像输入分辨率是 224×224，按照 $M=7$ 的窗口大小划分：\n图像首先经过 Patch Embedding，用 4×4 的 patch 切分，得到 $\\frac{224}{4} \\times \\frac{224}{4} = 56 \\times 56$ 个 token 再按 $M=7$ 的窗口划分，共得到 $\\frac{56}{7} \\times \\frac{56}{7} = 8 \\times 8 = 64$ 个窗口 每个窗口内有 $7 \\times 7 = 49$ 个 token，仅在窗口内部做注意力计算 复杂度对比：\n$$\\Omega(\\text{MSA}) = 2 \\times (56 \\times 56)^2 \\times C = 2 \\times 3136^2 \\times C \\approx 1.97 \\times 10^7 C$$$$\\Omega(\\text{W-MSA}) = 2 \\times 7^2 \\times 56 \\times 56 \\times C = 2 \\times 49 \\times 3136 \\times C \\approx 3.07 \\times 10^5 C$$窗口注意力的计算量仅为全局注意力的 $\\dfrac{49}{3136} \\approx \\dfrac{1}{64}$， 计算量降为原来的 1/64，代价只是把感受野从全局缩小到了单个窗口内， 而当输入分辨率继续升高时，这一比例还会进一步拉大。\n在代码中，\u0026ldquo;切窗口\u0026quot;和\u0026quot;还原窗口\u0026quot;由两个函数完成：\npythondef window_partition(x, window_size: int): \u0026#34;\u0026#34;\u0026#34;将特征图切分为一个个独立的窗口 [B, H, W, C] -\u0026gt; [num_windows*B, window_size, window_size, C] \u0026#34;\u0026#34;\u0026#34; B, H, W, C = x.shape x = x.view(B, H // window_size, window_size, W // window_size, window_size, C) return x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C) def window_reverse(windows, window_size: int, H: int, W: int): \u0026#34;\u0026#34;\u0026#34;将计算完注意力的窗口还原回完整特征图 [num_windows*B, window_size, window_size, C] -\u0026gt; [B, H, W, C] \u0026#34;\u0026#34;\u0026#34; B = int(windows.shape[0] / (H * W / window_size / window_size)) x = windows.view(B, H // window_size, W // window_size, window_size, window_size, -1) return x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1) window_partition 把形状为 $[B, H, W, C]$ 的特征图， 按照窗口大小切分成 $\\frac{H}{M} \\times \\frac{W}{M}$ 个窗口， 每个窗口展平为 $[M^2, C]$ 的 token 序列， 所有窗口拼成一个大 batch $[\\frac{HW}{M^2} \\cdot B,\\ M^2,\\ C]$， 一次性送入注意力模块并行计算，效率极高。 计算完成后，window_reverse 再把所有窗口拼回完整的特征图。\n然而，窗口注意力引入了一个新的问题：窗口之间的信息完全隔绝。 每个 token 只能看到自己窗口内的邻居，不同窗口的 token 永远无法交流， 模型的感受野被硬性限制在一个窗口内，这显然不够。 这正是 Swin Transformer 第二个核心改进——移位窗口（SW-MSA） 要解决的问题。\n2. 编码器整体结构 # 在详细讲述 Patch Merging 与 SW-MSA 之前，我们必须先弄清楚编码器的整体结构。\n完成 Patch Embedding 后，Swin Transformer 的编码器由 4 个 Stage 串联而成， 每个 Stage 内部包含若干个 SwinTransformerBlock（简称 Block）， 每个 Block 的结构和 ViT 的 Encoder Block 完全一致： Pre-LN → 注意力 → 残差 → Pre-LN → MLP → 残差。\n以 Swin-Tiny 为例，4 个 Stage 的 Block 数量和通道数如下：\nStage Block 数 输入分辨率 通道数 C Stage 1 2 56×56 96 Stage 2 2 28×28 192 Stage 3 6 14×14 384 Stage 4 2 7×7 768 对应代码中的工厂函数配置：\npythondef swin_tiny_patch4_window7_224(num_classes=1000): return SwinTransformer( embed_dim=96, depths=(2, 2, 6, 2), # 4个Stage的Block数 num_heads=(3, 6, 12, 24), # 每个Stage的注意力头数 window_size=7, ) 可以看到两个规律：\n通道数每个 Stage 翻倍：$96 \\to 192 \\to 384 \\to 768$，由 embed_dim * 2^i 决定 分辨率每个 Stage 减半：$56 \\to 28 \\to 14 \\to 7$，由 Stage 之间的下采样操作完成 这种\u0026quot;分辨率减半、通道翻倍\u0026quot;的层级结构，和经典 CNN（如 ResNet）的设计如出一辙， 这也是 Swin 能够直接对接 FPN 做检测和分割的根本原因——每个 Stage 的输出就是一个尺度的特征图。\n3. Patch Merging——下采样与通道变化 # Stage 与 Stage 之间的分辨率减半，由 Patch Merging 完成。 它的作用类似于 CNN 中的步长为 2 的卷积或池化，负责把空间分辨率压缩、通道数扩展。\n具体操作：把特征图上每个 $2 \\times 2$ 的相邻 patch 组合成一组， 将这 4 个 patch 的特征向量在通道维度上拼接，再经过一个 Linear 层压缩通道。\n以 Stage 3 到 Stage 4 的过渡为例，输入是 $14 \\times 14$ 个 patch，每个 patch 的维度是 $C=384$：\n$$\\text{输入：} [B,\\ 14 \\times 14,\\ 384] = [B,\\ 196,\\ 384]$$取出 $2 \\times 2$ 邻域的 4 个 patch，在通道维拼接：\n$$4 \\times C = 4 \\times 384 = 1536 \\text{ 维}$$patch 数量减少为原来的 $\\frac{1}{4}$：\n$$14 \\times 14 \\to 7 \\times 7 = 49 \\text{ 个 patch}$$此时形状为 $[B,\\ 49,\\ 1536]$，再经过一个 $1536 \\to 768$ 的 Linear 层压缩：\n$$\\text{输出：} [B,\\ 49,\\ 768]$$为什么通道是 $2C=768$ 而不是 $4C=1536$？\n这是有意为之的设计。4 个 patch 拼接后通道变为 $4C$， 但如果直接保留 $4C$，每个 Stage 的计算量会以 4 倍速增长，很快变得不可接受。 通过 Linear 层压缩到 $2C$，在保留空间下采样的同时， 将计算量的增长控制在可接受的范围内——分辨率减半带来的计算量减少（$\\frac{1}{4}$） 恰好被通道翻倍的增加（$\\times 4$）部分抵消，净效果是每个 Stage 的总计算量大致持平。\n对应代码：\npythonclass PatchMerging(nn.Module): def __init__(self, input_resolution, dim, norm_layer=nn.LayerNorm): super().__init__() self.reduction = nn.Linear(4 * dim, 2 * dim, bias=False) # 4C → 2C self.norm = norm_layer(4 * dim) def forward(self, x, H, W): B, L, C = x.shape x = x.view(B, H, W, C) # 取出2×2邻域的4个patch，在通道维拼接 x = torch.cat([ x[:, 0::2, 0::2, :], # 左上 x[:, 1::2, 0::2, :], # 左下 x[:, 0::2, 1::2, :], # 右上 x[:, 1::2, 1::2, :], # 右下 ], dim=-1) # [B, H/2, W/2, 4C] H_out, W_out = H // 2, W // 2 # LayerNorm + Linear 压缩通道：4C → 2C return self.reduction(self.norm(x.view(B, -1, 4 * C))), H_out, W_out 4. 移位窗口（SW-MSA）——打破窗口间的信息壁垒 # 层与层之间的交替\n在每个 Stage 内部，相邻两个 Block 交替使用两种注意力方式：\npython# BasicLayer 中，按奇偶为每个 Block 分配 shift_size SwinTransformerBlock( ..., shift_size = 0 if (i % 2 == 0) else window_size // 2 # 偶数 Block → shift_size=0 → W-MSA（普通窗口） # 奇数 Block → shift_size=3 → SW-MSA（移位窗口） ) 为什么要交替？ W-MSA Block 负责窗口内部的精细交流，其输出作为下一个 Block 的输入； SW-MSA Block 把窗口整体偏移 $\\lfloor M/2 \\rfloor = 3$ 个 patch， 让上一层被窗口边界隔开的相邻 token 得以在同一窗口内相遇，完成跨边界的信息交流。 两个 Block 串联，局部交流与跨窗口交流各做一次，形成完整的感受野扩展。\n直接移位的问题\n如果直接把 $7 \\times 7$ 的窗口向右下偏移 3 个 patch， 右下侧图像边缘区域会\u0026quot;移出\u0026quot;特征图，而左上方的图像则会凸出来，产生大小不一的残缺窗口，无法并行处理。 以 $14 \\times 14$ 的特征图为例：\n$$\\text{原始窗口数：} \\frac{14}{7} \\times \\frac{14}{7} = 4 \\text{ 个完整窗口}$$移位后：出现宽度为 3、4、7 的混合窗口，沿两个维度共有 3×3 = 9 种组合，无法统一处理\nCyclic Shift：循环折叠\nSwin 的解法是不真正\u0026quot;移动\u0026quot;窗口，而是对整张特征图做循环滚动—— 窗口向右下方偏移 $M/2$，右下角随之出现空缺，左上角的图片会凸出窗口 torch.roll 通过循环滚动把左侧和上方的 patch 补到左上角的空缺处：\npython# 特征图向左上方循环滚动，等价于窗口向右下方偏移 x = torch.roll(x, shifts=(-self.shift_size, -self.shift_size), dims=(1, 2)) 以 $14 \\times 14$、$M=7$、$\\text{shift}=3$ 为例，循环滚动后：\n原本位于左上角 $[0:3,\\ 0:3]$ 区域的 patch，折叠到了右下角 $[11:14,\\ 11:14]$ 的空缺处 特征图总 token 数不变，仍是 $14 \\times 14 = 196$ 个 按 $7 \\times 7$ 正常切分，仍得到 4 个形状统一的完整窗口，可以一次性并行处理\n$$\\text{滚动后切分：} \\frac{14}{7} \\times \\frac{14}{7} = 4 \\text{ 个窗口，每个 } 7 \\times 7 = 49 \\text{ 个 token}$$根据示意图来看，折叠后，依旧是四个形状统一的完整窗口，但显然除去左上角的窗口，其他的窗口都或多或少会有本不应该相邻的区域又拼接， 而他们之间是不应该互相计算注意力的，这就需要使用注意力掩码登场了。\nAttention Mask——屏蔽不合法的 token 对\n折叠后，部分窗口内同时包含了图像中本不相邻的区域，这样不相邻的区域不应该互相计算注意力， 需要增添一个掩码以消除这一问题。 解决方案是在初始化时预先计算好一个 attn_mask， 把同一窗口内来自不同区域的 token 对的注意力分数强制置为 $-100$：\n$$\\text{Softmax}(-100 + \\text{attn\\_score}) \\approx 0$$即注意力权重趋近于 0，效果等同于完全屏蔽，但不需要任何额外的分支处理。\nattn_mask 的计算逻辑是：把特征图划分为 9 个区域并分别编号（0-8）， 切分成窗口后，同一窗口内编号不同的 token 对就是需要屏蔽的非法对：\npython@staticmethod def _compute_attn_mask(H, W, window_size, shift_size): img_mask = torch.zeros(1, H, W, 1) # 把特征图按滑动切片划分为9个区域，分别赋予编号 0-8 h_slices = (slice(0, -window_size), slice(-window_size, -shift_size), slice(-shift_size, None)) w_slices = (slice(0, -window_size), slice(-window_size, -shift_size), slice(-shift_size, None)) cnt = 0 for h in h_slices: for w in w_slices: img_mask[:, h, w, :] = cnt cnt += 1 # 共 9 个区域，编号 0-8 # 切窗口，得到每个窗口内所有 token 的区域编号 mask_windows = window_partition(img_mask, window_size) # [nW, M, M, 1] mask_windows = mask_windows.view(-1, window_size * window_size) # [nW, M*M] # 同一窗口内，两个 token 编号相减不为 0 → 来自不同区域 → 置为 -100 attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2) # [nW, M*M, M*M] return attn_mask.masked_fill(attn_mask != 0, -100.0) \\ .masked_fill(attn_mask == 0, 0.0) 这个 mask 在初始化时计算一次，注册为 buffer，推理时直接复用：\npython# 在 forward 中加到注意力分数上 if mask is not None: nW = mask.shape[0] attn = attn.view(B_ // nW, nW, self.num_heads, N, N) \\ + mask.unsqueeze(1).unsqueeze(0) attn = attn.view(-1, self.num_heads, N, N) attn = attn.softmax(dim=-1) 计算完成后，反向滚动还原特征图位置：\npythonx = torch.roll(x, shifts=(self.shift_size, self.shift_size), dims=(1, 2)) 整个 SW-MSA 的完整流程：\n$$\\text{torch.roll}(-M/2) \\to \\text{window\\_partition} \\to \\text{W-Attention + mask} \\to \\text{window\\_reverse} \\to \\text{torch.roll}(+M/2)$$所有 token 在每一层都被注意力完整覆盖，没有任何遗漏， 只是被 mask 屏蔽的非法 token 对实际上不产生信息交流—— 这是在不增加任何计算量的前提下，实现跨窗口信息流动的完整方案。\n5. 整体梳理 # 这是 Swin Transformer 中最精华也是最难理解的部分，我们再做一次完整的数据流梳理。\nStage 的概念\n一个 Stage 可以类比为 ViT 中的若干个 Encoder Block 串联， 但在 Stage 尾部额外接了一个 Patch Merging 模块负责下采样—— 分辨率减半、通道翻倍，从而构建出类似 CNN 中 FPN 的多尺度特征金字塔。 正是这个层级结构，让 Swin Transformer 能够同时感知大目标和小目标， 成为检测和分割任务的强力骨干，也是它能在 COCO 等传统视觉基准上大幅刷新记录的根本原因。\nStage 内部结构\nStage 内部的每个 Block 结构与 ViT 完全一致：Pre-LN → 注意力 → 残差 → Pre-LN → MLP → 残差\n唯一的区别是注意力机制被替换为 W-MSA 和 SW-MSA 的交替组合—— 偶数编号（从0开始）的 Block 使用普通窗口注意力（W-MSA），奇数编号的 Block 使用移位窗口注意力（SW-MSA）， 因此同一 Stage 内永远不会出现两个相同类型的 Block 相邻的情况。\n完整数据流（以 Swin-Tiny，输入 224×224 为例）\n$$[B,3,224,224] \\xrightarrow{\\text{Patch Embed}} [B,3136,96]$$$$\\xrightarrow{\\text{Stage 1: 2个Block}} [B,3136,96] \\xrightarrow{\\text{Patch Merging}} [B,784,192]$$$$\\xrightarrow{\\text{Stage 2: 2个Block}} [B,784,192] \\xrightarrow{\\text{Patch Merging}} [B,196,384]$$$$\\xrightarrow{\\text{Stage 3: 6个Block}} [B,196,384] \\xrightarrow{\\text{Patch Merging}} [B,49,768]$$$$\\xrightarrow{\\text{Stage 4: 2个Block}} [B,49,768]$$最终根据任务类型分叉：\n分类：全局平均池化 → Linear → $[B,\\ \\text{num\\_classes}]$ 检测 / 分割：各 Stage 输出 reshape 为 2D 特征图，返回 p2/p3/p4/p5 四个尺度（在每个 stage 进行 patch merging 之前提取） 6. 相对位置编码 # 介绍完窗口注意力和移位窗口，还有一个细节值得单独拿出来讲—— Swin Transformer 用相对位置编码替换了原始 ViT 中的绝对位置编码。\n为什么要换掉绝对位置编码 # ViT 的绝对位置编码是一个形状为 $[1, N+1, C]$ 的可学习参数，通过与原始embedding相加得到包含绝对位置信息的语义向量。 而这也就意味着图像上的每一个位置是一个固定的值、固定的形状，这看上去似乎没有什么不妥，但却存在一个可能的隐患， 那就是绝对位置编码和输入分辨率强绑定。如果训练时输入是 $224 \\times 224$（196 个 patch）， 推理时换成 $384 \\times 384$（576 个 patch），就会导致训练得到的位置编码和输入的图像信息对不上号，形状不对， 必须在pytorch中插值处理，会降低精度。\n而Swin 的窗口注意力只在 $M \\times M$ 的窗口内计算， 窗口大小固定为 $7 \\times 7 = 49$ 个 token， 因此位置关系只需要描述窗口内两个 token 之间的相对距离， 而不需要知道它们在整张图中的绝对坐标。 这天然适合用相对位置编码来描述，同时也带来了更好的分辨率泛化能力。\n相对位置编码的数学形式 # 注意力分数的计算公式在原始 Transformer 的基础上加入了相对位置偏置项 $B$：\n$$\\text{Attention}(Q, K, V) = \\text{Softmax}\\left(\\frac{QK^T}{\\sqrt{d}} + B\\right)V$$其中 $B \\in \\mathbb{R}^{M^2 \\times M^2}$ 是一个偏置矩阵， $B_{ij}$ 表示窗口内第 $i$ 个 token 和第 $j$ 个 token 之间的相对位置偏置值， 由两个 token 的相对坐标 $(\\Delta h, \\Delta w)$ 查表得到。\n如何构建相对位置索引 # 窗口大小 $M=7$，窗口内每个 token 的坐标范围是 $[0, 6]$， 两个 token 之间的相对坐标范围是 $[-(M-1), M-1] = [-6, 6]$，共 $2M-1=13$ 个可能值。 行和列各有 13 个可能值，组合后共 $(2M-1)^2 = 169$ 种相对位置关系。\n代码中预先计算好所有 token 对的相对位置索引，存为 relative_position_index：\npython# 生成窗口内所有 token 的坐标网格 coords_h = torch.arange(self.window_size[0]) # [0,1,2,3,4,5,6] coords_w = torch.arange(self.window_size[1]) # [0,1,2,3,4,5,6] coords = torch.stack(torch.meshgrid(coords_h, coords_w, indexing=\u0026#34;ij\u0026#34;)) # [2, 7, 7] coords_flatten = torch.flatten(coords, 1) # [2, 49] # 计算所有 token 对之间的相对坐标：[2, 49, 49] relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] relative_coords = relative_coords.permute(1, 2, 0).contiguous() # [49, 49, 2] # 将相对坐标从 [-6,6] 平移到 [0,12]，方便作为索引使用 relative_coords[:, :, 0] += self.window_size[0] - 1 # 行方向平移 relative_coords[:, :, 1] += self.window_size[1] - 1 # 列方向平移 # 行方向乘以 (2M-1)，将二维索引展平为一维 relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1 self.register_buffer(\u0026#34;relative_position_index\u0026#34;, relative_coords.sum(-1)) # [49, 49] 以两个 token 坐标 $(2, 3)$ 和 $(5, 1)$ 为例，计算其一维索引：\n$$\\Delta h = 2 - 5 = -3 \\xrightarrow{+6} 3$$ $$\\Delta w = 3 - 1 = 2 \\xrightarrow{+6} 8$$ $$\\text{index} = 3 \\times 13 + 8 = 47$$即这对 token 的相对位置偏置从 relative_position_bias_table 的第 47 行取出。\n位置偏置表的查表过程 # relative_position_bias_table 是一个形状为 $[(2M-1)^2,\\ \\text{num\\_heads}]= [169, \\text{num\\_heads}]$ 的可学习参数， 训练时和模型其他参数一起更新：\npythonself.relative_position_bias_table = nn.Parameter( torch.zeros((2 * self.window_size[0] - 1) * (2 * self.window_size[1] - 1), num_heads) ) # [169, num_heads] nn.init.trunc_normal_(self.relative_position_bias_table, std=0.02) 在前向传播中，用预计算好的 relative_position_index 查表，得到偏置矩阵 $B$， 加到注意力分数上：\npython# 查表：[49*49] → [49, 49, num_heads] → [num_heads, 49, 49] rpb = self.relative_position_bias_table[self.relative_position_index.view(-1)] rpb = rpb.view(self.window_size[0] * self.window_size[1], self.window_size[0] * self.window_size[1], -1).permute(2, 0, 1) # 加到注意力分数上，unsqueeze(0) 对应 batch 维度 attn = attn + rpb.unsqueeze(0) # [B*nW, num_heads, 49, 49] 相对位置编码的优势 # 相比绝对位置编码，相对位置编码有两个明显的优势：\n泛化性更强：编码的是 token 之间的相对关系而非绝对坐标， 窗口大小固定为 $7 \\times 7$，无论输入图像分辨率如何变化， 窗口内的相对位置关系始终只有 169 种，位置偏置表无需做任何修改。\n每个注意力头独立学习：relative_position_bias_table 的第二维是 num_heads， 每个头拥有自己独立的位置偏置，可以学到不同的空间感知模式， 有的头可能更关注水平方向的相邻关系，有的头更关注垂直方向， 这与多头注意力\u0026quot;让不同头关注不同特征\u0026quot;的初衷不谋而合。\n总结 # 到这里我们基本介绍完了 Swin Transformer 的算法原理。用简单的话来概括：\nSwin 的核心改动是将 ViT 中的全局多头注意力替换为 W-MSA 和 SW-MSA 交替串联的窗口注意力机制， 将注意力的计算复杂度从 $O(N^2)$ 降低到 $O(N)$，从根本上解决了 ViT 处理高分辨率图像时的计算瓶颈。\n由此衍生出了独特的多 Stage 层级结构——每个 Stage 通过 Patch Merging 完成下采样， 分辨率逐级减半、通道数逐级翻倍，构建出类似 CNN 中 FPN 的多尺度特征金字塔。 这一设计让 Swin 天然适配检测、分割等需要多尺度感知的视觉任务， 而这恰恰是原始 ViT 的短板所在，可谓一举两得。\n此外，Swin 还做了两处细节改进： 用相对位置编码替换了 ViT 的绝对位置编码，使模型对不同输入分辨率的泛化能力更强， 每个注意力头也能独立学习不同的空间感知模式； 引入了 DropPath 随机深度正则化，随机跳过整个残差分支而非单个神经元， 训练稳定性和模型的泛化能力都得到了进一步提升。\n这些改进共同造就了 Swin Transformer 在 COCO 等视觉基准上的统治级表现， 也使它成为了此后众多视觉大模型的标准骨干选择。\n常见问题 Q\u0026amp;A # Q：Swin-Tiny、Swin-Small、Swin-Base 三个变体的核心区别是什么？\nA：主要差异在两个地方——embed_dim 和 depths：\n变体 embed_dim depths 参数量 Swin-Tiny 96 (2,2,6,2) ~28M Swin-Small 96 (2,2,18,2) ~50M Swin-Base 128 (2,2,18,2) ~88M Tiny 和 Small 的通道数相同，区别只在 Stage 3 的深度（6 vs 18）； Base 在此基础上把基础通道数从 96 提升到 128，四个 Stage 的通道数变为 128/256/512/1024， 整体参数量和计算量都有显著提升，适合对精度要求更高的场景。\nQ：Swin 处理不同分辨率的输入时会有问题吗？\nA：基本没有问题，代码对此做了专门处理。 当输入分辨率不能被窗口大小整除时，SwinTransformerBlock 的 forward 会自动 padding：\npythonpad_b = (ws - H % ws) % ws pad_r = (ws - W % ws) % ws if pad_b \u0026gt; 0 or pad_r \u0026gt; 0: x = F.pad(x, (0, 0, 0, pad_r, 0, pad_b)) 计算完注意力后再裁剪掉补的部分，对结果没有影响。 相对位置编码只描述窗口内的相对关系，也不依赖输入分辨率， 所以 Swin 在处理任意分辨率输入时比 ViT 更加灵活。\nQ：Swin 既然已经有了层级结构，为什么还需要 FPN，直接用四个 Stage 的输出不行吗？\nA：直接用四个 Stage 的输出做多尺度检测是可以的，这叫做简单特征金字塔（Simple FPN）。 但标准 FPN 的价值在于自顶向下的特征融合—— 用高层的语义信息增强低层的细节信息，让 p2 这样的浅层特征图也具备足够的语义判断能力。 Swin 的各 Stage 输出之间没有跨尺度的信息流动， 接上 FPN 后浅层特征图的检测精度，尤其是对小目标的检测，会有明显提升。\nQ：DropPath 是什么，和 Dropout 有什么区别？\nA：DropPath 又叫随机深度（Stochastic Depth），是专门为残差网络设计的正则化手段。 Dropout 随机置零单个神经元，DropPath 随机跳过整个残差分支—— 被 drop 的 Block 直接输出输入本身（恒等映射），相当于这一层\u0026quot;不存在\u0026rdquo;。 在 Swin 代码中，越深的 Block 被赋予越高的 drop 概率（由 drop_path_rate 线性分配）， 训练时模型学会了在不同深度下工作，推理时所有 Block 都参与计算， 这既起到了正则化的效果，又隐式地让模型具备了一定的深度自适应能力。\n","date":"2026年05月06日","externalUrl":null,"permalink":"/Owen-Studio/posts/swin-transformer/","section":"Posts","summary":"","title":"Swin Transformer的算法原理详解","type":"posts"},{"content":"","date":"2026年05月06日","externalUrl":null,"permalink":"/Owen-Studio/tags/cnn/","section":"Tags","summary":"","title":"CNN","type":"tags"},{"content":"","date":"2026年05月06日","externalUrl":null,"permalink":"/Owen-Studio/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"2026年05月06日","externalUrl":null,"permalink":"/Owen-Studio/tags/transformer/","section":"Tags","summary":"","title":"Transformer","type":"tags"},{"content":" vision transformer发展背景 # 2017年，一家来自谷歌的团队发表了可能将会影响人类历史走向的论文《Attention is All You Need》，提出了大名鼎鼎的Transformer架构，而现在各位所熟知的所有大语言模型，都是基于此基础架构的发展延伸。这个重大的架构发现可以说终结了一个时代，也开创了一个新的时代。在2017年之前，各大公司的AI lab研究路线高度集中在CNN（卷积神经网络）和RNN/LSTM（循环神经网络）两大基础范式之上，但因为彼时算力的限制，传统的架构似乎已经开发到了极限。而在新的Transformer架构来临后，各大AI lab迅速跟进，首先在NLP领域迎来爆发。2018年，谷歌推出BERT，预训练Tansfomer模型开始主导NLP领域发展；2019年，GPT-2在彼时算力并不充足的当下向世人展示了大幅超越传统模型的文本生成能力；2020年，拥有1750万亿参数的GPT-3,向通用NLP模型迈出了历史性一步。\nNLP领域在Transformer的驱动下高速发展，也有一些人在思考，Transformer能不能替代传统的CNN，在CV领域引发又一场革新。于是，2020年，Google Research 团队的 Dosovitskiy 等人在 ICLR 2021 发表论文《An Image is Worth 16×16 Words》，核心发现是：一个不含卷积组件的标准 Transformer 编码器，在有足够预训练数据的情况下，可以在图像识别上达到最先进的水平，这就是本篇博客的主角——Vision Transfomer(后文简述ViT)。在后面的几年里，VIT不断进行发展，又涌现了DeiT、PVT、TNT、Swin等一众变体，而上述的这些VIT模型，日后也成为了多模态视觉-语言模型中的重要基石。\n阅前杂谈\n本篇博客旨在向已有一定Transformer基础的人介绍VIT结构、优势、使用策略，如果你对Transformer的Encoder-Decoder结构不甚了解，建议先去网上寻找其他资料学习Transformer。本篇博客的介绍将会结合理论和代码，代码部分需要一定pytorch基础，并尽可能减少数学公式的计算，以介绍原理实现为主，同时代码部分将会以作者在github的开源仓库 https://github.com/everglow01/VIT 为基础，欢迎各位的关注。\n初识ViT整体结构 # 1.图像切块——Patch Embedding # 各位浏览上图可以看到，完整图片再进入Transformer Encoder之前，进行了诸多图像的预处理，在这一小节中将介绍将图像分为一个个小块的原理，以及为什么要把完整的图片切成块，这似乎并不符合传统CNN对图像的处理方法。\n为什么要切块？ # 传统的CNN通过滑动卷积核，逐像素的提取图像局部特征，天然理解图像的空间结构。也就是说，对于CNN而言，模型的输入就是一张完整的图片。\n但Tansformer的输入必须是一个个序列，他只能处理token，对二维图像毫无概念，所以VIT所做的工作，就是要把二维图像翻译成Transformer能读懂的一个个token\n类比一下：如果一张图片是一篇文章，那么每一个切出来的小块（Patch）就是其中的一个词。我们规定一张 224×224 的图片，用 16×16 大小的 patch 切割，横竖各切 14 刀，正好得到 14×14 = 196 个 patch，也就是 \u0026ldquo;一篇 196 个词的文章\u0026rdquo;。Transformer 读完这 196 个词，就完成了对整张图的理解。\n如何实现图像切块？ # 直觉上，“切块”听起来像要用传统CV方法先将图像裁成一块块，再分别处理，但我们在工程实现上有更效率、更优雅的处理办法，就是在代码中用一个指定类型的Conv2d一步搞定：\npython# kernel_size = stride = patch_size，保证不重叠地覆盖每个 patch # 输入 [B, 3, 224, 224] → 输出 [B, 768, 14, 14] # B -\u0026gt; Batch_size self.proj = nn.Conv2d(in_c, embed_dim, kernel_size=patch_size, stride=patch_size) 看上去只有一行代码，但它实际上完成了VIT里相当重要的工作！ 当卷积核大小等于步长（stride）时，每次滑动都落在下一个完全不重叠的区域，这意味着卷积核在扫过每一个Patch时，完成了一次线性投影——将每个16x16x3=768（这是一个Patch包含的全部像素数，16x16的大小，3的通道数）维的原始像素块，映射到768维的embedding向量空间。这一步将切块和投影两步并一步，虽然不符合我们的直觉，但相当高效的处理好了我们所需要的输入数据。\n完成这一步后，你可能还是有疑问，这里的数据结构依然是一个二维数组，也就是一个“图像”，并不是一个“序列”。是的，所以我们需要将图像展平（reshape），把二维图像reshape成Transformer需要的“序列”格式：\npython# [B, 768, 14, 14] # → flatten(2) → [B, 768, 196] # 把空间维展平 # → transpose → [B, 196, 768] # token 维度放到中间 x = self.proj(x).flatten(2).transpose(1, 2) 至此，一张图片就变成了形如 [B, 196, 768] 的 token 序列，196 个 patch，每个 patch 用一个 768 维的向量表示，可以直接送入后续的 Transformer Encoder。\n2.位置编码与 CLS Token —— Patch + Position Embedding # 对应图中：中间一排编号 0*、1、2…9 的 token，其中 0* 标注为 \u0026ldquo;Extra learnable [class] embedding\u0026rdquo;。\n完成上一步后，我们又有了新的疑惑和思考，刚刚说了Transformer并不能够像CNN那样理解空间结构，而我们输入的“序列”又只是对应图片的一个部分，196 个 patch token，对 Transformer 来说只是\u0026quot;一袋无序的词\u0026quot;，它不知道 patch 1 在左上角，patch 196 在右下角，我们要如何让Transformer Encoder理解每一个部分在完整图片中的位置关系呢？\n位置编码——给每个 Token 一个\u0026quot;座位号\u0026quot; # ViT 的做法简单直接：为每一个 patch token 额外加上一个可学习的位置向量，让模型在训练过程中自己学会\u0026quot;第 N 个 token 对应图像的哪个区域\u0026quot;。\npython# 位置向量形状 [1, 197, 768]，训练时和模型参数一起更新 self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim)) # forward 中，因为形状与Patch完全一致，所以直接相加即可 x = self.pos_drop(x + self.pos_embed) 每个 token 的 embedding 向量，加上对应位置的 position embedding 后，就同时携带了\u0026quot;我是什么内容\u0026quot;和\u0026quot;我在哪里\u0026quot;两份信息，Transformer 也就有了感知空间位置的能力。\n细心的读者可能注意到了一个细节：pos_embed 的长度是 num_patches + 1，也就是 196 + 1 = 197。多出来的这个\u0026quot;1\u0026quot;是哪里来的？\nCLS Token——图像的\u0026quot;班长\u0026quot; # 这个\u0026quot;+1\u0026quot;来自一个特殊的可学习向量，叫做 CLS Token（Classification Token），也就是图中编号为 0* 的那个，注释写着\u0026quot;Extra learnable [class] embedding\u0026quot;。\n它借鉴自 NLP 领域的 BERT 模型：在所有 patch token 之前，插入一个与任务无关的\u0026quot;占位 token\u0026quot;，让它在经过 Transformer Encoder 的层层注意力计算后，自然地聚合整张图的全局信息。最终分类时，我们只取这一个 token 的输出，而不是对 196 个 patch token 做平均或拼接。\npython# 可学习参数，形状 [1, 1, 768]，每个样本共享同一个初始值 self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) # forward 中，expand 成 [B, 1, 768]，拼到序列最前面 cls_token = self.cls_token.expand(x.shape[0], -1, -1) x = torch.cat((cls_token, x), dim=1) # [B, 196, 768] → [B, 197, 768] 拼接之后，序列从 196 个 token 变成了 197 个，CLS token 坐在\u0026quot;0 号位\u0026quot;，后面跟着 196 个 patch token，整个序列再加上位置编码，就准备好送入 Transformer Encoder 了。\n可以用一个简单的比喻来理解 CLS token 的角色：196 个 patch 是班里的同学，各自携带着自己区域的信息；CLS token 是班长，它没有自己对应的图像区域，但通过注意力机制和所有同学\u0026quot;交流\u0026quot;，最终汇总出整个班级（整张图片）的综合情况，交给分类头做最终判断。\n3.核心模块 —— Transformer Encoder # 终于来到了激动人心的时刻，所有图像的预处理已经完成，我们要将处理好的一个个token送入Transformer Encoder进行计算和学习。 对应图中：右侧的 Transformer Encoder 展开图，Norm → Multi-Head Attention → Norm → MLP，重复 L 次。 这一部分是模型进行学习的核心部分，我将分为三个小块进行叙述\nMulti-Head Attention # 最经典的Transformer中的多头注意力机制，这里默认各位读者有一定的Transfomer基础，我仅简要叙述。\n观察上图，输入序列是两个已经携带了位置编码的词向量——红色的 \u0026ldquo;Mad\u0026rdquo; 和绿色的 \u0026ldquo;Cat\u0026rdquo;。 多头注意力的整个计算流程，可以跟着这张图从左到右走一遍。\n第一步：生成 Q、K、V\n每个 token 的 embedding，分别乘以三组可学习的权重矩阵 $W_Q$、$W_K$、$W_V$（图中左侧的 Attention Weights）， 得到对应的 Query、Key、Value 向量。 图中可以看到，红色和绿色的 token 各自生成了一组 Q/K/V， 它们的含义是：\nQ（Query）：我想找什么信息？ K（Key）：我能提供什么信息？ V（Value）：我实际携带的内容是什么？ 第二步：计算注意力权重\n用每个 token 的 Q，去和所有 token 的 K 做点积， 衡量两两之间的相关程度，再除以缩放因子 $\\sqrt{d_k}$ 防止数值过大， 最后经过 Softmax 归一化，得到图中的 Attention Weight Matrices—— 一个表示\u0026quot;谁该关注谁、关注多少\u0026quot;的权重矩阵。\n$$S = \\text{Softmax}\\left(\\frac{Q^TK}{\\sqrt{d_k}}\\right)$$第三步：加权汇总 V，得到多头输出\n用上一步的注意力权重，对所有 token 的 V 加权求和， 每个 token 就得到了一个融合了全局上下文的新表示。 图中右侧可以看到，红色头和绿色头分别输出了各自的结果（Multiple Attention-Heads）。\n$$ \\text{Attention}(Q, K, V) = \\text{Softmax}\\left(\\frac{QK^T}{\\sqrt{d_k}}\\right)V $$\u0026ldquo;多头\u0026quot;的精髓在于：把 embedding 维度切成 h 份，每一份独立做一次注意力计算， 让不同的头去捕捉不同类型的关联—— 有的头可能学会关注空间上相邻的 patch，有的头则关注语义上相近的 patch。\n第四步：拼接 + 输出投影\n将 h 个头的输出拼接成一个长向量（图中 Concatenated Vectors）， 再乘以输出投影矩阵 $W_O$（图中最右侧的蓝色矩阵）（$W_O$本身是一个随模型一起训练的 768×768的权重矩阵，没有什么特殊的来源， 就是一个普通的 nn.Linear，只不过它在结构上承担了\u0026quot;多头融合\u0026quot;这个语义角色，论文里给它起了个专门的名字叫 $W_O$） 压缩回原始的 embedding 维度，得到最终输出。\n在代码中，Q/K/V 的生成被合并进了同一个线性层，效率更高：\npython# 一次生成 Q/K/V，再 reshape 拆分到每个头 qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4) q, k, v = qkv[0], qkv[1], qkv[2] # 缩放点积注意力 attn = (q @ k.transpose(-2, -1)) * self.scale # 除以 sqrt(d_k) attn = attn.softmax(dim=-1) # 加权汇总 V，拼接多头，输出投影 x = (attn @ v).transpose(1, 2).reshape(B, N, C) x = self.proj(x) # 对应图中的 W_O 对 ViT 而言，注意力机制带来了 CNN 所不具备的能力： 每一个 patch token，在一次计算中就能直接和图中任意位置的 patch 交互， 不需要像 CNN 那样靠堆叠多层卷积来逐步扩大感受野。 这正是 ViT 能捕捉长距离依赖关系的根本原因。\nMLP / FFN（Mlp 类） # 注意力层负责让 token 之间充分交流，而接下来的 MLP，则类似于传统的CNN模型中的全连接层，负责对每个 token 单独做进一步的非线性变换 可以把它理解为：注意力层解决的是\u0026quot;我该关注谁\u0026quot;的问题，MLP 解决的是\u0026quot;我该如何理解我收集到的信息\u0026quot;的问题。\n结构非常简洁，两个线性层夹一个激活函数：\npythonself.fc1 = nn.Linear(in_features, hidden_features) # 升维：768 → 3072 self.act = nn.GELU() # 非线性激活 self.fc2 = nn.Linear(hidden_features, out_features) # 降维：3072 → 768 其中隐藏层的维度由 mlp_ratio=4 控制，即先把 768 维升到 4 倍（3072 维）， 经过激活函数引入非线性后，再降回 768 维。 先升维再降维的设计，是为了在高维空间中完成更丰富的特征变换， 同时保持输入输出维度一致，方便后面的残差连接。\n激活函数选用的是 GELU 而非传统的 ReLU。 ReLU 会把所有负值直接清零，而 GELU 对负值有一个平滑的过渡， 保留了少量负值的信息，在 Transformer 类模型中表现普遍更好。\n值得注意的是，MLP 对每个 token 独立作用，不同 token 之间没有任何信息交换。\nPre-LN 残差结构（Block 类） # 介绍完注意力层和 MLP，我们可以把整个 Encoder Block 拼起来看。 图中右侧展示的结构是：Norm → Attention → 残差 → Norm → MLP → 残差，重复 L 次。\n对应到代码中，一个 Block 的 forward 只有两行：\npythonx = x + self.attn(self.norm1(x)) # 先 Norm，再 Attention，再残差 x = x + self.mlp(self.norm2(x)) # 先 Norm，再 MLP，再残差 这里有三个细节值得关注。\n残差连接：每个子层的输出都要加回输入本身（x + ...）。 这样做的好处是，即使某个子层的输出很差，原始信息也能通过\u0026quot;捷径\u0026quot;直接传递下去， 有效缓解了深层网络中的梯度消失问题，让模型能堆叠更多层。\nPre-LN：LayerNorm 放在 Attention 和 MLP 之前，而非之后。 原始 Transformer 论文采用的是 Post-LN（先计算再归一化）， 但实践中发现 Pre-LN 的训练更加稳定，不容易在深层网络中出现梯度爆炸， 因此 ViT 选择了 Pre-LN 的设计。\nLayerNorm 具体做了什么：LayerNorm 的目标是把每个 token 的 embedding 向量 归一化到均值为 0、方差为 1 的分布，防止数值在层与层之间传递时越来越大或越来越小，导致训练不稳定。\n具体计算分三步：对当前 token 的 768 维向量求均值 $\\mu$ 和方差 $\\sigma^2$， 然后做标准化，最后用两个可学习的参数 $\\gamma$（weight）和 $\\beta$（bias）做仿射变换， 让模型自己决定归一化后的缩放和偏移：\n$$\\text{LN}(x) = \\frac{x - \\mu}{\\sqrt{\\sigma^2 + \\epsilon}} \\cdot \\gamma + \\beta$$在代码中，LayerNorm 的初始化是：\npythonnorm_layer = partial(nn.LayerNorm, eps=1e-6) self.norm1 = norm_layer(dim) # dim=768 self.norm2 = norm_layer(dim) eps=1e-6 是加在分母里的一个极小值，防止方差为零时出现除以零的情况。 权重初始化时，$\\gamma$ 全部初始化为 1，$\\beta$ 全部初始化为 0：\npythonelif isinstance(m, nn.LayerNorm): nn.init.ones_(m.weight) # γ = 1 nn.init.zeros_(m.bias) # β = 0 这样在训练刚开始时，LayerNorm 相当于\u0026quot;什么都不做\u0026rdquo;（乘以 1 加上 0）， 只做纯粹的标准化，随着训练推进，$\\gamma$ 和 $\\beta$ 才逐渐学出有意义的缩放和偏移。\n还有一点值得和 BatchNorm 对比一下——BatchNorm 是跨样本、在 batch 维度上做归一化， 对 batch size 很敏感；而 LayerNorm 是在单个 token 的特征维度上做归一化， 每个 token 独立计算，和 batch size 完全无关，更适合 token 数量不固定、batch size 较小的 Transformer 场景。\n整个 Encoder 就是把这样一个 Block 串联 L 次（ViT-Base 中 L=12）， 每经过一层，token 之间的信息交换就更充分一次，最终 CLS token 汇聚了整张图的深层语义信息， 准备交给最后的分类头做判断。\n整理和总结ViT骨干结构 # 通过刚刚的介绍，各位对VIT骨干的结构一定有了更深刻的理解，但这也让我们不由得思考， 为什么是VIT——把Transformer运用到CV领域相比CNN究竟有何优势； ViT 怎么用——ViT 是否只是学术上的尝试，在工业界是否真的落地了？\n接下来让我们一起思考这些问题 。\n为什么是 ViT # 在回答这个问题之前，我们先正视一个事实：原始 ViT 在中小规模数据集上并不比 CNN 强。 在原论文中也坦诚地指出，在只有 ImageNet-1K（约 130 万张图）的情况下，ViT 的表现甚至不如同等规模的 ResNet。 这是因为 CNN 的卷积结构天然带有\u0026quot;局部感知\u0026quot;和\u0026quot;平移不变性\u0026quot;的归纳偏置， 而 ViT 没有这些先验，必须从数据中从头学习空间结构，确实产生了初始训练难以收敛的，数据量不足时容易欠拟合的问题。 但当数据规模足够大，训练周期足够长时，这一局面将完全反转。\nGoogle 在原始 ViT 论文中，用内部数据集 JFT-300M（3 亿张图）预训练后， ViT-H/14 在 ImageNet 上达到了 88.55% 的 Top-1 准确率，在 CIFAR-100 上达到 94.55%， 在 VTAB 评测集上达到 77.63%，同时所需的训练算力显著低于 BiT-L 和 EfficientNet-L2 等 CNN 的最强模型。\n随后 Google 进一步将数据集扩展到 JFT-3B（30 亿张图），并扩大模型规模， ViT-G/14 在 ImageNet 上达到了 90.45% 的 Top-1 准确率，刷新了当时的最优纪录， 并且在 ImageNet-v2 上比基于 EfficientNet-L2 的 Noisy Student 模型高出约 3%。\nViT 的变体也在各个视觉任务上持续推进 SOTA。 以微软的 Swin Transformer 为例，它在 ImageNet-1K 上达到 86.4% 的 Top-1 准确率， 在 COCO 目标检测任务上取得 58.7 box AP 和 51.1 mask AP， 在 ADE20K 语义分割任务上取得 53.5 mIoU， 以大幅度优势超越了此前的最优结果。\nCSWin Transformer 则进一步将这一趋势延续， 在不使用任何额外训练数据的情况下， 于 ImageNet-1K 上达到 85.4% 的 Top-1 准确率， COCO 检测任务上取得 53.9 box AP，ADE20K 分割任务上取得 51.7 mIoU， 在相近算力下全面超越了 Swin Transformer。\n这背后有一个深层的原因：ViT 的扩展性（Scalability）远强于 CNN。 CNN 的归纳偏置是一把双刃剑——它在小数据时是优势，但也限制了模型从超大规模数据中学习更通用表示的上限。 ViT 没有这些限制，模型越大、数据越多，它的提升幅度就越大，而直到今天，我们都仍未逼近这个上限。\n以下是几个关键 Benchmark 的数据汇总，供参考：\n模型 预训练数据 ImageNet Top-1 备注 ResNet-152x4 (BiT-L) JFT-300M ~87.5% CNN 最强基线之一 ViT-H/14 JFT-300M 88.55% 原始 ViT 论文 ViT-G/14 JFT-3B 90.45% 当时 SOTA Swin-L ImageNet-22K 87.3% 无需 JFT 级数据 CSWin-B ImageNet-21K 87.5% Swin 变体的超越 📄 论文原文：An Image is Worth 16×16 Words\n📄 Swin Transformer：arxiv.org/abs/2103.14030 | GitHub\n📄 CSWin Transformer：arxiv.org/abs/2107.00652 | GitHub\nViT怎么用 # 不得不面对的现实是，在实时检测，工业检测领域，CNN仍然是现在的首选，轻量化的卷积结构和低成本开销的训练，使得CNN风格的模型在边缘设备部署有相当明显的优势。在推理速度方面，哪怕是最轻量级ViT骨架也难以和现在已经普及的轻量化CNN模型媲美。但在现如今蓬勃发展的多模态模型领域，ViT风格的骨干是毫无疑问的首选。\nCNN在边缘端的优势\n数据说话：MobileViT 在精度上优于 MobileNetV2，但在 iPhone 12 上的推理延迟至少慢 5 倍。 即便是专门为移动端设计的轻量 ViT——EfficientFormer-L1，在 iPhone 12 上的推理延迟为 1.6 ms， 才勉强追平了 MobileNetV2×1.4 的 1.6 ms，而后者早在 2018 年就已经大规模落地。\n反观 CNN 阵营，YOLOv8n 在 CPU 上的单张推理延迟约为 80.4 ms， 兼顾了实时性与精度，更大的变体也只是在延迟和精度之间做权衡。 这种成熟的工程化能力，是目前 ViT 系模型在边缘端难以复制的。\n以下是轻量 CNN 与轻量 ViT 的推理速度对比（iPhone 12，CoreML 编译）：\n模型 类型 ImageNet Top-1 推理延迟（iPhone 12） MobileNetV2 ×1.4 CNN 74.7% 1.6 ms MobileViT-XS ViT 74.8% ~7.2 ms（约 4.5× 慢） EfficientFormer-L1 轻量 ViT 79.2% 1.6 ms EfficientFormer-L7 轻量 ViT 83.3% 7.0 ms 📄 EfficientFormer 论文：arxiv.org/abs/2206.01191\n📄 MobileViT 论文：arxiv.org/abs/2110.02178\n可以看到，轻量 ViT 经过精心设计后，在精度上已经显著超越同级别 CNN， 但\u0026quot;和 CNN 打平推理速度\u0026quot;本身就已经是一件值得写进论文标题的事了—— 这恰恰说明，推理效率仍然是 ViT 在边缘端落地的核心瓶颈。\nViT 的破局方式——预训练 + 微调\n当我们在训练或使用ViT模型到真实场景进行推理时，真正的使用范式并不是将模型参数完全初始化，从头训练， 而是应该加载大规模预训练后的权重（如DINOv2, ImageNet-21k等）进行微调， 这一块在稍后我将结合自己仓库的代码进行进一步讲解\n多模态领域的视觉特征提取器\n如果说边缘端实时推理是 CNN 的主场，那么多模态大模型领域，则是 ViT 毫无争议的疆土。\n这背后有一个天然的优势：多模态模型需要把图像和文本放在同一个语义空间里对齐， 而 ViT 输出的是一组 token 序列，和语言模型处理文本 token 的方式完全一致。 CNN 输出的是二维特征图，要接入语言模型还需要额外的适配层； ViT 则几乎可以\u0026quot;直插\u0026quot;进任何 Transformer 架构的语言模型，完美适配。\n而这一判断已经被一系列标志性模型用实践验证：\nCLIP（2021，OpenAI）：用 ViT 作为图像编码器，与文本编码器在 4 亿图文对上做对比学习， 打通了图像与语言的语义空间，成为后续几乎所有多模态模型的视觉编码器基础。\nSAM（2023，Meta AI）：SAM 的图像编码器基于经过 MAE 预训练的 ViT， 负责一次性生成图像的 embedding，再由提示编码器和掩码解码器完成分割任务。 SAM 提供了 ViT-B、ViT-L、ViT-H 三个规格的变体， 其中最大的 ViT-H 变体仅图像编码器就包含约 6.32 亿参数，占整个模型参数量的 99% 以上。\nLLaVA（2023）：LLaVA 的视觉编码器直接采用 OpenAI 的 CLIP ViT-L/14-336px， 通过一个轻量的投影层将视觉特征映射到语言模型的词嵌入空间， 实现了图像与文本的端到端联合理解。\nGPT-4V / 多模态 Claude：视觉编码器同样基于 ViT 架构， 具体实现虽未完全公开，但 ViT 作为视觉主干已是业界共识。\n目前主流多模态大语言模型所使用的视觉编码器， 几乎清一色是经过 CLIP 风格对比学习训练的 ViT， 其中 CLIP ViT-L 是使用最为广泛的标准配置。\n以下是几个代表性多模态模型的视觉编码器配置：\n模型 视觉编码器 发布机构 年份 CLIP ViT-L/14 OpenAI 2021 LLaVA / LLaVA-1.5 CLIP ViT-L/336px 学术界 2023 SAM ViT-H/16（MAE预训练） Meta AI 2023 InternVL ViT-6B（自研） 上海AI Lab 2023 Qwen-VL ViT（扩展至448分辨率） 阿里巴巴 2023 📄 CLIP 论文：arxiv.org/abs/2103.00020\n📄 SAM 论文：arxiv.org/abs/2304.02643\n📄 LLaVA 项目：llava-vl.github.io\n可以说，在多模态时代，ViT 已经不只是一个\u0026quot;视觉模型\u0026quot;， 而是连接视觉与语言两个模态的核心枢纽。 理解 ViT，是理解当下所有多模态大模型的必经之路—— 这也正是我们从头介绍这篇文章的意义所在。\nViT的入门使用策略 # 接下来将借助我个人仓库 https://github.com/everglow01/VIT 进行一些简单的入门使用策略介绍， 详细的使用说明请移步项目主页的README.md查看。\n1.预训练权重加载 # 如前文所述，ViT 在中小规模数据集上并不占优势，它真正的威力来自于大规模预训练。 对于普通开发者而言，从头训练一个 ViT 既不现实也无必要—— 正确的入门姿势是加载预训练权重，在自己的数据集上微调。\n本仓库使用的是 Google 在 ImageNet-21K 上预训练的 ViT 权重， 下载后放入 weights/ 目录，通过 --weights 参数指定路径即可加载：\nbashpython train.py \\ --task classify \\ --data-path 你的数据集路径 \\ --model vit_base_patch16_224_in21k \\ --weights weights/jx_vit_base_patch16_224_in21k-e5005f0a.pth \\ --epochs 100 \\ --device cuda:0 这里有一个参数值得特别关注：--freeze-layers。 设为 True 时，Transformer Encoder 的全部参数被冻结， 只有最后的分类头参与训练，显存占用更低、收敛更快， 数据量较少时强烈推荐开启。\n详细的权重下载地址、模型规格选择和参数说明，请参考 仓库 README。\n2. 图像分类 / 检测 / 分割微调 # 在上一步我们加载了预训练好的骨干权重，冻结骨干后需要学习的就只有对应任务的头部网络。 分类任务对应一个轻量的线性分类头，检测任务对应 Fast R-CNN 风格的检测头， 分割任务则在检测头之上再加一个 Mask R-CNN 风格的掩码分支。\n三个任务共用同一套 ViT 骨干，只是下游的头部不同， 通过 --task 参数切换即可：\nbash# 分类 python train.py --task classify ... # 检测 python train.py --task detect ... # 分割 python train.py --task segment ... 值得注意的是，三个任务对资源的要求差异显著：\n分类：头部参数极少，显存友好，8GB 显存下 batch-size 128 完全可行 检测 / 分割：FPN neck 和 R-CNN 头引入了大量额外计算， 显存需求明显上升，建议至少 8GB 显存，batch size 控制在 2-4 训练完成后，权重和训练曲线会自动保存到 run/train/expN/ 目录下， 包括 best.pth、loss_curve.png、confusion_matrix.png 等， 方便直观地评估训练效果。\n数据准备格式、完整训练命令和参数说明，请参考 仓库 README。\n3. ONNX 导出与部署 # 模型训练完成后，还面临一个现实问题：PyTorch 模型在生产环境中的推理效率并不理想， 直接部署 .pth 权重依赖完整的 PyTorch 环境，也难以在非 Python 环境中使用。\n将模型导出为 ONNX 格式，是 ViT 走向实际部署的关键一步。 ONNX 是一种开放的模型交换格式，导出后可以对接：\nTensorRT：NVIDIA GPU 推理加速，适合服务器端高吞吐场景 OpenVINO：Intel 硬件推理优化，适合工控机、工业相机等边缘设备 ONNX Runtime：跨平台通用推理引擎，部署成本最低 这也呼应了前文的讨论——ViT 在原生 PyTorch 下的推理速度确实不占优势， 但经过 TensorRT 等工具的图优化和量化加速后， 在算力充足的服务器端完全可以满足实时推理的需求。\n仓库的 onnx/ 目录提供了导出脚本，训练完成后即可一键导出：\nbashpython onnx/export.py \\ --weights run/train/exp/weights/best.pth \\ --model-name vit_base_patch16_224_in21k \\ --num-classes 你的类别数 \\ --output model.onnx 详细的导出参数和后续推理加速配置，请参考 仓库 README。\n4.推理结果展示 # 训练结束后，使用推理脚本用训练好的pth文件进行推理，就能得到如下图所示的效果： 总结和思考 # 1998年，Yann LeCun 的 CNN + 反向传播工作标志了图像处理领域的重大进步，自那时候起，CV领域的各路专家学者将目光移向深度学习的领域， 2012年，AlexNet赢得了ImageNet挑战赛，向全世界展示了深度学习的惊人潜力，深度学习的黄金时代也就此拉开， 2017年，Transformer横空出世，再到2020年ViT将这个全新的架构引入现代计算机视觉， 这条技术演进的脉络，本质上是人类对 \u0026ldquo;如何让机器理解图像\u0026rdquo; 这一问题的不断追问。 在这篇文章中我们学习了：\n理解了 ViT 为什么要把图像切成 patch，把视觉问题转化为序列问题 拆解了 Patch Embedding、位置编码、CLS Token、Multi-Head Attention、MLP 的每一个细节 看清了 ViT 在边缘端的局限，以及它在多模态领域无可替代的地位 借助仓库代码，走通了从预训练权重加载到分类、检测、分割微调的完整流程 我们认识到了Transformer的强大潜力，领略了vision transformer在庞大数据海洋里超群的特征提取能力， 那么还能更进一步吗？CNN 的卷积核是人类对视觉的先验——局部感知、平移不变； ViT 抛弃了这些先验，靠海量数据和全局注意力重新学会了\u0026quot;看\u0026quot;。 那么下一个突破口在哪里？继续堆数据、堆参数，还是会有人找到一种我们还没想到的新归纳偏置？\n也许不远的将来，又会诞生一种全新的架构，同时也会带来全新的思考方式， 或许我们可能真的有机会接触到真正的通用人工智能的产生，vision transformer显然不会是终点， 怀着这样的信念，人类才能推动着这一领域一次次自我颠覆，更多的可能性，仍然等着人们去探索和发现。\n","date":"2026年05月06日","externalUrl":null,"permalink":"/Owen-Studio/posts/brief-discussion-on-vit/","section":"Posts","summary":"","title":"浅谈ViT","type":"posts"},{"content":" 欢迎来到博客作者的主页！ # 这个博客站点旨在分享笔者在学习或项目实践中的经验与心得，主要面向计算机视觉领域，包含各种不同模型、架构的算法原理和数学原理，适当结合代码进行实战讲解。也会更新一些面向新手的基础环境搭建经验，python代码规范等等。\n笔者当前时大三在校学生，日后会持续更新和分享更多的知识内容，敬请期待。\n2026.5.23编：作者目前上班打工中，博客更新速度较缓，作者尽可能做到周更，敬请谅解。\n2026.5.31编：最近非常忙碌，暂缓更新，预计要到七月初才能恢复周更。近期有可能发一篇之前的存货(maybe)\n2026 6.23编：六月份的事情总算要忙完了，七月初将逐渐恢复更新。\n网站本体持续更新美化中，当前已完成的工作有 # 1.github page连接与action构建 2.网站本体的导航栏分类基本完成 3.Hugo框架和blowfish主题订阅基本完成 4.主页、各子网页美化基本定型 5.博客正文、title和主页文字已切换为默认“PingFang”字体 6.所有正文、图片已校验完毕，目前已发表博客无bug 7.已加入好友博客链接卡片 8.goatcounter访客计数workflow已搭建完成 9.github discussion已完成，可以正常留言评论 待完成的工作 # 1.正文格式的排版以及图片的归纳 2.搜索界面的优化调整，区别于Hugo自带的search模块 3.博客正文的阅读体验优化，以及深色/浅色background的美化 4.加入内链器，包括好友github、AgroTech实验室主页等 5.国内域名 计划未来更新的博客文章 # 1.镜头使用分享 新系列 2.Ultralytics Platform 简单教程 3.hermes agent、claude code的简单教程环境配置 4.nerf、3Dgs数学原理和代码 网站将持续更新基于二维视觉、三维视觉、多模态视觉等相关内容，计划在未来拓展更多模块。想要了解更多请查看我的github主页\n想要了解更多关于该站点技术请关注该网站在github的仓库，或在此篇博客下留言。\n","date":"2026年05月05日","externalUrl":null,"permalink":"/Owen-Studio/posts/hello-world/","section":"Posts","summary":"","title":"Hello-World","type":"posts"},{"content":"","date":"2026年05月05日","externalUrl":null,"permalink":"/Owen-Studio/categories/tech-talk/","section":"Categories","summary":"","title":"数码杂谈","type":"categories"},{"content":"","date":"2026年05月05日","externalUrl":null,"permalink":"/Owen-Studio/tags/%E6%9D%82%E8%B0%88/","section":"Tags","summary":"","title":"杂谈","type":"tags"},{"content":"","date":"2026年05月05日","externalUrl":null,"permalink":"/Owen-Studio/tags/%E8%87%AA%E6%88%91%E4%BB%8B%E7%BB%8D/","section":"Tags","summary":"","title":"自我介绍","type":"tags"},{"content":"","date":"2024年09月05日","externalUrl":null,"permalink":"/Owen-Studio/categories/linux/","section":"Categories","summary":"","title":"Linux 环境","type":"categories"},{"content":"","date":"2024年09月05日","externalUrl":null,"permalink":"/Owen-Studio/tags/opencv/","section":"Tags","summary":"","title":"OpenCV","type":"tags"},{"content":"","date":"2024年09月05日","externalUrl":null,"permalink":"/Owen-Studio/tags/ros/","section":"Tags","summary":"","title":"ROS","type":"tags"},{"content":"","date":"2024年09月05日","externalUrl":null,"permalink":"/Owen-Studio/tags/ubuntu/","section":"Tags","summary":"","title":"Ubuntu","type":"tags"},{"content":" 一.有关ROS # 1.工作空间的创建 # bashmkdir - p xxx_ws/src cd xxx_ws/ catkin_make 进入工作空间并启动vscode\nbashcd xxx_ws code . 2.检查cv_bridge的版本和卸载 # 检查版本\nbashrospack find cv_bridge 若报错：[rospack]Error:package\u0026rsquo;cv_bridge\u0026rsquo;not found\n则不需要卸载\n卸载命令：\nbashsudo apt-get remove ros-noetic-cv-bridge 3.鱼香ROS命令 # 主命令：\nbashwget http://fishros.com/install -O fishros \u0026amp;\u0026amp; . fishros 视情况选择数字，初次安装必选1\n4.launch文件 # 选定功能包右击新建launch文件夹\n选定launch文件夹右击添加launch文件.launch (.xml)\n编辑launch文件内容 # node\u0026ndash;\u0026gt;包含的某个节点\npkg\u0026ndash;\u0026gt;功能包名\ntype\u0026ndash;\u0026gt;被运行的节点文件\nname\u0026ndash;\u0026gt;为节点命名\noutput\u0026ndash;\u0026gt;设置日志输出目标（可选）\n二.opencv安装、编译和验证(以4.6.0.66为例) # 1.opencv-cpp的下载和编译 # 挂梯子，直接去官网的library -\u0026gt;release找到4.6.0的source直接下载\n解压到主目录，此时会看见一个opencv-4.6.0的文件夹，先别急着编译\n安装编译依赖！\nbashsudo apt update sudo apt install build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev sudo apt install libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libdc1394-22-dev sudo apt install libeigen3-dev libtheora-dev libvorbis-dev libxvidcore-dev libx264-dev sphinx-common sudo apt install yasm doxygen libfaac-dev libopencore-amrnb-dev libopencore-amrwb-dev libopenexr-dev sudo apt install libgstreamer-plugins-base1.0-dev libavutil-dev libavfilter-dev libavresample-dev 当你卸载了opencv-cpp或者需要重新catkin_make时(例如打开cuda-opencv),这一步需要重新安装编译依赖\n2.准备编译环境 # bashcd ~/opencv-4.6.0 mkdir build cd build 3.编译命令 # bashcmake -D CMAKE_BUILD_TYPE=Release -D OPENCV_GENERATE_PKGCONFIG=YES .. 上面这条语句会默认生成opencv.pc并写入正确的目录，不需要手动nano创建\nbashcmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local .. 这条就不行，不推荐\nbashmake -j$(nproc) 使用你cpu最大线程数进行编译（并向）。\nAMD处理器编译时有概率会出现卡顿，如果一直卡着请重新编译，这是AMDcpu架构的问题\n4.安装 # bashsudo make install 5.更新动态链接库缓存（可选） # bashsudo ldconfig 验证安装有三个手段，最好都试一下\n1.直接输入命令\nbashpkg-config --modversion opencv4 观察版本号是否能被正确输出\n2.在opencv-4.6.0文件夹中,opencv/samples/cpp/example_cmake 并把它在终端中打开，输入：\nbashcmake . make ./opencv_example 若无报错且正确弹出window：Hello OpenCV 安装成功\n3.验证opencv.pc文件\n命令：\nbashls /usr/local/lib/pkgconfig -name \u0026#34;opencv*.pc\u0026#34; 若能正确找到，安装成功\n若不能，可能需要手动nano创建\n三.opencv-python的安装 # bashpip install opencv-python==4.6.0.66 ez\n验证\nbashpython3 \u0026gt;\u0026gt;\u0026gt;import cv2 \u0026gt;\u0026gt;\u0026gt;print(cv2.__version__) 四.当完成cpp和py的安装后应验证 # 当你完成cpp和py的安装，应新建一个工作空间以验证ROS和Cmake是否能被正确编译\n这一步应该先于卸载和安装cv-bridge\n五.opencv-bridge安装 # 首先确认ROS自带的bridge有没有被卸载干净\n1.添加opencv4环境变量 # bashexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig GitHub上搜索ROS的vision_opencv，选择noetic版本 code fork到本地 创建一个新的工作空间bridge_ws ,并camke，将cv-bridge解压到src目录下，并更改其CmakeList.txt\n找到并更改\nfind_package(OpenCV 4.6.0 REQUIRED) 保存\n最好是把pkg.xml的cv_name也改一下，不过不改也无所谓\n2.重新编译cv_bridge # bashcd ~/your_catkin_ws catkin_make clean catkin_make bashcmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=F:\\opencv\\sources\\bulid\\release 此时大概率报错（若正常请忽略）\n:~/new_catkin_ws$ catkin_make clean Base path: /home/owen/new_catkin_ws Source space: /home/owen/new_catkin_ws/src Build space: /home/owen/new_catkin_ws/build Devel space: /home/owen/new_catkin_ws/devel Install space: /home/owen/new_catkin_ws/installRunning command: \u0026ldquo;cmake /home/owen/new_catkin_ws/src -DCATKIN_DEVEL_PREFIX=/home/owen/new_catkin_ws/devel -DCMAKE_INSTALL_PREFIX=/home/owen/new_catkin_ws/install -G Unix Makefiles\u0026rdquo; in \u0026ldquo;/home/owen/new_catkin_ws/build\u0026rdquo;\u0026ndash; Using CATKIN_DEVEL_PREFIX: /home/owen/new_catkin_ws/devel \u0026ndash; Using CMAKE_PREFIX_PATH: /home/owen/new_catkin_ws/devel;/opt/ros/noetic \u0026ndash; This workspace overlays: /home/owen/new_catkin_ws/devel;/opt/ros/noetic \u0026ndash; Found PythonInterp: /usr/bin/python3 (found suitable version \u0026ldquo;3.8.10\u0026rdquo;, minimum required is \u0026ldquo;3\u0026rdquo;) \u0026hellip;\u0026hellip;\n该错误说明无法找到boost_python库\n安装指令\nbashsudo apt-get update #更新 sudo apt-get install libboost-python-dev libboost-python1.71.0 再次编译\n六.深度学习环境安装 # 1.nVidia显卡驱动安装 # 首先确保你的电脑在独显直连模式上，其次必须是nv显卡。\n因为ubuntu会给nVidia显卡一个默认驱动，所以我们需要安装nv自己的驱动以完成后续的工作。\n首先更新你的软件\nbashsudo apt-get update sudo apt-get upgrade 查看驱动推荐：\nbashubuntu-drivers devices 推荐自动安装\nbashsudo ubuntu-drivers autoinstall 手动安装会触发Secure Boot,不推荐\n重启\nbashsudo reboot 如果正常安装应该可以重启，否则不能重启，会卡在读取界面（不要问我怎么知道的）需要进入recovery界面删除新驱动换回默认驱动，这里会比较麻烦\n重启后运行nvidia-smi命令，观察驱动版本号和最高支持的cuda版本\n这里以RTX 4060为例，驱动版本535，cuda最高支持12.2\ntips：可以试试你的电脑能否调节亮度，如果可以，那也算是安装好了\n2.cuda安装（11.1） # 首先应确定pytorch支持什么版本的cuda再安装，这里以11.1cuda为例\n去nv官网搜索cuda11.1 toolkit 选择Linux -\u0026gt;x86_64 -\u0026gt;ubuntu -\u0026gt;20.04 -\u0026gt;runfile(local)\ncopy下面出现的命令\n一般是wget\u0026hellip;\u0026hellip;.\nsudo sh cuda\u0026hellip;..\n自行安装 会比较慢\nsh过程中会弹出cuda的安装器，直接continue -\u0026gt;accept 最后取消勾选driver（第一个） 因为前面我们已经装过了，下面四个勾上，如果让你选择yes or no全选yes，install按回车 等待\u0026hellip;\u0026hellip;.\n3.配置环境变量 # 进入根目录\nbashcd ~ nano .bashrc #用nano或是gedit打开bashrc文件，尽量不去碰vim 在文末添加\nbashexport LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/cuda/extras/CPUTI/lib64 export CUDA_HOME=/usr/local/cuda/bin export PATH=$PATH:$LD_LIBRARY_PATH:$CUDA_HOME 保存（ctrl+o）\nbashecho \u0026#39;export PATH=/usr/local/cuda-11.1/bin:$PATH\u0026#39; \u0026gt;\u0026gt; ~/.bashrc echo \u0026#39;export LD_LIBRARY_PATH=/usr/local/cuda-11.1/lib64:$LD_LIBRARY_PATH\u0026#39; \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc #刷新 退出(ctrl+x)\n4.检查是否安装完成 # nvcc -V（大写）\n如果正确输出五行并且打印版本正确就行了\n可以看看你安装的四个软件是否在软件目录中\n5.pytorch安装(1.8.1 cuda加速版本) # 确认已经安装正确的cuda全家桶 ，驱动也安装完毕\n进入pytorch官网搜索pytorch1.8.1\n一定要使用pip安装方式！！！ 不要瞎搞conda\n更新pip\nbashpip install --upgrade pip 找到cuda11.1版本的pip安装方式，在终端键入命令\nbashpip install torch==1.8.1+cu111 torchvision==0.9.1+cu111 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html 等待安装完成\n终端键入\nbashpython3 -c \u0026#34;import torch; print(torch.cuda.is_available())\u0026#34; 若输出True，则安装完毕\n若False，重装\n七.ultralytics相关的准备 # 主要会介绍yolov5和yolov8两种模型的本地部署和训练，此篇内容较简单，具体操作请查阅官方文档https://docs.ultralytics.com/zh\n1.yolov5的本地部署和训练 # 确保在你的根目录下\nbashgit clone https://github.com/ultralytics/yolov5 # clone repository cd yolov5 pip install -r requirements.txt # install dependencies 直接在github上克隆下载整个文件夹 训练\n命令：\nbashpython3 train.py --data coco.yaml --epochs 300 --weights \u0026#39;\u0026#39; --cfg yolov5n.yaml --batch-size 128 该命令仅供参考\nepochs：训练轮数\nweights：权重\nyolov5n：所使用的神经网络模型类型\nbatch-size：一批量的照片数量（请根据显卡显存以及所选神经网络模型类型决定，宁小勿大）\n除此之外，你还需指定数据集的路径等等，此篇概不赘述，官方文档有详细解答\n用detect进行推理\n命令：\nbashpython3 detect.py --weights yolov5s.pt --source img.jpg 脚本 detect.py 用于对各种来源进行多功能推理。它能自动获取 模型 从最新的YOLOv5 释放 并轻松保存结果。\nwarning:本文只是概述了yolov5模型的使用，但绝不代表其功能仅限于此，请务必查阅官方文档了解更多\n2.yolov8和ultralytics软件包 # 本地部署：\nbashpip install ultralytics 具体功能查阅\nhttps://github.com/ultralytics/ultralytics\nhttps://github.com/ultralytics/assets/releases/tag/v8.2.0\n与yolov5有所不同\n图片均来自网络，若有侵权请联系作者删除\n该博客指南专用于笔者实验室指导\n如遇Linux安装问题请访问\nhttps://blog.anawaert.com/post/tittle-tattle/installation-of-ubuntu/\n","date":"2024年09月05日","externalUrl":null,"permalink":"/Owen-Studio/posts/ubuntu-markdown/","section":"Posts","summary":"","title":"Ubuntu20.04-ROS和深度学习环境配置","type":"posts"},{"content":"","externalUrl":null,"permalink":"/Owen-Studio/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/Owen-Studio/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","externalUrl":null,"permalink":"/Owen-Studio/categories/3d-vision/","section":"Categories","summary":"","title":"三维视觉","type":"categories"}]