☰
机器学习入门必学:线性回归原理、Python实现与实验避坑指南
2026/10/6 19:22:45 网站建设 项目流程

每次有朋友问我机器学习该从哪里入门,我给的答案几乎都一样:先把线性回归彻底吃透。不是因为它最简单,而是这个模型背后浓缩了回归问题的一整套方法论——损失函数怎么设计、参数怎么求、模型怎么评估、特征怎么处理。把这些串起来,机器学习线性回归实验就算真正做到位了,后面再学逻辑回归、树模型都会轻松很多。

线性回归解决的是“用连续特征预测连续数值”这类问题。拿我做过的一个投放项目来说,手上是过去半年每天在三个渠道的投放金额,要预测第二天的销售额。这是典型的多元线性回归:输入是渠道金额,输出是销售额。再比如大家最常见的房价预测,面积、楼层、房龄做特征,输出是总价。这些场景的共同点是都假设输出和输入之间存在近似线性关系,线性回归算法就是把这条关系拟合成一条直线或者一个超平面。

这篇内容我按自己实际做机器学习线性回归实验的顺序来写:先讲清楚模型在做什么,再推一遍参数求解的数学过程,然后给出可以直接跑的线性回归python代码,最后聊聊模型诊断和常见的坑。适合刚入门想建立完整认知的人,也适合那些已经调过包、但想搞明白背后原理的同学。

1. 先把线性回归这件事讲透:它到底在解决什么问题

1.1 从一个每天都在发生的场景说起

假设你管理一家线下门店,记录了每天进店客流量和当天销售额。数据大概是这样的:客流量50人时销售额1.1万,客流量80人时销售额2.5万,客流量120人时销售额4.2万。把这些点画在坐标轴上,你会发现它们大致沿一条直线分布,但并没有严格落在同一条直线上,因为还有天气、促销、商品结构等乱七八糟的因素在干扰。

线性回归想做的事情,就是从这些嘈杂的数据点里,找到一条最能代表整体趋势的直线。这条直线写作 y = wx + b,其中 x 是客流量,y 是销售额,w 是斜率——客流量每增加1人,销售额平均增加多少,b 是截距——客流量为0时的基础销售额。模型要学的就是 w 和 b 这两个参数。

你可以把它想象成在房间里拉一根挂衣绳:绳子当然不可能穿过每一件衣服,但要让所有衣服到绳子的平均距离尽量小。机器学习里这个“距离”就是损失,让损失最小化就是训练目标。这种思路贯穿所有回归模型,甚至延伸到分类和深度学习中,所以线性回归是理解监督学习最好的入口,没有之一。

1.2 数学语言:从一元线性回归到多元线性回归

一元线性回归只有单个特征,公式是:

y = wx + b

实际项目中一个特征根本不够用。房价预测至少要看面积、房间数、楼层、楼龄;销售预测要看多个渠道的投放下单。这时就升级为多元线性回归:

y = w1x1 + w2x2 + ... + wdxd + b

这里的 x1 到 xd 是不同特征,w1 到 wd 是每个特征对应的权重。更简洁的写法是矩阵形式:

y = Xθ + ε

其中 X 是所有样本的特征矩阵,每行是一个样本,每列是一个特征;θ 是把权重和截距合并在一起的参数向量;ε 是误差项。矩阵形式最大的好处是,不管多少个特征,推导和求解的公式都写成一套,后续的正规方程推导就非常优雅。

但注意,线性回归之所以叫“线性”,指的是模型对参数θ是线性的,并不要求原始特征和y之间一定是直线关系。你完全可以把特征做平方、取对数等变换后再喂给模型,这就是后文要讲的多项式回归。很多新手在这里栽跟头,以为线性回归只能拟合直线,其实是把“模型线性”和“数据线性”搞混了。

线性回归能工作,背后有四个基础假设:一是输入和输出之间存在近似线性关系;二是误差的期望为0,且各样本误差方差相同,叫同方差性;三是样本之间相互独立;四是误差服从正态分布,这主要用于后面的假设检验和置信区间。这些假设不是摆设,第4章做模型诊断时,我们会反复回到这里。

2. 参数怎么求:最小二乘、正规方程与梯度下降

2.1 损失函数为什么偏偏选MSE

模型有了,接下来要定义什么叫“拟合得好”。最自然的想法是让预测值和真实值的差值尽量小。把每个样本的差值加起来,就有两种常见方式:绝对值求和(MAE)和平方求和(MSE)。

实际训练几乎都选均方误差:

MSE = (1/n)Σ(yi - ŷi)²

这里有几个非常现实的原因。第一,平方函数处处可导,梯度好算,而绝对值在0点不可导,优化时还得特殊处理。第二,平方放大误差,预测值偏离真实值越远,惩罚越大,模型会更认真地拟合那些偏差大的点。第三,在误差服从正态分布的前提下,最小化MSE等价于极大似然估计,有扎实的统计学基础。

我自己写代码时经常给MSE加个1/2:

J(θ) = (1/2n)Σ(yi - ŷi)²

为什么要除以2?纯粹是因为对平方项求导时会多出个2,先除2,求导后的系数就干净了。反正乘一个常数不会改变最优解的位置,只是让推导过程少写一个系数。

2.2 正规方程:一步到位的闭式解

把损失函数写成矩阵形式:

J(θ) = (1/2n)(Xθ - y)ᵀ(Xθ - y)

让 J 对 θ 求导并令导数为0:

∇J = (1/n)Xᵀ(Xθ - y) = 0

整理一下:

XᵀXθ = Xᵀy

如果 XᵀX 可逆,就能得到唯一的闭式解:

θ = (XᵀX)⁻¹Xᵀy

这就是正规方程,也叫最小二乘解。它的意思非常直观:误差向量与特征空间正交,因此误差在这个方向上没有任何投影,预测值是在特征张成空间里离真实y最近的点。

正规方程的好处是不用调学习率、不用迭代,一步算出全局最优解。但坏处也明显:计算复杂度大约O(d³),矩阵求逆那一步对特征维度特别敏感。特征数d小于一万、样本量不太离谱时,基本秒出结果;一旦维度涨上去,比如做文本或图像特征,就完全跑不动了。

还有一类更麻烦的情况是XᵀX不可逆,典型场景有两个:特征之间存在完全共线性,或者特征数量大于样本数量。这时候正规方程直接失效,常见解法是加一个小的正则项变成岭回归,或者干脆转用梯度下降和降维方法。我在实际项目里遇到特征多于样本的场景很多,印象最深的是一次用300个营销特征预测转化率,只有120个样本,正规方程算法直接给我抛LinAlgError,后来一查就是矩阵奇异。新手一旦看到这个报错,不用慌,思路就是上面这几条。

2.3 梯度下降:真正的大规模解法

梯度下降是机器学习里出场率最高的优化方法,核心思想一句话:从初始点出发,沿着损失函数下降最陡的方向迈步,重复到收敛。

想象你在山间大雾里要找到谷底,最简单策略就是感受脚下哪个方向下降最快,然后迈一步,再感受、再迈。梯度就是那个“下降最快方向”,学习率α就是步长。步长太大会一步跨到对面山坡,太小则到天黑也走不到终点。

参数更新公式写出来很简洁:

θ ← θ - α·(1/n)Xᵀ(Xθ - y)

按“每轮用多少数据”分类,梯度下降有三种形态:全量样本的批量梯度下降,稳定但慢;单样本的随机梯度下降,快但波动大;每个小批量几十到几百个样本的小批量梯度下降,兼顾两者,实际训练默认选它。甚至到了深度学习里,用的还是这套思路,只是求梯度的方式换成了更复杂的反向传播。

用梯度下降有一个必须注意的问题:特征缩放。如果特征x1取值范围0到1000,x2取值范围0到1,损失函数的等高线会变成极扁的椭圆,梯度方向来回震荡,收敛慢得像蜗牛。把每个特征归一化或标准化后,等高线接近圆形,梯度路径又短又直。所以我做机器学习线性回归实验时,只要涉及梯度下降,第一件事就是看特征的量纲,量纲差异大的先缩放。

2.4 两套方案怎么选:一张表说清楚

这里我把正规方程和梯度下降做一个对比,方便大家选型时直接对照。

维度正规方程梯度下降
求解方式闭式解,一步到位迭代逼近,多轮更新
主要计算量矩阵乘法和求逆O(d³)每轮一次矩阵乘法O(nd)
特征维度低适用,效率高也可用,但没必要
特征维度高内存可能爆掉推荐,逐轮稳定推进
是否需要调学习率不需要需要,且对收敛影响很大
是否需要特征缩放不需要强烈建议
数据量大矩阵求逆太慢适合,可用随机/小批量
在线增量更新不支持天然支持

我的习惯是:特征数几千以内、内存装得下,直接用正规方程或套sklearn的LinearRegression,省心;特征几万以上或数据量很大,才考虑SGDRegressor等迭代方案。很多新手一上来就梯度下降,其实线性回归这种凸优化问题,正规方程往往更稳、更准,没必要给自己找麻烦。

3. 线性回归Python实验:从零手写一遍,再用sklearn验证

3.1 准备一份回归训练数据

为了演示,我用sklearn自带工具生成一份带噪声的单特征回归数据,这样我既知道真实系数,又能看到噪声对拟合的影响。

import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_regression from sklearn.model_selection import train_test_split X, y, true_coef = make_regression( n_samples=200, n_features=1, noise=15, coef=True, random_state=42 ) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) plt.scatter(X, y, alpha=0.6) plt.xlabel("X") plt.ylabel("y") plt.show() print("真实系数:", true_coef)

这里noise=15表示在真实的线性关系上叠加标准差为15的高斯噪声,random_state固定随机种子,保证每次跑出来的实验结果一致。数据切分也固定种子,方便复现对比。我实际工作里所有实验都会固定random_state,不然每次效果都不一样,根本没法判断是模型改进还是随机波动。

3.2 手写梯度下降,看着损失曲线收敛

为了搞懂原理,我先不调库,手写一个最朴素的批次梯度下降。这段代码跑完,你对“训练到底在做什么”会有特别直观的感受。

def mse_loss(X, y, w, b): n = len(y) pred = X @ w + b return (1 / (2 * n)) * np.sum((pred - y) ** 2) def gradient_descent(X, y, w, b, lr=0.01, epochs=300): n = len(y) losses = [] for _ in range(epochs): pred = X @ w + b dw = (1 / n) * X.T @ (pred - y) db = (1 / n) * np.sum(pred - y) w -= lr * dw b -= lr * db losses.append(mse_loss(X, y, w, b)) return w, b, losses w = np.zeros(X_train.shape[1]) b = 0.0 w_final, b_final, losses = gradient_descent(X_train, y_train, w, b, lr=0.01, epochs=300) plt.plot(losses) plt.xlabel("epoch") plt.ylabel("loss") plt.show() print("手写梯度下降学到的w:", w_final, "b:", b_final)

跑完之后,你会看到loss曲线从高处快速下降,后面慢慢变平,说明模型在逐步逼近局部最小值,而这个最小值在凸问题上就是全局最小值。拿学到的w和之前make_regression生成数据时传入的真实系数对比,两个数字很接近,但不是完全相等,因为数据里加了噪声,模型拟合的是带噪样本,不可能恢复出无噪的真值,这个偏差本身就是统计学习里的正常现象。

学习率的选择在这个实验里特别关键。我试过lr=0.1,loss曲线直接飞出天外,数值变成inf;lr=0.0001又慢得让人着急。0.01对这份数据刚好。这也印证了上一章说的,梯度下降调参要看的不是理论,而是损失曲线的实际形态。

3.3 用sklearn验证:三行代码拿到baseline

手写一遍是学习,实战里还是直接调sklearn。线性回归的封装极其简单,三行代码搞定训练和预测:

from sklearn.linear_model import LinearRegression model = LinearRegression() model.fit(X_train, y_train) y_pred = model.predict(X_test) print("coef:", model.coef_) print("intercept:", model.intercept_) print("train R2:", model.score(X_train, y_train)) print("test R2:", model.score(X_test, y_test))

sklearn的LinearRegression默认实现用的就是最小二乘正规方程思路,数据量不大时又稳又准,不需要你去管学习率和迭代次数。coef_对应w,每个特征一个权重;intercept_对应b。以这个演示数据为例,coef大约在70到90之间,含义是x每增加1个单位,y平均增加这么多,这是给业务方讲模型时要重点翻译的一句话。

score返回的是R²,表示模型解释了目标变量多大比例的方差。0.8以上算不错的线性拟合,接近1更好。但记住,R²高不高没有绝对标准,房价预测0.5可能已经很好,而物理实验要求0.99以上才算合理,一定要结合业务场景评判。

3.4 评估指标怎么读:MSE、MAE、R²

模型训练完,评估指标别只用score一个。我常用的三个指标,各有各的使用场景。

指标公式优点注意点
MSE(1/n)Σ(y - ŷ)²数学性质好,梯度友好量纲是平方,异常值影响大
MAE(1/n)Σ|y - ŷ|直观,对异常值稳健0点不可导,优化不便
R²1 - SS_res/SS_tot无量纲,容易解释加特征只增不减,需用调整R²

实际给业务方汇报时,我一般报MAE,因为它说法是“平均预测误差大概3.2万”,任何人都能理解。内部做模型对比和调参时用R²和MSE更多。R²还有一个容易误读的地方:它在测试集上是模型解释力的直接体现,但在训练集上非常容易刷到接近1,过拟合越严重,训练R²和测试R²的差距越大。所以看R²一定要把训练和测试的成绩放一起看。

小技巧是评估时写一个统一函数,一下输出MSE、MAE、R²三个值,整个实验过程中反复调用。我在项目工程里就是这么干的,既省代码又统一口径。

4. 模型诊断:别只盯着R²

4.1 残差图比指标更能反映问题

指标只看结果好不好,但为什么不好,要靠残差分析。残差定义很简单:

e_i = y_i - ŷ_i

也就是每个样本的真实值减预测值。理想情况下,残差应该在0附近随机波动,没有任何结构。如果残差图出现规律,说明模型有系统性缺陷。

最常见的三种坏情况:一是残差随预测值增大呈喇叭状扩散,说明存在异方差,方差不恒定,模型的置信区间和假设检验都会失效;二是残差整体呈弯曲曲线,说明真实关系不是线性的,模型欠拟合;三是残差在某个方向上长期正或长期负,说明漏掉了某个重要特征。

画残差图的代码也很简单:

train_pred = model.predict(X_train) residual = y_train - train_pred plt.scatter(train_pred, residual, alpha=0.6) plt.axhline(y=0, color='red', linestyle='--') plt.xlabel("predicted") plt.ylabel("residual") plt.show()

我在项目里几乎每版模型都会先看这张图,它会告诉你下一步该加特征、做变换还是换模型,比单纯盯着R²纠结半天有效得多。

4.2 多重共线性:让系数变得不可信的元凶

多元线性回归还有一个隐藏雷区:多重共线性。当两个特征高度相关,比如房价预测里的“面积”和“房间数”其实高度正相关,XᵀX接近奇异,正规方程解会非常敏感,数据的微小波动都会让系数大幅跳动,甚至出现明显不合理的正负号。

怎么检测?最简单的是方差膨胀因子:

VIF_j = 1 / (1 - R²_j)

其中R²_j是第j个特征对剩余特征做回归得到的决定系数。VIF大于10通常认为共线性严重。sklearn本身没直接给VIF,但statsmodels有现成函数,或者按公式自己算也很快。

处理共线性,我常用的顺序是:先看相关矩阵,把相关系数超过0.8的特征删掉一个;如果需要保留信息,用PCA做降维;如果目标是保留原始特征做解释,就给损失函数加L2正则化,也就是岭回归。这三种方案我都用过,最省事、最容易解释的还是先删冗余特征。

4.3 R²的陷阱与调整R²

前面提到加特征R²只会上升,这里展开说一下。每加入一个新特征,最小二乘模型至少能保持原来的误差不变,如果一个特征完全没用,它的权重会被学成0,SS_res不变,R²也不会下降。所以在多元场景下,光比“谁的R²高”毫无意义,维度更高的模型天然占便宜。

正确做法是看调整R²:

AdjR² = 1 - (1 - R²)·(n-1)/(n-d-1)

它引入了一个关于特征数量d的惩罚项,特征越多,惩罚越大,只有确实提升了拟合能力的特征才会让调整R²上升。选变量集合时,比原始R²合理得多。

如果你用训练R²和测试R²对比模型过拟合,那R²本身的单调性反而成了优势:训练R²虚高,测试R²暴跌,差距一拉就看出问题。所以我的习惯是三个数字一起报:训练R²、测试R²、调整R²,任何一个单独拿出来都可能误导你。

5. 实操中的避坑指南:我踩过的那些坑

5.1 数据泄漏:先标准化还是先切分

很多新手做机器学习线性回归实验时,会把整个数据集一股脑标准化,然后再做train_test_split,这个顺序是错的。标准化要计算训练集的均值和方差,如果先在全量数据上算,测试集的信息已经偷偷流进训练过程了,这叫数据泄漏。

正确顺序永远是先切分,再标准化,而且测试集要用训练集的均值和方差做变换,绝不能用测试集自己的统计量重新fit。代码上最容易出错的地方是忘记对测试集也用scaler.transform而不是scaler.fit_transform。

from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)

为了彻底杜绝这类低级错误,我现在偏好用Pipeline把预处理和模型打包成一个整体,fit和predict都走同一个流程,漏写transform的情况基本不会再发生。

from sklearn.pipeline import Pipeline pipe = Pipeline([ ('scaler', StandardScaler()), ('lr', LinearRegression()) ]) pipe.fit(X_train, y_train) y_pred = pipe.predict(X_test)

这个坑非常隐蔽,我在真实项目里见过同事反复踩,所以在这里单独标记出来。

5.2 异常值会把回归线拉偏

MSE对异常值极敏感,因为误差平方放大之后,一个极端点的影响力可能超过几十个正常点。典型表现是散点图里回归线被某个离群点“勾”过去,整体拟合被带偏。

识别异常值,我习惯先画箱线图和散点图,然后再看Cook距离这种统计量。处理手段有两个方向:一是清洗数据,把明显异常点剔除;二是换用对异常值稳健的模型,比如Huber回归,它在大误差区使用线性惩罚,而不是平方惩罚,天然不会被离群点牵着走。

from sklearn.linear_model import HuberRegressor huber = HuberRegressor(epsilon=1.35) huber.fit(X_train, y_train)

一个简单的经验是:如果异常值背后有真实业务含义,比如某个促销日的销售额暴涨,那就不该删,保留并且单独建模更好;如果是录入错误、传感器故障,直接清理。判断标准靠业务常识,不是靠统计公式。

5.3 线性回归不能直接杀非线性问题

线性回归名字里带着“线性”,但它的能力不只是直线。当数据里的真实关系是曲线,比如广告投放带来的销售额边际递减,先做一个多项式特征扩展,再跑线性回归,就能拟合曲线了。

from sklearn.preprocessing import PolynomialFeatures poly = PolynomialFeatures(degree=2, include_bias=False) X_poly = poly.fit_transform(X)

加多项式特征后要留意两点:一是特征的量级会变得很大,x和x²相差几个数量级,必须先做特征缩放;二是多项式特征之间高度相关,会加重共线性,一般建议配合岭回归或套索回归使用。我个人经验是,degree=2往往就够,degree=3以上绝大多数场景只会带来过拟合,不要为了追求测试集上那一点点提升把模型搞复杂。

5.4 别忘了线性回归最大的优势:可解释性

深度学习模型很像一个黑盒子,你很难说清楚某个特征对结果有多大影响。但线性回归的参数有明确的业务含义:系数就是“在其他条件不变时,该特征每增加一个单位,目标值平均变化多少”。这是我做交易预测、营销分析时必须依赖的能力。

我踩过的坑是只顾着在测试集上刷R²,用了一堆复杂特征变换后,模型效果确实好看了,但业务方一问“为什么”,我根本解释不过来。后来我学乖了:先跑一个基线线性回归,把系数稳定性和显著性摆出来,再决定要不要去追求非线性提升。如果可解释性是最重要的交付物,宁可损失一点精度也不要牺牲系数含义。这也是为什么我强烈建议大家做机器学习线性回归实验时,始终把“系数讲得通”当成第一验收标准。

最后的一点实际体会

我在实际项目里几乎每个回归任务都会先跑一个普通线性回归当baseline。它像一把尺子,后面就算用了梯度提升或深度模型,也要拿线性结果对比一下,才知道非线性结构到底带来了多少真正提升。如果提升只有0.01,却让整个系统变得无法解释、且上线成本翻倍,那这笔投入就得慎重。

最后再分享一个小建议:学线性回归,别急着调包。手动推导一次正规方程,亲手写一遍梯度下降,把loss下降曲线亲眼看到,再把残差图画出来。这些动作看似重复造轮子,但它能把你脑子里的概念一根根串起来。很多知识你以为是懂了,真正动手之后才发现只是眼熟。这个过程我走了不少弯路,现在回头看,是最值得花的那段时间。

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

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

立即咨询