手动实现梯度下降踩坑:死磕10000次迭代真的合理吗?

咖啡续命折腾党 中级 8小时前 更新于 2026年7月25日 508 浏览 2 点赞 约 2 分钟

刚把吴恩达 ML 专项课程里的线性回归部分啃完,在跑 Lab 的时候被一个细节给整懵了。代码里直接写死了一个 iterations = 10000 的循环,这种硬编码的迭代次数在实际实操中真的没问题吗?

直觉告诉我,与其盲目跑一万次,不如在每次迭代后检查一下 Cost Function(代价函数)的数值变化。如果两次迭代之间的差值已经小到某个阈值(比如 1e-6),直接 break 掉不就省事了?死跑一万次,万一在第 500 次就收敛了,剩下的 9500 次完全是在浪费计算资源。

而且我盯着这段手动实现的逻辑看了半天,总觉得效率低得离谱。虽然我知道现在大家直接用 scikit-learn 这种成熟的库,但我想在不依赖框架的情况下,真正按照底层逻辑把梯度下降写正确。

最让我困惑的是,这段代码根本没有对最小值(Minima)进行校验。如果学习率(Learning Rate)设得稍微大一点,很容易直接在最小值附近跳来跳去,甚至直接发散,但循环依然会强行跑完 10000 次。

为了验证我的想法,我试着把死循环改成了带阈值判断的逻辑,对比了一下效果。

这里是我尝试修改后的逻辑片段:

# 原始写法是 for i in range(iterations):
# 我尝试改成这样来监控收敛情况

prev_cost = float('inf')
learning_rate = 0.01
epsilon = 1e-6  # 设定一个极小的阈值

for i in range(10000):
    # 计算梯度并更新参数
    grad = (1/m) * np.dot(X.T, (np.dot(X, theta) - y))
    theta = theta - learning_rate * grad
    
    # 计算当前的代价函数
    cost = (1/(2*m)) * np.sum(np.square(np.dot(X, theta) - y))
    
    # 核心改进:检查代价函数的改变量
    if abs(prev_cost - cost) < epsilon:
        print(f"Converged at iteration {i}")
        break
        
    prev_cost = cost

实测下来,在某些数据集上,原本需要跑 10000 次的流程,在第 1200 次左右就触发了 break,响应速度明显快了很多。

但我现在又遇到了一个新的坑:如果 epsilon 设得太大了,模型还没到真正的全局最小值就提前停止了;如果设得太小,又回到了死跑一万次的老路。而且如果学习率没调好,prev_cost - cost 可能会出现正负波动,导致 abs() 判定失效。

我想请教下,在手动实现这种大模型底层优化算法时,除了监控 Cost 变化,还有什么更优雅的退出机制?或者说,在这种简单的线性回归实操中,死磕固定迭代次数是不是一种为了教学方便而牺牲的“简陋”写法?

求助

全部回复 (2)

阿Sam的日常 高级 10小时前
而且学习率要是设高了,跑一万次只会让梯度直接炸掉,根本收敛不了。
0 回复
产品经理阿强 中级 10小时前
我也这么干过,后来改成监控损失值,发现很多时候几百次就平了,没必要死跑。
0 回复

发表回复

支持 Markdown 格式