跳到内容

第 13 卷 / 第 5 章 / 第 3 课

5.3 梯度、条件数与正则化路径:收敛到什么比跑了多少轮更重要 ​

同一份数据上,某个学习率可能让 loss 发散,更小的值又可能几乎不动。目标函数即使凸,优化速度仍取决于曲率、尺度、稀疏性和 solver。标准化常能改善这个问题,但“又跑了一千轮”本身不算诊断。

本课目标 ​

  • 推导 batch/minibatch gradient 的计算成本;
  • 理解 convexity、Hessian 与 conditioning;
  • 区分 L1、L2 与 Elastic Net 的几何和算法;
  • 用 pipeline/CV 选择正则并验证收敛。

1. Batch gradient descent ​

线性回归 MSE:

L(w)=1n‖Xw−y‖22,∇L=2nXT(Xw−y).

每次 dense full-batch gradient 通常需要 O(np) 运算,不是笼统 O(n)。内存、稀疏矩阵和数据读取也影响成本。

下面是 Python 3 + NumPy 函数片段(partial),输入设计矩阵与响应向量。tolerance 是梯度范数的绝对阈值,max_steps 是更新预算;返回的 loss 与返回的 w 对应,预算耗尽会返回 converged=False。

python
import numpy as np

def fit_linear_gd(X, y, learning_rate, max_steps, tolerance):
    X, y = np.asarray(X, dtype=float), np.asarray(y, dtype=float)
    if X.ndim != 2 or y.ndim != 1 or len(y) == 0 or len(X) != len(y):
        raise ValueError("X must be a nonempty matrix and y a matching vector")
    if not np.isfinite(X).all() or not np.isfinite(y).all():
        raise ValueError("X and y must be finite")
    if (not np.isfinite(learning_rate) or learning_rate <= 0
            or not np.isfinite(tolerance) or tolerance <= 0
            or not isinstance(max_steps, int) or max_steps < 0):
        raise ValueError("invalid step size, tolerance, or update budget")

    w = np.zeros(X.shape[1])
    for step in range(max_steps + 1):
        with np.errstate(over="raise", invalid="raise"):
            residual = X @ w - y
            loss = float(np.mean(residual ** 2))
            gradient = (2.0 / len(y)) * X.T @ residual
            gradient_norm = float(np.linalg.norm(gradient))
        if not np.isfinite(loss) or not np.isfinite(gradient_norm):
            raise FloatingPointError("non-finite optimization state")
        converged = gradient_norm <= tolerance
        if converged or step == max_steps:
            return w, {
                "steps": step, "loss": loss,
                "gradient_norm": gradient_norm, "converged": converged,
            }
        w -= learning_rate * gradient

只跑固定轮数不能证明收敛。学习率极小时,loss 变化小也可能只是参数没怎么移动。本例检查 gradient norm;阈值仍依赖尺度和条件数,不能直接等同于预测误差界。用 max_steps=0 应得到零初始参数及其真实 loss,用一次更新则应得到新参数对应的 loss。

2. Convex 不等于优化轻松 ​

Squared loss Hessian:

H=2nXTX.

它总是 positive semidefinite;只有满足相应满秩条件时才 positive definite/严格凸。相关特征使 Hessian 条件数大,等高线狭长,固定 learning rate 沿陡方向震荡、沿平方向缓慢。

标准化改善许多优化器的 conditioning,也让正则惩罚可比较,但不是所有树模型/闭式求解都“必须标准化”。

3. SGD 与 minibatch ​

  • SGD 用一个样本的无偏/随机梯度估计;
  • minibatch 平衡向量化效率与梯度噪声;
  • epoch 表示大致遍历一次数据,不代表独立样本;
  • shuffle、batch size 和 learning-rate schedule 共同影响轨迹。

数据有时间/群组结构时,随机 shuffle 可能改变训练分布或破坏 stateful 模型。分布式 batch 增大还需调整学习率并验证泛化。

4. 选择 solver ​

平方损失和逻辑损失使用的 solver 不完全相同:

  • QR/SVD/least-squares solver 直接解决平方损失;
  • normal-equation variants(谨慎数值稳定);
  • gradient/accelerated gradient;
  • Newton、quasi-Newton(L-BFGS);
  • coordinate descent;
  • stochastic variance-reduced 方法。

选择看 n,p、稀疏性、loss、penalty、内存和精度。并非“梯度下降扩展到任意规模”;每轮全数据梯度在超大数据上仍昂贵。

5. L2、L1 与 Elastic Net ​

L2 ​

L(w)+λ∑jwj2.

平滑收缩,常在相关特征间分散权重并改善 conditioning;不是“鼓励权重均匀”。

L1 ​

L(w)+λ∑j|wj|.

非光滑,最优解可含精确零;通常需 coordinate descent、proximal methods 等。相关特征中选哪一列可能不稳定,零系数不证明无预测信息。

Elastic Net ​

组合 L1/L2,在稀疏与相关组稳定性间调节。两个正则参数/混合比例都要在 validation 内选择。

6. 正则强度和尺度 ​

不同库用 alpha、lambda、C=1/lambda,还可能对 loss 取 sum 或 mean,参数数值不能跨实现直接比较。

通常不惩罚 intercept;one-hot/drop-reference 策略会影响 penalty symmetry。数值特征未缩放时,同样系数 penalty 对原始单位的函数变化不同。

7. Pipeline 与 CV ​

下面的 Python 3 + scikit-learn 片段(partial)依赖样本对齐的 X、y、team_id。至少需要五个组,每折训练和验证都要有足够类别覆盖;时间泛化应改用预先设计的时间切分。这里搜索默认 L2 逻辑回归的 C,保留完整分组,并在折内拟合缺失值和缩放参数。

python
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV, GroupKFold
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

candidate = Pipeline([
    ("impute", SimpleImputer(strategy="median")),
    ("scale", StandardScaler()),
    ("model", LogisticRegression(max_iter=2000)),
])

search = GridSearchCV(
    candidate,
    param_grid={
        "model__C": [0.01, 0.1, 1.0, 10.0],
    },
    scoring="neg_log_loss",
    cv=GroupKFold(n_splits=5),
    error_score="raise",
)
search.fit(X, y, groups=team_id)

这段在 scikit-learn 1.9.0 验证,使用默认 lbfgs 和 L2 设置;没有显式传入该版本已弃用的 penalty 参数。换 solver 或 L1/Elastic Net 时先核对 LogisticRegression 官方参数说明。缩放、缺失处理和选择全部在 fold 内 fit。

8. 收敛警告不能忽略 ​

不收敛可能来自:

  • 尺度/条件数差;
  • 完全分离;
  • 正则太弱;
  • max_iter 太小;
  • learning rate 太大;
  • solver 与 penalty/稀疏输入不匹配;
  • 数据含 NaN/Inf 或巨大 outlier。

只把 max_iter 提高十倍可能拖延根因。比较 objective、gradient、参数范围和不同 solver,并在同一 validation protocol 下验证。

9. 正则化路径与稳定性 ​

沿 λ 从强到弱记录:

  • train/validation loss;
  • 非零系数数和系数路径;
  • 折间入选频率;
  • calibration/群体误差;
  • 收敛状态和运行时间。

One-standard-error rule 可在性能相近时选择更简单/更强正则方案。最终选择仍依赖业务损失,而非“非零越少越好”。

10. 线性模型为何仍是强 baseline ​

  • 训练和推断便宜;
  • 稀疏高维可扩展;
  • feature contribution 结构明确;
  • 易校准、监控和部署;
  • 对小数据常更稳定。

但 feature engineering、共线性、外推和概念漂移仍可能失效。解释性来自完整 pipeline 和特征语义,不是模型类标签。

常见误区 ​

  • GD 每轮是 O(n):还要乘特征维度与数据布局。
  • Hessian 总是正定:秩亏时只半正定。
  • L1 自动找出真正变量:相关性和 lambda 会改变选择。
  • 收敛 warning 只需增加轮数:先查尺度、分离和 solver。

练习 ​

  1. 比较未缩放/缩放数据的 Hessian 条件数与 GD 轨迹。
  2. 对 rank-deficient X 判断 Hessian 特征值。
  3. 绘制 L1/Elastic Net 正则路径和折间选择频率。
  4. 对同一个平方损失目标,在相同 CV 下比较 QR、L-BFGS 与 SGD 的时间和 loss。

小结 ​

线性模型的目标常是凸的,却仍需要正确的数值表示和 solver。正则化既改善泛化与稳定性,也改变 estimand;选择它要看 validation、系数路径和实际决策。

下一章进入树与集成:通过递归分区表示非线性和交互,同时面对深度、方差、boosting 目标与概率校准的新问题。

Built with VitePress | Software Systems Atlas