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

最小二乘默认每个点一样可信。加权回归只改一件事:每个点带一个权重,残差平方按权重加起来。 这个改动小到只是把 X'X 换成 X'WX, 可它能处理两件很实际的事:一个离谱的离群点,和「越远测得越不准」的测量。

📝 本节和前面几节的关系

上一节讲的共线性是「自变量之间打架」。 这一节讲的是另一种不平等:样本之间不等价 —— 有的点该多信,有的点不该信。 加权这个念头后面还会出现好几次:第 6 章 MCMC 里的重要性采样、第 9 章注意力机制的加权求和, 底下的想法都是同一个 —— 让重要的那部分多说几句。

一、一句话的改动

普通最小二乘最小化 Σ(yᵢ − ŷᵢ)²。 加权版本最小化 Σ wᵢ(yᵢ − ŷᵢ)²,就是每项的平方先乘一个权重。 推导过程一模一样(对 w 求导、令其为零),只是每个求和里多乘了一个 wᵢ, 结果变成正规方程 X'WX w = X'Wy,W 是对角线为 wᵢ 的对角矩阵。 两个极端情况值得记住:

权重取法 等价于
全部等于 1 普通最小二乘
某一项等于 0 把那一行数据删掉
等于 1/方差 噪声大的点少信,统计上最优
中间那条最有用:把权重设成 0 和删数据是一回事,所以加权不是新算法,
是最小二乘本来就允许的事 —— 只是默认值一直是 1,大家忘了它能改。

二、一个离群点能拉多歪

Demo 第一段:30 个点,真规律 y = 2 + 1.5x。 然后在 x = 9.0 处塞一个 y = −20 的点(其他点的 y 都在 5 到 17 之间)。

截距 斜率
30 个点 2.0636 1.4797
加那 1 个点 3.4069 0.9953
真值 2.0000 1.5000
31 个点里只有 1 个不对劲,斜率掉了 32.7%,截距涨了六成。一个点比其余三十个点加起来还有话语权。
原因就是那个平方。这一点的残差是 −32.4,平方之后 1047,
而其余 30 个点的残差平方和只有个位数。最小二乘要压住这一个大数,只能把整条线往它那边掰。
这也是「为什么不用绝对值」这条问题的另一半答案:
平方让解好算,也让离群点的破坏力按平方放大。想两头都要,
得用 Huber 损失这类折中方案 —— 小残差按平方、大残差按绝对值。

三、权重是连续可调的开关

Demo 第二段把那个离群点的权重从 1 一路调到 0:

该点权重 斜率
1.00 0.9953
0.30 1.3252
0.10 1.4273
0.01 1.4744
0.00 1.4797
权重 0.10 的时候斜率 1.4273,已经很接近「完全不信」的 1.4797。因为权重是乘在平方上的,
0.1 的权重相当于把这点的影响力压到十分之一 ——
平方根意义上,等于把它的「等效距离」缩到三分之一。
这个连续可调的性质,才是加权比「删数据」好用的地方。删数据是个不可逆的决定,删错了没法救;
给个 0.1 是「半信半疑」,既不让它主导,也不假装它不存在。真实数据里,
标注可疑但不敢删的样本太多了,这个中间档正好用得上。

四、异方差:越远越不准

Demo 第三段造了 50 个点,噪声幅度跟 x 成正比 —— x 越大测得越不准。 这是实测里最常见的情形:远处的、慢的、贵的目标,都测不准。

权重 斜率 标准误
全部等权 1.4556 0.3442
1/x² 1.4234 0.2344
真值 1.5000 —
标准误从 0.3442 降到 0.2344,降了 32%。可系数反而从 1.4556 挪到 1.4234,
离真值 1.5 远了 0.0322。
这两件事不矛盾,而且必须放在一起说:加权不是为了「这一次更准」,是为了「重复很多次时更稳」。
标准误小,意思是换一批同样条件的数据重来,估计值不会乱跑。
单次实现偏一点很正常,这一次就偏了一点。
为什么 1/x² 是对的权重?因为加权最小二乘的最优权重是 1/方差,而这里的噪声标准差正比于 x,
方差就正比于 x²。记住这句就够了:方差大的点,权重应该小。
但权重从哪来,这才是真问题。它要么来自已知的物理规律(比如计数器的读数误差正比于读数本身),
要么来自残差图(把残差对 x 画出来,看喇叭口往哪边张)。拍脑袋给的权重,等于把偏见写进了公式。

五、局部加权:每个预测点自己拟一条线

Demo 第四段换了个用法。 数据是 y = 3·sin(x) + 0.3x, 一条全局直线拟合出来最大误差 2.3744 —— 一条直线当然压不住一个弯。 改成局部加权:对每一个要预测的 x,按「离 x 越近权重越大」重新拟一条线,只取 x 处那一个值。 权重用高斯核 exp(−(xᵢ−x)²/2h²),h 叫带宽,控制「近邻」有多宽。

带宽 h 最大误差
0.10 0.8580
0.20 0.5735
0.30 0.5012
0.50 0.5161
1.00 1.0803
2.00 1.8672
5.00 2.3807
最小误差出现在 h = 0.3(0.5012),比全局直线的 2.3744 好四倍多。h 调小,曲线一个点一个点地跟,
把噪声也拟合进去;h 调大,权重摊平,退化成一条直线。两头都差,中间有个谷。
这个办法有个正式名字:LOESS(局部加权散点平滑)。它不产出「一个模型」,
只产出「一条曲线」——
没有系数可以解释,想知道 x=2 处的斜率是多少,得现场重算一遍。这是它的代价,也是它灵活的原因。

六、带宽和次数是同一个旋钮

上一节用「多项式的次数」控制复杂度,这一节用「带宽」控制。 两件事是同一个东西:模型能弯到什么程度。

旋钮 往哪边拧 后果
多项式次数 调高 能弯,也容易记噪声
带宽 h 调小 能弯,也容易记噪声
kNN 的 k 调小 能弯,也容易记噪声
决策树深度 调高 能弯,也容易记噪声
四个名字、四种算法,拧的都是同一个旋钮。所以「怎么选参数」这个问题,在每一章都会重新出现一次,
解法也总是同一套:留出没看过的数据,拿它来试。
最后提醒一句加权最容易被误用的地方:权重是你给的,不是数据给的。给错了,结果可能比不加权更糟。
上面 1/x² 敢用,是因为「噪声正比于 x」这个假设有物理依据;
要是凭感觉拍一个权重,那就只是把偏见写进了公式。

七、动手 Demo:一个离群点、一段测不准的数据、一条自己弯的曲线

wls 就是普通最小二乘多了一行:把 X'X 换成 X'WX,把 X'y 换成 X'Wy。 W 是对角矩阵,对角线就是权重。 前三段用的是同一批 30 个点的数据,只改权重; 第四段把权重换成「离预测点有多近」,同一个 wls 函数就变成了 LOESS。

def wls(X, y, wt):
    """加权最小二乘:把 X'X 换成 X'WX,X'y 换成 X'Wy。"""
    W = np.diag(wt)
    A = X.T.dot(W).dot(X)
    b = X.T.dot(W).dot(y)
    w, *_ = np.linalg.lstsq(A, b, rcond=None)
    return w

# 一个离群点:先等权,再把它的权重设成 0
w0 = wls(X, y, np.ones(30))          # 斜率 1.4797
w1 = wls(Xo, yo, np.ones(31))        # 斜率 0.9953,被那个点拉歪
wt = np.ones(31); wt[-1] = 0.0
w2 = wls(Xo, yo, wt)                 # 斜率 1.4797,回来了

# 局部加权:对每个预测点,按高斯核重新拟一条线
for t in xt:
    k = np.exp(-((xs - t) ** 2) / (2.0 * h * h))
    wh = wls(Xg, ys, k)
    pred.append(wh[0] + wh[1] * t)

实跑输出:

--------------------------------------------------------------------
给每个点配一个权重
--------------------------------------------------------------------
==============================================================

[1] 一个离群点能把整条线拉歪多少

  30 个点,真实规律 y = 2 + 1.5x。
  然后在 x = 9.0 处塞进一个 y = -20 的点(其他点的 y 都在 5 到 17 之间)。

                  截距          斜率
    ----------------------------------------
    30 个点       2.0636        1.4797
    加那 1 个点   3.4069        0.9953
    真值          2.0000        1.5000

  31 个点里只有 1 个不对劲,斜率就从 1.4797 掉到 0.9953,
  差了 32.7%;截距从 2.0636 涨到 3.4069。

  为什么一个点有这么大劲?因为最小二乘最小化的是平方和,
  这一点的残差是 -32.4,平方之后是 1047,
  比其余 30 个点的平方和加起来还大。它一个人说了算。

[2] 加权:给这个点一个 0 的权重

  加权最小二乘最小化的是 Σ wᵢ(yᵢ − ŷᵢ)²。
  把最后那个点的权重设成 0,等于把它从平方和里删掉。

    权重方案        截距            斜率
    ----------------------------------------------
    全部为 1        3.4069          0.9953
    最后一点为 0    2.0636          1.4797

  斜率回到 1.4797,跟没加那个点时(1.4797)几乎一样。
  把权重设成 0 和「把这行数据删掉」是同一件事——
  所以加权回归不是新算法,是最小二乘本来就允许的事。

  权重还可以取 0 到 1 之间的数。比如取 0.1,意思是「信一成」:

    最后一点权重 1.00:斜率 0.9953
    最后一点权重 0.30:斜率 1.3252
    最后一点权重 0.10:斜率 1.4273
    最后一点权重 0.01:斜率 1.4744
    最后一点权重 0.00:斜率 1.4797

  中间那几档就是「半信半疑」——不想删掉它,又不想让它主导。
  这个连续可调的性质,才是加权比「删数据」好用的地方。

[3] 异方差:越是后面的点越不准

  50 个点,噪声幅度跟 x 成正比:x 越大,测得越不准。
  这是实测里最常见的情形——远处的、慢的、贵的,都测不准。

                    斜率            标准误
    ----------------------------------------------
    全部等权        1.4556          0.3442
    权重 1/x²       1.4234          0.2344
    真值            1.5000          —

  斜率从 1.4556 挪到 1.4234 —— 离真值 1.5 反而远了 0.0322。
  但标准误从 0.3442 降到 0.2344,降了 32%。

  加权不是为了「这一次更准」,是为了「重复很多次时更稳」。
  标准误小,意思是换一批同样条件的数据重来,估计值不会乱跑。
  单次实现偏一点很正常,这一次就偏了一点。

  为什么 1/x² 是对的权重?因为加权最小二乘的最优权重是 1/方差,
  而这里的噪声标准差正比于 x,方差就正比于 x²。
  记住这句就够了:方差大的点,权重应该小。

[4] 局部加权:每个预测点自己拟一条线

  40 个点,规律是 y = 3·sin(x) + 0.3x。全局直线拟合出来:

    斜率 -0.6203,截距 2.7660
    在 60 个新点上最大误差 2.3744

  一条直线当然压不住一个弯。改成局部加权:对每个预测点 x,
  按「离 x 越近权重越大」重新拟一条线,只取 x 处的那个值。
  权重用高斯核 exp(−(xᵢ−x)²/2h²),h 是带宽。

    带宽 h          最大误差        说明
    --------------------------------------------------------
    0.10            0.8580          太窄,跟着噪声抖
    0.20            0.5735          偏窄
    0.30            0.5012          刚好
    0.50            0.5161          偏宽
    1.00            1.0803          更宽,弯处跟不上
    2.00            1.8672          快成直线了
    5.00            2.3807          全局平均

  误差最小出现在带宽 0.3 附近(0.5012)。h 调小,曲线一个点一个点地跟,
  把噪声也拟合进去;h 调大,权重摊平,退化成一条直线。
  两头都差,中间有个谷 —— 跟上一节那个多项式次数的形状一模一样。

[5] 带宽和次数是同一个旋钮的两种转法

  上一节用「多项式的次数」控制复杂度,这一节用「带宽」控制,
  两件事是同一个东西:模型能弯到什么程度。

    旋钮              往哪边拧          后果
    --------------------------------------------------------
    多项式次数        调高              能弯,也容易记噪声
    带宽 h            调小              能弯,也容易记噪声
    邻域大小 k(kNN) 调小              能弯,也容易记噪声
    决策树深度        调高              能弯,也容易记噪声

  四个名字、四种算法,拧的都是同一个旋钮。
  所以「怎么选参数」这个问题,在每一章都会重新出现一次,
  解法也总是同一套:留出没看过的数据,拿它来试。

  最后提醒一句加权最容易被误用的地方:
  权重是你给的,不是数据给的。给错了,结果可能比不加权更糟。
  上面 1/x² 敢用,是因为「噪声正比于 x」这个假设有物理依据;
  要是凭感觉拍一个权重,那就只是把偏见写进了公式。

第一段两组系数摆在一起看最直观。斜率 1.4797 对 0.9953, 这 0.48 的差距全来自一个点。注意截距也跟着从 2.0636 涨到 3.4069 —— 拉歪的是整条线,不只一头。 第二段那张权重表是连续变化,不是开关。从 0.30 到 0.10,斜率从 1.3252 跳到 1.4273, 大部分「修正」发生在前两档 —— 这也是「半信半疑」这个档位有用的原因。 第三段最容易读错。标准误降了 32%,可系数离真值更远了。 这两件事必须一起读:加权优化的是「重复实验时的稳定性」,不是「这一次的准确度」。 单次实现偏一点,完全在预期之内。 第四段是这一节的用法升级。前三段是「给样本加权」, 这一段是「给距离加权」—— 同一个函数,换个权重就换了用途。那一行 k = exp(-((xs - t)²)/(2·h·h)) 就是全部机关。 最后那张带宽表要看形状,不看某个数。0.10 太窄(误差 0.8580)、0.30 最好(0.5012)、 5.00 退化成直线(2.3807),这个 U 形跟上一节「多项式次数」的 X 形是同一个东西的两种表现。

30 秒实验

  1. 把第一段那个离群点从 y = −20 改成 y = 40(往另一头离谱),重跑。 斜率应该往 1.8 以上冲,方向反过来,幅度差不多 —— 平方不分正负。
  2. 第三段把噪声从 1.0 * u2 改成 1.0(等方差),再用 1/x² 加权。 这时候加权是错的做法,标准误应该反而变大 —— 这就是「权重给错了更糟」。
  3. 第四段把带宽 h 固定成 0.3,把核函数从高斯换成「距离小于 h 取 1、否则取 0」。 结果会有点像,但在预测点附近会跳 —— 权重不连续,拟合出的曲线也不连续。

常见误区

  1. 以为加权能「修复」离群点。它只是降低这个点的影响力, 点本身还在数据里,残差还在那 —— 你仍然需要回答「这个点为什么这么离谱」。 加权是缓冲,不是诊断。
  2. 以为权重越大越好、越信越好。权重的作用是分配「信任」, 总量有限。给一个点加权重,等于给其他点减权重。全给同一个点时,模型就只拟合那一个点。
  3. 以为加权一定让结果更准。它优化的是方差(稳定性), 单次实现照样可能偏。Demo 里加权之后系数离真值反而远了 0.0322,这不是失败, 是这类估计的正常表现。
  4. 以为「越近权重越大」是常识,不用解释。它其实是一个很强的假设: 「函数在局部可以用直线近似」。数据变化太剧烈、或者预测点落在数据空档里, 这个假设就不成立 —— 那时 LOESS 会给出很难看的结果。
自测三问(点开对答案) 1. 为什么一个离群点能顶过三十个正常点? —— 因为**最小二乘最小化的是残差平方和,一点的大残差按平方放大**。 Demo 里那个点残差 −32.4,平方 1047,而其余 30 个点的残差平方和只有个位数。 **要压住这一个大数,只能把整条线往它那边掰。**换成绝对值损失就没这么夸张, 但那样就没有闭式解了。 2. 加权之后标准误降了 32%,系数却离真值更远了,这算成功还是失败? —— 算成功,但要理解它成功在哪。 **加权优化的目标是「估计量的方差最小」,不是「这一次的结果最接近真值」。** 标准误小意味着换一批数据重来、 结论不会乱跑 —— **这是可以重复的性质,而「这一次准不准」是一次性的运气。**单次偏 0.0322 完全在预期之内。 3. 带宽 h 调小和多项式次数调高,为什么会出同样的问题? —— 因为它们拧的是同一个旋钮:**模型能弯到什么程度**。 h 小意味着只用很近的点、直线能快速改变方向;次数高意味着曲线能剧烈起伏。 **两者都会让模型跟着噪声跑。**所以调参的办法也一样 —— 留出没看过的数据,在那个上面挑。

到这儿,模型、误差、权重都齐了。 下一节换一个问题:两个东西像不像,这件事该怎么算?它看着更简单, 其实坑更多 —— 五种算法给五个答案。

相关笔记