
本文详解如何修复自定义梯度下降实现中因梯度推导错误、参数更新逻辑混乱导致的θ值发散或不收敛问题,通过使用解析梯度函数、消除冗余循环、统一数值类型和合理设置学习率,使算法稳定收敛至目标损失函数的多个局部极小点。
本文详解如何修复自定义梯度下降实现中因梯度推导错误、参数更新逻辑混乱导致的θ值发散或不收敛问题,通过使用解析梯度函数、消除冗余循环、统一数值类型和合理设置学习率,使算法稳定收敛至目标损失函数的多个局部极小点。
在实现梯度下降时,核心挑战往往不在于代码结构本身,而在于梯度是否准确、更新是否一致、数值是否稳健。原代码中存在多个关键缺陷:首先,gradient() 函数试图基于输入 X, Y, Ysol 等动态变量计算梯度,但实际损失函数 erreurJ(theta, sigma) 仅依赖于参数 theta = [w, b] 和激活函数 sigma,与中间变量 X, Y, h 无直接可微关系;其次,pasfixe() 中嵌套了对 theta[i] 的逐分量更新循环,并在每次更新后重新计算 h 和 Y,破坏了梯度方向的一致性——这本质上不是标准梯度下降,而是不稳定的坐标下降变体;最后,未对 theta 做 np.array 类型转换,导致 np.linalg.norm(grad(theta)) 在 grad() 返回列表时可能出错,且缺乏对浮点精度和溢出的防护。
正确的做法是:完全脱离输入数据流,直接对损失函数 J(θ) 求解析梯度。本例中,erreurJ(theta, sigma) 明确为:
[
J(w,b) = \frac{1}{4}\left[\sigma(b)^2 + \sigma(w+b)^2\right], \quad \text{其中 } \sigma(z) = z^2 - 1
]
代入并求偏导(经符号推导验证),得到精确梯度:
def grad(theta):
w, b = theta[0], theta[1]
return np.array([
2*b**3 + 3*b**2*w + 3*b*w**2 - 2*b + w**3 - w, # ∂J/∂w
b**3 + 3*b**2*w + 3*b*w**2 - b + w**3 - w # ∂J/∂b
])
该梯度函数仅接收 theta,输出 np.array,确保后续 np.linalg.norm() 和向量化更新(theta - eta * gradient)严格数学等价。
此外,需重构优化主循环以符合标准梯度下降范式:
- 移除所有内部
for i in range(len(theta))循环; - 每次迭代执行单次、同步、向量化参数更新;
- 将
theta初始化为np.array(..., dtype=np.float64),避免整数截断与类型隐式转换; - 学习率
eta应适度调小(如0.01),原0.1易导致步长过大、越过极小点; - 发散检测应使用
np.any(np.abs(theta) > 100)替代逐元素判断。
最终,5000 个随机初始点 [w₀, b₀] ∈ [-3, 3]² 经修正后的 pasfixe() 运行后,其收敛结果(如 [-2,1]、[2,-1]、[0,-1]、[0,1])能清晰映射到二维初始空间,形成分区域着色的收敛盆地图——这正是非凸损失函数多极小点特性的直观体现,也验证了梯度计算与更新逻辑的正确性。
关键总结:
✅ 使用解析梯度(而非启发式近似)是保证收敛性的数学基础;
✅ 向量化参数更新(theta -= eta * grad(theta))维持梯度方向完整性;
✅np.array+float64类型保障数值稳定性;
✅ 学习率需依损失曲率调整,过大会震荡,过小则收敛慢;
❌ 避免在梯度计算中混入非可微中间变量(如X,Ysol);
❌ 禁止在单次迭代内分量更新并重算前向过程——这会破坏梯度一致性。










