用Python+NumPy手把手实现最小二乘法:从拟合直线到理解投影矩阵
用Python+NumPy手把手实现最小二乘法:从拟合直线到理解投影矩阵
在数据科学和机器学习领域,最小二乘法是一个基础但极其重要的概念。它不仅是线性回归的核心算法,更是理解许多高级机器学习模型的基础。本文将通过Python和NumPy库,带你从零开始实现最小二乘法,并通过可视化手段深入理解其背后的线性代数原理。
1. 最小二乘法基础概念
最小二乘法(Least Squares, LS)的核心思想是通过最小化误差的平方和来寻找数据的最佳函数匹配。当我们有一组数据点,希望用一条直线来拟合这些点时,最小二乘法能给出最优的斜率和截距。
为什么需要最小二乘法?
- 在实际测量中,数据往往带有噪声
- 当方程数量多于未知数时,通常无精确解
- 最小二乘解能给出最接近所有数据点的直线
考虑三个数据点(1,1)、(2,2)、(3,2),我们可以建立方程组:
import numpy as np
A = np.array([[1, 1], [2, 1], [3, 1]])
b = np.array([1, 2, 2])
这个方程组是超定的(方程数多于未知数),没有精确解。最小二乘法就是要找到参数x使得Ax尽可能接近b。
2. 投影矩阵的数学原理
最小二乘法的核心在于投影矩阵。给定矩阵A,其投影矩阵P定义为:
P = A(AᵀA)⁻¹Aᵀ
这个矩阵的神奇之处在于,它能将任何向量b投影到A的列空间上。投影后的向量p=Pb就是A列空间中最接近b的点。
投影矩阵的性质:
- 对称性:P = Pᵀ
- 幂等性:P² = P
- 秩等于A的秩
在Python中,我们可以这样计算投影矩阵:
def projection_matrix(A):
return A @ np.linalg.inv(A.T @ A) @ A.T
P = projection_matrix(A)
print("投影矩阵P:\n", P)
3. 实现最小二乘解
有了投影矩阵,我们可以通过两种等价的方式得到最小二乘解:
方法一:直接解正规方程
AᵀAx̂ = Aᵀb
对应的Python实现:
x_hat = np.linalg.inv(A.T @ A) @ A.T @ b
print("最小二乘解:", x_hat)
方法二:通过投影
先计算投影p=Pb,然后解Ax̂=p:
p = P @ b
x_hat = np.linalg.lstsq(A, p, rcond=None)[0]
这两种方法在数学上是等价的,但数值计算上第一种更直接高效。
4. 验证正交性
最小二乘法的关键性质是误差向量e=b-Ax̂与A的列空间正交。我们可以用点积来验证:
e = b - A @ x_hat
print("A的转置与e的点积:", A.T @ e)
理论上这个结果应该非常接近零向量,验证了我们的解确实满足正交性条件。
5. 可视化理解
为了更直观地理解,我们可以用matplotlib将整个过程可视化:
import matplotlib.pyplot as plt
# 原始数据点
plt.scatter(A[:,0], b, color='red', label='原始数据')
# 拟合直线
x_line = np.linspace(0, 4, 100)
y_line = x_hat[0]*x_line + x_hat[1]
plt.plot(x_line, y_line, label='拟合直线')
# 投影点
p = A @ x_hat
plt.scatter(A[:,0], p, color='green', marker='x', label='投影点')
# 误差线
for i in range(len(b)):
plt.plot([A[i,0], A[i,0]], [b[i], p[i]], 'k--')
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.title('最小二乘法直线拟合')
plt.grid(True)
plt.show()
这张图会显示:
- 原始数据点(红色)
- 拟合的直线
- 投影到直线上的点(绿色×)
- 误差向量(黑色虚线)
6. 实际应用案例
最小二乘法在实际中有广泛应用。例如,考虑一个简单的房价预测模型:
# 房屋面积和价格数据
area = np.array([50, 60, 70, 80, 90]) # 平方米
price = np.array([200, 230, 250, 280, 310]) # 万元
# 构建A矩阵和b向量
A = np.vstack([area, np.ones(len(area))]).T
b = price
# 计算最小二乘解
x_hat = np.linalg.inv(A.T @ A) @ A.T @ b
print(f"房价模型: 价格 = {x_hat[0]:.2f} × 面积 + {x_hat[1]:.2f}")
这个模型可以帮助我们根据房屋面积预测价格,展示了最小二乘法在实际问题中的应用价值。
7. 数值稳定性考虑
在实际计算中,直接求逆矩阵可能带来数值不稳定性。更稳健的方法是使用QR分解或SVD:
使用QR分解:
Q, R = np.linalg.qr(A)
x_hat = np.linalg.inv(R) @ Q.T @ b
使用numpy内置的最小二乘函数:
x_hat = np.linalg.lstsq(A, b, rcond=None)[0]
这些方法在数值上更稳定,特别是当A的条件数较大时。
8. 扩展到多项式拟合
最小二乘法不仅可以拟合直线,还可以拟合更高阶的多项式。例如,二次拟合:
# 生成二次数据
x = np.array([1, 2, 3, 4, 5])
y = np.array([1.2, 3.9, 8.1, 15.2, 23.5])
# 构建A矩阵:包含x², x, 1项
A = np.vstack([x**2, x, np.ones(len(x))]).T
# 求解
x_hat = np.linalg.lstsq(A, y, rcond=None)[0]
print(f"二次模型: y = {x_hat[0]:.2f}x² + {x_hat[1]:.2f}x + {x_hat[2]:.2f}")
这种扩展性展示了最小二乘法的强大灵活性。
9. 理解AᵀA的性质
最小二乘法中,AᵀA矩阵扮演着核心角色。它的性质决定了我们能否得到唯一解:
- AᵀA总是对称的
- 当A列满秩时,AᵀA可逆
- AᵀA的条件数是A条件数的平方
我们可以检查矩阵的条件数:
cond_A = np.linalg.cond(A)
cond_ATA = np.linalg.cond(A.T @ A)
print(f"A的条件数: {cond_A:.2f}")
print(f"AᵀA的条件数: {cond_ATA:.2f}")
理解这些性质有助于我们在实际问题中选择合适的求解方法。
10. 常见问题与调试技巧
在实现最小二乘法时,可能会遇到以下问题:
问题1:矩阵不可逆
- 检查A是否列满秩
- 考虑使用正则化(岭回归)
问题2:数值不稳定
- 使用QR分解代替直接求逆
- 对数据进行标准化
问题3:拟合效果差
- 检查数据是否有异常值
- 考虑使用更复杂的模型(如多项式拟合)
# 检查矩阵秩
print("A的秩:", np.linalg.matrix_rank(A))
print("AᵀA的秩:", np.linalg.matrix_rank(A.T @ A))
11. 性能优化技巧
对于大规模数据,我们可以采用一些优化策略:
使用广播机制加速计算:
# 快速计算误差平方和
def compute_sse(A, b, x):
return np.sum((b - A @ x)**2)
利用稀疏矩阵(当A很大且稀疏时):
from scipy.sparse import csr_matrix
from scipy.sparse.linalg import lsqr
A_sparse = csr_matrix(A)
result = lsqr(A_sparse, b)
x_hat = result[0]
12. 从线性代数到机器学习
最小二乘法是连接线性代数和机器学习的重要桥梁。在机器学习中:
- 线性回归就是最小二乘法
- 正则化最小二乘对应岭回归和Lasso
- 核方法可以看作是在特征空间中的最小二乘
理解最小二乘法的原理,为学习更复杂的机器学习模型奠定了坚实基础。
更多推荐



所有评论(0)