系列:《从小白到 AI 大模型开发工程师的进阶之路》 技术点:AI-0305 特征选择与特征工程 主人公:小蓝伞 前置:AI-0238 本期产出:特征工程记录
小蓝伞给随机标签生成了两千列噪声特征,先挑最相关的十列再交叉验证,分数竟然看起来不错。代码没有报错,错误藏在筛选顺序里。本期固定同一数据和折分,对照全量筛选与折内筛选,追踪验证标签如何进入特征名单。 教学情境用来组织问题,下面的执行数据来自构造实验。
一、先给结论
所有依赖数据的特征筛选应在交叉验证训练折内拟合。用 Pipeline 包住筛选器与估计器,让验证行既不参与统计量,也不影响列名单。最终测试集留到选定流程之后。
二、问题范围与成功判据
本期技术范围是相关性、过滤法、包装法、嵌入法和领域特征。读者应已能运行 Python 并理解训练与验证的区别。
| 项目 | 本期约定 |
|---|---|
| 输入 | 代码中给出的教学样本 |
| 输出 | 可逐项核对的中间量与预测或切分结果 |
| 成功判据 | 运行完成,正常与故障对照符合下文解释 |
| 不作承诺 | 真实业务最佳参数、独立测试成绩和生产性能 |
三、核心机制与容易混淆的概念
过滤法先计算统计量再按分数选列,例如方差、相关系数或单变量检验。它速度快,但单变量显著不代表联合最优,两个单独弱的特征也可能通过交互共同有用。包装法以模型验证结果评价特征子集,计算成本高,也会因尝试太多子集而选择性过拟合。嵌入法在模型拟合过程中选择特征,常见例子是稀疏线性系数和树的重要性。
从两千个噪声特征中挑十个,总能找到与随机标签偶然相关的列。全量筛选把验证标签也用于挑列,所以交叉验证只隔离了最后的分类器,没隔离整个学习过程。表面上没有训练验证行,实际上列名单已编码了这些行的标签信息。
Pipeline 每折重新拟合筛选器,验证行只变换,不参与打分。各折列名单不同是正常现象,说明筛选本来就是学习流程的一部分。稳定性差也值得报告,不能挑其中一折的名单冒充稳定结论。
四、方案选择与反例
方差阈值适合去常量或近常量列,但低方差并不等于没有业务意义。单变量过滤适合建立快速基线;高度相关特征宜先分组检查,避免把冗余当作多份证据。递归消除和顺序选择适合候选数较小、计算预算明确的任务。稀疏回归的系数受缩放和相关列竞争影响,不能把非零系数直接解释为因果变量。
领域特征同样有时间边界。退款后才能知道的退款原因,不能用于下单时预测。先画出预测时刻与字段可用时刻,比先跑一百种选择器更有效。
五、完整实践与运行步骤
本次执行环境为 Windows 11、CPython 3.12.14,NumPy 2.3.5、scikit-learn 1.7.2。输入均由文中代码构造,不读取真实用户或业务数据,不需要下载数据集。
新建空目录,将下方完整代码保存为 main.py。目录仅需这个脚本和安装后的虚拟环境,输入直接写在代码里,结果打印到终端,没有未提供的前置文件。
python -m venv .venv
.venv/Scripts/python.exe -m pip install numpy==2.3.5 scikit-learn==1.7.2 scipy==1.18.1 joblib==1.6.0 threadpoolctl==3.7.0 cloudpickle==3.1.2
.venv/Scripts/python.exe main.py
macOS 或 Linux 将解释器路径换为 .venv/bin/python。安装阶段需要可访问的官方包源或已获授权的离线依赖;运行阶段不访问网络。不要覆盖既有项目环境。删除这个专用练习目录即可清理,示例不改数据库、系统配置或用户文件。
import numpy as np
from sklearn.datasets import make_classification
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import cross_val_score, StratifiedKFold
rng = np.random.default_rng(42)
X = rng.normal(size=(100, 2000))
y = rng.integers(0, 2, 100)
cv = StratifiedKFold(5, shuffle=True, random_state=42)
selected = SelectKBest(f_classif, k=10).fit_transform(X, y)
bad = cross_val_score(LogisticRegression(), selected, y, cv=cv)
good = cross_val_score(make_pipeline(SelectKBest(f_classif,k=10), LogisticRegression()), X, y, cv=cv)
print('global_select', np.round(bad, 3).tolist(), round(float(bad.mean()),3))
print('fold_select', np.round(good, 3).tolist(), round(float(good.mean()),3))
# 标签完全随机,偶然相关不应被解释为可迁移规律。
六、可复现失败案例与修复
演练的错误管道先调用 fit_transform 全部样本,再把缩小的矩阵传给 cross_val_score。小蓝伞最初怀疑随机种子,换种子后仍可能看到较高结果。排查应先找所有 fit、统计聚合和选列动作,再检查它们是否发生在折分之前。把 SelectKBest 放回 Pipeline 后,验证标签不再决定列名单,复验应比较相同折分下的全部分数。
七、实验设计、实际输出与判读
global_select [0.75, 0.85, 0.75, 0.7, 0.85] 0.78
fold_select [0.4, 0.5, 0.65, 0.55, 0.6] 0.54
两个结果都是五折准确率,折分、分类器和选列数量保持一致,唯一变化是筛选器的拟合位置。每折只有 20 个验证样本,分数以 0.05 为步长变化,均值附近的波动并不罕见。随机标签基线只用于反证泄漏,不能作为业务模型的效果承诺,也不能因为一次折内结果高于 0.5 就宣布还有泄漏。
八、迁移到工程时需要补什么
特征工程记录要把生成公式、字段可用时刻、拟合范围和输出列顺序连在一起。上线字段无法取得或计算延迟超标,即便离线有效也无法部署。
选择 k、检验方法或模型正则参数仍属于调参。反复在同一验证结果上搜索会产生乐观偏差,需要留出测试集或外层交叉验证评价整套选择流程。
九、推导、决策与扩展验证
偶然相关为何会被多次尝试放大
一个噪声字段与标签的样本相关性通常不会恰好为零。候选字段越多,极端相关值出现的机会越大,从中挑最大的几个相当于在一次大规模搜索后只展示赢家。两千列与一百行的组合专门放大这个现象。它不需要恶意篡改标签,普通的"先筛一下再评估"就足以制造乐观结果。
特征选择之前做随机标签实验,是检查管道的负对照。正确管道未必每次恰好得到一半准确率,因为有限样本和分类器本来有波动;应结合多次随机标签重跑的分布判断。若只挑其中最低的一次证明安全,仍然在选择结果。负对照也不能证明真实特征在预测时可用,那要靠字段来源审查。
过滤、包装与嵌入怎样公平比较
比较三类方法时,让它们共享外层切分、最终估计器或明确说明估计器差异,并为各自调参配置相近预算。过滤法若只试一个 k,包装法却试上百个子集,验证最优分数并不公平。除评分外,还要记录拟合时间、最终列数、特征生成延迟和缺失率。
相关列常使选择不稳定。两个几乎重复的传感器列轮流入选,不一定代表算法故障,可能是数据无法区分两者贡献。报告可以把它们视为相关组,观察组的选择频率,而非强行指定某一列为唯一原因。若删列的目的是减少线上调用成本,还应检查删除后是否仍需要同一外部服务,列少不必然意味着成本低。
领域特征先写时间契约
"最近三十天购买次数"要定义窗口右端、是否包含本单、退款后如何修订、时区和迟到数据。若离线用最终全量表回填过去状态,可能把事后信息带回预测时刻。应按当时可见的数据快照计算,或者保存可回放的事件时间与入库时间。
标签字段的近亲也值得排查。订单最终状态、结案原因、审核人工备注可能在统计上极强,却在预测时不可用。先列出最强特征及其产生流程,往往比继续提高筛选复杂度更能发现根因。对于无法确认可用时刻的字段,先隔离实验,不能把未确认当成默认可用。
选择稳定性与解释限度
可以在合法训练重采样中统计每列被选中的频率,再看不同子集的验证表现。频率高代表在这套数据扰动与方法下稳定,不代表因果。稀疏回归中相关列相互竞争,树中高基数列有更多分裂机会,互信息估计还受样本量与参数影响,重要性来源必须写清。
单变量检验还隐含适用条件。卡方输入要求非负,连续值随意分箱会改变统计量;方差筛选受单位影响,把金额与比例直接按同一阈值删列没有自然意义。方法名字不能替代输入约束检查,选择器报错应先核对数据范围与类型。
从最终管道交付到回滚
最终训练完成后保存原始字段到输出列的完整映射,而不是只保存十个列号。新增或重排列时,列号可能仍合法却指向不同特征。部署前用固定输入回放,检查特征值、列名、缺失标记与预测,确认服务使用同一版本计算公式。
持续监控保留列的缺失率、范围和来源可用性。如果外部字段停供,不能静默补零并沿用旧分数,应启用事先验证过的降级管道或回滚。不同降级模型需要独立的验证证据,本期随机标签实验不提供这种生产保证。
为什么测试集不能成为最后的筛选器
即使前面的所有交叉验证正确,只要看了测试结果后再删列、换 k 或选模型,测试也加入了选择过程。此时应如实把它称为开发反馈,另取未参与选择的数据确认。没有剩余独立样本时,可以报告局限并使用嵌套验证估计流程效果,但不能把同一份已反复查看的测试重新命名成"最终测试"。
十、实现复核与判断边界
排查先搜索整条流程中所有学习状态,除了模型 fit,还包括相关性表、缺失填充值、类别字典和特征名单。每个状态标注输入行范围,验证行是否进入一目了然。只检查分类器没见过验证标签,会漏掉前面监督筛选已经发生的选择。
实现上让选择器与分类器组成一个可克隆管道,把原始矩阵交给交叉验证,不预先得到全量 selected。记录每折列名单用于诊断稳定性,但最终部署仍由全训练数据按选定规则重新拟合,不能随意挑某折最漂亮的名单上线。
上线验收既检查列数,也检查列名与计算公式。特征从第十列变成第十一列时,保留裸整数索引很危险;输入重排仍可能让模型顺利运行却读取另一字段。遇到某字段停供,使用经过独立验证的降级方案,而不是临时填零后沿用历史分数。
随机标签上较高的验证分数为什么值得警惕?因为标签没有稳定关系可供学习,噪声里挑出的相关列只是在这批样本上碰巧吻合。若验证标签也参与挑列,它就帮助决定了待评估模型能看见哪些输入,已经不能再叫未知答案。
交叉验证并不是给一个固定分类器盖章,它应评价整套建模过程。先选列再验证只评价最后一步,遗漏了最容易过拟合的搜索。折内筛选让每次未知的行保持未知,因此更接近部署时从训练材料选择特征的现实条件。
修好后分数不必恰好一半。有限样本仍有波动,不能用一次略高的结果直接判有泄漏。正确判断需要数据流审查与负对照共同支持,而非要求输出某个理想数字。真实任务还必须继续检查字段是否在预测时刻可用。
十一、系统与库版本差异
本期使用锁定的库版本。在线 stable 文档可能随版本更新,重现旧结果时应同时核对对应版本的 API。不要把缺少依赖、输入不合法和算法效果不佳混成一种报错。
Windows 与其他系统的虚拟环境解释器路径不同,核心数组与评分语义相同。浮点末位可能受线性代数后端影响,应按文中精度比较,不比较完整字符串。随机实验固定了数据种子;真实新数据仍可能得到不同分数,种子不是泛化保证。
十二、验证清单
- 字段在预测时刻可用。
- 筛选器在每个训练折单独拟合。
- 选列数量与方法只在内部验证选择。
- 保存最终列名单与特征计算版本。
十三、五道面试题与追问
-
为何先选列再交叉验证会泄漏?验证标签参与了选择;追问是无监督变换是否也需要隔离拟合。 进一步追问,无监督筛选就能全量拟合吗?它也读取验证分布,通常仍纳入训练边界。
-
相关性为零就没价值吗?非线性关系和交互可能被单变量线性统计遗漏。 进一步追问,单独没关系的两列联合也没用吗?交互关系可能单变量弱而联合强。
-
树的重要性高能证明因果吗?不能,代理变量与高基数偏好都可能影响排序。 进一步追问,相关列置换重要性为何低?其他列可能替代它,不证明整组没有信息。
-
如何比较筛选方案?使用相同切分与评分,评价整个管道而非筛选后的固定矩阵。 进一步追问,各折名单不同是错吗?筛选是学习过程,稳定性需额外报告。
-
为何保留外层测试?内层选方案会对验证结果过拟合,外层用于评价选择过程。 进一步追问,嵌套验证外层要重做什么?重新执行内层选特征和调参流程。
小蓝伞的工程金句
隔离验证集要从第一步统计开始,列名单本身也会泄漏答案。
官方资料与用途
-
scikit-learn 官方主题文档,用于核对本期概念、接口与算法边界。
-
常见错误与数据泄漏,用于核对拟合范围和验证流程。
查阅日期为 2026 年 10 月 30 日。实验固定到 1.7.2;网页的新版本说明不能自动视为本机旧版本实测。
适用边界
随机标签实验只展示监督筛选泄漏。它不评估真实信号的可解释性、因果性或上线稳定性;两千列的稠密矩阵也不代表大规模稀疏文本的内存成本。
本期技术清单与下一期
本期覆盖相关性、过滤法、包装法、嵌入法和领域特征。下一期【第84期】AI-0306「过拟合与欠拟合」,继续沿系列路线推进。可结合本系列前置篇目的训练验证规则复做实验。