ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

线性回归原理与Python手写实现:从最小二乘到梯度下降

线性回归原理与Python手写实现:从最小二乘到梯度下降 机器学习入门几乎每个人都从线性回归开始但真正把线性回归吃透的其实不多。我见过太多人写 (y wx b) 没问题调 sklearn 的LinearRegression也很熟练可一旦被问到“为什么最小二乘要用平方误差而不是绝对值”“正规方程和梯度下降到底什么关系”就卡住了。这篇文章就是来补这块短板的从数学原理一路推到代码实现全程只用 Python 和 numpy不依赖任何现成的机器学习库把线性回归每一步掰开揉碎。内容会覆盖最关键的几个问题线性回归到底在解决什么、损失函数为什么是误差平方和、正规方程怎么推导、梯度下降为什么绕不开、代码怎么写与怎么诊断。无论你是在准备机器学习期末复习还是刚开始接触机器学习算法又或者只是想把底层原理搞明白这篇文章都应该能帮到你。1. 线性回归要解决的从来不只是“画一条直线”1.1 试想一个真实场景房价预估假设你手里有一批房屋数据每套房有面积、卧室数量、周边配套评分还有最终成交价。你现在拿到一套还没有定价的新房源想预估一个合理的挂牌价。最朴素的思路是什么找出面积、卧室数、评分对价格的共同影响用它们算出价格——这正是线性回归在做的事。更正式地说我们有一组输入特征 (x^{(i)} (x_1^{(i)}, x_2^{(i)}, ..., x_p^{(i)}))和一个连续输出 (y^{(i)})。线性回归假设输出是输入的线性组合再加一个偏置项[ y w_1 x_1 w_2 x_2 ... w_p x_p b ]写成紧凑的向量形式就是[ y \mathbf{w}^T \mathbf{x} b ]目标是根据已知数据找到一组最合适的 ((\mathbf{w}, b))让预测值 (\hat{y}) 尽量接近真实值 (y)。这件事在机器学习里叫“回归”因为输出是一个连续数值而不是离散类别。很多同学在入门时容易把回归和分类混在一起记住一个判断标准就行输出是“多少”就是回归输出是“哪一类”就是分类。上面这个例子是经典的“监督学习”任务。监督学习本质上是“从带标签的数据中学习映射”线性回归是其中最直观的表达式也是后面逻辑回归、神经网络、支持向量机等模型的逻辑起点。你要是不把这一步搞清楚后面看复杂模型大概率也是半懂不懂因为损失的构造方式、参数求解思路、过拟合风险控制几乎都能在线性回归里找到原型。1.2 “线性”两个字到底意味着什么很多初学者一听“线性回归”第一反应是只能拟合一条直线。这个理解不完全对。线性回归的“线性”指的是对参数 (\mathbf{w}) 线性而不是对特征 (x) 线性。什么意思就是说模型长这样[ \hat{y} w_1 x_1 w_2 x_2 b ]是线性的但如果我把 (x_1^2) 也当成一个特征放进去[ \hat{y} w_1 x_1 w_2 x_2^2 b ]这个模型相对于 (x_1) 和 (x_2) 是非线性的但相对于参数 (w_1, w_2, b) 仍然是线性的。所以在实际项目里我们可以通过构造多项式特征让线性回归拟合曲线。这种做法叫“多项式回归”严格来说它仍然属于线性回归家族因为求解方法完全不变。换句话说线性回归不是一个只能画直线的玩具它是一个“用线性参数组合去近似任意函数”的工具。这个认知很重要否则你会在后面学特征工程的时候产生困惑为什么明明数据呈曲线还能用线性回归处理答案就是你其实是在用 (x^2, x^3, \log x) 等扩展特征让线性模型在“特征空间”里照样可以拟合非线性关系。2. 为什么损失函数偏偏是误差平方和最小二乘的两种解读2.1 残差不能直接求和一场正负抵消的骗局有了模型下一个问题是怎样才算“尽量接近”我们需要一个量化标准让程序能判断某组参数好不好。最直接的想法是计算每个样本的预测误差残差[ e_i y_i - \hat{y}_i ]然后把这些误差加起来当作总损失。听起来挺合理但立刻会遇到问题误差有正有负直接求和会互相抵消。假设有三个样本误差分别是 3、-3、3总和是 3实际上模型已经偏离很大了可损失看着不大。这不是个好指标。那取绝对值再求和也就是平均绝对误差MAE[ L \frac{1}{n}\sum_{i1}^{n} |y_i - \hat{y}_i| ]这个方案解决了正负抵消的问题也比平方误差更抗异常值。但它在数学上有个麻烦绝对值函数在 0 处不可导。虽然可以用次梯度之类的手段处理但最优解可能不唯一优化起来也更别扭。在梯度下降大行其道的时代不可导意味着很多现成的优化工具用不了。于是人们盯上了平方误差[ L \frac{1}{n}\sum_{i1}^{n} (y_i - \hat{y}_i)^2 ]它有三个非常讨喜的性质第一平方之后所有残差都是非负的正负不会抵消第二它是一个处处可导的凸函数有唯一的全局最小值用梯度下降或正规方程都能稳定求解第三平方对大误差的惩罚比小误差重得多——误差 2 的惩罚是误差 1 的四倍这会让模型更愿意照顾那些“错得离谱”的样本。这第三种性质是一把双刃剑好处是模型会比较认真对待每一个样本坏处是它会被离群点牵着鼻子走。这一点我放到后面第 6 章专门讲这里先不展开。把这三种候选损失放在一起看会更清楚损失形式正负抵消可导性对异常值敏感度典型使用场景残差和严重——基本不用绝对值 / MAE不抵消在0处不可导低鲁棒回归平方和 / MSE不抵消处处可导高标准线性回归、神经网络损失正因为平方误差的计算过程叫“最小化误差平方和”所以这种方法也被称为“最小二乘法”Ordinary Least Squares, OLS。2.2 高斯视角最小二乘等价于极大似然估计如果你觉得上面只是“平方用起来方便”的工程理由那就低估了最小二乘的数学地位。19 世纪初高斯在预测天体运行轨道时发现如果在假设误差服从正态分布的前提下做极大似然估计推出来的结果恰好就是最小二乘。我简单推一遍。假设每个样本的真实关系是[ y_i \mathbf{w}^T \mathbf{x}_i b \varepsilon_i ]其中误差 (\varepsilon_i) 服从均值为 0、方差为 (\sigma^2) 的正态分布。那么给定参数后观测到 (y_i) 的概率是[ P(y_i | \mathbf{x}_i; \mathbf{w}, b) \frac{1}{\sqrt{2\pi}\sigma} \exp\left(-\frac{(y_i - \mathbf{w}^T\mathbf{x}_i - b)^2}{2\sigma^2}\right) ]所有样本独立同分布所以整个训练集的似然函数是这些概率的乘积。取对数后得到[ \ln L \sum_{i1}^{n} \left[ -\frac{1}{2}\ln(2\pi\sigma^2) - \frac{(y_i - \mathbf{w}^T\mathbf{x}_i - b)^2}{2\sigma^2} \right] ]最大化这个对数似然等价于最小化后面那项平方和因为前面那一堆都是和参数无关的常数[ \min_{\mathbf{w}, b} \sum_{i1}^{n} (y_i - \mathbf{w}^T\mathbf{x}_i - b)^2 ]看到了吗这就是最小二乘。这条推导建议你亲手抄一遍因为它打通了一个更通用的认知很多模型的损失函数本质上都可以从“概率假设 极大似然”这个框架里推出来。比如逻辑回归用交叉熵而不是平方误差就是因为分类问题里的输出服从伯努利分布而不是正态分布。你以后看到一个新模型的损失函数时不会再觉得它是“拍脑袋想出来的”而是会下意识想问一句这个模型假设了什么分布3. 正规方程推导从一元斜率达到矩阵闭式解3.1 一元线性回归的手推过程先从最简单的一元情况开始。假设只有一个特征 (x)模型是[ \hat{y}_i w x_i b ]损失函数为[ S(w, b) \sum_{i1}^{n} (y_i - w x_i - b)^2 ]我们的目标是找到让 (S) 最小的 (w) 和 (b)。因为 (S) 是关于 (w, b) 的凸二次函数最小值处偏导数一定为 0。分别求偏导[ \frac{\partial S}{\partial b} -2 \sum_{i1}^{n} (y_i - w x_i - b) 0 ][ \frac{\partial S}{\partial w} -2 \sum_{i1}^{n} x_i (y_i - w x_i - b) 0 ]由第一个式子整理得[ nb \sum_{i1}^{n} y_i - w \sum_{i1}^{n} x_i ][ b \bar{y} - w \bar{x} ]这说明回归直线一定经过样本均值点 ((\bar{x}, \bar{y}))这是一个非常好用的直觉。把 (b) 代入第二个式子经过几步代数整理可以得到[ w \frac{\sum_{i1}^{n} (x_i - \bar{x})(y_i - \bar{y})}{\sum_{i1}^{n} (x_i - \bar{x})^2} ]分子是 (x) 和 (y) 的协方差分母是 (x) 的方差。所以斜率的本质是“x 每变化一个标准差y 跟着变化多少”这比“背公式”要有意思得多。理解了这一点后续你看很多统计报表里的回归系数时就能直接读出变量之间的关系强度。3.2 用矩阵语言写出多维解一元推导虽然直观但实际项目里特征往往不止一个。这时候再用偏导一个一个推就不现实了矩阵是更优雅的武器。把截距 (b) 吸收进权重向量方法是给设计矩阵 (X) 加一列全 1。假设 (X) 是 (n \times (p1)) 的矩阵第 1 列全为 1后面每列对应一个特征(y) 是 (n) 维列向量(\mathbf{w}) 是 (p1) 维列向量。那么预测值和真实值的残差向量是[ y - X\mathbf{w} ]损失函数写成矩阵形式[ J(\mathbf{w}) (y - X\mathbf{w})^T (y - X\mathbf{w}) ]展开得[ J(\mathbf{w}) y^T y - 2 \mathbf{w}^T X^T y \mathbf{w}^T X^T X \mathbf{w} ]现在对 (\mathbf{w}) 求梯度。这里需要用到两条矩阵求导法则[ \frac{\partial (\mathbf{w}^T \mathbf{a})}{\partial \mathbf{w}} \mathbf{a} ][ \frac{\partial (\mathbf{w}^T A \mathbf{w})}{\partial \mathbf{w}} (A A^T)\mathbf{w} ]注意 (X^T X) 是对称矩阵所以第二条可化简为 (2X^T X \mathbf{w})。代入得到[ \frac{\partial J}{\partial \mathbf{w}} -2X^T y 2X^T X \mathbf{w} ]令梯度等于 0[ X^T X \mathbf{w} X^T y ][ \mathbf{w} (X^T X)^{-1} X^T y ]这就是著名的正规方程Normal Equation也叫最小二乘的闭式解。因为它是直接一步算出来的不涉及任何迭代过程所以只要 (X^T X) 可逆这个解就是全局最优解不需要纠结学习率、迭代次数这些事。3.3 不可逆怎么办伪逆与数值方法的出场正规方程看起来美妙但 (X^T X) 不一定可逆。最常见的情况有两种。第一种是特征之间存在完全的多重共线性比如一个特征是“房子总面积”另一个特征是“卧室面积”它们高度相关导致矩阵接近奇异。第二种是样本数小于特征数比如做医学研究时只有 50 个病人却有 200 个基因指标(X^T X) 是 200×200 的矩阵但实际秩最多只有 50必然不可逆。遇到这种情况数学上可以用伪逆Moore-Penrose Pseudoinverse[ \mathbf{w} X^{} y ]Python 里np.linalg.pinv(X)就能算伪逆或者直接np.linalg.lstsq(X, y, rcondNone)求解。伪逆不要求 (X^T X) 可逆它会给出一个最小二乘意义下的解。但从另一个角度看不可逆本身往往是在提醒你数据有问题需要做特征筛选或正则化。单纯的伪逆能算出个结果却不解决共线性背后的解释性问题。这一点到第 6 章讲多重共线性时再展开。4. 梯度下降那套你迟早要懂的迭代公式4.1 正规方程的工程极限在哪里闭式解既然能一步到位为什么还要学梯度下降因为正规方程在实际工程中有几个硬伤。第一个硬伤是计算复杂度。计算 ((X^T X)^{-1}) 需要对一个 (p \times p) 矩阵求逆时间复杂度大约是 (O(p^3))。当特征数 (p) 达到几万、几十万时这一步基本跑不动。而梯度下降每一步的计算量大约是 (O(np))迭代几百上千次通常就能收敛性价比非常高。第二个硬伤是内存和可扩展性。正规方程要把整个 (X) 矩阵装进内存遇到海量数据直接内存爆炸。梯度下降则天然支持分批训练每次只拿一小批样本算梯度内存占用小得多这也是深度学习训练几乎全靠梯度下降类算法的原因。第三个硬伤是模型扩展。闭式解只适用于损失函数是凸二次函数的情况。一旦换成带 L1 正则的 Lasso、带非线性变换的神经网络损失函数变得复杂解析解就不存在了只能依赖迭代优化。说句题外话很多同学备考时容易把正规方程和梯度下降对立起来其实它们是互补的数据规模小、特征少、(X^T X) 可逆时优先用正规方程数据规模大、特征多或模型复杂时用梯度下降。没有谁绝对更好只有合不合适。4.2 梯度公式推导与更新规则梯度下降的核心思想特别简单可以拿“下山”来类比你站在山腰上想走到山谷最快的方式是沿着当前最陡的方向往下走一步然后重新判断方向再走一步反复直到到达山谷。这里的“山”就是损失函数曲面“最陡方向”就是负梯度方向。对我们最常用的损失函数[ J(\mathbf{w}) \frac{1}{n} \sum_{i1}^{n} (y_i - X_i \mathbf{w})^2 ]前面已经推导过它的梯度是[ \nabla J(\mathbf{w}) \frac{2}{n} X^T (X\mathbf{w} - y) ]于是参数更新规则就是[ \mathbf{w} \leftarrow \mathbf{w} - \eta \nabla J(\mathbf{w}) ]其中 (\eta) 是学习率控制每次迈多大步子。这个公式看起来简单但每个符号的来龙去脉都值得想清楚(X^T (X\mathbf{w} - y)) 本质上是把“每个样本的预测误差”重新映射回特征空间用它来指导每个参数往哪个方向调。误差为正的样本对应的参数会往一个方向推误差为负的样本会往反方向推综合起来就是梯度。配套的伪代码是初始化 w 0 重复 steps 次 pred X w error pred - y grad (2 / n) * (X.T error) w - lr * grad在实际工程里我们通常还会记录每一轮的损失值画一条 loss 曲线用来判断是否收敛。4.3 学习率、特征缩放、三种梯度下降的取舍学习率是梯度下降里最需要经验调参的地方。我直接给一张实践速查表现象可能原因处理方式loss 发生震荡或变成 NaN学习率过大调小学习率从 0.001/0.01 开始试loss 一直在缓慢下降但速度很慢学习率过小适当增大学习率或增加迭代次数不同特征量纲差异大loss 等高线呈扁长椭圆迭代路径呈锯齿状特征未标准化对特征做 Z-score 标准化loss 曲线看起来还在明显下坡迭代次数不够增大 steps 或设置早停条件特征缩放这件事值得多说一句。如果两个特征量纲差一个数量级比如“房屋面积”是几十到几百“卧室数量”是 1 到 10那么损失函数的等高线会被拉成极长的椭圆。梯度下降在这种地形里会来回震荡收敛极慢。解决办法是对每个特征做标准化[ z \frac{x - \mu}{\sigma} ]标准化之后每个特征均值为 0、方差为 1损失函数的地形更接近等高的圆梯度下降路径又直又快。代价是回归系数的解释性会变差因为此时每个系数衡量的不再是“原始单位变化”的影响而是“标准差变化”的影响。你要是在做需要可解释性的分析要么保留原始特征要么记得在解释时换算回去。梯度下降还有一个家族之分每次用全量数据算梯度的叫批量梯度下降BGD每次随机抽一个样本更新的叫随机梯度下降SGD每次抽一小批、比如 32 或 128 个样本的叫小批量梯度下降Mini-batch GD。三者没有对错只有场景。数据量小、特征少BGD 稳定简单数据量巨大、追求速度SGD 或 Mini-batch 更实际深度学习训练基本全是 Mini-batch。5. Python 从零手写线性回归不调库也要能跑5.1 构造一份可复现的数据并设计实验流程理论讲完是时候写代码了。这一节我会用 numpy 手写两种版本的线性回归一种用正规方程的闭式解一种用梯度下降迭代。首先造一份能复现的模拟数据。为了贴合实际场景我模拟“营销费用与销售额”的关系花费 (x) 从 0 到 10 均匀分布真实销售额满足 (y 3x 2)再加上标准差为 1.2 的高斯噪声。import numpy as np # 固定随机种子保证结果可复现 rng np.random.default_rng(42) # 生成 200 个样本 X np.linspace(0, 10, 200).reshape(-1, 1) true_w 3.0 true_b 2.0 y true_w * X.ravel() true_b rng.normal(0, 1.2, sizeX.shape[0]) # 划分训练集和测试集 indices np.arange(len(X)) rng.shuffle(indices) # 打乱顺序避免排序带来偏差 train_idx indices[:160] test_idx indices[160:] X_train, y_train X[train_idx], y[train_idx] X_test, y_test X[test_idx], y[test_idx]注意几个细节。第一一定要固定随机种子否则你每次运行得到的数据不同后面调参时根本没法对比。第二数据要先打乱再划分如果直接用前 80% 当训练集测试集恰好落在某个区间结果会有误导性。第三这里我只造了一个特征方便画图和理解换成多个特征原理完全一样只是无法可视化。5.2 先用正规方程实现闭式解版本正规方程的实现非常简短核心就是给 (X) 加一列全 1然后套公式 (\mathbf{w} (X^T X)^{-1} X^T y)。def normal_equation(X, y): # 给 X 左边加一列 1这一列对应截距项 b X_b np.c_[np.ones((X.shape[0], 1)), X] # 核心公式 theta np.linalg.inv(X_b.T X_b) X_b.T y return theta theta_ne normal_equation(X_train, y_train) print(正规方程结果:, theta_ne) # 输出大概在 [2.0 左右, 3.0 左右]theta_ne[0]是截距theta_ne[1]是斜率。因为真实参数是 (b2.0, w3.0)只要噪声不算离谱结果会和真实值非常接近。这段代码一共三行核心逻辑但它把数学推导完整落地了。你可以试试去掉np.c_那行看看模型是不是会强行穿过原点结果差很多——这就是截距项的作用。新手往往容易漏掉这一步直接拿原始特征矩阵去算导致拟合效果很差。5.3 梯度下降版本同一套数据看它怎么收敛接下来是梯度下降版。为了让收敛过程可观察我把每一步的损失都记录下来。def gradient_descent(X, y, lr0.02, epochs1000): X_b np.c_[np.ones((X.shape[0], 1)), X] n len(y) theta np.zeros(X_b.shape[1]) # 从零向量开始 losses [] for _ in range(epochs): pred X_b theta error pred - y # 梯度公式2/n * X^T (Xw - y) grad (2 / n) * (X_b.T error) theta - lr * grad loss (error ** 2).mean() losses.append(loss) return theta, losses theta_gd, losses gradient_descent(X_train, y_train, lr0.02, epochs1000) print(梯度下降结果:, theta_gd)运行之后你会发现梯度下降学到的 (b) 和 (w) 与正规方程几乎一致说明两种路子殊途同归。然后可以画一下 loss 曲线import matplotlib.pyplot as plt plt.plot(losses) plt.xlabel(Iteration) plt.ylabel(MSE Loss) plt.title(Gradient Descent Convergence) plt.show()如果 curve 是平滑下降并趋于一条水平线说明学习率和迭代次数选得合适。如果看到 loss 先降后升像波浪一样多半是学习率偏大如果 1000 轮之后 loss 还在明显下降说明迭代次数不够。我自己调试时第一次跑就踩过坑直接把学习率设成 0.5结果 loss 直接冲到了 NaN。原因很简单梯度下降每一步都在跨越“山谷”的谷底步子太大反而跳出到更高的地方最终发散。所以我的习惯是面对新数据永远从 0.01 这类保守值开始试再一点点往上加。5.4 和 sklearn 的 LinearRegression 对照一次手写实现验完原理之后再用 sklearn 对照一下确保我们的“自制版”没有改错公式。from sklearn.linear_model import LinearRegression model LinearRegression() model.fit(X_train, y_train) print(sklearn 截距:, model.intercept_) print(sklearn 系数:, model.coef_) # 计算 R² def r2_score(y_true, y_pred): ss_res ((y_true - y_pred) ** 2).sum() ss_tot ((y_true - y_true.mean()) ** 2).sum() return 1 - ss_res / ss_tot # 用测试集评估 y_pred_ne np.c_[np.ones((X_test.shape[0], 1)), X_test] theta_ne y_pred_gd np.c_[np.ones((X_test.shape[0], 1)), X_test] theta_gd y_pred_sk model.predict(X_test) print(正规方程 R²:, r2_score(y_test, y_pred_ne)) print(梯度下降 R²:, r2_score(y_test, y_pred_gd)) print(sklearn R²:, r2_score(y_test, y_pred_sk))三次输出的 (R^2) 应该非常接近。这说明自己实现的逻辑没有错sklearn 的LinearRegression底层用的也是最小二乘的思路只不过做了更多数值稳定性优化。那为什么还要自己写一遍很简单生产环境当然用 sklearn又快又稳但学习阶段如果不亲自写一遍正规方程和梯度下降你对“参数到底怎么来的”就永远是黑箱。后面你换损失函数、加正则项时能不能改明白代码就在这上面见真章。6. 拟合之后四件诊断让模型不死得不明不白6.1 别迷信 R²新增无用特征只会让它变大模型拟合完大家第一反应都是看 (R^2)。(R^2) 的定义是[ R^2 1 - \frac{\sum (y_i - \hat{y}_i)^2}{\sum (y_i - \bar{y})^2} ]它表示模型解释了总方差里百分之多少的变化。(R^2 0.95) 听起来很美但它有一个陷阱你往模型里塞多少特征(R^2) 都只会增不会减哪怕塞进去的是一个纯随机噪声列。因为最小二乘总能找到一组系数来利用这种噪声哪怕只是微弱的、虚假的相关。所以要用调整后的 (R^2)Adjusted R²它会对特征数量做出惩罚[ R^2_{\text{adj}} 1 - (1 - R^2) \cdot \frac{n - 1}{n - p - 1} ](p) 是特征数量。当你新增一个几乎没用的特征时(R^2) 本身可能涨一点点但 (\frac{n-1}{n-p-1}) 这个惩罚项会让调整 (R^2) 反而下降。这也是为什么在特征筛选时调整 (R^2) 比普通 (R^2) 靠谱得多。6.2 残差图模型用没用对的照妖镜(R^2) 告诉你“拟合得好不好”残差图告诉你“模型是不是被用对了”。所谓残差就是真实值减预测值[ \text{residual}_i y_i - \hat{y}_i ]理想的残差应该是在 0 附近随机分布的没有趋势、没有形状。你把残差画在纵轴上、预测值画在横轴上如果看到一团均匀的“云”说明模型基本正确。如果看到漏斗形预测值越大的地方残差波动越大说明存在异方差性如果看到 U 形或倒 U 形说明数据里有明显的非线性关系你该考虑增加多项式特征如果残差和某个特定特征仍然相关说明你可能漏掉了交互项。这种诊断在线性回归里特别重要因为最小二乘的很多统计性质比如系数标准误的估计都建立在残差独立同分布且同方差的假设上。残差图不满足即使 (R^2) 很高参数的置信区间也可能失真。6.3 多重共线性系数符号反了是常有的事多重共线性是“看着预测准但实际上很困惑”的典型问题。假设你的特征里同时有“房屋总面积”和“卧室面积”两者高度相关那么模型在分配权重时会有无数种组合都差不多好用。你拿到的系数可能是一个正、一个负可实际上卧室面积多的房子确实更贵符号为负显然违背直觉。检测共线性最常用的指标是方差膨胀因子VIF。简单理解就是用当前特征做因变量、其他特征做自变量的回归看它的 (R^2)然后[ VIF \frac{1}{1 - R^2} ]VIF 超过 10 就被认为需要警惕。处理办法也很直接删除其中一个高度相关的特征或者做 PCA 降维后再回归再或者改用岭回归L2 正则来稳定系数。注意如果你只是想做预测不太关心单个系数的解释那共线性造成的伤害有限。但如果公司领导问你“为什么面积越大反而房价越低”你就得先查一查 VIF。6.4 离群点的杀伤力与解决思路最小二乘对离群点非常敏感因为平方项会给大误差极高的权重。一个极端离群点哪怕只有一个也可能把整条回归线“拖”向自己让大部分正常样本的拟合变差。处理离群点的思路分几层。先看数据是不是录入错误比如把 100 万录成了 10 万这种直接修然后是统计上的判断比如用箱线图或 Z-score 找出那些超出合理界限的样本结合业务场景决定是否剔除再往上是采用更鲁棒的损失函数比如用绝对值损失MAE代替平方损失或者使用sklearn里的HuberRegressor它综合了平方损失和绝对值损失的特点对离群点更加宽容。我个人的建议是不要一看到离群点就删。数据科学里离群点可能是异常值也可能是重要信号——比如欺诈交易、故障设备。你需要先理解它为什么离群再决定是修正、剔除还是换鲁棒算法。7. 我在实际项目里用线性回归攒下的几点经验最后分享几条踩过坑之后才真正理解的经验。第一线性回归的“可解释性”是它最大的竞争力。很多时候企业不会只关心预测“准不准”还会关心“为什么是这个值”。同样是预测员工离职倾向或者分析营销效果一个精度略低但系数含义清晰的线性模型往往比一个黑箱的复杂模型更受业务部门信任。所以不要觉得线性回归“太简单”就不屑一顾它在金融风控、医学、经济学这些强调解释的场景里仍然大量使用。第二特征是决定模型上限的东西。线性回归本身很简单真正拉开差距的是特征工程。把你对业务的理解变成特征比如把时间戳拆成“是否周末”“是否节假日”把连续值切分成分段变量这些操作经常能让一个朴素线性模型的性能大幅提升。我之前做某个销售预测项目辛辛苦苦调模型参数学到手都没有明显提升后来发现是少了一个“是否为促销日”的特征加上之后模型表现立刻上了一个台阶。第三梯度下降的学习率不要想当然。新数据新场景下第一次跑训练时一定记得把 loss 曲线打出来看一眼。它能告诉你学习率设大了还是小了迭代次数够不够。我见过太多人闷头跑模型结果 loss 一直发散成 NaN 还在问为什么原因就是没养成看损失曲线的习惯。线性回归虽然是最基础的机器学习算法但它背后的思想——损失函数怎么写、闭式解和迭代解怎么选、模型诊断怎么做——会伴随你整个机器学习生涯。把这一套真正搞懂后面再学逻辑回归、岭回归、Lasso、神经网络你会发现很多“新知识”不过是这里改一点、那里换一个假设而已。
返回列表