基础与框架核心25 分钟kp-004#优化#梯度下降#基础

梯度下降与凸优化基础

进度

一句话定义

梯度下降沿负梯度方向迭代更新参数以最小化目标函数:θ ← θ - η ∇_θ J(θ),是绝大多数机器学习模型的通用训练引擎。

为什么重要

机器学习 = 假设空间 + 损失 + 优化器。逻辑回归、神经网络、GBDT(函数空间版)全都依赖梯度类方法;学习率、批量大小等优化超参数直接决定训练是否收敛、收敛到哪。不懂梯度下降,就无法诊断「loss 不降、震荡、爆掉」这类最常见的训练问题。

前置知识

损失函数:从 0-1 损失到代理损失 的损失与经验风险;多元函数的偏导数与链式法则。

核心概念

  • 梯度:目标函数对各参数偏导数组成的向量,指向函数上升最快的方向。
  • 批量梯度下降(BGD):每步用全部样本计算梯度,稳定但昂贵。
  • 随机梯度下降(SGD):每步用 1 个或一小批(mini-batch)样本,梯度是有噪声的估计,便宜且噪声本身有探索作用。
  • 学习率(learning rate)η:步长,太大震荡发散,太小收敛缓慢。
  • 动量(momentum):累积历史更新方向 v ← γ v + η ∇ J,θ ← θ - v,抑制震荡、加速穿越平缓区。
  • 凸函数:任意两点连线位于函数图上方,局部最优即全局最优;线性模型多数损失是凸的,树模型与深度网络不是。

直观类比

下山:蒙住眼睛,用脚感受当前最陡的下坡方向(负梯度),迈一小步(学习率),重复。雾大时(SGD 噪声)路径歪歪扭扭但可能绕出小坑;动量像给身体加惯性,冲过小坑与缓坡。

原理与机制

一阶泰勒展开给出更新依据:J(θ - η g) ≈ J(θ) - η ‖ g ‖2(g = ∇ J),只要 η 足够小且 g ≠ 0,每步严格下降,这就是收敛性的来源。对强凸且梯度 Lipschitz(上界 M)的函数,经典结论是学习率满足 0 < η < 2/M 时收敛。SGD 中「噪声梯度」的期望仍是无偏估计 𝔼[gmini] = ∇ J,因此整体轨迹朝最优点漂移,后期靠衰减学习率(ηt ∝ 1/t 或余弦退火)压住方差。牛顿法引入二阶信息(H-1)可大幅加速但代价 O(p2) 存储,工程上常用拟牛顿(L-BFGS)或干脆用一阶法+自适应步长。

公式与推导

推导「为什么负梯度是最速下降方向」:在 θ 处沿单位方向 d 的一阶变化为 ∇ J^⊤ d,由柯西-施瓦茨不等式

∇ J^⊤ d ≥ -‖ ∇ J ‖ · ‖ d ‖

当 d = -∇ J / ‖ ∇ J ‖ 时取等号且下降最快,故最速下降方向是负梯度方向。

手写一个朴素梯度下降拟合二次目标 J(θ) = 12n ‖ Xθ - y ‖2:

import numpy as np
rng = np.random.default_rng(0)
X = rng.normal(size=(200, 3)); y = X @ np.array([2.0, -1.0, 0.5]) + rng.normal(scale=0.1, size=200)
theta = np.zeros(3); eta = 0.05
for t in range(500):
    grad = X.T @ (X @ theta - y) / len(y)   # 均方误差的解析梯度
    theta -= eta * grad                      # 负梯度方向更新
print(theta)                                 # ≈ [2, -1, 0.5]

图示

J(θ)
 ▲      ●步长过大:跨过谷底来回震荡
 │   \●/\●/
 │    \  /
 │     \/ ← 步长合适:逐级下落
 │      ●
 │       ●
 └──────────────▶ θ

实例或案例

scikit-learn 中 SGDClassifier(loss='log_loss') 等价于用 SGD 训练逻辑回归,适合百万级样本;对比 LogisticRegression(默认 lbfgs 拟牛顿)可体会同一模型不同优化器的取舍:

from sklearn.linear_model import SGDClassifier, LogisticRegression
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=100_000, n_features=20, random_state=0)
a = SGDClassifier(loss='log_loss', max_iter=20, tol=1e-3, random_state=0).fit(X, y)
b = LogisticRegression(max_iter=500).fit(X, y)
print(a.score(X[:2000], y[:2000]), b.score(X[:2000], y[:2000]))  # 精度接近,SGD 更快

常见误区

  • 学习率只调一次:应配合调度(衰减/预热),训练后期大步长会在极小值附近震荡不收敛。
  • 把 SGD 的 loss 抖动当成 bug:mini-batch 梯度天然带噪,应看平滑后的趋势。
  • 忽略特征尺度:各维尺度差异大时损失面呈狭长椭圆,普通 GD 之字形缓慢;先标准化(见 kp-027)能显著改善条件数。

与其他知识点的关系

线性回归与最小二乘法 的正规方程是「一步解析解」的对照组;梯度提升树 GBDT 与 XGBoost 把梯度下降搬到函数空间(每步拟合负梯度的一棵树);超参数调优:网格、随机与贝叶斯搜索 的调优对象包含学习率与批量大小这类优化超参数。

自测题

  1. 学习率过大时损失曲线通常什么形态?为什么?

- 要点:震荡甚至发散;一步跨过谷底到达更高处,二阶修正项 M2η2‖ g‖2 超过一阶下降量。

  1. BGD、SGD、mini-batch 各适合什么场景?

- 要点:BGD 小数据求稳;SGD 流式/超大数据;mini-batch(32~1024)是 GPU 时代默认,兼顾方差与吞吐。

  1. 为什么树模型(如 CART)不直接用梯度下降训练?

- 要点:树结构(分裂特征与阈值)是离散选择,不可导;GBDT 的解法是把可导的「预测值」当参数、在函数空间做梯度下降,每步用回归树拟合负梯度。

延伸阅读

Bottou 等, Optimization Methods for Large-Scale Machine Learning(SIAM Review, 2018);李航《统计学习方法》附录 B。