最小二乘干的活只有一句话:找一组参数,让残差平方和最小。这句话对一条直线成立, 对「任何参数的线性组合」也成立。 这一节分两件事: 一条线到底能表达什么,以及「线性」这个词在三个不同地方出现时的区别。分不清这三个, 后面所有讨论都会糊。
第 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 秒实验
- 把第二段噪声的标准差从 0.3 改成 1.5,重跑。 斜率应该离 2.0 更远,但残差符号变化次数仍然很多 —— 噪声变大,形状不变。
- 第三段把拟合用的
cols改成(0, 1, 2, 3)(三次),看残差那四段的均值还偏不偏。 理论上应该全在 0 附近,因为 x³ 能局部补偿 x² 的弯曲——这也是「过拟合」的雏形。 - 第五段把异或的四个点换成「同或」(正负对调),重跑。 两个权重仍然会是 0,乘积项的系数会变成负的 1。
常见误区
- 以为「线性回归」的线性说的是图像是直的。它说的是「对参数线性」。
y = w0 + w1·x + w2·x²是标准的线性回归,虽然它画出来是弯的。 - 以为 R² 高就说明模型对了。Demo 里直线拟合抛物线,R² 到 0.93, 可残差摆成一个 U 形 —— 模型漏了一整个二次项。R² 只跟「比平均值强多少」有关, 跟「缺不缺东西」无关。
- 以为残差小就没问题。残差小可能是真的拟合好了, 也可能是模型把噪声也记住了(下一节会看到极端的例子)。看残差要看形状,不能只看大小。
- 以为线性模型分不开异或,所以线性模型没用。加一个乘积项就分开了。 真正的说法是「在这组特征上分不开」—— 这个区别决定了你该去改模型,还是该去改特征。
自测三问(点开对答案)
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²。
- ☐ (本节完,下一节 4.2 回归分析)