第 47 课 · 进阶 · 19 分钟

Python 梯度下降:一步一步靠近

沿梯度向下走,看看学习率为什么重要。

梯度下降损失学习率收敛

那条线是用公式算出来的;现在 Maya 想一步步走下山去找它。学习率决定她是走到,还是冲过头。步子太大时会怎样?

梯度下降就是一个循环:测量损失,求出让损失增加的方向,然后朝反方向移动。这个想法用几行 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 的斜率。梯度为负,说明增大 w 会降低损失,所以更新时减去梯度。系数 2 来自对平方项求导。

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]))

损失上升最快的方向

走一步,再重复

学习率控制步子大小。每轮都在新的 w 上重新计算梯度,再更新一次。对于这条干净的数据线,估计值会逐渐靠近 2,而整个过程不需要提前知道答案。

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(3):
    w -= 0.1 * gradient(w, xs, ys)
print(round(w, 3))

小步走向更好的拟合

学习率会让循环失控

学习率太小会浪费迭代;太大会冲过谷底甚至发散。始终检查损失曲线,设置步数上限,并在更新量或改进量可以忽略时停止。

def gradient(w, xs, ys):
    return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)

def train(learning_rate, xs, ys):
    w = 1.0
    for _ in range(5):
        w -= learning_rate * gradient(w, xs, ys)
    return w

xs = [1, 2]
ys = [2, 4]
print(round(train(0.1, xs, ys), 2))
print(abs(train(1.5, xs, ys)) > 1000)

同一组数据,完全不同的结果

动量平滑重复方向

动量保留一个会累积过去梯度的速度。它可以沿着一致的方向走得更快,并抑制噪声造成的来回摆动,但也可能在最小值附近过冲。动量是另一个需要验证的超参数,不保证更快收敛。

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 = 0.0
velocity = 0.0
for _ in range(10):
    velocity = 0.9 * velocity - 0.1 * gradient(w, xs, ys)
    w += velocity
print(round(w, 3))

先累积方向,再迈步

特征尺度会改变梯度

把输入乘以十会大幅改变损失曲面和梯度。同一个学习率不再等价。应标准化特征,或按尺度调整学习率,并在长时间训练前检查最初几个损失值。

def gradient(w, xs, ys):
    return 2 * sum((w * x - y) * x for x, y in zip(xs, ys)) / len(xs)

for factor in [1, 10]:
    xs = [factor, 2 * factor]
    ys = [2 * factor, 4 * factor]
    print(factor, round(gradient(1.0, xs, ys), 1))

同一个学习率并不具备尺度不变性

本课要点

  • 损失把预测误差变成优化目标。
  • 梯度指向损失增加的方向,所以更新时相减。
  • 学习率控制每次更新移动多远。
  • 学习率过大可能发散,过小则可能很慢。
  • 记录损失,并设置步数上限或收敛阈值。
  • 动量累积方向;特征缩放会改变有效学习率。

常见错误

  • 目标是最小化损失,却把梯度加上去。
  • 参数已经改变,却继续使用旧梯度。
  • 只看最终参数,不看损失曲线。
  • 改变特征单位后继续沿用原学习率,并假定收敛行为不变。
轮到你

在同一组数据上以学习率 0.1 更新十次,打印最终权重,保留三位小数。

动手试试

改改变量、先预测输出,再运行代码。第一次点击“运行代码”时会下载 Python 运行时,可能需要几秒钟。

浏览器内 Python本地运行 · 无需账号
首次运行会从 CDN 下载 Pyodide。修改后点击“运行代码”。

练习本课

12 道题,作答后立即看到解释。

查看 12 道题答案开始练习
← 线性回归朴素贝叶斯 →