做了这么多年的机器学习项目,面试也面了不少候选人,我发现一个很有意思的现象:一提到线性回归,十个里有八个能写出公式 $y = wx + b$,也能张口说“用最小二乘拟合”。但你要是追问一句——“你的模型用的是正规方程还是梯度下降?为什么选这种优化方法?损失函数为什么要用平方误差?”——不少人就开始含糊了。今天想把这部分内容彻底讲透,从线性回归要解决的问题出发,把优化方法这条路从头到尾走一遍,再配上可直接复现的代码和项目里踩过的坑,希望能帮真正想把基础打扎实的人补上这块短板。
这篇内容适合三类人:刚入门机器学习、还在调包阶段的新手;准备面试、想把算法细节讲清楚的同学;以及工作中经常用回归模型做业务预测、却很少深究底层原理的从业者。文章会覆盖线性回归的建模思路、损失函数设计、优化方法对比、完整实操流程,以及故障排查心得,尽量不堆公式,但关键推导一个不少。
1. 线性回归到底在解决什么问题
1.1 一个业务问题如何变成一条直线
线性回归的核心任务非常朴素:给定一堆样本,每个样本有若干特征,我们要预测一个连续的数值输出。比如根据房屋面积、卧室数量、地理位置来预测房价,根据广告投入预测销售额,根据历史温度预测明天的气温——这些都是典型的回归问题。
建模过程其实就是把业务语言翻译成数学语言。假设有一个样本的特征是 $x_1, x_2, \dots, x_n$,我们要学的模型就是给每个特征配一个权重 $w_1, w_2, \dots, w_n$,再加上一个偏置项 $b$,最终输出:
$$y_{pred} = w_1x_1 + w_2x_2 + \dots + w_nx_n + b$$
这个公式写成向量形式更简洁:$\hat{y} = Xw + b$。如果你只有两个特征,那模型在二维平面上就是一条直线;如果有三个特征,就是一个平面;当特征数量超过三个,我们没法直观看见,于是叫它“超平面”。但本质上,模型做的事情没变:在输入空间里找到一个线性的“切面”,让所有样本点到这个面的垂直距离总体尽量小。
我见过很多新手在这里卡住,因为“超平面”这个词听起来很吓人。其实你把它类比成“高维空间里的一张纸”就行了——线性回归就是找这张纸摆在哪里,能让所有已知的点距离这张纸最近,之后新来的点落在纸上的位置,就是预测值。
1.2 为什么线性回归是机器学习的“第一课”
很多框架、深度学习的教程一上来就讲神经网络,但我始终觉得,如果线性回归没吃透,后面很多东西都是空中楼阁。原因有几个:
第一,逻辑回归只是在线性回归的输出上套了一个 sigmoid 函数,把连续值压缩成概率区间;第二,神经网络中的每一层本质上也是线性变换(矩阵乘法)加激活函数,多层堆叠只是做了多次线性变换的组合;第三,SVM 的线性核、推荐系统的矩阵分解、甚至某些强化学习里的值函数近似,底层都离不开线性模型的思想。
更重要的是,线性回归把“优化”这件事完整地展示了一遍:我们有目标函数,有参数,有梯度,有更新迭代的过程。你搞明白了线性回归的优化,就搞明白了机器学习训练的本质——所有复杂的深度模型,训练过程都是在做同一件事:定义损失,求梯度,更新参数。所以这篇文章花了很大篇幅讲优化方法,不只是为了线性回归本身,更是为后续学习铺路。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 损失函数是怎么回事,为什么非要用平方误差
2.1 残差、平方与“为什么要除以 m”
模型做预测,必然有误差。每个样本的预测值和真实值之间的差,叫残差:$\epsilon_i = y_i - \hat{y}_i$。残差有正有负,如果直接把所有残差加起来,正负会相互抵消,可能得到 0,但我们显然不能说“模型很完美”。所以需要把残差做某种变换,让所有误差都变成正值。
最常见的做法是取平方。为什么是平方而不是绝对值?绝对值虽然也能把负号去掉,但它在原点不可导,优化时梯度不连续,会让推导和计算都很麻烦。平方则有天然的数学优势:处处可导、导数形式漂亮、放大了大误差的惩罚,让模型更不愿意犯大错。当然,平方也意味着对异常值非常敏感,这个后面在排查章节会详细讲。
于是我们定义均方误差(MSE):
$$J(w,b) = \frac{1}{2m} \sum_{i=1}^{m} (\hat{y}_i - y_i)^2$$
这里有两个细节值得解释。为什么要除以样本数 $m$?因为我们要的是一个“平均损失”,如果不除以 m,样本数越多损失自然越大,模型好坏没法在不同数据集规模之间比较。为什么要有个 $\frac{1}{2}$?纯粹是为了求导时抵消平方项带来的系数 2,让梯度表达式更清爽,不引入额外的常数因子。这个技巧在数学推导中非常常见,不影响最优解的位置。
从概率角度还能给出一个更漂亮的理解:如果假设误差服从均值为 0、方差固定的高斯分布,那么用极大似然估计、把似然函数取对数,折腾下来你会发现,最大化似然等价于最小化平方误差。也就是说,MSE 并不是拍脑袋定的,它背后有一个合理的统计假设作为支撑。这也是为什么最小二乘法从勒让德、高斯时代一直活到今天,依然统治着回归问题的原因。
2.2 向量化表达与目标函数的矩阵形式
实际项目中样本是矩阵,特征是多列的,逐条用循环计算损失效率太低了,所以要写成矩阵形式。把偏置项并入权重后,加一列 1 到特征矩阵 $X$,模型输出就是 $\hat{y} = Xw$。损失函数写成:
$$J(w) = \frac{1}{2m}(Xw - y)^T(Xw - y)$$
向量化的好处不只是代码快,更重要的是它让理论推导变得简洁优雅。后面要讲的正规方程推导,就是从这个矩阵形式出发的。我建议初学者自己在纸上把矩阵乘法的过程展开一遍,验证一下展开之后确实等于所有样本残差平方再求和,这一步能帮你跨过“公式恐惧症”。
3. 优化方法全景:从解析解到迭代逼近
3.1 正规方程:一次求解的数学快车
既然目标函数 $J(w)$ 是个关于 $w$ 的二次函数,而且是凸函数,那它一定存在全局最小值。高等数学告诉我们一句话:函数在极值点处的导数为 0。所以我们对 $w$ 求导,令导数等于零,直接解出最优权重即可:
$$\frac{\partial J}{\partial w} = \frac{1}{m}X^T(Xw - y) = 0$$
整理一下:
$$X^TXw = X^Ty$$
从而得到最优解的闭式表达式:
$$w^* = (X^TX)^{-1}X^Ty$$
这就是正规方程。它不需要迭代,只要矩阵 $X^TX$ 可逆,一次性就能算出来。在小规模数据集上,这个方法非常干脆利落。
那什么时候会出问题呢?第一,$X^TX$ 可能是奇异矩阵(不可逆),比如特征之间存在多重共线性(两个特征高度相关),或者样本数比特征数还少,这时求不了逆。第二,即使可逆,求逆的计算复杂度是 $O(n^3)$,特征数量到几万、几十万的时候,矩阵求逆的开销会迅速变得不可接受。第三,$X^TX$ 的条件数很大时,数值稳定性很差,小误差都会被放大,求出来的解可能抖动剧烈。
所以正规方程适合特征维度不高(比如几百到几千以下)、样本量中等、且没有严重共线性的场景。普通教材里的小例子、小型比赛的数据集,用正规方程完全没问题。但真实工业项目动辄上百万特征、上亿样本,矩阵直接存都存不下来,更别提求逆了——这时候就得请出梯度下降。
3.2 梯度下降:最通用的迭代逼近方法
梯度下降的思想可以用一句话概括:如果你站在山坡上想走到山脚,最靠谱的做法是每走一步都朝“最陡的下坡方向”迈一步,反复走就能到达谷底。数学上,“最陡的下坡方向”就是损失函数梯度的反方向。
参数更新公式:
$$w := w - \eta \cdot \frac{1}{m}X^T(Xw - y)$$
其中 $\eta$ 是学习率,控制每一步迈多大。学习率太小,收敛极慢,可能半天走不到谷底;学习率太大,可能一脚迈过谷底,甚至在山谷两侧来回震荡、损失爆炸。这个参数基本是线性回归里第一个要调的参数。
根据每次更新用的样本量,梯度下降分成了几个流派:
| 方法 | 每次更新的样本数 | 优点 | 缺点 |
|---|---|---|---|
| 批梯度下降(BGD) | 全部样本 | 每一步方向准确,损失平滑下降 | 计算量大,更新慢,内存压力大 |
| 随机梯度下降(SGD) | 单个样本 | 更新快,能跳出局部极小点 | 梯度方向抖动剧烈,收敛路径曲折 |
| 小批量梯度下降(Mini-batch GD) | 一小撮样本(如 32/64/128 个) | 兼顾效率和稳定性,现代主流方案 | 需要手动设置批大小和学习率 |
实践里几乎没人用纯 BGD,也没人用纯 SGD 做最终训练。Mini-batch 是行业默认选择,它有天然的噪声扰动,反而能在一定程度上帮助跳出局部极值,而批量的统计平滑又让收敛过程相对稳定。GPU 的并行计算能力也让批处理变成天然高效的形态。
梯度下降里的学习率衰减策略也值得一提。训练初期用大学习率快速下降,后期逐步缩小学习率微调收敛,这就像一个猎人先大步赶路、到目标附近改成小碎步精确定位。常见做法包括按 epoch 线性衰减、指数衰减、或者用 Adam 这类自适应学习率优化器。线性回归的损失函数是凸的,用普通梯度下降已经足够,但理解这些技巧对你迁移到深度学习模型很有帮助。
3.3 为什么项目里几乎都在用迭代优化
有人会问:有正规方程这种一步到位的解法,为什么实战中大家还是默认梯度下降?
核心原因有三个。第一,规模问题。工业级数据的特征维度动辄百万甚至上亿,$X^TX$ 这个矩阵的体积就是特征数的平方,根本没法存在内存里,求逆更是天方夜谭;梯度下降的每一步只需要一个矩阵乘法的复杂度,配合分布式计算可以平滑扩展。
第二,灵活性。正规方程只能解决损失函数闭式可导的情况。一旦你加入 L1 正则化(后面会讲),目标函数在零点不可导,正规方程就失效了;再或者你换一个非凸的损失函数,正规方程则完全不知道该怎么办。梯度下降作为一种通用框架,换目标函数只是换一个梯度表达式,整体逻辑不变。
第三,在线学习需求。很多推荐、广告系统需要模型持续接收新数据、不断更新参数。正规方程每次有新数据都要从头算一遍,显然不现实;梯度下降可以随时用一条新样本做一次更新,实现增量学习。
话说回来,sklearn 的 LinearRegression 类默认其实不是用梯度下降,而是调用 LAPACK 库里的最小二乘求解器,本质上是正规方程或者 QR 分解的思路。很多人以为 fit 就是梯度下降,其实不对。如果你真的想用梯度下降,sklearn 里对应的是 SGDRegressor,这一点混用的人不在少数,面试时也经常露馅。
4. 完整实操:从数据处理到模型评估
4.1 数据处理是建模的一半工作
线性回归虽然名字里带“线性”,但现实中没有任何数据是天生适合直接塞进模型的。数据处理这部分工作,新手往往忽视,但它对模型效果和训练稳定性影响巨大。
首先是特征缩放。梯度下降有个很隐蔽的坑:如果不同特征的数值尺度相差太大——比如一个特征是“房屋面积”,取值几百,另一个特征是“房间数”,取值个位数——损失函数的等高线会长成极扁的椭圆形,梯度下降在里面会走出一条锯齿形的路径,收敛非常慢。解决办法就是标准化:把每个特征减去均值、除以标准差,让数值大致落在 0 附近、单位尺度统一。
其次是缺失值处理。缺失率低的特征可以填充中位数或均值,缺失率高的特征干脆删除。线性回归对缺失值零容忍,模型根本算不了含 NaN 的矩阵。
再次是异常值。由于 MSE 对偏差大的样本惩罚很重,一个极端异常值就能把回归线拽歪。可视化散点图、计算 Z-score 都是排查异常值的重要手段,必要时可以用 IQR 规则截断或做 winsorize 处理。
最后是数据集划分。至少要分训练集和测试集,用训练集拟合参数,用测试集看模型泛化能力。更严谨的做法是再加一个验证集用于调参,或者直接做 K 折交叉验证。
4.2 手写梯度下降代码与收敛观察
下面这段代码用 numpy 手写一个批量梯度下降,目的是让你看清楚每一步发生了什么。数据用 sklearn 内置的糖尿病数据集,里面有 10 个医学特征,目标变量是血糖水平,是个标准的回归任务。
python复制import numpy as np
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
data = load_diabetes()
X, y = data.data, data.target.reshape(-1, 1)
# 标准化特征,这一步对梯度下降至关重要
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(
X_scaled, y, test_size=0.2, random_state=42
)
def linear_regression_gd(X, y, lr=0.5, epochs=500):
m, n = X.shape
X_with_bias = np.hstack([np.ones((m, 1)), X])
w = np.zeros((n + 1, 1))
losses = []
for epoch in range(epochs):
y_pred = X_with_bias @ w
grad = (X_with_bias.T @ (y_pred - y)) / m
w -= lr * grad
loss = np.mean((y_pred - y) ** 2)
losses.append(loss)
if epoch % 100 == 0:
print(f"epoch {epoch}, loss {loss:.4f}")
return w, losses
w, losses = linear_regression_gd(X_train, y_train, lr=0.5, epochs=500)
print("最终权重(含偏置):", w.ravel())
注意上面这个实现把偏置项直接拼成了 X 的第一列,这样梯度更新公式统一,代码也简洁。跑完你可以打印 losses 看变化趋势,正常情况应该是单调下降、最终趋于平稳。如果你把学习率调大到 2 以上,观察 loss 的变化,就能直观感受梯度爆炸长什么样。
训练完成之后用测试集评估:
python复制def predict(X_test, w):
X_with_bias = np.hstack([np.ones((X_test.shape[0], 1)), X_test])
return X_with_bias @ w
y_pred = predict(X_test, w)
mse = np.mean((y_pred - y_test) ** 2)
print("测试集 MSE:", mse)
我实测这个流程,训练集的 loss 能从初始值接近 29000 一路降到约 3200 左右(数值会因随机划分略有不同),说明模型是真正在学习的。初次尝试的人不用纠结具体数字,重点看下降曲线和最终量级。
4.3 sklearn 建模与评估指标解读
手写梯度下降是为了理解原理,日常建模还是用现成工具更高效。下面演示两条路线:一个用解析解 LinearRegression,一个用 SGDRegressor 做梯度下降,同时也把评估指标一并展示。
python复制from sklearn.linear_model import LinearRegression, SGDRegressor
from sklearn.pipeline import make_pipeline
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
# 路线一:正规方程/最小二乘求解
lr = LinearRegression()
lr.fit(X_train, y_train)
y_pred1 = lr.predict(X_test)
# 路线二:SGDRegressor,本质是梯度下降
sgd = make_pipeline(
StandardScaler(),
SGDRegressor(max_iter=1000, learning_rate='adaptive', eta0=0.01, random_state=42)
)
sgd.fit(X_train, y_train.ravel())
y_pred2 = sgd.predict(X_test)
for name, y_pred in [("最小二乘", y_pred1), ("SGD", y_pred2)]:
print(f"--- {name} ---")
print(f"RMSE: {mean_squared_error(y_test, y_pred, squared=False):.4f}")
print(f"MAE : {mean_absolute_error(y_test, y_pred):.4f}")
print(f"R² : {r2_score(y_test, y_pred):.4f}")
这里顺带解释几个指标的含义。RMSE 是均方根误差,和 y 的单位一致,方便直观理解误差平均多大;MAE 是平均绝对误差,抗异常值能力更强,不容易被极端样本带偏;R² 是决定系数,表示模型解释了目标变量多大比例的方差。R² = 0.5 可以粗暴理解为“模型解释了一半的波动”,接近 1 表示拟合效果很好,等于或小于 0 就说明模型连平均值都不如。
初次跑糖尿病数据集时 R² 通常只有 0.5 到 0.6 之间,别嫌弃它低,这说明血糖水平本身受很多因素影响,单靠这 10 个特征本来就不能完全预测。线性回归的价值不在于一定拿高分,而在于给你一个清晰、稳定、可解释的基线模型。
5. 进阶:正则化与非线性扩展
5.1 正则化解决过拟合:L1 和 L2 有什么区别
线性回归如果特征很多、样本很少,很容易出现过拟合——模型在训练集上把噪声都背下来了,测试集成绩一塌糊涂。这时候需要在损失函数后面加一项惩罚项,逼着权重尽量小。这就是正则化。
- L2 正则化(岭回归):在原始损失上加 $\lambda \sum w_i^2$,它的效果是让权重整体压缩,但不会压缩到 0。适合特征之间相关性较高的场景,数值稳定。
- L1 正则化(Lasso):加 $\lambda \sum |w_i|$,它的特点是会把某些权重直接变成 0,实现特征选择,高维稀疏特征的场景非常有用。
- ElasticNet:两者结合,同时具备压缩和稀疏能力,工程里经常作为默认选择。
实际使用时那个 $\lambda$(sklearn 里写作 alpha)决定了正则强度。alpha 太小,正则形同虚设;alpha 太大,模型欠拟合,甚至把所有权重都压成接近 0。调这个参数最容易的办法就是交叉验证网格搜索,sklearn 里 RidgeCV、LassoCV 都能自动在给定候选范围内找最优值。
换一个角度理解:正则化就是给训练过程加约束。就像开车时放宽速度限制反而更安全一样,不让模型权重自由放飞,反而让它在未知数据上更稳健。我见过一个项目,不加正则时线性回归系数巨大、符号混乱,加了 L2 正则之后系数变得规整,业务逻辑一下子也说通了。
5.2 多项式特征:让“直线”学会弯曲
线性回归本身的确只能拟合直线,但现实关系大都不是直线。一个身形猥琐的套路是:把原始特征做多项式扩展,再扔进线性回归里。例如有两个特征 $x$ 和 $y$,扩展后变成 1、$x$、$y$、$x^2$、$y^2$、$xy$,然后在线性回归眼里,这些已经是线性组合了。
这就是 sklearn 中 PolynomialFeatures 加 LinearRegression 的组合拳。它能拟合曲线,并且不需要改模型结构。问题在于:多项式阶数上去之后,特征数量是指数爆炸的,例如 20 个原始特征做 3 阶扩展,特征数可能瞬间变成几千几万,过拟合风险极高。所以这个技术必须配合正则化使用,工程上推荐用 Pipeline 串联 PolynomialFeatures、StandardScaler、Ridge 一条龙处理。
这里的通用经验是:先用低阶多项式(2 阶、3 阶)试水,看验证集误差有没有下降;如果没下降甚至升高,不是多项式不够强,而是过拟合了,赶紧把阶数降下来或者加大正则力度。
6. 常见问题与排查技巧实录
6.1 训练不收敛或损失爆炸
最常见的原因有三个:学习率太大、特征尺度不统一、梯度代码写错。
排查第一步,打印前几轮和中间过程的 loss,看它是涨还是跌。如果 loss 在跳来跳去甚至变成 NaN,先把学习率降到原来的十分之一再试。第二步,确认特征已经标准化或者归一化,这一步无数人栽过跟头,我早期用房价数据训练时不加标准化,迭代几千轮还在原地打转,加上 StandardScaler 之后几十轮就收敛了。第三步,检查梯度公式,尤其是矩阵乘法的维度是否正确,可以用数值梯度法验证:给权重每个分量加一个超小扰动,用 $(J(w+\epsilon) - J(w-\epsilon)) / (2\epsilon)$ 和解析梯度对比,误差在 1e-6 量级基本就说明没问题。
6.2 权重方向与业务逻辑不符
有时候模型精度不错,但某个特征的权重正负号明显不符合常识。比如“卧室数量越多,房价应该越贵”,模型却给出了负权重。
这通常不是模型坏了,而是特征之间存在多重共线性。比如房屋面积和卧室数量高度相关,模型把解释力全部分给了其中一个,另一个权重就变得飘忽不定、甚至出现反直觉的符号。解决办法:计算特征之间的相关系数矩阵,把高度相关的特征删掉一个;或者改用岭回归,通过 L2 正则压低这类不稳定权重;Lasso 则可以更暴力地把冗余特征直接置零。
另外,异常值也会扭曲权重方向。一个被错误录入的超高房价样本,可能会把整条回归线拉向自己,导致某些系数严重失真。遇到反常识结果,先画散点图,看看是不是有离群点在搞事。
6.3 R² 很低甚至为负是怎么回事
R² 为负,意味着模型比“直接预测全部均值”还要差。如果你拿一个没训练好的模型去预测测试集,或者用训练集拟合、在其他分布完全不同的数据集上评估,很容易出现这种情况。
还有一种可能:你选错了任务类型,回归问题拿 R² 看,分类问题费力不讨好地套用这些指标。另外,线性回归在你当前任务里可能真的是不合适的模型——数据本身存在严重非线性、样本量太少、关键特征没采集到,这些都会让线性模型表现得不如一个均值预测。这时你拿到的不是失败,而是信号:该升级模型了,试试决策树、随机森林或者更复杂的非线性模型。
6.4 训练集效果很好,测试集一塌糊涂
这是典型的过拟合。特征是万能的学习器,只要你给的容量足够大,它能把训练集每个样本都背下来。但背下来了不代表它会泛化。
处理手段按优先级排序:一是增加训练样本量,样本多了,模型自然没法“背完”;二是加正则化,从 alpha 小的候选值开始网格搜索;三是减少特征数量,特别是揪出那些方差几乎没有意义的特征;四是交叉验证来检测和验证,不要让测试集在反复调参中被你偷看到太多次,不然测试集本身也变成了变相的训练集。
判断过拟合一个很实用的做法是打印训练集和验证集的指标差:训练 R² 0.98、验证 R² 0.62,差的这 0.36 就是模型在“背”的那部分。如果两者都低,那更多是欠拟合,该加特征、加多项式复杂度,而不是加正则。
6.5 残差图到底怎么看
调试回归模型,我最常看的是残差图:横轴是预测值,纵轴是残差(真实值减预测值)。一个好的回归模型,残差应该均匀地分布在 0 水平线上下,没有明显扇形结构。如果残差随着预测值增大而发散,说明误差方差不稳定,可能存在异方差性,此时考虑对目标变量取对数,或者换一个更合适的模型。如果残差图中呈现出明显的曲线趋势,说明模型漏掉了非线性成分,去加多项式特征比盲目调参更有效。
有一句我一直很认同的顺口溜:先看残差,再看指标。指标只是个数字,残差图能告诉你模型到底错在哪里——是系统性偏差,还是随机噪声,还是存在某些特定区间的结构化错误。这个习惯被很多人忽略,但一旦养成,排查效率能翻一倍。
最后再说点个人体会。早期我做线性回归项目,也疯狂追求 R² 刷到最高,后来发现业务方真正关心的是“误差大概多大、涨跌方向对不对、预测有没有解释力”。线性回归虽然简单,但它是一面最透明的镜子,能照出你数据处理的糙与细,也能照出你对优化方法理解的深浅。
如果你刚学到这里,我的建议是别急着往后赶:先手写一个梯度下降跑通全部流程,再换三种学习率观察收敛曲线变化,然后回去看正规方程推导,最后再用正则化感受模型抗过拟合的过程。这套组合拳打下来,你对机器学习训练这件事的底层直觉会扎实很多。后续哪怕去学再复杂的深度学习,也只是换个损失函数、换个网络结构、换个优化器,核心逻辑依然是你最早在线性回归里学到的那些东西。
