4. 步长过大导致振荡发散的参数调试
问题定位:固定步长下的残差振荡
梯度类反演迭代式为 mk+1 = mk − α·gk。当 α 固定且偏大时,即使梯度方向正确,迭代点也会在极小值两侧来回跨越,表现为残差曲线呈锯齿状上下跳动而不下降。判断依据不是"残差大",而是残差序列出现持续的正负交替增量:ΔΦk = Φk+1 − Φk 符号反复翻转且幅度不衰减。
本例用一个一维二次型目标 Φ(m) = 0.5·a·(m − m*)²(取 a = 200,m* = 3.0,初值 m₀ = 0)做教学演示。梯度 g = a·(m − m*),理论稳定条件为 0 < α < 2/a = 0.01。取 α = 0.015 即越过稳定边界,必然振荡发散;α = 0.009 则单调收敛。这组参数可直接手算验证。
可运行的调试脚本
下面脚本对同一目标函数分别执行固定步长与衰减步长,并打印每步的残差增量符号,用于确认振荡判据。所有参数在脚本内定义,可直接运行。
import math
A = 200.0 # 曲率
M_STAR = 3.0 # 真解
M0 = 0.0 # 初值
def phi(m):
return 0.5 * A * (m - M_STAR) ** 2
def grad(m):
return A * (m - M_STAR)
def run(alpha0, gamma, iters=15, tol=1e-8):
m = M0
hist = []
alpha = alpha0
for k in range(iters):
g = grad(m)
m_new = m - alpha * g
d = phi(m_new) - phi(m)
hist.append((k, alpha, m_new, phi(m_new), d))
if abs(d) < tol:
break
m = m_new
alpha *= gamma
return hist
def report(name, hist):
print(f"--- {name} ---")
flips = 0
prev_sign = 0
for k, a, m, p, d in hist:
s = 1 if d > 0 else (-1 if d < 0 else 0)
if prev_sign != 0 and s != 0 and s != prev_sign:
flips += 1
prev_sign = s
print(f"k={k:2d} alpha={a:.5f} m={m:8.4f} phi={p:10.4f} dPhi={d:+.4f}")
print(f"符号翻转次数 = {flips}\n")
report("固定步长 alpha=0.015", run(0.015, 1.0))
report("衰减步长 alpha0=0.015, gamma=0.7", run(0.015, 0.7))
预期现象(由给定参数直接推出,非实测):固定步长组每步 dPhi 符号交替、绝对值逐次增大,翻转次数接近迭代数;衰减组在若干步后 dPhi 单调趋零,翻转次数为 0 或 1。若固定组翻转次数明显少于迭代数,说明 α 尚未越过稳定边界,应加大 α 复现问题。
步长与收敛速度的记录表
调试时把不同策略的迭代步数与最终残差并列记录,才能判断"稳定"与"快"的取舍。下表为按上述脚本逻辑可复现的示例记录格式(数值为示例,需以实际运行结果填写):
| 策略 | α₀ | γ | 达到 Φ<1e-6 的步数 | ΔΦ 符号翻转 | 结论 |
|---|---|---|---|---|---|
| 固定步长 | 0.015 | 1.0 | 未达到 | 持续翻转 | 振荡发散,弃用 |
| 固定步长 | 0.009 | 1.0 | 约 12 步 | 0 | 稳定但偏慢 |
| 衰减步长 | 0.015 | 0.7 | 约 9 步 | 0 | 先大步后稳,推荐 |
| 衰减步长 | 0.015 | 0.5 | 约 14 步 | 0 | 过保守,收敛变慢 |
γ 的选择存在权衡:γ 接近 1 保留大步长但可能仍振荡,γ 过小则退化为小步长慢收敛。实用做法是先按理论界
α < 2/L(L 为梯度 Lipschitz 常数)取 α₀ 的 1.2~1.5 倍以暴露问题,再用 γ∈[0.6, 0.8] 衰减,并监控 ΔΦ 符号翻转是否消失。衰减步长只是缓解手段。若缩小步长后残差仍不下降,问题多半不在步长,而在梯度方向或目标函数量级,应转向梯度校验或权重归一化排查,不要继续无限制减小 α。