CNN图像分类入门实战:从卷积原理到训练调参与部署

发布时间:2026/10/27 4:42:52
CNN图像分类入门实战:从卷积原理到训练调参与部署 做图像分类的人第一次自己动手搭卷积神经网络CNN的时候八成都会卡在同一个地方教程里的代码能原样跑通但换个数据集、改个输入尺寸、调个通道数立刻就报一堆形状不匹配的错误。我最早碰这块也是这样对着一个手写数字识别的小例子反复改了两天最后发现只是展平之后少乘了一个通道数。这篇 CNN 入门级实战教程就是把这些年带新人、自己踩坑攒下来的东西捋成一条完整路径从卷积核到底在学什么到数据怎么喂、网络怎么搭、曲线怎么看、报错怎么查最后到模型怎么导出。它不假设你有深度学习基础但也不打算把公式堆一遍了事重点是让每个步骤背后的“为什么”说得清楚你看完之后能自己改结构、自己调参数、自己定位问题。1. CNN 到底在学什么先把直觉建立起来1.1 从全连接网络的三处硬伤说起要理解卷积神经网络存在的理由最直接的方式是先看看不用它会发生什么。一张 32×32 的彩色图展开成向量是 3072 维接一个只有 1000 个神经元的隐藏层参数量就是 3072×1000 再加偏置三百多万个参数。这还是小图换成 224×224 的输入单层就有一亿五千万个参数训练时的显存和计算量直接顶到天花板。这是第一处硬伤参数量随输入尺寸线性甚至平方级膨胀模型还没开始学东西先把内存吃光了。第二处硬伤更隐蔽把图像展平成向量这个动作等于亲手把像素之间的空间关系撕碎了。原本上下左右相邻的像素在向量里可能隔了几百个位置网络要靠大量样本自己去重新学“哪些位置本来是邻居”这个学习成本极高且极不稳定。第三处是平移不变性缺失同一只猫出现在画面左上角和右下角对全连接网络来说是两组完全不同的输入模式它得把两个位置各学一遍样本效率低得离谱。卷积神经网络的三件套——局部连接、权值共享、池化降采样——正好对应解决这三个问题它不是更高级的模型而是针对图像这类网格结构数据量身定制的归纳偏置。1.2 卷积核、权值共享与局部感受野三句话讲透卷积核可以先不当成数学运算就当成一块小模板。想象你手里有个 3×3 的格子上面写着九个权重数字把它盖在图像左上角九个像素上对应位置相乘再全部加起来得到一个数然后往右挪一个像素重复同样的动作一路扫完整张图最后得到一张新的二维图。这个过程就是卷积。如果这九个权重恰好是“上排全是正、下排全是负”那么扫出来的结果在水平边缘处响应特别强这块模板就变成了一个边缘检测器。CNN 训练的过程本质上就是让网络自己去找出该用哪些模板而不是由人来设计。权值共享是卷积最省参数的地方同一个卷积核在整张图上滑动时用的是同一组权重。这带来两个直接好处——参数数量只跟卷积核大小和通道数有关跟图像尺寸完全脱钩同时因为权重到处复用模型天然获得了平移等变性猫在左边和右边激活的是同一组卷积核只是激活位置不同。局部感受野则是“每个神经元只看一小块”的约束这符合图像的统计特性相邻像素高度相关远处像素关系弱。多层堆叠之后浅层感受野小、看边缘和颜色块深层感受野大、看物体部件和整体形状这个层级抽象的过程不需要人干预是堆叠结构自然涌现的。1.3 池化层到底还该不该留入门教程里几乎都会放最大池化2×2 窗口、步长 2把特征图尺寸砍一半。它的作用很实在降低计算量、扩大后续层的有效感受野、提供一定程度的平移鲁棒性——输入稍微平移几个像素池化后的输出可能完全不变。对新手来说最大池化还有一个隐性价值它没有可学习参数不会引入额外的不确定性出了问题好排查。所以我一般建议入门阶段老老实实用最大池化先把整个流程跑通。近几年的主流结构里池化的地位在下降很多网络改用步长为 2 的卷积来做降采样理由是让网络自己学怎么降采样信息损失更可控。这个说法有道理但代价是参数量和调试复杂度上升。我的建议是分阶段第一个能跑通的模型用最大池化等你对形状推导、感受野计算都熟练了再换成 stride2 的卷积做对比实验看看在你的数据上精度差多少。这个对比本身就是一个很好的练习能让你直观感受到“结构选择”对结果的影响幅度而不是迷信某种结构一定更好。1.4 跟着张量走一遍形状是怎么一层层变的新手最容易出错的地方就是形状。与其死记不如建立一个固定的推导习惯每次写一层就在纸上或者注释里写下输出形状格式统一用[N, C, H, W]N 是批大小、C 是通道数、H 和 W 是空间尺寸。卷积层只改 C、H、W不动 N展平层只动后三维把它们乘起来全连接层只动最后一维。把这四条记牢九成的 shape 报错能在写代码阶段就避免掉。举一个完整的例子输入是[16, 3, 32, 32]也就是一批 16 张 32×32 的彩色图。经过一个 3→32 通道、3×3 卷积、padding1 的层之后因为 padding 抵消了卷积核带来的尺寸缩减空间尺寸保持 32×32形状变成[16, 32, 32, 32]接一个 2×2 池化变成[16, 32, 16, 16]再经过 32→64、3×3、padding1 的卷积通道翻倍、尺寸不变是[16, 64, 16, 16]池化后[16, 64, 8, 8]第三组卷积池化后[16, 128, 4, 4]。到这一步展平128×4×4 2048全连接层的输入维度就确定了。整个过程只需要在每一层后面追一句注释出错时逐层打印 shape 就能秒定位。2. 环境和数据入门阶段最容易翻车的地方2.1 环境选型别一上来就折腾分布式我见过太多人卡在第一步不是因为模型不会写而是因为环境装了两天还没跑起来。入门阶段的正确姿势是一个干净的虚拟环境、一个固定版本的深度学习框架、一块能用的 GPU没有也行先用 CPU 跑小数据、一个能显示训练进度的终端。框架之间的差异在这个阶段其实很小选哪个都能学到卷积的核心概念重要的是版本定死并记录下来因为不同版本之间 API 改名、默认行为变化的情况并不少见隔几个月回头复现会相当痛苦。我用得最多的是 Python 加 PyTorch 的组合原因是它的动态图机制对新手友好print一下就能看到中间变量的形状调试体验比静态图顺畅得多。安装的时候建议用官方提供的版本对照表确认框架版本、Python 版本、CUDA 版本的对应关系先装 CPU 版本验证代码逻辑确认能跑通之后再换成 GPU 版本这样能把“代码写错了”和“环境配错了”这两类问题分开排查。虚拟环境务必建好用conda或venv都行一定要养成把依赖导出成文件、把随机种子固定下来的习惯这两件事后期会救你很多次。2.2 数据集怎么选别从大库直接开刀入门数据集的选择有个很朴素的判断标准单次训练能不能在十分钟内跑完一轮。跑不完你就没法快速迭代调试效率会低到让人放弃。按这个标准路线大概是这样的先用手写数字这类极小数据集几万张 28×28 灰度图CPU 上十几分钟就能训出一个 98% 以上精度的模型目的是验证整条流水线通畅然后换 10 类彩色小图数据集32×32 的尺寸感受一下三通道、颜色、类别间相似度更高的场景最后再上自己业务里的真实数据。真实数据这一步往往落差最大。常见的问题是类别不均衡某一类占了一半以上样本标注噪声同一张图不同人标出不同结果还有分辨率不一致、长宽比千奇百怪。这些问题不会出现在公开数据集上但一定会出现在你自己的数据里。所以在做公开数据集的阶段就要有意识地练习处理这些情况比如主动去统计每类样本数、画出类别分布、挑几十张图肉眼过一遍看有没有标错的。这个习惯养成之后你拿到新数据集的第一反应会是先摸底而不是直接丢进网络里开训。2.3 目录结构、Dataset 与 DataLoader数据组织方式建议从第一天就规范起来按类别分文件夹训练集和验证集各自一份不要把所有图混在一个目录里再用 CSV 标注那个方式在样本量上去之后维护成本很高。下面的结构是最省心的dataset/ ├── train/ │ ├── class_a/ xxx1.jpg xxx2.jpg ... │ ├── class_b/ ... │ └── class_c/ ... └── val/ ├── class_a/ ... ├── class_b/ ... └── class_c/ ...代码侧直接调用文件夹数据集加载器它会按子目录名自动生成类别索引并自带一份类别到索引的映射务必把这份映射保存下来推理阶段要严格按同样的顺序还原否则会出现“模型明明很准但上线全乱”的经典事故。数据加载器负责批处理、打乱、多进程读取和预处理流水线其中num_workers这个参数值得单独说它设为 0 时所有读取都在主进程里做调试方便但速度慢设为 4 到 8 通常能明显提升吞吐但在某些系统上会因为多进程启动方式的问题卡死。我的做法是先在主进程里跑通整个流程确认数据没问题再开多进程。2.4 归一化与数据增强几个容易踩雷的参数归一化这件事入门的标准做法是用ToTensor()把像素缩到 0 到 1再用固定的均值方差做标准化常用值是三个通道都取 0.5 或者数据集本身的统计量。这里有个隐蔽的坑如果你用的是自己算出来的均值方差必须只用训练集统计绝不能把验证集和测试集一起算进去。一旦混进去验证集的信息就提前泄漏到预处理里你会看到一个偏乐观的精度等真正上线时性能掉下来还找不到原因。这是我见过最隐蔽也最常见的一类错误因为它不会有任何报错提示。数据增强的作用是在不增加真实样本的前提下扩大数据分布抑制过拟合。常用的有随机裁剪加填充、随机水平翻转、小角度旋转、颜色抖动。但每一种都有适用边界用错了会直接损害精度字符识别任务里水平翻转会把字母变成镜像模型学到的特征完全无意义某些类别左右不对称的工业检测场景翻转等于凭空制造了不存在的样本颜色抖动在金属表面缺陷检测里可能把关键的颜色差异抹掉。我的建议是每次只加一种增强跑一轮看验证集精度变化确认有正收益再加下一种而不是一次性堆五六种然后完全不知道是哪个起了作用。3. 手把手搭一个能跑的 CNN3.1 结构设计层数、通道数、卷积核的经验法则入门阶段的结构设计不需要太复杂一套流传很广的经验法则足够用空间尺寸每减半一次通道数翻一倍。原因是池化会让特征图面积变成四分之一为了保持每一层的信息容量大致相当通道数就得补回来这个平衡关系在绝大多数小模型上都成立。卷积核统一用 3×3堆两个 3×3 的效果约等于一个 5×5 的感受野但参数更少、非线性更强。输入尺寸如果在 64 以下三层卷积起步到 128 以上四到五层比较合适。还有一个经常被忽略的点每个卷积层后面接什么。现代做法是卷积、批归一化、激活函数三件套顺序一般是卷积 → BN → ReLU。批归一化的价值在于让每层输入分布保持稳定这样就能用更大的学习率、收敛更快对新手最大的好处是容错率高学习率设得不太准也还能训下去。激活函数用 ReLU 及其变体就行不要在入门阶段去纠结各种激活函数的细微差异那个量级的收益远不如把数据质量提上来。最后一层全连接输出维度等于类别数不要加激活函数因为交叉熵损失内部已经包含了 softmax。3.2 形状推导与参数量计算手算一遍就通了参数量的手算公式很简单卷积层是卷积核高 × 卷积核宽 × 输入通道 × 输出通道 输出通道最后那一项是偏置全连接层是输入维度 × 输出维度 输出维度。拿前面那个结构算一遍输入[N, 3, 32, 32]层配置输出形状参数量conv13→32, k3, p1N,32,32,323×32×3×332 896pool1k2, s2N,32,16,160conv232→64, k3, p1N,64,16,1632×64×3×364 18496pool2k2, s2N,64,8,80conv364→128, k3, p1N,128,8,864×128×3×3128 73856pool3k2, s2N,128,4,40flatten-N,20480fc12048→256N,2562048×256256 524544fc2256→10N,10256×1010 2570加起来总共 620362 个参数其中八成集中在全连接层。这个数字本身就说明了一件事卷积层的参数效率极高三个卷积层加起来不到十万个参数就能提取出 128 通道的语义特征。所以如果你发现自己的模型参数爆炸先去看看全连接层是不是设太宽了而不是去砍卷积层。顺便把感受野也手算一遍这个技能排查“为什么模型看不见大目标”时非常有用。递推规则是当前层感受野等于上一层感受野加上卷积核大小减一乘以当前累计步长步长则逐层累乘。按上表走conv1 后感受野 3、步长 1pool1 后感受野 4、步长 2conv2 后 8、步长 2pool2 后 10、步长 4conv3 后 18、步长 4pool3 后 22、步长 8。也就是说最后一个特征图上的一个点对应原图 22×22 的区域。如果你的目标物体在原图上只有 10×10 像素那它在这个网络里最多只能激活一个点信息量严重不足这时候要么减小下采样次数要么提高输入分辨率。3.3 完整网络代码把上面的结构写成代码注意每一层都加形状注释import torch import torch.nn as nn class SmallCNN(nn.Module): def __init__(self, num_classes10): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, 3, padding1), # [N, 32, 32, 32] nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # [N, 32, 16, 16] nn.Conv2d(32, 64, 3, padding1), # [N, 64, 16, 16] nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # [N, 64, 8, 8] nn.Conv2d(64, 128, 3, padding1), # [N, 128, 8, 8] nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # [N, 128, 4, 4] ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 4 * 4, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.3), nn.Linear(256, num_classes), ) def forward(self, x): return self.classifier(self.features(x))写完之后立刻做一件事造一个假输入跑一遍打印输出形状确认是[N, 10]。这一步花不到一分钟但能省掉后面训练跑了一半才报错的麻烦。同时可以用框架自带的参数统计工具核对一下总数是不是 62 万左右对不上就说明结构和你脑子里想的不一样。3.4 损失函数、优化器与初始化的取舍分类任务用交叉熵损失这里有个高频错误在网络的最后一层又手动加了一个 softmax然后交叉熵损失内部再做一次等于算了两遍梯度会变得非常小训练几乎不动。记住原则损失函数用带 softmax 的交叉熵时网络输出必须是原始 logits不能加任何激活。优化器方面Adam 是入门期的稳妥选择默认学习率 1e-3 起步对超参不敏感、收敛快缺点是最终精度有时略低于调好的 SGD。我的建议是先用 Adam 把流程验证通等模型结构和数据都稳定了再换成带动量的 SGD 加学习率衰减去冲精度这个对比实验能让你理解两种优化器的行为差异。权重初始化在新手阶段经常被忽略因为批归一化已经承担了大部分稳定训练的责任。但如果你的网络没有 BN或者最后一层全连接层特别宽初始化就变得关键。默认初始化在深一点的网络上容易让激活值逐层衰减或者爆炸可以用何恺明初始化它针对 ReLU 的特性做了方差补偿。实际做法很简单遍历模块对卷积层和全连接层统一施加这个初始化偏置置零。这段代码值得写进你的模板里以后每次新建模型都带上。3.5 训练循环骨架与四个易错点训练循环本身只有十几行但里面藏着四个让我反复栽跟头的地方。第一个是模式切换训练时必须调用model.train()推理和验证时必须调用model.eval()。因为 dropout 和批归一化在两种模式下的行为完全不同忘了切换的话验证精度会莫名其妙地抖动甚至比训练精度还高让人误以为模型很好。第二个是梯度清零每轮反向传播之前必须清零梯度不清的话梯度会累加等效于把学习率放大了好几倍。第三个是验证阶段要用torch.no_grad()包起来否则计算图会一直累积显存一路涨到爆炸这个现象在训练前期不明显跑几十轮之后突然报显存不足排查起来很费时间。第四个是数据搬移设备模型和数据必须在同一个设备上忘了搬数据会报设备不匹配忘了搬模型则是另一条报错。下面是一个可以直接复用的骨架def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total 0.0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() logits model(imgs) loss criterion(logits, labels) loss.backward() optimizer.step() total_loss loss.item() * imgs.size(0) correct (logits.argmax(1) labels).sum().item() total imgs.size(0) return total_loss / total, correct / total torch.no_grad() def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total 0.0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) logits model(imgs) loss criterion(logits, labels) total_loss loss.item() * imgs.size(0) correct (logits.argmax(1) labels).sum().item() total imgs.size(0) return total_loss / total, correct / total注意损失累加时的写法是loss.item()乘以批大小再累加最后除以样本总数这样即使最后一个批次不满算出来的也是真实的样本平均损失。如果直接对每批的 loss 求平均遇到批大小不一致的情况会有偏差虽然幅度不大但既然能算准就没必要留这个隐患。4. 训练诊断看懂曲线才谈得上调参4.1 四条曲线组合起来读训练日志里至少要记录四条曲线训练损失、验证损失、训练精度、验证精度。单看任何一条都会误判必须组合起来看。训练损失和验证损失同步下降、两条精度曲线一起上升并逐渐收敛这是最理想的状态说明模型容量和数据难度匹配。训练损失持续下降但验证损失在某轮之后开始上升这是典型的过拟合模型开始背训练集了。训练损失和验证损失都很高、几乎不降说明欠拟合或者根本没学起来原因可能是学习率太小、模型容量不足、数据预处理有 bug也可能是标签和图像错位了。还有一种容易被忽视的情况训练损失正常下降验证损失从第一轮就明显偏高且一直高出一截这往往不是过拟合而是训练集和验证集的数据分布不一致比如两者的图像来源不同、预处理路径不同。我遇到过一次训练集图片是压缩过的验证集是原图模型在验证集上精度就是上不去查了两天才发现是数据源的问题。4.2 过拟合的应对手段与优先级发现过拟合之后不要慌着加 dropout按收益从高到低试顺序很重要因为低优先级的手段会掩盖高优先级手段的效果。第一优先是数据增强和增加数据量这是从根上解决问题模型见过更多变化自然不容易背。第二是权重衰减给损失加一个参数范数惩罚通常 1e-4 到 1e-2 之间试。第三是 dropout放在全连接层前面比例 0.3 到 0.5卷积层后面加 dropout 的收益通常不如 BN 明显。第四是减小模型容量或者加早停。早停很实用保存验证精度最高的那一轮权重而不是最后一轮的这一条几乎零成本建议从一开始就做。至于减小模型一般是最后手段因为容量小了可能转为欠拟合来回折腾。这里有个经验小数据集上数据增强带来的提升往往比换更大模型来得明显很多人第一反应是加层加宽结果参数翻倍精度不升反降白花训练时间。4.3 学习率是性价比最高的超参数如果要给所有超参数排个影响力顺序学习率排第一没有争议。学习率太大损失会剧烈震荡甚至发散太小收敛极慢还容易停在不好的局部区域。入门的做法是从一个较大的值开始扫描比如 1e-1、1e-2、1e-3、1e-4 各跑几轮看哪一档损失下降最稳。更省事的办法是学习率扫描在一个 epoch 内让学习率从极小指数增长到较大值记录每一步的损失画出曲线损失下降最陡的那一段对应的学习率就是最佳起点。训练中期通常会配学习率调度。最朴素的是阶梯下降跑固定的轮数后把学习率除以 10更平滑的是余弦退火让学习率按余弦曲线从初始值衰减到接近 0还有一种自适应策略监控验证损失连续几轮不降就自动减半。另外批大小和学习率存在大致线性关系批大小翻倍时学习率也可以相应放大因为大批次的梯度估计更准可以迈更大的步子。这个关系在换显卡、改批大小时特别有用能省掉重新调参的时间。4.4 批归一化的两个隐藏坑批归一化很好用但它有两个坑必须知道。第一个是批大小太小时统计量不准。批归一化用的是当前批次的均值和方差批次只有 2 到 4 张图时这两个统计量噪声极大训练会变得非常不稳定精度忽高忽低。判断方法很简单如果你把批大小从 32 降到 4性能明显崩掉那就是这个问题。解决办法是改用组归一化或者层归一化它们不依赖批大小代价是收敛速度可能略慢一点。第二个坑在推理阶段。批归一化在训练时用批统计量在推理时用训练过程中累计的滑动平均统计量这两者必须切换正确。如果你手动实现推理逻辑却忘了切换到评估模式推理结果会和训练时不一致而且差异在小批次上非常明显。更隐蔽的情况是微调时冻结了部分层但 BN 的统计量还在更新导致原本学好的特征被破坏。这类问题没有报错只能靠对比训练精度和推理精度来发现所以我一般在训练结束后立刻在训练集上跑一遍推理精度应该和训练日志基本一致差得多了就是模式或者预处理除了问题。5. 常见问题与排查实录5.1 损失不下降或者直接变成 NaN这是新手最高频的问题按下面的顺序查基本能覆盖八成情况。第一步查数据和标签是否对齐一个很粗暴但有效的验证方法是故意只取一个批次反复训练几十次如果损失能降到接近 0说明模型和数据管道是通的问题出在数据多样性或者学习率上如果连这个批次都拟合不了那就是结构或者损失函数有问题。第二步查学习率是不是太大直接除以 10 再跑几轮。第三步查标签是否越界或者类型错误交叉熵损失要求标签是整数类型且在类别范围内。现象最可能原因处理方式损失完全不动恒定不变学习率为 0 或优化器没绑定参数检查优化器初始化顺序损失先降后突然变 NaN学习率过大梯度爆炸降学习率 10 倍加梯度裁剪损失缓慢抖动不降数据未归一化或学习率偏小加标准化学习率乘 3 到 10单批次都拟合不了网络结构或损失函数用错检查输出维度与是否重复 softmax损失为负或异常大输入数据未转浮点确认数据类型为 float32提示加梯度裁剪是成本极低的保险措施一行代码能挡住大部分偶发的梯度爆炸。训练大模型之前先加上别等出了 NaN 再回头补。5.2 验证精度异常高的三个来源验证精度高当然是好事但如果高得不合常理先怀疑数据泄漏。最常见的泄漏是验证集的图片和训练集重复或者同一张原图经过不同裁剪分别落进了两个集合模型其实是在背答案。第二种是预处理的泄漏前面提到的用全量数据算归一化统计量就是典型。第三种是反复在测试集上挑模型试了二十个配置选了在测试集上最好的那个这个精度就不再是无偏估计了。检查方法很直接先把训练集和验证集做一次哈希去重看看有没有重叠然后在一个完全没见过的、独立采集的小样本上测一遍如果精度断崖式下跌说明之前的验证结果不可信。还有一个技巧是看混淆矩阵如果某一类的精度是 100% 而其他类都很低很可能是这一类在验证集里只有一两个样本或者样本本身极易区分。总之看到异常好的数字第一反应应该是怀疑而不是庆祝。5.3 显存不够和训练变慢的排查思路显存问题有几种不同的表现需要分开处理。如果是启动就报显存不足多半是批大小太大或者模型太大先降批大小再看数据加载器是不是预设了很大的缓冲区。如果是训练几十轮之后突然报显存不足那基本可以确定是验证阶段没关梯度累积或者列表里存了带梯度的张量没释放。如果是推理时显存够但速度慢检查是不是每次都重新加载模型。训练速度慢的原因更多样可以按这个顺序看先确认数据是不是真的在 GPU 上算很多人遇到过模型在 GPU 但数据还在 CPU 上每一步都有 PCIe 拷贝开销然后看数据加载是不是瓶颈把num_workers从 0 调到 4 或 8如果每个 epoch 时间明显缩短说明之前卡在读盘上再看是不是开了不必要的日志同步每个 batch 都打印或者写文件会严重拖慢速度。混合精度训练是提速的常规手段能在基本不掉精度的情况下把显存占用和训练时间都降下来但要注意损失缩放这个细节遇到梯度下溢时数值会变成 NaN。5.4 一次完整的调参记录分享一个我自己做过的调参过程供参考。任务是三分类的工业部件缺陷识别训练集 2400 张验证集 600 张输入 128×128。第一版直接用前面的 SmallCNNAdam学习率 1e-3批大小 32训 30 轮。结果训练精度到 99%验证精度卡在 82% 不动典型的过拟合。第一轮调整只加了两种增强随机裁剪加填充、随机旋转 10 度其他不变验证精度到 87%。第二轮把权重衰减设成 1e-4dropout 从 0.3 提到 0.4到 89%。第三轮换优化器改成带动量的 SGD学习率 0.01配余弦退火轮数加到 60验证精度 92%训练精度降到 95%过拟合明显缓解。第四轮针对混淆矩阵里表现最差的那一类补了 300 张样本这类样本原本只有 200 张补完到 94%。整个过程大概花了一周其中大部分时间在标注和验证数据上真正调参的时间不到两天。这个比例很典型数据侧的投入回报远高于结构侧的微调。6. 从能跑到能用评估、推理与轻量化6.1 别只看准确率这一个数准确率在类别均衡的时候够用一旦类别不均衡就会产生严重误导。举个极端的例子十类数据里某一类占九成模型全部预测成这一类准确率也是 90%看上去很美实际毫无用处。这个阶段必须看混淆矩阵和每类的精确率、召回率、F1 分数。混淆矩阵的行是真实类别、列是预测类别对角线是预测对的非对角线上集中出现的地方就是模型最容易混淆的两类。看懂混淆矩阵之后你就知道下一步该往哪儿使劲了。如果两类之间互相混淆严重先去看看这两类的样本长什么样很可能是标注标准不统一或者这两类本身就高度相似、人工都难分。这时候加数据、改标注比改网络有效得多。如果某一类的召回率很低但精确率正常说明漏检多考虑给这一类加权。另外如果业务上假阳性和假阴性的代价不同那阈值的选择就不能用默认的 0.5要根据实际需求调这时候画一条不同阈值下的指标曲线会比单个数字有用得多。6.2 推理端的预处理必须和训练完全一致这是上线过程中最容易犯错的地方而且错得很隐蔽。训练时用了随机裁剪和翻转验证时用的是中心裁剪加固定尺寸缩放如果推理代码里写成了直接缩放不做中心裁剪输入到模型里的图像分布就变了精度会莫名下降几个点。更常见的是归一化参数不一致训练用 0.5 标准化推理用了 ImageNet 的统计量模型照样输出结果只是全错。我的做法是把预处理逻辑抽成一个独立模块训练和推理共用同一份代码杜绝两边写两遍的情况。同时把配置里的图像尺寸、均值、方差、类别映射全部固化进模型文件旁边的配置文件部署时一起走。上线前必须做一个一致性检查拿训练集里的一百张图分别在训练框架和部署环境里跑一遍比较两边输出的差异如果 logits 差距在极小范围内才算过关。这一步花不了多少时间但能挡掉绝大多数线上事故。6.3 模型导出与端侧部署的几个坑模型训练好之后导出这一步有几个细节值得注意。第一是维度固定问题导出的模型通常会固定输入尺寸如果你的业务场景中图像尺寸是变化的要么在导出时声明动态维度要么在预处理阶段统一 resize 到固定尺寸。第二是算子兼容性训练时用的某些自定义算子或者特殊激活函数在目标推理引擎里可能不支持导出时不会报错加载时才会失败所以导出后一定要在目标环境里实际跑一遍。第三是量化。端侧部署为了速度和体积通常会把浮点模型量化成低精度精度损失一般在可接受范围内但要注意量化校准用的样本必须是有代表性的从验证集里随机抽几百张就够。校准样本分布偏了量化后的精度会掉得比预期多很多。还有一个小经验量化之后的模型用同样的测试集重新评估一遍别直接沿用浮点模型的精度数据两者之间总会有差异心里要有个数提前准备好精度回退的方案。最后分享一点个人体会。我入门的时候花了太多时间在“找到最好的网络结构”上试了各种层数、各种技巧结果精度提升非常有限。后来回头看那些提升幅度最大的改动无一例外都在数据侧把标注标准统一、补上长尾类别、清理掉错标样本。模型结构更像是天花板数据质量决定你离天花板有多近。所以如果你现在正卡在一个精度上不去、也不知道该怎么改的状态建议先停下来把验证集里预测错的样本一张张看一遍通常会看到一些比调参重要得多的东西。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询