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」,生成的特征天差地别。参数必须记录。

嗯,以上就是特征工程中血缘记录的核心内容。说白了,就是一句话:每个特征都要有「出生证明」和「成长记录」。做到这一点,问题定位的效率至少提升一倍。