线性回归从原理到实战:最小二乘法、梯度下降与sklearn调参指南

发布时间:2026/9/15 16:46:22
线性回归从原理到实战:最小二乘法、梯度下降与sklearn调参指南 线性回归这四个字在机器学习里几乎算是Hello World级别的存在。很多人第一次接触算法就是从LinearRegression开始的给一堆数据画一条直线完事。但真到了面试、比赛、实际业务里才发现自己只是会调个model.fit()对背后这条线为什么这么画系数是什么意思R²到底能不能信这些事一问三不知。这篇文章我不堆公式也不复制文档就用人话把线性回归从原理讲到实战从手写实现讲到sklearn调包再把我自己在实际项目里踩过的坑一并交代清楚。不管你是刚入门的初学者还是想系统补一遍基础的转行人都应该能从中捞到点东西。1. 线性回归是干什么的从一个猜房租的场景说起1.1 你其实早就在用线性回归先说一个很生活化的例子。假设你准备去杭州工作想在某个小区租房子中介告诉你一套两居室月租5800。你第一反应是什么大概率是心里默默盘算这小区的一居室大概4500两居室比一居室多了个房间贵1000出头好像合理再一看楼层高、带电梯加200也能接受。于是你得出一个判断5800差不多。注意就在这几秒钟里你其实已经完成了一次线性回归推理。你的大脑在无意识中把房间数量楼层是否带电梯这些因素分别乘上了一个权重加起来得到一个心理价位再去和实际价格对比。线性回归干的事和你这个大脑活动完全一样它假设目标值房租等于每个特征值房间数、楼层、面积乘以对应的权重再加一个基础项截距最后累加成一个预测值。用数学语言写出来就是租金 w1 * 面积 w2 * 房间数 w3 * 楼层 b这里的w1、w2、w3叫权重或者系数b叫截距也叫偏置。整个训练过程其实就是不断调整这些w和b让预测结果尽量贴近真实成交价。你脑子里的多一个房间加1000高楼层加200训练完之后模型学到的就是类似的一组数字。1.2 从一元到多元直线是怎么变成超平面的很多教程会从一元线性回归讲起也就是只有一个特征的情况公式是y kx b画出来就是初中数学里那条直线。这个阶段大家都能理解一堆散点找一条直线让它尽量穿过这些点。但实际业务里几乎没有只靠一个特征就能预测的场景。房价不止看面积还要看地段、房龄、朝向销售额不止看广告投放还要看季节、竞品活动、渠道。所以真正用的基本都是多元线性回归公式变成y b w1*x1 w2*x2 ... wn*xn当特征只有两个的时候画出来是一个平面想象一张倾斜的纸悬浮在三维空间里超过三个特征就没法直观画图了但本质还是一样在n维空间里找一个超平面让所有样本点到这个平面的距离误差总体最小。这一点理解透了后面看代码会轻松很多。因为LinearRegression这个模型不管你的数据是1列还是100列它的核心逻辑完全一致只是求解时矩阵运算的规模变大了而已。2. 最小二乘法那条最佳直线是怎么被定义出来的2.1 残差与损失函数什么叫拟合得好既然要找一条最好的直线首先得定义什么叫好。你画出一条线数据点没落在这条线上它们之间的垂直距离叫残差residual也就是真实值和预测值的差。残差越小说明这条线越贴近数据。那是不是把所有残差加起来就行不行。因为有些点在线上面残差为正有些点在线下面残差为负直接相加会正负抵消哪怕拟合得一塌糊涂加起来也可能是个很小的数。解决办法就是给残差加个平方变成正数再相加这就是均方误差MSEMSE (1/m) * Σ(yi - ŷi)²其中m是样本数量yi是真实值ŷi是预测值。MSE越小说明整体预测误差越小。所谓拟合本质就是找到一组w和b让这个MSE取到最小值。这也是最小二乘法这个名字的来历——二乘就是平方最小就是让平方误差之和最小。2.2 怎么求出最优的w和b两条路正规方程和梯度下降找到让MSE最小的那组参数数学上有两条典型路径。第一条路正规方程Normal Equation。思路很简单MSE是一个关于w和b的二次函数二次函数怎么找最小值对w求导令导数等于0解方程就行。把整个过程写成矩阵形式就是w (X^T X)^(-1) X^T y一行代码直接算出最优解。这个方法的优点是快不用调超参数缺点是必须计算矩阵的逆当特征数量很大比如超过几万时矩阵求逆会非常慢甚至内存爆掉。另外如果特征之间存在完全的多重共线性两个特征一模一样X^T X会不可逆直接报错。第二条路梯度下降Gradient Descent。思路更像是下山你站在山坡上当前参数对应一个损失值不知道最低点在哪但你能感觉到哪个方向是下坡梯度方向就朝那个方向迈一小步反复迭代最终逼近最低点。更新公式长这样w : w - α * ∂L/∂w b : b - α * ∂L/∂b其中α是学习率决定每次都多大步。学习率太大可能直接跨过最低点来回震荡甚至发散学习率太小收敛慢得让人崩溃。正规方程适合小规模数据、特征不过万的情况下用梯度下降则在大规模数据、特征非常多的情况下更现实也是深度学习时代一切优化的基础。2.3 一个必懂的直觉为什么是平方而不是绝对值有些读者会问那为什么不用绝对值把残差的绝对值加起来MAE同样不会正负抵消而且对异常值没那么敏感。这个问题问得很好答案有三个层次。第一数学上方便。绝对值函数在零点不可导做梯度下降的时候很尴尬你得到处讨论分段情况。平方函数处处光滑可导求导简单优化起来顺畅。第二平方会放大较大的误差。一个误差为10的样本平方后贡献是100两个误差为5的样本平方后贡献是50。这意味着MSE天然会把个别差得离谱的预测当成更严重的问题来惩罚。很多场景下这确实是好事因为大误差往往不可接受。但反过来如果你的数据里有很多异常值MSE会被这些点严重带偏这是线性回归的一个经典缺陷后面我还会细讲。第三从统计学的视角看当误差项服从独立同分布的正态分布时最小二乘估计是最佳线性无偏估计这是高斯-马尔可夫定理说的。换句话说在理想条件下没有其他线性方法能比它更好。所以平方不是随便选的背后是有统计学理论支撑的。3. 用Python从零手写线性回归不调库3.1 手工实现梯度下降说再多概念不如亲手写一遍代码。我来用NumPy手写一个最简单的多元线性回归核心就三个函数预测、算损失、梯度下降更新。import numpy as np def predict(X, w, b): # X形状为(m, n)w形状为(n,)b是标量 return X.dot(w) b def compute_mse(y_true, y_pred): m len(y_true) return np.mean((y_true - y_pred) ** 2) def gradient_descent(X, y, w, b, lr): m len(y) y_pred predict(X, w, b) # 残差 residual y - y_pred # 计算梯度 dw -(2 / m) * X.T.dot(residual) db -(2 / m) * np.sum(residual) # 更新参数 w - lr * dw b - lr * db return w, b def train(X, y, lr0.01, epochs1000): n_features X.shape[1] w np.zeros(n_features) b 0.0 for epoch in range(epochs): w, b gradient_descent(X, y, w, b, lr) if epoch % 100 0: loss compute_mse(y, predict(X, w, b)) print(fEpoch {epoch}, MSE: {loss:.4f}) return w, b这个代码里最核心的是gradient_descent函数里的三步算残差、算梯度、更新参数。我给个简单的二维数据试一下比如y 3x 5加一点噪声rng np.random.default_rng(42) X rng.uniform(0, 10, (200, 1)) y 3 * X[:, 0] 5 rng.normal(0, 1, 200) w, b train(X, y, lr0.01, epochs2000) print(f学到的w: {w[0]:.4f}, b: {b:.4f})正常跑下来w会接近3b会接近5。看到自己手写的代码学到了正确参数那种感觉和调包完全不同。3.2 学习率怎么选、迭代多少次手写梯度下降最常遇到两个问题不收敛或者收敛太慢。学习率太大的典型症状是损失函数一会上天一会入地打印出来的MSE来回震荡甚至变成inf。我最初试过把lr设为1.0结果前三步损失就到了几千万。学习率太小则是另一个极端跑了5000轮损失还挂在半山腰。实践里一个比较稳妥的做法是从0.01或0.001起步观察损失曲线。如果震荡就除以10如果收敛平稳但速度太慢就试着乘以3。另一个问题是特征量纲。如果x1的取值范围是0到1x2的取值范围是10000到100000那么梯度下降在x2这个方向上的更新会非常不稳定。这个问题的标准解法是特征缩放最简单的是标准化减去均值除以标准差把每个特征拉到均值为0、方差为1的区间。后面调包的时候你可能感觉不到它的存在那是因为sklearn帮你处理了一些情况但手写的时候不做标准化基本寸步难行。3.3 手写版和sklearn结果对比我把手写版和sklearn.linear_model.LinearRegression放在同一份数据上跑参数基本一致这个验证过程很有必要。核心代码如下from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X) # 手写版 w, b train(X_scaled, y, lr0.1, epochs1000) # sklearn版 sk_model LinearRegression().fit(X_scaled, y) print(f手写版 w{w[0]:.6f}, b{b:.6f}) print(fsklearn版 w{sk_model.coef_[0]:.6f}, b{sk_model.intercept_:.6f})两者结果会非常接近细微差别主要来自收敛精度。sklearn的LinearRegression默认用的不是梯度下降而是最小二乘的另一种数值解法基于LAPACK的SVD分解能直接算出最小范数解而且对不太满秩的情况也能处理。这也是为什么它不需要设学习率、不需要迭代——它走的是正规方程那条路只是在数值稳定性上做了很多优化。4. 上手scikit-learn的LinearRegression波士顿房价实战4.1 数据集准备与训练流程说到线性回归的经典实战绕不开波士顿房价数据集。不过这里先提一个很多教程没更新的坑从scikit-learn 1.0之后load_boston()被移除了因为这个数据集存在数据来源和变量定义不透明等问题。现在想用要么找历史版本的sklearn要么去网上下载csv要么直接用替代数据集。我建议直接用fetch_california_housing()它也是回归任务的经典数据集包含了加州的房价中位数和一系列特征。完整的训练流程分四步加载数据、划分训练集和测试集、训练模型、评估。from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score housing fetch_california_housing() X, y housing.data, housing.target X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model LinearRegression() model.fit(X_train, y_train) y_pred model.predict(X_test) print(f训练集R²: {r2_score(y_train, model.predict(X_train)):.4f}) print(f测试集R²: {r2_score(y_test, y_pred):.4f}) print(f测试集RMSE: {mean_squared_error(y_test, y_pred, squaredFalse):.4f})跑出来的R²通常在0.6左右对应的RMSE大约是0.7到0.8单位是十万美元。也就是说模型平均预测误差在7000到8000美元左右。对于这种跟地理位置强相关的数据线性回归的拟合上限大概就到这里想再往上提就得考虑非线性模型或者加交互特征了。4.2 解读模型的系数和截距训练完之后model.coef_里存着每个特征的系数model.intercept_是截距。很多人止步于跑出数字但真正有价值的是解读这些数字。以加州房价为例如果某个特征叫MedInc该街区收入中位数它的系数大概是0.4左右。意思是在其他特征不变的情况下收入中位数增加1个单位预测房价中位数增加0.4个单位。如果你发现某个特征系数是负数比如AveOccup平均房屋入住人数系数为负说明该特征和目标值呈负相关——入住人数越多预测房价越低。这在直觉上说得通同一栋楼住的人过多往往是拥挤、租金偏低的区域。但这里有个极其重要的坑系数的绝对值大小不能直接用来衡量特征重要性因为不同特征的量纲完全不同。收入的单位是万美元入住率的分布则是另一个量级。想比较特征重要性要么先把所有特征标准化再训练要么用标准化的系数。我一般习惯在建模前先做标准化然后从系数绝对值排序来粗看特征贡献。4.3 训练集/测试集划分为什么关键这是新手最容易忽略、却影响全局的一步。如果你的核心诉求是模型在没见过的数据上表现如何那千万不能用全部数据训练再去评判模型好坏否则会造成严重的过拟合误判。举个例子你用100%的数据训练R²能到0.85看起来很漂亮。但当你拿着这个模型去预测下个月的销量时发现误差大得离谱。原因就是模型把训练数据里的噪声和特殊规律都背下来了而不是学到了通用的规律。正确的做法是像上面代码那样用train_test_split切出20%作为测试集训练时完全见不到这部分数据最后拿它模拟未来数据的效果。这才能比较真实地反映模型的泛化能力。划分比例也不是固定死的。数据量大的时候比如超过几十万条可以只留10%甚至更少做测试数据量小只有几百条时就得考虑留出更多或者使用交叉验证来获得更稳定的评估。5. 模型效果好不好评估指标和常见误区5.1 从MSE到RMSE到R²说完训练评估环节同样重要。线性回归的评估指标里最常见的是这三个指标全称公式人话版特点MSE均方误差残差平方的平均值对大误差敏感单位是原单位平方RMSE均方根误差MSE开根号单位回归到原目标单位最直观MAE平均绝对误差残差绝对值的平均对异常值不敏感但数学性质较差R²决定系数1 - 残差平方和/总平方和无量纲越接近1说明模型整体解释力越强其中RMSE是我最常用的指标因为它的单位和目标变量一致。比如预测房价RMSE是0.75十万美元我立刻能知道平均误差大约7500美元。R²则是用来回答模型比瞎猜好多少的R²0.6意味着和直接用平均值当预测值相比模型的误差减少了60%。5.2 一个容易翻车的评估误区只看R²R²高真的代表模型好吗不一定。有几个场景会让R²虚高场景一在训练集上报告R²。模型本来就是在拟合训练集R²高是应该的。如果训练集R²远远高于测试集R²说明过拟合了。这个我在前面已经强调过。场景二数据有强时间趋势。如果你的数据是某个随年份持续上涨的指标比如房价、销量、股票哪怕模型没学到任何因果规律它只要学到越来越贵这个趋势R²就能非常高。这时候评估要格外小心最好用时间序列的切分方式而不是随机切分。场景三样本量太少。100个样本里拟合5个特征R²很容易高得吓人但这只是模型把每个点都记住了。一个粗略的参考标准是样本量至少应该是特征数的10到20倍太少的话任何回归评估都不太可信。5.3 可视化诊断残差图怎么看除了数字指标做回归分析一定要养成看残差图的习惯。残差图就是横轴是预测值、纵轴是残差的散点图。理想情况下这张图应该像一片均匀分布的云没有明显模式围绕y0这条线上下对称。但如果看到以下两种形态就要警觉了漏斗形预测值越大残差的波动范围越大。这说明模型的误差不是恒定不变的违反了线性回归关于误差等方差的基本假设。应对思路是尝试对目标变量取对数把压缩后的大值区间扩回来。U形或弯月形残差呈现出规律性的弯曲。这通常说明数据存在非线性关系线性模型拟合不出来。比如房价先随面积快速上涨、后面增速放缓用直线去拟合中间段的残差就是正的两端是负的画出来就是一条弯曲的带子。这时候就要考虑加多项式特征或改用非线性模型。6. 实战中容易踩的坑与调优思路6.1 特征的多重共线性系数可能在精分多重共线性指两个或多个特征高度相关比如特征A是房子面积特征B是房间数量两者本来就强相关。表面上模型还能跑但代价是系数不稳定。举个极端的例子你有一条真实规律y 3x1 5x2如果x1和x2几乎一样那么模型可能学成y 5x1 3x2也能把训练数据拟合得很好损失几乎相同。问题在于模型的系数失去了可解释性——你没法说x1每增加1y增加5还是增加3因为x1和x2能相互替代。检测方法很简单计算特征之间的相关系数矩阵或者看VIF方差膨胀因子。处理方式包括删除其中一个相关特征、做主成分分析降维、或者改用带正则化的回归如岭回归。如果你做线性回归只是为预测不是为解释系数那么多重共线性对预测精度影响通常不大但如果你要向业务解释哪个因素影响最大这个问题就必须处理。6.2 特征的度量衡一致性问题这个问题虽然基础但在合作项目里反复出现。一个特征的量纲是万元另一个是元还有一个是百分比小数如果不做任何处理直接丢进模型正规方程解出来的权重会非常悬殊。虽然预测本质不受影响模型会自己调整权重来抵消量纲差异但有两个问题第一梯度下降的效率会变得很差和我在手写部分说的一样没有做标准化的模型要跑更多轮才能收敛还容易震荡。第二系数的可解释性被破坏。你没法直观比较面积系数和房龄系数谁更重要因为它们单位根本不同。所以只要涉及系数分析、特征重要性评估我几乎一律先做标准化再训练。6.3 线性回归解决不了的问题别硬用这一点想给所有热衷于万物皆可回归的新手提个醒线性回归不是万能的。分类问题不能用线性回归硬扛。虽然从形式上看你确实可以用连续数值去拟合0/1标签但线性回归的预测值会落在0到1范围之外无法解释成概率。而且异常值会把整条线拉得乱七八糟。分类请用逻辑回归名字带回归但其实是分类算法。强非线性关系要用其他模型。比如预测点击率随出价提高先升后降这种倒U形关系线性回归的直线永远拟合不出来。可以先用散点图观察数据形态再决定是否需要加平方项、交互项或者直接换决策树、随机森林这类非线性模型。数据量太大时注意性能。正规方程需要计算X^T X的逆矩阵当特征数超过几万时内存就是灾难样本量达到百万级时梯度下降虽然可行但需要合理设置批量大小。这种场景我一般会改用SGDRegressor它一次处理一个小批次能扛住大规模数据。6.4 一个容易被忽视的细节目标变量也要看形态特征处理做得好往往忽略了目标变量y本身的分布。线性回归对异常值的敏感度很高而y如果严重右偏比如个人收入少数超高收入把均值拉得远远偏离中位数会破坏误差正态分布假设模型会为了迁就几个异常点牺牲大多数普通样本的拟合效果。我遇到这种情况的常规操作是先看一眼y的直方图如果明显右偏就做对数变换训练时用log(y)作为目标预测后再用exp还原。这个技巧非常实用特别是做房价、销量、收入这类数据时能让RMSE下降不少。最后再分享一条经验线性回归真正强大的地方不在于它能拟合多么复杂的数据而在于它的可解释性——你能把模型输出映射回业务语言告诉别人是哪个因素推高了预测值推高了多少。这正是它在信贷风控、医疗诊断、销量预测这些监管严、需要解释的领域里经久不衰的原因。所以别看它简单把原理吃透把坑都踩一遍你后面学逻辑回归、岭回归、Lasso这些进阶模型会发现它们全都是在线性回归这棵老树上长出来的枝条。