做图像分类任务,很多人的第一反应是找个预训练模型,把数据丢进去,等准确率出来就完事。但真正在工程里做过一遍就会发现,这个任务像一面放大镜,把数据质量、训练策略、模型理解和部署细节全部照得清清楚楚。图像分类的目标很朴素:给一张输入图片,模型输出它属于预定义类别中的哪一类,通常还附带每个类别的概率。但正是这个看似简单的输入输出关系,撑起了目标检测、语义分割、图像检索等一大片计算机视觉上层应用。这篇文章我把做图像分类任务时反复踩过的坑和验证过的做法整理出来,适合刚跑通第一个模型的新手,也适合想把模型推到生产环境的同学参考。
1. 图像分类任务到底在解决什么问题
1.1 从像素矩阵到语义标签:任务本质
一张RGB图像在计算机眼里就是三个二维矩阵,每个维度分别代表红、绿、蓝通道的亮度值。图像分类要做的事,就是让模型在这些密密麻麻的数字里找到有效模式,最后映射到“猫”“狗”“汽车”这样的语义标签。写成数学形式很简单:给定图像x,模型输出一个概率分布p(y|x),其中y属于预定义的C个类别,最后取概率最高的类别作为预测结果。
如果只用传统方法,你需要手工设计边缘、颜色直方图、纹理特征,再交给分类器。缺点非常明显:换一个场景,特征可能需要重新设计。深度卷积网络则会把“找特征”这件事也一并学出来,低层识别边缘和纹理,高层组合出部件和整体形状。这也是为什么深度模型能在图像分类任务上把准确率拉到远超手工特征的水准。
看上去简单,真正处理起来难点都在现实数据里。同一个猫,趴在沙发上是一个样子,在黑暗里又是另一个样子;不同品种的狗,可能比同一只狗在不同光线下的差异还要大。类内差异、类间相似、遮挡、姿态变化、光照变化,这些因素叠加起来,才让图像分类任务成为一个需要系统性工程落地的问题。做这个任务时,我习惯先花时间搞清楚数据里的真实分布,而不是急着调模型,因为后面所有指标都建立在数据之上。
1.2 为什么图像分类是计算机视觉的基石
很多上层任务都在用图像分类的思想做底层支撑。目标检测可以拆成“在哪里”加“是什么”,那个“是什么”就是一个图像分类子问题;语义分割本质上是给每个像素做分类,只不过类别数以像素为单位;图像检索则在最后通过分类或度量学习把图像映射成特征向量,再比较相似度。所以把图像分类做扎实,后面迁移到检测、分割项目会顺很多。
应用场景也不用多说:相册自动归档要识别场景和物体,工业质检要判断产品是否合格,医疗影像辅助要判断病变区域,机器人要先认物再抓取。这些场景里,图像分类模型往往是整个系统最前置的一环,一旦分类出错,后续流程再精细也难免跟着错。因此做图像分类任务时,我最看重两件事:一是测试集的评估方式是否准确还原真实分布,二是模型在小概率类别上是否保留足够鲁棒性,而不是只看一个整体准确率。很多项目在演示集上效果不错,一上线就崩,问题恰恰出在这两点上。
2. 动手前把数据折腾明白
2.1 数据集获取、目录划分与标签清洗
图像分类项目的第一个分水岭不是模型,而是数据。数据没理清楚,后面所有指标都是空中楼阁。公开数据集可以直接拿来玩,像CIFAR-10这种适合验证流程,ImageNet子集适合做迁移学习预训练。但真实项目里更多情况是自己采集图像,常见来源包括设备拍摄、系统截图、历史素材库等。无论来源是什么,第一步都是统一目录结构,推荐按train/val/test划分,每个子目录下按类别名建文件夹。这样做的好处是很多框架自带的数据加载工具可以直接读取目录作为标签,代码量少,也不容易出错。
dataset/ train/ cat/001.jpg dog/001.jpg val/ cat/... dog/... test/ ...划分比例通常7:2:1,但如果数据量很大,验证集和测试集可以适当缩到几千张就够;如果数据量很小,宁可多做几折交叉验证,也别只切一小撮当测试集。切分时要按类别分层抽样,避免随机切完某类图像全跑到训练集里,导致验证集没有那个类。标签清洗同样关键,我见过不少项目在计算指标时发现准确率低得离谱,最后查出来是有几百张标签标错了,有些是复制数据时张冠李戴,有些是误把相似类别混在一起。建议在训练前随机挑一批图像人工过一遍,尤其关注类别边界模糊的样本。这一步很花时间,但省掉它的代价通常更大。
2.2 预处理与数据增强:喂给模型的图长什么样
拿到图片后不能直接塞给网络。不同照片尺寸不同,需要先缩放到统一输入尺寸,常用224×224,像CIFAR-10这种小图则是32×32。缩放之外还要归一化,把像素值从0到255缩放到模型期望的范围内。最常用的做法是减去均值再除以标准差,例如224输入模型时用ImageNet统计的mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225]。这里有个新手常犯的错误,训练时做了归一化,推理时忘了做,结果图像分布完全不匹配,准确率掉一截。
数据增强是图像分类任务里最“性价比高”的一环。最简单有效的组合是随机水平翻转、随机裁剪和多尺度缩放。裁剪建议先按比例缩放后再随机裁剪成目标尺寸,而不是直接在原图上裁,否则会把目标物切掉。颜色抖动要慎用,尤其是对颜色敏感的工业质检场景;医学图像还要避免过强的几何变形,因为解剖结构的方向有生理意义。增强强度可以用一个原则把握:训练集和真实场景越接近,增强越保守;数据越少,增强越激进。像MixUp和CutMix这类高级增强,适合中等规模数据集,能显著提升鲁棒性,但会增加训练时长,需要跑通基线后再加。
3. 模型选型与网络结构解析
3.1 从经典到轻量:卷积网络怎么选
图像分类任务中,模型选择没有绝对最优,只有资源约束和目标之间的平衡。ResNet系列最稳,一个ResNet-18足够验证绝大部分二分类、十分类项目,训练速度快,显存占用小;ResNet-50效果更好,但训练时间和显存都会上来。EfficientNet在同等参数下精度更高,适合追求上限的场景。如果最终要部署到手机或边缘设备,MobileNet和轻量化改造后的网络更合适。做个直观的对比表格,参考参数如下:
| 网络结构 | 参数量(约) | 输入尺寸 | 适合场景 |
|---|---|---|---|
| ResNet-18 | 11M | 224×224 | 快速验证、中小数据集 |
| ResNet-50 | 25M | 224×224 | 精度优先、较大数据集 |
| EfficientNet-B0 | 5.3M | 224×224 | 精度和参数量的平衡 |
| MobileNetV3 | 2M~5M | 224×224 | 移动端、边缘设备 |
ResNet的跳跃连接解决了深层网络梯度回传受阻的问题,让50层、101层的模型也容易收敛。EfficientNet则是用自动搜索方式找出一组缩放系数,同时调整深度、宽度和分辨率。移动端网络大量使用深度可分离卷积,把普通卷积拆成逐通道卷积和逐点卷积,计算量少一截。选型时我习惯先跑一个快速验证:如果数据量只有几千张,直接上ResNet-18加预训练权重;数据量达到几万张或类别特别多,再考虑更深的模型。因为瓶颈往往不在模型容量,而在训练是否充分和数据是否干净。
3.2 迁移学习:小数据集也能出效果
真实项目很少从零随机初始化训练。用ImageNet预训练权重做初始化,是图像分类任务中几乎默认的做法。原因很简单:网络浅层学到的是边缘、纹理、颜色等通用特征,这些特征在不同数据集间高度复用;预训练相当于让模型从小就有“视觉常识”,再来学你的任务只需要微调。
迁移学习有两种常见操作。一是特征提取:冻结backbone所有参数,只训练最后的分类头,适合数据很少或只想快速验证特征是否有效。二是微调:先全部训练一段时间,再解冻某些层,用更小学习率更新,适合目标任务和预训练数据差异较大的场景。我的经验是先冻结主干训练5到10个epoch,让分类头稳定下来,再解冻全部参数训练。这样比一开始全量微调更不容易震荡。解冻后主干学习率设为分类头的十分之一左右,比如分类头1e-3,主干1e-4。另外要注意,如果输入图像和预训练输入尺寸不一致,不仅需要缩放图像,最后的全连接层维度也要随类别数量重新初始化,这一层不能继承预训练权重,否则输出类别对不上。
4. 训练参数调优的实战经验
4.1 学习率、批次大小与优化器怎么定
训练配置里,学习率是第一敏感项。太大会看到loss在某个值附近震荡,太小则学半天不动。图像分类任务里,我习惯先从1e-3起步,用预训练权重时甚至可以降到1e-4到5e-5之间;batch size一般取32或64,显存允许可以到128,但不要为了大batch盲目冲。batch size变大的同时通常需要相应调大学习率,比如从64提到256,学习率可以按比例放大两倍左右,否则收敛速度会不同。优化器主流就两个:AdamW和带动量的SGD。AdamW收敛快,对学习率不那么敏感,适合快速出结果;带动量的SGD虽然调起来费劲,但最终精度和泛化往往更好,尤其配合余弦退火。我的常规操作是:时间紧用AdamW,追求指标上限再切SGD。
训练节奏上也有些值得注意的细节。warmup(学习率预热)可以解决训练初期loss不降反升的怪问题,常见做法是先用5个epoch从0逐渐升到目标学习率。之后接余弦退火或阶梯式衰减,比全程用同一个学习率效果好。权重衰减(weight decay)建议1e-4或5e-4,AdamW里这个参数和传统SGD里的weight decay含义不完全一样,但直接设1e-4通常安全。训练过程中要实时盯训练loss、验证loss和验证准确率,一旦出现验证loss连续上升但训练loss还在下降,就是过拟合信号,该停或者该加正则。
4.2 正则化与过拟合的博弈
过拟合在小数据集上几乎必然出现,表现就是训练集准确率非常高,验证集却上不去。除了数据增强,还有几个常用正则手段。
Label Smoothing把硬标签变成软标签,比如真实类别概率从1变成0.9,剩下0.1均匀分给其他类别。这个操作可以防止模型太自信,提升泛化,多分类任务里我一般设0.1。Dropout在分类头之前加一层,比例0.2到0.5,模型越大、数据越少,比例可以调高。对于CNN主干,Dropout效果不如在最后几层有效,所以不要太依赖。EarlyStopping是省时间利器:监控验证loss,连续若干个epoch没改善就恢复最佳权重,patience我一般设5到10。注意不是验证准确率不变就停,验证准确率可能在很长一段平台期后才突然上升,所以用验证loss作为停止指标更稳。
还有一个高阶技巧:先跑通一个过拟合小样本测试。取出训练集里几十张图,让模型在这几十张里训练,如果loss能一直降到很低,说明网络结构没问题、学习率也够;如果连这一点数据都拟合不了,那大概率是bug而不是模型问题。这个技巧能省下大量排查时间,我在每次换数据集或换模型结构时都会先做一遍。
5. 评估与部署:别被准确率骗了
5.1 混淆矩阵、精确率、召回率的核心解读
图像分类任务最常用的指标是准确率,但它有个大坑:类别不均衡时严重失真。假如99%是负样本,1%是正样本,模型什么都不做,准确率也有99%,但这个模型完全没用。所以分类项目一定要看混淆矩阵、精确率、召回率和F1。
举个例子,三分类场景下混淆矩阵的行表示真实类别,列表示预测类别,对角线上是分类正确的数量。如果某个类别的对角线数字明显偏低,就需要单独分析它被错认到哪些类别。对于二分类或多分类,精确率是“预测成这个类别的样本里,有多少真的属于这个类别”,召回率是“真实属于这个类别的样本里,有多少被找出来了”。两者往往此消彼长,F1是调和平均,能综合反映。比如医疗辅助筛查场景,漏诊代价高,就要优先提高召回率,哪怕牺牲一些精确率;工业质检要控制误杀,就要优先提高精确率。
如果类别特别多,还可以看Top-5准确率:只要真实类别出现在模型概率最高的前五个结果里就算对。这在十类别以上的任务里很实用,因为类别之间的边界本来就模糊,硬性要求排名第一会低估模型能力。评估时也要保证测试集和训练集的分布一致,不能拿不同来源、不同时间采集的数据混在一起测,否则指标会虚高。每次评估后我会顺手把预测错误的图像可视化看一遍,这比看任何数值都更能发现问题。
5.2 部署前的检查清单
模型训练完不等于项目结束,部署环节稍不小心就翻车。我把自己实际用过的检查清单列一下,每一条都是踩过坑换来的。
第一,推理时的预处理必须和训练时完全一致。尺寸、归一化均值方差、通道顺序都要逐一核对,最稳妥的办法是训练和推理共用同一个预处理函数。第二,确认模型是eval模式还是train模式。如果模型里有BatchNorm和Dropout,推理时必须切到eval模式,否则BatchNorm会用当前batch统计量,结果一塌糊涂。第三,导出的模型最好做一次数值一致性验证,拿同一张图分别用训练框架和部署框架跑,输出差异要控制在可接受范围内。第四,看看模型对哪些错误样例最容易犯,把这些样例打印出来人工检查,往往能发现数据集标签错误或者类别定义不清晰的问题。第五,移动端或边缘设备要考虑量化,把FP32模型转成FP16或INT8,能有效减少体积和推理耗时,但量化后要重新验证精度,尤其对微小目标或高相似类别影响较大。
6. 图像分类常见故障排查手册
6.1 训练不收敛或loss震荡的排查思路
遇到loss不下降,先别急着调模型结构,按顺序排查几类问题。首先确认数据预处理是否正确,归一化有没有做,图像有没有被错误读取成全黑或全白;然后确认标签是否对得上,我遇到过索引错位导致模型学习的是乱标签的现象,特征再强也学不出来。其次检查模型输出层和标签数量是否一致,优化器参数是否有问题。再就是前面提到的过拟合小样本测试,如果模型在小样本上也无法把loss降下来,问题大概率在网络结构或数据读取逻辑。
loss震荡通常是学习率偏大或batch size偏小。可以先把学习率降低一个数量级看看,比如从1e-3降到1e-4,同时把batch size从16提到32或64,观察几轮曲线是否稳定。如果梯度爆炸,loss会出现NaN,这时要检查学习率、权重初始化,以及数据里是否存在异常的极端像素值。clip gradient的norm设为1.0也可以兜底,但根治还是要找到原因。我习惯把所有曲线都记录下来,训练loss、验证loss、学习率、每一层梯度的L2范数,出了问题直接看曲线定位,比盲猜高效得多。
6.2 类别不均衡和难例样本怎么处理
真实图像分类任务里,类别数量不均衡是常态。比如工业场景中良品占了绝大多数,不良品只占很小比例;或者某个稀有类别样本很少。最简单的办法是重采样,让每个batch里各类别出现频率接近,代码层面可以用加权采样器,权重跟类别样本数成反比。也可以给loss加权重,比如类别样本越少,损失权重越大。更进阶的做法是Focal Loss,它专门压低容易分类样本的损失,让模型把注意力集中在难分类样本上,适合二分类且正负样本极度不平衡的场景。
难例样本不一定是类别不均衡造成的,也可能本身视觉特征和多个类别都相似。这时候不要只盯着模型,先回看标注规则。我参与的一个模拟项目里,模型总把“杯子”和“保温杯”搞混,后来发现标注本身就存在灰色地带:带杯盖的保温杯到底算杯子还是保温杯?标注规则没有统一,模型自然学不稳定。最后重新定义类别边界,把“有盖且保温”强制归到“保温杯”,问题才缓解。所以难例样本的处理往往是数据和规则问题优先,模型技巧次之。
6.3 推理速度与精度的权衡取舍
模型部署时,速度和精度永远在拔河。如果模型在GPU上跑得很慢,第一步看输入分辨率是否过大,224×224不是必须,很多任务用160×160也能达到接近的精度,却能省下大约一半计算量。其次检查主干网络是否适合目标设备,移动端优先MobileNet这类轻量结构,服务端则可以用ResNet或EfficientNet。再往下是模型压缩:剪枝可以去掉冗余通道,量化能把模型体积压到四分之一左右,知识蒸馏则用大模型当老师教小模型。
我在实际项目里比较推荐“先精度后速度”的策略:先把大模型跑到90分,再用它作为老师蒸馏出一个小模型,往往比直接训练小模型更容易达到比90分略低的水平,但速度翻几倍。具体流程大致是训练一个高精度大模型,构造蒸馏数据集,让小模型同时学习真实标签和大模型输出的软标签,温度参数设在3到5之间效果比较好。这一套在图像分类任务里非常成熟,比盲目换更小的模型靠谱得多。