最小二乘法:从原理到实践,掌握线性回归与机器学习基石
2026/9/14 22:25:56 网站建设 项目流程

1. 项目概述:从“猜”到“算”的思维跃迁

干了这么多年数据分析和算法工程,我越来越觉得,很多听起来高大上的机器学习概念,其核心思想往往朴素得惊人。今天想聊的“最小二乘法”就是这样一个典型。你可能在各种教科书、论文或者技术博客里见过它,公式看起来有点复杂,但它的内核,其实就是我们解决“猜不准”这个问题时,一种最本能、最优雅的数学表达。简单说,它就是在一堆散乱的数据点里,找出一条最“合适”的直线(或曲线),让这条线到所有点的“总体距离”最小。这个“总体距离”,用的就是“差的平方和”,所以叫“最小二乘”。

别被“机器学习”这个词吓到。无论你是刚入门的数据科学新生,还是在为“山东大学机器学习期末”或“西电机器学习期末”复习抓耳挠腮的同学,亦或是工作中需要快速理解模型原理的工程师,最小二乘法都是你必须啃下的第一块硬骨头。它不仅是线性回归的基石,更是理解整个参数化模型拟合思想的钥匙。掌握了它,你再看逻辑回归、支持向量机甚至一些神经网络,都会有“哦,原来是这个思路”的豁然开朗感。它解决的,就是从“transform机器学习 word文档”里那些枯燥理论,到亲手构建一个“机器学习模型”并理解其“应用流程”之间,最关键的一步跨越。

2. 核心思想与数学原理拆解

2.1 问题场景:我们究竟想干什么?

想象一个最经典的场景:你想研究房屋面积(X)和售价(Y)之间的关系。你手头有100套房子的数据,把它们画在坐标系上,得到100个散点。肉眼看去,这些点大致呈一条斜向上的带状分布,但并非严格在一条直线上。现在,你想用一条直线来概括这种关系,以便预测一个新面积的房子大概能卖多少钱。

这条直线方程我们设为Y = wX + b。这里的w(权重)和b(偏置)就是我们需要确定的两个参数。问题来了:有无数条可能的直线,哪一条才是“最好”的?这就需要定义一个“好”的标准。

2.2 损失函数:如何定义“犯错”的成本?

“好”的反面是“犯错”。对于第i个数据点(x_i, y_i),我们用直线预测的值是ŷ_i = w*x_i + b,真实值是y_i。那么预测误差(残差)就是e_i = y_i - ŷ_i

如果简单地把所有误差加起来Σe_i行不行?不行。因为误差有正有负,直接相加会相互抵消,比如一条直线在某个点高估了10万,在另一个点低估了10万,总和为0,但这显然不是一条好直线。

于是,很自然地想到用绝对值来消除正负影响:Σ|e_i|。这叫做最小一乘法。它在数学上没问题,但绝对值函数在零点不可导,后续求解计算比较麻烦。

最小二乘法采用了另一种更“平滑”的思路:对误差取平方e_i²。平方操作同样消除了正负号,并且放大了大误差的影响(因为平方增长更快),同时,平方函数处处可导,性质非常好。我们把所有数据点的误差平方加起来,就得到了损失函数(Loss Function),也称为目标函数

L(w, b) = Σ(y_i - (w*x_i + b))²(求和从 i=1 到 n,n是样本数)

我们的目标,就是找到一对wb,使得这个损失函数L的值达到最小。这就是“最小二乘法”名称的由来:最小化误差的平方和。

注意:为什么是“二乘”?“二乘”就是平方的意思。中国古代称平方为“二乘”,这个叫法在数学中沿用下来。

2.3 求解过程:从几何与微积分视角理解

如何找到使L最小的wb?这是微积分中的经典问题:求多元函数的极值点。

1. 几何视角:损失函数L(w, b)可以看作一个三维空间中的曲面(碗状)。这个曲面必然有一个最低点。最小二乘法的解,就是这个碗的碗底坐标(w*, b*)

2. 微积分视角:在碗底这个最低点,函数L分别对wb的偏导数都应为0(这是极值点的必要条件)。这为我们提供了两个方程:

∂L/∂w = 0:-2 * Σ[x_i * (y_i - w*x_i - b)] = 0∂L/∂b = 0:-2 * Σ(y_i - w*x_i - b) = 0

整理这两个方程,我们得到一个关于wb的二元一次方程组,称为正规方程(Normal Equations)

w * Σx_i² + b * Σx_i = Σ(x_i * y_i) w * Σx_i + b * n = Σy_i

解这个方程组,就能得到wb的解析解(闭式解):

w = (nΣx_i y_i - Σx_i Σy_i) / (nΣx_i² - (Σx_i)²) b = (Σy_i - wΣx_i) / n

这个解是精确的、唯一的(只要分母不为零)。这意味着,对于线性回归问题,我们不需要用复杂的迭代算法,直接通过一套公式计算就能得到全局最优解。这是最小二乘法在线性模型中的一个巨大优势。

2.4 扩展到多元情形

现实中,房价不可能只由面积决定。我们还可能考虑房间数、楼层、房龄等多个特征。此时,自变量X从一个标量变成了一个向量[x1, x2, ..., xp],参数w也变成一个向量[w1, w2, ..., wp]。模型变为:

Y = w1*X1 + w2*X2 + ... + wp*Xp + b

用矩阵表示非常简洁:Y = Xβ(这里X是增加了全为1的列以包含偏置b的矩阵,β是包含所有参数的向量)。

此时的损失函数为:L(β) = (Y - Xβ)^T (Y - Xβ)对其求导并令导数为零,得到正规方程的矩阵形式:X^T X β = X^T Y解为:β = (X^T X)^{-1} X^T Y

这个公式是机器学习中最重要的公式之一,它清晰地展示了数据XY与模型参数β之间的数学关系。

3. 实操实现:从公式到代码

理解了原理,我们动手实现它。这里我会展示纯Python实现和利用NumPy的向量化实现,并比较它们的优劣。

3.1 基础Python实现

我们先从最直观的、基于公式的循环实现开始。这有助于彻底理解计算过程的每一个细节。

def least_squares_naive(x_list, y_list): """ 使用最小二乘法拟合一元线性回归模型 (原始公式版) 参数: x_list: 自变量列表 y_list: 因变量列表 返回: w: 斜率 b: 截距 """ n = len(x_list) # 计算必要的中间量 sum_x = sum(x_list) sum_y = sum(y_list) sum_xy = sum(x * y for x, y in zip(x_list, y_list)) sum_x2 = sum(x * x for x in x_list) # 计算分母,防止除零错误 denominator = n * sum_x2 - sum_x * sum_x if abs(denominator) < 1e-10: # 处理数值问题 raise ValueError("数据可能导致分母为零或过小,无法计算。") # 根据公式计算 w 和 b w = (n * sum_xy - sum_x * sum_y) / denominator b = (sum_y - w * sum_x) / n return w, b # 示例数据:房屋面积(平米)和售价(万元) areas = [50, 60, 70, 80, 90, 100] prices = [150, 180, 210, 240, 265, 290] w, b = least_squares_naive(areas, prices) print(f"拟合直线方程: y = {w:.4f} * x + {b:.4f}") print(f"斜率w(单价): 每平米约{w:.2f}万元") print(f"截距b(基础价): {b:.2f}万元")

输出结果示例:

拟合直线方程: y = 2.8571 * x + 7.1429 斜率w(单价): 每平米约2.86万元 截距b(基础价): 7.14万元

实操心得:在实现基础公式时,一定要处理分母为零或接近零的边界情况。这通常发生在所有x值都相同的时候,意味着数据没有提供任何关于x变化的信息,自然无法拟合出有意义的斜率。添加一个极小的阈值判断是工程上的好习惯。

3.2 NumPy向量化实现

对于多元回归或大数据集,循环效率太低。NumPy的向量化操作能极大提升计算速度,并且代码更简洁。

import numpy as np def least_squares_vectorized(X, y): """ 使用最小二乘法拟合线性回归模型 (NumPy向量化版) 参数: X: 特征矩阵,形状为 (n_samples, n_features)。对于一元回归,需reshape为列向量。 y: 目标值向量,形状为 (n_samples,) 返回: beta: 参数向量,包含偏置项。beta[0]是偏置b,beta[1:]是权重w。 """ # 为X添加一列全1,用于计算偏置项b (即X0=1的系数) X_b = np.c_[np.ones((X.shape[0], 1)), X] # 拼接后形状: (n_samples, n_features+1) # 计算正规方程的解: beta = (X^T X)^{-1} X^T y # 使用np.linalg.pinv求伪逆,比直接求逆更数值稳定,能处理X^T X不满秩的情况 beta = np.linalg.pinv(X_b.T @ X_b) @ X_b.T @ y # 等价于: beta = np.linalg.lstsq(X_b, y, rcond=None)[0] (更推荐,专门求解最小二乘) return beta # 示例:一元回归 areas_np = np.array(areas).reshape(-1, 1) # 转为列向量 (6,1) prices_np = np.array(prices) beta = least_squares_vectorized(areas_np, prices_np) print(f"参数向量beta: {beta}") print(f"偏置b: {beta[0]:.4f}") print(f"权重w: {beta[1]:.4f}") # 示例:多元回归(假设新增一个特征:房间数) rooms = np.array([2, 2, 3, 3, 4, 4]) # 构造特征矩阵X,两列:面积和房间数 X_multi = np.column_stack((areas_np, rooms)) beta_multi = least_squares_vectorized(X_multi, prices_np) print(f"\n多元回归参数: {beta_multi}") print(f"方程: 价格 = {beta_multi[0]:.2f} + {beta_multi[1]:.2f}*面积 + {beta_multi[2]:.2f}*房间数")

输出结果示例:

参数向量beta: [ 7.14285714 2.85714286] 偏置b: 7.1429 权重w: 2.8571 多元回归参数: [ 5.00000000e+00 2.50000000e+00 1.25000000e+01] 方程: 价格 = 5.00 + 2.50*面积 + 12.50*房间数

核心技巧np.linalg.lstsq是NumPy提供的专门用于求解最小二乘问题的函数,它内部使用了更稳定、更高效的数值算法(如SVD分解),能处理秩亏矩阵,比手动计算(X^T X)^{-1}更健壮,是生产环境中的首选。手动求逆仅适用于教学和理解原理。

3.3 使用Scikit-learn进行生产级实现

在实际的机器学习项目中,我们几乎总是使用成熟的库,如Scikit-learn。它高效、稳定且接口统一。

from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score # 准备数据 X = np.array(areas).reshape(-1, 1) y = np.array(prices) # 创建模型实例。注意默认设置:fit_intercept=True(拟合截距),normalize=False。 model = LinearRegression() # 拟合模型(训练) model.fit(X, y) # 获取参数 print(f"Scikit-learn 拟合结果:") print(f"截距 (b): {model.intercept_:.4f}") print(f"系数 (w): {model.coef_}") # 进行预测 areas_new = np.array([[55], [120]]) # 预测55平和120平房子的价格 prices_pred = model.predict(areas_new) print(f"预测房价: 55平 -> {prices_pred[0]:.2f}万, 120平 -> {prices_pred[1]:.2f}万") # 评估模型 y_pred = model.predict(X) mse = mean_squared_error(y, y_pred) r2 = r2_score(y, y_pred) print(f"均方误差 (MSE): {mse:.2f}") print(f"决定系数 (R²): {r2:.4f}")

输出结果示例:

Scikit-learn 拟合结果: 截距 (b): 7.1429 系数 (w): [2.85714286] 预测房价: 55平 -> 164.29万, 120平 -> 350.00万 均方误差 (MSE): 14.88 决定系数 (R²): 0.9949

注意事项LinearRegression默认使用最小二乘法(基于scipy.linalg.lstsq)。值越接近1,说明模型对数据的拟合程度越好。但要注意,高在训练集上很容易获得,不代表模型泛化能力强,需在测试集上进一步验证。

4. 深入理解:假设、局限与陷阱

最小二乘法不是银弹,它的有效性建立在一系列统计假设之上。忽略这些假设盲目使用,很可能得到错误甚至荒谬的结论。

4.1 高斯-马尔可夫定理与经典假设

最小二乘法能得到“最优”估计(BLUE,最佳线性无偏估计),依赖于以下经典假设:

  1. 线性关系:因变量与自变量之间确实存在线性关系。这是模型形式的基本假设。
  2. 随机抽样:样本数据是随机从总体中抽取的。
  3. 无完全共线性:自变量之间不存在严格的线性关系。否则X^T X矩阵不可逆,无法求解。
  4. 条件零均值:误差项ε的期望值为0。这意味着没有系统性偏差,所有影响Y的因素都已包含在模型中。
  5. 同方差性:误差项ε的方差为常数。即数据点的波动幅度大致相同。
  6. 无自相关:不同观测值的误差项之间不相关。
  7. 正态性(可选但常用):误差项ε服从正态分布。这个假设主要用于假设检验和构建置信区间。

4.2 常见问题与诊断

当这些假设被违反时,模型就会出现问题。下面是一个问题诊断表:

问题可能违反的假设诊断方法后果与解决方案
预测不准,模式复杂线性关系绘制YX的散点图;绘制残差与预测值的散点图(若存在曲线模式则违反)。模型无法捕捉真实模式。方案:考虑添加多项式特征(如)、使用样条回归或非线性模型。
异方差同方差性绘制残差与预测值的散点图,观察残差分布是否随预测值增大而扩散(如漏斗形)。参数估计仍无偏,但标准误估计不准,导致假设检验失效。方案:使用加权最小二乘法(WLS)或稳健标准误。
异常值/强影响点所有假设计算库克距离、杠杆值。可视化残差图、杠杆值图。个别点对回归线产生巨大拉扯,扭曲整体关系。方案:检查数据是否正确;使用稳健回归方法(如RANSAC, Huber回归)。
多重共线性无完全共线性计算方差膨胀因子(VIF)。通常VIF > 10表示存在严重共线性。系数估计值方差变大,不稳定,难以解释单个变量的影响。方案:剔除高度相关的变量;使用主成分回归(PCR)或岭回归(L2正则化)。
自相关(时间序列常见)无自相关绘制残差序列图;进行Durbin-Watson检验(DW统计量接近2表示无自相关)。标准误被低估,导致t检验失效。方案:使用时间序列模型(如ARIMA)或包含滞后项。

4.3 一个关键的陷阱:过拟合与正则化

最小二乘法的目标是完美拟合训练数据,使损失函数降到最低。但在特征很多(p很大)甚至接近样本数(n)时,这会导致一个严重问题:过拟合。模型会变得极其复杂,不仅拟合了数据中的普遍规律,也“记住”了训练数据中的随机噪声。其结果是,在训练集上表现完美(很高,MSE很低),但在未见过的测试集上表现糟糕。

解决方案是引入正则化,即在损失函数中加入对模型复杂度的惩罚项。

  • 岭回归(Ridge Regression, L2正则化): 损失函数变为:L(β) = Σ(y_i - ŷ_i)² + α * Σβ_j²j从1到p,通常不惩罚截距项) 它惩罚大的系数,使所有系数向零收缩,能有效处理多重共线性,提高模型稳定性。

  • 套索回归(Lasso Regression, L1正则化): 损失函数变为:L(β) = Σ(y_i - ŷ_i)² + α * Σ|β_j|它不仅能收缩系数,还能将一些不重要的特征的系数压缩至零,从而实现自动特征选择。

from sklearn.linear_model import Ridge, Lasso from sklearn.preprocessing import StandardScaler # 正则化前通常需要标准化 # 假设我们有一个高维数据集 # X_high_dim: (n_samples, n_features), n_features 很大 scaler = StandardScaler() X_scaled = scaler.fit_transform(X_high_dim) # 岭回归 ridge_model = Ridge(alpha=1.0) # alpha是正则化强度 ridge_model.fit(X_scaled, y) print("岭回归系数(部分趋于小值,但很少为0):", ridge_model.coef_[:5]) # 套索回归 lasso_model = Lasso(alpha=0.01, max_iter=10000) lasso_model.fit(X_scaled, y) print("套索回归系数(部分严格为0):", lasso_model.coef_[:5]) print(f"非零系数个数: {np.sum(lasso_model.coef_ != 0)}")

实操心得:使用正则化时,特征标准化(减均值除标准差)是必须的步骤。因为正则化惩罚项对系数大小一视同仁,如果特征量纲不同(如年龄(0-100)和收入(0-1000000)),量纲大的特征会天然承受更大的惩罚,这不公平。StandardScaler能解决这个问题。alpha参数控制正则化强度,需要通过交叉验证来调优。

5. 工程实践与性能考量

在实际的机器学习应用流程中,最小二乘法及其变种的应用远不止于拟合一条直线。我们需要从工程角度考虑其效率、规模和稳定性。

5.1 大规模数据下的求解:迭代方法

当数据量极大(样本数n或特征数p上百万)时,直接求解正规方程β = (X^T X)^{-1} X^T Y会变得不可行。计算X^T X的复杂度是O(p² n),求逆的复杂度是O(p³),内存和计算开销都巨大。

此时,我们需要使用迭代优化算法来近似求解最小二乘问题,最常见的是梯度下降法及其变种(随机梯度下降SGD、小批量梯度下降)。

梯度下降法的核心思想是:损失函数L(β)的梯度方向是其上升最快的方向。那么,沿着梯度的反方向(即下降方向)更新参数β,就能逐步逼近最小值。

对于线性回归的损失函数L(β) = (1/2n) Σ(y_i - ŷ_i)²,其对参数β_j的梯度为:∂L/∂β_j = -(1/n) Σ x_ij (y_i - ŷ_i)

参数更新公式为:β_j := β_j - η * ∂L/∂β_j其中η是学习率,控制每一步更新的幅度。

# 梯度下降法实现线性回归(简易版) def gradient_descent(X, y, learning_rate=0.01, n_iters=1000): n_samples, n_features = X.shape # 初始化参数 beta = np.zeros(n_features) # 为计算方便,添加偏置列 X_b = np.c_[np.ones(n_samples), X] # 梯度下降迭代 for i in range(n_iters): # 计算预测值 y_pred = X_b.dot(beta) # 计算误差 error = y_pred - y # 计算梯度 (向量化形式) gradients = (1/n_samples) * X_b.T.dot(error) # 更新参数 beta -= learning_rate * gradients # 可选:每100次迭代打印损失 if i % 100 == 0: loss = (1/(2*n_samples)) * np.sum(error**2) print(f"Iteration {i}: loss = {loss:.4f}") return beta # 使用标准化后的数据 beta_gd = gradient_descent(X_scaled, y, learning_rate=0.1, n_iters=1000) print("梯度下降求解的参数:", beta_gd)

核心技巧:学习率η的选择至关重要。太大可能导致震荡甚至发散,太小则收敛缓慢。通常需要尝试一系列值(如0.001, 0.01, 0.1),并观察损失函数下降曲线。对于特征尺度差异大的数据,务必先进行标准化,否则梯度下降会很难收敛。

5.2 数值稳定性与病态问题

即使数据规模不大,直接求解正规方程也可能遇到数值计算问题。核心在于矩阵X^T X的条件数。如果特征之间存在高度相关性(多重共线性),或者某些特征的尺度相差巨大,X^T X会接近奇异矩阵(行列式接近0),其条件数很大,求逆运算会变得非常不稳定,微小的数据扰动会导致解的巨大变化。

解决方案:

  1. 特征标准化/归一化:如前所述,这是预处理的基本步骤。
  2. 使用更稳定的求解器:如np.linalg.lstsqscipy.linalg.lstsq,它们内部使用SVD(奇异值分解)或QR分解,比直接求逆更稳定。
  3. 添加正则化(岭回归):在X^T X的对角线上添加一个小的常数α,即求解(X^T X + αI)β = X^T y。这能显著改善矩阵的条件数,使求逆操作稳定。这本质上是为问题引入了一点先验信息(系数不应太大)。

5.3 在线学习与增量更新

在流式数据或实时学习场景下,数据是源源不断到来的。我们不可能每次都保存全部历史数据重新计算(X^T X)^{-1} X^T Y。这时需要递归最小二乘法(RLS)随机梯度下降(SGD)这类在线学习算法。

其核心思想是,当新数据点(x_{new}, y_{new})到来时,利用旧的参数估计和协方差矩阵,通过一个更新公式快速得到新的参数估计,而无需重新处理全部数据。这在“储能EMS系统”的实时负荷预测,或“机器学习检测”系统中的模型快速自适应等场景下非常有用。

虽然RLS的推导稍复杂,但其思想与卡尔曼滤波类似,是工程实践中处理序列数据拟合的强大工具。

6. 从最小二乘看机器学习模型评估

拟合完模型,我们如何知道它好不好?最小二乘法的损失函数本身——均方误差(MSE)——就是一个最直接的评估指标。但仅仅看MSE是不够的。

6.1 误差分解与R²

总平方和(SST)衡量了因变量Y自身的总波动:SST = Σ(y_i - y_mean)²回归平方和(SSR)衡量了模型解释的波动:SSR = Σ(ŷ_i - y_mean)²残差平方和(SSE)衡量了模型未能解释的波动:SSE = Σ(y_i - ŷ_i)²三者关系:SST = SSR + SSE

决定系数 R²定义为:R² = SSR / SST = 1 - SSE / SST它表示模型能够解释的目标变量方差的比例。越接近1,说明模型对数据的拟合程度越好。

但要注意,会随着特征数量的增加而自然增大,即使加入无关特征。因此,对于多元回归,我们更常用调整后R²Adjusted R² = 1 - [(1 - R²)(n - 1) / (n - p - 1)]其中p是特征数。调整后R²会对无关特征进行惩罚。

6.2 交叉验证:防止过拟合的黄金准则

正如前文所述,在训练集上表现好(高R²,低MSE)可能是过拟合的结果。为了可靠地评估模型泛化能力,必须使用交叉验证

最常用的是K折交叉验证:

  1. 将数据集随机分成K个大小相似的子集(折)。
  2. 依次将其中一个子集作为测试集,其余K-1个子集作为训练集。
  3. 在训练集上训练模型,在测试集上计算评估指标(如MSE)。
  4. 重复K次,得到K个测试分数,最后计算其平均值作为模型泛化性能的估计。
from sklearn.model_selection import cross_val_score from sklearn.linear_model import LinearRegression model = LinearRegression() # 执行5折交叉验证,评估指标为负均方误差(scikit-learn约定) scores = cross_val_score(model, X, y, cv=5, scoring='neg_mean_squared_error') # 将负MSE转为正MSE mse_scores = -scores print(f"各折MSE: {mse_scores}") print(f"交叉验证平均MSE: {mse_scores.mean():.2f} (+/- {mse_scores.std() * 2:.2f})")

注意事项:交叉验证的折数K需要选择。K太小(如2)评估方差大;K太大(如等于样本数,即留一法)计算成本高,且各折训练集高度相似。K=5K=10是常见的选择。进行交叉验证前,如果数据有顺序(如时间序列),需要先打乱,或使用专门的时间序列交叉验证方法。

7. 总结与延伸思考

走完这一趟从原理到公式、从代码到实践、从优势到陷阱的旅程,你应该对最小二乘法不再感到陌生和畏惧。它就像一把精准的尺子,为我们从混乱的数据中丈量出那条最“公允”的趋势线。

我个人在多年的实践中,对最小二乘法的体会是:它首先是一种思想,其次才是一个方法。“最小化误差平方和”这个思想,贯穿了整个监督学习。当你学习支持向量机(SVM)时,它的目标是最大化间隔,这可以转化为一个带约束的优化问题;当你学习逻辑回归时,它的目标是最大化似然函数,等价于最小化交叉熵损失。这些不同形式的损失函数,其核心哲学与最小二乘一脉相承——定义一个衡量模型“犯错”程度的函数,然后想办法让这个函数值最小。

最后分享一个在特征工程中的小技巧:当你怀疑特征与目标之间的关系不是简单的直线,而可能是曲线时,不要急于换用复杂的非线性模型。可以先尝试使用最小二乘法拟合一个多项式回归。这非常简单,只需将原始特征X扩展为[X, X², X³, ...],然后扔进线性回归模型。这本质上还是在用最小二乘法,但模型能力却得到了非线性扩展。当然,要小心过拟合,务必使用交叉验证来选择合适的多项式阶数。

机器学习的世界浩瀚如海,但只要你牢牢掌握了最小二乘法这把钥匙,就相当于打通了理解众多模型的第一道关隘。无论是应对期末考试,还是在实际项目中构建预测模型,这份从根基处建立起来的直觉和理解,都会让你走得更稳、更远。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询