计算机视觉是近年来发展最快的AI领域之一,从图像分类到目标检测,从语义分割到人脸识别,各种新模型新方法层出不穷。我做计算机视觉也有几年了,在这个过程中踩了很多坑,有些问题甚至让我熬了好几个通宵才解决。本文记录了我在计算机视觉项目中遇到的一些典型问题和解决过程,包括数据问题、模型问题、训练问题、部署问题等。如果你也在做计算机视觉相关的项目,希望这篇文章能帮你少踩一些坑。

一、入门:从图像分类开始

先说说我是怎么进入计算机视觉这个领域的吧。

大概四年前,公司有一个图像分类的需求,需要把用户上传的图片自动分类成不同的类别。那时候深度学习刚刚开始火起来,CNN(卷积神经网络)在图像分类任务上取得了很好的效果。领导让我调研一下,看看能不能用深度学习来解决这个问题。

那时候我对深度学习还不太了解,只知道一些基本的概念。于是我开始看论文,看教程,学TensorFlow,学PyTorch。花了大概一个月的时间,终于用ResNet跑通了第一个图像分类模型,准确率还不错。

从那以后,我就开始做计算机视觉相关的项目了。这几年做过图像分类、目标检测、语义分割、人脸识别、OCR等各种任务,用过各种模型和框架。在这个过程中,踩了无数的坑,也学到了很多东西。

下面就分享一些让我印象深刻的踩坑经历。

二、数据篇:垃圾进垃圾出

计算机视觉项目中,数据是最重要的部分。很多时候模型效果不好,不是模型的问题,而是数据的问题。

坑1:标注质量差

这是我遇到的第一个大坑。

刚开始做目标检测项目的时候,我们找了一批外包人员来标注数据。为了赶进度,我们没有太仔细地检查标注质量,只是随机抽查了一部分,觉得差不多就开始训练了。

结果训练出来的模型效果很差,mAP(平均精度均值)只有30%多。我们以为是模型的问题,换了好几个模型,调了各种超参数,效果都不好。

后来我们仔细检查了标注数据,才发现问题所在。标注质量太差了。有的目标框只框了一半,有的目标框偏移了很多,有的类别标错了,有的目标漏标了。尤其是一些小目标和遮挡目标,标注质量更是惨不忍睹。

我们花了整整一周的时间,重新检查和修正了所有的标注数据。然后重新训练模型,mAP直接从30%多提升到了70%多。

这件事给了我深刻的教训。数据是模型的基础,标注质量直接决定了模型的上限。标注质量差,再强的模型也没用。后来我们做项目,都会花大量的时间在数据标注和质量检查上,这是最值得投入的地方。

坑2:数据分布不均

第二个坑是数据分布不均。

有一次做一个多分类的图像分类项目,我们收集了大概10万张图片。但是不同类别的图片数量差异很大,多的类别有几万张,少的类别只有几百张。

刚开始我们没有处理这个问题,直接用原始数据训练。结果模型对多数类别的准确率很高,但是对少数类别的准确率很低,几乎识别不出来。

我们尝试了几种方法来解决这个问题。第一种是过采样少数类,就是把少数类别的图片重复采样,让每个类别的数量差不多。第二种是欠采样多数类,就是从多数类别中随机抽取一部分,让每个类别的数量差不多。第三种是用带权重的损失函数,给少数类别更大的权重。

最后我们发现,过采样加上数据增强的效果最好。过采样让类别平衡了,数据增强(随机裁剪、翻转、颜色抖动等)增加了少数类别的多样性,防止过拟合。

数据分布不均是计算机视觉项目中很常见的问题,尤其是在真实场景中收集的数据。一定要重视这个问题,否则模型会偏向多数类别,对少数类别的效果很差。

坑3:数据增强过度

数据增强是提升模型效果的常用手段,但是用得不好也会出问题。

有一次为了提升模型的泛化能力,我用了很激进的数据增强。随机裁剪、随机翻转、随机旋转、颜色抖动、高斯噪声、模糊,各种增强都用上了,而且参数设置得很大。

结果训练的时候损失降不下去,模型效果很差。我检查了很久才发现,是数据增强太激进了,把图片改得面目全非,模型根本学不到有用的特征。比如旋转角度太大,目标都转出图片了。颜色抖动太厉害,颜色信息都失真了。

后来我把数据增强调得温和了一些,只保留了常用的几种,参数也调小了。模型的效果立刻就好了起来。

数据增强是一把双刃剑,用得好可以提升模型效果,用得不好反而会损害模型效果。要根据具体的任务和数据来选择合适的增强方式和参数,不要盲目地堆增强。

坑4:测试集和训练集泄漏

还有一个坑是测试集和训练集泄漏。

有一次我们做一个项目,模型在测试集上的准确率很高,有95%。但是上线之后,实际效果很差,准确率只有70%多。

我们排查了很久,最后发现是数据集划分有问题。我们是随机划分的训练集和测试集,但是同一个场景的图片可能同时出现在训练集和测试集中。因为同一个场景的图片很相似,模型其实是"记住"了这些场景,而不是真正学会了识别目标。

后来我们按照场景来划分数据集,同一个场景的图片要么全在训练集,要么全在测试集。这样划分之后,测试集的准确率降到了75%左右,但是和线上的实际效果一致了。

数据集的划分非常重要,一定要保证训练集和测试集是独立的,没有信息泄漏。否则测试集的指标没有参考价值,会误导你对模型效果的判断。

三、模型篇:没有最好的模型,只有最合适的

模型的选择和调优也是一个大坑。

坑5:盲目追求新模型

刚入行的时候,我特别喜欢追新模型。哪个模型新就用哪个,哪个模型在论文里效果好就用哪个。

有一次目标检测领域出了一个新模型,论文里说mAP比之前的模型高了好几个点。我立刻就把项目中的模型换成了这个新模型。结果训练了好几天,效果还不如之前的老模型。而且新模型的结构很复杂,训练速度慢,推理速度也慢,部署起来很麻烦。

后来我才明白,论文里的效果是在特定的数据集上取得的,不一定适合你的数据和任务。而且新模型往往有很多坑,实现细节不明确,复现起来很困难。

现在我选择模型的原则是:先从简单成熟的模型开始,比如ResNet、YOLO、Faster R-CNN这些经典模型。这些模型文档多,社区活跃,坑都被踩过了,用起来比较稳定。如果经典模型的效果不够,再考虑尝试新模型。

而且模型的效果不是唯一的考量因素。训练速度、推理速度、内存占用、部署难度,这些都是需要考虑的。在实际项目中,一个效果稍差但是速度快、易部署的模型,往往比一个效果好但是慢、难部署的模型更实用。

坑6:预训练权重不匹配

用预训练模型是计算机视觉的常规操作,可以大大加快训练速度,提升模型效果。但是预训练权重也有坑。

有一次我用了一个在ImageNet上预训练的模型权重,但是我自己的数据集是灰度图像,只有一个通道。我直接把预训练权重加载进去了,结果训练的时候损失不下降,模型效果很差。

排查了很久才发现,预训练模型的第一层卷积是3个输入通道的(对应RGB图像),而我的数据是1个通道的(灰度图像)。通道数不匹配,预训练权重加载有问题,模型的第一层相当于随机初始化的,学起来很慢。

解决方法是把灰度图像复制成3个通道,或者修改模型的第一层卷积,把输入通道改成1,然后只加载后面层的预训练权重。

还有一次,我用了一个预训练权重,但是输入图像的尺寸和预训练时的尺寸不一样。虽然卷积网络可以处理不同尺寸的输入,但是如果尺寸差异太大,效果也会受影响。后来我把输入图像resize到和预训练时差不多的尺寸,效果就好了很多。

用预训练权重的时候,一定要注意输入通道、输入尺寸、归一化方式这些细节,确保和预训练时一致,否则效果会大打折扣。

坑7:模型太大过拟合

模型不是越大越好。

有一次做一个小数据集的图像分类任务,只有几千张图片。我用了一个很大的模型,ResNet-152,结果训练集准确率很高,接近100%,但是验证集准确率只有60%多,明显过拟合了。

后来我换成了比较小的模型,ResNet-18,并且加了正则化(Dropout、权重衰减),验证集准确率反而提升到了80%多。

大模型的参数量多,表达能力强,但是在小数据集上很容易过拟合。小数据集适合用小模型,或者用大模型但是加强正则化和数据增强。

选择模型大小的时候,要根据数据集的大小来定。大数据集可以用大模型,小数据集要用小模型或者加正则化。不要盲目追求大模型,大模型不一定好。

四、训练篇:训练是一门艺术

模型训练是最让人头疼的环节,各种问题层出不穷。

坑8:学习率设置不当

学习率是训练中最重要的超参数,也是最容易出问题的。

有一次训练一个模型,损失一直不下降。我检查了数据,检查了模型,都没有问题。最后发现是学习率设置得太大了,导致模型在最优点附近震荡,无法收敛。把学习率调小之后,损失立刻就降下去了。

还有一次,学习率设置得太小了,损失下降得非常慢,训练了好几天还没收敛。把学习率调大之后,很快就收敛了。

学习率的设置真的很关键。太大了模型发散或者震荡,太小了模型收敛太慢。我的经验是,先用默认的学习率(比如0.001或者0.01)试一下,看看损失的变化情况。如果损失震荡或者上升,就把学习率调小。如果损失下降很慢,就把学习率调大。

现在有很多自动找学习率的方法,比如学习率查找器(Learning Rate Finder),可以快速找到合适的学习率。也可以用学习率调度器,比如余弦退火、warmup等,让学习率在训练过程中动态调整,效果比固定学习率好。

坑9:Batch Size太小

Batch Size对训练效果也有很大的影响。

有一次因为显存不够,我把Batch Size设得很小,只有2。结果训练的时候损失波动很大,模型效果也不好。后来我用了梯度累积(Gradient Accumulation),把等效的Batch Size提高到了16,模型效果就好了很多。

Batch Size太小的话,每个batch的数据不能很好地代表整体数据分布,梯度估计的噪声很大,训练不稳定。尤其是有Batch Normalization的模型,Batch Size太小的话,BN的均值和方差估计不准,会严重影响模型效果。

一般来说,Batch Size大一些训练更稳定,效果也更好。但是受限于显存,Batch Size不能太大。这时候可以用梯度累积来模拟大Batch Size,或者用Group Normalization代替Batch Normalization,GN对Batch Size不敏感。

坑10:不收敛的各种原因

模型训练不收敛是最让人崩溃的问题,原因有很多,需要一个个排查。

我遇到过的不收敛的原因包括:学习率太大、数据有问题(标注错误、数据归一化不对)、模型结构有问题(比如激活函数用错了、损失函数写错了)、梯度消失或者梯度爆炸、初始化不对等等。

有一次我调了好几天,模型就是不收敛。最后发现是损失函数写错了,把交叉熵损失写成了均方误差损失,而且标签的格式也不对。改过来之后立刻就收敛了。

还有一次模型不收敛,排查了很久,发现是数据归一化的问题。我用了ImageNet的均值和方差来归一化自己的数据,但是我的数据和ImageNet的数据分布差异很大,归一化之后数据范围不对,模型学不到东西。后来用自己数据集的均值和方差来归一化,就好了。

训练不收敛的时候,要系统化地排查。先检查数据(数据是否正确加载、标签是否正确、归一化是否正确),再检查模型(结构是否正确、前向传播是否正确),再检查训练配置(学习率、损失函数、优化器),一步步缩小范围,找到问题所在。

坑11:过拟合和欠拟合

过拟合和欠拟合是训练中最常见的问题。

过拟合就是训练集效果好,验证集效果差。解决方法包括:增加数据、数据增强、正则化(Dropout、权重衰减)、用更小的模型、早停(Early Stopping)等。

欠拟合就是训练集和验证集效果都不好。解决方法包括:用更大的模型、减少正则化、训练更久、调大学习率、检查数据是否有问题等。

判断过拟合还是欠拟合,要看训练集和验证集的曲线。如果训练集准确率持续上升,验证集准确率先升后降,就是过拟合。如果训练集和验证集准确率都很低,而且上升缓慢,就是欠拟合。

我一般会同时画训练集和验证集的损失和准确率曲线,根据曲线来判断问题,然后采取对应的措施。不要盲目地调参,要先搞清楚问题是什么。

五、部署篇:训练好只是开始

模型训练好之后,部署上线又是一个新的坑。

坑12:训练和推理的预处理不一致

这是最常见的部署问题之一。

有一次我们训练了一个图像分类模型,在测试集上准确率很高。但是部署到线上之后,准确率下降了很多。排查了很久,发现是预处理不一致的问题。

训练的时候,我们用的是PIL库来读取和处理图片,resize的方式是双线性插值,归一化用的是ImageNet的均值和方差。但是部署的时候,工程同学用的是OpenCV来读取图片,resize的方式是最近邻插值,归一化也忘了做。

预处理不一致,导致模型的输入和训练的时候不一样,效果自然就差了。后来我们统一了预处理的方式,线上的准确率就恢复了。

这个问题非常常见,因为训练和部署往往是不同的人做的,用的库和方法可能不一样。一定要确保训练和推理的预处理完全一致,包括图片读取方式、resize方式、颜色空间转换、归一化方式等。最好是把预处理的代码封装成一个统一的函数,训练和部署都用同一个函数。

坑13:模型推理速度慢

模型训练好了,但是推理速度太慢,满足不了线上的性能要求,这也是一个常见问题。

有一次我们训练了一个很复杂的目标检测模型,准确率很高,但是单张图片的推理时间要500毫秒,线上要求是100毫秒以内,完全不达标。

我们尝试了很多方法来加速。首先是用更轻量的模型,把大模型换成了小模型,准确率稍微降了一点,但是推理速度快了很多。然后是模型量化,把FP32的模型量化成INT8,推理速度又快了一倍,准确率损失很小。最后是用TensorRT来优化模型,TensorRT对模型做了层融合、内核优化等,推理速度又提升了不少。

经过这些优化,推理时间从500毫秒降到了50毫秒,满足了线上的要求。

模型加速的方法有很多,包括:用更轻量的模型结构、模型剪枝、模型量化、知识蒸馏、用推理优化引擎(TensorRT、ONNX Runtime、OpenVINO等)。可以根据实际情况选择合适的方法,在准确率和速度之间做权衡。

坑14:显存和内存不足

部署的时候还会遇到显存和内存不足的问题。

有一次我们把模型部署到一台只有4G显存的GPU服务器上,结果模型加载不进去,直接OOM了。因为训练的时候用的是16G显存的GPU,模型比较大,没有考虑到部署环境的显存限制。

解决方法是优化模型的显存占用。可以用更小的batch size,用更轻量的模型,用模型量化,或者用CPU来推理(如果对速度要求不高的话)。

还有一次是内存不足。模型本身不大,但是处理图片的时候,图片解码和预处理占用了太多内存,导致OOM。解决方法是流式处理图片,不要一次性把所有图片都加载到内存里,处理完一张释放一张。

部署的时候一定要考虑目标环境的资源限制,包括显存、内存、CPU等。在训练的时候就要考虑到部署的需求,不要训练一个部署不了的模型。

六、一些通用的建议

踩了这么多坑之后,我总结了一些通用的建议。

1. 先跑通baseline

做项目的时候,不要一开始就用最复杂的模型和最花哨的技巧。先用最简单的方法跑通一个baseline,看看效果如何。有了baseline之后,再一步步优化,每一步都做对比实验,确认每个改动是否有效。

如果一开始就用很复杂的方法,出了问题很难排查是哪里的问题。有了baseline,就有了参照物,可以清楚地看到每个改动带来的影响。

2. 可视化是最好的调试工具

计算机视觉项目中,可视化是最好的调试工具。

要经常可视化训练数据,看看数据加载是否正确,标注是否正确,数据增强之后是什么样子。要可视化模型的预测结果,看看模型在哪里预测对了,在哪里预测错了,错误的样本有什么特点。要可视化特征图,看看模型学到了什么特征。

很多问题通过可视化一眼就能看出来,比看日志和指标高效得多。养成可视化的习惯,可以帮你快速定位和解决问题。

3. 做好实验记录

做深度学习实验,一定要做好记录。每次实验用了什么模型、什么参数、什么数据、得到了什么结果,都要记录下来。

否则实验做了很多,但是记不清哪个参数对应哪个结果,最后一团糟。可以用实验管理工具(比如Weights & Biases、MLflow、TensorBoard等)来记录实验,也可以用简单的表格来记录。

有了完整的实验记录,才能分析哪些改动有效,哪些没用,找到最优的方案。

4. 不要忽视工程能力

很多做算法的人只关注模型和算法,忽视了工程能力。但是在实际项目中,工程能力非常重要。

数据处理、模型训练、模型部署、性能优化,这些都需要扎实的工程能力。代码写得好,实验才能复现,模型才能顺利部署。工程能力差的话,算法再厉害也发挥不出来。

所以要重视工程能力的培养,写好代码,做好版本管理,熟悉各种工具和框架。算法和工程并重,才能成为一个优秀的计算机视觉工程师。

七、写在最后

计算机视觉是一个充满挑战也充满乐趣的领域。每一个项目都会遇到各种各样的问题,每一个问题的解决都需要付出很多努力,甚至熬好几个通宵。但是当你终于解决了问题,看到模型达到了预期的效果,那种成就感是无法用语言形容的。

这几年踩了很多坑,但是也成长了很多。从一个连CNN都搞不懂的新手,到现在能够独立完成各种计算机视觉项目,这些坑功不可没。每一个坑都是一次学习的机会,每一次问题的解决都让我对这个领域有了更深的理解。

本文分享的这些坑,只是我遇到的问题中的一小部分。计算机视觉领域还有很多很多坑,每个人的项目不同,遇到的问题也不同。但是解决问题的思路是相通的:系统化地分析,一步步地排查,不断地尝试和总结。

希望我的这些踩坑经历能给正在做计算机视觉的你一些帮助,让你少走一些弯路,少熬一些夜。也欢迎大家在评论区分享自己的踩坑经历,一起交流,一起进步。

最后用一句话结束本文:"在计算机视觉的道路上,每一个坑都是通往更高水平的垫脚石。"愿每一个CV工程师都能在踩坑中不断成长,做出更好的模型和产品。