矿物分类这事儿,放在地质行业里一直是刚需。岩矿鉴定、矿产资源调查、选矿流程监控,哪一步都离不开对矿物种类的快速判断。传统做法靠人工肉眼加偏光显微镜,一来慢,二来对经验要求极高,同一个薄片换两个人看,结论经常打架。几个做地质信息化项目的朋友聊下来,最集中的痛点就一个:需要一套能自动识别矿物的系统,哪怕做不到专家级,能先粗分类把候选范围缩小,也能把人工工作量往下拉一大截。
我之前做过一版矿物分类系统,用的还是老一套手工设计特征加传统分类器的路子。当时数据集小,争取到几百张标注图,效果勉强能看,但样本稍微换批岩石,准确率就掉得没法用。这次重制版下了决心,整个推翻重来:深度学习模型换成了迁移学习,数据管线重写,推理服务单独拆出来,指标也从单一准确率扩展到精度、召回、混淆分析、推理延迟一套组合拳。这篇文章就把我重制这个系统过程中的设计思路、踩坑经验和实测记录完整梳理一遍,给想入坑图像识别落地项目的同学做个参考。
1. 为什么要重制:旧系统的痛点和重制的目标
1.1 第一代系统卡在哪里
先交代一下老系统的实底。当时用的是"颜色直方图加纹理特征加支持向量机"的组合,放到今天的眼光看,这套路不是不行,但在矿物识别这个场景下被三个问题卡死了。
第一是特征表达能力的上限太低。矿物的鉴别特征本来就不全在颜色和纹理上:解理、断口、光泽、晶型,这些关键判别点在传统手工特征里很难量化。比如黄铁矿和黄铜矿,颜色都是黄亮金属光泽,直方图特征几乎无法区分,但语义上完全是两种矿物。第二是跨数据集泛化能力弱。训练集里以高光白底图为主,一上野外实拍照片,光照、灰尘、共生矿物干扰全来了,准确率直接崩掉。第三是工程维护成本高,特征提取、降维、分类器、阈值调整各个环节耦合在一起,每次加新矿物类别都要跑全流程,迭代极慢。
1.2 重制版的目标定位
重制之前,我先把需求重新理了一遍。当时组织内部的对矿物识别平台的期望是:不是做一个完美的专家系统,而是做成一个"筛子"——从一堆矿物图像里快速锁定最有嫌疑的几个候选类别,再交给人工复核。这一定位决定了设计上的取舍。
- 准确率目标:Top-1准确率做到90%左右,Top-3做到95%以上。人工复核场景下Top-3命中基本够用。
- 类别规模:首批覆盖30类常见矿物(石英类、长石类、云母类、碳酸盐类、硫化物类、氧化物类等)。
- 推理速度:单张图像在CPU下也要控制在2秒内,GPU下100毫秒内。
- 模块化:模型、数据、业务逻辑分开,后续增加类别或换骨干网络不影响系统整体。
这四条目标倒过来,直接决定了我后面的技术选型。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据集构建:矿物数据远比想象中难搞
2.1 原始数据的真实问题
做这个项目之前,我以为找矿物图像数据不难,真做起来才发现到处都是坑。
第一个坑是类别分布极度不均衡。石英、长石这种造岩矿物,公开数据集里能翻出几千张图,但像角闪石、辉石这类暗色矿物,或者石榴子石这种目标明确的矿物,找几百张都费劲。如果直接用原始分布训练,模型会对高频类别特别偏。第二个坑是背景干扰。很多公开图片是博物馆标本,带标签、带底座;野外照片则是整块岩石露头,矿物只占画面一边。本意是识别矿物,模型很可能学的是标签和底座的形状。第三个坑是光照和反光。金属矿物(黄铁矿、磁铁矿)特别容易过曝,透明矿物(方解石、石英)在不同光线下透明度完全不同。
针对这些问题,我在预处理上做了几手准备。先手工清洗掉明显带外文标签、尺寸极不规范的图片,然后把图片统一做主体裁剪加缩放:短边小于256像素的直接丢弃,长宽比大于2比1的视为全景图不入训练集。每张图保留原始RGB三通道,不做灰度化,颜色对矿物识别仍是强信号,尤其是鉴定铁染、风化色时。
2.2 数据增强策略
矿物识别里数据增强不能乱用,这点我要重点强调。通用的增强手段直接搬过来会帮倒忙。比如随机旋转90度、180度,对矿物图片实际上是合理增强,因为矿物没有固定朝向;但水平翻转配垂直翻转要谨慎,有些矿物晶型有对称性,乱翻转会破坏物理特征。我最终确定的增强组合如下:
- 水平翻转、垂直翻转:开启,翻转不改变矿物的本质特征。
- 随机旋转0到180度:开启,角度域扩充。
- 亮度扰动(正负20%)和对比度扰动(正负15%):开启,模拟不同光照条件。
- 饱和度扰动(正负10%):幅度小一些,防止颜色偏移过大。
- 随机缩放裁剪:中心区域裁剪后缩放到统一尺寸,增强尺度和主体位置变化。
- 高斯噪声:轻量加入,提升模型对颗粒噪点的抗性。
有一个增强我始终关着:随机遮挡。矿物图像里主体被遮挡一大块,这类样本对分类没有正面作用,反而会让模型去猜,把信心分数搞乱。
2.3 类别不平衡处理
类别不均衡我用了三层方案,而不是简单粗暴的复制少数类样本。
第一层是过采样比例控制。少数类图片最多复制到多数类均值水平的1.5倍,再高就可能导致记忆这些重复样本。第二层是类别权重进入损失函数,让少数类样本的梯度贡献放大。这里注意别把权重设成"样本数倒数",那样少数类会被过分放大,导致高频类精度下降。我用的权重公式是:类别权重 = 中位数样本数 / 该类别样本数,再整体clip到0.8到3.0之间。第三层是评估指标放弃只看整体准确率,改为按类别记录精确率和召回率,保证每个类都有交代。
补了一版之后,我最关心的是几个"难兄难弟"类别的表现,比如黑云母和普通角闪石,都是暗色、片状或柱状,颜色纹理特别接近。如表所示,重制前和重制后的表现确实拉开了差距。
| 类别 | 重制前召回率 | 重制后召回率 | 说明 |
|---|---|---|---|
| 黑云母 | 72% | 91% | 增强和类别权重都有效 |
| 普通角闪石 | 68% | 88% | 容易与黑云母混淆 |
| 方解石 | 85% | 96% | 透明矿物语义性强 |
| 黄铁矿 | 79% | 93% | 金属光泽经过光照增强后识别改善 |
3. 模型选型与训练细节
3.1 为什么直接上迁移学习
我之前第一版系统从头训练了一个小CNN,结构不小,效果却差强人意。这次重制版学乖了,直接采用迁移学习思路,在ImageNet预训练权重上做微调。
原因并不复杂:矿物图像识别的训练样本量,在不做大规模爬取的情况下,几千张撑破了天。要在这点数据上从头学出强分类器,基本不可能。预训练模型已经掌握了边缘、纹理、形状、组织等通用视觉特征,我们只需要把顶层换成自己的分类头,在矿物数据上微调低层部分参数。这比从零开始训练快得多,也稳定得多。
在骨干网络的选择上,我对比了ResNet50、EfficientNet-B3和ConvNeXt-Tiny三款(这三个都是公开视觉模型,不涉及任何商业产品偏好)。ResNet50相对保守,但成熟稳定;EfficientNet-B3的理论FLOPs更低,理论上推理更快;ConvNeXt-Tiny是较新的结构,微调后精度很有竞争力。最终选了EfficientNet-B3,原因有三:模型文件小,部署友好;推理速度实测在CPU上比ResNet50更快;在接近的真实样本上微调,精度比ResNet50略好,大概高出1.5个百分点。代价是训练时的显存占用稍高一些,但对现有的训练设备来说压力不大。
3.2 输入尺寸与数据管线
输入尺寸定在224x224?我开始也是这么想的,后来发现不行。很多矿物图纹理细节很密集,比如微斜长石的格子双晶,或者方铅矿的解理纹,如果缩得太小,这些高频信息直接被损失函数忽略。实测对比下来,224输入比288输入整整低了2个百分点左右。最终定为288x288,兼顾纹理信息与显存、速度。
数据管线用标准流程:读取图片,解码成RGB,按短边等比缩放至288,中心裁剪,再做在线增强。推理管线里同样保留中心裁剪逻辑,不做乱序随机裁剪。这一点很重要,训练和推理的预处理必须严格一致,少一项都会导致最终指标打折。
3.3 训练配置和超参数
训练阶段我维持以下配置:
- 优化器:AdamW,初始学习率3e-4。
- 批次大小:32,显存不够时降到24,但不要降到16以下,过低会引发BN统计量漂移。
- 学习率调度:余弦退火,加上5个epoch的线性预热的组合方案。先线性升到3e-4,再余弦衰减到1e-5。
- 总训练轮数:50个epoch。最好在验证集loss开始反弹前停止。
- 损失函数:标签平滑交叉熵,平滑系数0.1。标签平滑能有效降低模型在难样本上的过度自信,对后续置信度阈值判断有益。
- 数据划分:训练集70%、验证集15%、测试集15%,按类别分层切分。
- 冻结策略:前10个epoch冻结骨干网络底层,只训练分类头;从第11个epoch起解冻全部层,用1e-4的学习率全参数微调。这种两段式训练能显著提高稳定性,防止前期梯度把预训练权重冲乱。
训练日志里我最关注的指标是验证集每类召回率的中位数(这个指标更能反映模型综合能力)以及Top-3准确率(贴合业务需求)。单个验证集整体准确率波动很容易被多数类别拉平,只有看分项召回率和混淆矩阵才靠谱。
4. 训练调优与性能验证
4.1 训练曲线与收敛观察
训练到第20个epoch左右,模型在验证集上的表现逐步稳定。最终测试集上的成绩是:Top-1准确率91.7%,Top-3准确率96.4%,加权平均F1分数0.89。这个结果在线上验证中基本达到了预期,但要提防的一点是:模型在训练集上的准确率早已接近满分,而验证集还有1到2个点差距。这说明模型存在一定程度的过拟合倾向。缓解方式是在网络结构里加入了一个全局平均池化层后的Dropout层,比率0.3。加了之后验证集分数提升不算明显,但模型对光照变化的稳定性确实好了一些。
4.2 混淆矩阵的针对性分析
调优过程中最有价值的输出之一,是把测试集的混淆矩阵打出来逐项排查。我发现在30类矿物中,混淆主要集中在这几组:
- 黑云母 / 普通角闪石:这两类颜色极深的光泽矿物,形状和解理容易混淆。
- 白云母 / 滑石:片状硅酸盐,颜色发白,莫氏硬度不同但视觉上很接近。
- 石英 / 长石类:尤其是肉红色钾长石和无色石英在普通画质下非常难区分。
针对这些容易混淆的组合,我增加了专门的"难例对"采样——从训练集中找出被模型预测错但真实标签属于这两个类别的图片,做标注修正后放回训练集,再配合在验证集上做细粒度评估。这种难例挖掘策略比单纯增加整体图片数量更快见效。
4.3 置信度阈值与业务侧判断
模型输出的softmax概率不能直接当业务置信度用。我单独做了置信度校准实验,发现模型预测概率普遍偏高。比如模型对某张图输出0.92的概率,实际准确率并没有那么高。为保险起见,我用验证集做了一个等距阈值搜索,最终把业务侧的判定阈值定为0.6。高于0.6就直接展示该矿物类别;0.35到0.6之间展示Top-3候选列表并提示"需人工复核";低于0.35则干脆不展示矿物类别,返回"无法识别或样本质量不足"。这样的三段式输出比单纯的单选更符合实际工作流程,也更容易获得使用者的信任。
5. 系统落地:从模型到可用服务
5.1 推理服务的设计取舍
重制版系统一个比较大的架构变化是:模型不再嵌在一个单体脚本里,而是独立成一个推理服务。
我用的方案是模型服务化,对外提供统一图像分类接口,只收图片,返回类别标签、置信度、候选列表。这个服务内部负责图像预处理、模型推理、结果后处理,业务系统只通过标准交互协议调用。这么拆的好处是显而易见的:模型更新不影响业务逻辑;不同业务线可以共用同一个推理能力;推理服务可以独立水平扩展,应对高并发识别需求。
5.2 前后端交互流程
前端是一个简单的图像上传界面,用户上传矿物照片后,页面会先做一次本地预览,然后调用后端接口。后端收到图片后先做基本校验(格式、大小、尺寸),再转发给推理服务。整个链路的具体流程是:
- 用户上传矿物图片。
- 后端服务接收图片,校验非空、格式为常见图片格式且大小在10MB以内。
- 图片转码为RGB,按288x288中心裁剪,归一化后通过内部接口送入推理服务。
- 推理服务执行模型推理,产出Top-5结果。
- 后端根据置信度规则决定展示方式,并把结果写入数据库日志。
- 前端拿到结果,展示主分类、候选列表和置信度。
架构上需要额外考虑一个细节:上传图片的类型会多样化,移动端拍的照片经常带着横向拍摄导致旋转角度异常。处理策略是读取图片的EXIF旋转信息并在进入模型前先做物理旋转。如果忽略这个环节,界面展示正常但模型推理看到的图其实是倒着的,效果会很差。
5.3 模型更新与灰度发布
机器学习系统的维护不只发生在训练期,上线之后更需要一套版本控制机制。我做了一个模型文件仓库,每个版本记录对应的训练日期、训练集版本、验证集指标和已知问题。发布新模型时不直接全量替换,而是先让新模型跑一段时间的"影子模式",即新模型和线上模型同时推理,比较两者结果,观察是否有大面积不一致。一致率高且新模型在某些类别的表现明显提升,再切换到正式环境。这类流程不一定需要很重的平台支撑,一个版本表加一个开关配置就能跑起来。
6. 实际运行中的问题排查和典型场景处理
6.1 推理结果不稳定怎么办
有用户反馈,同一张图偶尔会识别出两种不同矿物。排查发现是推理管线的预处理存在随机性:训练时保留了随机旋转增强,但推理阶段没有关闭随机开关。这种问题如果代码模块偶合就会非常隐蔽。修复方法也简单,推理管线里彻底去掉随机性操作,固定一个随机种子,保证同一个输入永远输出同一个结果。这一点在线下图像识别类应用里极其重要,业务方对"同一张图不同结果"的容忍度几乎为零。
6.2 野外照片的“主角不居中”问题
真实使用场景里,用户拍的照片往往是整块岩石,目标矿物只占很小一块区域。我的系统一开始应对这种场景很差,经常把背景的岩石误当作矿物。处理方案在算法层面和产品层面同时进行。算法层面增加一个基于目标检测的预处理前置模块,先框出矿物所在区域,再做分类。这个额外模块初期并没有上,后来单独训练了一个检测模型,用标注框标注矿物位置,分类只在框内进行。产品层面则在界面里增加了一个"请将矿物置于画面中央"的提示,两张手段叠加后,野外场景的准确性大幅提升。
6.3 推理速度优化
推理延迟方面,我采取了一个很朴素但有效的优化:批处理。单张请求往往是毫秒级问题,但如果把多张请求合并成一个批次送进GPU,吞吐量能提升不少。实际实现时在服务里加了一个队列,每隔一定时间或攒够一定数量后统一推理。另外还用了混合精度推理,在显卡支持的情况下显存占用直接减半,速度也提升了约30%。CPU纯算力不足时,建议模型量化到8位整数精度,实测Top-1准确率会掉0.5到1个百分点,但延迟可以压到300毫秒以内,适合边缘部署场景。
6.4 光照过曝与过暗的处理手段
金属矿物反光强,野外拍摄时要么反光成白白一片,要么阴影里黑成一团。这类图像进入模型前需要做一次自适应直方图均衡化,把过曝和过暗区域的信息拉回来。我最初没用这步,导致磁铁矿召回率一直上不去,加了之后效果立竿见影。不过要注意不要太激进,增强过度会引入色偏,尤其对区分赤铁矿和磁铁矿这类依赖颜色特征的分类场景影响明显。
6.5 模型更新后老样本识别退化的处理
有一次换新版本模型后,测试集整体分数提高了,但老版本里几个识别正确的样本反而识别错了。这种"跷跷板"现象在真实系统里不可避免。我的应对方式是建立一份"黄金样本集",专门收集每次迭代中出现分歧的样本,比如新老版本结果不一致、人工复核标记错误的样本。每轮训练后都在这套样本集上做回归测试,发现退化的类别立即排查原因。这套流程滚了几轮以后,系统的整体稳定性明显提高,大方向向下漂移的问题基本控制住了。
7. 几个值得重点留意的实操细节
7.1 标签噪声处理
矿物数据集的标注本身带有较强的噪声。同一块标本有人标"石英",有人标"石英岩",还有可能有人在备注里写"玉髓"。做归属讨论时不细看,直接当独立类别就会把数据集搞得支离破碎。我处理的原则是:类别颗粒度以"识别任务需求"为准,不是越细越好。名称上保留学名,但把亚种归并到主类别。训练时同时记录每张图的可信度标记,低可信度的样本在计算损失时权重减半。
7.2 参数保存和随机性问题
再次强调,深度学习的训练是带随机性的,同样的数据和代码,换一台机器跑两遍结果就不完全一致,尤其是在多卡分布式环境下。所以每次实验结果必须固定随机种子并记录环境详情,便于问题回溯。矿物分类系统的实验周期短,一次全训练大约几小时,完全有条件做多次重复实验取均值,这样拿到的指标更可靠,不至于被某一次随机跑偏误导。
7.3 部署思维要提前
模型训练完之后,我才发现推理环境的库版本和训练环境对不上,临时折腾了一会儿。建议在项目一开始就确定部署目标:是用容器部署还是用裸机进程,推理框架选哪个版本,CPU还是GPU。如果是CPU部署,模型输入就不要选太大的尺寸,骨干网络也不要选太重的那类。如果是GPU部署,则要提前确认驱动版本和容器镜像里的计算库是否匹配。这个决定越早做,后面踩的坑越少。
8. 知识扩展:从分类到识别层级的更多可能
8.1 层次化分类思路
矿物的分类天然具有层级结构:大类(硅酸盐、氧化物、硫化物、碳酸盐)到具体种属(如长石族下的钾长石、斜长石)。单模型直接输出30个平铺类别,训练样本不充足时,混淆问题会被放大。可以考虑的一个优化方向是使用两阶段模型:先训练一个低层大类分类器,再在每个大类下训练一个种属分类器。好处是每个模型的分类任务更简单,负样本更集中;代价是推理时可能要走两遍,延迟增加。实测下来,两阶段方案在特定困难类别上可以再提升3到5个点的召回率。
8.2 多模态融合前景
矿物识别不能只看可见光图像,如果能引入光谱信息,比如短波红外或拉曼光谱数据,对很多疑难矿物(如碳酸盐族内部、黏土矿物族的区分)会有质的提升。这类多模态数据在实验室里比较常见,但在野外移动端采集成本较高。目前的主流折中方案还是视觉为主、光谱为辅,先做同步数据采集,再慢慢积累标注数据。未来条件成熟后,把光谱特征和图像特征在特征层做融合,会很有价值。
8.3 模型蒸馏的轻量化路径
系统要在资源受限设备上跑,除了量化,还有一个方向是模型蒸馏。用一个精度高的教师模型去指导参数量更小的学生模型学习,学生模型输出尽量模仿教师模型的类别分布。我在实验中发现,学生模型体积可以缩小到教师模型的四分之一,Top-1准确率只掉0.8个百分点,但推理延迟仅为原来的三分之一左右。这个路线对边缘端部署非常实用,虽然蒸馏训练过程会多一些调试成本,但收益是落地的灵活度。
9. 写在最后的一些个人体会
矿物分类系统这个项目,做下来最大的感受是:机器学习项目最花时间的环节不在模型结构,而在数据、评估和工程落地的闭环。旧系统的问题不是某个模型不好,而是数据定义不清晰、评估只看整体准确率、模型与业务逻辑揉在一起。重制版把所有环节都拆开了,数据有独立的清洗流程,评估有分项指标,模型服务化之后可以单独升级。这套思路不仅适用于矿物分类,任何垂直领域的图像识别项目都通用。
再分享一个实操小技巧:如果刚开始做类似项目,不要急着追新模型、试新框架,不如先把数据管线做到极致,把混淆矩阵看到烂熟,再用最经典的骨干网络搭一套基线模型出来。有了稳定的基线,后面任何改进都有了比较基准,不会越调越乱。我对现在这套系统的评价是"够用、稳、可扩展",下一轮如果数据量翻倍,我会直接补训练数据并把类别列表扩到60类,然后继续保持模块结构不变,只替换模型文件重新验证。这个项目的乐趣也就在这里:每一次数据变多、每一轮模型迭代,都能看到系统的上限被抬高一截。
