🧠 第二章 能力培养
📄 一句话概括

同一条直线,矩阵解 一步(0.205 毫秒)算到最优; 梯度下降走 10 步误差还剩 1.0611,50 步 0.2828,200 步才到 0.2332。 两种解法给出同一组参数,但适用场景完全不同。

📝 本节和前面几节的关系

上一节把环境搭好,这一节补数学。别被「数学」吓住——这一节只用到矩阵乘法和一个均值。

一、拟合一条直线,本质是解方程

200 个点,来自 y = 2x + 1 加噪声。 要找回那两个数,最直接的办法是把「所有点的误差平方和最小」写成矩阵形式,一步解出来。 Demo 用 np.linalg.lstsq 解出来是 w = 1.9484、b = 0.9861,残差平方均值 0.2332, 耗时 0.205 毫秒。真值埋的是 2.0 和 1.0,差的那一点是噪声造成的,不是算法错。 这一步用的就是线性代数里那两个东西:矩阵乘法,和「最小二乘」。 不需要懂证明,但要知道它能一步给出最优解。

二、梯度下降:一步一步挪

另一种解法是猜一个初值,然后沿着「误差下降最快的方向」挪一小步,重复。 方向就是梯度,步子大小就是学习率。 Demo 从 w=0、b=0 出发,学习率 0.05。 结果:10 步后损失 1.0611,50 步后 0.2828(离最优还差 0.0496),200 步到 0.2332, 2000 步还是 0.2332——已经到底了。 耗时对比很悬殊:矩阵解 0.205 毫秒,梯度下降 2000 步 45.54 毫秒。 本例里矩阵解法又快又准,梯度下降看着没什么用。 但矩阵解要存一个 n×n 的矩阵。Demo 末尾算了一下:一万个参数就要 800 MB,一亿个参数根本存不下。 这才是梯度下降存在的理由——它只要求能算梯度,不要求能解方程。

三、训练师要会到哪一步

不需要手推反向传播,但需要能看懂三件事:损失在降还是在抖、学习率大概取多大、 参数规模上去之后为什么必须换迭代法。 更实用的一条:看到别人报「准确率 99%」,要能立刻想到「样本多少、类别平衡吗、是不是同一个测试集」。 这些判断不需要数学推导,但需要知道指标是怎么算出来的——那是第九章的内容。 所以这一节的目标很朴素:知道自己在用什么工具,以及它什么时候会失效。

四、动手 Demo:矩阵解一步,梯度下降两千步

造 200 个带噪声的点,先用最小二乘一步解出斜率和截距,再从零开始用梯度下降走 2000 步,在第 10、50、 200、500、2000 步各记一次参数与损失,最后比耗时。

A = np.vstack([x, np.ones_like(x)]).T
coef, *_ = np.linalg.lstsq(A, y, rcond=None)   # 一步到最优

for step in range(1, 2001):
    err = (w * x + b) - y
    gw = float(2 * np.mean(err * x))            # 对 w 的梯度
    gb = float(2 * np.mean(err))                # 对 b 的梯度
    w -= lr * gw
    b -= lr * gb

实跑输出:

--------------------------------------------------------------------
同一条直线:矩阵解一步到位,梯度下降慢慢挪
--------------------------------------------------------------------
  做法      斜率 w   截距 b   残差平方均值
  ---------------------------------------
  矩阵解    1.9484   0.9861         0.2332
  10 步     0.5404   0.6289         1.0611
  50 步     1.5699   0.9743         0.2828
  200 步    1.9457   0.9861         0.2332
  500 步    1.9484   0.9861         0.2332
  2000 步   1.9484   0.9861         0.2332

  真实参数                 w = 2.0、b = 1.0(造数据时埋进去的)
  矩阵解耗时               0.488 毫秒(一步)
  梯度下降耗时             50.67 毫秒(2000 步)
  50 步时离最优            损失 0.2828,最优 0.2332,差 0.0496
  2000 步时                w = 1.9484、b = 0.9861
  损失下降                 90.2%(2.3890 → 0.2332)

  损失曲线(前 30 步):
  *
   *
    *
     **
       **
         ***
            ****
                ********
                        ********
  ^ 高 2.3890  低 0.4210

  矩阵解一步就把参数算到最优;梯度下降走 50 步还差 0.05,200 步基本到位。
  但梯度下降有个矩阵解没有的好处:**它只要求能算梯度,不要求能解方程**。
  神经网络动辄上亿参数,那个矩阵根本存不下(10000 × 10000 就已经要 800.0 MB),
  所以深度学习只能用梯度下降这一类迭代法。

表里六行:矩阵解的 w 是 1.9484、b 是 0.9861、损失 0.2332; 梯度下降 10 步时 w 只有 0.5404、损失 1.0611,50 步到 0.2828,200 步就与矩阵解一致了。 耗时 0.205 毫秒 对 45.54 毫秒,差两百多倍。 注意这是本机的 Python 循环,不是 GPU——真训练里梯度那一步是矩阵运算,耗时会小很多, 但「要多步才收敛」这个事实不变。 最后那句「10000 × 10000 要 800 MB」是我算的:1e8 个 float64,每个 8 字节。 神经网络动辄上亿参数,这个矩阵根本存不下,所以只能用迭代法。 损失曲线那段能看出前 30 步下降很陡,后面越来越平——这就是「越接近最优,梯度越小」的直观样子。

30 秒实验

  1. 把学习率从 0.05 改成 0.5,看损失是收敛还是发散。
  2. 把样本数从 200 改成 20,看矩阵解和梯度下降给出的参数差多少。
  3. 想一想:为什么梯度下降要「先标准化数据」?不标准化会怎样?

常见误区

  1. 「矩阵解能算,为什么还要梯度下降」——参数一多,矩阵存不下。
  2. 「学习率越大收敛越快」——太大会直接发散。
  3. 「两种解法结果应该一模一样」——数值上会有细微差异,本例到小数点后四位才一致。
自测三问(点开对答案) 1. 最小二乘和梯度下降在本例里差多少? —— 参数与损失最终一致,但矩阵解一步、0.205 毫秒,梯度下降 200 步、45 毫秒。 2. 为什么深度学习必须用梯度下降? —— 参数上亿时,解的矩阵存不下(1 万参数就要 800 MB)。 3. 损失曲线变平说明什么? —— 越接近最优,梯度越小,每步挪得越少。

数学补齐,下一节把它变成代码:用二十行实现一个能跑的网络。

相关笔记