典型图像分类卷积神经网络(CNN)的基本流程,听起来像是教科书里的一章,但我更愿意把它看成一堂动手课。搞图像分类这几年,我自己从“跑通一个 LeNet 就欢呼半天”到现在能比较从容地设计、调试、优化一个分类模型,踩过的坑、绕过的弯都不少。这篇东西不打算从“什么是神经网络”这种最底层开始念经,而是直接沿着一条真实可复现的流程走一遍:拿到一批图片,怎么把它变成模型能吃的东西,怎么搭一个能出结果的卷积网络,怎么训练到收敛,以及最后怎么判断模型是真的“会了”而不是“背下来了”。
如果你刚接触深度学习,或者已经跑过几个开源项目但总觉得隔着层纱,这篇文章应该能帮你把整条链路串起来。我会把每个环节的“为什么这么做”也讲清楚,毕竟光会调库不够,知其然才好在出问题时知道去哪排查。
1. 整体思路:图像分类到底在做什么
1.1 分类任务的本质
图像分类这件事,说白了就是给一张图片贴一个标签。比如给一张猫的照片,模型输出“猫”;给一张飞机的照片,模型输出“飞机”。听起来简单,但计算机看到的图片和你看到的不一样,它看到的是一堆数字——一个三通道彩色图片,在代码里就是一个宽乘高乘三的数组,每个数值代表某个像素点上某个颜色通道的亮度。
分类模型要做的,就是从这些数字里找到能区分不同类别的规律。早期传统方法靠人工设计特征,比如颜色直方图、纹理特征、边缘检测,再用支持向量机之类的分类器去分。问题是:换一个数据集、换一种图片风格,人工特征往往就不灵了,得重新设计。卷积神经网络的革命性在于,它把“特征提取”这件事也变成了可学习的部分。网络自己从数据里学到底该看纹理、看轮廓还是看局部形状,不需要人手动指定。
整个 CNN 图像分类流程可以拆成五个大块:数据准备与预处理、模型结构设计、训练配置与执行、评估与迭代、部署与应用。每个环节都有各自的坑,我会把每一块的细节都铺开讲。你把这五块串起来,就是一个完整的图像分类项目。
1.2 为什么选 CNN 而不是别的结构
在图像领域,CNN 几乎是默认选择,原因在于它有别的结构替代不了的两个特性:局部连接和权值共享。
- 局部连接:每个卷积核只观察输入的一个小窗口,比如 3x3 的区域,而不是整张图。这符合图像的局部相关性——一个物体边缘、一块纹理,往往只和它周围的像素有关系,离得很远的像素之间直接关系不大。
- 权值共享:同一个卷积核会滑遍整张图,也就是说全图共享同一套参数。这极大减少了参数量。一张 224x224 的图,如果第一层就用全连接,每个神经元要连 15 万个输入,参数爆炸。而卷积层几个 3x3 的核就能搞定,还能自动学到空间平移不变的特征——猫在图的左上角还是右下角,卷积核都能识别出同样的边缘模式。
当然,现在 Transformer 结构在视觉领域也火过一阵,但它需要海量数据和极大的算力才能发挥威力。对于绝大多数中小规模项目、学术实验、个人实践,CNN 依然是性价比最高的方案,训练快、收敛稳、调参经验丰富、部署生态成熟。所以我后面的流程都围绕 CNN 展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据准备与预处理:决定模型上限的隐形环节
2.1 数据集的组织与标签设计
很多人一上来就写模型代码,结果发现训练效果稀烂,回头排查才发现是数据没弄对。数据准备是整个流程里最不性感但最重要的一环,模型的上限由数据决定,模型只是去逼近这个上限。
先看数据集怎么组织。最常见的格式是按类别分文件夹,每个子文件夹的名字就是类别标签。训练时用一个映射表把文件夹名转成数字索引:比如 cat -> 0, dog -> 1, plane -> 2。这个映射表要单独存好,后面预测新图片时还要用同一个映射来解读输出。
这里有一个容易忽略的点:类别编号本身没有语义,0、1、2 只是 ID,不是数字大小。模型输出的 softmax 概率分布是对每个类别分别算的,类别之间的顺序没有任何数学意义,所以千万别因为“猫排在 0,狗排在 1”就觉得模型在输出一个数值。
数据集要划分成三份:训练集、验证集、测试集,常用比例是 7:2:1 或者 8:1:1。划分时要注意保证每个类别的样本比例在各集合里大致一致,这叫做分层采样。否则训练集里猫占 90%,验证集里猫只占 10%,模型在训练和验证时的数据分布不一致,验证分数就不能反映真实水平。
实操心法:如果数据集本身就很小,几百张图这种量级,先用一个简单的分层划分就行;如果数据量中等且做正式实验,建议用 5 折交叉验证来估算模型稳定性,但日常迭代训练中用一次固定的划分就够了,省时间。
2.2 预处理的三板斧:缩放、归一化、数据增强
图片不能直接扔进网络,得先经过几个标准操作。
缩放:CNN 一般要求输入尺寸固定,因为全连接层的输入维度是固定的。常见做法是把短边缩放到固定尺寸,比如 256,再从中心裁剪出 224x224 的区域。像 ResNet 系列的标准输入就是 224x224。
归一化:像素值原本是 0 到 255 的整数,直接喂给网络会导致梯度数值不稳定。标准做法是除以 255 缩放到 0 到 1 区间,再按通道减均值、除标准差,把数据分布拉成接近标准正态分布。以 ImageNet 数据集统计出的均值 [0.485, 0.456, 0.406] 和标准差 [0.229, 0.224, 0.225] 是最常用的。如果你用的是在 ImageNet 上预训练的模型做微调,最好沿用这套均值和标准差,因为你是在人家预训练的特征空间里继续学。
数据增强:这个操作对小数据集尤其重要。图像分类的数据增强就是在训练时对图片做随机变换,等效于凭空造出更多样本来。常用手段包括:随机水平翻转、随机旋转、随机裁剪、颜色抖动(调整亮度、对比度、饱和度)。增强的目的不是“让图变好看”,而是让模型在变换下依然能认出同一类物体,这样学到的特征更鲁棒。
注意一个细节:验证集和测试集一般不做随机增强,只做缩放和归一化。因为你评估的是模型对“正常图片”的识别能力,而不是它能不能扛住随机截图的干扰。
2.3 一个即使老手也容易踩的预处理坑
我在实际项目里踩过这么一次:训练集做增强时把随机旋转的范围设太大,比如正负 30 度,结果模型在训练集上表现很好,验证集上却一直在掉点。查了半天发现,验证集图片里物体基本都是正的,而训练时模型见了大量被旋转得很歪的图,学的特征被带偏了。
所以数据增强的强度要跟着任务走。水平翻转、小幅裁剪这类通用增强基本无损;而旋转、颜色变化这类强增强,一定要先可视化几张增强后的图看看,人眼都不能接受了,模型更别想学好。
另外,增强不是越多越好,过强的增强会让模型学不到真实分布。我的经验是:先不加增强跑一轮看基线,然后逐步加增强观察验证集曲线,找到收益的临界点就停。
3. 模型构建:搭一个能出结果的 CNN
3.1 卷积层、池化层、全连接层的角色分工
一个典型 CNN 分类模型的骨架是这样的:
输入图片 → [卷积层 + 激活函数 + 池化层](重复若干次)→ 展平 → 全连接层 → 输出层(softmax)
- 卷积层负责提取特征。低层卷积核学到的是边缘、颜色块、角点等低级特征,高层卷积核会把低级特征组合成更抽象的语义,比如眼睛、轮子、机翼。
- 池化层做下采样,常见的是最大池化,取窗口内的最大值。它的作用有两个:减小特征图的尺寸从而降低计算量,以及提供一定的平移不变性——物体在窗口里稍微挪动几个像素,最大值大概率不变。
- 全连接层相当于把前面提取到的空间特征“压扁”成全局特征,最后映射到类别得分上。在较新的结构里,全连接层经常被全局平均池化替代,直接让最后一个卷积层输出的每个通道对应一个类别,参数量更小、更不容易过拟合。
3.2 卷积核尺寸与通道数怎么定
第一层卷积核尺寸的选择有讲究。3x3 是现在的绝对主流,两个堆叠的 3x3 卷积拥有和一个 5x5 卷积相同的感受野,但参数量更少,而且中间多了一层非线性变换,表达能力更强。第一层也有人用 7x7 的大核加 stride=2 来快速降低分辨率,这在 ResNet 等深层网络里很常见,算是一种特化设计。
通道数的变化规律是:随着特征图分辨率逐渐下降,通道数逐层增加,典型的翻倍节奏:64 → 128 → 256 → 512。这是因为分辨率降低了,要靠更多的通道来保存足够的信息。总参数量可以用公式估算:卷积层参数量 = 输入通道数 × 输出通道数 × 卷积核宽 × 卷积核高,再加一个偏置项。
举个例子,输入是三通道的 224x224 图片,第一个卷积层用 64 个 3x3 卷积核,参数量是 3 × 64 × 3 × 3 + 64,只有 1792 个参数。对比一下,如果这一层用全连接,输入是 224 × 224 × 3 约 15 万个值,输出 64 个神经元,参数量直接到 960 万。这个差距就是你选卷积而不是全连接的根本理由。
3.3 激活函数、归一化层与经典结构参考
卷积之后一定要接非线性激活函数,否则多少层卷积都等效于一层线性变换。ReLU 是最常用的选择:f(x) = max(0, x),计算快,梯度不易消失。但它有个缺点:负值部分全部归零,可能导致部分神经元“死掉”。所以后来出了 LeakyReLU、ELU 等变体,给负值一小段斜率,这些在深层网络上会有一定帮助,但日常中小型模型直接用 ReLU 就够了。
批量归一化(Batch Normalization)是让网络能训练得更深的关键模块。它把每一层的输出分布拉回到均值 0、方差 1 附近,这样梯度在反向传播时不容易失控。实践建议:卷积层 → BN → ReLU 这个顺序是主流做法,不要随便调换,BN 放在激活函数之前效果最稳定。
如果你想起步快一点,直接参考经典结构:LeNet 适合 32x32 的小图(比如 MNIST),AlexNet 是历史里程碑但结构现在看偏重,ResNet 的残差连接让深层网络可训练,是中小型数据集上最稳的选择。实际项目中,我的习惯是先用 ResNet-18 做快速基线,后面再考虑换 ResNet-50 或更深的版本。基线能跑通,再追求精度才有意义。
4. 训练流程的实操配置:损失函数、优化器、超参数
4.1 损失函数与优化器如何配对
分类任务的标配损失函数是交叉熵损失。它的核心思想是衡量模型输出的概率分布和真实标签的 one-hot 分布之间的距离。PyTorch 里的实现常写成 CrossEntropyLoss,注意它内部已经整合了 softmax 操作,所以你模型的最后一层不需要再手动加 softmax,直接输出原始的类别得分(logits)就行。
优化器的选择上,SGD 加动量是经典答案,动量的作用像给优化过程加了惯性,能更快穿越平坦区域、减少震荡。学习率设 0.01 或 0.001,配合余弦退火或逐步衰减策略,在图像分类任务上通常表现很好。Adam 收敛快、对学习率不敏感,适合快速实验;但 Adam 的泛化能力在某些任务上略逊于调好超参的 SGD,这个在学术上有很多讨论,工程上我的经验是两者都值得试试,先跑一两个 epoch 看看 loss 下降趋势,再决定用哪个。
一个重要的配套是正则化手段。L2 正则(即 weight decay)是标配,它会惩罚过大的权重,防止模型过分依赖某些特征。SGD 的 weight decay 设 1e-4 附近是经验值,Adam 做 weight decay 的方式和 SGD 不完全一样,需要调低一些,比如 1e-5 或 5e-5。Dropout 在卷积层之间用得少,更强力地用在进入全连接层之前,比例在 0.5 左右比较安全。
4.2 batch size、epoch 和学习率之间的微妙关系
这几个超参数不是独立工作的,它们共同决定了训练的动态。
batch size 决定了一次更新梯度时看的样本数。它影响着梯度的稳定性:太小,梯度噪声大,训练曲线会剧烈抖动;太大,梯度稳定但内存开销大,而且 batch size 特别大时模型容易收敛到尖锐的极小值,泛化性反而变差。常用的区间是 32 到 256,视觉任务里如果显存允许,64 到 128 是个不错的起点。
epoch 数量没有绝对标准。我的做法是看验证集 loss:验证 loss 不再下降甚至开始上升,就该停下来了。这比拍脑袋定固定 epoch 靠谱得多。一个常用技巧是早停:验证 loss 连续 N 个 epoch 没改善就终止训练并恢复最佳模型权重。
学习率的设置在这三者里最关键。太大,loss 直接发散,数值变成 NaN;太小,loss 死活降不下来。一个土办法是先用 0.01 试跑几个 epoch,观察 loss 曲线形状再微调。如果 loss 在下降但很慢,可能学习率偏低;如果 loss 突然跳到极大值,那就是学习率过大了。还有一个小技巧是 warmup——训练前几百步用一个很小的学习率慢慢爬升到目标值,对大 batch 和深层网络尤其有效。
4.3 训练循环里值得盯住的几个检查点
训练代码本身很多人会写,有几件事是新手不会主动做的:
- 每个 epoch 在验证集上跑一次评估,别只在训练集上看 loss。训练 loss 下降不代表模型能泛化,只有验证集上的准确率才是相对可信的衡量。
- 保存最优模型的时候,依据是验证集准确率而不是最后一次训练的结果。因为最后几次迭代模型可能已经开始过拟合,准确率反而下降了。
- 记录每一轮的训练 loss、验证 loss、学习率,绘成曲线。这些曲线是排查问题的第一手情报。loss 曲线长什么样、发散还是收敛,会告诉你该往哪个方向调。
- 偶尔可视化卷积层的输出特征图。虽然不常用,但当模型怎么调都不收敛时,看一眼特征图是空白的还是一团噪声,能较快分辨出是网络结构问题还是数据问题。
5. 评估与调优:怎么判断模型是真的会了
5.1 不要只盯准确率
分类任务最常见也最容易误导人的指标就是准确率。如果类别不均衡——比如 95% 的样本是“背景”类,5% 是“目标”——一个把所有图都猜成背景的模型就能拿到 95% 准确率,但它实际上毫无用处。
这时候要看混淆矩阵。它把每个类别的真实标签和预测标签对应关系列成矩阵,能直观看出模型在哪些类之间互相混淆。比如猫狗分类,混淆矩阵会告诉你模型是把猫误判成狗更多,还是狗误判成猫更多,据此可以针对性补数据或调整决策阈值。
精确率和召回率也是一对重要指标。精确率关心的是“你说是猫的那些图里,真的是猫的比例”,召回率关心的是“所有真猫里,你找回来了多少”。不同的应用场景对两者的侧重不同:比如垃圾图过滤,宁可错杀也不想放进来,那就优先高精确率;比如医疗筛查,宁可多报也不愿漏检,那就优先高召回率。
5.2 过拟合的识别与应对清单
只要训练集和验证集准确率差距明显拉大,基本就能判定过拟合。训练准确率 98%,验证集准确率只有 82%,这是过拟合的典型信号,说明模型在背训练集而不是学规律。
应对手段按优先级排序:
- 增加数据增强强度,让模型见更多样的样本。
- 增加 dropout 或者调大 weight decay。
- 把网络变小,减少可学习的参数量——别一上来就用 ResNet-50,你的任务可能 ResNet-18 就够了。
- 早停。别让模型在训练集上无止境地涨,验证集 loss 一回头就立刻拉闸。
- 如果条件允许,收集更多数据。这永远是最有效的手段。
还有一种近似过拟合的情况叫欠拟合:训练集准确率都上不去,说明模型表达能力不足或者优化没到位。先调大学习率试试,不行就得加深网络、加宽通道数。
5.3 从一次实验中提炼可复用的迭代套路
我自己的迭代套路差不多是固定的:先快速搭一个最小可用模型,跑通流程,记录基线的准确率和 loss 曲线;然后做一轮超参数对比,主要看学习率、batch size 和数据增强的搭配;再针对混淆矩阵里问题最大的类别做专门的优化,比如补样本、调整损失函数权重、做类别增强。这样一轮轮下来,模型的提升路径是清晰可追溯的,而不是东一榔头西一棒子。
这个过程还有一个容易被忽略的价值:你会积累一批“什么情况对应什么调整”的经验,这些经验换数据集、换任务时一样能用。踩过的坑、调过的参,最后都变成你做新项目的直觉。
6. 常见问题与排查技巧实录
6.1 问题速查表
列几个我自己经常遇到的问题,附上排查思路。
| 现象 | 常见原因 | 排查思路与解决方案 |
|---|---|---|
| 训练 loss 不下降 | 学习率过小 / 数据没做归一化 / 模型结构有问题 | 先调到 0.01 试跑几个 epoch;打印输入数据的分布确认归一化做了;如果是自定义网络,逐层检查输出形状有没有变成 0 |
| 训练 loss 变成 NaN | 学习率过大 / 数据里有异常值 | 马上吧学习率降到原值的 1/10 再跑;检查是不是有损坏的图片文件,读入后像素值异常 |
| 训练准确率很高但验证准确率低 | 过拟合 | 加数据增强、加 dropout、加 weight decay、减小模型 |
| 验证准确率飘忽不定,每次结果差很大 | 数据划分不均 / batch size 太小 / 初始化不稳定 | 检查是否做了分层采样;调大 batch size;固定随机种子做多次实验取均值 |
| 预测新图时结果明显不对 | 预处理方式不一致 | 确认预测时的缩放、归一化操作和训练时完全一致,这是最容易踩的坑 |
| 显存不足 | 模型太大或者 batch size 太大 | 降低 batch size;减小输入图尺寸;换更轻的网络结构 |
6.2 两条自救的经验法则
第一条是“先跑通再优化”。我第一次自己搭 CNN 去分类花类图片时,花了很长时间调参,后来才发现自己的训练循环里压根没写优化器 step 那一行,参数根本没更新。所以第一版代码只求能完整跑完一个 epoch,不求准确率,中途把每一行关键代码都打印一遍确认没有低级失误。
第二条是“一次只改一个变量”。一个项目做久了会忍不住同时调学习率、改结构、加增强,结果精度掉了,你也说不清是哪一步导致的。我的习惯是每次实验只动一个因素,其他全部固定,用表格记录每次实验的配置和结果。这听起来麻烦,但实际上是最快的排查方式,也方便回看。
6.3 从训练到部署的过渡
很多教程讲完评估就结束了,但实际项目里训练完只是开始。模型部署到服务端或移动端,还需要做格式转换,比如转成 ONNX 或 TensorRT 的格式来加速推理。转换过程中容易踩的坑是:训练时模型用的是 Dropout、BN 这些训练态的操作,推理时必须切换到 eval 模式,否则同一个输入每次的输出结果都不一样。很多人在部署时忘了这步,线上出现预测结果不稳定,回头查才发现是 eval 模式没设置。
另一个部署期常被忽略的问题是预处理代码的重写。训练时用的增强代码在推理时根本不需要,但归一化和缩放的顺序、参数必须和生产环境的代码保持一致。项目上线后如果发现某个类别在特定分辨率图片上预测不准,先检查是不是预处理这条链路出了问题,再考虑模型本身。
我把这些都记在项目的 README 里,每次迭代新版本都会对照检查一遍部署链路,省掉了不少线上事故。
写在最后的几点经验
做图像分类这几年,我最大的体会就是:这个领域的知识密度很高,新模型层出不穷,但核心流程是稳定的。数据准备到评估迭代的整个链路,换一百个项目也还是这套逻辑。所以与其追求读懂每一篇新论文,不如先把流程里的每个环节吃透——你把数据预处理做扎实了,把训练日志看明白了,把过拟合一出现就能识别出来,那大部分项目你都能应付。
还有一个小心得:每次跑完一个实验,我会写一个简短的项目小结,记录数据规模、模型结构、关键超参数、最终指标、踩过的坑。几个月后再回看,这些笔记比模型代码本身更值钱,它们能帮你快速复现结果,也能让你在同类新任务上少走很多弯路。
希望这篇把基本流程讲透的文章,能让你少踩几个我当年踩过的坑。如果你按这个流程跑通了一个分类任务,再回头看那些花里胡哨的模型时,你会发现底层逻辑其实都是一样的。
