梯度下降自实现代码问题:优化时Loss持续上升(Python)
从你描述的现象来看,核心问题大概率是损失函数对参数的偏微分计算符号错误,导致梯度下降的更新方向完全相反了——这也解释了为什么把减号改成加号后模型能正常工作,但这其实是“负负得正”的巧合纠正了方向错误。
1. 梯度下降的核心逻辑回顾
梯度下降的本质是沿着损失函数下降最快的方向更新参数,数学上的标准表达式是:
参数 = 参数 - 学习率 × 损失函数对该参数的偏导数
这里的偏导数(梯度)指向的是损失函数上升最快的方向,所以必须用减号来让参数向损失减小的方向移动。如果你的偏微分计算出来的是梯度的相反数,那么原本的更新式tau - dif_tau/len(data)就相当于往损失上升的方向走,自然会出现loss持续上涨、参数单向变化的情况;而改成加号后,相当于tau + (-正确梯度)/len(data),刚好纠正了方向,所以模型能正常拟合。
2. 重点检查偏微分的符号
假设你的指数衰减模型形式是 y_pred = exp(-t/tau) + b,损失函数用的是均方误差(MSE):
loss = sum((y_pred - y_true)**2) / len(data)
我们手动推导一下偏微分:
- 对
b的偏导:d(loss)/db = 2 * sum(y_pred - y_true) / len(data) - 对
tau的偏导:d(loss)/dtau = 2 * sum( (y_pred - y_true) * (t/(tau**2)) * exp(-t/tau) ) / len(data)
你可以把代码中计算dif_tau和dif_b的部分和上面的推导对比,重点看符号是否一致。比如如果你的代码里把(y_pred - y_true)写成了(y_true - y_pred),那偏微分的符号就完全反了,直接导致更新方向错误。
3. 验证梯度正确性的小技巧
如果不确定解析梯度是否正确,推荐用数值微分来验证:
import math epsilon = 1e-6 # 很小的扰动 # 假设tau、b、data已定义 tau_current = tau b_current = b # 验证tau的梯度 tau_plus = tau_current + epsilon y_pred_plus = [math.exp(-t/tau_plus) + b_current for t, y in data] loss_plus = sum((yp - y)**2 for yp, y in zip(y_pred_plus, [y for t,y in data]))/len(data) tau_minus = tau_current - epsilon y_pred_minus = [math.exp(-t/tau_minus) + b_current for t, y in data] loss_minus = sum((yp - y)**2 for yp, y in zip(y_pred_minus, [y for t,y in data]))/len(data) numerical_grad_tau = (loss_plus - loss_minus)/(2*epsilon) # 对比数值梯度和你计算的dif_tau/len(data) print(f"数值梯度tau: {numerical_grad_tau}, 解析梯度tau: {dif_tau/len(data)}")
如果两者符号相反,那肯定是解析梯度的符号算错了,这就是问题根源。
4. 关于参数单向变化的解释
因为更新方向一直是损失上升的方向,不管学习率调大还是调小,参数只会持续向让loss更大的方向移动,自然不会出现震荡——这完全符合你观察到的现象,调学习率只是改变了loss上升的速度,并没有解决方向错误的核心问题。
最后补充
你提到用数据集1_7时得到了较好的拟合参数,这大概率是因为当你误把减号改成加号后,纠正了更新方向,所以模型能正常收敛到合理的参数值。只要修正偏微分的符号,用正确的梯度下降更新式(保持减号),应该能在所有数据集上得到正常的拟合效果。
内容的提问来源于stack exchange,提问作者user20988246

