那条线是用公式算出来的;现在 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 运行时,可能需要几秒钟。
练习本课
12 道题,作答后立即看到解释。