专项练习 · 10 分钟阅读

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 道题,覆盖损失、梯度、学习率、发散与停止规则。

打开练习

继续阅读: