本文是《LLM 时代的经典机器学习:只讲它在哪里重现》系列的第 2 篇(共十一篇)。上一篇:什么是学习——划分、泛化、过拟合与偏差-方差;下一篇:逻辑回归与奖励模型——每个分类头的原型

线性回归是最简单的模型:用一条直线(高维时是一个平面)预测一个数。它简单到有闭式解——写出公式就能算出最优参数——但它同时是后面一切的原型:每一个神经网络的最后一层都是它(或它的分类版本),weight decay 就是它的 Ridge 正则,梯度下降、学习率、特征缩放、共线这些深度学习里天天遇到的问题,都可以在它身上用两个参数画出来看清楚。这一篇从 20 个点拟合一条直线开始,把这些概念一个一个建立起来。

全篇的核心问题是:

lstsq 那一行到底解了什么方程,为什么它一步就能算出答案?1 梯度下降为什么有时候要几百步、有时候直接发散?2 LLM 训练配置里的 weight_decay=0.1 和一百年前的 Ridge 回归是什么关系?3

一、总览

1. 一条线、一个碗、一个惩罚项

本文按”从一个模型到一个训练过程”的顺序组织:先定义模型与损失(一条直线、残差的平方和),再看两种求解方式(闭式解一步到位、梯度下降一步一步走),再看梯度下降在什么地形上难走(特征量纲、共线),最后给损失加一个惩罚项(Ridge / Lasso)并证明它就是 weight decay。

概念 一句话 本文里的数字
模型 \(\hat y = w^T x + b\),特征的加权和 20 个点:\(w = 1.525, b = 1.997\)
损失 残差的平方和(MSE) 最小二乘 38.7 vs 随手猜的直线 98
闭式解 \((X^T X) w = X^T y\),解一个线性方程组 与梯度下降差 \(5 \times 10^{-10}\)
梯度下降 沿损失下降最快的方向走 学习率上限 = 2 / 最大特征值 = 0.027
特征缩放 让碗变圆 条件数 540 万 → 1
Ridge / Lasso 惩罚大系数:\(L_2\) 压小、\(L_1\) 压到零 50 个特征,Lasso 归零 45 个
weight decay 每步把 \(w\) 乘 \((1 - 2\eta\lambda)\) 与 Ridge 闭式解差 \(10^{-13}\)

2. 本文的章节安排

主题 内容
模型与损失 一条直线、20 个点;残差;为什么用平方;多个特征就是矩阵乘
闭式解 一个参数时求导令为零;两个参数时是两个方程;写成矩阵就是正规方程;投影的几何
梯度下降 一个参数时用导数当指南针、手算四步;两个参数的碗与沟;学习率上限从一个数推到矩阵;GD vs SGD 到同一个答案
地形不好走的两种情形 特征量纲不同(沟太窄);两个特征共线(沟没有底)
Ridge 与 Lasso 加惩罚项;系数路径;菱形与圆的几何;Lasso 做特征选择
Ridge 就是 weight decay 推导 + 数值对上;回到上一篇的 15 次多项式
为什么是平方 高斯噪声的最大似然;有离群点时换绝对值
本文小结  
自测 七道题

二、模型与损失

1. 一条直线、20 个点

20 个点,横坐标 \(x\) 在 0 到 10 之间,纵坐标 \(y\) 由 \(y = 1.5x + 2\) 加上标准差 2 的噪声生成——我们假装不知道 1.5 和 2,要从点里把它们找回来。模型是一条直线:

\[\hat y = w x + b\]
  • \(w\):斜率——\(x\) 每增加 1,\(\hat y\) 增加多少;
  • \(b\):截距——\(x = 0\) 时的预测;
  • \(\hat y\):预测值,戴帽子以区别于真实值 \(y\)。

哪条直线最好?先随手画两条,再看最小二乘选的那条:

左:三条候选直线与 20 个点——直线 A 太平、直线 B 太陡,红色的最小二乘直线穿过点群中央;右:最小二乘直线上每个点到直线的竖线是残差,最小二乘让这些竖线长度的平方和最小

2. 残差与平方和

每个点的预测值与真实值之差叫残差:\(r_i = y_i - \hat y_i\)。右图里每条竖线就是一个残差。评价一条直线好坏的标准是残差的平方和:

\[L(w, b) = \sum_{i=1}^{n} \big(y_i - (w x_i + b)\big)^2\]
直线 \(w\) \(b\) 残差平方和
直线 A 1 5 97.6
直线 B 2 0 89.2
最小二乘 1.525 1.997 38.7

最小二乘(least squares)就是找让这个平方和最小的 \((w, b)\)。找出来的 1.525 与 1.997 离真实的 1.5 与 2 很近——差的那点是 20 个点的噪声。平方和除以 \(n\) 就是上一篇的 MSE(这里 1.934)。

用 3 个点在纸上算一遍。20 个点的数字算不过来,把问题缩小到 3 个点:\((1, 3), (2, 5), (3, 8)\)。全文后面每个公式都会回到这 3 个点,所以先把它们记住。随手画一条 \(\hat y = 2x + 1\):

\(x_i\) \(y_i\) 预测 \(2x_i + 1\) 残差 \(r_i\) \(r_i^2\)
1 3 3 0 0
2 5 5 0 0
3 8 7 1 1
      合计 1.0

这条线穿过前两个点、离第三个点差 1,平方和是 1.0——看起来已经不错。但最小二乘找到的是 \(w = 2.5, b = 1/3\)(第三章会算出它),三个残差是 \(0.167, -0.333, 0.167\),平方和 \(0.028 + 0.111 + 0.028 = \mathbf{0.167}\),比 1.0 小得多。它没有精确穿过任何一个点,却让三个点”都差一点”,而不是”两个点完全对、一个点差很多”——平方对大残差的惩罚是不成比例的(差 1 罚 1,差 0.333 只罚 0.111),所以最小二乘偏爱把误差摊平。第八章会说清为什么是平方而不是别的。

为什么要平方而不直接把残差加起来?残差有正有负,直接相加会互相抵消——一条离所有点都很远的线也可能残差之和为零。平方(或取绝对值)先把符号去掉,再相加才能衡量”总共差多少”。

3. 多个特征:矩阵写法

现实里输入通常不止一个数:预测房价要用面积、房龄、楼层……\(d\) 个特征 \(x = (x_1, \ldots, x_d)\),模型是它们的加权和:

\[\hat y = w_1 x_1 + w_2 x_2 + \cdots + w_d x_d + b = w^T x + b\]

\(w^T x\) 读作”\(w\) 转置乘 \(x\)“,就是两个向量对应位置相乘再相加(点积):\(w = (1, 2), x = (3, 4)\) 时 \(w^T x = 1 \times 3 + 2 \times 4 = 11\)。上标 \(T\) 是转置——把行写成列、列写成行;写它只是为了让”一行乘一列”的矩阵乘法规则成立,不用想得更多。

把 \(n\) 个样本摞成一个 \(n \times d\) 的矩阵 \(X\)(每行一个样本),全部预测就是一次矩阵乘法 \(\hat y = X w + b\)——矩阵乘向量的规则是”\(X\) 的每一行与 \(w\) 做点积,得到一个数”,\(n\) 行就得到 \(n\) 个预测。再做一个常用的小动作:给 \(X\) 加一列全 1,\(b\) 就成了这一列的系数,可以并进 \(w\) 里——之后的公式都不再单独写 \(b\)。用 3 个点写出来:

\[X = \begin{pmatrix} 1 & 1 \\ 1 & 2 \\ 1 & 3 \end{pmatrix},\quad w = \begin{pmatrix} b \\ w \end{pmatrix},\quad X w = \begin{pmatrix} b + 1w \\ b + 2w \\ b + 3w \end{pmatrix},\quad y = \begin{pmatrix} 3 \\ 5 \\ 8 \end{pmatrix}\]

\(Xw\) 的三行正好是三个点的预测值 \(w x_i + b\),只是排成了一列。代码里是一行:

def add_bias(X):
    return np.c_[np.ones(len(X)), X]        # 左边加一列 1,b 变成 w[0]

np.c_[...] 把几个数组按列拼起来;np.ones(len(X)) 是长度为 \(n\) 的全 1 向量。之后 X @ w 就是矩阵乘(@ 是 NumPy 的矩阵乘法运算符),一行算出全部预测。

三、闭式解

1. 先用一个参数看懂”求导令为零”

先把 \(b\) 拿掉,只留斜率 \(w\),模型是 \(\hat y = w x\)。损失 \(L(w) = \sum_i (y_i - w x_i)^2\) 是 \(w\) 的二次函数——展开后是 \(a w^2 + c w + e\) 的形式,\(a = \sum x_i^2 > 0\),图形是一个开口向上的抛物线。抛物线的最低点在哪里?斜率为零的地方:左边斜率为负(往右走会下降),右边斜率为正(往右走会上升),最低点恰好是斜率从负变正的那一点。函数在某点的斜率就是它的导数,所以”求最小值”变成”求导数、令它等于零、解方程”。

用 3 个点算:\(L(w) = (3 - w)^2 + (5 - 2w)^2 + (8 - 3w)^2\)。对每一项求导用链式法则——\((a - bw)^2\) 的导数是 \(2(a - bw) \cdot (-b)\):

\[L'(w) = -2 \cdot 1 \cdot (3 - w) - 2 \cdot 2 \cdot (5 - 2w) - 2 \cdot 3 \cdot (8 - 3w) = -2 \sum_i x_i (y_i - w x_i)\]

令 \(L'(w) = 0\):\(\sum_i x_i y_i = w \sum_i x_i^2\),即 \(w = \frac{\sum x_i y_i}{\sum x_i^2} = \frac{3 + 10 + 24}{1 + 4 + 9} = \frac{37}{14} = 2.64\)。这是”过原点的最佳直线”的斜率。加回 \(b\) 后有两个未知数,就要对 \(w\) 和 \(b\) 各求一次导(对一个求导时把另一个当常数,这叫偏导数),得到两个方程:

\[\begin{aligned} \frac{\partial L}{\partial b} = 0 &\;\Longrightarrow\; n\, b + \Big(\sum x_i\Big) w = \sum y_i &&\Longrightarrow\; 3b + 6w = 16 \\ \frac{\partial L}{\partial w} = 0 &\;\Longrightarrow\; \Big(\sum x_i\Big) b + \Big(\sum x_i^2\Big) w = \sum x_i y_i &&\Longrightarrow\; 6b + 14w = 37 \end{aligned}\]

两个一次方程、两个未知数,初中解法:第一式得 \(b = (16 - 6w)/3\),代入第二式 \(32 - 12w + 14w = 37\),\(w = 2.5\),\(b = 1/3\)。这就是第二章表里那条平方和 0.167 的直线。

2. 写成矩阵:正规方程

上面两个方程的系数——\(3, 6, 6, 14\) 和右边的 \(16, 37\)——不是凭空来的。用第二章的 \(X\) 算两个矩阵乘法:

\[X^T X = \begin{pmatrix} 1 & 1 & 1 \\ 1 & 2 & 3 \end{pmatrix} \begin{pmatrix} 1 & 1 \\ 1 & 2 \\ 1 & 3 \end{pmatrix} = \begin{pmatrix} 3 & 6 \\ 6 & 14 \end{pmatrix}, \qquad X^T y = \begin{pmatrix} 1 & 1 & 1 \\ 1 & 2 & 3 \end{pmatrix} \begin{pmatrix} 3 \\ 5 \\ 8 \end{pmatrix} = \begin{pmatrix} 16 \\ 37 \end{pmatrix}\]

(\(X^T X\) 左上角的 3 是”第一行 \((1,1,1)\) 点乘第一列 \((1,1,1)\)“;右下角的 14 是 \((1,2,3) \cdot (1,2,3) = 1 + 4 + 9\)。)两个手算的方程,正好就是 \(X^T X\, w = X^T y\) 这一个矩阵等式——矩阵只是把”\(d\) 个方程”打包成一行的记法。一般地,对 \(L(w) = \lVert X w - y \rVert^2\)(\(\lVert v \rVert^2\) 表示向量 \(v\) 各分量的平方和,这里就是残差平方和)求导:

\[\frac{\partial L}{\partial w} = 2 X^T (X w - y) = 0 \quad \Longrightarrow \quad X^T X\, w = X^T y\]

这叫正规方程(normal equation)。它是一个 \(d \times d\) 的线性方程组——\(X^T X\) 是已知矩阵、\(X^T y\) 是已知向量、\(w\) 是未知数——解它就是最优解。写成”除过去”的形式(矩阵的”除”是乘逆矩阵):

\[w^* = (X^T X)^{-1} X^T y\]

上一篇 fit_poly 里的 np.linalg.lstsq 解的就是这个(用更稳的数值方法,不真的求逆)。手写版是一行:

def fit_closed_form(X, y):
    return np.linalg.solve(X.T @ X, X.T @ y)     # 解 (XᵀX) w = Xᵀy

np.linalg.solve(A, c) 解线性方程组 \(A w = c\),对 3 个点它算的就是上面那个 \(2 \times 2\) 方程组,返回 [0.333, 2.5]。20 个点、1000 个特征,代码一个字不变——这是矩阵记法的全部价值。

3. 几何:投影

为什么导数为零就是最优?换个角度看。把 3 个点的 \(y = (3, 5, 8)\) 看成三维空间里的一个点(三个坐标各是一个样本的真实值);预测 \(Xw = b \cdot (1, 1, 1) + w \cdot (1, 2, 3)\) 是 \(X\) 两列的线性组合(各乘一个数再相加)。\(b, w\) 取遍所有值,\(Xw\) 扫出一个过原点的平面(列空间)——三维空间里两个方向能张出一个平面,但填不满整个空间。\(y\) 一般不在这个平面上(否则残差可以为零,三个点恰好共线)。离 \(y\) 最近的平面上的点,是 \(y\) 在平面上的垂直投影——就像你站在地面上方某点,离你最近的地面点是你正下方那个。”垂直”意味着残差 \(y - Xw\) 与平面里的每个方向(\(X\) 的每一列)都正交:\(X^T (y - Xw) = 0\)——这正是正规方程。用 3 个点验算:残差 \((0.167, -0.333, 0.167)\) 与 \((1, 1, 1)\) 的点积是 \(0.167 - 0.333 + 0.167 = 0\),与 \((1, 2, 3)\) 的点积是 \(0.167 - 0.667 + 0.5 = 0\)。最小二乘 = 把 \(y\) 投影到特征张成的平面上,残差是垂足到 \(y\) 的那段。

4. 什么时候解不出来

\(X^T X\) 要可逆,需要 \(X\) 的各列线性无关。两种情况会坏掉:特征比样本多(\(d > n\),\(X^T X\) 秩不足,有无穷多个解都能让残差为零——这就是上一篇”参数比样本多就能背下训练集”的代数版),以及两个特征几乎相同(第五章)。两者的解法都是第六章的 Ridge。

四、梯度下降

1. 一个参数的梯度下降:用导数当指南针

闭式解一步到位,但它要解一个 \(d \times d\) 的方程组,\(d\) 是几十亿时不可能;而且只有线性模型有闭式解。通用的办法是梯度下降:不解方程,而是从任意一点出发,每次朝”下坡”方向挪一小步,挪很多步到碗底。

“下坡方向”怎么知道?还是导数。一个参数时,导数是斜率:斜率为正说明右边更高,要往左走;斜率为负要往右走。所以每一步都是”减去导数乘一个小步长“——导数为正就减小 \(w\),为负就增大 \(w\),方向自动对。用最简单的碗 \(L(w) = (w - 3)^2\)(最低点在 3,导数 \(L'(w) = 2(w - 3)\))从 \(w = 0\) 出发,步长 \(\eta = 0.1\):

当前 \(w\) 导数 \(2(w - 3)\) 新 \(w = w - 0.1 \times\) 导数
1 0 −6 0.6
2 0.6 −4.8 1.08
3 1.08 −3.84 1.464
4 1.464 −3.07 1.771

每一步都朝 3 走,而且越靠近越慢(导数越小、步子越小),永远不会跨过去——这是学习率合适时的样子。步长 \(\eta\) 就是学习率

多个参数时,”导数”变成梯度:对每个参数各求一次偏导,排成一个向量。梯度指向损失上升最快的方向,所以沿着负梯度走就是下降最快。更新公式与一个参数时完全一样,只是 \(w\) 和导数都成了向量:

\[w \leftarrow w - \eta \cdot \frac{\partial L}{\partial w}, \qquad \frac{\partial L}{\partial w} = \frac{2}{n} X^T (X w - y)\]

梯度的表达式就是第三章正规方程左边那个 \(2X^T(Xw - y)\)(这里多除了 \(n\),用平均而不是求和,让学习率的量级不随样本数变)。\(Xw - y\) 是残差向量(预测减真实),\(X^T\) 乘它相当于”每个特征与残差做点积”——某个特征与残差正相关,说明这个特征的权重该调小,梯度在这一维就是正的。

2. 两个参数:碗与沟

对 20 个点的直线,损失只有两个参数 \((w, b)\),可以把整个”碗”画出来:横轴 \(w\)、纵轴 \(b\),每个位置的损失值用等高线表示——同一条线上的所有 \((w, b)\) 损失相同,像地图上的海拔线。再画上梯度下降走过的路:

左:损失 L(w, b) 的等高线是一组倾斜的椭圆,红点是学习率 0.005 走 400 步的路径——先垂直冲到沟底,再沿着沟慢慢爬向碗底的橙星(闭式解);右:学习率 0.0265 接近上限,路径在沟的两壁之间来回震荡,虽然也在靠近但极慢

怎么读这张图:橙星是第三章的闭式解,也就是碗底;椭圆越小损失越低;红点从右上角的起点出发,每一步是一个点。碗不是圆的,是一条斜的窄沟:椭圆很扁,沿短轴方向(大致是 \(b\) 方向)损失变化剧烈、沿长轴(沟底)方向变化缓慢。梯度总是垂直于等高线(等高线方向损失不变,垂直方向变化最快),所以第一步几乎垂直冲向沟底,之后只能在沟底沿着缓的方向一点点爬——GD 在窄沟里慢,是因为陡的方向限制了步长,缓的方向又要走很远

为什么沟是斜的?因为 \(w\) 与 \(b\) 不独立:\(x\) 的均值是 5,把斜率 \(w\) 加大一点、同时把截距 \(b\) 减小 5 倍那么多,直线在 \(x = 5\) 附近几乎不动,损失几乎不变——这个”互相补偿”的方向就是沟底的方向。

3. 学习率的上限

步子太大会怎样?回到一个参数的碗 \(L(w) = (w - 3)^2\),把学习率改成 1.1:

当前 \(w\) 导数 新 \(w = w - 1.1 \times\) 导数
1 0 −6 6.6
2 6.6 7.2 −1.32
3 −1.32 −8.64 8.184
4 8.184 10.37 −3.22

一步跨过碗底落到对面更高的地方,再跨回来更高,在碗的两壁之间越跳越远——发散。到底多大算太大?每一步 \(w - 3\) 变成 \((1 - 2\eta)(w - 3)\):\(\eta = 0.1\) 时乘 0.8,越乘越小;\(\eta = 1.1\) 时乘 \(-1.2\),符号翻转、绝对值越乘越大。临界点是 \(\lvert 1 - 2\eta \rvert = 1\),即 \(\eta = 1\)。这里的 2 是这个碗的曲率——二阶导数 \(L''(w) = 2\),碗越陡(曲率越大)允许的学习率越小:一般地 \(\eta < 2 / L''\)。

多个参数时,碗在不同方向的曲率不同(窄沟:陡壁方向曲率大,沟底方向曲率小)。这些曲率由矩阵 \(H = \frac{2}{n} X^T X\)(损失的二阶导,叫 Hessian)的特征值给出——特征值就是碗沿各条主轴方向的曲率,最大的那个对应最陡的方向,记 \(\lambda_{\max}\)。步长必须让最陡的方向不发散,所以稳定条件是

\[\eta < \frac{2}{\lambda_{\max}}\]

20 个点的直线:\(H\) 的特征值是 0.55 与 74.62,上限 \(2 / 74.62 = 0.027\)。上一节右图学习率 0.0265 贴着上限,所以震荡;超过 0.027 就飞出去。(3 个点的版本:\(H\) 的特征值是 0.24 与 11.09,上限 0.18。)两个特征值之比 \(\lambda_{\max} / \lambda_{\min} = 135\) 叫条件数(condition number),它就是”沟有多窄”:最陡与最缓方向的曲率之比。条件数 1 是圆碗,任何方向一样陡,一步到底;条件数越大,学习率被陡方向压得越小、缓方向要走的步数越多。

4. GD 与 SGD 到同一个答案

200 个样本、3 个特征的数据上,三种方法一起算:

def fit_gd(X, y, lr=0.1, steps=200, batch=None, seed=0):
    r = np.random.default_rng(seed)
    w = np.zeros(X.shape[1])
    for _ in range(steps):
        idx = slice(None) if batch is None else r.choice(len(y), batch, replace=False)   # ① 全量,或随机抽一个小批
        Xb, yb = X[idx], y[idx]
        grad = 2 * Xb.T @ (Xb @ w - yb) / len(yb)                                        # ② 这一批上的梯度
        w -= lr * grad                                                                   # ③ 走一步
    return w
  1. batch=None 时每步用全部 200 个样本,是全量梯度下降(GD);给了 batch=16 就每步随机抽 16 个,是小批量随机梯度下降(SGD)——深度学习用的全是后者,因为全量数据算一次梯度太贵;
  2. ② 小批上的梯度是全量梯度的一个有噪声的估计;
  3. ③ 同一条更新公式。
方法 系数 与闭式解的最大差
真实系数 [24.66, 59.64, 56.82]
闭式解 [24.93, 59.98, 57.08] 0
GD 300 步 [24.93, 59.98, 57.08] \(4.9 \times 10^{-10}\)
SGD(16)300 步 [24.37, 60.05, 56.92] 0.56
sklearn.LinearRegression [24.93, 59.98, 57.08] 0

纵轴是当前 MSE 减去闭式解的 MSE(对数轴):全量 GD 一条直线下降到 0.001 以下;小批量 SGD 前 50 步与 GD 一样快,之后在 0.01–1 之间抖动不再下降

怎么读这张图:纵轴是”当前损失比最优损失多多少”,用对数刻度(每格差 10 倍),所以越往下越接近最优。GD 走到闭式解,差 \(10^{-10}\)——同一个最优解。SGD 每步只算 16 个样本(算量是 GD 的 1/12),前 50 步与 GD 一样快,之后在最优点附近抖动、不再逼近——因为每步的梯度带噪声,噪声大小与学习率成正比、与 batch 大小成反比。深度学习里”学习率衰减”就是为了让这个抖动最后收小。线性回归是唯一一个能把闭式解、GD、SGD 放在一起对答案的地方,值得亲手跑一次。

五、地形不好走的两种情形

1. 特征量纲不同:沟太窄

两个特征,一个取值 0–1、一个 0–1000(比如”评分”与”字数”)。碗的形状由 \(X^T X\) 决定,大特征那个方向的曲率是小特征的 \(1000^2\) 倍:

特征 条件数 最大安全学习率 500 步后 MSE 比最优多
原始 5,421,535 \(2.9 \times 10^{-6}\) 1.18
标准化后 1 0.8 0.0000

原始特征下,安全学习率被大特征压到百万分之三,小特征那个方向 500 步几乎没动。标准化(standardization)——每个特征减去它的均值、再除以它的标准差:

\[x'_j = \frac{x_j - \mu_j}{\sigma_j}\]

做完之后每个特征均值 0、标准差 1,”字数”和”评分”变成同一个量级。例如三篇文章字数 100、300、800:均值 400,标准差 \(\sqrt{((300)^2 + (100)^2 + (400)^2)/3} = 294\),标准化后是 \(-1.02, -0.34, 1.36\)。它让碗变圆,条件数变成 1,几十步收敛。注意均值和标准差要在训练集上算、再用同样的数去变换验证集与测试集——用全部数据算是上一篇说的信息泄漏。这就是为什么 scikit-learn 的例子总有一个 StandardScaler,也是深度学习里 BatchNorm / LayerNorm(L3 第二篇)要解决的同一个问题:让每个方向的曲率差不多。

2. 两个特征共线:沟没有底

第二种坏地形:两个特征几乎一样(\(x_2 \approx x_1\))。真实规律是 \(y = 2 x_1\),看最小二乘学到什么:

\(x_2\) 与 \(x_1\) 的相关系数 无正则 \(w_1, w_2\) Ridge \(\alpha = 1\) 的 \(w_1, w_2\)
0.626 2.04, 0.00 2.00, 0.02
0.994 2.47, −0.45 1.46, 0.54
0.9999 7.88, −5.95 1.00, 0.92

两列几乎相同时,\(w_1 = 7.88, w_2 = -5.95\) 与 \(w_1 = 1, w_2 = 1\) 给出几乎一样的预测(只有它们的和 ≈ 2 是确定的),最小二乘就在这条”沟底”上随噪声乱选一个点——系数巨大、符号相反、换一批数据完全不同。这叫共线(collinearity),\(X^T X\) 接近不可逆。上一篇 17 次以上多项式曲线走平,就是 \(x^{17}\) 与 \(x^{18}\) 在 \([0,1]\) 上几乎共线。右列的 Ridge 把两个系数各分一半(1.00 与 0.92)——它是怎么做到的,下一章。

六、Ridge 与 Lasso

1. 加一个惩罚项

上一篇说正则化是”把模型拉向简单解”。对线性回归,”简单”就是系数小。在损失后面加一项惩罚大系数:

\[\text{Ridge:}\quad \min_w \lVert X w - y \rVert^2 + \alpha \lVert w \rVert_2^2 \qquad\qquad \text{Lasso:}\quad \min_w \lVert X w - y \rVert^2 + \alpha \lVert w \rVert_1\]
  • \(\lVert w \rVert_2^2 = \sum_j w_j^2\):系数的平方和(\(L_2\) 范数的平方);
  • \(\lVert w \rVert_1 = \sum_j \lvert w_j \rvert\):系数绝对值之和(\(L_1\) 范数);
  • \(\alpha\):惩罚的强度。\(\alpha = 0\) 回到普通最小二乘,\(\alpha \to \infty\) 所有系数趋于零。

Ridge 仍有闭式解:对新目标求导令为零,惩罚项 \(\alpha \sum_j w_j^2\) 的导数是 \(2\alpha w\),正规方程变成 \((X^T X + \alpha I) w = X^T y\)——\(I\) 是单位矩阵,所以只是在 \(X^T X\) 的对角线上各加了 \(\alpha\)。用 3 个点、\(\alpha = 1\),惯例上不惩罚截距 \(b\)(它只是把直线整体上下平移,不算”复杂”),所以只给 \(w\) 那一格加:

\[\begin{pmatrix} 3 & 6 \\ 6 & 14 \end{pmatrix} \;\to\; \begin{pmatrix} 3 & 6 \\ 6 & 15 \end{pmatrix}, \qquad \text{解得 } (b, w) = (2.0, 1.67)\]

斜率从无正则的 2.5 被压到 1.67,截距则补上去变成 2.0——直线更平了,这就是”压小系数”(3 个点太少,\(\alpha = 1\) 相对而言很重;50 个特征的实验里 \(\alpha\) 的合适量级会用验证集选)。为什么加对角线就一定可逆?共线时 \(X^T X\) 在沟底方向的曲率是 0(那个方向的特征值为零,沟没有底),加 \(\alpha I\) 让每个方向的曲率都至少是 \(\alpha\)——沟底被抬起来变成了碗底,最低点于是唯一。这就是它救共线与 \(d > n\) 的原理。Lasso 的绝对值在零点不可导(左边斜率 \(-1\)、右边 \(+1\),没有唯一的斜率),没有闭式解,用坐标下降等算法。

2. 系数路径:压小 vs 压到零

50 个特征、只有 5 个真有用、100 个样本。把 \(\alpha\) 从小到大扫一遍,画每个系数怎么变:

左:Ridge 的 50 条系数曲线随 α 增大一起平滑地缩向零,5 条红色(真有用的特征)缩得慢、45 条蓝色(无用特征)在零附近抖动但从不恰好为零;右:Lasso 的蓝色曲线在 α 约 1 处全部变成精确的零,红色曲线到 α 约 10–50 才依次归零

模型 恰好为 0 的系数 系数绝对值均值 最大
无正则 0 / 50 4.03 67.3
Ridge \(\alpha = 10\) 0 / 50 3.74 56.8
Lasso \(\alpha = 1\) 36 / 50 2.84 65.7
Lasso \(\alpha = 5\) 45 / 50 2.31 61.4
  • 无正则:45 个没用的特征也得到了非零系数(均值 4)——模型在用噪声特征拟合噪声,这是过拟合的样子;
  • Ridge:所有系数一起压小,但没有一个恰好为零
  • Lasso:把 36–45 个没用的特征压到恰好为零,只留下真正有用的几个——稀疏。\(\alpha = 5\) 时 45/50 为零,恰好对应 5 个有用特征。Lasso 因此是一种特征选择

3. 几何:为什么 \(L_1\) 能压到零

“加惩罚项 \(\alpha \lVert w \rVert\)“等价于”在约束 \(\lVert w \rVert \le c\) 下最小化损失”——惩罚越重(\(\alpha\) 越大),相当于允许的范围 \(c\) 越小;两种说法选出的是同一个 \(w\)。约束的说法好画图:把 \(w\) 限制在原点附近一个区域里,在区域里找损失最低的点。两个参数时能画出来:

两张图都画了损失的椭圆等高线与无正则解(橙星)。左:L1 的约束区是一个菱形,等高线扩大时先碰到菱形的角,角在坐标轴上,所以解 (0.95, 0.05) 的 w₂ 几乎为零;右:L2 的约束区是圆,等高线碰到圆周上一个一般的点 (0.95, 0.28),两个系数都变小、都不为零

\(L_1\) 的约束区 \(\lvert w_1 \rvert + \lvert w_2 \rvert \le c\) 是一个菱形,角在坐标轴上;损失的等高线从最优点向外扩,多数情况下先碰到的是角——角上某个坐标恰好为零。\(L_2\) 的约束区是圆,没有角,碰到哪里都行,两个坐标一般都不为零。高维时菱形变成有很多尖角的多面体,”碰到角”的概率更大,Lasso 归零的系数就更多。另一个说法:\(L_1\) 惩罚的导数在零附近仍是 \(\pm\alpha\)(不随 \(w\) 变小而减弱),一直有动力把系数推到零并停在零;\(L_2\) 的导数 \(2\alpha w\) 随 \(w\) 变小而变小,越接近零推力越弱,永远到不了。

七、Ridge 就是 weight decay

1. 推导

对 Ridge 的目标求梯度:

\[\frac{\partial}{\partial w}\Big[\frac{1}{n}\lVert X w - y \rVert^2 + \lambda \lVert w \rVert^2\Big] = \underbrace{\frac{2}{n} X^T (X w - y)}_{\text{普通梯度 } g} + 2 \lambda w\]

代进梯度下降:

\[w \leftarrow w - \eta (g + 2\lambda w) = (1 - 2\eta\lambda)\, w - \eta g\]

每步先把 \(w\) 乘一个略小于 1 的数(衰减),再走一步普通梯度。这就是 weight decay(权重衰减)——名字来自这个”先缩小”的动作。代码里是一行的差别:

w = (1 - 2 * lr * lam) * w - lr * grad          # weight decay:先衰减,再走普通梯度

2. 数值对上

200 个样本、5 个标准化后的特征,\(\lambda = 0.5\):

方法 系数
Ridge 闭式解 \((X^T X / n + \lambda I)^{-1} X^T y / n\) [48.351, 58.067, 51.452, 4.740, 49.103]
GD + weight decay 2000 步 [48.351, 58.067, 51.452, 4.740, 49.103]
无正则闭式解 [69.783, 88.874, 80.168, 10.382, 75.429]

两者最大差 \(9 \times 10^{-14}\)。LLM 训练配置里的 weight_decay=0.1 就是 Ridge 在几十亿参数上的形态(AdamW 里它与 \(L_2\) 正则有一点区别——衰减不经过 Adam 的自适应缩放,L3 第三篇讲)。LLM 几乎不用 Lasso:稀疏性对稠密的 Transformer 权重没有直接用处,它的用武之地是特征工程与剪枝。

3. 回到上一篇的 15 次多项式

上一篇 15 次多项式过拟合,系数大到 2020。不减容量、只加 Ridge:

三张图都是 15 次多项式拟合同样的 30 个点:α = 0 时曲线在点之间乱跳、最大系数 2021;α = 0.01 时曲线平滑地贴着正弦、最大系数 5、验证 MSE 从 0.143 降到 0.108;α = 10 时曲线被压得太平、连正弦的形状都跟不上,验证 MSE 0.222

\(\alpha\) 训练 MSE 验证 MSE 最大系数
0 0.049 0.143 2020.5
0.01 0.071 0.108 4.6
10 0.196 0.222 0.3

正则项不改变模型容量(还是 15 次),只是不让高次系数变大——曲线于是平滑。\(\alpha\) 太大又变成欠拟合(右图)。\(\alpha\) 是一个超参数,用上一篇的验证集选。

八、为什么是平方

1. 高斯噪声的最大似然

为什么损失用残差的平方,不是绝对值、不是四次方?第二章给了一个朴素理由(去掉符号),但绝对值也能去掉符号。真正的理由是一个关于噪声的假设

假设数据是”直线加高斯噪声”:\(y = w^T x + \varepsilon\),\(\varepsilon \sim \mathcal{N}(0, \sigma^2)\)——每个点的 \(y\) 是直线上的值加一个随机扰动 \(\varepsilon\),扰动服从均值 0、标准差 \(\sigma\) 的高斯分布(正态分布,钟形曲线:小扰动常见、大扰动罕见,离 0 越远概率越小,下降得很快)。那么观察到某个 \(y\) 的概率密度

\[p(y \mid x) = \frac{1}{\sqrt{2\pi}\sigma} \exp\Big(-\frac{(y - w^T x)^2}{2\sigma^2}\Big)\]

\(\exp(z)\) 就是 \(e^z\)(\(e \approx 2.718\));指数上是负的”残差平方除以 \(2\sigma^2\)“,残差为 0 时密度最大,残差越大密度按平方指数式衰减。现在反过来想:给定 20 个点,哪条直线最”可能”产生它们?每个点的密度相乘就是全部点一起出现的可能性(假设各点噪声独立),选让这个乘积最大的 \(w\)——这叫最大似然估计(maximum likelihood estimation,MLE)。

20 个很小的数相乘不好处理,取对数:\(\log(ab) = \log a + \log b\),乘积变成和;对数是单调增的,让乘积最大等价于让对数和最大。再加个负号变成”让某个东西最小”,好与损失函数的习惯一致。对上面的密度取负对数,\(\log \exp(z) = z\),得

\[-\log p(y \mid x) = \frac{(y - w^T x)^2}{2\sigma^2} + \underbrace{\log(\sqrt{2\pi}\sigma)}_{\text{与 } w \text{ 无关的常数}}\]

对全部样本求和、丢掉常数和系数 \(1/2\sigma^2\),就是 \(\sum_i (y_i - w^T x_i)^2\)——最小二乘就是高斯噪声假设下的最大似然估计。平方来自高斯密度指数上的那个平方。换成拉普拉斯噪声(密度 \(\propto e^{-\lvert y - w^T x \rvert / b}\),指数上是绝对值)做同样的推导,就得到绝对值误差。

2. 离群点

这个假设的代价:高斯分布的尾巴很薄,它”不相信”会有很大的残差,所以一个很大的残差(平方后更大)会把直线狠狠拉过去。20 个点里改两个成离群点:

20 个点中两个被移成离群点(红圈):平方误差拟合的橙线被拉得很平(w = 0.82);绝对值误差拟合的绿线仍与真实的灰色虚线(w = 1.5)几乎重合(w = 1.57)

情形 斜率 \(w\)
无离群点,平方误差 1.57
两个离群点,平方误差 0.82
两个离群点,绝对值误差 1.57

数据里有离群点(标注错误、爬虫抓到的乱码)时,平方误差会被少数点带偏,绝对值误差(或 Huber 损失——小残差用平方、大残差用绝对值)稳得多。这是”损失函数是对噪声分布的假设”的第一个实例:选损失,就是在说你相信误差长什么样。

九、本文小结

  • 模型 \(\hat y = w^T x + b\),加一列 1 把 \(b\) 并进 \(w\);损失是残差平方和,20 个点的最小二乘直线 38.7 vs 随手画的 90 多。
  • 闭式解:对损失求导令为零得正规方程 \(X^T X w = X^T y\),几何上是把 \(y\) 投影到特征张成的平面;\(d > n\) 或特征共线时 \(X^T X\) 不可逆。
  • 梯度下降:损失是一个碗,GD 沿负梯度走;学习率上限 \(2 / \lambda_{\max}\)(20 个点的直线是 0.027),条件数 = 沟有多窄;GD 与闭式解差 \(10^{-10}\),SGD 用 1/12 的算量到同一邻域但在附近抖动(噪声 ∝ 学习率 / batch)。
  • 两种坏地形:特征量纲不同(条件数 540 万,标准化后 1);特征共线(系数 7.88 / −5.95 乱跳,Ridge 各分一半)。
  • Ridge \(+\alpha \lVert w \rVert_2^2\) 把所有系数一起压小、让 \(X^T X + \alpha I\) 可逆;Lasso \(+\alpha \lVert w \rVert_1\) 把无用系数压到恰好为零(50 个特征归零 45 个)——菱形的角在坐标轴上。
  • Ridge = weight decay:\(w \leftarrow (1 - 2\eta\lambda) w - \eta g\),与闭式解差 \(10^{-13}\);15 次多项式加 \(\alpha = 0.01\) 的 Ridge,最大系数 2021 → 5,验证 MSE 0.143 → 0.108,容量不变、曲线平滑。
  • 平方误差 = 高斯噪声的最大似然;有离群点时它被拉偏(斜率 1.57 → 0.82),绝对值误差不受影响。

配套代码:本文全部数字与图由 classical-ml/02_linear_regression.py 产生(line / surface / gd / scaling / collinear / paths / geometry / wd / smooth / robust 十个子实验),CPU 上十几秒跑完。

十、自测

  1. 三个点 \((0, 1), (1, 3), (2, 4)\),用第三章的两个方程手算最小二乘直线。

    答案

    \(n = 3, \sum x = 3, \sum x^2 = 5, \sum y = 8, \sum xy = 0 + 3 + 8 = 11\)。方程 \(3b + 3w = 8\)、\(3b + 5w = 11\),相减得 \(2w = 3\),\(w = 1.5\),\(b = (8 - 4.5)/3 = 1.17\)。直线 \(\hat y = 1.5x + 1.17\),残差 \(-0.17, 0.33, -0.17\)。

  2. 20 个点、一个特征,闭式解要解几元几次方程?1000 个特征呢?

    答案

    加偏置后 \(w\) 有 2 个未知数,解 2×2 的线性方程组;1000 个特征是 1001×1001。线性方程组,一次,一步解出。

  3. 梯度下降的损失曲线先降后突然变成 NaN。最可能的原因?怎么算一个安全的学习率?

    答案

    学习率超过 \(2 / \lambda_{\max}\) 发散了;算 \(\frac{2}{n} X^T X\) 的最大特征值取 2 除以它,或先标准化特征让条件数变小。

  4. 一个特征是”字数”(0–10000),一个是”评分”(1–5),不标准化直接梯度下降会怎样?

    答案

    碗变成极窄的沟,安全学习率被”字数”限制到极小,”评分”的系数几乎不动;标准化后条件数接近 1。

  5. 100 个样本、1000 个特征,无正则的线性回归会发生什么?Ridge 和 Lasso 各怎么救?

    答案

    \(X^T X\) 不可逆 / 无穷多解、严重过拟合;Ridge 加 \(\alpha I\) 让解唯一并压小系数,Lasso 选出少数特征。

  6. 为什么 Lasso 能把系数压到恰好为零而 Ridge 不能?用几何说一句。

    答案

    \(L_1\) 的约束区是菱形、角在坐标轴上,等高线先碰到角;\(L_2\) 是圆、没有角。(或:\(L_1\) 的推力在零附近不减弱。)

  7. AdamW 里 weight_decay=0.1,从线性回归的角度看它在做什么?为什么 LLM 不用 Lasso?

    答案

    每步把权重乘 \((1 - \eta \lambda)\)——Ridge 的梯度下降形态;Lasso 的稀疏性对稠密 Transformer 权重没有直接用处。

下一篇

下一篇把线性回归的输出过一个 sigmoid,变成预测概率的逻辑回归——每一个神经网络分类头的原型;然后证明奖励模型就是一个作用在”两个回答的特征差”上的逻辑回归,它的准确率上限由标注一致性决定。

  1. 它解的是正规方程 \(X^T X w = X^T y\)——对残差平方和求导令为零得到的一个 \(d \times d\) 线性方程组;几何上是把 \(y\) 投影到特征张成的平面。因为损失是二次的、碗只有一个底,所以一步能解出;\(d > n\) 或特征共线时 \(X^T X\) 不可逆,要加 Ridge。详见第三章。 

  2. 损失是一个碗,但通常是一条斜的窄沟:陡的方向限制学习率(上限 \(2 / \lambda_{\max}\),20 个点的直线是 0.027),缓的方向要走很远——条件数越大越慢;超过上限就在沟壁间越跳越高、发散。特征量纲不同(条件数 540 万)与特征共线是两种典型的坏地形,标准化与 Ridge 分别修它们。详见第四章第五章。 

  3. 同一个东西。Ridge 在损失里加 \(\lambda \lVert w \rVert^2\),梯度多出 \(2\lambda w\),代进梯度下降就是 \(w \leftarrow (1 - 2\eta\lambda) w - \eta g\)——每步先把权重乘一个略小于 1 的数,这就是 weight decay;数值上与 Ridge 闭式解差 \(10^{-13}\)。详见第七章。 

本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/linear-regression-least-squares-ridge-and-lasso.html)的前提下,欢迎各种形式的转载、翻译或商业引用。


COMMENTS

评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。

×