算法可视化与交互学习平台
线性模型中的梯度下降Gradient Descent for a Linear Model
用最小线性回归例子理解 loss、gradient、参数更新,并通过实时调参观察训练曲线与拟合效果。
目标理解
这个模块聚焦三个关键词:loss、gradient、参数更新。我们从最小线性模型 y = wx + b 出发,用梯度下降训练参数,并观察损失如何一步步下降。
交互直线:y = wx + b
单独调节斜率 w 和截距 b,观察直线如何围绕坐标轴旋转和上下平移。这里的 w 是数据空间里的直线斜率:x 每增加 1,y 改变多少;它不是梯度下降里的 ∇L。
模型与损失
线性模型用参数 w、b 生成预测值,再用均方误差衡量预测与真实值之间的差异。
梯度下降更新
梯度告诉我们 loss 在参数空间中增长最快的方向,因此更新时要沿负梯度方向移动。学习率 η 控制每一步走多远。
公式 + 代码 解释
1. 我们到底在做什么?
目标很简单:用一条直线去拟合一组数据。模型写成 y = wx + b,其中 w 是斜率,b 是截距。
在代码里,这一步就是:
preds = w * xs + b这里的 w 和 b 是要学习的参数。一开始它们通常只是随便给一个初值,所以预测往往并不准确。
2. 怎样衡量预测得好不好?
我们用损失函数 loss 衡量“模型错了多少”。这个例子使用的是均方误差:
对应代码:
errors = preds - ys
loss = np.mean(errors * errors)含义是:先计算每个样本的误差,再把误差平方后取平均。
loss大,说明模型还差得远。loss小,说明预测已经更接近真实数据。
3. 怎样知道参数该往哪边改?
核心问题不是“要不要改参数”,而是“w 和 b 应该往哪个方向改,才能让 loss 变小”。答案就是看梯度。
先换一个空间看问题
y = wx + b 描述的是数据空间:横轴是 x,纵轴是 y。训练时真正优化的是 L(w,b),这属于参数空间:横轴可以是 w,纵轴可以是 b,高度是 loss。所以 w 是直线的斜率,而 dw、db 是损失函数对参数的斜率。
对 w 求导,可得到:
代码对应:
dw = np.mean(2.0 * errors * xs)对 b 求导,可得到:
代码对应:
db = np.mean(2.0 * errors)可以把它们理解成:
dw:如果改变w,loss会怎么变。db:如果改变b,loss会怎么变。
误差越大,梯度通常也会越大;而 x 的取值越大,对 w 的影响也会更明显。
4. 梯度下降到底在“下降”什么?
梯度指向的是 loss 增长最快的方向,所以如果我们想让 loss 下降,就要沿着梯度的反方向更新参数:
代码就是:
w = w - lr * dw
b = b - lr * dblr就是学习率,也就是公式里的 \eta。- 减号表示我们要往“下坡方向”走,而不是往上坡走。
5. 整个训练过程其实就是重复同一件事
训练并不神秘,本质上就是在循环里不断重复四步:
- 先预测:
preds = w * xs + b - 再计算误差和损失:
errors、loss - 然后计算梯度:
dw、db - 最后更新参数:
w、b
对应代码中的训练循环:
for epoch in range(num_epochs):
preds = w * xs + b
errors = preds - ys
loss = np.mean(errors * errors)
dw = np.mean(2.0 * errors * xs)
db = np.mean(2.0 * errors)
w = w - lr * dw
b = b - lr * db同时程序会把每一轮的损失记录下来:
loss_history.append(...)6. 图表里应该重点看什么?
实验台里最值得观察的是 loss_history 对应的曲线。它展示的是:随着训练一轮轮进行,损失是如何变化的。
理想情况下,你会看到这样的趋势:
- 开始时
loss较大。 - 前几轮下降较快。
- 后面逐渐趋于稳定。
这说明参数正在逐步逼近更合适的值,拟合直线也在不断贴近数据点。
7. 一句话串起来
我们先用 loss 衡量“错多少”,再用梯度判断“往哪改”,然后按学习率控制步长,一步步更新参数,让误差持续减小,最后得到更合适的直线。
为什么“碗形”的是损失,不是模型
1. 先纠正一个常见误区
在线性模型里,\hat{y} = wx + b 本身是一条直线,不是碗形。
真正呈现“碗形”的,是损失函数。例如单个样本下:
这里的 L 是关于参数的函数。只要把某个样本的 x、y 和当前的 b 固定下来,它就会变成一个关于 w 的二次函数。
2. 为什么它一定像碗?
因为它本质上是“线性函数再平方”:
线性 + 平方 = 二次函数
比如固定 x = 2、b = 1、y = 5,那么:
展开后可以直接看到最高次项是 4w^2,系数为正,所以曲线一定开口向上,也就是碗形。
3. 梯度和“往哪边走”有什么关系?
对上面的损失函数求导:
- 当 w < 2 时,梯度为负,说明继续增大
w会让损失下降。 - 当 w > 2 时,梯度为正,说明应该减小
w。 - 当 w = 2 时,梯度为 0,正好落在最低点。
同理,直线的斜率是 w;而梯度下降里的梯度,是这个“碗形损失”对 w,b 的斜率。
这就是梯度下降能工作的原因:不是模型本身是碗,而是平方误差把优化目标变成了碗,梯度就能稳定地把参数往最低点推过去。
4. 和更一般的线性回归怎么对应?
上面的例子只是把问题简化成“只看一个样本、只看一个参数”。回到完整线性回归时,均方误差仍然是关于参数的凸函数,所以整体思路不变:
- 先看当前参数下损失有多大。
- 再看梯度的方向和大小。
- 沿负梯度方向更新参数,让损失继续下降。
所以要记住的不是“y = wx + b 是碗”,而是“误差平方把损失函数变成了碗”。
固定样本下的损失曲线
import numpy as np
x = 2.0
b = 1.0
y = 5.0
ws = np.linspace(-1, 5, 121)
losses = (x * ws + b - y) ** 2
gradients = 2 * (x * ws + b - y) * x
w_star = 2.0
loss_star = 0.0
plot_data = {
'data': [
{
'x': ws.tolist(),
'y': losses.tolist(),
'type': 'scatter',
'mode': 'lines',
'name': 'L(w) = (2w - 4)^2',
'line': {'color': '#0f766e', 'width': 3}
},
{
'x': [w_star],
'y': [loss_star],
'type': 'scatter',
'mode': 'markers+text',
'name': 'minimum',
'text': ['grad = 0'],
'textposition': 'top center',
'marker': {'size': 12, 'color': '#f59e0b'}
},
{
'x': ws[::20].tolist(),
'y': losses[::20].tolist(),
'type': 'scatter',
'mode': 'markers',
'name': 'sample points',
'marker': {
'size': [max(8, min(18, abs(g) * 0.6)) for g in gradients[::20]],
'color': gradients[::20].tolist(),
'colorscale': 'RdBu',
'showscale': True,
'colorbar': {'title': {'text': 'gradient'}}
}
}
],
'layout': {
'title': {'text': 'Loss over w for one sample: x=2, b=1, y=5'},
'xaxis': {'title': {'text': 'w'}},
'yaxis': {'title': {'text': 'L(w)'}},
'annotations': [
{
'x': 0.5,
'y': 9,
'text': 'gradient < 0, increase w',
'showarrow': True,
'ax': -60,
'ay': -30
},
{
'x': 3.5,
'y': 9,
'text': 'gradient > 0, decrease w',
'showarrow': True,
'ax': 60,
'ay': -30
}
]
}
}
print('For x=2, b=1, y=5:')
print('L(w) = (2w - 4)^2')
print('dL/dw = 8w - 16')
print('minimum at w = 2')两种“斜率”:直线斜率与损失斜率
1. 先保留直觉:w 确实是直线的斜率
在数据空间里,模型 y = wx + b 画出的是一条直线。这里的横轴是 x,纵轴是 y。
所以当你说“x 单位长度下,y 的变化率”时,描述的是:
也就是 x 每增加 1,y 会增加多少。这是模型在 x-y 空间里的几何关系。
2. 训练时换到了参数空间
梯度下降真正优化的不是 x 和 y,而是参数 w 和 b。训练目标可以写成:
这时我们关心的是:沿着 w 方向挪一点,loss 怎么变;沿着 b 方向挪一点,loss 又怎么变。
于是就会得到两个偏导数:
它们描述的是 loss 对参数变化的敏感程度,而不是直线本身对 x 的斜率。
3. 梯度是 loss 曲面的坡度向量
把这些方向上的变化率放在一起,就得到梯度:
这时它不再是一个单独的数,而是一个向量。每个分量告诉你某个参数方向有多陡,整个向量告诉你当前 loss 往哪里上升最快。
所以梯度长度更像“loss 曲面的坡度大小”,不是“拟合直线的斜率大小”。
4. 两个空间不要混在一起
| 空间 | 对象 | 斜率/梯度在说什么 |
|---|---|---|
| 数据空间 | y = wx + b | w 表示 x 变时 y 怎么变 |
| 参数空间 | L(w,b) | ∇L 表示参数变时 loss 怎么变 |
可以把前者想成纸上的一条直线,后者想成一座以 loss 为高度的山。训练不是在直线上走,而是在 loss 山面上移动参数。
5. 为什么梯度下降要减去梯度
梯度指向的是 loss 上升最快的方向。但训练目标是让 loss 下降,所以更新时要走反方向:
这里的减号就是关键:梯度指向最陡上坡,而我们要走最陡下坡。
6. 一句话记住
w 描述的是直线在 x-y 数据空间里有多斜;∇L 描述的是 loss 在 w-b 参数空间里有多陡。
梯度的方向与大小
1. 梯度是一个有方向和大小的向量
在线性模型训练里,我们同时更新两个参数:w 和 b。因此梯度不是一个单独的数,而是一个向量:
它像参数空间里的一支箭头:方向告诉我们 loss 往哪里上升最快,长度告诉我们当前这片 loss 曲面有多陡。
2. 方向决定往哪走
梯度 \nabla L 指向 loss 增长最快的方向。训练要让 loss 下降,所以更新时走负梯度方向:
w = w - lr * dw
b = b - lr * db这里的减号表示:不沿着上坡方向走,而是沿着下坡方向改参数。
3. 大小决定坡有多陡
梯度大小也叫梯度模长,可以写成:
- 梯度大,说明当前位置坡很陡,参数更新量容易变大。
- 梯度小,说明曲面变平,参数接近最低点时更新会自然慢下来。
4. learning rate 只是缩放器
学习率 \eta 不是用来代替梯度大小的。真正的更新量由梯度和学习率共同决定:
移动量 = 梯度大小 × learning rate。 梯度大小像坡度,learning rate 像每一步允许迈出的比例。
5. 太大和太小都会出问题
如果 |\nabla L| 极大,即使学习率不大,也可能一步更新太多,导致 loss 发散、震荡甚至 NaN,这就是梯度爆炸。
如果 |\nabla L| 接近 0,参数几乎不动,模型学得很慢甚至学不动,这就是梯度消失。
Adam 这类优化器会根据历史梯度自动调节不同方向的步长,所以可以看成更聪明的梯度下降变体。
梯度不是背公式,是看变化如何传播
1. 梯度公式不是背出来的
求梯度时,真正的问题不是“公式是什么”,而是:如果参数 w 或 b 动一点点,loss 会变多少。
所以所有梯度公式,本质上都在描述一件事:微小变化如何传播。
2. 求导公式来自微小变化
先看最简单的变化规律。如果 y = 5,无论 w 怎么变,y 都不变,所以导数是 0。
如果 y = aw,w 增加一点,y 会按 a 倍变化,所以导数是 a。
也就是说,求导公式不是凭空来的,而是常见微小变化规律的总结。
3. 推一次平方求导
平方函数最能说明“公式如何长出来”。设:
现在让 w 增加一个很小的量 \Delta w:
所以变化量是:
两边除以 \Delta w:
当 \Delta w \to 0 时,最后只剩下主要变化项:
这就是求导的核心直觉:展开微小变化,保留最主要的变化项。
4. 链式法则:变化一层层传递
训练线性模型时,w 并不是直接变成 loss。它会先影响预测,再影响误差,最后影响损失:
w → prediction → error → loss如果写成函数嵌套,就是:
总变化率等于每一层变化倍率相乘:
可以把它想成齿轮传动:第一层放大 3 倍,第二层放大 5 倍,最终就是 15 倍。
5. 回到本模块:dw 和 db 怎么来的
单个样本的平方误差可以写成:
它可以拆成三层:
prediction = w * x + b
error = prediction - y
loss = error * error因为平方的变化率是 2 * error,而 w 对 prediction 的影响倍率是 x,所以:
b 对 prediction 的影响倍率是 1,所以:
多样本时,把每个样本的梯度取平均,就得到实验台里的代码:
dw = np.mean(2.0 * errors * xs)
db = np.mean(2.0 * errors)6. 神经网络和自动求导也是同一件事
神经网络只是把这种变化传播链条拉长了:
x → Linear → ReLU → Linear → LossPyTorch 和 TensorFlow 的自动求导不是魔法。它们会记录计算图,然后从 loss 往回不断应用链式法则,这就是反向传播。
梯度公式不是“记忆公式”,而是“描述微小变化如何传递”的数学规律。
实验台
调整学习率、训练轮数、初始参数和观察 epoch,查看参数点在 w-b-loss 曲面上的下降轨迹、梯度大小变化,以及对应拟合直线和数值变化。