📘 第4章 权重和寻找最优解
📄 一句话概括

最小二乘干的活只有一句话:找一组参数,让残差平方和最小。这句话对一条直线成立, 对「任何参数的线性组合」也成立。 这一节分两件事: 一条线到底能表达什么,以及「线性」这个词在三个不同地方出现时的区别。分不清这三个, 后面所有讨论都会糊。

📝 本节和前面几节的关系

第 3 章讲的是「人把规则写好,机器照着跑」。 这一章换了:规则不写了,改成给一堆点,让机器自己找出关系。 第一步必须先认清工具的能力边界。 一条线能干什么、不能干什么,决定了后面要不要换工具、什么时候换。

一、「线性」出现在三个地方

这个词在书上至少指三件不同的事,混起来就全乱了:

说法 意思是 例子
对参数线性 模型是参数的加权和 y = w0 + w1·x + w2·x²
对输入线性 模型是输入的加权和 y = w0 + w1·x1 + w2·x2
线性可分 一条直线能分开两类点 二维平面上的两类点
第一行和第二行常被当成一回事,其实差得很远。y = w0 + w1·x + w2·x² 画出来是弯的,
可它对参数 w 是线性的 —— 所以它照样能用最小二乘一步解出来。
这就是「多项式回归」这个名字别扭的地方:
名字里带「回归」,本质还是解一组线性方程。「线性」说的是参数,不是图像。
第三行是另一码事,说的是「能不能用一条直线把两类点分开」,跟参数怎么组合没关系。
这一节后半段会看到,这条才是真正卡人的那条。

二、最小二乘只做一件事

给一堆点 (xᵢ, yᵢ),要挑一组 w 让 Σ(yᵢ − ŷᵢ)² 最小。 对 w 求导、令导数等于 0,得到正规方程:X'Xw = X'y,解出来就是 w = (X'X)⁻¹X'y。 为什么要平方,不用绝对值?三个理由,一个比一个实际: 一,平方处处可导,求导能一路推到底;绝对值在 0 点不可导,得用迭代法慢慢磨。 二,平方有闭式解,一次矩阵运算就出来了。 三,平方和最小等价于「假设噪声是正态分布时的极大似然」 —— 这一条把最小二乘和概率论接上了, 是它能在统计里站住脚的根本。 平方也有代价:它对离群点极其敏感。残差 0.5 的点平方是 0.25, 残差 32 的点平方是 1024 —— 一个离谱的点能盖过三十个正常的点。这个坑下一节会用具体数字踩一遍。

三、模型对了,残差就该是噪声

Demo 第二段造了 60 个点,真规律 y = 2x + 1,加了标准差 0.3 的噪声。 拟合出来截距 0.9412、斜率 2.0547,R² 0.9790。 系数没有正好落在 1 和 2 上,这很正常 —— 噪声在那摆着。 更值得看的是残差:最大 0.516、最小 −0.672,均值 0.0000,符号在 60 个点里换了 25 次。 这就是「模型对了」的样子:残差在零上下乱跳,没有形状。模型把能解释的都解释了, 剩下的纯粹是噪声,而噪声按定义就是没有规律的。

四、模型错了,残差会有形状

Demo 第三段换成真规律 y = x²,还是用一条直线去配。 R² 是 0.9277 —— 看着挺好。可把残差按 x 分四段看均值:

x 区间 残差平均
0.0 到 0.5 +0.859
0.5 到 1.5 −0.411
1.5 到 2.5 −0.479
2.5 到 3.0 +0.920
两头偏正、中间偏负,一个 U 形。符号在 60 个点里只换了 8 次,而线性那一组换了 25 次。
变化次数少了三倍,因为残差不再是随机的,它有形状了。
残差有形状,说明缺的那块是系统性的,不是噪声。模型没学到的部分全堆在残差里,摆成一个 U。
这是所有拟合诊断里最有用的一条:看残差,不看 R²。
R² 高不代表模型对。0.93 这个数字只会告诉你「这条线比「直接取平均值」强不少」,
它不会告诉你「还差一个二次项」。这两个问题的答案完全可能一个好一个坏。

五、加一列 x²,问题回到线性

Demo 第四段把模型改成 y = w0 + w1·x + w2·x²。 一个字的算法都没改,只是设计矩阵多了一列 x²,结果 R² 从 0.9277 跳到 0.9957, 二次项系数 1.0293 —— 真值是 1。 残差符号换回 31 次,形状也没了。 同一个 fit 函数,换一个设计矩阵就是另一个模型,这就是第 [1] 段说的「对参数线性」。 代价在这儿:你得先想到「加一列 x²」。想不到就加不进去。 这个「想不到」正是手工特征的死穴,也是后面神经网络要接的活 —— 让它自己找该加哪一列。

六、异或:卡住的是特征,不是模型

Demo 第五段拿四个点做异或:(1,1) 和 (−1,−1) 是正类,另外两个是负类。 用 y = w0 + w1·x1 + w2·x2 去拟合 +1/−1, 三个系数全落在 1e-16 上 —— 那不是「很小」,是「零」。 结果是准确率 50%,跟扔硬币一样。原因不难看懂: x1 取 +1 和 −1 时,标签一半正一半负;x2 也一样。两个方向单独看都没有倾向, 模型只能给出一个常数。 Demo 第六段加一列 x1·x2,四个点立刻全对,那个 1.0000 就落在乘积项上:两个方向同号判正、 异号判负 —— 这正是异或的定义。 所以「线性模型分不开异或」这句话不够准确。准确的说法是: 在原来那两个特征上分不开。换一组特征,它立刻就分开了。卡住的从来不是模型,是特征。 这句话值得记住,因为它是整个深度学习的前奏: 既然特征这么要紧,那能不能不让人类去猜,让机器自己造特征?第 9 章讲的多层网络, 做的就是这件事。

七、动手 Demo:一条直线、一条抛物线、一个异或

整段代码只有一个拟合函数 fit,从头到尾没换过。变的一直是设计矩阵 X —— 这就是「对参数线性」在实际代码里的样子。 design(x, cols) 就是拼设计矩阵的:cols=(0, 1) 出 [1, x],cols=(0, 1, 2) 出 [1, x, x²]。 加一列就是加一个模型复杂度。 异或那部分直接用 2×2 的四个点,np.linalg.lstsq 求最小二乘解, 看系数就能判断模型有没有学到东西 —— 系数全是 0,等于没学。

def fit(X, y):
    """最小二乘:w = (X'X)^-1 X'y。X 第一列是常数项。"""
    w, *_ = np.linalg.lstsq(X, y, rcond=None)
    return w

def design(x, cols):
    """按 cols 指定的次数拼设计矩阵。"""
    n = len(x)
    X = np.ones((n, len(cols)))
    for j, p in enumerate(cols):
        X[:, j] = x ** p
    return X

# 同一句 fit,换个设计矩阵就是另一个模型
w_line = fit(design(x, (0, 1)),        y)   # y = w0 + w1·x
w_quad = fit(design(x, (0, 1, 2)),     y)   # 多一列 x²

# 异或:加一列乘积项
X = np.array([[1.0, a, b] for a, b in XOR])            # 分不开
X = np.array([[1.0, a, b, a * b] for a, b in XOR])     # 分开了

实跑输出:

--------------------------------------------------------------------
一条直线能管多宽
--------------------------------------------------------------------
==============================================================

[1] 「线性」在两个地方出现,别混

    说法                  意思是                        例子
    --------------------------------------------------------------------------
    对参数线性            模型是参数的加权和            y = w0 + w1·x + w2·x²
    对输入线性            模型是输入的加权和            y = w0 + w1·x1 + w2·x2
    线性可分              一条直线能把两类分开          二维平面上的两类点

  第一行和第二行常被当成一回事,其实差很远。
  y = w0 + w1·x + w2·x² 对 x 是弯的,可它对参数 w 是线性的——
  所以它照样能用最小二乘一步解出来。这叫【多项式回归】,
  名字里带「回归」,本质还是解一组线性方程。

[2] 该用直线的地方:y = 2x + 1 加噪声

  真值:截距 1.0,斜率 2.0
  拟合:截距 0.9412,斜率 2.0547
  R² = 0.9790
  残差最大 0.516,最小 -0.672,均值 0.0000
  残差符号变了 25 次(一共 60 个点,几乎隔一步就换)

  残差在零上下乱跳、没有形状,这才是「模型对了、只差噪声」的样子。
  系数也差不多落在真值附近,差的那一点是噪声本来的幅度。

[3] 该用曲线的地方,硬塞一条直线

  真值:y = x²
  拟合:截距 -1.4518,斜率 2.9950
  R² = 0.9277

  R² 是 0.93,看着还挺好。可这不是「差不多对」,是错得有结构:

    x 在 0.0 到 0.5 之间:残差平均 +0.859
    x 在 0.5 到 1.5 之间:残差平均 -0.411
    x 在 1.5 到 2.5 之间:残差平均 -0.479
    x 在 2.5 到 3.0 之间:残差平均 +0.920

  两头偏正、中间偏负,这是 U 形,不是噪声。
  线性那一组变了 25 次符号,这一组只变了 8 次,而且变化挤在 x 的两头。

  残差有形状,说明缺的那一块是【系统性】的,不是噪声。
  模型没学到的部分全都堆在残差里,摆成一条 U 形。

[4] 把 x² 加进去,同一条直线就弯了

  模型改成 y = w0 + w1·x + w2·x²
  拟合:截距 0.0659,一次项 -0.0927,二次项 1.0293
  R² = 0.9957(刚才 0.9277)
  残差符号变了 31 次

  注意:这一步没换算法。还是那句 w = (X'X)⁻¹X'y,
  只是设计矩阵多了一列 x²。
  这就是第 [1] 段说的「对参数线性」——把 x² 当成一个新变量,
  问题就回到线性最小二乘,一步解完。

  代价是:要人先想到「加一列 x²」。
  想不到就加不进去,这是手工特征的软肋,也是后面神经网络要接的活。

[5] 异或:直线怎么摆都不行

  四个点:(1,1) 和 (-1,-1) 是正类,另外两个是负类。
  拿 y = w0 + w1·x1 + w2·x2 去拟合 +1/-1:

    点          该是    模型输出      判定
    ------------------------------------------
    (+1, +1)    +1      -1.11e-16     -1
    (+1, -1)    -1      -1.11e-16     -1
    (-1, +1)    -1      +1.11e-16     +1
    (-1, -1)    +1      +1.11e-16     +1

  四个点里判对 2 个,准确率 50% —— 跟扔硬币一样。
  三个系数:0.00e+00  -1.11e-16  0.00e+00

  三个数都落在 1e-16 这一档——那不是「很小」,是「零」。
  异或的两个方向各自都没有倾向:x1 取 +1 和 -1 时,
  标签一半是正一半是负,x2 也一样。模型只能给出一个常数,
  剩下的事全扔给符号函数去猜,猜对两个纯属浮点误差的运气。

[6] 加一列 x1·x2,一刀切开

  模型改成 y = w0 + w1·x1 + w2·x2 + w3·(x1·x2)
  系数:0.0000  -0.0000  0.0000  1.0000

    点          该是    模型输出    判定
    ----------------------------------------
    (+1, +1)    +1      +1.0000     +1
    (+1, -1)    -1      -1.0000     -1
    (-1, +1)    -1      -1.0000     -1
    (-1, -1)    +1      +1.0000     +1

  四个点全对。注意那个 1.0000 落在 x1·x2 那一列上,
  意思是「两个方向同号时判正、异号时判负」——这正好就是异或的定义。

  所以「线性模型分不开异或」这句话不够准确。
  准确的说法是:在原来那两个特征上,线性模型分不开。
  换一组特征(多一个乘积项),它立刻就分开了。
  卡住的从来不是模型,是特征。

第一段那张三行表是全书的常用背景。后面每讲一个模型, 都会问一遍「它对参数线性吗」——只要答案是「是」,最小二乘这套工具就能直接拿来用。 第二段是「模型对了」的样板。残差均值 0.0000 是必然的(有常数项就一定有), 但「符号换 25 次」不是必然的,那是残差没有形状的证据。 第三段那个 R² 0.9277 是最容易骗人的数字。它只说明「比取平均值强」,不说明「模型对了」。 真正判对错的是那张分段残差表:+0.859、−0.411、−0.479、+0.920。 第四段注意 R² 只从 0.9277 涨到 0.9957。看着涨幅不大,可残差从「有 U 形」变成「没形状」, 这是性质上的变化,不是程度上的。 第五段那三个 1e-16 别当成「很小的数」看。它们是浮点运算的零 —— 数学上精确解就是 0。 系数全零意味着模型退化成一个常数,输入完全没起作用。 第六段加了一列乘积项,四个点全对。这里的乘积项正好把「同号/异号」编码进去, 换成别的问题,该加哪一列往往没那么显然 —— 这就是下一节要讲的「猜特征」。

30 秒实验

  1. 把第二段噪声的标准差从 0.3 改成 1.5,重跑。 斜率应该离 2.0 更远,但残差符号变化次数仍然很多 —— 噪声变大,形状不变。
  2. 第三段把拟合用的 cols 改成 (0, 1, 2, 3)(三次),看残差那四段的均值还偏不偏。 理论上应该全在 0 附近,因为 x³ 能局部补偿 x² 的弯曲——这也是「过拟合」的雏形。
  3. 第五段把异或的四个点换成「同或」(正负对调),重跑。 两个权重仍然会是 0,乘积项的系数会变成负的 1。

常见误区

  1. 以为「线性回归」的线性说的是图像是直的。它说的是「对参数线性」。 y = w0 + w1·x + w2·x² 是标准的线性回归,虽然它画出来是弯的。
  2. 以为 R² 高就说明模型对了。Demo 里直线拟合抛物线,R² 到 0.93, 可残差摆成一个 U 形 —— 模型漏了一整个二次项。R² 只跟「比平均值强多少」有关, 跟「缺不缺东西」无关。
  3. 以为残差小就没问题。残差小可能是真的拟合好了, 也可能是模型把噪声也记住了(下一节会看到极端的例子)。看残差要看形状,不能只看大小。
  4. 以为线性模型分不开异或,所以线性模型没用。加一个乘积项就分开了。 真正的说法是「在这组特征上分不开」—— 这个区别决定了你该去改模型,还是该去改特征。
自测三问(点开对答案) 1. 为什么 `y = w0 + w1·x + w2·x²` 算线性模型? —— 因为「线性」在这里指的是**对参数 w 线性**:模型是 w0、w1、w2 的加权和, 把 x 和 x² 当成两个已知的列,问题就变成解一组线性方程。 **图像弯不弯跟这件事无关——直线、抛物线、三次曲线,只要待定系数是加权出现,** **对最小二乘来说都是同一类问题。** 2. R² = 0.93,你怎么判断这个模型好不好? —— **先看残差有没有形状。**Demo 里把残差按 x 分段,均值是 +0.859、−0.411、−0.479、+0.920, **两头正中间负,摆成 U 形**,说明漏掉了一个系统性成分。 **再看符号变化次数:8 次对噪声情形的 25 次,次数少就是有结构的信号。** R² 这个数本身回答不了这个问题。 3. 「线性模型分不开异或」这句话哪里不准? —— 不准在没说是**在哪些特征上**分不开。 **在 (x1, x2) 这两个特征上确实分不开,三个系数都会是 0。**但加上 `x1·x2` 这一列之后, 同一套最小二乘、同一行代码,四个点全对。 **所以卡住的是特征,不是模型——这个区别决定了你该换工具还是该换输入。**

一条线找出来了,可「这条线有多可信」还没回答。 下一节把这件事做成一套能报数字的流程:系数、标准误、t 值、R²。

相关笔记