🤖 第3章 最小二乘学习法
📄 一句话概括

20 个点、5 个多项式基,最小二乘的残差和每一列基的内积都是 8e-15 量级。 这不是巧合,是最小二乘的定义本身:解是 y 在基张成的空间上的投影,残差就是那条垂线。 所以「误差平方和最小」和「投影」是同一件事的两种说法。分解式也成立: 22.240 等于 20.669 加 1.571。

📝 本节和前面几节的关系

第 2 章把模型定下来了:f 等于 θ 转置乘 φ(x)。这一节回答第二个问题:给定模型,参数怎么求。 答案分三步:定准则(平方损失)、写矩阵(A θ 减 y)、解方程(正规方程)。 三步都能写成一个公式,这是全书最省事的一节。

一、准则:为什么偏偏是平方

最理想的准则是期望风险——模型在整个数据分布上的平均误差。但它算不出来,因为你不知道那个分布。 退一步,用训练集上的平均误差代替,这叫经验风险。 再把「误差」具体化成 (f 减 y) 的平方,就得到了最小二乘。 为什么偏偏是平方?三个理由。一是它可导,而且导数就是线性的,好算; 二是它对应高斯噪声下的最大似然;三是它有闭式解,一步到位。 代价写在明面上:平方把大误差罚得极重。一个偏离 10 倍的点,贡献的误差是正常点的 100 倍, 所以最小二乘天生怕离群点。第 6 章整章都在处理这件事。 写成式子就是 J(θ) 等于 Σ (f 减 y) 的平方。 把 f 代进去,J 是 θ 的二次函数,一个开口向上的抛物面。 抛物面只有一个最低点,所以「最优解唯一」在这一步就定了。

二、写成矩阵,然后解方程

把 n 条样本摞起来:A 的第 i 行是 φ(x_i) 转置,y 是所有 y_i 摞成的列。 于是目标函数就是 ||A θ 减 y|| 的平方。 对 θ 求导、令其为零,得到正规方程 A 转置 A 乘 θ 等于 A 转置 y。 解出来就是 A 转置 A 的逆乘 A 转置 y。 代码里就一行:np.linalg.lstsq(A, y)。 我调 lstsq 而不是手写求逆,是因为手写求逆在 A 转置 A 病态时会放大误差。 lstsq 内部用分解法,稳得多。 「病态」是什么意思?如果两列基几乎线性相关,A 转置 A 就接近奇异, 它的逆会把输入的一点误差放大成巨大的系数。Demo 里多项式基先把 x 除以 3,就是为了压住这个风险。 如果 A 转置 A 真的不可逆(列数多于行数、或者有完全重复的列),解不唯一, 这时用伪逆取「系数范数最小」的那一个。np.linalg.pinv 干的就是这件事。

三、几何:这其实是一次投影

把 A 的每一列看成一个方向,它们张成一个子空间。A θ 能到达的所有点,都在这个子空间里。 而 y 一般不在里面——它多出来的那部分是噪声,是模型解释不了的东西。 最小二乘要做的事,是在子空间里找一个离 y 最近的点。最近的点就是垂足,所以解就是投影。 Demo 验了两件事。第一,残差和每一列基的内积都是 8e-15,也就是零,这就是「垂直」的代数定义。 第二,||y|| 平方等于 ||拟合值|| 平方加 ||残差|| 平方。 22.240 等于 20.669 加 1.571,勾股定理在 n 维空间里照样成立,前提就是那个直角。 这个视角给出一个很有用的判断:残差平方和的下界,等于 y 到子空间的距离。你换基、加基, 只能改变子空间,不能改变 y。所以「模型上限」不是玄学,它就是那段垂直距离。 残差下不去的时候,不要再调参了,该换模型或者加特征。

四、实测:闭式解和梯度法差多少

同一份数据,一边用 lstsq 一次算完,一边从全零出发做批量梯度下降。

梯度下降步数 与闭式解的距离 训练 MSE
200 2.28e-01 0.0787
2000 1.62e-02 0.0786
20000 8.93e-14 0.0786
2 万步之后距离 8.93e-14,收敛到闭式解。但 200 步时还差 0.23,而两者的训练 MSE 已经几乎一样。
这个现象要记住:目标函数在最优点附近是平的,参数差得远、误差却差不多。
所以「损失不再降」不等于「已经到最优」,这是调参时最常见的误判之一。
闭式解的代价是解一个 b 乘 b 的方程组,复杂度是 b 的三次方。
b 是基的个数,通常几十到几千,这个代价可以接受。真正吃不消的是 n——A 转置 A 要扫全部 n 条样本。

五、这一节的边界

最小二乘只解决「平方损失加线性模型」这一种组合。换损失(l1、Huber、hinge),闭式解立刻消失, 要迭代。换模型(核、层级),要么换个形式解,要么根本没有解。 但它值得单独占一章,因为它给了三样别人给不了的东西: 一个闭式解、一个几何解释、一组能严格证明的性质。 后面所有方法,都是在这三样上做减法的结果。 所以读第 4 到 6 章时,心里要一直挂着这个基准。 「如果不加约束、不用别的损失,答案就是这一次投影」。别的章节讲的都是「为什么不能这样、 代价是什么、怎么补救」。

六、动手 Demo:闭式解与投影:残差与列空间正交到 8e-15,梯度下降 2 万步后差 9e-14

20 个点、5 个多项式基,真值参数是 0.5、负 1.2、0.8、0.3、负 0.4,噪声标准差 0.3。 先用 lstsq 求闭式解,检查残差与每一列基的内积; 再从全零出发做批量梯度下降,步长按 A 转置 A 的最大特征值取倒数,看它要走多少步才追平闭式解。

A = np.vander(x / 3.0, b, increasing=True)   # 20 乘 5 的基矩阵
w = np.linalg.lstsq(A, y, rcond=None)[0]      # 闭式解

r = y - A @ w                                  # 残差
print(np.abs(A.T @ r).max())                   # 应该近似 0:残差与列空间正交

lr = 1.0 / np.linalg.eigvalsh(A.T @ A / n).max()   # 步长按最大特征值定
z = np.zeros(b)
for _ in range(20000):
    z -= lr * (A.T @ (A @ z - y) / n)          # 批量梯度下降

实跑输出:

--------------------------------------------------------------------
闭式解与投影:残差与列空间正交到 8e-15,梯度下降 2 万步后差 9e-14
--------------------------------------------------------------------
  数据                     20 个点,5 个多项式基
  真值参数                 0.5、-1.2、0.8、0.3、-0.4
  正规方程残差             7.11e-15(应该近似 0)
  分解                     ||y|| 平方 22.240 等于拟合 20.669 加残差 1.571

  梯度下降步数   与闭式解的距离   训练 MSE
  ---------------------------------------
           200         2.28e-01     0.0787
          2000         1.62e-02     0.0786
         20000         8.93e-14     0.0786

  **残差和每一列基的内积都是 7.9e-15 量级——这就是「正交」。**
  **最小二乘解就是 y 在 A 的列空间上的投影,残差是那条垂线。**
  **垂线最短,所以「误差平方和最小」和「投影」是同一件事的两种说法。**
  **再看那个分解:22.240 等于 20.669 加 1.571。**
  **它成立的原因同样是正交——勾股定理在 n 维里照样有效。**
  **所以最小二乘只能解释「y 落在列空间里的那部分」,剩下的它无论如何都解释不了。**
  **这是理解「模型上限」最直接的一把尺子:残差不是没调好,是根本不在这个空间里。**
  **梯度下降那边:200 步差 2.28e-01,20000 步差 8.93e-14——收敛了,但走得很慢。**
  **步长是我按 A 转置 A 的最大特征值取倒数算的,不是试出来的。**
  **闭式解一次算完,代价是要解一个 5 乘 5 的方程组。**
  **这个对比划定了第 3.3 节的边界:基的个数 b 小的时候闭式解完胜,b 大到解方程组吃不消时才轮到迭代法。**
  要说明:数据是我按线性模型**造的**,噪声 0.3;**梯度下降从全零出发,没有加速技巧,就是最朴素的批量梯度法。**

第一行那个除以 3 别省略。不缩放的话,x 的五次方在 x 等于 3 时是 243, 基的各列量级差两个数量级以上,A 转置 A 的条件数会到上万。 缩放不改变模型能表示的函数,只改变求解的数值稳定性。 rcond=None 也别省。它控制「多小的奇异值算作零」,默认行为在不同 numpy 版本里改过。 写死 None 是让它用机器精度的默认阈值,行为可复现。 残差内积那个 8e-15 是本节的核心证据。它不是「很小所以可以忽略」,而是「按浮点精度就是零」。 double 大约有 16 位有效数字,量级 1 的数算出 1e-15,就是舍入误差的地板。 梯度下降那边的步长不是试出来的。对二次函数,步长取「最大特征值取倒数」时收敛是有保证的, 再大就会震荡甚至发散。这个判据在第 3.3 节还会用到,只是那里换成了「步长要随批大小调整」。 要说明:数据是我按线性模型造的,噪声 0.3。 梯度下降从全零出发、没有动量、没有加速,就是最朴素的那种。 所以 2 万步这个数字只说明「朴素方法慢」,不说明「梯度法不行」。

30 秒实验

  1. 把 phi 的缩放去掉(不除以 3),打印 A 转置 A 的条件数。 它变大之后,闭式解的系数和真值差多少?
  2. 把基从 5 个加到 8 个,残差平方和会怎么变?再想想「残差下界」这个说法还成立吗。
  3. 把梯度下降的步长乘 2,会发生什么?这就是「步长过大」最直观的形态:损失先降后爆。

常见误区

  1. 「最小二乘就是求导等于零」。 求导等于零只是找到驻点,能说它是最小值,靠的是目标函数是凸二次函数。换成非凸问题, 同样的方程解出来可能是最大值。
  2. 「残差越小模型越好」。 残差可以靠加基无限压小,压到零都有可能,但那是把噪声也拟合了。判断标准是留出集上的误差, 不是训练残差。
  3. 「手写求逆和 lstsq 是一回事」。 数值上不是。求逆会把条件数平方,lstsq 用 QR 或 SVD,只把条件数放大一倍。 条件数上万时,两者差好几个有效数字。
  4. 「最小二乘要求 y 服从正态分布」。 它不要求。正态假设只在「把它解释成最大似然」时才需要; 作为纯粹的代数方法,它对 y 的分布没有任何假设。
自测三问(点开对答案) 1. 为什么残差一定和 A 的每一列正交? —— **因为如果不是,就还能沿着那一列再挪一点,让距离更短。** **「已经最短」和「垂直」是同一件事。**代数上就是正规方程 A 转置乘 r 等于零, 它正是求导等于零的结果。 2. 分解式 ||y|| 平方等于 ||拟合|| 平方加 ||残差|| 平方,为什么成立? —— **因为拟合值和残差正交,勾股定理成立。** **这个式子告诉我们:y 的总能量被分成「模型能解释的」和「解释不了的」两块,** **中间没有交叉项。**所以「解释率」有一个天然定义,就是拟合那部分占总量的比例, 这就是 R 平方。 3. 梯度下降 200 步时参数还差 0.23,为什么训练误差已经和最优差不多了? —— **因为目标函数在最优点附近是二次的、而且是扁的。**参数沿着长轴方向移动,损失几乎不变。 **这在工程上是好消息(不要求参数精确),也是坏消息(损失曲线看不出有没有收敛)。**

最小二乘解能写出闭式,所以它的性质可以被完整推出来。 下一节把三条最要紧的量出来:加基必降、帽子矩阵幂等、无偏但有方差。 这三条决定了能拿最小二乘做什么、不能做什么。

相关笔记