最小二乘法 vs 梯度下降:3 大核心差异与 2 种 Python 实现对比
当我们需要从数据中找出变量之间的关系时,线性回归是最常用的工具之一。而求解线性回归参数,最小二乘法和梯度下降是两种经典方法。本文将深入解析它们的数学原理、核心差异,并通过Python代码实现对比它们的实际效果。
1. 数学原理对比
1.1 最小二乘法:解析解的优雅
最小二乘法(Ordinary Least Squares, OLS)的核心思想是通过最小化预测值与真实值之间的平方误差和来求解参数。对于一元线性回归模型:
y = wx + b最小二乘法的目标是找到w和b,使得:
min Σ(y_i - (wx_i + b))^2通过求导并令导数为零,我们可以得到闭式解(解析解):
# 最小二乘法参数计算 def least_squares(X, y): n = len(X) w = (n*np.sum(X*y) - np.sum(X)*np.sum(y)) / (n*np.sum(X**2) - np.sum(X)**2) b = (np.sum(y) - w*np.sum(X)) / n return w, b优点:
- 直接计算,无需迭代
- 当特征数量不多时效率高
- 能得到全局最优解
1.2 梯度下降:迭代逼近的艺术
梯度下降是一种迭代优化算法,通过沿着损失函数的负梯度方向逐步调整参数。对于同样的损失函数:
J(w,b) = 1/2m * Σ(y_i - (wx_i + b))^2参数更新规则为:
# 梯度下降参数更新 def gradient_descent(X, y, w, b, learning_rate, iterations): m = len(X) for _ in range(iterations): y_pred = w*X + b dw = -(2/m) * np.sum(X * (y - y_pred)) db = -(2/m) * np.sum(y - y_pred) w -= learning_rate * dw b -= learning_rate * db return w, b特点:
- 适用于特征维度高的情况
- 可以逐步优化,看到收敛过程
- 需要选择合适的学习率和迭代次数
2. 3 大核心差异分析
2.1 计算效率对比
| 方法 | 计算复杂度 | 适合场景 |
|---|---|---|
| 最小二乘法 | O(n^3) | 特征数少(<10000) |
| 梯度下降 | O(kn) | 特征数多或样本量巨大 |
提示:当特征矩阵X^T X不可逆时,最小二乘法需要引入正则化或使用伪逆,而梯度下降仍可工作。
2.2 收敛特性
- 最小二乘法:一步到位得到解析解,没有收敛过程
- 梯度下降:需要监控损失函数下降曲线,可能遇到:
- 学习率过大:震荡甚至发散
- 学习率过小:收敛过慢
# 监控梯度下降的损失变化 loss_history = [] for i in range(iterations): # ...参数更新代码... loss = np.mean((y - (w*X + b))**2) loss_history.append(loss)2.3 适用场景差异
最小二乘法更优的情况:
- 数据量适中,特征维度不高
- 需要精确解析解
- 硬件资源有限(无需存储中间结果)
梯度下降更优的情况:
- 特征维度极高(如>10000)
- 数据量太大无法一次性加载
- 需要在线学习(持续更新模型)
3. Python实现对比
3.1 最小二乘法实现
import numpy as np import matplotlib.pyplot as plt # 生成模拟数据 np.random.seed(42) X = 2 * np.random.rand(100, 1) y = 4 + 3 * X + np.random.randn(100, 1) # 最小二乘法实现 def least_squares(X, y): X_b = np.c_[np.ones((len(X), 1)), X] # 添加偏置项 theta = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y) return theta # 计算参数 theta = least_squares(X, y) print(f"参数估计:截距={theta[0][0]:.3f}, 斜率={theta[1][0]:.3f}") # 可视化 plt.scatter(X, y) plt.plot(X, theta[1]*X + theta[0], 'r-') plt.title("最小二乘法拟合结果") plt.show()3.2 梯度下降实现
# 梯度下降实现 def gradient_descent(X, y, learning_rate=0.1, n_iterations=1000): m = len(X) theta = np.random.randn(2, 1) # 随机初始化 X_b = np.c_[np.ones((m, 1)), X] # 添加偏置项 for iteration in range(n_iterations): gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y) theta -= learning_rate * gradients return theta # 计算参数 theta_gd = gradient_descent(X, y) print(f"梯度下降估计:截距={theta_gd[0][0]:.3f}, 斜率={theta_gd[1][0]:.3f}") # 可视化迭代过程 theta_path = [] def plot_gradient_descent(X, y, theta, learning_rate, n_iterations): m = len(X) X_b = np.c_[np.ones((m, 1)), X] plt.scatter(X, y) for iteration in range(n_iterations): if iteration % 100 == 0: y_predict = X_b.dot(theta) plt.plot(X, y_predict, 'b-', alpha=0.3) gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y) theta -= learning_rate * gradients theta_path.append(theta) plt.plot(X, X_b.dot(theta), 'r-') plt.title("梯度下降拟合过程") plt.show() plot_gradient_descent(X, y, np.random.randn(2, 1), 0.1, 1000)4. 实际效果对比
我们在波士顿房价数据集上对比两种方法:
from sklearn.datasets import load_boston from sklearn.preprocessing import StandardScaler # 加载数据 boston = load_boston() X = boston.data[:, 5:6] # 只使用RM特征 y = boston.target.reshape(-1, 1) # 标准化 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) y_scaled = scaler.fit_transform(y) # 对比两种方法 theta_ls = least_squares(X_scaled, y_scaled) theta_gd = gradient_descent(X_scaled, y_scaled, learning_rate=0.1, n_iterations=1000) # 计算R^2分数 def r2_score(y_true, y_pred): ss_res = np.sum((y_true - y_pred)**2) ss_tot = np.sum((y_true - np.mean(y_true))**2) return 1 - (ss_res / ss_tot) y_pred_ls = theta_ls[1]*X_scaled + theta_ls[0] y_pred_gd = theta_gd[1]*X_scaled + theta_gd[0] print(f"最小二乘法R2: {r2_score(y_scaled, y_pred_ls):.4f}") print(f"梯度下降R2: {r2_score(y_scaled, y_pred_gd):.4f}")典型输出结果:
最小二乘法R2: 0.4835 梯度下降R2: 0.4835从实际项目经验看,当数据预处理得当且迭代次数足够时,两种方法得到的模型性能非常接近。但在处理超大规模数据时,梯度下降的批量变体(如随机梯度下降)往往更具优势。