跳到内容

第 12 卷 / 第 6 章 / 第 3 课

6.3 正则化、交叉验证与预测区间:模型选择不能偷看答案 ​

训练误差下降,新数据上的误差却可能上升,原因包括过拟合、分布偏移和泄漏。正则化限制模型自由度;预处理和系数在各训练折拟合,再用对应验证折比较正则强度。最终测试集则留到选择流程固定后再使用。

本课目标 ​

  • 区分 ridge、lasso 与 elastic net 的目标;
  • 用 pipeline 把预处理放入交叉验证;
  • 根据独立、分组或时间结构选择切分;
  • 报告基线、预测误差和预测区间。

1. Ridge 与 Lasso 惩罚不同 ​

Ridge:

minβ∑i(yi−xiTβ)2+λ∑jβj2.

Lasso:

minβ∑i(yi−xiTβ)2+λ∑j|βj|.
  • ridge 连续收缩相关特征系数,通常不精确归零;
  • lasso 可产生稀疏解,但相关特征间选择可能不稳定;
  • elastic net 混合 L1/L2,常在相关特征组中更稳健。

截距通常不惩罚,具体库行为需确认。惩罚改变了优化目标;正则化系数不能直接按普通 OLS p 值解释。

上式使用未归一化的平方误差和,库参数未必采用同一约定。scikit-learn Ridge 使用 SSE 加 L2 惩罚,Lasso则用 SSE 除以 2n,再加 alpha 乘 L1 范数。两个估计器的 alpha 数字相同,不表示惩罚强度相同。

2. 缩放决定惩罚公平性 ​

一个以字节计、一个以比例计的特征尺度差异巨大。相同系数惩罚在原尺度上代表不同函数变化,所以正则化前通常标准化数值特征。

标准化参数只能在每个训练折拟合。scikit-learn pipeline 示例:

示例使用 scikit-learn 1.4 及以上版本,按顺序运行。四个完整队伍留作最终测试,其余 16 个队伍用于模型选择。数值缺失在折内填补;样本没有类别缺失,预测时未知类别会映射为全零 one-hot 块,这只是兜底表示,不代表模型学会了新类别。

python
import numpy as np
import pandas as pd

rng = np.random.default_rng(31)
frame = pd.DataFrame({
    "team_id": np.repeat(np.arange(20), 5),
    "team_size": rng.integers(2, 9, 100).astype(float),
    "resources_used": rng.uniform(10, 50, 100),
    "mission_type": rng.choice(["survey", "repair"], 100),
})
y = 30 - 2 * frame["team_size"] + .4 * frame["resources_used"] + rng.normal(0, 3, 100)
frame.loc[3, "team_size"] = np.nan
features = ["team_size", "resources_used", "mission_type"]
train_mask = frame["team_id"] < 16
X_train, y_train = frame.loc[train_mask, features], y.loc[train_mask]
X_test, y_test = frame.loc[~train_mask, features], y.loc[~train_mask]
team_ids_train = frame.loc[train_mask, "team_id"]

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

numeric = ["team_size", "resources_used"]
categorical = ["mission_type"]

preprocess = ColumnTransformer([
    ("num", Pipeline([
        ("impute", SimpleImputer(strategy="median")),
        ("scale", StandardScaler()),
    ]), numeric),
    ("cat", OneHotEncoder(handle_unknown="ignore", sparse_output=False), categorical),
])

pipeline = Pipeline([
    ("preprocess", preprocess),
    ("model", Ridge()),
])

交叉验证前先在全数据拟合预处理,会让验证值影响缩放、填补或学习出的类别表。Pipeline 把这些拟合放进切分内部,但提前在 Pipeline 外做过的处理仍可能泄漏。

3. 三层数据职责 ​

  • training:拟合参数;
  • validation/CV:选择特征、模型与超参数;
  • test:只在最终方案固定后估计泛化表现。

反复查看 test 结果再调整模型,test 已成为验证集。需要保留新的独立数据,或诚实报告评估偏乐观。

嵌套交叉验证在内层选择超参数,在外层评估整套选择流程,减少用同一批折同时选择和评分带来的乐观偏差。切分仍要匹配部署人群与依赖结构,多套一层不等于自动无偏。

4. 切分单位必须匹配应用 ​

独立样本 ​

对独立、可交换的观测,打乱后的 K-fold 可作起点。仍要检查各折样本数和关键群体覆盖,普通 K-fold 不保证这些分布均衡。

重复实体 ​

若目标是推广到新用户或新队伍,同一实体应完整地留在一折。若目标是预测已知实体的未来记录,保留这些实体可能合理,但仍须遵守时间与标签可用性边界。

时间预测 ​

训练必须早于验证;随机打散会用未来分布预测过去。使用 rolling/expanding window,并在特征窗口与标签窗口之间设置必要 gap。

空间或站点泛化 ​

若要部署到未见站点,应留出完整站点或空间块。随机拆分相邻记录,可能把共享环境信息同时留在两侧;留出单位应对应部署时真正的新单位。

5. 超参数选择包含不确定性 ​

python
from sklearn.model_selection import GridSearchCV, GroupKFold

search = GridSearchCV(
    pipeline,
    param_grid={"model__alpha": [0.01, 0.1, 1.0, 10.0, 100.0]},
    scoring="neg_mean_absolute_error",
    cv=GroupKFold(n_splits=5),
)
search.fit(X_train, y_train, groups=team_ids_train)

评分指标应与业务损失一致。搜索范围、折分随机性和候选数量都会影响“最佳”alpha。可使用 one-standard-error rule 选择性能接近但更简单的模型。

GroupKFold(n_splits=5) 至少需要五个不同训练组,并保证验证组不出现在对应训练折。GridSearchCV 默认将选定 Pipeline 在全部训练数据上重拟合。各折共享大量训练数据,因此 one-standard-error rule 是选择启发式,不是独立样本意义下的置信保证。

6. 指标与基线 ​

  • MAE:目标单位,较少放大极端误差;
  • RMSE:平方惩罚,大误差权重高;
  • R2:相对均值基线的平方误差改善;
  • MAPE:零值附近不稳定,并对低值偏重。

至少比较:

  • 训练集均值/中位数基线;
  • 简单业务规则;
  • 当前生产模型;
  • 新候选模型。

总体平均还要按群体、时间和目标范围切分,防止某一大群体掩盖小群体严重误差。

python
from sklearn.dummy import DummyRegressor
from sklearn.metrics import mean_absolute_error, root_mean_squared_error

prediction = search.predict(X_test)
baseline = DummyRegressor(strategy="median").fit(X_train, y_train).predict(X_test)
print({
    "test_mae": mean_absolute_error(y_test, prediction),
    "test_rmse": root_mean_squared_error(y_test, prediction),
    "baseline_mae": mean_absolute_error(y_test, baseline),
    "selected_alpha": search.best_params_["model__alpha"],
})

最终测试记录不参与 alpha 选择,也不参与中位数基线拟合;分组报告还应确认每个留出组都有记录。通常的 R2 使用评估集均值作评分参照,需要另行比较用训练均值拟合的基线。

7. 预测区间不是均值区间 ​

均值响应置信区间估计 E[Y|X=x];新观测预测区间还包含个体噪声,通常更宽。

同方差线性模型可给解析区间,但分布错设、异方差和分布漂移会破坏覆盖。可考虑:

  • 条件方差模型;
  • 分位数回归;
  • conformal prediction(在交换性等条件下提供边际覆盖);
  • 按群体检查实际覆盖率。

只报告点预测会让用户误以为模型精度相同。区间宽度本身也是决策信息。

另一个 split conformal 小例子说明校准步骤:先固定预测器,在独立校准样本上收集绝对残差,再取有限样本修正后的分位数。

python
from math import ceil
import numpy as np

# Toy absolute errors from a separate calibration set, with the predictor already fixed.
calibration_errors = np.arange(1., 20.)
alpha = 0.10
n_calibration = len(calibration_errors)
rank = ceil((n_calibration + 1) * (1 - alpha))
q = np.inf if rank > n_calibration else np.sort(calibration_errors)[rank - 1]
new_prediction = 30.0
prediction_interval = (new_prediction - q, new_prediction + q)
print(prediction_interval)  # (12.0, 48.0)

校准与未来分数可交换时,该构造提供边际覆盖,不保证每个特征值或每个群体都有 90% 覆盖。这里的 19 个示意残差只演示秩计算;不能再用校准标签调预测器。重复实体或分布偏移需要考虑相应结构的方法。参见 Angelopoulos 与 Bates 的介绍。

8. Lasso 选择不等于稳定解释 ​

在高度相关特征中,lasso 可能随机保留一个、把另一个压零;换一个折或轻微扰动数据,选择就变化。零系数不证明特征“没有作用”,非零也不代表因果重要。

评估:

  • bootstrap/折间选择频率;
  • 系数路径;
  • 相关特征组;
  • out-of-sample 性能;
  • 领域合理性。

若目标是确认性变量选择和推断,需要专门方法和预设分析,不能先用 lasso 筛选再把普通 OLS p 值当未选择过。

9. 模型交付记录 ​

text
目标与使用范围
训练/验证/测试时间和实体边界
特征定义与可用时点
预处理参数和类别策略
候选模型与超参数搜索
总体与分组误差
预测区间覆盖
已知失效区间和 fallback
数据/代码/模型版本
监控与重训触发条件

常见误区 ​

  • Lasso 归零表示特征无用:选择受尺度、相关性和 lambda 影响。
  • 交叉验证前统一缩放没关系:验证折信息已泄漏。
  • 随机 K-fold 适合所有数据:实体、时间和站点结构决定切分。
  • 置信区间就是单个预测的范围:均值区间与预测区间不同。

练习 ​

  1. 比较同一数据在缩放前后 ridge 系数与预测。
  2. 对重复队伍数据比较随机 K-fold 与 GroupKFold 分数。
  3. 用时间切分找出随机切分造成的未来泄漏。
  4. 报告 MAE、RMSE、基线和四个群体的误差与区间覆盖。

小结 ​

正则化限制模型自由度,却不能替代独立评估。预处理必须进入 pipeline,切分必须反映真实泛化目标,测试集只能承担一次最终评估。模型输出还要连同区间、群体误差和失效范围交付。

下一章进入特征工程:特征不是越多越好,而是必须在预测时可获得、与实体和时间对齐,并由训练数据拟合转换参数。

Built with VitePress | Software Systems Atlas