3、特征工程中的血缘:特征提取、特征选择、特征组合的血缘记录
特征工程,说白了就是「造特征」的过程。但造出来的特征从哪来、怎么变、跟原始数据是什么关系——这些信息如果不记录,后面出了问题你根本没法查。
我在项目中遇到过好几次这样的情况:模型上线后效果突然变差,排查了半天,最后发现是某个衍生特征的计算逻辑被人改过,但没有任何记录。嗯,从那以后,我养成了一个习惯——所有特征操作都必须留下血缘记录。
3.1 特征提取的血缘记录
特征提取,就是从原始数据中抽取出有意义的特征。比如从时间戳里提取「小时」「星期几」,从文本里提取「词频」「TF-IDF」,从图片里提取「像素均值」「边缘强度」。
这些操作看似简单,但血缘记录要回答三个问题:
- 源字段是谁?——比如「小时」来自哪个时间戳字段
- 提取函数是什么?——用了什么算法或规则
- 参数配置有哪些?——比如窗口大小、阈值、分桶数
核心原则:每个提取出来的特征,都要能追溯到它的「出生证明」——原始字段 + 提取逻辑 + 参数。
举个例子,我常用的血缘记录格式是这样的:
{
"feature_name": "hour_of_day",
"source_fields": ["timestamp"],
"extract_type": "datetime_component",
"extract_func": "dt.hour",
"params": {"timezone": "Asia/Shanghai"},
"created_at": "2024-01-15",
"owner": "zhang_san"
}
你想想看,如果这个特征出了问题,我只要查一下血缘记录,就能知道它来自哪个字段、用了什么逻辑。不用翻代码、不用问同事,省了多少时间。
3.2 特征选择的血缘记录
特征选择,就是从一堆特征里挑出有用的,扔掉没用的。常见的做法有:
- 基于统计的:卡方检验、互信息、方差分析
- 基于模型的:L1正则、树模型特征重要性
- 基于业务规则的:覆盖率、稳定性、可解释性
这里有个坑——被选掉的特征,不代表永远没用。我曾经遇到过,某个特征在A版本被淘汰了,到了B版本又需要加回来。如果没有血缘记录,根本不知道它为什么被淘汰,也不敢随便加。
注意:特征选择的血缘记录,不仅要记录「选了谁」,还要记录「淘汰了谁」以及「淘汰原因」。
我建议用这样的结构来记录:
{
"selection_id": "sel_20240115_v2",
"method": "feature_importance",
"model_type": "XGBoost",
"threshold": 0.01,
"selected_features": ["f1", "f3", "f7"],
"dropped_features": [
{"name": "f2", "reason": "importance=0.003, below threshold"},
{"name": "f5", "reason": "missing_rate > 80%"}
],
"evaluation_metric": "auc",
"eval_result": 0.823
}
为什么要记录淘汰原因?因为下次迭代时,你可能会调整阈值或换方法。有了历史记录,你就能对比不同版本的选择结果,知道哪些特征是「稳定好特征」,哪些是「偶尔被选中」。说白了,这就是特征选择的「经验沉淀」。
3.3 特征组合的血缘记录
特征组合,是特征工程里最灵活、也最容易出问题的地方。常见的组合方式有:
- 笛卡尔积组合:比如「年龄 × 性别」生成多个交叉特征
- 多项式组合:比如 x1²、x1×x2
- 业务规则组合:比如「收入/负债比」、「点击率 = 点击/曝光」
特征组合的血缘记录,难点在于组合爆炸。两个特征组合可能生成几十个新特征,每个都要记录来源,工作量很大。
我的做法是:记录组合规则,而不是记录每个组合结果。比如:
{
"combo_id": "combo_20240115",
"combo_type": "cartesian_product",
"base_features": ["age_group", "gender"],
"combo_rule": "age_group × gender → [age_group_gender_1, age_group_gender_2, ...]",
"output_prefix": "age_gender_",
"num_output": 6,
"sparsity": 0.35
}
这样,只要知道组合规则和输入特征,就能推导出所有输出特征的来源。而且,如果后续发现某个组合特征有问题,也能快速定位到是哪个规则出了问题。
小技巧:特征组合的血缘记录,建议加上「稀疏度」或「覆盖率」指标。我在项目中就靠这个发现了一个组合特征覆盖率只有0.1%,基本等于噪声,果断删掉。
3.4 血缘记录的完整链路
把上面三种操作串起来,就形成了特征工程的血缘链路:
原始数据 → 特征提取 → 特征选择 → 特征组合 → 最终特征集
每个环节都要记录「输入是什么、输出是什么、做了什么操作」。这样,任何一个最终特征,都能一路回溯到原始数据。
下面这张图展示了完整的血缘链路:
3.5 血缘记录的存储与查询
记录完了,怎么存?怎么查?我推荐两种方式:
| 存储方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 元数据数据库(如MySQL) | 小团队、特征数量少 | 查询方便、支持SQL | 扩展性有限 |
| 特征存储系统(如Feast) | 大规模、多团队协作 | 自动记录、版本管理 | 部署成本高 |
我个人习惯先用元数据数据库,等特征数量超过1000个再迁移到特征存储系统。没必要一开始就上大系统,够用就好。
查询技巧:血缘记录要支持「正向查询」和「反向查询」。
- 正向:给定原始字段,查出所有衍生特征
- 反向:给定最终特征,回溯到原始字段
这两个方向都要能在一分钟内查到结果,否则血缘记录就失去了「快速定位问题」的价值。
3.6 避坑指南
最后,分享几个我踩过的坑:
- 坑1:只记录最终结果,不记录中间过程。——结果就是,特征出问题了,你只知道它「不对」,但不知道「哪一步不对」。
- 坑2:血缘记录和代码分离。——我曾经把血缘记录写在Excel里,代码改了Excel没更新,最后对不上。一定要让血缘记录和代码一起版本管理。
- 坑3:忽略参数变化。——同样的提取函数,参数不同结果完全不同。比如「滑动窗口大小=3」和「滑动窗口大小=7」,生成的特征天差地别。参数必须记录。
嗯,以上就是特征工程中血缘记录的核心内容。说白了,就是一句话:每个特征都要有「出生证明」和「成长记录」。做到这一点,问题定位的效率至少提升一倍。