算法可视化与交互学习平台

线性模型中的梯度下降Gradient Descent for a Linear Model

用最小线性回归例子理解 loss、gradient、参数更新,并通过实时调参观察训练曲线与拟合效果。

OptimizationBeginnerFree
PyodideKernel
1

目标理解

这个模块聚焦三个关键词:lossgradient参数更新。我们从最小线性模型 y = wx + b 出发,用梯度下降训练参数,并观察损失如何一步步下降。

2

交互直线:y = wx + b

单独调节斜率 w 和截距 b,观察直线如何围绕坐标轴旋转和上下平移。左侧改参数,右侧图会实时变化。

Parameter Panel
2 Params
3

模型与损失

线性模型用参数 w、b 生成预测值,再用均方误差衡量预测与真实值之间的差异。

y^=wx+b,L=1ni=1n(yiy^i)2\hat{y} = wx + b,\quad L = \frac{1}{n}\sum_{i=1}^n (y_i - \hat{y}_i)^2
ww
bb
LL
ww
权重 / 斜率
weight / slope
bb
偏置 / 截距
bias / intercept
LL
均方误差损失
mean squared error loss
4

梯度下降更新

梯度告诉我们 loss 增长最快的方向,因此更新时要沿负梯度方向移动。学习率 η 控制每一步走多远。

wt+1=wtηLw,bt+1=btηLbw_{t+1}=w_t-\eta\frac{\partial L}{\partial w},\qquad b_{t+1}=b_t-\eta\frac{\partial L}{\partial b}
η\eta
Lw\frac{\partial L}{\partial w}
Lb\frac{\partial L}{\partial b}
η\eta
学习率
learning rate
Lw\frac{\partial L}{\partial w}
损失对 w 的梯度
gradient of loss with respect to w
Lb\frac{\partial L}{\partial b}
损失对 b 的梯度
gradient of loss with respect to b
5

公式 + 代码 解释

1. 我们到底在做什么?

目标很简单:用一条直线去拟合一组数据。模型写成 y = wx + b,其中 w 是斜率,b 是截距。

在代码里,这一步就是:

preds = w * xs + b

这里的 wb 是要学习的参数。一开始它们通常只是随便给一个初值,所以预测往往并不准确。

2. 怎样衡量预测得好不好?

我们用损失函数 loss 衡量“模型错了多少”。这个例子使用的是均方误差:

L = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2

对应代码:

errors = preds - ys
loss = np.mean(errors * errors)

含义是:先计算每个样本的误差,再把误差平方后取平均。

  • loss 大,说明模型还差得远。
  • loss 小,说明预测已经更接近真实数据。

3. 怎样知道参数该往哪边改?

核心问题不是“要不要改参数”,而是“wb 应该往哪个方向改,才能让 loss 变小”。答案就是看梯度。

w 求导,可得到:

\frac{\partial L}{\partial w} = \frac{1}{n}\sum_{i=1}^{n} 2(wx_i + b - y_i)x_i

代码对应:

dw = np.mean(2.0 * errors * xs)

b 求导,可得到:

\frac{\partial L}{\partial b} = \frac{1}{n}\sum_{i=1}^{n} 2(wx_i + b - y_i)

代码对应:

db = np.mean(2.0 * errors)

可以把它们理解成:

  • dw:如果改变 wloss 会怎么变。
  • db:如果改变 bloss 会怎么变。

误差越大,梯度通常也会越大;而 x 的取值越大,对 w 的影响也会更明显。

4. 梯度下降到底在“下降”什么?

梯度指向的是 loss 增长最快的方向,所以如果我们想让 loss 下降,就要沿着梯度的反方向更新参数:

w_{t+1} = w_t - \eta \frac{\partial L}{\partial w}, \qquad b_{t+1} = b_t - \eta \frac{\partial L}{\partial b}

代码就是:

w = w - lr * dw
b = b - lr * db
  • lr 就是学习率,也就是公式里的 \eta
  • 减号表示我们要往“下坡方向”走,而不是往上坡走。

5. 整个训练过程其实就是重复同一件事

训练并不神秘,本质上就是在循环里不断重复四步:

  1. 先预测:preds = w * xs + b
  2. 再计算误差和损失:errorsloss
  3. 然后计算梯度:dwdb
  4. 最后更新参数:wb

对应代码中的训练循环:

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 衡量“错多少”,再用梯度判断“往哪改”,然后按学习率控制步长,一步步更新参数,让误差持续减小,最后得到更合适的直线。

6

为什么“碗形”的是损失,不是模型

1. 先纠正一个常见误区

在线性模型里,\hat{y} = wx + b 本身是一条直线,不是碗形。

真正呈现“碗形”的,是损失函数。例如单个样本下:

L = (wx + b - y)^2

这里的 L 是关于参数的函数。只要把某个样本的 xy 和当前的 b 固定下来,它就会变成一个关于 w 的二次函数。

2. 为什么它一定像碗?

因为它本质上是“线性函数再平方”:

线性 + 平方 = 二次函数

比如固定 x = 2b = 1y = 5,那么:

L(w) = (2w + 1 - 5)^2 = (2w - 4)^2 = 4w^2 - 16w + 16

展开后可以直接看到最高次项是 4w^2,系数为正,所以曲线一定开口向上,也就是碗形。

3. 梯度和“往哪边走”有什么关系?

对上面的损失函数求导:

\frac{dL}{dw} = 8w - 16
  • w < 2 时,梯度为负,说明继续增大 w 会让损失下降。
  • w > 2 时,梯度为正,说明应该减小 w
  • w = 2 时,梯度为 0,正好落在最低点。

这就是梯度下降能工作的原因:不是模型本身是碗,而是平方误差把优化目标变成了碗,梯度就能稳定地把参数往最低点推过去。

4. 和更一般的线性回归怎么对应?

上面的例子只是把问题简化成“只看一个样本、只看一个参数”。回到完整线性回归时,均方误差仍然是关于参数的凸函数,所以整体思路不变:

  • 先看当前参数下损失有多大。
  • 再看梯度的方向和大小。
  • 沿负梯度方向更新参数,让损失继续下降。

所以要记住的不是“y = wx + b 是碗”,而是“误差平方把损失函数变成了碗”。

7

固定样本下的损失曲线

固定一个样本,只看参数 w 的变化时,平方误差会形成开口向上的二次曲线。最低点对应梯度为 0,左右两侧梯度符号相反,因此梯度下降总能知道该往哪边走。
当前 Python 代码尚未生成可显示的图表。
Chart Python
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')
8

为什么叫“梯度”

1. 从“斜率”开始理解

在一元函数里,比如 y = f(x),我们用导数 \frac{dy}{dx} 描述“x 变化一点,y 会变化多少”。它本质上就是斜率。

所以在一维里,可以把导数理解成“当前这条曲线有多陡,以及是向上还是向下”。

2. 多个变量时,一个斜率就不够了

在线性回归里,损失不是只依赖一个变量,而是依赖参数 wb

L = L(w, b)

这时我们不能只问“整体斜率是多少”,而要分别问:

  • 沿着 w 方向走一点,L 怎么变?
  • 沿着 b 方向走一点,L 怎么变?

于是就会得到两个偏导数:

\frac{\partial L}{\partial w},\qquad \frac{\partial L}{\partial b}

它们分别表示每个方向上的“局部斜率”。

3. 梯度就是“所有方向的斜率集合”

把这些方向上的斜率放在一起,就得到梯度:

\nabla L = \begin{bmatrix} \frac{\partial L}{\partial w} \\ \frac{\partial L}{\partial b} \end{bmatrix}

这时它不再是一个单独的数,而是一个向量。你可以把它理解成:

  • 每个分量告诉你某个方向有多陡。
  • 整个向量一起告诉你:在当前点,函数往哪里上升最快。

4. 为什么叫“梯度”

“梯度”这个词本来就表示“变化的快慢和方向”。比如温度梯度表示哪里升温最快,海拔梯度表示哪里上升最快。

放到优化问题里,梯度描述的是:

函数在当前点增长最快的方向,以及这个增长有多快。

5. 为什么梯度下降要减去梯度

如果把损失函数想成一座山:

  • 山的高度就是 loss。
  • 你当前所在的位置就是参数点 (w, b)

那么梯度告诉你的其实是:“往哪个方向走,会上坡最快”。但训练的目标不是上山,而是下山,所以更新时要沿着反方向走:

\theta_{t+1} = \theta_t - \eta \nabla L

这里的减号就是关键:梯度指向最陡上坡,而我们要走最陡下坡。

6. 一句话把三个概念打通

概念本质
导数一维情况下的斜率
偏导某一个方向上的斜率
梯度所有方向斜率组成的向量

所以最值得记住的一句话是:

梯度之所以叫“梯度”,是因为它描述的是多维空间里函数变化最快的方向和速度。

实验

实验台

调整学习率、训练轮数和初始参数,观察 loss 下降、参数收敛以及拟合直线如何变化。

Parameter Panel
4 Params
Initial w
w0 · [-5, 5], step 0.1
-1.5
Initial b
b0 · [-5, 5], step 0.1
2
拓展
延伸提问