numpy 编写梯度下降的例子 (二)
正规方程(Normal Equation)是线性回归在最小二乘准则下的解析解(闭式解,Closed-Form Solution)。与梯度下降通过迭代逼近极值点不同,正规方程利用微积分极值条件,一步直接求出最优参数。
一、正规方程的数学推导
1. 矩阵与符号定义
设数据集包含 \(n\) 个样本,\(d\) 个特征。定义包含常数偏置项的增广设计矩阵 \(X\)、真实标签向量 \(\mathbf{y}\) 与待求参数向量 \(\theta\):
模型预测值为:
2. 构造损失函数
采用无约束最小二乘误差(MSE 的常数系数并不影响极值点位置,推导中常取残差平方和的一半 \(\frac{1}{2}\sum e_i^2\) 以简化求导常数):
3. 展开二次型
利用转置性质 \((A - B)^T = A^T - B^T\) 展开:
因为 \(\theta^T X^T \mathbf{y}\) 和 \(\mathbf{y}^T X \theta\) 都是标量(维度为 \(1 \times 1\)),且标量的转置等于其自身:
因此中间两项相等,可直接合并:
4. 矩阵求导与极值条件
对向量 \(\theta\) 求梯度。需要用到两条经典矩阵微分法则:
- 线性项求导: \(\nabla_\theta (\mathbf{a}^T \theta) = \mathbf{a}\)
- 二次型求导: 若矩阵 \(A\) 为对称矩阵,则 \(\nabla_\theta (\theta^T A \theta) = 2 A \theta\)。这里矩阵 \(A = X^T X\) 显然满足对称性:\((X^T X)^T = X^T (X^T)^T = X^T X\)。
对损失函数求偏导:
因为 \(J(\theta)\) 是凸函数,当梯度为零向量时取得全局极小值:
整理得到正规方程的标准形式:
5. 求解最优参数
若矩阵 \(X^T X\) 满秩可逆,在方程两边左乘 \((X^T X)^{-1}\):
注: 若特征之间存在严重线性相关(多重共线性)或样本量小于特征数(\(n < d\)),\(X^T X\) 可能奇异不可逆。实际工程中会采用 Moore-Penrose 伪逆(
np.linalg.pinv),或引入 \(L_2\) 正则化(岭回归,变为 \((X^T X + \lambda I)^{-1} X^T \mathbf{y}\),保证矩阵严格正定可逆)。
二、几何视角:正交投影
正规方程在几何上代表正交投影:
- 预测值 \(\hat{\mathbf{y}} = X\theta\) 是设计矩阵 \(X\) 的列向量张成的超平面(记为 \(\operatorname{col}(X)\))中的一个点。
- 真实值 \(\mathbf{y}\) 通常不在这个超平面上。
- 残差向量 \(\mathbf{e} = \mathbf{y} - X\theta\) 代表从点 \(\mathbf{y}\) 到超平面的连线。当该连线垂直(正交)于该平面时,残差模长最小。
- 正交条件意味着残差向量与 \(X\) 的每一列内积全为 0:
三、正规方程 vs. 梯度下降
| 比较维度 | 正规方程 (Normal Equation) | 梯度下降 (Gradient Descent) |
|---|---|---|
| 求解机制 | 解析解:矩阵求逆一步到位 | 迭代法:沿负梯度逐步逼近 |
| 超参数调节 | 无需选择学习率 \(\eta\);无需指定迭代轮数 | 需调优学习率 \(\eta\)、迭代次数、收敛容差 |
| 特征缩放需求 | 不需要特征缩放(对量纲差异不敏感) | 高度依赖特征缩放(否则等高线扁平导致震荡) |
| 计算时间复杂度 | 矩阵求逆复杂度约为 \(\mathcal{O}(d^3)\) 或 \(\mathcal{O}(d^{2.8})\) | 每轮迭代复杂度约为 \(\mathcal{O}(k \cdot n \cdot d)\) |
| 内存占用 | 需在内存中计算并存储 \(X^T X\)(\(\mathcal{O}(d^2)\)) | 支持小批量(Mini-batch),内存开销极低 |
| 适用场景 | 特征维度小 (\(d \le 10,000\))、数据可全部放入内存 | 大规模特征 (\(d \gg 10^5\))、海量样本、流式数据 |
| 算法拓展性 | 仅适用于线性回归、岭回归等简单凸优化 | 普适于逻辑回归、深度神经网络等复杂非线性架构 |
四、NumPy 验证对比
用前文提到的两特征房价数据,直接对比两种求解方式的数值输出:
import numpy as np
# 样本数据 (面积 10m², 房间数, 价格万元)
raw_X = np.array([
[5.0, 1.0],
[7.0, 2.0],
[9.0, 2.0],
[11.0, 3.0],
[13.0, 4.0]
])
y = np.array([102.0, 148.0, 179.0, 229.0, 275.0])
n = len(y)
# 构造增广矩阵
X = np.c_[raw_X, np.ones(n)]
# 1. 正规方程直接求解
# 优先使用 np.linalg.pinv (伪逆) 避免矩阵不可逆崩溃
theta_normal = np.linalg.pinv(X.T @ X) @ X.T @ y
# 2. 梯度下降求解 (学习率 0.005, 30000 轮)
theta_gd = np.zeros(3)
lr = 0.005
for _ in range(30000):
grad = (2.0 / n) * (X.T @ (X @ theta_gd - y))
theta_gd -= lr * grad
print("正规方程解析解 :", np.round(theta_normal, 4))
print("梯度下降近似解 :", np.round(theta_gd, 4))
运行结果:
正规方程解析解 : [17.5714 9.7143 4.0714]
梯度下降近似解 : [17.5714 9.7143 4.0714]
当特征维度 \(d\) 不大时,正规方程简单直接且没有调参负担;但在工业界和现代深度学习中,由于参数动辄数百万到数百亿,计算逆矩阵在算力与显存上完全不可行,梯度下降(以及 Adam、SGD 等变体)是无可替代的核心基石。
误差
导数确实只是当前点无限小邻域内的“瞬时变化率”,一旦离开这个点,导数本身就会改变。因此,直接拿固定步长去减,必然存在误差。
理解这个误差的来源以及算法如何解决它,需要从泰勒展开和迭代机制来看。
一、误差从何而来:一阶泰勒展开的近似截断
考虑一元函数 \(f(w)\)。当参数从 \(w\) 移动一个微小增量 \(\Delta w\) 时,根据泰勒展开(Taylor Expansion):
梯度下降选择的更新步长为:
将 \(\Delta w\) 代入泰勒展开式中:
- 理想情况(局部线性): 如果步长极小(\(\eta \to 0\)),高阶项 \(\eta^2\) 可以忽略不计,函数值确实下降了 \(\eta [f'(w)]^2\)。
- 现实情况(非线性曲率): 只要步长不是无限小,就必定会受到二阶项(曲率 \(f''(w)\))的干扰。这就是你感受到的“更新误差”。
二、既然有误差,为什么模型不会崩?
既然每一步都不精确,梯度下降能收敛主要依赖两个机制:
1. “盲人下山”的动态修正机制
梯度下降从来没有指望一步就算准全局最优解。
- 导数只在脚下的一小块区域有效。
- 我们沿着当下的斜率迈出很小的一步(参数更新)。
- 踩到新位置后,旧的瞬时变化率立刻作废;我们在新位置重新测量此时此刻的瞬时变化率,再决定下一步往哪迈。
这种高频的“微调-重新采样-再微调”闭环,不断吞噬掉上一步产生的二阶误差。
2. 学习率 \(\eta\) 的调控作用
学习率 \(\eta\) 本质上是对“局部线性假设”成立范围的约束:
- 如果 \(\eta\) 适中偏小: 负项 \(-\eta [f'(w)]^2\) 远大于正向误差项 \(\frac{1}{2} \eta^2 f''(w) [f'(w)]^2\),每一步总能保证函数值净下降。
- 如果 \(\eta\) 太大: 此时忽略二阶误差的代价太大,误差项超过了下降项,导致损失不降反升,甚至彻底震荡发散。
三、能消除这个误差吗?(牛顿法与二阶优化)
如果你希望消除这种“用瞬时变化率代替整体弯曲”的误差,数学上有现成的方案——牛顿法(Newton's Method)。
牛顿法直接把泰勒二阶项也纳入考量,让一阶导数与二阶导数共同决定步长:
在多维空间中,二阶导数对应的是 Hessian 矩阵(海森矩阵,包含所有二阶偏导):
牛顿法直接用抛物面去逼近曲面,更新时几乎没有线性截断误差,在极少数步数内即可收敛。
为什么深度学习通常不用牛顿法?
- 如果模型有 1 亿个参数,梯度向量只有 1 亿维(\(\mathcal{O}(d)\));
- 但海森矩阵的大小是 \(1\text{ 亿} \times 1\text{ 亿}\)(\(\mathcal{O}(d^2)\)),求逆矩阵的计算复杂度更是高达 \(\mathcal{O}(d^3)\)。
因此,现代机器学习做出的妥协是:宁愿承担每一步用瞬时变化率带来的微小近似误差,靠小步快跑(一阶梯度迭代)来换取极高的计算吞吐量。

浙公网安备 33010602011771号