AI 工程基础体系 · 第 41/100 篇。内容覆盖机器学习、深度学习与生成式 AI;模型、数据、评测、权限和成本会作为同一生产系统处理。

机器学习特征工程:编码、缩放、交叉、选择、泄漏与线上一致性

特征工程是把原始观测转换为模型可以使用的输入表示。原始数据可能包含数值、类别、文本、时间、位置、事件序列以及由多个来源拼接而成的记录;特征工程决定了这些信息以什么形式进入模型,也决定了训练阶段和预测阶段是否使用了同一套规则。

设原始样本为 xx,标签为 yy,特征变换为

z=T(x)z = T(x)

模型实际学习的是

y^=fθ(z)=fθ(T(x))\hat y = f_\theta(z)=f_\theta(T(x))

其中 TT 可以包括缺失值填充、类别编码、数值缩放、特征交叉、文本向量化等步骤。训练阶段学习的不只是模型参数 θ\theta,还可能包括变换参数,例如均值、标准差、类别字典、分位点和词表。若这些参数没有被正确保存、版本化并在线复用,模型即使离线指标很好,也可能在线失效。


一、先明确特征工程解决什么问题

特征工程通常同时处理四类问题。

第一类是表示问题:模型不能直接理解字符串“北京”、日期“2025-03-08”或一段原始文本,需要把它们转换成数值表示。

第二类是尺度问题:年龄可能在 00100100 之间,收入可能在 0010710^7 之间。对距离、内积或梯度敏感的模型而言,不同尺度会改变优化行为。

第三类是结构问题:单个特征可能无法表达真正的关系。例如,商品价格对不同用户群体的影响不同,需要表达“商品价格 × 用户群体”的交互。

第四类是数据边界问题:特征只能使用预测时刻已经可获得的信息。如果特征构造使用了未来数据、标签或测试集统计量,就会发生数据泄漏。

特征工程的目标不是盲目增加列数,而是构造满足以下条件的输入:

zt=T(xt,It)z_t = T(x_{\le t}, \mathcal{I}_t)

这里 tt 是预测时刻,xtx_{\le t} 表示截至时刻 tt 已发生的数据,It\mathcal{I}_t 表示预测时可访问的权限和数据范围。一个特征即使在数学上有效,只要它在 tt 时刻不可获得,就不应进入线上预测。


二、编码:把非数值信息变成可学习的表示

2.1 类别变量不能直接当作连续数字

假设城市字段为:

北京、上海、广州

若直接编码为:

北京 -> 0
上海 -> 1
广州 -> 2

线性模型会把它解释为存在大小关系:

广州>上海>北京\text{广州} > \text{上海} > \text{北京}

而城市通常没有这种数值顺序。树模型也可能按阈值切分出“编码小于 1.5”的集合,这同样人为引入了顺序。

因此,序数编码只适用于确实具有顺序的类别,例如:

低 -> 0
中 -> 1
高 -> 2

对于无序类别,最常见的方法是独热编码。

2.2 独热编码

若类别集合为

C={北京,上海,广州}\mathcal{C}=\{\text{北京},\text{上海},\text{广州}\}

则城市“上海”可以表示为:

(0,1,0)(0,1,0)

对于线性模型,独热编码后的预测函数可以写为:

y^=b+w北京I(北京)+w上海I(上海)+w广州I(广州)\hat y = b+w_{\text{北京}}I(\text{北京})+ w_{\text{上海}}I(\text{上海})+ w_{\text{广州}}I(\text{广州})

每个类别有自己的偏置贡献。若使用带截距的线性模型,删除一个类别列可以避免完全共线性;但现代正则化实现通常可以处理冗余列,是否删除参考类别应根据模型和实现决定,而不是机械执行。

独热编码的主要边界是维度增长。假设一个字段有 KK 个类别,它会产生约 KK 列;当类别数达到百万级时,独热编码会造成高维稀疏矩阵、模型参数膨胀以及未知类别处理困难。

2.3 未知类别是线上必然事件

训练数据中没有出现的类别,在生产环境中可能出现:

  • 新增的商品或商户;
  • 新注册的用户;
  • 训练数据未覆盖的地区;
  • 类别字典版本发生变化;
  • 数据源传入脏值或空字符串。

编码器必须定义未知类别策略。常见策略是将未知类别映射到全零向量或专门的 unknown 列。训练和服务使用不同类别字典则会产生列错位:模型认为第 5 列是“上海”,服务端却把第 5 列填成了“广州”,结果不会一定报错,却会产生静默错误。

2.4 频数编码与哈希编码

频数编码用类别在训练数据中的出现次数或比例替代类别:

z(c)=#{i:xi=c}nz(c)=\frac{\#\{i:x_i=c\}}{n}

它保留了“常见或稀有”的信息,但不直接表示类别的语义。频数统计必须只在训练折或历史窗口中计算,否则可能把验证集分布信息带入训练过程。

哈希编码使用哈希函数将类别映射到固定数量的桶:

j=h(c)modBj=h(c)\bmod B

其中 BB 是桶数量。它不需要维护完整字典,适合高基数类别和流式系统,但不同类别可能发生哈希碰撞。碰撞不一定导致错误,但会让多个类别共享同一组参数;桶数越小,碰撞概率通常越高,内存和泛化之间需要取舍。

2.5 目标编码必须防止标签泄漏

目标编码用某个类别对应的标签统计量表示类别。例如二分类任务中:

TE(c)=E[yx=c]\operatorname{TE}(c)= \mathbb{E}[y\mid x=c]

如果训练集中某个类别只出现一次,并且该样本标签为 11,直接目标编码会把这个样本自己的标签编码进输入,模型可以近似“读取答案”。

常用的平滑目标编码为:

TE(c)=ncμc+αμnc+α\operatorname{TE}(c)= \frac{n_c\mu_c+\alpha\mu}{n_c+\alpha}

其中:

  • ncn_c 是类别 cc 的样本数;
  • μc\mu_c 是类别 cc 的标签均值;
  • μ\mu 是全局标签均值;
  • α\alpha 是平滑强度。

ncn_c 很小时,编码值更接近全局均值;当 ncn_c 很大时,编码值更接近类别自身均值。

即使使用平滑,训练集内的目标编码仍可能包含当前样本的标签。更安全的做法是 out-of-fold(OOF)目标编码

  1. 将训练集分成 KK 个折;
  2. 对第 kk 折样本,只使用其余 K1K-1 折计算类别统计量;
  3. 用该统计量编码第 kk 折;
  4. 拼接所有折得到训练特征;
  5. 最后用完整训练集统计量编码验证集、测试集和线上请求。

时间序列任务不能随意使用随机 K 折,而应使用只依赖过去数据的历史窗口或时间切分。否则“未来类别的标签统计量”仍可能流入过去样本。


三、数值缩放:改变坐标尺度,不是改变信息本身

3.1 标准化

标准化将特征转换为:

z=xμσz=\frac{x-\mu}{\sigma}

其中 μ\muσ\sigma 必须由训练数据估计,分别是均值和标准差。变换后,训练数据通常接近均值为 00、标准差为 11

标准化不会让数据必然落在 [1,1][-1,1],也不能消除异常值。若数据存在极端离群点,均值和标准差可能被显著拉动。

3.2 最小最大缩放

最小最大缩放为:

z=xxminxmaxxminz=\frac{x-x_{\min}}{x_{\max}-x_{\min}}

常见目标区间是 [0,1][0,1]。它适合需要固定输入范围的场景,但对异常值敏感。如果训练集出现一个极大值,普通样本会被挤压到很窄的区间。

3.3 鲁棒缩放与对数变换

当分布有明显离群点时,可以使用中位数和四分位距:

z=xmedian(x)IQR(x)z=\frac{x-\operatorname{median}(x)} {\operatorname{IQR}(x)}

对于收入、订单金额、访问次数等右偏变量,常使用:

z=log(1+x)z=\log(1+x)

对数变换压缩大值之间的距离,同时保留零值可定义性。它不是普适规则:如果业务关系是线性的,强行取对数可能损失可解释性;如果变量可能为负数,也不能直接使用 log(1+x)\log(1+x)

3.4 为什么缩放影响某些模型

对两个样本 xa,xbx_a,x_b 使用欧氏距离:

d(xa,xb)=j(xajxbj)2d(x_a,x_b)= \sqrt{\sum_j(x_{aj}-x_{bj})^2}

若某个特征的数值范围比其他特征大一千倍,它几乎会完全支配距离。因此 K 近邻、K-means、核方法等通常需要缩放。

在线性回归或逻辑回归中,带 L2L_2 正则化的目标可以写为:

minwL(w)+λjwj2\min_w L(w)+\lambda\sum_jw_j^2

若原始特征 xjx_j 放大 aa 倍,为保持相同预测效果,参数可以缩小为 wj/aw_j/a。但正则项变成:

λ(wj/a)2\lambda(w_j/a)^2

不同尺度会让不同特征承担不同的正则惩罚,因此缩放会影响解,而不仅是影响优化速度。

对于基于阈值分裂的决策树,单调缩放通常不改变样本排序,因此理论上不会改变可选切分点。但实际实现仍可能受到浮点精度、缺失值处理、正则约束或早停策略影响。不能把“树模型通常不需要标准化”误解为“所有模型都不需要检查尺度”。

3.5 训练集统计量不能来自全数据

假设训练集均值为 1010,验证集均值为 100100。如果先把训练集和验证集合并计算均值 5555,再分别缩放,就把验证集的分布信息带入了训练流程。这种泄漏可能不会直接使用标签,但仍会让评估不再模拟真实未知数据。

正确顺序是:

μtrain,σtrainXtrain\mu_{\text{train}},\sigma_{\text{train}} \leftarrow X_{\text{train}}

然后:

Xtrain=XtrainμtrainσtrainX'_{\text{train}}= \frac{X_{\text{train}}-\mu_{\text{train}}} {\sigma_{\text{train}}}

Xvalid=XvalidμtrainσtrainX'_{\text{valid}}= \frac{X_{\text{valid}}-\mu_{\text{train}}} {\sigma_{\text{train}}}

验证集不能重新估计一套均值和标准差,否则模型接收到的坐标系发生变化。


四、特征交叉:表达“一个变量的作用取决于另一个变量”

4.1 加法模型表达不了所有关系

线性模型:

y^=b+w1x1+w2x2\hat y=b+w_1x_1+w_2x_2

假设 x1x_1 是商品价格,x2x_2 是会员等级。该模型隐含地认为价格每增加一单位,预测值变化始终是 w1w_1,与会员等级无关。

加入乘积项:

y^=b+w1x1+w2x2+w12x1x2\hat y=b+w_1x_1+w_2x_2+w_{12}x_1x_2

此时价格的边际作用为:

y^x1=w1+w12x2\frac{\partial\hat y}{\partial x_1} =w_1+w_{12}x_2

也就是说,会员等级改变后,价格的作用也会改变。这就是交互特征或特征交叉的核心。

4.2 类别交叉

如果“渠道”和“设备类型”分别独热编码,只能表达两个主效应:

渠道=搜索
设备=移动端

加入二者交叉后,可以表达:

渠道=搜索 AND 设备=移动端

对于广告点击率、推荐排序和风控模型,这种组合可能具有独立意义。类别交叉的代价是组合空间增长。例如两个字段分别有 10001000500500 个类别,理论组合达到 500000500000 个;实际数据可能只覆盖其中一小部分,但长尾会导致稀疏和过拟合。

4.3 多项式特征的增长

dd 个输入生成最高阶数为 pp 的多项式特征,包含交互项时,特征数量可能达到:

(d+pp)\binom{d+p}{p}

例如 d=20,p=3d=20,p=3 时,数量为:

(233)=1771\binom{23}{3}=1771

这还没有考虑类别独热后的维度。高阶交叉容易造成:

  • 计算和存储成本上升;
  • 稀疏组合在训练中几乎没有观测;
  • 线性模型方差增大;
  • 交叉项与原始项高度相关,优化变得困难。

因此交叉不是“越多越强”。应依据业务机制、样本量和正则化能力选择有限的交互项。树模型和神经网络可以隐式学习部分交互,但不代表所有交互都能稳定学到;显式交叉仍可能帮助小数据线性模型。


五、特征选择:减少噪声、成本和过拟合

特征选择是从候选特征中保留子集:

S{1,,d}S\subseteq\{1,\ldots,d\}

模型使用:

y^=fθ(xS)\hat y=f_\theta(x_S)

它不同于降维。特征选择保留原始字段,结果通常更容易解释;主成分分析等降维方法会把多个字段变换成新的组合坐标。

5.1 过滤式选择

过滤式方法不依赖最终模型或只弱依赖模型,例如:

  • 方差阈值;
  • 与标签的相关性;
  • 互信息;
  • 卡方统计量;
  • 缺失率和唯一值比例。

例如方差为零的列在训练集中始终不变,对该训练任务没有区分能力。卡方方法通常适用于非负离散特征,不能不加检查地用于任意带负值的标准化特征。

过滤式方法计算便宜,但单变量有效不代表组合有效,单变量无效也不代表在交互中无效。例如 XOR 问题中,两个输入单独看都与标签不相关,但二者组合完全决定标签。

5.2 包裹式选择

递归特征消除(RFE)等方法反复训练模型并删除不重要特征。它能利用模型结构,但训练成本较高,也容易受到模型随机性和数据切分影响。

5.3 嵌入式选择

L1 正则化会推动部分参数变为零:

minwL(w)+λjwj\min_w L(w)+\lambda\sum_j|w_j|

零参数可以作为特征筛选结果。但高度相关的特征可能被任意保留其中一个,不能把“参数为零”简单解释为“该业务因素没有作用”。

树模型的特征重要性也有边界。基于不纯度下降的指标会偏向取值多的特征;在相关特征之间,重要性可能被分摊。置换重要性则测量打乱某个特征后性能下降,但当特征高度相关时,其他特征可能补偿它,导致重要性被低估。

5.4 选择过程也必须在训练数据内部完成

如果先在全数据上选择与标签最相关的前 100 个特征,再划分训练集和测试集,测试标签已经参与了特征选择。即使模型训练只使用训练部分,测试集也不再是真正未知的数据。

交叉验证中,正确结构是:

每个训练折拟合选择器选择特征拟合模型\text{每个训练折} \rightarrow \text{拟合选择器} \rightarrow \text{选择特征} \rightarrow \text{拟合模型}

外层验证折只能被最终评估,不能参与选择器拟合。这称为嵌套交叉验证的基本原则。


六、一个可运行的预处理与建模示例

下面的示例使用 scikit-learn 构造包含数值、类别和缺失值的二分类数据。Pipeline 将预处理和模型绑定起来,确保交叉验证的每个训练折独立拟合变换参数。

import numpy as np
import pandas as pd

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

rng = np.random.default_rng(42)
n = 1000

df = pd.DataFrame({
    "age": rng.integers(18, 70, size=n).astype(float),
    "income": rng.lognormal(mean=10, sigma=0.8, size=n),
    "city": rng.choice(["beijing", "shanghai", "guangzhou"], size=n),
    "device": rng.choice(["ios", "android", "web"], size=n),
})

# 构造一个仅用于示例的标签:收入和城市共同影响概率
logit = (
    -4.0
    + 0.04 * df["age"]
    + 0.00001 * df["income"]
    + 0.7 * (df["city"] == "shanghai")
    + 0.4 * (df["device"] == "ios")
)
prob = 1 / (1 + np.exp(-logit))
df["label"] = rng.binomial(1, prob)

# 模拟缺失
df.loc[rng.choice(n, 40, replace=False), "income"] = np.nan
df.loc[rng.choice(n, 20, replace=False), "city"] = None

X = df.drop(columns="label")
y = df["label"]

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y,
)

numeric_features = ["age", "income"]
categorical_features = ["city", "device"]

numeric_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler()),
])

categorical_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="most_frequent")),
    (
        "onehot",
        OneHotEncoder(
            handle_unknown="ignore",
            # scikit-learn 1.2+ 使用 sparse_output;
            # 较旧版本通常使用 sparse=False。
            sparse_output=False,
        ),
    ),
])

preprocessor = ColumnTransformer([
    ("num", numeric_pipeline, numeric_features),
    ("cat", categorical_pipeline, categorical_features),
])

model = Pipeline([
    ("preprocessor", preprocessor),
    ("classifier", LogisticRegression(max_iter=1000)),
])

model.fit(X_train, y_train)

test_probability = model.predict_proba(X_test)[:, 1]
print("test AUC:", roc_auc_score(y_test, test_probability))

# 训练后直接接收原始格式的新请求
new_request = pd.DataFrame([{
    "age": 35,
    "income": 120000,
    "city": "shenzhen",  # 训练集未出现,handle_unknown="ignore" 会处理
    "device": "android",
}])

print("new probability:", model.predict_proba(new_request)[:, 1])

这段代码中,数值缺失值先用训练集中的中位数填充,再标准化;类别缺失值使用训练集中的众数填充,再独热编码。handle_unknown="ignore" 保证新城市不会因为类别未见过而直接报错,未知城市对应的独热列通常为全零。

如果把 StandardScaler().fit_transform(X) 放在划分数据之前,或者先对完整数据调用 get_dummies 再切分,就破坏了这个隔离。Pipeline 的价值不是语法简洁,而是把“拟合变换”和“应用变换”放到模型选择流程的正确边界内。

OneHotEncoder 的参数名具有版本差异:较新的 scikit-learn 使用 sparse_output,较旧版本常见的是 sparse。部署时应锁定 scikit-learn 版本并通过测试验证序列化模型可恢复,不能仅凭本地环境中的示例推断生产环境参数一定可用。


七、数据泄漏:模型看到了预测时不应知道的信息

数据泄漏是指训练过程使用了在真实预测时不可获得的信息。它的关键不是“数据是否来自测试集”这一单一判断,而是信息是否穿越了预测时间或评估边界。

7.1 直接标签泄漏

贷款违约预测中,若特征包含“催收后是否结清”,而预测时点是放款时刻,这个字段虽然可能存在于数据库中,却是在预测事件发生之后产生的。模型会学到结果的后果,而不是预测风险。

7.2 聚合窗口泄漏

构造用户过去 30 天消费次数时,若查询条件没有上界,实际统计了全生命周期数据:

SELECT user_id, COUNT(*) AS order_count
FROM orders
WHERE order_time >= :start_time
GROUP BY user_id;

正确的点时间统计应明确上下界:

SELECT user_id, COUNT(*) AS order_count_30d
FROM orders
WHERE order_time >= :prediction_time - INTERVAL '30 days'
  AND order_time <  :prediction_time
GROUP BY user_id;

右端使用 < :prediction_time 而不是 <=,是为了避免把预测时刻同时发生、但在业务上尚未可用的事件纳入统计。具体边界仍应以事件时间和数据可见时间定义为准。

7.3 训练和测试混合导致的间接泄漏

以下操作通常也必须只在训练数据上拟合:

  • 均值、中位数、标准差;
  • 词表和 IDF;
  • 类别字典;
  • 目标编码;
  • 特征选择;
  • PCA 或其他降维参数;
  • 异常值截断分位点;
  • 缺失值模式筛选。

单独看,这些统计量不包含标签;但它们包含了评估数据的分布信息,可能让离线评估偏乐观。

7.4 时间和实体边界

随机切分并不适合所有数据。以下场景通常需要特殊切分:

  • 时间序列:训练集必须早于验证集;
  • 用户行为:同一用户跨集合出现会让用户画像泄漏;
  • 医疗患者:同一患者的多次就诊应按患者分组;
  • 设备监控:同一设备的相邻窗口高度相关;
  • 商品推荐:新商品和老商品应分别评估冷启动能力。

如果同一实体同时出现在训练和验证中,模型可能记住实体身份,而不是学习可迁移规律。GroupKFold 适合按实体分组,时间序列则需要只使用过去的切分策略;二者不能仅靠一次随机种子解决。

7.5 通过异常指标识别泄漏

泄漏常表现为:

  • 验证 AUC 接近 1,而线上性能很低;
  • 特征重要性集中在几个难以解释的字段;
  • 删除某个字段后指标断崖式下降;
  • 随机切分很好,时间切分显著变差;
  • 训练时没有字段,线上请求却需要填补该字段;
  • 指标随数据清洗版本变化异常。

诊断时应逐列记录特征的业务含义、生成时间、可见时间、数据来源和权限范围,并构造“按预测时刻回放”的样本,而不是只检查最终矩阵是否有空值。


八、训练—服务一致性:同一个特征必须有同一套语义

线上一致性,也称训练服务偏差(training-serving skew),是指训练时的特征与线上预测时的特征虽然字段名相同,实际计算语义却不同。

一个完整的线上预测链路通常是:

flowchart LR
    A[原始事件与主数据] --> B[离线特征计算]
    A --> C[在线特征计算]
    B --> D[训练样本]
    D --> E[预处理与模型训练]
    C --> F[在线特征向量]
    E --> G[模型版本与变换状态]
    F --> G
    G --> H[预测结果]

关键路径是:离线和在线都应从相同的业务定义出发,使用相同的时间边界、缺失语义、单位、类别字典和版本化变换状态。只把模型文件部署到线上,而不部署训练时的预处理状态,并不能保证一致性。

8.1 预处理器是有状态的

以下内容都属于模型输入契约的一部分:

  • 数值填充值;
  • 标准化的均值和标准差;
  • 类别到列索引的映射;
  • 哈希桶数和哈希算法;
  • 文本分词器、词表和截断长度;
  • 分位点和异常值裁剪边界;
  • 特征交叉的字段顺序;
  • 时间窗口和时区。

例如训练时把金额单位定义为“元”,线上若传入“分”,数值仍然是合法浮点数,但预测语义会整体偏移一百倍。此类错误通常不会触发类型检查,只能通过契约测试、分布监控和样本回放发现。

8.2 两种实现策略

一种策略是把完整变换封装在模型包中。请求进入服务后,先按原始字段格式执行预处理,再调用模型。优点是版本绑定清晰;缺点是服务依赖较重,跨语言实现可能困难。

另一种策略是先由统一特征服务生成特征,模型服务只接收已定义的特征向量。优点是多模型复用特征;缺点是特征服务需要严格管理版本、回填、延迟和点时间正确性。

无论采用哪种方式,线上不能重新“猜测”训练规则,例如根据当前请求临时计算标准化均值,或按当前批次重新生成类别列。服务必须加载训练时保存的状态。

8.3 离线回放应验证数值一致性

可以选择一批历史请求,分别走离线计算路径和线上计算路径,比较:

Δj=zjofflinezjonline\Delta_j = |z^{\text{offline}}_j-z^{\text{online}}_j|

对于连续特征,可使用绝对误差和相对误差阈值;对于类别、布尔和哈希特征,应比较精确相等。比较前必须统一浮点精度、时区和排序,否则会把序列化差异误判为业务差异。

一致性测试至少应覆盖:

  • 正常样本;
  • 缺失字段;
  • 未知类别;
  • 极端数值;
  • 空集合聚合;
  • 事件边界恰好等于预测时刻;
  • 重复事件和乱序事件;
  • 新旧特征版本并存。

九、时间窗口、聚合和状态是线上特征的核心难点

很多生产特征不是原始列,而是从事件流聚合出来的状态,例如:

用户过去 7 天登录次数
商户过去 24 小时拒付率
设备最近一次活跃距现在的秒数

这类特征需要同时定义:

  1. 事件发生时间;
  2. 数据进入系统的时间;
  3. 特征可见时间;
  4. 聚合窗口;
  5. 去重规则;
  6. 延迟数据如何处理;
  7. 预测请求缺少历史状态时如何处理。

如果事件在 tet_e 发生,但在 te+10t_e+10 分钟才到达特征系统,那么它在 te+1t_e+1 分钟的预测中不应可见。只使用事件时间过滤而忽略到达时间,会制造“回放时可见、当时线上不可见”的泄漏。

线上并发还会改变特征语义。例如两个同一用户的请求几乎同时到达:

  • 请求 A 读取用户状态;
  • 请求 B 读取用户状态;
  • A 更新计数;
  • B 更新计数。

如果更新不是原子的,可能丢失一次计数。若特征定义要求“请求前的历史计数”,则读取和写入的先后关系必须明确;若要求“包含当前事件”,则事件入账时机也必须明确。缓存、数据库和流处理系统中的最终一致性可能让短时间内不同服务读取到不同状态,这属于特征系统的正确性问题,而非单纯的模型问题。


十、深度学习和生成式 AI 中的特征工程

10.1 深度学习减少手工交叉,不等于不需要特征工程

神经网络可以通过多层非线性函数学习交互,但仍需要:

  • 正确的缺失值表示;
  • 合理的数值尺度;
  • 类别 ID 与词表;
  • 序列截断和填充;
  • 时间和位置编码;
  • 训练与推理一致的归一化;
  • 防止未来信息进入注意力上下文。

例如 Transformer 的自回归生成要求位置 tt 的注意力不能读取 t+1t+1 及之后的 token。若训练数据构造时把完整答案拼进输入,模型可能在训练中看到目标内容,形成与传统标签泄漏类似的上下文泄漏。

10.2 类别嵌入

深度模型常把类别 ID 映射为向量:

ec=W[c]e_c=W[c]

其中 WW 是嵌入矩阵,W[c]W[c] 是类别 cc 的向量。嵌入可以学习类别之间的相似性,比独热向量更紧凑。但嵌入表仍然需要处理未知类别、词表版本和新增类别;线上把 ID 映射到错误的行会产生静默错误。

高基数实体嵌入还可能记忆用户或商品身份。如果训练和验证按随机样本切分,同一实体同时出现,指标可能反映记忆能力,而不是对新实体的泛化能力。

10.3 文本、向量和检索增强生成

在生成式 AI 系统中,常见输入表示包括:

  • token ID;
  • 稠密向量嵌入;
  • 文档分块后的检索结果;
  • 用户、权限和会话状态;
  • 工具调用的结构化参数。

文本切分规则、嵌入模型版本、向量维度、距离函数和索引更新策略,都是特征或输入表示的一部分。检索增强生成(RAG)中,若离线评测检索库包含评测答案的未来版本,而线上库没有,就会产生检索层面的泄漏。

权限也属于输入正确性。若检索特征在生成前没有按用户权限过滤,模型可能获得本来不可访问的文档。此时即使预测质量更高,系统仍然是不正确的。特征流水线需要记录数据来源、访问主体和授权范围,不能把权限检查留给模型“自行判断”。


十一、特征选择和工程取舍应使用完整成本函数

模型效果不是唯一目标。生产系统更接近优化:

总成本=训练成本+在线计算成本+存储成本+延迟成本+错误风险+维护成本\text{总成本} = \text{训练成本} +\text{在线计算成本} +\text{存储成本} +\text{延迟成本} +\text{错误风险} +\text{维护成本}

一个只带来很小离线收益、却需要实时调用多个外部系统的特征,可能不值得上线。相反,一个略微降低 AUC、但显著降低延迟和故障面、并且更容易解释的特征,可能更适合核心链路。

特征选择还应考虑数据权限和保留策略。某个字段即使预测有效,如果不能在生产环境合法使用,或者只能在训练阶段访问,就不应作为线上特征。训练集中的脱敏数据、线上可访问的数据和评测数据的权限边界必须分别审计。


十二、失败表现与诊断顺序

当模型离线和线上表现不一致时,可以按输入层、变换层、数据时间层、模型层的顺序排查。

首先比较同一批请求的原始输入,确认字段名称、类型、单位、时区和缺失值是否一致。字符串空值、数据库 NULL、JSON 缺失字段和数值 0 不能默认视为同一语义。

然后比较变换后的向量,重点检查列顺序、类别字典、缩放参数、哈希桶、交叉顺序和特征版本。若原始输入一致而向量不同,问题通常在预处理或序列化。

再检查特征生成的时间边界和数据可见时间。离线回放如果能使用当时线上尚未到达的迟到事件,离线结果会虚高。对聚合特征,应保存构造时的 prediction_time,并在回放中重新执行点时间连接,而不是直接读取今天已经更新过的聚合表。

最后检查模型文件、运行时依赖和数值类型。模型升级后特征维度变化、类别列删除、浮点精度变化或默认参数变化,都可能造成结果漂移。每次发布应记录模型版本、特征版本、预处理版本和依赖版本,并保留可恢复的旧版本。


十三、一个可操作的检查框架

提交一个新特征前,至少需要回答以下因果问题:

  • 它表示什么业务事实,而不是仅仅叫什么名字?
  • 预测时刻之前,系统是否真的能获得它?
  • 它的时间窗口是左闭右开还是其他边界?
  • 训练、验证和线上是否使用同一单位、时区和缺失语义?
  • 类别字典如何版本化,未知类别如何处理?
  • 统计量是在哪个数据边界内拟合的?
  • 它是否包含标签、标签后果或未来聚合结果?
  • 如果特征服务超时、返回空值或版本不兼容,模型如何降级?
  • 该特征的计算延迟、存储、权限和成本是否可接受?
  • 删除该特征后,指标下降是否在时间切分、实体切分和线上回放中都成立?

特征工程最终应形成一个可验证的输入契约,而不是散落在 notebook、SQL、服务代码和人工操作中的若干片段。契约至少要包含字段类型、单位、时间语义、缺失规则、变换状态、版本号和错误处理。


结语

编码解决“模型如何表示离散和非数值信息”,缩放解决“不同坐标尺度如何影响距离、正则化和优化”,交叉解决“变量之间的条件关系如何进入模型”,选择解决“哪些输入值得保留”。泄漏则规定了这些操作必须在什么信息边界内完成,线上一致性进一步要求训练时学到的变换状态和业务语义在服务时原样复用。

真正可靠的特征不是离线表格中看起来有预测力的列,而是满足以下条件的完整输入定义:

可获得无未来信息变换可复现权限合法线上可计算\text{可获得} \land \text{无未来信息} \land \text{变换可复现} \land \text{权限合法} \land \text{线上可计算}

只有当数据、特征变换、模型、评测、权限和成本共同构成可回放、可监控、可恢复的生产系统时,特征工程才从一次性的建模技巧变成了机器学习系统的基础设施。


系列导航与关联阅读

官方资料

本文依据研究论文、标准组织与主流框架官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。