跳到内容

7.2 数值、类别与时间编码:转换参数属于模型

通过时间点审查后,气温、装备等级、任务类型和出发时刻终于都是合法输入。但模型对这些值的数学解释不同:类别编号不是数量,午夜与 23 点并不遥远,缩放也会改变正则化惩罚。

转换不是数据表的永久“清洗结果”,而是模型的一部分。它必须随训练一起拟合、版本化和部署。

本课目标

  • 根据模型机制决定是否缩放数值;
  • 正确处理无序、有序和高基数类别;
  • 理解目标编码为何必须交叉拟合;
  • 用 pipeline 保证各训练折与线上执行同一转换。

1. 缩放解决什么问题

对距离、内积、梯度或系数惩罚敏感的模型,特征尺度会影响结果:

  • k-nearest neighbors 与聚类的距离由大尺度列主导;
  • ridge/lasso 对系数施加惩罚,尺度不一致时惩罚含义不同;
  • 神经网络和梯度优化常因尺度接近而更易训练。

树模型按阈值切分,通常不要求为了预测而标准化;但具体实现、正则化或后续解释工具仍可能带来例外。

  • StandardScaler 使用训练集均值与标准差;
  • RobustScaler 使用中位数与分位距,对极端值影响较小;
  • MinMaxScaler 映射训练范围,但不会让未来值自动留在区间内。

“有异常值就一定用 RobustScaler”不是规则。选择应由模型、损失、分布和验证结果共同决定。

2. 无序类别:One-Hot 与稀有值

任务类型没有自然顺序,把 scout=1, rescue=2, transport=3 直接交给线性模型,会虚构距离与大小关系。One-hot 为各类别创建指示列。

python
from sklearn.preprocessing import OneHotEncoder

encoder = OneHotEncoder(
    handle_unknown="infrequent_if_exist",
    min_frequency=20,
)

min_frequency 应由训练规模和业务语义确定,不存在“类别少于 10 就 one-hot”的通用边界。合并稀有类别能限制维度,却也可能把风险不同的群体混在一起。

未知类别策略必须明确:忽略、归入稀有组、拒绝请求,还是回退旧模型。离线验证要模拟线上可能出现的新类别。

在线性模型中随意丢掉 one-hot 类别会改变系数参照;带惩罚模型下,删除类别还可能引入不对称。编码参数应和模型解释方式一起设计。

3. 有序类别:顺序不等于等距

low < medium < high 有顺序,可以使用显式类别表:

python
from sklearn.preprocessing import OrdinalEncoder

ordinal = OrdinalEncoder(
    categories=[["low", "medium", "high"]],
    handle_unknown="use_encoded_value",
    unknown_value=-1,
)

编码为 0、1、2 同时暗示相邻级别距离相等。若这个假设不合理,可以 one-hot、单调约束,或基于领域定义数值。不要用字母排序代替业务顺序。

4. 高基数类别与目标编码

城堡、设备或商户可能有数万取值。可选方案包括:

  • 合并有业务意义的层级;
  • 频次/计数编码;
  • 哈希编码;
  • 学习式 embedding;
  • 平滑目标编码。

目标编码用某类别的标签统计替换类别,最容易泄漏。训练行若参与计算自己的类别均值,罕见类别会近似记住标签。正确流程是:

  1. 在训练集内部划分折;
  2. 每一折的编码只由其他折标签计算;
  3. 验证集、测试集和线上数据只使用完整训练集得到的映射;
  4. 用全局先验与类别样本量做平滑;
  5. 未见类别回退到预先定义的先验。

这就是 cross-fitting。仅仅把目标编码器放进外层 pipeline,不代表其内部已经交叉拟合;要确认所用实现的 fit_transform 语义。

5. 时间不是一个整数

从时间戳可构造:

  • 小时、星期、月份与节假日;
  • 距离某事件的时长;
  • 滚动计数、速率与趋势;
  • 业务周期和季节。

小时具有周期性。对线性模型可以表示为:

$$ x_{sin}=\sin(2\pi h/24),\qquad x_{cos}=\cos(2\pi h/24). $$

但周期编码不负责处理时区和夏令时。先明确业务使用的是当地时间还是 UTC,并处理重复或不存在的本地时刻。elapsed_days 还可能让模型记住训练期趋势,部署到新时期前要做时间外验证。

6. 非线性变换与交互

  • 对数变换可压缩右偏正值,但零值、负值和单位需要明确;
  • 分箱可表达阈值,却损失箱内信息,边界应在训练折学习;
  • 交互项表达“一个变量的作用依赖另一个变量”;
  • 比率特征需要稳定分母,并保留分子、分母以便诊断。

变换应对应合理机制或在独立验证中证明收益。大量自动交叉会扩大维度和过拟合空间。

7. 把转换放入 Pipeline

python
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

numeric = ["temperature", "days_since_supply"]
categorical = ["mission_type", "equipment_level"]

preprocess = ColumnTransformer([
    ("num", Pipeline([
        ("impute", SimpleImputer(strategy="median", add_indicator=True)),
        ("scale", StandardScaler()),
    ]), numeric),
    ("cat", Pipeline([
        ("impute", SimpleImputer(strategy="most_frequent")),
        ("encode", OneHotEncoder(handle_unknown="ignore")),
    ]), categorical),
])

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

交叉验证会在每个训练折重新拟合填充、缩放和类别词表。保存并部署整个 pipeline,而不是只保存最后的分类器。

常见误区

  • 所有模型都要标准化:取决于模型机制,不是数据科学仪式。
  • 整数编码类别最节省空间:它可能向模型注入虚假的顺序和距离。
  • 目标编码只要先切分就安全:训练集内部仍需 out-of-fold 编码。
  • 时间拆得越细越好:高维日历特征可能记住历史噪声。

练习

  1. 为线性模型与树模型分别解释哪些数值列需要缩放。
  2. 用训练集中未出现的任务类型测试 unknown category 行为。
  3. 实现 out-of-fold 目标编码,并与错误的全训练集编码比较验证分数。
  4. 用 UTC 和当地时间各生成小时特征,检查夏令时边界。

小结

编码和缩放都在引入数学假设。参数必须只从训练数据学习,未知值和时间边界必须预先定义,完整转换链要与模型一起交付。

下一课把候选特征收束成可维护接口:选择必须发生在验证过程内,训练与服务还要保持同一份语义。

Built with VitePress | Software Systems Atlas