ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

计算机视觉入门到实战:卷积神经网络原理、项目流程与避坑指南

计算机视觉入门到实战:卷积神经网络原理、项目流程与避坑指南

1. 从一节公开课说起:计算机视觉到底在解决什么问题

很多人第一次接触“计算机视觉”这个词,是在某门深度学习公开课的某一讲里。视频里讲师放了一张图片,说“让机器看懂这张图”,然后开始讲卷积、边缘检测、池化。看完之后感觉懂了,但关掉视频又说不清楚它到底能干什么。我自己当年也是这个状态,笔记记了好几页,真正动手跑代码的时候才发现,理论和落地之间隔着一条不小的沟。

这篇内容就是围绕“计算机视觉”这个主题,把我在学习深度学习相关课程时积累的笔记、踩过的坑、以及后来在实际项目里验证过的经验,做一次系统性的梳理。它适合刚学完神经网络基础、准备进入视觉方向的人,也适合已经能跑通Demo但说不清原理的人。核心关键词就一个:计算机视觉。我会从它解决的问题讲起,一路讲到卷积网络的细节、实操中的参数选择、以及那些只有真正跑过模型才会知道的注意事项。

先说清楚它是什么。计算机视觉,简单讲就是让计算机从图像或视频中提取信息、做出判断的一类技术总称。人眼看一张照片,能瞬间分辨出猫、狗、桌子、马路,但计算机看到的只是一个数字矩阵,每个像素是0到255之间的数值。视觉任务要做的,就是从这个矩阵里找出规律,最终输出我们想要的结论——这张图里有没有猫、猫在哪个位置、这只猫是什么品种。

它能做的事情大致分几类:图像分类(这张图是什么)、目标检测(图里有什么、分别在哪)、语义分割(每个像素属于什么类别)、图像生成(根据描述造一张图)。不同任务对应不同的网络结构和训练方式,但底层都离不开卷积这个核心操作。这也是为什么几乎所有视觉课程都会花大量篇幅讲卷积神经网络,因为它是整个方向的基石。

提示:如果你连全连接网络的前向传播和反向传播都还没搞明白,建议先补上这一课再来看视觉内容。卷积网络本质上是在全连接的基础上加了“局部连接”和“权值共享”两个约束,基础不牢会越看越糊涂。

2. 为什么视觉任务不能直接用全连接网络

2.1 参数量爆炸这个硬伤

假设你有一张224乘224的彩色图片,展平之后是224乘224乘3,也就是大约15万个输入值。如果第一层隐藏层只放1000个神经元,光这一层的权重参数就是1.5亿个。这还只是一层,深一点的网络参数量会到几十亿。参数量大带来的直接后果是:显存装不下、训练慢、极易过拟合。

我早期做过一个实验,用全连接网络去分类一批32乘32的小图,参数量勉强能接受,但准确率怎么调都上不去。后来换成卷积结构,同样的数据,参数量降了一个数量级,准确率反而涨了十几个点。这个对比让我真正理解了为什么视觉领域必须用卷积。

2.2 图像的空间结构被浪费了

全连接网络把图片展平成一维向量,这一步操作直接把像素之间的空间关系丢掉了。原本相邻的两个像素,展平之后可能隔了很远,网络很难学到“局部特征”。但图像的核心信息恰恰藏在局部结构里——边缘、角点、纹理,这些都是相邻像素之间的关系。

卷积操作保留了这种空间结构。一个3乘3的卷积核在图片上滑动,每次只看一个小窗口,这样学到的就是局部特征。而且同一个卷积核在整个图片上共享,意味着不管猫出现在左上角还是右下角,同一个“猫耳朵检测器”都能起作用。这就是平移不变性的来源,也是卷积网络在视觉任务上碾压全连接网络的根本原因。

2.3 卷积到底在算什么

用一个具体的例子说明。假设有一个5乘5的灰度图,用一个3乘3的卷积核去卷它。卷积核从左上角开始,覆盖图片的3乘3区域,对应位置相乘再求和,得到一个数。然后卷积核向右滑动一格,再算一个数。滑完一行再往下滑。最终得到一个3乘3的输出,这叫特征图。

这个过程用生活化的类比:就像你拿一个放大镜在图片上逐块扫描,每扫一块就记录一个“匹配分数”。卷积核里的数值就是“你关心的模式”,分数高说明这块区域和模式很像。训练网络的过程,就是让卷积核里的数值自动调整,直到它能检测出对分类最有用的模式。

注意:卷积核的数值不是人工设计的,是反向传播学出来的。早期层学到的往往是边缘、颜色块这类低级特征,深层学到的是眼睛、轮子这类高级语义特征。这个“从低级到高级”的层次结构,是卷积网络可解释性的重要来源。

3. 卷积网络的核心组件逐个拆解

3.1 卷积层:特征提取的主力

卷积层有几个关键参数需要理解清楚。卷积核大小常见的有3乘3、5乘5、7乘7。3乘3是最常用的,因为两个3乘3堆叠的感受野等于一个5乘5,但参数量更少、非线性更强。步长控制卷积核滑动的间隔,步长为1时输出尺寸基本不变,步长为2时输出尺寸减半,相当于下采样。填充是在图片边缘补零,目的是控制输出尺寸,让边缘像素也能被充分卷积。

输出尺寸的计算公式是:输出边长等于输入边长减去卷积核大小加上两倍填充,再除以步长,最后加1。这个公式我建议背下来,调网络结构的时候会反复用到。举个例子,输入32,卷积核5,填充0,步长1,输出就是32减5加0除以1再加1,等于28。如果填充2,输出就变回32,这就是所谓的“same padding”。

3.2 池化层:降维和抗扰动

池化层不做卷积,它只是在一个小窗口里取最大值或平均值。最大池化最常用,窗口2乘2、步长2,效果是把特征图的长宽各减半,参数量减少四分之三。池化的作用是减少计算量、扩大感受野、增强对小幅平移的鲁棒性。

但池化也有争议。有些新架构用步长为2的卷积代替池化,认为池化丢失了太多信息。我自己的经验是:在小数据集上,池化能有效防止过拟合;在大数据集上,用卷积下采样可能效果更好。没有绝对的对错,要看具体任务和数据量。

3.3 激活函数:引入非线性

卷积和池化都是线性操作,堆再多层也等价于一个线性变换。激活函数的作用是引入非线性,让网络能拟合复杂函数。视觉任务里最常用的是ReLU,因为它计算简单、梯度不容易消失。但ReLU有个问题叫“神经元死亡”——如果某个神经元的输入长期为负,梯度就永远是零,这个神经元就废了。

后来出现了Leaky ReLU、ELU等变体,在负半轴给一个很小的斜率,避免死亡问题。我在实际项目里默认用ReLU,如果发现训练后期loss不降了,会换成Leaky ReLU试试。这个切换成本很低,但有时候能救活一个模型。

3.4 批归一化:训练加速器

批归一化是视觉网络里的标配。它在每一层之后把输出重新标准化到均值为0、方差为1的分布,然后再缩放平移。这样做的好处是:允许更大的学习率、减少对初始化的敏感、有一定正则化效果。

我做过对比实验,同一个网络,加批归一化之后训练轮数能减少三分之一,最终准确率还略高。但要注意,批归一化在训练和推理时的行为不一样。训练时用当前批次的均值和方差,推理时用全局统计量。如果推理时忘了切换模式,结果会完全乱掉。这个坑我踩过,排查了半天才发现是模式没切对。

4. 一个完整视觉项目的实操流程

4.1 数据准备:比模型更重要的环节

很多人把精力全花在调网络结构上,但实际项目中,数据质量对最终效果的影响往往更大。我经手过一个图像分类任务,一开始准确率卡在70%上不去,后来花了两天时间清洗数据、剔除标注错误的样本、做数据增强,准确率直接跳到85%。模型一行没改。

数据增强是视觉任务里性价比最高的操作。常用的手段包括:随机裁剪、随机翻转、颜色抖动、旋转、缩放。这些操作等价于凭空造出更多训练样本,能显著降低过拟合。但要注意增强的力度要和任务匹配。比如识别数字的任務,翻转增强就不合适,因为6翻转过来变成9,标签就错了。

提示:数据增强只在训练时做,验证和测试时绝对不能用。我见过有人在验证集上也做随机裁剪,导致每次验证结果都不一样,完全没法比较模型好坏。

4.2 网络结构设计:从简单开始

新手最容易犯的错误是一上来就搭一个很深的网络。我的建议是:先用一个浅层的小网络跑通全流程,确认数据管道没问题、训练能收敛、评估指标合理,然后再逐步加深加宽。

一个典型的入门级卷积网络结构是这样的:卷积层加ReLU加池化,重复两到三次,然后展平接全连接层,最后接输出层。卷积核数量从32开始,每经过一次池化翻倍。这个结构在MNIST和CIFAR-10上都能跑到不错的准确率,而且训练快、容易调试。

4.3 训练配置:学习率和批大小

学习率是最重要的超参数,没有之一。太大导致震荡不收敛,太小导致收敛太慢。常用的起点是0.001,配合Adam优化器。如果训练loss震荡厉害,就降到0.0001。如果loss下降太慢,就升到0.01试试。

批大小影响梯度估计的稳定性。批太小,梯度噪声大,训练不稳定;批太大,显存吃紧,而且泛化性能可能下降。常用的批大小是32、64、128。我的习惯是从64开始,根据显存占用调整。

学习率调度也很关键。固定学习率往往不是最优的,常用的策略是每隔若干轮把学习率乘以0.1,或者用余弦退火。我实测下来,余弦退火在大多数任务上比固定学习率好,最终准确率能高出一到两个点。

4.4 评估与调优:看准指标再动手

训练过程中要同时监控训练集和验证集的loss与准确率。如果训练loss降但验证loss升,说明过拟合了,需要加正则化或数据增强。如果两个都降但验证准确率不涨,可能是模型容量不够,需要加深网络。如果两个都不降,可能是学习率太大或数据有问题。

混淆矩阵是分类任务里非常有用的工具。它能告诉你哪些类别容易被混淆。比如猫和狗的分类器,如果混淆矩阵显示大量猫被预测成狗,那就要针对性地补充猫的样本,或者检查标注是否有问题。只看总体准确率会掩盖很多细节。

5. 那些只有跑过模型才知道的坑

5.1 显存不够用的几种解法

显存溢出是视觉任务里最常见的报错。解法有几个:减小批大小、用更小的输入尺寸、用梯度累积模拟大批次、用混合精度训练。混合精度是我最推荐的方式,它把部分计算转成半精度浮点,显存占用能降三分之一到一半,速度还能提升,而且对最终精度的影响通常很小。

但混合精度有个坑:某些操作在半精度下会溢出。如果发现loss变成NaN,先检查是不是混合精度导致的。解决办法是给这些操作加一个缩放因子,或者把这几层强制转回单精度。

5.2 过拟合的判断和处理

过拟合的典型表现是训练准确率远高于验证准确率。处理手段按优先级排序:第一,加数据增强;第二,加Dropout;第三,加权重衰减;第四,减小模型容量。我一般按这个顺序试,因为数据增强不增加推理成本,Dropout和权重衰减会稍微影响训练速度,减小模型容量是最后手段。

Dropout在卷积网络里的用法和全连接网络不太一样。全连接层后面通常加Dropout,卷积层后面一般不加,因为卷积层参数量少,本身就不容易过拟合。如果非要在卷积层加,Dropout率要设得很低,比如0.1。

5.3 迁移学习:小数据集的救命稻草

如果你的数据集只有几千张图,从零训练一个卷积网络几乎不可能得到好结果。这时候要用迁移学习:拿一个在大数据集上预训练好的网络,把最后的分类层换成你自己的类别数,然后微调。

微调有两种策略:一种是只训练最后的分类层,前面的卷积层冻结;另一种是所有层都参与训练,但用很小的学习率。数据量越少,冻结的层应该越多。我一般先冻结所有卷积层训练几轮,等分类层稳定了再解冻最后几个卷积块一起微调。这样比一上来就全解冻效果更稳。

注意:迁移学习时预处理必须和预训练模型保持一致。比如预训练用的是ImageNet的均值和方差,你也要用同样的值去归一化你的数据。这个细节很容易被忽略,但影响很大。

5.4 常见问题速查表

问题现象可能原因排查方向
loss变成NaN学习率太大、混合精度溢出降低学习率、检查混合精度配置
验证准确率不涨过拟合、数据标注错误加数据增强、检查标注质量
训练速度极慢数据加载瓶颈、批大小太小检查数据管道、增大批大小
显存溢出批太大、输入尺寸太大减小批、用混合精度、梯度累积
推理结果全错训练推理模式未切换检查批归一化和Dropout的模式

6. 从分类到检测:视觉任务的进阶路线

6.1 目标检测的基本思路

分类任务只回答“图里有什么”,检测任务还要回答“在哪里”。主流做法是:在图片上生成大量候选框,对每个框判断类别并回归位置偏移。两阶段方法先筛选候选框再分类,精度高但速度慢;单阶段方法直接在每个位置预测类别和框,速度快但小目标检测稍弱。

我建议的进阶路线是:先把分类做熟,理解卷积特征提取的流程,然后再看检测。检测网络里的骨干部分就是分类网络,只是后面接了额外的预测头。分类没搞明白直接看检测,会觉得很乱。

6.2 数据标注的注意事项

检测任务需要标注边界框,标注质量直接影响模型上限。我见过一个项目,标注框普遍偏大,导致模型学出来的框也偏大,定位精度始终上不去。重新标注之后,同样的网络结构,定位误差降了一半。

标注时要注意:框要贴紧目标边缘,不要留太多空白;遮挡目标要标注可见部分;小目标不要漏标。如果预算允许,至少让两个人独立标注一批数据,计算一致性,一致性低说明标注标准不清晰,需要重新对齐。

6.3 视觉任务的落地考量

实验室里跑出高准确率是一回事,真正部署到生产环境是另一回事。落地时要考虑:推理速度能不能满足业务要求、模型大小能不能塞进目标设备、对光照和角度的鲁棒性够不够、异常输入怎么处理。

我的一般做法是:先训练一个大模型追求精度上限,然后用知识蒸馏或剪枝压缩成小模型,最后在真实场景数据上做一轮微调。这个过程比单纯调网络结构复杂得多,但也是视觉技术真正产生价值的地方。

7. 我个人在实际操作中的几点体会

学计算机视觉这几年,最大的感受是:理论看一遍就够,代码要反复写。卷积的计算过程看视频觉得很简单,但自己手写一遍前向传播和反向传播,才会真正理解每个维度的变化。我建议每个学视觉的人都用numpy手写一个小的卷积网络,不用深,两三层就够,写完再去看框架的API,会有豁然开朗的感觉。

另一个体会是:不要迷信最新架构。每年都有新网络刷新榜单,但很多改进在具体任务上带来的提升很有限。把ResNet这类经典结构吃透,把数据管道和训练流程做扎实,比追新架构的收益大得多。我见过太多人花大量时间复现最新论文,结果数据增强没做对、学习率没调好,最终效果还不如老老实实用ResNet。

最后分享一个小技巧:训练视觉模型时,先把图片尺寸调小跑一遍完整流程,确认代码没问题、loss能降,再放大尺寸正式训练。这样能省下大量等待时间,也更容易定位问题。我早期经常直接上大尺寸,结果跑了一晚上发现数据路径写错了,白白浪费算力。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进