去年我参加了一个挺有挑战性的数据挖掘竞赛B题要求做岩石样本智能识别。题目给了几千张偏光显微镜下的岩石薄片图像让模型自动判断每张图像属于哪类岩石。一开始我们以为是纯地质问题后来才发现落到算法上这就是细粒度图像分类。团队折腾了将近三个星期最后拿到了优秀作品奖。很多朋友私信问我是怎么做的这篇就把完整思路、模型选型、训练细节和踩过的坑一并写出来给想入门图像分类竞赛或者琢磨地质图像识别的人做个参考。1. 先看透题目表面是地质本质是图像分类1.1 题目到底在考什么这个B题的核心很直接输入一张岩石薄片在偏光显微镜下的照片输出它的岩性类别。听起来像是地质学专业的任务但作为参赛者我拿到数据的第一反应是这其实是一个标准的监督图像分类问题。图像本身是RGB图片类别标签是若干个岩石种类数量不多不少大概有十来个类别。特征非常依赖于矿物颗粒的形态、颜色、纹理和边界关系而这些在图像上都能看到。第一周我们组在讨论时产生过分歧有人想尝试传统的颜色直方图加纹理算子也有人想直接上卷积神经网络。后来我们把两种思路都跑了一遍基线发现传统特征在天差地别的图像光照下非常不稳定准确率只有六成左右而一个预训练的ResNet轻轻松松就能跑到八成以上。这次对比让我印象深刻也直接确定了后续的技术路线深度学习做特征提取配合针对性的数据清洗和增强而不是在地质学特征工程上空耗时间。1.2 为什么深度学习在这个场景里有优势岩石薄片图像和自然图像不太一样。它不像ImageNet里的猫狗那样有清晰的物体边界而是由大量矿物晶体区域随机交织在一起类间差异可能非常细微类内又因为偏振光角度、切片厚度不同而差异很大。传统特征往往只捕捉到全局颜色分布或局部纹理统计一旦光照变化特征分布就会漂移。深度学习模型尤其是卷积神经网络可以通过端到端训练自动筛选出有判别性的局部模式比如特定矿物的解理纹、干涉色、颗粒轮廓等。这些模式很难用手工设计但网络在足够数据下能自己学会。不过也要泼一盆冷水深度学习在岩石识别上并不是无脑堆模型。数据集通常只有几千张类别也不平衡直接从头训练一个深层网络很容易过拟合。我们最终采用的方案是“预训练模型 针对性数据增强 模型集成”这也是好几个优秀队伍共同的经验路径。核心思路是借助ImageNet上学到的通用边缘纹理特征作为起点再用岩片数据微调让模型把注意力从通用物体转向矿物结构。1.3 整体技术路线图我们组最后把整个流程拆成了四段数据清洗与图像预处理、模型选择与训练策略、结果分析与错误排查、工程化补充实验。第一段花的时间最多大概占了一半因为岩石图像质量参差不齐很多图像存在黑边、模糊、重复和标签错乱直接喂给模型就是灾难。第二段主要是对比不同backbone、不同损失函数和训练策略。第三段通过混淆矩阵和热力图定位模型的薄弱类。第四段是为了让方案能真正落地做了轻量化和推理速度测试。这里想强调一个观点竞赛成绩并不完全取决于模型结构是否新颖反而更依赖你是否把数据打扫干净、把评估指标吃透、把训练过程调稳。很多队伍一开始就追求最前沿的模型结果数据里的脏标签把模型带偏后面怎么调都拉不回来。我们队伍能从B题里拿到优秀作品很大程度上是因为在前期数据上花了笨功夫。2. 数据清洗与增强一半时间花在模型之外2.1 原始数据分析与清洗官方给的数据解压之后每个类别一个文件夹图片大小不统一大部分是几千乘几千的大图也有一部分是已经裁剪好的小块。我们写脚本统计了尺寸分布发现训练集里有些图片分辨率超过3000像素如果直接缩放到224分辨率很多细小矿物颗粒会糊掉。但如果用原图训练又太吃显存。最后采取了两级方案基础实验用中心区域缩放最终方案用滑窗切patch后训练推理时再对多个patch的结果做平均。这样既保留了细节又不会让单个样本的显存开销爆炸。清洗时最耗时的不是去黑边而是查脏标签。我们抽了每类大概30张图人工过了一遍发现有两类存在明显标错的情况比如某张图从颜色和结构上更像另一类却被归在当前类别里。对这类样本我们没有直接删除而是细看之后做了人工修正。竞赛规则一般不允许修改标签但作为研究项目我们做的预处理本质是“数据矫正”。如果你不想动标签至少要保证混淆矩阵里出现的异常不是脏标签造成的否则会误导后续分析。另外还做了一个很关键的步骤去重。同一张薄片可能因为拍摄位置微调出现多张近似图像甚至完全相同。如果训练集和验证集之间有这种重复图像验证分数会虚高最终测试时会跌得很惨。这里我们用了感知哈希算法对不同目录下的图片两两计算相似度把重复样本标记出来并保证同一组重复样本只出现在同一个数据划分中。2.2 类别不平衡与训练集划分原始数据里类别分布极不均匀最多的一个类有近千张最少的一类只有几十张。刚开始用普通交叉熵训练模型完全被大类带跑小类别F1惨不忍睹。我们用了三种方法缓解不平衡第一是类别加权采样让每个batch里出现小类的概率更高第二是在损失函数层面给小类更大的权重但权重不能拍脑袋设我们是按类别样本数的倒数开根号来设的第三是离线复制小类样本配合数据增强。最终效果最好的组合是类别加权采样加带label smoothing的交叉熵而不是上来就用Focal Loss。数据划分上我们没有直接用官方给的验证集而是按“薄片来源”而不是“图像文件”来分层划分。因为同一块岩石薄片的多张图像高度相关如果这些图像同时出现在训练和验证里会无意中提高验证分数。这种数据泄漏在竞赛里很常见也很隐蔽。我们的做法是先在文件命名里提取薄片编号然后保证同一个编号的图像全部进入同一个fold这样验证集才能反映真实泛化能力。2.3 数据增强策略岩石薄片图像有一个特点旋转不变性很强因为矿物颗粒没有固定的“正立方向”。所以旋转增强在我们实验里收益非常明显。我们用Albumentations库做了随机旋转90度、随机水平垂直翻转、随机裁剪缩放、ColorJitter中的亮度和对比度微调、以及少量的高斯噪声。其中颜色相关增强要谨慎因为偏光下干涉色虽然变化大但过于剧烈的色相偏移会破坏岩性判别的核心特征比如某些矿物在正交偏光下特有的干涉色等级。我们最终公开的增强参数大概是随机裁剪Scale在0.7到1.0之间旋转角全角度亮度系数0.8到1.2对比度0.9到1.1水平翻转和垂直翻转都以0.5的概率开启。训练时还用了MixUpalpha设为0.2。MixUp对岩石这种细粒度图像没有那么明显的提升但会提升一点鲁棒性尤其在类别边界模糊的时候。推理阶段我们做了TTA把同一张图做水平翻转和90度旋转各一次对五个预测结果取平均准确率大约能提升一到两个百分点代价是推理时间变为原来的五倍。这个要在提交时间允许的情况下才用。3. 模型选型与训练细节把迁移学习用到极致3.1 Backbone选择实验模型选择上我们一共试了ResNet50、EfficientNet-B4、ConvNeXt-Tiny和Swin Transformer-Tiny。最开始用ResNet50是因为它稳定、显存友好、社区资料多适合做baseline。它的Top-1准确率在验证集上大约87%已经比传统方法好出一大截。EfficientNet-B4准确率到了89.2%参数和计算量略大但可以接受。ConvNeXt-Tiny和Swin-Tiny在小数据集上的表现没有预想中那么惊艳Swin-Tiny甚至因为数据量不够稳定验证集波动很大。最终正式提交的模型不是单模型而是三个模型的集成两个不同输入分辨率下的EfficientNet-B4加上一个加了更强的随机擦除的ResNet50。集成方式很简单对三个模型输出的softmax概率取平均。这个集成比单模型提升了大约两个点验证集宏平均F1从0.89左右提升到0.91。事实上在细粒度分类任务里集成不同结构的模型比单纯调大单个模型更划算尤其是数据量有限时多个模型的偏差可以互相弥补。如果你想复现完全可以从两个不同分辨率的EfficientNet开始不必一上来就堆五六个模型。3.2 迁移学习与输入分辨率这里要单独说一下为什么迁移学习这么关键。岩石薄片图像和自然图像从像素分布上完全是两回事但卷积网络的前几层学习的是通用特征比如边缘、角点、颜色梯度这些特征在不同领域之间是可以迁移的。我们使用ImageNet预训练权重训练时先将backbone所有层解冻但初始学习率开得很低前几个epoch只更新分类头再全量微调。输入分辨率也是一个重要杠杆。我们对比了224和384分辨率384分辨率在EfficientNet-B4上比224高了1.5个点左右因为岩石颗粒的细节信息更多。分辨率越高训练和推理速度直线下降所以最终只在集成模型中的其中一个EfficientNet上使用384分辨率另一个使用256兼顾精度和速度。如果你显存不足可以考虑先用224跑通流程最后再用更大的分辨率微调几个epoch这样不会让训练时间爆炸。3.3 损失函数与训练配置损失函数我们用过普通交叉熵、带label smoothing的交叉熵、Focal Loss。普通交叉熵在类别不平衡时问题明显小类很容易被忽略。label smoothing设0.1之后虽然训练准确率略降但验证集F1更稳不容易在某个类别上过于自信地犯错。Focal Loss在我们这个小数据集上并没有发挥优势gamma设为2时反而让训练收敛变慢这可能是因为岩石图像类别重叠度高硬样本太多导致模型被带偏。训练配置我们推荐如下优化器用AdamW权重衰减设1e-4初始学习率3e-5batch size为32训练20到30个epochs。使用CosineAnnealingLR作为学习率调度器并搭配前3个epoch的线性warmup。warmup非常重要直接从预训练权重微调时如果初始学习率太高很容易在第一个epoch就把预训练特征破坏掉。另一个细节是在最后几个epoch关闭MixUp否则模型预测的置信度会被平滑得偏低影响TTA后处理。训练过程我们记录了每一轮的验证集宏平均F1而不是只看准确率。因为类别不平衡场景下准确率容易被大类覆盖F1更能反映小类的表现。模型保存规则是每次验证F1创新高就存一份checkpoint最后再对所有checkpoint做滑动平均。这种做法的效果类似EMA能让最终权重更平滑泛化更好。4. 实验对比与结果分析用混淆矩阵找问题4.1 评估指标的选择比赛一般会看准确率但我们在研究过程中主要看宏平均F1。原因很简单类别样本量差异大时准确率虚高即使某个小众类别完全没识别出来准确率可能也有八成以上。宏平均F1会平等对待每个类别小类表现差就会被拉下来。另外我们也关注了Cohen的Kappa系数它考虑了随机一致性的因素在类别分布不均时比准确率更稳妥。最终提交验证集上单模型EfficientNet-B4384分辨率、带TTA宏平均F1达到了0.894准确率0.926。三个模型集成后宏平均F1提升到0.912准确率0.939。对于岩石样本这种类间差异细腻的任务这个成绩在竞赛里算是很有竞争力的。如果你想判断自己的实验是否靠谱建议至少看两个维度一是验证集宏平均F1二是混淆矩阵对角线的平均占比。前者衡量综合能力后者能准确暴露哪几类互相纠缠。4.2 多组关键对比实验下面这张表是我们验证集上比较重要的几组对比结果。需要说明的是为了防止偶然性每次实验都用了固定的随机种子并且在同一个fold上评测。模型配置分辨率增强方式宏平均F1准确率ResNet50baseline224基础旋转翻转0.8620.901EfficientNet-B4256基础颜色抖动0.8840.918EfficientNet-B4384全套增强TTA0.8940.926ConvNeXt-Tiny224全套增强0.8730.908Swin-Tiny224全套增强0.8680.904三模型集成混合全套增强TTA0.9120.939从表里能读出两个信息第一输入分辨率带来的提升是明显的单纯把EfficientNet-B4的分辨率从256提到384F1提高了1个点第二Swin和ConvNeXt这类较新的结构在小数据集上没有发挥出预期优势反而因为缺少卷积的归纳偏置需要更多数据去拟合。所以如果你刚入门别盲目追新架构先把ResNet和EfficientNet跑透性价比更高。4.3 可视化与错误案例分析我们画了测试集上的混淆矩阵发现主要错误集中在三类颜色接近、矿物结构相似的岩石之间。比如某两类片岩放大后都呈现明显的条带状构造肉眼也很难分清。针对这个问题我们做了两个补救一是对这几类采集了部分hard example在增强时特意加入更高强度的随机裁剪让模型更多看到局部纹理二是用Grad-CAM画出每个模型的注意力热力图发现模型有时把注意力集中在图像边缘的黑色背景上而不是矿物区域。这个发现促使我们在预处理中加入了中心裁剪并去掉了非岩石区域。Grad-CAM还有一个用处它让我们发现某些模型对小类别图像的关注区域太散。这时我们会把该类别的损失权重稍微调高并配合强增强重新训练让模型“被迫”聚焦于有判别性的局部结构。这种基于错误分析的反向调整比单纯堆模型参数有效得多。我在很多项目里都验证过同一个规律先可视化再改数据最后才调模型。5. 训练过程中的常见问题与排查思路5.1 验证集不升反降过拟合怎么处理我们第一次训练时训练准确率很快到了95%以上但验证集一直卡在85%上下典型的过拟合。当时的增强只有随机翻转和裁剪模型容量太大把训练集里的噪声也记住了。解决办法分三步第一步增强强度拉满尤其是随机裁剪比例和颜色抖动第二步在分类头前加一个Dropout层概率设为0.3第三步把Batch Size从16提到32。Batch Size变大之后BN统计更稳定反而起到了正则化作用。如果你遇到验证集完全不动先检查增强有没有生效很多人会在训练循环里忘了调用增强函数。这种低级错误我们确实也犯过写代码时留意一下就行。5.2 Loss直接变成NaN训练崩溃怎么办训练到某个epoch时Loss突然变成NaN这种情况多半是学习率太大或者MixUp过程中标签计算出现类型错误。我们遇到过一次排查后确认是MixUp的alpha系数设置成了0.8导致混合后的图像过于模糊网络输出极端置信度梯度爆炸。解决办法很简单把学习率从1e-4降到3e-5同时把alpha降回0.2。如果你用的是AdamW而且加了warmup一般不会出现NaN。如果还崩就检查数据标准化是否生效有些预训练模型要求特定的mean和std忘了做归一化会让初始loss就非常离谱。5.3 小类别几乎识别不出来怎么办小类别样本少模型就是不认识。第一个有效操作是类别加权采样我们写了一个WeightedRandomSampler让每个类别的采样概率反比于样本数量。第二个操作是给这个小类单独做离线增强比如弹性形变和随机仿射从几十张扩到两百多张。第三个操作是不要在训练一开始就把小类样本重复采样太多否则模型会过拟合到那几十张图像上。我们最终的做法是采样权重设置一个上限最多不超过大类权重的五倍这样既保证小类能出现又不会过度记忆。5.4 隐性数据泄漏比想象中更常见很多队伍到交结果前才发现分数虚高原因就是数据泄漏。岩石薄片图像通常是从同一块岩石不同视野拍摄的同一薄片的图像之间背景和矿物分布高度相似。如果划分训练集和验证集时没有把同一薄片放在同一边验证集里就会混入训练集的“近亲”。我们重新按薄片编号分组后验证集F1掉了大概两个点这才是真实水平。排查方法也很简单用感知哈希把训练集和验证集做相似度检索如果找到重复或近似图像就要重新划分数据。6. 从竞赛方案到实际项目落地6.1 真实场景里的泛化挑战竞赛数据集往往是经过筛选的理想样本但实际项目里岩石图像会来自不同显微镜、不同实验室、不同放大倍数。我们后来在本地一个模拟项目里测试过竞赛模型发现当图像色彩分布完全不同时模型准确率掉得很厉害。这说明迁移学习虽然强大但对抗“域偏移”的能力有限。解决思路有两种一是收集少量目标域数据做微调哪怕只有几十张都能明显缓解颜色偏移问题二是在预处理阶段加入色彩归一化让不同光源下的图像先映射到同一个标准色域再喂给模型。这个思路是从地质学的标准薄片色彩规范里来的实际效果很好。6.2 模型轻量化与部署竞赛里可以堆大模型但实际部署时要考虑速度。我们把EfficientNet-B4蒸馏到一个MobileNetV3-Large结构上并用TensorRT做INT8量化。实验结果显示剪掉原始模型的最后一个blockF1只下降不到一个点但推理速度提升了接近一倍。如果是嵌入式设备建议优先考虑MobileNetV3或ShuffleNetV2配合ONNX导出可以很方便地接入Python推理服务或者移动端框架。轻量化过程中最需要注意的是BN层的融合很多框架导出时不会自动融合BN导致推理结果出现轻微差异手动融合后精度会恢复。6.3 从分类到辅助决策岩石样本智能识别真正有落地价值的场景不只是给一张图打个标签而是帮助地质人员快速圈定感兴趣的区域。比如我们可以在模型输出类别的同时用Grad-CAM把注意力区域叠回原图辅助专家判断模型依据的是哪些矿物颗粒。模型算力有限时也可以先用一个轻量模型做粗筛把明显有把握的样本自动归类把不确定样本推送给人工复核。这种“人机协同”的模式在专业领域更容易被接受也降低了对模型置信度的苛刻要求。我们最后在项目报告中加入了这样的交互逻辑评委普遍反馈很实用。最后再分享一个让我印象很深的教训比赛结束复盘时我们三个人都承认一开始犯了同一个毛病急着跑模型没先翻数据。后来花了整整两天清洗和可视化数据才把整个项目拉回正轨。很多时候深度学习的瓶颈不在模型而在你对数据有没有建立直觉。如果你也想做岩石图像识别或者类似细粒度分类我建议第一周不要写任何训练代码先去看图像、统计分布、找脏数据这比调参重要得多。另一个建议是每一步实验都要做好记录包括随机种子、增强参数、模型权重路径否则拿到一个提升效果你都复现不出来最后只能重训白白浪费大量时间。
阅读完成 · 觉得有帮助?