用Python和NumPy手把手实现最小二乘法:从拟合直线到理解投影矩阵
用Python和NumPy手把手实现最小二乘法:从拟合直线到理解投影矩阵
在数据分析和机器学习领域,最小二乘法是一个基础但极其重要的概念。它不仅是线性回归的核心算法,更是理解许多高级机器学习模型的基础。本文将通过Python和NumPy库,从零开始实现最小二乘法,并通过可视化手段直观展示其背后的线性代数原理。
1. 最小二乘法基础与问题设定
最小二乘法(Least Squares, LS)的核心思想是通过最小化误差的平方和来寻找数据的最佳函数匹配。假设我们有一组二维数据点,希望找到一条直线y = kx + b,使得所有数据点到这条直线的垂直距离的平方和最小。
为什么需要最小二乘法?
- 在实际测量中,数据往往存在噪声和误差
- 当方程数量多于未知数时,通常无法找到精确解
- 最小二乘解提供了在统计意义上最优的近似解
让我们先创建一个简单的数据集作为示例:
import numpy as np
import matplotlib.pyplot as plt
# 创建示例数据
x = np.array([1, 2, 3, 4, 5])
y = np.array([2.1, 3.9, 6.2, 8.1, 9.8])
plt.scatter(x, y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('原始数据点')
plt.show()
2. 构建矩阵方程与投影矩阵
最小二乘问题可以转化为线性代数中的矩阵方程。对于直线拟合问题,我们需要解以下形式的方程:
Aθ = b
其中:
- A是设计矩阵,包含x值和常数项
- θ是参数向量[k, b]ᵀ
- b是观测值向量
具体构建方法如下:
# 构建矩阵A和向量b
A = np.column_stack([x, np.ones(len(x))]) # 添加一列1用于截距项
b = y.reshape(-1, 1) # 转换为列向量
print("设计矩阵A:\n", A)
print("\n观测向量b:\n", b)
投影矩阵在最小二乘法中扮演着关键角色。它可以将向量b投影到矩阵A的列空间上:
P = A(AᵀA)⁻¹Aᵀ
这个矩阵的性质非常有趣:
- 对称性:P = Pᵀ
- 幂等性:P² = P
- 秩等于A的秩
3. 计算最小二乘解
有了投影矩阵的概念,我们可以通过两种等价的方式计算最小二乘解:
方法一:直接求解正规方程
# 计算最小二乘解
theta = np.linalg.inv(A.T @ A) @ A.T @ b
k, b = theta.flatten()
print(f"拟合直线方程: y = {k:.3f}x + {b:.3f}")
方法二:使用投影矩阵
# 计算投影矩阵
P = A @ np.linalg.inv(A.T @ A) @ A.T
# 计算投影后的b值
b_proj = P @ b
# 解方程Aθ = b_proj
theta_proj = np.linalg.pinv(A) @ b_proj
这两种方法得到的结果应该完全相同,这验证了最小二乘法的数学一致性。
4. 结果可视化与误差分析
理解最小二乘法的几何意义至关重要。让我们将原始数据、拟合直线和投影点可视化:
# 生成拟合直线上的点
x_fit = np.linspace(0, 6, 100)
y_fit = k * x_fit + b
# 计算投影点
y_proj = k * x + b
plt.figure(figsize=(10, 6))
plt.scatter(x, y, label='原始数据点', c='blue')
plt.plot(x_fit, y_fit, label=f'拟合直线: y = {k:.2f}x + {b:.2f}', c='red')
plt.scatter(x, y_proj, label='投影点', c='green', marker='x')
# 绘制误差线
for xi, yi, ypi in zip(x, y, y_proj):
plt.plot([xi, xi], [yi, ypi], 'k--', alpha=0.3)
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.title('最小二乘法拟合结果')
plt.grid(True)
plt.show()
误差分析是评估拟合质量的重要环节。我们可以计算几个关键指标:
# 计算预测值
y_pred = k * x + b
# 计算残差
residuals = y - y_pred
# 计算R平方
ss_res = np.sum(residuals**2)
ss_tot = np.sum((y - np.mean(y))**2)
r_squared = 1 - (ss_res / ss_tot)
print(f"残差平方和: {ss_res:.3f}")
print(f"R平方值: {r_squared:.3f}")
5. 扩展到多元线性回归
最小二乘法不仅适用于直线拟合,还可以扩展到多元线性回归。假设我们有多个自变量,只需扩展设计矩阵A即可:
# 假设我们有第二个特征x2
x2 = np.array([0.5, 1.5, 2.5, 3.5, 4.5])
# 构建设计矩阵
A_multi = np.column_stack([x, x2, np.ones(len(x))])
# 计算多元回归系数
theta_multi = np.linalg.inv(A_multi.T @ A_multi) @ A_multi.T @ b.reshape(-1, 1)
print("多元回归系数:", theta_multi.flatten())
投影矩阵在多元回归中的应用同样重要。通过投影矩阵,我们可以:
- 理解模型如何将高维响应变量投影到设计矩阵的列空间
- 计算帽子矩阵(Hat Matrix)用于诊断回归分析
- 进行变量选择和模型比较
6. 数值稳定性与实用技巧
在实际应用中,直接计算(AᵀA)⁻¹可能会遇到数值不稳定的问题。以下是几种改进方法:
使用QR分解
Q, R = np.linalg.qr(A)
theta_qr = np.linalg.inv(R) @ Q.T @ b
print("QR分解得到的解:", theta_qr.flatten())
使用奇异值分解(SVD)
U, S, Vt = np.linalg.svd(A, full_matrices=False)
theta_svd = Vt.T @ np.linalg.inv(np.diag(S)) @ U.T @ b
print("SVD得到的解:", theta_svd.flatten())
正则化方法(如岭回归)可以处理病态矩阵问题:
lambda_ = 0.1 # 正则化参数
theta_ridge = np.linalg.inv(A.T @ A + lambda_ * np.eye(2)) @ A.T @ b
print("岭回归解:", theta_ridge.flatten())
7. 从线性代数视角理解最小二乘
最小二乘法的美妙之处在于它完美结合了几何直观和代数严谨性。从线性代数角度看:
- 列空间与投影:最小二乘法寻找的是b在设计矩阵A列空间上的正交投影
- 正交性原理:残差向量与A的列空间正交
- 四个基本子空间:理解值域、零空间、行空间和左零空间的关系
这些概念不仅帮助我们理解算法本质,还能指导我们解决更复杂的问题。
8. 实际应用中的注意事项
在实际项目中使用最小二乘法时,需要注意以下几点:
数据预处理
- 特征缩放:特别是使用正则化时
- 处理异常值:最小二乘对异常值敏感
- 检查多重共线性:避免(AᵀA)接近奇异矩阵
模型诊断
- 残差分析:检查是否满足线性假设
- 影响点检测:识别对模型影响过大的样本
- 交叉验证:评估模型泛化能力
替代方法
- 当数据存在异方差性时,考虑加权最小二乘
- 对于高维数据,考虑正则化方法(岭回归、Lasso)
- 对于非线性关系,考虑多项式回归或核方法
9. 性能优化与大规模实现
当数据量很大时,直接矩阵求逆可能效率低下。可以考虑以下优化:
迭代方法
- 共轭梯度法
- 随机梯度下降
分块计算
# 假设数据太大,无法一次性加载
def block_solver(A_blocks, b_blocks):
ATA = np.zeros((A_blocks[0].shape[1], A_blocks[0].shape[1]))
ATb = np.zeros(A_blocks[0].shape[1])
for A, b in zip(A_blocks, b_blocks):
ATA += A.T @ A
ATb += A.T @ b
return np.linalg.solve(ATA, ATb)
使用专用库
- scikit-learn的LinearRegression
- statsmodels提供的更全面的统计工具
- 分布式计算框架如Spark MLlib
10. 从最小二乘到现代机器学习
最小二乘法是许多现代机器学习算法的基础。理解它有助于掌握:
- 线性模型:如何扩展到广义线性模型
- 正则化路径:从岭回归到弹性网络
- 核方法:通过特征映射处理非线性问题
- 贝叶斯视角:最大后验估计与高斯过程
在深度学习时代,最小二乘的思想仍然重要。例如,神经网络的训练通常使用梯度下降来最小化平方误差损失函数。
更多推荐

所有评论(0)