梯度下降
不再盲目地试,而是每一步都算出往哪个方向改参数、改多少。从斜率讲起理解导数,手算梯度,写出梯度下降的循环,再亲眼看看学习率太小、太大和数据没有标准化时会发生什么。
- 约 45 分钟
- 难度:入门
- 实测:2026-09-14 numpy 2.5,固定随机种子
上一课用网格搜索找到了最好的直线,但也看到了它的死路:参数一多,要试的组合就是天文数字。
想象你蒙着眼睛站在一座山的山坡上,要走到山谷的最低点。你看不见整座山,但能用脚感觉到脚下的地面往哪边倾斜。那就往下坡的方向迈一小步,再感觉一下,再迈一步。一直走下去,最后就到了谷底。
这就是梯度下降(gradient descent)。"山"是损失,你站的位置是当前的参数,"往哪边倾斜"就是梯度。
导数:就是斜率
先只看一个参数 w,固定 b = 0。把 w 往右挪一点点,损失会变多少?变化量除以挪动的距离,就是损失在这一点的斜率。用数值的办法直接量一量:
def mse(w, b, x=area, y=price):
return np.mean((w * x + b - y) ** 2)
w, b = 1.0, 0.0
for h in [0.1, 0.01, 0.001]:
slope = (mse(w + h, b) - mse(w, b)) / h
print(f" w 往右挪 {h:<6},损失变化 / 挪动距离 = {slope:10.2f}")
== 1. 在 w=1.0, b=0 这一点,损失对 w 的斜率
w 往右挪 0.1 ,损失变化 / 挪动距离 = -7300.21
w 往右挪 0.01 ,损失变化 / 挪动距离 = -8256.23
w 往右挪 0.001 ,损失变化 / 挪动距离 = -8351.83
挪动的距离越小,量出来的斜率越稳定,逐渐接近一个固定的值。这个"挪动距离无限小时的斜率",就是导数。它告诉我们两件事:
- 符号:这里是负数,意思是
w往右挪(变大),损失会变小。所以应该把w调大。 - 大小:绝对值很大,意思是这里的坡很陡,损失对
w非常敏感。
梯度:每个参数一个导数
我们有两个参数,w 和 b。对每个参数分别求导(求 w 的导数时把 b 当成常数,反过来也一样),得到的两个数合在一起,叫做梯度。
用数值的办法量太慢,也不精确。好在均方误差的导数可以直接写出公式。损失是 平均((w×x + b - y)²),按求导的规则:
损失对 w 的导数 = 平均( 2 × (w×x + b - y) × x )
损失对 b 的导数 = 平均( 2 × (w×x + b - y) )
不需要记这个推导,只要知道它就是"把上面量斜率的过程,用代数一次算准"。写成代码:
def gradients(w, b, x=area, y=price):
error = w * x + b - y
dw = np.mean(2 * error * x) # 损失对 w 的导数
db = np.mean(2 * error) # 损失对 b 的导数
return dw, db
== 2. 公式算出的梯度:dw=-8362.45,db=-79.98
dw = -8362.45,和上面数值量出来的结果(挪动 0.001 时是 -8351.83)非常接近。这是一个好习惯:自己推导的梯度公式,用数值的办法核对一下,能发现很多错误。下一课写反向传播时,我们还会这样做。
梯度下降的循环
梯度指向损失增加最快的方向。我们要让损失减小,就往梯度的反方向走一小步:
w = w - 学习率 × dw
b = b - 学习率 × db
学习率(learning rate)控制每一步迈多大。从 w = 0, b = 0 出发,重复这一步:
def descend(lr, steps, x=area, y=price, report=()):
"""从 w=0, b=0 出发走 steps 步。发散时返回 None。"""
w, b = 0.0, 0.0
for step in range(1, steps + 1):
dw, db = gradients(w, b, x, y)
w -= lr * dw
b -= lr * db
if abs(w) > 1e6: # 越走越远,已经发散了
print(f" 第 {step} 步:w 已经变成 {w:.3g},发散了")
return None
if step in report:
print(f" 第 {step:5d} 步:w={w:7.4f} b={b:7.3f} 损失 {mse(w, b, x, y):10.2f}")
return w, b
这就是训练的全部核心逻辑。第 09 模块训练 GPT 时,循环的结构一模一样,只是参数多了、算梯度的办法换成了下一课的反向传播。
走一走:没那么顺利
用学习率 0.00005 走 2 万步:
== 3. 学习率 0.00005,走 20000 步
第 1 步:w= 1.4804 b= 0.014 损失 244.38
第 10 步:w= 1.3935 b= 0.014 损失 164.22
第 100 步:w= 1.3934 b= 0.027 损失 164.18
第 1000 步:w= 1.3922 b= 0.155 损失 163.81
第 5000 步:w= 1.3871 b= 0.711 损失 162.27
第 20000 步:w= 1.3695 b= 2.615 损失 157.43
第一步,损失就从几千降到了 244;10 步之后降到 164。然后就几乎不动了:走了 2 万步,损失才降到 157,而上一课网格搜索找到的最低损失是 143。b 更是慢得出奇,2 万步只从 0 挪到了 2.6,离最好的值(约 14.5)还很远。
为什么会这样?下面两个实验会揭开原因。
学习率:太小太慢,太大就炸
换几个学习率,都走 1000 步:
== 4. 不同的学习率(都走 1000 步)
学习率 1e-05 :w=1.3932 b=0.041 损失 164.14
学习率 5e-05 :w=1.3922 b=0.155 损失 163.81
学习率 9e-05 :w=1.3912 b=0.268 损失 163.50
第 115 步:w 已经变成 1.03e+06,发散了
学习率 0.0001 :发散
学习率越大,走得越快,b 挪得越远。可学习率从 0.00009 提高到 0.0001,只差了一点点,就彻底炸了:第 115 步,w 变成了一百多万。
发散是这样发生的:w 方向的坡太陡(上面量到斜率是 -8362),步子稍微大一点,一步就跨过了谷底,落到对面更高的地方;那里的坡更陡,下一步跨得更远……每一步都比上一步离谷底更远,最后越来越大,直到数值溢出。
问题的根源在于:w 方向的坡太陡,b 方向的坡太缓。dw 是 -8362,db 只有 -80,差了一百倍。学习率要小到不让 w 发散,b 就只能挪得极慢。一个学习率没法同时照顾两个方向。
标准化:让每个方向的坡差不多陡
w 方向的坡为什么这么陡?因为面积的数值很大,几十到一百多平方米。梯度公式里 dw = 平均(2 × 误差 × x),x 越大,dw 就越大。
办法是把面积换算成"均值为 0、标准差为 1"的数,这叫标准化:
mean, std = area.mean(), area.std()
area_n = (area - mean) / std
标准化之后,面积的数值都在 -2 到 2 左右,w 和 b 两个方向的坡差不多陡了。这时学习率可以放心地用 0.1,只走 100 步:
== 5. 面积标准化之后(均值 98.0,标准差 32.1),学习率 0.1 只走 100 步
第 1 步:w= 8.0605 b= 27.588 损失 13360.27
第 10 步:w=35.9749 b=123.129 损失 381.15
第 50 步:w=40.3018 b=137.939 损失 143.05
第 100 步:w=40.3023 b=137.941 损失 143.05
换算回原单位:w=1.2571 b=14.806
50 步就到了最低点,损失 143.05,比上一课网格搜索的 143.1 还低一点(网格搜索每隔 0.01 才试一次,最好的点可能落在两个格子之间)。换算回原来的单位,w = 1.2571, b = 14.806。
之前 2 万步都做不到的事,标准化之后 50 步就完成了。这是训练神经网络时一个非常实用的经验:输入的数值范围要差不多。以后你会看到的各种"归一化"(LayerNorm、BatchNorm),出发点都和这里一样。
小结一下梯度下降
- 损失对每个参数的导数合起来叫梯度,它指向损失增加最快的方向。
- 每一步都往梯度的反方向走一小步,步长由学习率决定。
- 学习率太小,走得慢;太大,会发散。它是训练中最需要调的一个数。
- 输入的数值范围差别很大时,训练会很难调,先做标准化。
我们的直线只有两个参数,梯度公式可以手推。可一个神经网络有成百上千个参数,一层套一层,手推每个参数的导数是不可能的。下一课讲怎么让计算机自动算出所有参数的梯度:反向传播。
练习
- 在标准化的数据上,把学习率改成 0.5、1.0、1.1,各走 100 步,看看结果。临界的学习率大约是多少?
- 不做标准化,只用学习率 0.00009,要走多少步,损失才能降到 144 以下?估算一下,再运行验证。
- 数值求导时,
h越小越好吗?把h改成1e-10、1e-15,看看量出来的斜率。结果是不是反而变差了?想一想为什么(提示:计算机里的小数精度是有限的)。
自测
1. 梯度下降为什么要往梯度的"反方向"走?
梯度指向损失增加最快的方向。我们的目标是让损失减小,所以要往相反的方向走。
2. 学习率只从 0.00009 提高到 0.0001,训练就发散了。发生了什么?
在 w 方向,损失的坡非常陡。学习率稍微大一点,一步就跨过了最低点,落到对面更高的地方,那里的坡更陡,下一步跨得更远。每一步都比上一步离最低点更远,最后数值越来越大,直到溢出。
3. 为什么对面积做了标准化之后,训练快了几百倍?
没有标准化时,面积的数值很大,w 方向的梯度比 b 方向大了一百倍。学习率只能迁就陡的 w 方向,设得很小,结果 b 方向走得极慢。标准化后两个方向的坡差不多陡,可以用大得多的学习率,两个参数都能很快走到最低点。
提问与讨论
这一课没看懂的地方,在这里问。看到别人的问题,也欢迎你来回答。
提问 +3 积分,回答别人 +6 积分。内容经审核后公开。
正在加载讨论…