
玻璃这东西看着都是一个样透明、冰凉、敲起来叮当响。可一旦把它丢进回收产线、质检工位或者材料溯源流程里它到底是建筑门窗用的浮法玻璃、汽车车窗玻璃、瓶子罐子、餐具器皿还是车灯罩判定错一步后面整批料的去向就全错了。我最近把“根据玻璃的特征对玻璃进行类别判定”这件事从头到尾跑了一遍用的不是摄像头加视觉模型而是一张纯数值表一个折射率加上七八种氧化物的质量百分比。听起来有点朴素但成分数据的好处是它不讲情面谁含镁多、谁含钾多、谁掺了钡数字摆在那儿。这篇我把整个判定流程摊开讲清楚七类玻璃靠什么区分、九个特征各自在说什么、清洗和构造特征时哪些动作是必需的、模型怎么选、类别极不均衡时评估口径怎么定以及误判高发区在哪、怎么排查。适合刚接触成分分类的读者照着复现也适合已经跑通过一遍但模型指标卡住的人对照找问题。1. 先想清楚要判什么七类玻璃与成分特征的对应关系1.1 判定目标不是“玻璃长什么样”而是“它走的是哪条工艺路线”刚拿到这个题目的人第一反应往往是往图像方向走拍张照、做个纹理分析、跑个卷积网络。我试过这条路很快放弃了。玻璃在成品状态下视觉差异极小同一批瓶子换个模具就长得完全不一样靠外观特征去区分“浮法玻璃”和“非浮法玻璃”基本是缘木求鱼。真正有判别力的信息藏在化学成分里因为每一类玻璃的配方是被工艺要求和经济性硬约束住的配方差异会直接体现在氧化物比例上。所以我们判定的对象不是玻璃的外观而是它的工艺身份与用途身份。业内常用的一个玻璃成分样本集大致覆盖七类建筑用浮法玻璃、建筑用非浮法玻璃、车辆用浮法玻璃、车辆用非浮法玻璃、容器玻璃瓶罐类、餐具玻璃、车头灯玻璃。这七类两两之间不是等距的——“建筑浮法”和“建筑非浮法”的差异主要来自成型方式“建筑浮法”和“车辆浮法”的差异则来自原料纯度与铁含量控制。搞清楚这种“层级式”的类别结构比闷头调参重要得多。我个人的判断是这个任务的本质是一个小样本、多类别、类别不均衡、特征量纲不一致的表格分类问题。四个定语缺一不可后面所有的方案选择都得围绕这四条来谁忽略了其中一条模型上线就会在某一类上系统性翻车。1.2 九个特征各自在说什么为什么它们有判别力特征一共九个折射率 RI以及 SiO₂、Na₂O、MgO、Al₂O₃、K₂O、CaO、BaO、Fe₂O₃ 八种氧化物的质量百分比。单看名字很枯燥但每一个背后都对应着明确的配方逻辑理解了这层逻辑后面构造特征和解释误判才有依据。特征在玻璃里的角色主要的判别指向RI折射率成分的综合光学结果与密度、CaO、BaO、PbO 正相关整体“密度档位”辅助区分容器玻璃与建筑玻璃SiO₂网络形成体占七成左右是骨架本身判别力弱主要看它被谁挤占Na₂O助熔剂降低熔化温度容器玻璃、餐具玻璃偏高浮法玻璃控制得较稳MgO由白云石引入浮法工艺常用浮法与非浮法的核心分水岭非浮法常见接近 0Al₂O₃提高化学稳定性与耐水性非浮法、容器玻璃偏高浮法玻璃一般压得较低K₂O助熔兼“拉长料性”餐具玻璃的最强信号手工成型需要更长的操作窗口CaO稳定剂来源是石灰石容器玻璃偏高与 RI 联动明显BaO提高折射率与光泽车头灯玻璃几乎绕不开的标记其他类多为 0Fe₂O₃杂质着色控制严格度反映原料档次车辆浮法玻璃压得最低非浮法偏高把这张表读透你会发现一个有意思的事实最强的判别信号不是绝对值而是“有还是没有”。MgO 是不是接近零、BaO 是不是存在这两个近乎二值的判断就能把七类里的一大半先劈开。很多人在这一步就直接把原始特征喂给模型然后抱怨准确率上不去问题往往是他没把这种“存在性”显式表达出来。1.3 数据到手的第一件事把类别分布摊在桌面上看我见过太多人拿到数据先head()看一眼就开跑模型跑到最后发现某一类的召回率是 0回头一查才知道那一类总共只有个位数样本。这个数据集的类别分布非常不客气占比最大的一类接近总量的三分之一而最小的两类样本数只有个位数到十几个最大类与最小类的比例超过 7:1。这件事的连锁反应很直接如果按普通随机划分测试集里最小类可能只有一两个样本一次误判就让该类召回率直接砍半指标波动大到没法做决策。所以我在动手建模前一定会做三件事——统计每类样本数与占比、检查每类的特征分布是否有明显离群、确认最小类在分层抽样后每折还剩几个样本。如果分层后某折里某类只剩一个样本那这一折的交叉验证结果就没有参考价值得考虑合并或改用其他验证策略。提示类别数多于五个且最小类样本不足二十条时先别急着建模先确定评估口径和验证策略。这两件事定不下来后面所有指标都是幻觉。2. 特征工程把材料常识翻译成模型能吃的数字2.1 三个必须先做的清洗动作清洗这一步没什么炫技空间但漏掉任何一项都会在后面放大成难以解释的误差。我固定做三件事。第一件是量纲与量级检查。RI 在 1.5 附近SiO₂ 在 70 附近Fe₂O₃ 常常是 0.05 这种量级如果直接用距离类模型KNN、SVM 的 RBF 核SiO₂ 会单方面主导距离计算Fe₂O₃ 的信息几乎被抹平。我的做法是先看每列的取值范围凡是最大值与最小值跨两个数量级以上的都列入待缩放名单。第二件是零值与缺失的区分。这个数据集里 0 不代表缺失它代表“这个成分没有刻意添加”或者“低于检出限”。比如某类玻璃的 BaO 列大量为 0那是真实的配方事实绝对不能拿均值或中位数去填。我一般会统计每列 0 值的比例如果某列 0 值占比超过六成就把它当作“存在性标记”来对待而不是连续变量。第三件是重复样本与近重复样本排查。成分数据容易因为录入或取样重复出现完全相同的行。我习惯先按全部特征做一次完全去重再用四舍五入到两位小数后的特征做一次近似去重看看能压缩掉多少行。这一步不做交叉验证时同一条样本可能同时出现在训练折和验证折里指标会虚高上线后一落千丈。2.2 用比值和差值把“隐含的判别规则”显式化原始九个特征喂进去树模型大概能跑到七八十分的水平但想再往上走靠的是把化学常识变成新列。我实测下来最有效的几个构造方向都是围绕前面那张表里的“分水岭”来的。第一个是镁铝关系。浮法玻璃用白云石引入 MgO同时会把 Al₂O₃ 压得比较低非浮法玻璃往往不用或少用白云石MgO 接近零而 Al₂O₃ 因为原料和配方原因偏高。所以MgO / (Al₂O₃ 0.01)这个比值在两类之间的分离度明显好于两个原始特征中的任何一个。加 0.01 是为了避免除零这个技巧在处理成分比时很常用。第二个是钡的存在性编码。BaO 大于某个阈值我实测下来 0.5 附近比较稳就置 1否则置 0。这一列几乎就是车头灯玻璃的专用开关构造出来之后决策树的第一层分裂往往会直接用它模型的解释性也跟着变好。第三个是钾钠比与钾的绝对量。餐具玻璃因为要手工成型、需要较长的料性K₂O 明显偏高。单看 K₂O 已经有效但K₂O / (Na₂O 0.01)在面对容器玻璃Na₂O 高、K₂O 低时能进一步拉开差距。第四个是折射率与钙的联合项。RI 与 CaO 有较强的正相关两者一起看能稳住“密度档位”这个维度。我用的是RI × CaO这样一个粗糙的交互项虽然不优雅但在小样本下比让模型自己学交互更可靠。第五个是铁的相对水平。Fe₂O₃ 绝对值很小但在车辆浮法玻璃里被压到极低在非浮法玻璃里明显偏高。我对这一列做了一次符号对数变换log1p(x * 100)把它从挤在零点附近的一堆数拉开成一个有区分度的连续变量实测对提升车辆类的识别率有直接帮助。import numpy as np import pandas as pd def build_features(df: pd.DataFrame) - pd.DataFrame: out df.copy() # 镁铝比浮法 vs 非浮法的主分割线 out[mgo_al2o3_ratio] out[MgO] / (out[Al2O3] 0.01) # 钡存在性车头灯玻璃的开关 out[has_bao] (out[BaO] 0.5).astype(int) # 钾钠比餐具玻璃的助推项 out[k_na_ratio] out[K2O] / (out[Na2O] 0.01) # 折射率与钙的交互稳住密度档位 out[ri_x_cao] out[RI] * out[CaO] # 铁的相对水平拉开被压缩的小量级 out[fe_scaled] np.log1p(out[Fe2O3] * 100) return out2.3 缩放策略的选择要看模型不要一刀切缩放这件事最容易被做成机械动作无脑StandardScaler全表来一遍。我踩过的坑是这样的——树模型对单调变换完全不敏感缩放与否结果几乎一样但对逻辑回归、SVM、KNN 这些依赖距离或系数的模型缩放方式的选择会直接影响结果。我的实际做法是按模型分管道。树模型管道里只做特征构造不做缩放线性模型和距离模型管道里做标准化。另外有一点值得单独说成分百分比这种数据各列加起来接近 100存在天然的成分约束compositional constraint严格来说应该做对数比变换CLR 或 ILR再进线性模型。我在这个数据集上试过 CLR效果比直接标准化略好但提升幅度有限考虑到实现复杂度和可解释性最终没有采用。如果你追求极致指标可以试如果追求可维护标准化就够用。注意任何缩放、变换、编码都必须放进 Pipeline 里在交叉验证内部拟合。先在全量数据上 fit 再划分是最经典的数据泄漏指标能虚高好几个点。3. 模型选型与训练从基线到真正可用的分类器3.1 先跑一个逻辑回归当“体温计”不管最后上什么模型我都会先跑一个带多项式的逻辑回归当基线。原因很实在它快、稳定、系数可解释而且能立刻告诉我这批特征到底有没有可分性。如果逻辑回归的 macro-F1 只有 0.3那说明特征构造出了问题这时候直接上复杂模型只是在给错误特征做非线性拟合。我的基线配置是特征构造后的全部列 标准化 多项逻辑回归正则强度用交叉验证在小网格里搜。小样本下C的搜索范围不要太宽我一般在 0.01 到 10 之间取对数网格再宽就是在拟合噪声。基线结果给我的信息比分数本身更多。系数一看就知道MgO 相关特征和 BaO 存在性这两组的权重最高符合预期而 SiO₂ 的系数几乎在零附近晃说明它在这个任务里确实是陪跑项。这个判断在后面做特征筛选时帮了大忙。3.2 树模型与小样本的博弈我实测的四种方案对比这个数据集样本量只有两百多条属于典型的“特征不算多、样本更不多”的场景。我拿四种模型在同一套分层交叉验证下做了对照结果差异比想象中大。模型关键配置macro-F1多次重复均值特点与坑多项逻辑回归标准化 L2C 网格搜索中等偏上稳定、可解释线性边界对车辆两类区分吃力随机森林树数 500最小叶样本 1class_weightbalanced较高对特征构造敏感最小叶样本设 2 以上会明显压掉小类梯度提升学习率 0.05深度 3迭代 300较高但不稳小类样本太少时训练波动大不同随机种子结果差异明显KNNk5距离加权标准化后欧氏距离中等对缩放极其敏感k 值稍大就吃掉小类我的结论是随机森林是这个小样本场景里性价比最高的选择不需要太多调参就能给出稳定结果而且特征重要性可以直接拿来和化学常识对账。梯度提升虽然上限可能略高但在样本量不足三百、最小类只有个位数的情况下种子敏感性太强做出来的模型没法给别人复现。KNN 不是不能用但它的表现高度依赖缩放方式和 k 值可调空间小适合作为对照而不是主力。至于“最小叶样本”这个参数我要单独强调。它的默认值通常是 1很多人为了“防过拟合”随手改成 3 或 5在这个数据集上这等于直接告诉模型“不要学小类”。我实测把最小叶样本从 1 提到 5最小那一类的召回率会掉得很夸张。小样本多分类里这个参数要往小调防过拟合应该交给样本权重和正则而不是砍掉叶子。from sklearn.ensemble import RandomForestClassifier from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import StratifiedKFold, cross_val_score pipe Pipeline([ (scaler, StandardScaler()), (clf, RandomForestClassifier( n_estimators500, min_samples_leaf1, max_featuressqrt, class_weightbalanced, random_state42, n_jobs-1, )), ]) cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(pipe, X, y, cvcv, scoringf1_macro) print(scores.mean(), scores.std())3.3 类别不平衡别把 SMOTE 当万能药看到 7:1 的不均衡比例很多人的第一反应是上过采样。我在这里的建议是先试类权重再试采样最后才考虑合成样本。理由是样本量太小了。最小类只有个位数样本SMOTE 在这么少的点上做插值生成出来的“新样本”基本是在原样本之间连线上取点等于把原本就很窄的分布进一步压缩模型学到的是一个更窄的、不真实的决策区域。我实测过在最小类上做 SMOTE训练集指标很漂亮验证集上小类召回率反而下降因为它把边界推得过于激进。类权重class_weightbalanced是更稳的第一步它只是改变损失函数的加权不改变样本分布风险小得多。如果类权重还不够可以考虑对多数类做欠采样把最大类的样本数压到次大类的 1.5 倍左右配合类权重一起用。我在最终方案里用的就是“类权重 轻微欠采样”没有引入任何合成样本。注意任何重采样操作都必须在交叉验证的训练折内部完成不能在划分前对整个数据集做。否则验证折里混入了由验证样本合成的点评估结果完全失真。3.4 评估口径为什么我更信 macro-F1 而不是准确率这个数据集上一个把所有样本都预测成最大类的“傻子模型”准确率就能到三成以上。所以准确率在这里几乎没有决策价值。我用的主指标是macro-F1它对每个类一视同仁小类判错会被放大惩罚正好对应我真正关心的东西——不能因为某一类样本少就放弃它。辅助指标我还会看两样每类的召回率和混淆矩阵的对角线分布。召回率能告诉我哪一类在系统性漏判混淆矩阵能告诉我错判流向了哪里是“浮法判成非浮法”还是“建筑判成车辆”这两种错误的业务含义完全不同前者说明工艺判别特征没抓到后者说明纯度相关特征不够。还有一个实操细节小样本下单次交叉验证的方差很大我固定做五次不同随机种子的分层五折报均值和标准差。如果标准差超过均值的三成说明结果不可靠得回头看验证策略而不是继续调参。4. 误判排查实录错在哪里为什么错4.1 混淆矩阵暴露出的三个高发区跑完一轮之后我把多次交叉验证的混淆矩阵累加起来看误判并不是均匀分布的而是集中在三个区域而且每一个都能对应到具体的化学原因。第一个高发区是建筑浮法与建筑非浮法之间。这两类的错判量占全部误判的相当大一部分。原因不复杂它们的用途相同配方的大方向接近差异主要来自成型工艺带来的 MgO 与 Al₂O₃ 差别。当某个样本的 MgO 正好落在中间地带比如 1% 到 2% 之间单靠这一个特征就判不准必须结合 Al₂O₃ 和 RI 一起看。第二个高发区是建筑浮法与车辆浮法之间。这两类都是浮法工艺配方骨架几乎一样差别在于原料纯度和铁含量控制。这是全任务里最难的一段我实测即使加了铁的对数变换这两类的区分度依然有限。现实一点的预期是把它们分到“浮法”这个大层没问题细到建筑还是车辆能到八成左右就算不错。第三个高发区是容器玻璃与餐具玻璃之间。这两类都含较高的 Na₂O 和 CaO区别在 K₂O。餐具玻璃的 K₂O 明显更高但如果某条样本的 K₂O 落在中间值就容易被判反。钾钠比这个构造特征在这块起了明显作用加进去之后这一对的混淆量下降了不少。4.2 从一条具体误判反推特征归因举个我印象比较深的例子。有一条真实类别是“非浮法建筑玻璃”的样本被模型高置信度判成了“浮法建筑玻璃”。我把它的特征拉出来和两类的均值对比发现它的 MgO 并不接近零反而有一个不算低的值同时 Al₂O₃ 也偏低——这两项组合起来从化学上讲确实更像浮法配方。这条样本给我的启发是“非浮法玻璃的 MgO 一定接近零”这个经验规则是有例外的。某些非浮法工艺也会引入含镁原料只是比例和来源不同。所以构造特征时用“MgO 是否为零”这种二值判断会误伤用比值镁铝比更稳因为它同时纳入了 Al₂O₃ 的信息容错空间更大。这也是我后来把has_mgo这种硬编码标记从特征集里删掉、换成连续比值的原因。类似的复盘我还做过几条车头灯玻璃的样本。有一条 BaO 不为零但数值偏低模型没有给出足够置信度。查下来发现它的 BaO 确实在阈值边缘而我的存在性编码用的是硬阈值 0.5。后来我把这一列改成“BaO 原值 存在性编码”两列都保留让模型有得选比单一硬阈值稳。4.3 常见问题速查表下面这张表是我在实际调试中反复用到的排查清单按现象倒查原因基本能覆盖八成的卡点。现象最可能的原因处理方式整体准确率还行但某一类召回率为 0该类样本太少被多数类淹没加类权重检查该类的分层抽样是否生效交叉验证分数很高换一批数据就崩缩放或采样在划分前做了数据泄漏全部变换塞进 Pipeline在折内拟合加了新特征后指标反而下降新特征与已有特征高度共线或引入了噪声做相关性检查构造特征后重新做一次特征筛选不同随机种子结果差异巨大样本量小 模型方差大固定多次重复交叉验证报均值与标准差浮法与非浮法总是互相错判只用了 MgO 单一特征没用比值加入镁铝比结合 Al₂O₃ 与 RI 一起判断车辆两类几乎分不开铁含量特征被量级压缩模型没抓到对 Fe₂O₃ 做对数变换检查缩放是否正确KNN 表现忽好忽坏未标准化或 k 值过大吃掉小类强制标准化k 取 3 到 7 之间小范围搜索4.4 上线前我会坚持的几条纪律最后分享几条我个人在这个任务上吃过亏之后定下来的规矩不一定适合所有场景但在这个数据集上确实管用。第一特征构造的每一列都要能说出化学理由。我曾经为了刷分堆了十几列多项式交互交叉验证分数涨了两个点但换一批样本直接崩。后来我把所有构造特征列出来凡是说不出“它对应哪个成分关系”的全部删掉。留下来的五列每一个都有明确的判据支撑模型的可解释性和泛化性同时变好了。第二结论要分层给不要只给一个类别标签。因为浮法内部的两类确实难分我在实际交付时会把输出组织成两层第一层判“浮法 / 非浮法 / 容器 / 餐具 / 车灯”第二层在浮法内部再判建筑还是车辆并且对第二层给出置信度。置信度低的样本走人工复核比强行给一个高置信度错判有用得多。第三永远保留一个“未知”出口。这个数据集的七类是封闭的但真实场景里一定会有配方超出这七类的样本。我在最终方案里加了一条规则如果模型的最大类别概率低于某个阈值我实测下来 0.45 附近比较合适就输出“待定”。这条规则让模型在封闭测试集上的指标略微下降但在真实数据上的可用性提升非常明显——宁可说不知道也不要自信地判错。第四把特征分布监控做起来。成分数据的漂移往往是渐进的某段时间原料来源变了MgO 的整体水平会慢慢移动模型不会立刻崩但准确率会一点点掉。我习惯定期统计几个关键特征的分位数一旦某个特征的中位数偏移超过历史波动范围就触发重训检查。这个动作比盯着准确率曲线更早发现问题。最后再补一个我觉得最容易被忽略的点这个任务里最值钱的东西不是模型是那张“特征—类别”的对应表。模型可以换、参数可以调但只要那张表理解到位了换任何算法都能跑出可用结果反过来表没读透堆再复杂的模型也只是在拟合噪声。我在实际使用中的体会是花在理解 MgO、K₂O、BaO 这三列上的时间回报远高于花在调参上的时间。