资讯动态

图像识别、目标检测、语义分割经典算法PyTorch复现全攻略

发布时间:2026/10/20 16:14:51 来源:尧图企业网站定制
图像识别、目标检测、语义分割可以说是计算机视觉领域的三大支柱方向也是绝大多数入门CV的开发者绕不开的三座山。无论你是刚学完PyTorch基础想找个练手项目还是已经在实际业务里被检测框和分割掩码折磨了一段时间最终都会发现一个共同的需求把手里的经典算法用PyTorch完整地复现出来搞清楚它们到底是怎么跑起来的而不是停留在调库、改参数的层面。这篇文章就把我整理过的图像识别、目标检测、语义分割三大方向经典算法的PyTorch复现思路、关键细节和实战经验一次性摊开来讲尽量做到既有原理层面的拆解也有可以直接落地的代码思路和踩坑记录。1. 三大视觉任务到底在解决什么问题1.1 任务定义与核心输出差异很多初学者容易把图像识别、目标检测、语义分割混为一谈上来就抱着一个模型想通吃所有场景。实际上这三个任务解决的是完全不同层级的问题输出形式也截然不同。图像识别准确说是图像分类解决的是“这张图里有什么”的问题输出是整张图片属于某个类别的概率分布一个类别标签对应一整张图。比如一张图片里有一只猫和一只狗图像分类只能告诉你图片里有猫或者有狗通常选择置信度最高的那个作为预测结果。目标检测解决的是“哪里有什么”的问题输出是若干个边界框坐标和每个框对应的类别标签。边界框通常用左上角和右下角的坐标表示也就是 (x_min, y_min, x_max, y_max) 或 (x_center, y_center, width, height) 两种常见格式。检测模型要同时完成定位和分类两件事。语义分割解决的是“每一个像素属于什么类别”的问题输出是跟原图同样分辨率的分割掩码每个像素分配一个类别标签但不区分同一类别的不同个体。如果你需要区分同一类别的不同实例那是实例分割的范畴典型代表是Mask R-CNN。从技术栈来说三者都依赖卷积神经网络作为骨干网络提取特征但后续处理方式的差异极大。分类网络尾部通常接全局池化和全连接层把特征图压缩成固定维度的向量。检测网络需要引入区域提议、锚点机制或者中心点预测来定位目标。分割网络则需要对特征图做密集预测输出和原图等大的逐像素结果。1.2 经典算法的演进脉络这三个方向经过了十几年的发展各自形成了一条非常清晰的演进路线。我在复现的过程中最大的体会是只有理解了这条路线你才能真正看懂那些看似复杂的模型结构为什么长这样。图像分类方向从2012年AlexNet在大规模图像识别竞赛上大放异彩开始到VGG用更小的卷积核堆出更深的网络再到GoogLeNet引入Inception模块解决计算量问题然后ResNet用残差连接解决了深层网络退化问题之后还有MobileNet在轻量化方向上的探索。这条线的核心逻辑一直是怎么在增加网络容量的同时控制计算开销和优化难度。目标检测方向分成两大流派。两阶段检测器以Faster R-CNN为代表先由区域提议网络生成候选框再对候选框进行分类和回归精度高但速度慢。单阶段检测器以YOLO和SSD为代表直接在特征图上预测边界框和类别速度快但早期版本精度略逊。后来的YOLOv3通过多尺度预测缩小了精度差距RetinaNet用焦点损失解决了正负样本不均衡问题把单阶段检测推到了精度前沿。语义分割方向同样有清晰脉络。FCN率先把分类网络改造成全卷积结构实现了端到端的密集预测。U-Net提出编码器-解码器对称结构和跳跃连接在医学图像领域大放异彩。DeepLab系列则从空洞卷积、条件随机场、深度可分离卷积、空洞空间金字塔池化一路演进每一代都在解决分割边界精细度的问题。1.3 为什么要自己用PyTorch复现一遍既然是经典算法代码库里其实都有现成实现比如各种论文官方代码、复现集合库、预训练权重仓库等等。那为什么还建议自己动手用PyTorch复现一遍我的体会是跑通别人写好的代码和从零搭出模型结构对算法的理解深度完全不在一个量级。自己复现的过程逼着你去看原始论文、梳理每个模块的输入输出维度、推敲损失函数的具体形式、处理数据加载的细节、调训练超参数、排查各种奇奇怪怪的bug。这一整套流程走下来你才真正理解为什么ResNet的残差连接能解决梯度消失、为什么Faster R-CNN的锚点要设置那么多尺度和比例、为什么语义分割的损失函数要结合交叉熵和Dice Loss。另外一个很现实的好处是你在复现过程中积累的这套工程能力可以直接迁移到自己的项目和业务里。真实落地场景很少直接套用现成模型多多少少需要改结构、换输出头、调整损失函数。如果你只会调库遇到这类需求就会非常被动。2. 图像识别经典算法复现从LeNet到ResNet2.1 LeNet手写体识别的开山之作LeNet是最早的卷积神经网络之一结构非常简单但对理解卷积神经网络的原理非常有价值。完整复现LeNet基本上一百多行代码就能搞定非常适合作为第一个PyTorch复现项目。LeNet的结构包括两个卷积层和三个全连接层。第一个卷积层用5×5卷积核输入1通道输出6通道后面接平均池化和Sigmoid激活。第二个卷积层同样用5×5卷积核输入6通道输出16通道再接平均池化和Sigmoid。然后展平之后接三个全连接层输出维度分别是120、84、10。在PyTorch里复现LeNet时有几个细节需要特别注意。LaTeX原文里用的是Sigmoid激活函数但实际经验是换成ReLU训练会更快更稳。平均池化在现代实现中通常被最大池化替代因为最大池化能保留更显著的纹理特征。全连接层的输入维度要根据输入图片尺寸动态计算LeNet原生设计是32×32输入如果你的数据集不是这个尺寸需要手动算一下展平后的特征数。我用一个变量在forward里打印张量形状的方法来辅助计算这样最不容易出错。LeNet适合用于理解卷积核是怎么提取特征的。我自己做了一次可视化之后很直观地看到第一个卷积层学到的是边缘、角点这类低层特征第二个卷积层学到的是部分纹理和形状片段后面的全连接层则负责把这些特征组合成高级语义。这个认知对理解后续所有复杂网络都有帮助。2.2 AlexNet现代深度学习的起点AlexNet是2012年那届大规模图像识别竞赛的冠军模型它的核心贡献是验证了深度卷积神经网络在大型数据集上的有效性。从现代眼光看AlexNet的很多设计已经过时但复现它的过程能很好地带你理解深度学习的几个基础机制。AlexNet包含五个卷积层和三个全连接层。第一个卷积层用了11×11的大卷积核和步长4目的是快速降低特征图分辨率。后面几个卷积层逐渐缩小卷积核中间穿插局部响应归一化和最大池化。最后的特征图经过展平后接三个全连接层其中前两个全连接层都带着Dropout最后的输出层接Softmax。复现AlexNet比较容易踩坑的地方在于原始论文里的输入是227×227很多人写代码时喜欢用自己的尺寸这本身没问题但要注意第一个卷积层的感受野是11×11、步长是4如果输入尺寸太小第一个卷积层输出的特征图可能直接缩成一个点。另一个就是Dropout和BatchNorm的配合问题AlexNet原始结构中没有BatchNorm但很多复现代码会加进去。我的建议是初次复现尽量忠于原文不加BatchNorm用局部响应归一化或者干脆直接去掉这样你能体会到早期研究者面对梯度问题时的处理思路。训练AlexNet时有一件事值得注意初始化方式。原始论文里使用高斯分布随机初始化方差跟神经元数量相关如果初始化不当深层网络的激活值很快就会饱和。现代PyTorch在nn.Conv2d里默认用的是Kaiming初始化实际效果比原始论文里的高斯初始化好不少所以复现的时候不需要刻意模仿原始初始化方式用默认初始化就行。2.3 VGG规则化堆叠的力量VGG的核心思想特别朴素用连续的小卷积核替代单个的大卷积核比如用两个3×3卷积叠加替代一个5×5卷积用三个3×3卷积叠加替代一个7×7卷积。在相同感受野的条件下这种替代既减少了参数量又增加了非线性表达能力。用PyTorch复现VGG有一个天然优势VGG的网络结构高度规整卷积层和池化层的排列非常有规律可以用列表推导式动态构建。我的做法是定义一个VGGBlock类用来构建连续的卷积序列用make_layers函数根据配置字典生成整个网络。这种方式让代码非常简洁即使需要改成别的数据集或调整个别层的通道数只需要改配置就行。VGG训练时的显存占用比其他同规模的网络要高原因在于中间层的特征图数量很多。如果在显存有限的设备上训练建议把批量大小适当降低或者考虑使用梯度累积策略。还有一个实用技巧VGG后面那几个全连接层的参数量特别大占了网络的绝大部分容量复现时可以用全局平均池化替代全连接层能大幅减少参数量精度基本不受影响。这也是后来很多网络结构设计的思路之一。2.4 ResNet残差连接解决深层退化ResNet是我在所有图像分类模型里最推荐认真复现的因为它的残差连接思想直接影响了后来几乎所有的视觉模型。ResNet解决的核心问题是网络越来越深时训练误差反而升高这就是所谓的退化问题。何恺明团队提出用跳跃连接让网络学习残差而不是直接拟合底层映射理论分析是残差学习更容易优化梯度也能通过捷径路径回流到浅层。ResNet在PyTorch里的实现非常优雅。每个残差块包含两条路径主路径通常是若干卷积层加BatchNorm捷径路径根据输入输出维度是否一致来决定是恒等连接还是用1×1卷积做维度变换。BasicBlock适用于ResNet34及以下Bottleneck适用于ResNet50及以上Bottleneck用1×1卷积先降维、3×3卷积提取特征、1×1卷积再升维这样做可以在增加网络深度的同时控制计算量。我在复现ResNet时踩过的一个典型坑是捷径连接的维度匹配问题。当stride为2时主路径的特征图大小减半、通道数加倍此时捷径连接必须使用1×1卷积且stride也要设为2否则两个分支的维度无法相加。另一种常见情形是自定义数据集只有单通道灰度图输入维度和预训练权重不一致如果不加处理直接把输入通道改成1加载预训练权重时就会报错。ResNet的训练经验对后续所有模型都通用。如果不加载预训练权重从零训练初始学习率建议设置在0.01到0.1之间配合余弦退火或分段衰减的学习率调度器。如果加载预训练权重做微调初始学习率降到0.001以下只训练最后的分类头时学习率还可以更小。BatchNorm在训练和评估时的行为不一样PyTorch的model.eval()会自动切换但如果你在测试时忘了切换到eval模式BatchNorm的均值方差用当前batch统计推理结果会明显变差。3. 目标检测经典算法复现锚点与边界框的博弈3.1 从R-CNN到Faster R-CNN两阶段检测的完整链条两阶段检测框架的经典路线是三段式先产生候选区域然后对每个候选区域做分类和回归最后用非极大抑制筛掉冗余框。R-CNN最初的做法是用选择性搜索算法产生约两千个候选区域每个区域缩放到固定尺寸后送入卷积网络提取特征再用SVM分类器判断类别边界框回归器修正位置。这一步算下来速度慢是必然的因为每张图片的两千个候选区域都要过一遍网络。Fast R-CNN做了两个关键优化一是整张图只过一次卷积网络通过感兴趣区域池化层从特征图中提取每个候选区域对应的特征二是把分类和边界框回归统一到一个网络中训练用多任务损失同时优化。但Fast R-CNN的候选区域仍然由选择性搜索产生这部分大概率是推理瓶颈于是Faster R-CNN引入了区域提议网络用一个小卷积网络直接在特征图上生成候选区域这样整个检测流程就完全端到端可训练了。复现Faster R-CNN是一个工程量大但收获非常大的过程。区域提议网络的实现要点包括先在特征图上做3×3卷积然后分支成分类头和回归头。分类头输出每个锚点是前景还是背景的置信度回归头输出锚点相对于真实边界框的偏移量。锚点通常在每个特征图位置生成三种尺度和三种长宽比共九个锚点。训练时区域提议网络有自己的监督信号其损失函数包含分类的交叉熵和回归的平滑L1损失。Faster R-CNN的头部是感兴趣区域池化层。实现时我的处理方式是先把候选框坐标除以特征图的步长得到候选框在特征图上的位置然后通过兴趣区域对齐做双线性采样将候选区域统一池化到固定大小再送入分类和回归分支。原来的兴趣区域池化直接用双线性插值实现存在两次量化误差现代实现基本都改用双线性采样的兴趣区域对齐精度确实更好。整个模型训练需要分阶段进行先单独训练区域提议网络再冻结区域提议网络训练检测头然后联合微调。每张图片的候选区域数量建议设在128到256之间过大会导致Loss偏高、训练不稳过小则容易漏掉难样本。3.2 YOLOv3单阶段检测的扛鼎之作YOLO的全称是You Only Look Once强调的就是单次前向推理同时得到类别和位置不需要候选区域阶段。最早的YOLO把图像划分为网格每个网格预测固定数量的边界框这种设计速度极快但定位精度较差因为一个网格只能对应一个物体小目标或者密集目标漏检严重。YOLOv3做了几个关键改进到现在依然是很多检测项目的骨干框架。骨干网络换成了类似ResNet的Darknet53并在三个不同尺度的特征图上分别做预测分别负责大、中、小目标的检测。每个边界框不再直接预测坐标而是相对于预设锚点的偏移量使用逻辑斯蒂激活函数将位置限制在合理的范围内。类别预测部分使用二元交叉熵而不是Softmax因为Softmax要求类别互斥而实际数据集中同一物体可能同时适配多个标签。复现YOLOv3时会发现一个容易出错的地方是锚点尺寸的设定。模型使用的锚点是在完整数据集上聚类得到的不同数据集的最优锚点不一样。如果你在自己的数据集上训练不管你选择了什么样的锚点尺寸一定要按照当前数据集重新聚类一遍或者至少保证锚点尺寸跟目标尺度匹配。我的做法是用K-means在训练集的边界框尺寸上聚类九组锚点按照面积从大到小分配给三个预测尺度最上面的特征图负责大目标最下面的特征图负责小目标。YOLOv3的前向推理需要对预测结果做解码。网络输出的原始张量包含4个位置参数、一个目标置信度和若干个类别概率解码时先把位置参数经过逻辑斯蒂激活然后乘以锚点宽高得到最终的边界框坐标目标置信度和类别概率相乘得到综合置信度低于阈值的框全部过滤剩下的做非极大抑制。非极大抑制的实现细节影响很大类别置信度阈值通常设在0.25到0.5之间交并比阈值设在0.4到0.5之间这两个参数的配合直接决定了误检和漏检的平衡。训练YOLOv3时正负样本划分的规则值得特意调一搏。每个真实边界框分配给我预测的所有锚点的基础上与真实框交并比最大的锚点作为正样本其余锚点中与真实框交并比超过0.5的也可以作为正样本低于0.4的算负样本位于0.4到0.5之间的忽略不计。这样算下来每张图片真正参与回归的锚点并不多训练难度恰恰集中在那些交并比在0.4到0.5之间的模糊样本上这也是很多复现效果不佳的重要来源。3.3 SSD多尺度特征图上的直接预测SSD的思路本质上是用不同分辨率的特征图检测不同尺度的目标。浅层特征图分辨率高、感受野小适合检测小目标深层特征图分辨率低、感受野大适合检测大目标。SSD从主干网络的几个不同阶段取出特征图每个阶段都接独立的检测头最终把所有阶段的预测结果汇总到一起。在PyTorch里复现SSD时比较大的工程点在于数据预处理。SSD训练用到了随机裁剪和颜色失真等一系列数据增强操作这些操作必须在边界框坐标上同步进行不然增强之后标签就和图片对不上了。我的做法是用自定义的数据集类在__getitem__里依次完成读取图像、应用几何变换、同步变换边界框坐标、转为张量、标准化等操作。这里最需要注意的是坐标变换的相对性和绝对性随机裁剪的区域要在原图坐标里确定然后在裁剪后的图片上重新计算边界框坐标。SSD依赖的置信度损失用的是多类别Softmax交叉熵位置损失用平滑L1。训练过程中常出现的情况是挖掘样本正负比失衡因此需要做困难样本挖掘。我的实现逻辑是先计算所有锚点的损失把正样本的损失全部保留负样本按损失从大到小排序选取一定数量的高损失负样本参与反向传播总正负样本比控制在1比3左右。这个方法能明显加快收敛速度我实测负样本全部参与训练的话Loss虽然下降但模型学到的基本都是背景检测效果很不好。SSD的锚点设置跟YOLO略有不同它是在不同尺度和比例的网格上预定义固定数量的锚点没有聚类过程直接用公式生成。每个特征图位置生成6个锚点比例从1到5种不同长宽比不等大特征图上的锚点小小特征图上的锚点大。复现时可以用一组循环语句把锚点生成逻辑写到prior box工具模块里提前在训练前把所有锚点在原图尺度上的坐标计算好前向推理时直接查表偏移。4. 语义分割经典算法复现逐像素分类的精细化之路4.1 FCN全卷积网络的基石FCN是语义分割领域的开山之作核心贡献是把分类网络最后的全连接层全部替换为卷积层使得网络可以接受任意尺寸的输入直接输出与输入同尺寸的分割图。这个改造看起来只是换个层实际影响极其深远从此图像分割从一个“对区域进行判断”的问题转变为一个“对像素进行预测”的问题。在PyTorch里实现FCN比较容易只需要在分类网络的基础上替换掉分类层。更关键的是上采样路径的设计。一种方式是用转置卷积直接输出原分辨率的分割图另一种是结合跳跃连接的多阶段上采样。我在复现时选了跳跃连接方案具体操作是把深层特征图向上采样两倍然后跟对应层的浅层特征图在通道维度拼接在一起再继续卷积和上采样重复这个过程直到输出尺寸和输入一致。这样能很好地保留浅层的边缘细节。FCN最容易被诟病的问题是分割边界粗糙。原因很好理解深层特征图经过多次下采样后分辨率已经很低上采样只会是低分辨率信息恢复成高分辨率的过程边界细节显然会丢失。复现的时候我在损失函数层面做了个调整来缓解这个问题除了主损失还在中间层加了辅助损失用来监督浅层特征更多地学习边界信息。这个技巧在分割任务里很实用能显著提升边缘质量。训练FCN的输入图像不需要固定尺寸这给数据加载带来一点小麻烦。一个batch里的图片尺寸必须一致否则无法组成张量批量前向。最简单的做法是所有训练图片统一裁剪到固定尺寸预测阶段把整张图切块预测再拼接。另外有条件的场景可以用随机缩放作为数据增强的手段让网络对不同尺度的目标具备更好的鲁棒性这个技巧对小目标和超大目标的效果都很明显。4.2 U-Net编码器-解码器结构的天作之合U-Net最初是为医学图像分割设计的但它的泛化能力极强几乎适合所有小样本、细节要求高的分割任务。U-Net的核心设计是编码器-解码器对称结构左侧编码器逐级下采样提取语义特征右侧解码器逐级上采样恢复空间细节左右两侧通过跳跃连接把尺寸相同的特征图拼接起来。因为结构图呈字母U形所以叫U-Net。PyTorch复现U-Net建议先将基本的下采样块和上采样块封装成类然后用列表把它们串联起来。下采样块包括两次3×3卷积加ReLU、一个2×2最大池化。上采样块包括一个2×2转置卷积做两倍上采样、然后跟编码器中对应的特征图拼接、再做两次3×3卷积加ReLU。上采样中拼接操作对通道数理解的要求比较高特征拼接后通道数等于解码器当前通道数加编码器对应层的通道数设计网络时最好把各层通道数画个表格写清楚免得拼到一半维度对不上。U-Net的优势在于它能够在很小的数据集上从零训练出不错的效果这得益于跳跃连接为解码器提供了丰富的浅层细节。同时跳跃连接本质上提供了多条梯度传播路径使得深层训练更容易收敛。在实际使用时要注意的是输入与输出尺寸的整除关系。U-Net的特征图尺寸每经过一次池化就减半如果输入尺寸不是2的整数次幂上采样后输出尺寸会和输入不一致这时需要用中心裁剪或者填充来手动对齐我一般习惯让输入尺寸是16的倍数这样不会出问题。分割任务的损失函数我有一次认真做了对比实验。单独用交叉熵训练整体收敛快但对小目标和边界区域不够敏感单独用Dice Loss对类别不平衡鲁棒但优化过程很不平滑Loss曲线经常抖动。实测下来效果最好的是交叉熵和Dice Loss按一定比例加权求和比如交叉熵权重0.5、Dice权重0.5。类别极不平衡时可以把类别权重向量传给交叉熵给数量少的类别更大权重这是最简单有效的改进手段。4.3 DeepLab系列空洞卷积与空间金字塔池化DeepLab系列代表了语义分割模型向大规模、精细化方向发展的典型路线其中我用得最多的是DeepLabV3和DeepLabV3Plus。它们都建立在一个关键思想上空洞卷积可以在不降低特征图分辨率的情况下扩大感受野。空洞卷积理解起来可以作为这样一个逻辑来接受通过在卷积核元素之间插入空洞让卷积核在同样参数量的情况下覆盖更大范围。膨胀率越大感受野越大但不足之处是可能丢失细节信息所以需要组合不同膨胀率的空洞卷积来平衡感受野和细节保留。DeepLabV3的核心是空洞空间金字塔池化模块用膨胀率不同的几个空洞卷积分支并行地对输入特征进行处理然后拼接融合实现对多尺度上下文信息的捕获。DeepLabV3Plus在基础版本上增加了解码器结构把编码器输出的高级特征和主干网络浅层的低级特征融合起来恢复边界细节。PyTorch里实现空洞卷积非常简单只需在nn.Conv2d里设置dilation参数即可但设计好空洞空间金字塔池化模块需要想清楚三个问题通道数怎么设、膨胀率怎么选、多个分支的特征怎么融合。我通常在实现时把各通道数设为256与主干网络输出通道数对齐损失函数配合一个辅助的深度监督分支。膨胀率的选取要跟数据集的目标尺度匹配如果大部分目标比较小膨胀率过大反而会丢失细节需要做一组对比实验来确定。训练DeepLab系列模型时另外一个常见的现实问题是显存占用极高。原因是空洞卷积计算量比普通卷积大且输入分辨率本身就高。我个人的应对策略是混合精度训练结合梯度累积。梯度累积的本质是维护一个虚拟的大批量每积累若干个小batch的梯度后再统一更新一次参数这样既能近似大批量训练的效果又不会超过显存限制。4.4 分割模型的评价指标与后处理细节语义分割的模型评价不能只看训练Loss必须结合实际评估指标和数据可视化结果来判断模型是否真的收敛、是否真的学到了有效特征。最常用的指标是平均交并比和像素准确率其中平均交并比是主流选择。它的计算方式是对每个类别计算真实标注与预测结果的交集面积除以并集面积然后对所有类别取平均。这里有一个新手比较容易疏忽的点平均交并比有两种计算口径一种是忽略背景类别的平均交并比另一种是包含背景类别的平均交并比。具体选择哪一个要看你关注什么。如果你关注分割目标本身的精度就应该忽略背景如果你关心整张图的综合效果就包含背景。实际项目报告建议两个都算因为背景占比大时像素准确率可能虚高平均交并比更能反映模型真实水平。分割结果的后处理也很重要。模型输出的是每个像素的概率分布通常用argmax取得类别编号这时候很容易出现噪声点、孔洞、边缘毛刺。用数学形态学做一下开闭运算把小的孤立点去掉、把断裂的区域连接起来能明显提升视觉上的分割质量。如果对边界平滑度有更高要求可以引入条件随机场做最后的精修但要注意条件随机场的推理速度较慢在实时场景要谨慎使用。我实际更推荐的是在损失函数中增加边界感知机制让网络自己学习预测更平滑的边界这样比事后后处理更彻底。5. 复现实践中的共性经验与排错技巧5.1 数据加载与增强的正确姿势不管复现哪个方向的算法数据加载都是要被认真对待的一环。分类任务的数据加载相对简单torchvision.datasets.ImageFolder就能解决大部分问题。检测和分割任务的数据加载要麻烦得多需要同时处理图像、边界框或分割掩码以及各种数据增强之间的同步问题。我建议培养一个习惯不管什么任务数据加载器写好之后第一件事是跑一个可视化脚本把加载出来的图片、边界框、掩码画在一起保存到本地逐张检查。很多标注错位、坐标转换错误、掩码与图像不匹配的问题单纯靠数值检查很难发现可视化一眼就能看出毛病。这一步做到位能避免后面训练半天才发现数据本身就有问题的尴尬局面。数据增强方面分类任务通常用随机裁剪、水平翻转、颜色抖动就足够了。检测任务可以额外加随机缩放、随机旋转、多尺度变换但所有几何变换必须同步作用于边界框。分割任务需要更多针对形变的增强因为掩码本身就是像素级密集标注像随机仿射变换、弹性形变这类操作对提升分割模型泛化能力很有帮助。增强力度要适中过强的增强会让模型学不到稳定特征过弱则容易过拟合。5.2 训练Loss曲线解读与超参数调优动手训练之前先明确一个问题Loss曲线应该长什么样才算正常我见过很多同学一上来就焦虑Loss怎么不降其实要分阶段看。训练初期的Loss快速下降是正常的说明模型开始学习数据的主要模式中期Loss缓慢下降属于正常现象这个阶段模型在逐渐适应难样本如果Loss到后期还在剧烈震荡说明学习率太大或者批量大小太小。反之如果Loss一直不降先排查数据加载是否有问题再确认模型结构是否能正常前向和反向传播最后检查损失函数是否写对了。学习率的设置是影响训练效果最大的超参数之一。从零训练时建议初始学习率设在0.01到0.03之间结合余弦退火或者步长衰减。如果加载了预训练权重微调初始学习率要降到原来的十分之一甚至百分之一。批量大小和学习率存在联动关系批量越大学习率可以适当调大但批量太小时梯度噪声大且归一化统计量不稳定需要配合更小的学习率。另外一个容易被忽略的因素是优化器的选择。Adam收敛快但最终精度可能略逊于带动量的随机梯度下降。我的习惯是调试阶段用Adam快速验证模型能跑通正式训练用带动量的SGD冲最终精度。动量设为0.9权重衰减设为1e-4或者5e-4这个组合在大多数视觉任务上表现都比较稳定。5.3 PyTorch复现中常见的十个坑附解决方案这几年帮别人调试过不少复现代码也自己踩过很多坑整理一个高频问题清单放在这里基本覆盖了新手到中级玩家最常遇到的问题。张量形状与设备不匹配问题。出现频率最高。特征图拼接或全连接层展平时报形状错误建议在模型里阶段性打印形状确认每一层的变换规律。设备不匹配的典型错误是device混用导致的解决方法是获取模型参数所在的设备然后把你创建的标签张量移动到同一个设备上或者统一用.to(device)。推理时忘记调用model.eval()。如果推理时BatchNorm和Dropout没有关闭后果是结果不稳定。测试时务必调用model.eval()。边界框坐标的归一化位置搞错。检测任务中边界框在数据加载阶段就应归一化到0到1的范围内具体会在损失计算时还原。如果归一化时机不对会对Loss曲线和最终指标产生严重影响。非极大抑制的阈值设置不当会导致相近目标被合并掉或者冗余框堆积。建议结合可视化结果动态调整目标密集时交并比阈值调低目标稀疏时调高。分割任务中直接用nn.BCELoss或nn.CrossEntropyLoss的默认行为却不了解这两个损失函数对输入格式的要求。交叉熵对尺寸为(N, C, H, W)的logits直接有效但如果传入了argmax之后的预测标签梯度就直接为0了模型不会发生任何学习。混合精度训练中梯度缩放参数设置不当。初始缩放因子太小会导致梯度下溢特征停滞建议使用PyTorch自带的自动梯度缩放让框架自动管理范围。数据加载数量不均衡导致每个epoch的batch数量不一致。如果数据集大小不能被批量大小整除最后一个batch会很小甚至为空。通常在自定义数据类里做好对最后一个batch的拦截或者框架自动丢弃然后设置drop_lastTrue即可。保存和加载模型权重时的状态字典错位。在state_dict键名上如果你用了nn.DataParallel包装模型保存的权重前面会多一层module前缀。加载权重时要么也用同样的包装方式要么在加载前手动去掉或加上这层前缀否则会形成键名不匹配问题。训练时数据增强过度尤其是检测和分割任务里对边界框或掩码做了不合理几何变换导致标签失效。建议每个增强操作都写单元测试检查变换前后标签和图像的对应关系。最后是学习率调度器和优化器的顺序问题。PyTorch中学习率调度器的step()必须在优化器step()之后调用否则学习率变化会提前作用到下一轮迭代造成训练行为混乱。5.4 正确看待复现代码与论文之间的差异复现论文时你会发现一个非常普遍的现象按论文所述设置完全训练不出来论文报告的数字。这个现象的原因是多方面的。论文里往往隐藏了许多未写明的工程细节比如具体的初始化方式、数据增强的具体参数、学习率调度策略、训练多少个epoch才切换学习率等等。论文报告的数字可能是多次实验里最好的那次而你只跑了一次结果自然有差异。另外一个不可忽视的因素是框架和硬件差异同一个模型在不同深度学习框架下的数值行为、不同GPU型号上的卷积算法选择都会带来细微的差异。那复现时应该追求完全一致吗我的建议是不要被数字绑架。复现的核心价值在于理解和验证思想而不是复刻数字。我个人的标准是复现代码在验证集上的指标与论文报告的差距在可接受范围内就算成功。或者简化来说如果能达到论文报告的九成左右效果说明你的实现逻辑在本质上正确剩下的差距可以通过更细致的调参来逐步弥补。6. 三方向一起学该怎么规划学习路线如果你是刚接触PyTorch不久的新手想系统地把这三个方向都掌握我给一个经过验证的路线建议。第一步把PyTorch最基础的数据加载、张量操作、自动求导、模型定义和训练循环跑清楚。第二步从图像分类入手先复现LeNet和ResNet把卷积神经网络的基本构建块用熟。第三步转向目标检测先复现YOLOv3这种结构相对简单的单阶段检测器再尝试Faster R-CNN这种两阶段模型。第四步转向语义分割先复现U-Net理解编码器-解码器结构再尝试DeepLab系列掌握空洞卷积和多尺度特征融合。这个路线的设计逻辑在于由浅入深、前后衔接。图像分类能让你熟练掌握卷积神经网络的基本构建块目标检测中的主干特征提取、区域提议、锚点机制都完全建立在图像分类的基础上。语义分割和图像分类共用编码器结构而目标检测中很多特征提取的手段也直接适用于分割。每完成一个方向下一个方向的理解成本都会明显降低。每一方向的复现过程都建议严格按照三步走第一步用最小实现跑通训练流程和推理流程最小实现指不考虑精度和速度只保证逻辑正确能用很小的数据子集验证Loss在下降就行。第二步手动完成数据预处理、模型结构、损失函数、训练循环的完整实现这个阶段你会遇到大量细节问题逐个排查解决的过程就是最有价值的学习过程。第三步在完整实现基础上做优化实验包括更换损失函数、调整数据增强策略、对比不同初始化方式等把每一步改动带来的效果差异记录下来形成自己的实验经验。弄完三个方向的复现之后你会发现视觉技术那层神秘面纱基本揭开了。检测和分割模型无论结构多复杂背后的本质都是特征提取加任务头特征提取部分全是卷积网络堆叠检测任务多了一堆锚点和框解码分割任务多了一堆上采样和像素级损失。这个全局视野会直接提高你在后续真实项目里做方案选型和模型改造的判断力。我对三个方向一起学这件事最大的体会是不要把复现当成抄代码要当作拆解设计思路的过程。每当我看到一个精妙的网络结构我不会急着只看代码而是先想清楚这个结构和直观直觉之间到底是什么关系。读到残差连接时想到的是跳跃连接带来的恒等映射便利性读到锚点机制时想到的是怎么让模型在密集网格上预测稀疏目标读到空洞卷积时想到的是如何不增加参数量单靠膨胀来扩大感知范围。这种深度理解一旦建立起来遇到新模型的时候你几乎能凭直觉猜到它大概好用的套路和解决的痛点这种判断力是刷多少遍文档都换不来的。显存优化策略我最后再单说一嘴。三大任务相比分类模型显存压力最小检测模型居中分割模型尤其是高分辨率分割任务的压力最大。如果你的显卡显存有限有几个很好用的办法用混合精度训练把计算量减半把大尺寸输入切成小块训练并配合随机裁剪数据增强用梯度累积模拟更大的批量能用全局平均池化降维的尽量降低全连接层参数量在分割任务中减少不必要的膨胀率过大的空洞卷积分支。实际踩过几轮之后我发现很多显存压力其实来源于粗心的代码设计而不是模型本身比如中间变量没有及时释放、特征图过了没有必要的分支等显存管理这件事值得单独立一个专题好好做。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑