☰
人工智能数学基础第十八章:优化理论与迭代求解实战指南
2026/10/10 7:20:45 网站建设 项目流程

1. 为什么这一章值得单独拿出来聊

如果你正在啃人工智能方向的数学基础,学到第十八章的时候,大概率已经过了“向量、矩阵、导数、概率”这些入门关卡,开始进入一个让人又爱又恨的区域——优化理论与迭代求解。这一章在很多教材里被安排在靠后的位置,不是因为它不重要,恰恰相反,是因为它需要前面十几章的知识做铺垫:线性代数给你空间结构,微积分给你梯度信息,概率统计给你不确定性视角,而第十八章要做的事情,是把这些工具拧成一股绳,去解决一个核心问题:怎么让模型自己找到最好的那组参数。

我见过太多人学到这里就卡住了。前面的章节还能靠背公式、刷题应付,到了这一章,突然满屏都是迭代公式、收敛条件、步长选择,符号一多就晕。但我想说的是,这一章其实是整个数学基础里最接近工程实战的部分。你后面要理解的梯度下降、动量法、自适应学习率、甚至二阶优化方法,根都在这里。学透了,你看论文里的优化器推导就不会发怵;学不透,后面调参就只能靠试。

这篇文章我会按照一个完整的项目拆解思路来写:先讲清楚这一章的整体设计逻辑,再逐块拆解核心细节和实操要点,然后给出可以跟着走的推导与验证流程,最后把我自己踩过的坑和常见问题整理成速查表。适合正在自学人工智能数学基础的学习者,也适合已经工作但想回头补优化理论底子的开发者。不管你用的是哪本教材,只要章节主题落在“优化与迭代”这个范围内,下面的内容都能对上号。

2. 第十八章的整体设计与思路拆解

2.1 这一章到底在解决什么问题

先把问题说清楚。人工智能里绝大多数模型的训练过程,本质上都可以写成一个目标函数的极小化问题:

找到参数 θ,使得损失函数 L(θ) 的值尽可能小。

这句话听起来简单,但里面藏着三个难点。第一,L(θ) 通常是非凸的,你没法像解一元二次方程那样直接写出解析解。第二,参数维度可能非常高,几十万甚至上亿维,你不可能遍历整个空间。第三,你只能通过有限次的计算去逼近最优解,每一次计算都有成本。第十八章要教的,就是在这样苛刻的条件下,如何设计一套迭代策略,一步步逼近最优解。

所以这一章的核心不是某个孤立的公式,而是一整套方法论:从最朴素的梯度下降出发,分析它为什么慢、为什么震荡、为什么会在某些方向上卡住,然后针对每个问题引入改进手段。教材把它放在第十八章,是因为你需要先有梯度的概念、有矩阵的特征值分析能力、有概率期望的基础,才能理解这些改进背后的数学动机。

2.2 章节结构的常见编排逻辑

不同教材的具体小节划分会有差异,但第十八章这类“优化”主题的章节,通常遵循一条主线:从一阶方法到二阶方法,从固定步长到自适应步长,从确定性优化到随机优化。我把它拆成四个层次来理解。

第一个层次是基础迭代框架。包括梯度下降的基本形式、步长(学习率)的作用、迭代终止条件。这一层是地基,后面所有方法都是在这个框架上做文章。

第二个层次是收敛性分析。教材会引入凸函数、Lipschitz 连续、强凸性这些概念,用来回答“迭代序列到底能不能收敛到最优解”“收敛速度有多快”。这部分是很多人觉得最抽象的地方,但它是你判断一个优化方法好坏的唯一理论依据。

第三个层次是加速与改进方法。动量法解决震荡问题,牛顿法利用二阶信息加速收敛,拟牛顿法在不想算 Hessian 矩阵时做近似。每一个方法都对应一个具体的痛点。

第四个层次是随机优化。当样本量巨大时,每次计算全量梯度不现实,随机梯度下降(SGD)及其变体成为主流。这一层直接对接深度学习实战。

2.3 为什么这样编排是合理的

你可能会问,为什么不直接讲 SGD,非要绕这么大一圈?我的理解是,没有前面的铺垫,你根本无法理解 SGD 为什么有效、什么时候会失效。举个例子,SGD 的梯度是真实梯度的无偏估计,这个“无偏”的概念来自概率论;SGD 的方差会导致在最优解附近震荡,这个现象需要用收敛性分析里的步长衰减策略来解释;而动量法和自适应方法,本质上是在用历史梯度信息降低方差。如果你跳过前面的层次直接看 SGD,就只能是“知道有这么个东西”,而无法在它出问题时做出正确判断。

从工程角度看,这套编排还有一个好处:它训练你一种分层思考的习惯。遇到一个优化问题,先判断它是不是凸的,再判断梯度好不好算,再决定用一阶还是二阶方法,最后考虑要不要随机采样。这个决策链条,比记住十个优化器的名字有用得多。

3. 核心细节解析与实操要点

3.1 梯度下降的数学本质与步长选择

梯度下降的迭代公式大家都熟:

# 梯度下降核心迭代(伪代码) theta = initialize_parameters() for i in range(max_iter): grad = compute_gradient(L, theta) # 计算当前点的梯度 theta = theta - lr * grad # 沿负梯度方向更新 if norm(grad) < tol: # 收敛判断 break

但真正决定它能不能work的,是那个lr(学习率)。从数学上看,梯度方向是函数值上升最快的方向,负梯度方向就是下降最快的方向。但“最快”是局部的,只在一个足够小的邻域内成立。步长太大,你会一步跨过谷底,甚至直接发散;步长太小,收敛慢到让人怀疑人生。

这里有个基于常见实践的判断方法:用二阶泰勒展开估算安全步长。在点 θ 附近,损失函数可以近似为:

L(θ - lr·g) ≈ L(θ) - lr·gᵀg + (lr²/2)·gᵀHg

其中 H 是 Hessian 矩阵。要让更新后的值小于当前值,需要 lr < 2·gᵀg / (gᵀHg)。如果 H 的最大特征值是 λ_max,那么一个保守的安全上界是 lr < 2/λ_max。这就是为什么在强凸问题里,学习率的上限和函数的曲率直接相关。

实操中你没法每次算 Hessian,所以常见的做法是:先用一个较小的学习率(比如 0.01 或 0.001)跑几十步,观察损失曲线。如果损失稳定下降但很慢,可以适当放大;如果损失上下跳动甚至变大,立刻缩小。我个人的经验是,初始学习率宁可小一点,因为放大比缩小容易判断——损失下降太慢你还能等,损失爆炸了你只能重来。

注意:学习率不是越调越好的,它和 batch size、数据归一化程度、网络结构都有关。换一个数据集,之前调好的学习率很可能就不适用了。

3.2 收敛性分析里的几个关键概念

教材在这一部分会引入一堆定义,我挑三个最关键的讲清楚它们到底在说什么。

凸函数:函数图像上任意两点连线都在函数图像上方。凸函数的局部最小值就是全局最小值,这是优化里最理想的情况。判断方法之一是看 Hessian 矩阵是否半正定。深度学习里的损失函数几乎都不是凸的,但很多理论分析仍然在凸假设下做,因为它是理解非凸问题的起点。

Lipschitz 连续:函数的变化速度有上界。对梯度来说,如果梯度是 L-Lipschitz 连续的,意味着函数曲率不会无限大,这保证了梯度下降在步长小于 1/L 时一定能让函数值下降。这个条件在分析收敛速度时反复出现。

强凸性:比凸性更强,要求函数不仅向上弯,而且弯曲程度有下界。强凸函数的收敛速度可以是线性的(每步误差按固定比例缩小),而普通凸函数可能只有次线性收敛。这就是为什么强凸问题好优化。

这三个概念的关系可以这样理解:凸性保证你能找到全局最优,Lipschitz 连续性保证你的步长有安全范围,强凸性保证你收敛得够快。教材后面所有的收敛定理,基本都是在这些条件的组合下给出的。

3.3 动量法与二阶方法的动机

动量法要解决的问题是:在狭长山谷形的损失面上,梯度下降会在陡峭方向来回震荡,在平缓方向前进缓慢。动量法引入一个速度变量 v,让它累积历史梯度:

v = beta * v + grad theta = theta - lr * v

这样在梯度方向一致的方向上,速度会越来越快;在反复变号的方向上,正负抵消,震荡被抑制。beta 通常取 0.9,这个值的含义是“大约累积最近 10 步的梯度”。

牛顿法则换了个思路:不用一阶近似,用二阶近似直接跳到二次函数的极小点。更新公式是 θ = θ - H⁻¹g。它的收敛速度是二次的,非常快,但代价是要计算和求逆 Hessian 矩阵,维度高时计算量是 O(n³),根本扛不住。所以实际中用的是拟牛顿法,比如 BFGS,通过历史梯度信息去近似 Hessian 的逆,把每次迭代的复杂度降到 O(n²)。

这里有个重要的取舍逻辑:一阶方法每步便宜但需要更多步,二阶方法每步贵但需要更少步。在参数维度极高(比如百万维以上)的深度学习场景里,二阶方法基本不可用,所以大家还是用一阶方法加各种加速技巧。但在一些中小规模的传统优化问题里,拟牛顿法往往是首选。

3.4 随机梯度下降的方差问题

SGD 每次只用一个或一小批样本估计梯度,好处是单步计算极快,坏处是梯度有噪声。这个噪声不是bug,某种程度上是feature——它可以帮助跳出较差的局部极小点。但在最优解附近,噪声会导致参数一直在最优解周围跳动,无法精确收敛。

解决办法是步长衰减。常见策略有:

衰减策略公式特点
分段常数lr = lr0 / 2^k简单,需要手动设衰减点
逆时衰减lr = lr0 / (1 + k·t)平滑,k 控制衰减速度
指数衰减lr = lr0 · γ^t衰减快,γ 通常取 0.95 左右
余弦退火lr = lr0 · (1 + cos(πt/T))/2周期性重启,实战效果好

理论上的要求是步长满足 ∑lr_t = ∞ 且 ∑lr_t² < ∞,这样既能保证走到最优解附近,又能让方差影响趋于零。实际中你不需要严格满足这个条件,但理解它有助于你判断衰减策略是否合理。

4. 实操过程与核心环节实现

4.1 从零实现梯度下降并验证收敛

光看公式不够,我建议你亲手写一遍。下面是一个完整的验证流程,用二次函数做测试,因为二次函数的 Hessian 是常数,收敛行为可以精确预测。

import numpy as np # 定义目标函数:f(x) = 0.5 * x^T A x - b^T x # 梯度:Ax - b,最优解:A^{-1}b A = np.array([[4.0, 1.0], [1.0, 3.0]]) b = np.array([1.0, 2.0]) x_star = np.linalg.solve(A, b) def f(x): return 0.5 * x @ A @ x - b @ x def grad(x): return A @ x - b # 计算 A 的特征值,用于确定安全学习率 eigvals = np.linalg.eigvalsh(A) lr_max = 2.0 / eigvals.max() print(f"最大特征值: {eigvals.max():.4f}, 安全学习率上界: {lr_max:.4f}") # 梯度下降 x = np.array([0.0, 0.0]) lr = 0.3 # 故意取一个接近上界的值 for i in range(50): g = grad(x) x = x - lr * g if i % 10 == 0: print(f"iter {i}: x = {x}, f(x) = {f(x):.6f}, |grad| = {np.linalg.norm(g):.6f}") print(f"最优解: {x_star}, 最终解: {x}")

跑完这段代码你会发现,当 lr 接近 2/λ_max 时,收敛会出现明显的震荡,因为不同方向上的收敛速度差异被放大了。条件数(λ_max/λ_min)越大,这种差异越明显。这就是为什么数据归一化和白化处理对优化如此重要——它们本质上是在改善问题的条件数。

4.2 动量法的对比实验

在上面的代码基础上加一个动量项,观察收敛曲线的变化:

x = np.array([0.0, 0.0]) v = np.array([0.0, 0.0]) lr = 0.3 beta = 0.9 for i in range(50): g = grad(x) v = beta * v + g x = x - lr * v if i % 10 == 0: print(f"iter {i}: f(x) = {f(x):.6f}")

实测下来,动量法在前几步可能比纯梯度下降还慢一点(因为速度需要累积),但中后期会明显加速,尤其是在条件数大的方向上。beta 取 0.9 是个稳妥的起点,如果你发现震荡还是厉害,可以降到 0.8;如果收敛太慢,可以升到 0.95,但不要超过 0.99,否则动量会过大导致 overshoot。

4.3 用 SGD 训练一个简单模型

把上面的思路搬到实际场景。假设你有一个线性回归任务,数据量很大,用全量梯度不现实,那就用 mini-batch SGD:

# 模拟数据 np.random.seed(42) N, D = 10000, 20 X = np.random.randn(N, D) true_w = np.random.randn(D) y = X @ true_w + 0.1 * np.random.randn(N) # Mini-batch SGD w = np.zeros(D) lr = 0.01 batch_size = 64 epochs = 20 for epoch in range(epochs): indices = np.random.permutation(N) for start in range(0, N, batch_size): idx = indices[start:start+batch_size] Xb, yb = X[idx], y[idx] grad = Xb.T @ (Xb @ w - yb) / len(idx) w = w - lr * grad # 每个 epoch 结束后计算全量损失 loss = np.mean((X @ w - y) ** 2) print(f"epoch {epoch}: loss = {loss:.6f}")

这里有几个实操细节值得注意。第一,每个 epoch 前要 shuffle 数据,否则梯度的随机性会引入系统性偏差。第二,batch_size 的选择会影响梯度的噪声水平,64 到 256 是比较常用的范围。第三,学习率要和 batch_size 配合,一般 batch_size 翻倍时学习率也可以适当放大,但这不是线性关系,需要实验确定。

4.4 收敛判断与早停策略

训练过程中怎么判断该停了?常见的有三种判据:

  • 梯度范数:当 ||grad|| 小于某个阈值时停止。适合凸问题,非凸问题里梯度小不一定代表到了好解。
  • 参数变化量:当 ||θ_new - θ_old|| 小于阈值时停止。简单直接,但可能因为学习率太小而误判。
  • 验证集损失:当验证损失连续若干轮不再下降时停止。这是深度学习里最常用的早停策略,因为它直接关注泛化性能。

我个人的习惯是组合使用:先设一个最大迭代次数兜底,再用验证集损失做早停,同时监控梯度范数作为辅助判断。单独用任何一个都容易出问题。

5. 常见问题与排查技巧实录

5.1 损失不下降或下降极慢

这是最常见的问题。排查顺序应该是:先检查学习率是不是太小,再检查梯度计算是否正确,然后看数据有没有归一化。

现象可能原因排查方法解决手段
损失几乎不变学习率过小打印梯度范数,看更新量放大学习率 10 倍试
损失下降但极慢条件数太大计算 Hessian 特征值比数据归一化、用动量法
损失忽大忽小学习率过大观察损失曲线震荡幅度缩小学习率或加衰减
损失变成 NaN梯度爆炸打印梯度最大值梯度裁剪、缩小学习率

5.2 收敛到非最优的驻点

非凸优化里,梯度为零的点可能是局部极小、鞍点、甚至是极大点。鞍点在低维里少见,但在高维空间里非常普遍。如果你发现梯度范数很小但损失明显不是最优,很可能卡在鞍点了。

处理办法:加一点随机扰动,或者用 SGD 的噪声自然跳出。动量法也有帮助,因为累积的速度可以冲过鞍点附近的平坦区域。我在实践中发现,在训练初期用较大的学习率配合动量,能显著降低卡在鞍点的概率。

5.3 学习率衰减的时机把握

衰减太早,模型还没充分探索就慢下来了;衰减太晚,最优解附近震荡太久。一个实用的经验法则是:在验证损失进入平台期时衰减。具体操作是设一个 patience 参数,比如连续 5 个 epoch 验证损失没有改善,就把学习率乘以 0.5。这样比固定步数衰减更自适应。

另外,余弦退火里的周期性重启(warm restart)在实战中效果不错,它让学习率周期性回升,帮助模型跳出当前的局部区域。如果你用的是主流深度学习框架,这些策略通常都有现成实现,不需要自己写。

5.4 批量大小与学习率的配合

这两者的关系经常被误解。理论上,如果 batch size 扩大 k 倍,梯度方差缩小 k 倍,学习率可以放大 sqrt(k) 倍左右。但实际中还要考虑泛化性能——有研究表明,过大的 batch size 会导致模型泛化变差,因为梯度的噪声本身有正则化效果。

我的建议是:从小 batch 开始调,找到能稳定收敛的学习率,再逐步放大 batch size 并相应调整学习率。不要一上来就用超大 batch,那样你既不知道学习率该设多少,也失去了噪声带来的探索能力。

5.5 二阶方法什么时候值得用

虽然深度学习里基本用一阶方法,但在以下场景里,二阶或拟牛顿方法值得考虑:参数维度在几千到几万之间、目标函数比较光滑、需要高精度解、计算 Hessian 的成本可以接受。比如一些传统的机器学习模型(逻辑回归、条件随机场)在小规模数据上的训练,L-BFGS 往往比 SGD 收敛更快更稳。

判断标准很简单:算一次 Hessian 近似的时间,是否小于它帮你省下的迭代时间。如果维度太高,答案通常是否定的。

6. 我在这部分踩过的坑和几点体会

学这一章的时候,我最大的误区是“只看公式不动手”。收敛性定理的证明看懂了,不代表你能在实际问题里判断收敛行为。后来我强迫自己每学一个方法就用代码实现一遍,在二次函数、Rosenbrock 函数、简单的神经网络上各跑一次,才真正建立起直觉。

另一个坑是过度迷信理论上的最优步长。理论给出的 2/λ_max 是上界,实际中你往往需要用比它小不少的值才能稳定。因为理论假设是精确梯度、精确二次模型,而实际中有噪声、有非线性、有数值误差。

还有一点体会是,优化方法的选择没有银弹。Adam 在很多任务上开箱即用,但在某些图像分类任务上反而不如带动量的 SGD。原因在于自适应方法会缩放每个方向的步长,这在某些损失面上会破坏方向的一致性。所以不要只用一个优化器,多试几个,用验证集说话。

最后分享一个我常用的调试技巧:把优化过程可视化。二维问题直接画等高线和迭代轨迹,高维问题画损失曲线和梯度范数曲线。很多时候你看数字看不出问题,一画图就一目了然了。这个习惯帮我省下了大量盲目调参的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询