1. 最小二乘法的核心痛点:拟合直线到底难在哪
先抛个场景。假设你手里有一组数据,横轴是房子的面积,纵轴是房价,你想找一条直线,用面积去预测房价。数据集大概长这样:
| 面积(m²) | 价格(万元) |
|---|---|
| 50 | 80 |
| 60 | 95 |
| 70 | 110 |
| 80 | 125 |
| 90 | 140 |
画在坐标图上,点并不是一条严格的直线,但明显有向上的趋势。现在问题来了:怎么画出“最合适”的那条直线?怎么定义“最合适”?这就是最小二乘法要解决的事情。
学校里教过你“两点确定一条直线”,但那只是插值,数据一多就完蛋。现实里的数据永远带噪声、有波动,你不可能让一条直线穿过每一个点,只能让这条线整体上“尽量贴近”所有点。最小二乘法做的事情简单说就是:找到一条直线,让所有数据点到这条直线的垂直距离的平方和最小。
注意关键词是“平方”。为什么是平方,不是绝对值,也不是四次方?这个问题我在第三节详细展开。你只需要先记住一个直觉:平方意味着大的误差会被放大惩罚,小误差则相对宽容,这样拟合出来的直线不会为了迁就某一个离群点而扭曲整体趋势。如果你用绝对值距离,异常值的影响会小一些,但数学上很难求导、不好优化;如果用四次方,异常值的权重又被放得太大,直线会被个别坏点带偏。平方是最平衡的选择,兼顾了数学可解性和对噪声的鲁棒性。
这篇文章适合谁看?两类人。第一类是刚接触数据分析和机器学习的小白,被“最小二乘法”“正规方程”“残差平方和”这些名词绕晕了,想搞明白原理到底是什么;第二类是工作中需要做回归分析、传感器校准、实验数据拟合的工程师和研究者,想快速掌握最小二乘法的公式推导和工程实现。我会从最基础的直线拟合讲起,推导一遍一元最小二乘法的完整过程,再讲多元场景下的矩阵解法,最后用Python代码落地,顺带把回归拟合里最常见的坑全部踩一遍。你不需要有深厚的数学底子,只要有初中代数和一点点矩阵概念就能跟上。
2. 最小二乘法的数学建模:从“残差”说起
2.1 残差:你和真理之间的差距
任何一条拟合直线都可以写成:
y = wx + b
其中 w 是斜率,b 是截距。对于每一个真实数据点 (xi, yi),我们代入横坐标 xi 算出一个预测值 yi_hat = wxi + b,那么真实值 yi 和预测值 yi_hat 之间的差距就是:
ei = yi - yi_hat = yi - (wxi + b)
这个 ei 就叫残差(residual),也叫误差项。它度量的是“这个点偏离拟合直线有多远”。
如果用日常语言翻译一下:残差就是你和正确答案之间的偏差。你想减肥,按照某个运动计划预测一周能减1公斤,结果实际只减了0.7公斤,那0.3公斤就是“残差”。好的拟合方法应该让所有点的残差整体上尽可能小。
但是问题来了。残差有正有负,点在线上面是负的,在线下面是正的。如果直接把所有残差加起来,正负会相互抵消,导致一个很糟糕的结果:一条严重偏离数据但恰好让残差总和很小的直线,会被误判成“最优直线”。
所以我们需要一个方法来处理正负抵消的问题。几种常见做法:
- 取绝对值:|ei|,可以避免抵消,但在ei=0处不可导,数学处理麻烦。
- 取平方:ei²,同样避免抵消,而且处处可导,方便用微积分求极值。
- 取四次方:ei⁴,更能惩罚大误差,但对离群点过于敏感。
最小二乘法选择的是平方,也就是把目标函数定义为所有残差的平方和:
S(w, b) = Σ(yi - wxi - b)²
优化目标:找到一组 (w, b),让 S(w, b) 尽可能小。这就是“最小二乘”名字的来源——“二乘”就是平方的意思,“最小二乘”就是让平方和最小。
2.2 为什么平方而不是绝对值:数学背后的工程直觉
把平方和作为损失函数,不只是一个数学选择,背后有统计学的支撑。在高斯-马尔可夫定理的框架下,如果误差项满足零均值、同方差、不相关的条件,那么最小二乘估计在线性无偏估计中拥有最小方差,也就是最有效。换句话说,当噪声是随机的、没有系统偏差时,最小二乘法给出的解就是“最稳”的线性解。
再举个直观的例子。假设有三个点:(0, 0)、(1, 1)、(2, 0)。你拿一条水平线 y = 0.5 去拟合,三个残差分别是 -0.5、0.5、-0.5。绝对值误差之和是1.5,平方误差之和是0.75;换成一条斜率很大的线 y = 2x - 1,在x = 0, 1, 2处的预测值是 -1、1、3,残差绝对值之和是1 + 0 + 3 = 4,平方和是1 + 0 + 9 = 10。你看,平方和会非常严厉地惩罚“预测值3”这种离谱的点,逼着拟合线往“整体均衡”的方向走。
注意:平方惩罚是一把双刃剑。它让解在数学上更优、也更容易求导,但遇到数据集里有一个极端离群点时,平方项会被这个点主导,拟合直线会被强行拉过去。我后面会在第五节专门讲这个问题和处理方法。
2.3 目标函数的极值问题:把“找直线”变成“找最小点”
一旦把拟合问题转化为“最小化 S(w, b)”,接下来的事情就清晰了:这是一个二元函数求极值的问题。S(w, b) 是关于 w 和 b 的二次函数,展开后是一个开口向上的抛物面。抛物面的底部就是我们要找的最优参数组。
用微积分的语言说,要找到这个底部,就对 w 和 b 分别求偏导,令偏导等于零:
∂S/∂w = 0 ∂S/∂b = 0
联立求解这两个方程,就能得到一组闭式解(closed-form solution),不需要迭代。这是最小二乘法与梯度下降法最本质的区别之一:最小二乘法一步到位直接算出最优参数,梯度下降则需要设学习率、迭代很多轮才能逼近最优解。代价是,最小二乘法需要求解一个线性方程组或者矩阵求逆,当特征维度特别高时计算量会暴涨;梯度下降则更适合大数据量、高维特征的场景。我后面会在实战部分详细对比两者的适用边界。
3. 一元最小二乘法完整推导:从公式到代码
3.1 手推公式:斜率w和截距b到底怎么算
现在我来完整走一遍推导,这是很多教程跳过的部分,但恰恰是理解最小二乘法的分水岭。目标函数是:
S(w, b) = Σ(yi - wxi - b)²
先对 b 求偏导:
∂S/∂b = Σ 2(yi - wxi - b)(-1) = -2Σ(yi - wxi - b)
令它等于0:
Σ(yi - wxi - b) = 0
把它展开:
Σyi - wΣxi - nb = 0
于是得到第一个方程:
b = y_mean - w * x_mean
其中 y_mean 是 y 的平均值,x_mean 是 x 的平均值。这个结论值得记住:最优拟合直线一定经过样本均值点 (x_mean, y_mean)。这是一个非常直观的结果,拟合直线的“锚点”就是数据中心。
再对 w 求偏导:
∂S/∂w = Σ 2(yi - wxi - b)(-xi) = -2Σxi(yi - wxi - b)
令它等于0:
Σxi(yi - wxi - b) = 0
把 b = y_mean - w*x_mean 代入:
Σxi[yi - wxi - y_mean + w*x_mean] = 0
整理:
Σxi(yi - y_mean) - wΣxi(xi - x_mean) = 0
于是:
w = Σxi(yi - y_mean) / Σxi(xi - x_mean)
这个形式还可以写成更对称的形式。注意 Σxi(yi - y_mean) 等于 Σ(xi - x_mean)(yi - y_mean)(因为 Σ(yi - y_mean) = 0),分母同理。所以我们得到最常用的公式:
w = Σ(xi - x_mean)(yi - y_mean) / Σ(xi - x_mean)²
b = y_mean - w * x_mean
分母是 x 的离差平方和,衡量 x 的离散程度;分子是 x 和 y 的协方差(未归一化版本),衡量两者共同变化的趋势。斜率 w 的本质就是“x每变化一个单位,y平均变化多少”,这和直觉是一致。
3.2 一步步手算案例:用Excel都能算出来的拟合
拿第一节的房价数据来算一遍。先列一个表:
| 面积xi | 价格yi | xi-mean_x | yi-mean_y | 分子项 | 分母项 |
|---|---|---|---|---|---|
| 50 | 80 | -20 | -30 | 600 | 400 |
| 60 | 95 | -10 | -15 | 150 | 100 |
| 70 | 110 | 0 | 0 | 0 | 0 |
| 80 | 125 | 10 | 15 | 150 | 100 |
| 90 | 140 | 20 | 30 | 600 | 400 |
计算均值:x_mean = 70,y_mean = 110。分子总和 = 600 + 150 + 0 + 150 + 600 = 1500。分母总和 = 400 + 100 + 0 + 100 + 400 = 1000。
于是:
w = 1500 / 1000 = 1.5
b = 110 - 1.5 × 70 = 110 - 105 = 5
拟合直线是:
y = 1.5x + 5
用面积80去预测,得到 y = 1.5×80 + 5 = 125,正好等于真实值,这不是巧合,是因为这个数据集本身就在一条直线上。真实数据不会这么干净,但计算流程是一模一样的。
实操心得:当你手算最小二乘法时,先用均值把数据中心化能极大减少计算量。在实际工程中我们当然不手算,但理解这个中间过程对后面调试模型、排查问题非常有用。比如你算出 w 是负的,但业务上明明应该正相关,那基本可以断定数据预处理出了问题。
3.3 用Python实现一元最小二乘法:三种写法
实际工作中没人手算,但自己实现一遍能加深理解。我给出三种实现方式,从最底层到最封装。
第一种:纯Python手写公式。
import numpy as np def fit_line_by_least_squares(x, y): x_mean = np.mean(x) y_mean = np.mean(y) numerator = np.sum((x - x_mean) * (y - y_mean)) denominator = np.sum((x - x_mean) ** 2) w = numerator / denominator b = y_mean - w * x_mean return w, b x = np.array([50, 60, 70, 80, 90]) y = np.array([80, 95, 110, 125, 140]) w, b = fit_line_by_least_squares(x, y) print(f"斜率: {w}, 截距: {b}") # 输出: 斜率: 1.5, 截距: 5.0第二种:用numpy的polyfit,这是最常用的快捷方式。
import numpy as np x = np.array([50, 60, 70, 80, 90]) y = np.array([80, 95, 110, 125, 140]) w, b = np.polyfit(x, y, 1) # deg=1 表示一次多项式,即直线 print(f"斜率: {w}, 截距: {b}")注意返回顺序是 [斜率, 截距],别记反了。polyfit还支持二次、三次等高阶拟合,本质都是最小二乘法,只是把矩阵X从一列扩展成多列。
第三种:用sklearn的LinearRegression,这是机器学习场景的标准姿势。
import numpy as np from sklearn.linear_model import LinearRegression x = np.array([50, 60, 70, 80, 90]).reshape(-1, 1) # 需要二维列向量 y = np.array([80, 95, 110, 125, 140]) model = LinearRegression() model.fit(x, y) print(f"斜率: {model.coef_[0]}, 截距: {model.intercept_}")sklearn的好处是接口统一,后面换Lasso、Ridge、弹性网这些带正则项的线性模型,只需要把LinearRegression换成对应类,训练和预测代码完全不用改。这在做实验对比时特别方便。
三种方法算出来结果完全一致,因为它们的底层数学是同一个东西。区别只在于代码封装层级。你需要根据场景选择:快速验证用polyfit,进入机器学习pipeline用sklearn,想彻底调试逻辑、自定义损失函数时用手写版本。
4. 多元线性回归与矩阵解法:当参数不再只有一个
4.1 从一元到多元:模型形式与矩阵表达
现实中几乎不会有只拿一个变量去预测的情况。预测房价,除了面积还可能有房间数、楼层、房龄、地理位置等一堆特征。模型变成:
y = w1x1 + w2x2 + ... + wpxp + b
这里 p 是特征数量。为了表示方便,业界习惯把它写成矩阵形式。把截距 b 吸收进权重向量,方法是在特征矩阵 X 左侧加一列全为1的列,对应权重 w0 = b。于是模型简化为:
Y = XW
其中 Y 是 n×1 的列向量(n个样本),X 是 n×(p+1) 的矩阵(每行是一个样本的特征,第一列全为1),W 是 (p+1)×1 的权重向量。
残差平方和写成矩阵形式是:
S(W) = (Y - XW)ᵀ(Y - XW)
展开后是:
S(W) = YᵀY - 2WᵀXᵀY + WᵀXᵀXW
对 W 求导令其等于0,得到所谓的正规方程(Normal Equation):
XᵀXW = XᵀY
若 XᵀX 可逆,则闭式解为:
W = (XᵀX)⁻¹XᵀY
这就是多元最小二乘法的矩阵解。和一元公式对比一下就明白了:一元里分子是协方差项,相当于 XᵀY 的标量版;分母是离差平方和,相当于 XᵀX 的标量版。矩阵形式的本质是一元公式在多维度上的推广。
4.2 正规方程的切线解读:什么时候用矩阵解法,什么时候用梯度下降
矩阵解法看着简单,一行公式就能求解,但在工程里不能无脑用。核心瓶颈有两个:
第一,计算复杂度高。(XᵀX)⁻¹ 的计算量大约在 O(p³) 量级。当特征数 p 只有几十、几百时完全没问题,但一旦特征数到几千、几万,矩阵求逆会非常慢,内存开销也大。比如文本分类里的TF-IDF特征动辄几万维,直接求逆会把内存撑爆。
第二,XᵀX 可能不可逆或奇异。特征之间高度相关(多重共线性)、样本数小于特征数,都会导致这个问题。此时没有逆矩阵,正规方程直接失效。
梯度下降法通过迭代逼近最优解,不要求矩阵可逆,每次只基于一个或一批样本计算梯度更新参数,内存占用小、能处理高维特征。所以业界有个朴素的分线标准:特征维度低(比如几千以下)且样本量适中,优先用正规方程;特征维度高、样本量大,用梯度下降(或者它的大批量变体)。
我在实际工作中还见过第三种情况:XᵀX 接近奇异但又不是完全不可逆,此时直接求逆会得到数值上极不稳定的权重,稍微扰动一点数据权重就大幅震荡。这种情况的正解不是硬用最小二乘,而是考虑岭回归(Ridge Regression),在 XᵀX 上加一个小的单位阵倍数 λI,把问题变成:
W = (XᵀX + λI)⁻¹XᵀY
引入正则项后的代价是权重会有轻微偏差,但换来了数值稳定性和泛化能力。工程上通常默认这种取舍是值得的。
4.3 多元最小二乘实战:从数据准备到结果解读
用sklearn跑一个多元线性回归非常快,但有几个细节比写代码本身更重要。以一个二手房价格预测的实际场景为例:
import numpy as np import pandas as pd from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score from sklearn.preprocessing import StandardScaler # 构造模拟数据: 面积、房龄、楼层、房价 np.random.seed(42) n = 500 area = np.random.normal(90, 25, n).round(1) age = np.random.uniform(0, 20, n).round(1) floor = np.random.randint(1, 30, n) price = 1.8 * area - 1.2 * age + 0.3 * floor + np.random.normal(0, 8, n) X = pd.DataFrame({"area": area, "age": age, "floor": floor}) y = price # 划分数据集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 建模 model = LinearRegression() model.fit(X_train, y_train) y_pred = model.predict(X_test) print("截距:", model.intercept_) print("系数:", dict(zip(X.columns, model.coef_))) print("MSE:", mean_squared_error(y_test, y_pred)) print("R²:", r2_score(y_test, y_pred))输出大致可以看到系数接近真实生成规则(area约1.8、age约-1.2、floor约0.3),MSE在64左右(噪声标准差8的平方),R²接近0.9。这验证了最小二乘法的无偏性:当误差满足条件时,它能把真实参数“还原”出来。
注意:多元回归前建议对特征做标准化。虽然没有正则项的普通最小二乘对量纲不敏感,但如果你后续要对比岭回归、Lasso,那些模型对特征尺度非常敏感。统一做标准化是保持实验公平的好习惯。
5. 拟合效果评估与模型诊断:不只是算出公式就够了
5.1 决定系数R²:你的模型解释了多少信息
算出拟合直线后,第一个要回答的问题是:这条线靠谱吗?最常用的指标是决定系数R²,定义是:
R² = 1 - SS_res / SS_tot
其中 SS_res = Σ(yi - yi_hat)²(残差平方和),SS_tot = Σ(yi - y_mean)²(总平方和)。
如果完全拟合(每个预测值都等于真实值),SS_res = 0,R² = 1。如果拟合效果和直接用均值预测一样,R² = 0。如果比均值预测还差,R² 为负,说明模型惨不忍睹。
但R²有几个陷阱必须知道:
第一,R²会随自变量的增多而单调不降。你加入一堆完全无关的随机噪声特征,R²仍然可能上升,因为模型总能通过这些特征的组合微调来减少一点点残差。这就是为什么不能只盯着R²选模型,要结合调整R²(Adjusted R²)或者直接看测试集表现。
第二,R²衡量的只是“线性关系”,不保证因果关系。冰淇淋销量和溺水人数有很高的R²,但谁都不是谁的因,背后是气温在起作用。业务解释时一定要结合领域常识。
第三,R²对非线性关系不敏感。数据呈明显U型曲线时,线性拟合的R²可能很低,但不代表变量之间没有关系,只是没有线性关系。
5.2 残差分析:看不见的误差也要盯
最小二乘法的统计推断(比如置信区间、显著性检验)依赖一个重要前提:残差应当服从均值为0的正态分布,且方差恒定。如果这个前提被打破,算出的p值和置信区间就不可信了。
实际工程里我们不会做严格的统计检验,但至少要画两张残差图:
- 残差 vs 预测值散点图:如果残差均匀地分布在0线上下,呈随机带状,说明模型没漏掉什么系统性结构。如果残差随预测值增大而发散(漏斗状),说明方差不是恒定的,可能需要对y做log等变换。
- Q-Q图:检验残差是否接近正态分布。如果点大致落在直线上,说明正态性基本满足。
import matplotlib.pyplot as plt import scipy.stats as stats plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) plt.scatter(y_pred, y_pred - y_test, alpha=0.5) plt.axhline(y=0, color="red", linestyle="--") plt.xlabel("预测值") plt.ylabel("残差") plt.title("残差 vs 预测值") plt.subplot(1, 2, 2) stats.probplot(y_pred - y_test, dist="norm", plot=plt) plt.title("残差Q-Q图") plt.tight_layout() plt.show()如果残差图出现明显曲线形状(比如倒U型),说明你用直线拟合了一个本质非线性关系。这时候升级模型不是加更多线性特征,而是考虑多项式回归、样条回归或者干脆上树模型。
我在实际项目里还发现一个容易被忽略的点:很多业务的误差服从正态分布这个假设其实不成立,比如房价、收入这类数据通常右偏。处理办法是先把y取对数再拟合,最后再把预测结果指数还原。这种做法在金融和房地产领域很常见,模型解释也顺手:对数变换后系数直接对应“弹性”,也就是某个特征每变化1%,预测值平均变化百分之几。
5.3 欠拟合与过拟合:两个方向的极端伤害
最小二乘法本身是线性模型,天生有欠拟合的风险。如果真实关系是二次曲线,你用一条直线去拟合,R²不会高,残差图也会露出马脚。解决办法是增加多项式特征:
from sklearn.preprocessing import PolynomialFeatures from sklearn.pipeline import make_pipeline poly_model = make_pipeline( PolynomialFeatures(degree=2, include_bias=False), LinearRegression() ) poly_model.fit(X_train_poly_demo, y_train_poly_demo)但degree设太高又容易过拟合。比如degree=10,模型会把训练数据里的噪声也当成规律学进去,训练集R²可能高达0.999,测试集却一塌糊涂。
想要平衡,业界有几个常规做法:
- 交叉验证选degree:把数据分成几折,轮流做验证,选整体误差最小的degree。
- 加正则项:岭回归或Lasso,正则强度用交叉验证选。
- 不看训练集R²,只看测试集或验证集表现。
特别是在样本量不大时,模型越复杂越危险。机器学习的核心是泛化,不是背诵训练集。这个道理在最小二乘法上一样成立,只不过线性模型的复杂度上限比深度模型低,过拟合的形态比较隐蔽——它不会像深度学习那样train loss持续下降,而是通过系数震荡体现出来:某个系数的符号和常识相反,或者绝对值大得离谱,这些都有可能是过拟合或者多重共线性的信号。
6. 工程实战:温度传感器的校准与多项式拟合
6.1 场景描述:传感器读数和真实温度之间的映射
下面分享一个真实类型的项目参考,这类场景在嵌入式开发和仪器仪表行业里非常普遍。
假设你手里有一个温度传感器,它输出的AD值(模拟数字转换后的原始读数)和真实温度不是线性关系,更接近一个二次曲线。为了校准它,你做了以下实验:用一个高精度的水银温度计作为标准,记录一组标准温度下传感器输出的AD值:
| 标准温度 T(°C) | 传感器AD值 V |
|---|---|
| 0 | 512 |
| 10 | 561 |
| 20 | 618 |
| 30 | 684 |
| 40 | 762 |
| 50 | 855 |
| 60 | 965 |
| 70 | 1092 |
| 80 | 1240 |
| 90 | 1408 |
| 100 | 1600 |
画出散点图能看到,AD值随温度增长的速度在加快,明显不是一条直线。如果用线性拟合,端点误差会特别大。这时候用最小二乘法做多项式拟合,把“如何从AD值反推温度”这个系统的校准公式拟合出来。
这里我想强调一个工程上的细节:校准的输入输出方向。我们要做的是“从AD值反推真实温度”,所以 x 应该是AD值,y 是标准温度。很多初学者会习惯性地用温度做x、AD做y,拟合完再解反函数,那样会引入不必要的误差,而且数学上也不严谨。最小二乘法总是在最小化“你选择的y方向上的误差”,所以y一定要选择最终要预测的量。
6.2 多项式拟合代码与参数选择
用numpy的polyfit做一个二次多项式拟合:
import numpy as np import matplotlib.pyplot as plt adc = np.array([512, 561, 618, 684, 762, 855, 965, 1092, 1240, 1408, 1600]) temp = np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) # 二次多项式拟合: p2*x^2 + p1*x + p0 coeffs = np.polyfit(adc, temp, 2) print("二阶拟合系数:", coeffs) # 生成拟合曲线用于可视化 adc_smooth = np.linspace(500, 1650, 200) temp_fit = np.polyval(coeffs, adc_smooth) plt.scatter(adc, temp, label="观测值") plt.plot(adc_smooth, temp_fit, color="red", label="二次拟合") plt.xlabel("ADC值") plt.ylabel("温度(°C)") plt.legend() plt.show() # 验证:将ADC值代入拟合公式,计算残差 temp_pred = np.polyval(coeffs, adc) residuals = temp - temp_pred print("最大残差(°C):", np.max(np.abs(residuals)))输出结果里,二次项系数通常很小(因为ADC值本身是几千的量级),线性项系数才是主导,常数项是偏移量。拟合完成后,实际使用时就一行代码:temp = np.polyval(coeffs, adc_reading),微控制器里甚至可以手动展开成三次乘法加两次加法,计算廉价到不需要查表。
6.3 如何在二次和三次之间做取舍
拟合阶数不是越高越好。阶数越高,拟合曲线越能弯曲,但也会变得越来越“蛇形”。在小样本、带噪声的数据上,高阶多项式很容易在边缘区域剧烈震荡,这是多项式拟合最经典的毛病。比如用9次多项式去拟合11个数据点,虽然所有点都完美落在曲线上(训练误差几乎为0),但曲线在两个端点之间的波动可能大得离谱,外推一点点就飞到天上去。
我的选择经验是:
- 项数不超过样本数的1/4到1/3,否则过拟合风险急剧上升。
- 优先选择最低阶且残差满足需求的那个模型。如果二阶拟合最大残差0.2°C,而传感器本身的精度只有±0.5°C,那二阶足够用了,三阶提升的那点精度没有实际意义。
- 关注边缘区域的残差分布。多项式拟合常见“两端残差大、中间残差小”的现象,如果边界上的残差已经超过系统要求,加阶数往往治标不治本,不如增加采样密度,或者改用分段样条拟合。
在这个场景里还有个小技巧:如果你手里没有高精度标准温度计,可以拿水浴锅加搅拌器来制造恒温环境,用0°C冰水混合物、100°C沸水做两个端点,中间每隔10°C取一个点。采样时一定要等读数稳定后再记录,否则一个坏点会把整个多项式拉偏。
7. 最小二乘法的常见问题与排查技巧
7.1 数据量很小也能用最小二乘法吗
可以,但结果的可信度要打问号。最小二乘法本质上是用数据估算总体趋势,如果只有3个点,虽然能解出直线,但这个解对噪声极其敏感,第三个点挪动一点点,直线就大幅摆动。
经验法则是:拟合直线至少需要10个样本才比较稳,拟合二次多项式至少需要20个,且样本要覆盖整个关心的范围,不要只在中间密集采样、两端留下巨大的空白。比如你要标定0-100°C,那就应该从0到100均匀布点,不要只采40-60°C。缺失两端的数据会让外推区域的风险完全不可控。
如果数据真的很少又必须用,可以考虑引入物理约束或先验知识来做有偏估计,效果通常会好很多。
7.2 有异常值怎么办:三种稳健处理策略
最小二乘法的平方损失天然放大了异常值的影响。一个飞点可能把整条线上拉的斜率从正的拉成负的。三个处理思路:
第一,先用可视化或3σ规则把异常值筛掉,再拟合。这是最常用也最简单的办法。注意筛选依据应该是x-y联合分布,而不是单看x或y。具体操作是先拟合一次,计算标准化残差,把标准化残差绝对值大于3的点剔除,再拟合一次。对多数场景来说这已经够用。
第二,换用稳健回归方法,比如RANSAC(随机抽样一致)或Huber回归。RANSAC每次随机选择一小部分点拟合,统计哪些点是“内点”,然后只基于内点重新拟合,能非常鲁棒地抵抗大量离群点。我在图像配准、点云处理中经常用它处理数据很脏的场景。
from sklearn.linear_model import RANSACRegressor ransac = RANSACRegressor() ransac.fit(X.reshape(-1, 1), y)RANSAC最大的工程意义在于不需要手动给异常值定阈值,它自己推断。如果你对业务数据的污染比例有把握,设好min_samples和residual_threshold,效果会非常好。
第三,如果异常值是由于数据本身的异方差造成的,考虑用加权最小二乘法(WLS),给方差大的点更低的权重。这在物理实验里特别常见:测量仪器在不同量程下的噪声水平不同,比如低量程精度高、高量程噪声大,不给加权的话,高量程的噪声会主导回归结果。
7.3 多个特征高度相关时的应对方法
多重共线性在多元回归里非常容易出现。比如预测房价时,“房屋总面积”和“客厅面积”高度相关,两个特征同时放进模型,XᵀX 会接近奇异,导致系数估计不稳定、符号对调。你甚至可能看到“客厅面积系数为负”,这完全不等于“客厅越大房价越低”,纯粹是共线性在搅局。
排查方法很简单:计算特征相关矩阵,看哪些特征相关系数超过0.8。处理办法也不是只有删除一个,还有几个更优雅的方向:
- 保留业务上更容易解释的那个变量,删掉另一个。
- 用主成分分析(PCA)先把特征降维再做回归,代价是系数不再对应原始特征,解释性变差。
- 用岭回归或Lasso。Lasso会把冗余特征的系数压缩到0,天然做特征选择,是处理共线性的常用工具。
我在实际项目里遇到过很极端的案例:合作方扔来一个100多个特征的表,很多特征是手工拼接的派生变量,共线性问题极其严重。直接用LinearRegression拟合,训练集R² 0.999,测试集R² 0.3,系数忽大忽小。换成Lasso并做标准化后,测试集R²稳定在0.83,系数也收敛到有业务解释含义的范围内。这个对比是我在线性模型课上一定会讲的案例。
7.4 优化算法选型参考
针对不同数据规模与病态程度,我整理了一张选型表,方便你直接做决策:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 特征少、样本几万以内、无严重共线性 | 正规方程(最小二乘闭式解) | 一步到位、无超参数、计算快 |
| 特征多、样本几十万以上 | 梯度下降/SGD回归 | 内存友好、可增量训练 |
| 特征共线严重 | 岭回归或Lasso | 正则化解稳定系数、兼顾特征选择 |
| 数据中存在大量离群点 | RANSAC或Huber回归 | 对异常点稳健,不怕个别坏数据 |
| 需要在线实时更新模型 | 递归最小二乘(RLS)或SGD | 支持流式增量更新,不保留历史数据 |
| 低阶多项式拟合 | np.polyfit | 接口简单、直接出系数 |
选型思路的核心是认清自己的瓶颈到底在哪里。如果你连数据量都很小,那谈SGD就是个笑话;如果特征几万维连XᵀX都存不下来,正规方程再优雅也没意义。工具只是手段,先诊断,再选型。
8. 回归之后的下一步:从最小二乘到机器学习
8.1 最小二乘法在其他模型中的影子
在机器学习的体系里,最小二乘法的思想被大量模型继承。神经网络做回归任务时,损失函数用的就是均方误差(MSE),也就是最小二乘的“多批量版本”。逻辑回归虽然用的是交叉熵损失,但本质上也是从“极大似然估计”出发,和最小二乘“误差平方和最小”同属统计推断的极大似然框架。
所以,不要觉得最小二乘法只是传统统计学的老古董。它是整个监督学习里最底层的基石。你掌握了它,再去看岭回归、LASSO、弹性网,会发现它们只是“最小二乘 + 不同的惩罚项”;再去看广义线性模型,会发现只是把y做了不同的变换。线性模型的整个家族,几乎都在最小二乘法这个地基上生长。
8.2 非线性关系的扩展思路
如果残差分析发现线性模型不够用,有两条常规进化路径:
一条路径是特征扩展,保留线性模型框架但增加非线性特征。一个搞法是在特征集里加入平方项、交互项、对数项,比如把x扩展成 [x, x², log(x), x1*x2] 等,然后用最小二乘法拟合。这个操作的逻辑是:模型在参数上仍然线性,但在特征上非线性,因此照样可以用正规方程一步求解。
另一条路径是换模型。直接用决策树、随机森林、XGBoost等非线性模型。这类模型的预测能力更强,但解释性变差,而且小样本下容易过拟合。实用性建议是:如果业务需要向非技术背景的人解释回归结果(比如向老板解释“面积每多1平米,房价涨1.5万”),优先用线性模型和最小二乘法;如果目标是纯粹的最高预测精度,并且数据量足够大,直接用XGBoost这类树模型。
8.3 K折交叉验证:选模型和选参数的靠谱方法
无论选什么模型,都不要只在单一的训练/测试集上做决定。数据分割的随机性会造成很大的方差,可能这次测试集表现好只是碰巧。
K折交叉验证的做法是:把数据集切成K份,轮流拿其中1份做验证、其余K-1份做训练,记录K次验证误差,取平均值作为模型真实表现的估计。K常用5或10,数据量小时K可以更大,让训练集更大一些。
交叉验证最低运行成本不高,养成习惯之后,你几乎不会再用单次划分的效果去判断模型好坏。我个人的习惯是:任何小于几万条数据的项目,默认做5折交叉验证,而且会对同一模型跑几次不同的随机种子,观察方差。如果方差太大,说明模型对数据划分太敏感,通常意味着模型过拟合或数据分布有问题。
9. 写在最后的实操心得
这篇文章从最小二乘法的数学原理讲到了工程落地,从一元直线拟合讲到了多元矩阵解法,从残差诊断讲到了异常值和共线性的应对策略。最后分享几条我在实际项目中反复受益的经验。
第一,永远先画图再建模。拿到任何数据,第一步不是写代码,而是散点图看一眼。线性还是非线性、有没有离群点、方差是否恒定,一眼就能看出来。这一步能帮你提前避开无数模型层面的坑。
第二,不要把R²奉为神明。R²高不一定是好模型,R²低也不代表变量无关。结合业务逻辑去解释系数方向和量级,远比盯着单一指标追高更靠谱。我见过的很多刚入门的数据分析师,都在盲目追求测试集R²,结果模型结构越搞越复杂,解释性越来越差,在业务评审会上根本站不住脚。
第三,最小二乘法的适用范围是有限的。当数据存在严重异方差、强非线性、大面积缺失或离群点时,它是第一个要怀疑的对象。解决手段往往不是强行上手艺,而是回到业务本身,搞清楚数据为什么长成这样、噪声从哪里来。有时候把数据质量提上去,比换任何高级模型都管用。
第四,也是我最想强调的一点:最小二乘法最迷人的地方,不是那个公式本身,而是它代表的“量化偏差、最小化误差”这一整套方法论。你把这套思路迁移到任务规划、目标管理、产品优化上,同样成立。知道自己“偏离目标多少”,并且持续朝减少偏差的方向迭代,这本身就是最朴素也最有效的人生算法。