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 为各类别创建指示列。
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 有顺序,可以使用显式类别表:
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;
- 平滑目标编码。
目标编码用某类别的标签统计替换类别,最容易泄漏。训练行若参与计算自己的类别均值,罕见类别会近似记住标签。正确流程是:
- 在训练集内部划分折;
- 每一折的编码只由其他折标签计算;
- 验证集、测试集和线上数据只使用完整训练集得到的映射;
- 用全局先验与类别样本量做平滑;
- 未见类别回退到预先定义的先验。
这就是 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
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 编码。
- 时间拆得越细越好:高维日历特征可能记住历史噪声。
练习
- 为线性模型与树模型分别解释哪些数值列需要缩放。
- 用训练集中未出现的任务类型测试 unknown category 行为。
- 实现 out-of-fold 目标编码,并与错误的全训练集编码比较验证分数。
- 用 UTC 和当地时间各生成小时特征,检查夏令时边界。
小结
编码和缩放都在引入数学假设。参数必须只从训练数据学习,未知值和时间边界必须预先定义,完整转换链要与模型一起交付。
下一课把候选特征收束成可维护接口:选择必须发生在验证过程内,训练与服务还要保持同一份语义。