Python 梯度下降练习:损失、步长与学习率
当每一步都看得见时,梯度下降才更容易被信任。这些练习把损失、梯度、更新和停止规则拆开,再展示一个糟糕的学习率如何让它们全部失效。
练习:计算平方误差损失
损失必须返回一个数字。这里的模型是过原点的单个权重,预测值等于 w 乘以 x,误差就是它与 y 的差。
def loss(w, xs, ys):
return sum((w * x - y) ** 2 for x, y in zip(xs, ys)) / len(xs)
print(loss(1, [1, 2], [2, 4]))当前模型的得分
练习:计算梯度
对平方误差关于 w 求导。系数 2 和额外的 x 不是装饰,它们决定更新的方向和大小。
def gradient(w, xs, ys):
return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)
print(gradient(1, [1, 2], [2, 4]))指向改进方向的负斜率
练习:记录损失历史
只打印最终权重,看不出学习是否稳定。每次更新前后把损失加入列表,检查序列。健康的训练整体应下降。
def gradient(w, xs, ys):
return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)
def loss(w, xs, ys):
return sum((w * x - y) ** 2 for x, y in zip(xs, ys)) / len(xs)
xs = [1, 2]
ys = [2, 4]
w = 1.0
history = []
for _ in range(3):
history.append(loss(w, xs, ys))
w -= 0.1 * gradient(w, xs, ys)
print([round(value, 2) for value in history])损失应该下降
练习:比较两个学习率
用同样步数分别运行较小和较大的学习率。不要为了证明发散而打印一个巨大的数字,改为检查最终值是否仍在合理范围内。
def gradient(w, xs, ys):
return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)
def final_weight(rate, xs, ys):
w = 1.0
for _ in range(5):
w -= rate * gradient(w, xs, ys)
return w
xs = [1, 2]
ys = [2, 4]
print(abs(final_weight(0.1, xs, ys)) < 10)
print(abs(final_weight(1.5, xs, ys)) < 10)一个收敛,一个失控
练习:加入停止规则
固定步数只是预算,不是收敛判断。当梯度或参数更新量低于容差时停止,同时保留步数上限作为硬保护。
def gradient(w, xs, ys):
return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)
xs = [1, 2]
ys = [2, 4]
w = 1.0
for _ in range(100):
g = gradient(w, xs, ys)
if abs(g) < 0.01:
break
w -= 0.1 * g
print(abs(gradient(w, xs, ys)) < 0.01)信号足够小就停止
你的练习清单
- 先定义损失,再调步长。
- 手工更新一次,检查梯度符号。
- 记录损失历史,而不只看最终权重。
- 同时设置步数上限和收敛容差。
- 特征范围差异很大时先缩放。
常见问题
梯度下降一定能找到最优答案吗?
不一定。它可能停在局部最小、发散,或因缩放不佳而变慢。对于简单的凸损失,合适步长有更强的保证。
学习率怎么选?
先取较小值,观察损失曲线,再按数量级调整。不存在通用的最佳值。
为什么开始时梯度是负的?
当前预测低于目标,因此增大权重会降低损失。负梯度提示更新应该增大 w。
一次只练一个更新
12 道题,覆盖损失、梯度、学习率、发散与停止规则。
继续阅读: