
1. 线性回归到底在解决什么问题很多刚接触Python的朋友看到线性回归四个字就觉得是数学课的内容下意识想绕道走。其实线性回归就是数据领域最基础、最常用的一把尺子——它帮我们从一堆乱七八糟的数据里找到规律然后用一条直线把规律画出来。举个最直白的例子你记录了过去10个月的广告花费和对应销售额现在老板问你下个月投8万块广告大概能卖多少货如果你拍脑袋说20万老板会怀疑你。但如果你用线性回归拟合出一条广告费→销售额的关系曲线就能给出一个有理有据的预测值。这就是线性回归的核心价值用一条直线描述两个变量之间的关系并基于这条线做预测。我当年第一次接触线性回归是在一个电商数据分析项目里。运营同事扔给我一份表格里面是几百个商品的定价和销量让我分析价格降到多少销量能翻倍。当时我用Excel画散点图、加趋势线勉强能交差。后来学会了用Python写线性回归才发现以前效率低得离谱——Excel里每次手动拖拽、改参数Python只要几十行代码还能顺便算出置信区间、系数显著性这些Excel根本不给你的指标。线性回归的适用场景比大部分人想象中广得多销售预测根据历史销量、促销力度预测未来需求成本估算根据项目规模、人力投入估算开发成本用户行为分析根据用户使用时长预测留存率房价估值根据面积、地段、楼层等特征估算房价测试数据分析根据CPU占用率预测接口响应时间如果你身边有做数据分析、算法、测试开发的朋友你可以观察一下他们的工作流里几乎都离不开线性回归。它既是入门机器学习的第一个算法也是工业界实际落地最频繁的模型之一。原因很简单它足够简单跑得快结果还能解释——你能说清楚广告费每增加1万销售额平均增加3.2万这种可解释性在很多业务场景里比一个黑盒的深度学习模型更值钱。这篇文章我打算按我自己的学习路径来写先讲线性回归的数学原理然后手写实现一遍最小二乘法再用sklearn快速实现最后聊评估指标和调参经验。整个过程中我会把我踩过的坑、记过的笔记一并分享出来全程可以跟着敲代码不需要额外买书。2. 线性回归的数学原理一句话就能看懂2.1 一元线性回归的方程长什么样线性回归的数学表达其实特别朴素。数据里有一个自变量x和一个因变量y我们假设y和x之间存在这种关系y wx b这里w叫斜率或者叫权重、系数b叫截距。w表示x每变化一个单位y平均变化多少b表示x等于0时y的取值。机器学习圈子里喜欢把w和b合起来叫模型的参数模型训练的过程其实就是找一组最合适的w和b。举个直观的栗子假设你要预测学习时长和考试成绩的关系数据里x是学习小时数y是考试分数模型学出来的结果是 y 8.5x 30。这个式子怎么解读意思是哪怕你完全不学习x0也能蒙到30分b30每多学1小时成绩平均提高8.5分w8.5。这种清晰的解释能力就是线性回归在业务场景里特别好用的原因。2.2 最小二乘法怎么找那条最优直线关键问题来了数据点那么多能画出来的直线有无数条怎么判断哪条最好这里的标准做法是计算残差——每个数据点到直线的垂直距离严格说是实际y值和预测y值的差然后把所有残差的平方加起来。这个总和叫残差平方和RSS。我们想找的最优直线就是让残差平方和最小的那条线。为什么要平方而不是直接加绝对值有两个原因第一残差有正有负直接相加会互相抵消明明每条线都差得很远相加结果却可能是0这就失去衡量意义了第二平方运算会放大误差让差得远的点得到更大的惩罚这样拟合出来的直线会更照顾那些偏离严重的点。这种通过最小化误差平方和来求解参数的方法就是最小二乘法。最小二乘法的求解过程用微积分可以推导出闭式解高中知识水平就能跟上w Σ(xi - x̄)(yi - ȳ) / Σ(xi - x̄)²b ȳ - w·x̄这个公式不需要死记硬背关键是理解它的含义w的分子衡量了x和y是否同向变化、变化幅度是否匹配分母则是一个归一化因子。如果x越大y越大那么分子就是一个较大的正值w就是正的直线向右上方倾斜如果x越大y反而越小w就是负的直线向右下方倾斜。2.3 多元线性回归多个变量一起上现实中往往不止一个影响因素。比如预测房价面积、房龄、楼层、朝向都在起作用。这时候模型就变成多元的了y w₁x₁ w₂x₂ ... wₙxₙ b每一个特征对应一个权重模型要同时学所有权重。多元线性回归的原理和一元的本质一样都是最小二乘法只是矩阵运算更复杂。实际做项目时特征之间还会互相影响比如面积和房间数高度相关这就引出了后面要讲的多重共线性问题。3. 手写最小二乘法从零实现一个线性回归3.1 为什么要手写一遍你可能会想sklearn里一行代码就搞定LinearRegression了手写这不是多此一举吗我第一次学的时候也这么想。后来在一次面试里面试官问我线性回归的损失函数是什么梯度下降和最小二乘有什么区别我发现自己只能说个大概细节全糊。那一刻我才意识到调包只是会用手写才是真懂。手写一遍代码能帮你把数据是怎么流动的、每个参数是怎么算出来的彻底搞清楚后面再学逻辑回归、岭回归、Lasso都会顺畅很多。另外在实际开发中也会遇到一些场景必须手写。比如数据量特别大、不能一次性加载到内存的时候或者要实时更新模型权重的时候sklearn的fit接口用起来就很别扭反而手写的梯度下降更灵活。3.2 准备数据用NumPy造一组带规律的数据手写之前先用NumPy生成一组模拟数据也好验证我们模型的正确性。真实项目里没有标准答案但在模拟数据上你能清楚地看到模型学到了什么。import numpy as np # 固定随机种子保证实验结果可复现 np.random.seed(42) # 生成100个在[0, 10]区间均匀分布的样本点 X np.linspace(0, 10, 100) # 真实规律y 2.5 * x 1.0再加上随机噪声模拟现实波动 true_w 2.5 true_b 1.0 y true_w * X true_b np.random.normal(0, 1.0, sizeX.shape[0])这里有几个细节值得注意。np.random.seed(42)是给随机数生成器设定种子这样你每次运行代码拿到的随机数据都是一样的方便对照实验结果。噪声的均值为0、标准差为1.0模拟的是现实世界里那些无法解释的随机波动。有了这组数据我们就可以把还原出来的w和b和真实的2.5、1.0做比较验证实现是否靠谱。3.3 用最小二乘公式求解w和b上面提到过手写最小二乘法的核心就是算两组值分子和分母。用NumPy写出来非常简洁def least_squares(X, y): 一元线性回归的最小二乘法实现 参数: X: 自变量数组shape为(n,) y: 因变量数组shape为(n,) 返回: w: 斜率 b: 截距 x_mean np.mean(X) y_mean np.mean(y) # 分子x和y的协方差相关部分 numerator np.sum((X - x_mean) * (y - y_mean)) # 分母x的方差部分 denominator np.sum((X - x_mean) ** 2) w numerator / denominator b y_mean - w * x_mean return w, b w_hat, b_hat least_squares(X, y) print(f最小二乘法求得的斜率: {w_hat:.4f}) print(f最小二乘法求得的截距: {b_hat:.4f})运行结果大概会是这样最小二乘法求得的斜率: 2.5017 最小二乘法求得的截距: 1.0142对照真实的w2.5、b1.0你会发现结果非常接近。这就是最小二乘法的魅力在误差服从正态分布的前提下它给出的估计是所有线性无偏估计里方差最小的。3.4 用梯度下降再来一遍最小二乘法能一步到位算出解析解但它的局限是当特征很多、数据量很大的时候矩阵求逆的运算开销非常高甚至可能因为矩阵不可逆而无法求解。这时候工程上更常用的方案是梯度下降法——它不是一步算出最优解而是一点一点往最优方向蹭。梯度下降的核心思路可以用下山的场景来比喻你站在山顶四周一片漆黑只能靠感觉判断哪个方向是下坡。每次迈一小步走一段再重新判断方向重复这个过程最终就能到达山脚。这里的坡度就是梯度步长就是学习率。def gradient_descent(X, y, w0.0, b0.0, lr0.01, epochs1000): 使用梯度下降法求解线性回归参数 参数: X: 自变量数组shape为(n,) y: 因变量数组shape为(n,) w: 斜率初始值 b: 截距初始值 lr: 学习率步长 epochs: 迭代次数 返回: w, b, loss_history: 最终参数和损失记录 n len(X) loss_history [] for epoch in range(epochs): # 前向计算用当前参数得到预测值 y_pred w * X b # 计算误差 error y_pred - y # 计算损失均方误差MSE loss np.mean(error ** 2) loss_history.append(loss) # 计算梯度 grad_w (2 / n) * np.sum(error * X) grad_b (2 / n) * np.sum(error) # 更新参数 w - lr * grad_w b - lr * grad_b if epoch % 200 0: print(fEpoch {epoch}: loss {loss:.4f}, w {w:.4f}, b {b:.4f}) return w, b, loss_history w_gd, b_gd, losses gradient_descent(X, y, lr0.01, epochs1000) print(f梯度下降求得的斜率: {w_gd:.4f}) print(f梯度下降求得的截距: {b_gd:.4f})梯度下降的每次迭代中核心都在算两个梯度grad_w和grad_b。它们在数学上就是损失函数对w和b的偏导数。学习率lr是这里最敏感的超参数——设得太大参数会在最优解附近来回震荡甚至发散设得太小训练半天还没收敛。我在实际调参时一般先按0.01试看loss曲线再指数级调整。如果对梯度下降的收敛过程感兴趣可以把loss_history画成曲线你能看到loss快速下降然后逐渐平缓的过程非常直观import matplotlib.pyplot as plt plt.plot(range(len(losses)), losses) plt.xlabel(Epoch) plt.ylabel(Loss (MSE)) plt.title(梯度下降过程中的损失变化) plt.show()3.5 两行代码画出拟合直线模型训练完了不画个图总感觉少了点什么。可视化能让我们一眼看出拟合效果数据里有没有异常点、模型是不是有偏差看图比看数字更直观。import matplotlib.pyplot as plt # 将预测直线和原始散点叠加在一张图里 plt.figure(figsize(10, 6)) plt.scatter(X, y, alpha0.6, label原始数据, colorsteelblue) plt.plot(X, w_hat * X b_hat, colorred, linewidth2, label最小二乘拟合线) plt.plot(X, w_gd * X b_gd, colorgreen, linestyle--, linewidth2, label梯度下降拟合线) plt.xlabel(X) plt.ylabel(y) plt.title(线性回归拟合效果对比) plt.legend() plt.grid(alpha0.3) plt.show()两条线几乎重合说明两种方法都成功找到了数据背后的规律。这也是我推荐手写一遍的原因——当你亲眼见到两条不同原理的线落在同一位置时对算法的信心是纯抄代码得不到的。4. 用sklearn快速实现线性回归工程级方案4.1 环境准备与sklearn安装手写实现的意义在于理解原理但到了实际项目中我们一般直接用sklearn这个成熟库。它封装了模型训练、预测、评估的完整流程不需要自己处理繁琐的矩阵运算底层是经过高度优化的C和Cython代码大数据量下比纯Python手写快得多。先确认环境里有没有装好库。很多初学者在装包这一步就卡住半天这里给一个通用的检查方法直接在终端或命令行里执行python --version pip show scikit-learn numpy如果提示找不到numpy或scikit-learn用pip安装pip install numpy scikit-learn matplotlib如果你是在Linux服务器上操作可能还需要用pip3代替pip或者用python3 -m pip install确保装到正确的Python环境里。我之前在一个权限受限的公司内网机器上装包经常要加--user参数始终记住一条原则用哪个Python跑代码就用哪个Python对应版本的pip装包。最稳妥的方式是把pip install写全比如python -m pip install numpy这样绝对不会装错环境。4.2 用sklearn一行代码完成训练sklearn的LinearRegression接口设计得极其简洁训练模型只要两行代码。我见过很多新手这样写却没跑通原因多半是数据格式不对——sklearn要求特征X是一个二维数组n行1列而不是一维数组。from sklearn.linear_model import LinearRegression # 注意这里把X从一维(100,)变成二维(100, 1) X_2d X.reshape(-1, 1) # 或者用更规范的方式X_2d X[:, np.newaxis] model LinearRegression() model.fit(X_2d, y) print(f斜率 w: {model.coef_[0]:.4f}) print(f截距 b: {model.intercept_:.4f}) print(fR² 决定系数: {model.score(X_2d, y):.4f})fit方法就是训练过程sklearn在底层自动完成了我们刚才手写的全部运算。model.coef_是权重数组model.intercept_是截距model.score(X, y)返回的是R²决定系数它衡量了模型对数据的解释程度取值范围通常在0到1之间越接近1说明模型拟合得越好。这里我特别想强调X.reshape(-1, 1)这个操作。sklearn的输入要求特征必须是二维的这是它和pandas、NumPy习惯一维数组最大的差异点之一。别问为什么记住就行——我一开始忘了reshape报错信息Expected 2D array, got 1D array instead会让人一头雾水现在看到这种报错第一反应就是检查输入维度。4.3 模型预测与结果检验模型训练好之后就需要拿它做预测了。sklearn提供predict方法输入新的特征值就能返回预测结果# 预测当x8时的y值 x_new np.array([[8]]) y_pred model.predict(x_new) print(f当 X8 时预测 y {y_pred[0]:.4f}) # 批量预测新样本 x_batch np.array([[3], [5], [7]]) y_batch model.predict(x_batch) print(批量预测结果:, y_batch)这种方式和Excel里的趋势线预测本质上是同一件事但sklearn的预测可以用来做更复杂的操作比如把模型嵌入到自动化流程里每天定时从数据库读数据、训练、预测、推送结果。我在实际项目中就这样做过一个销量预测的定时任务整个流程从过去的人工Excel操作变成全自动效率提升了不少。4.4 交叉验证别让你的模型只会背题一个刚训练完的模型如果直接在训练数据上评估效果往往会虚高。就像学生做自己做过的题目分数自然高但到了考试就会露馅。所以工程上更严谨的做法是把数据分成训练集和测试集用一部分数据训练另一部分数据评估。sklearn里最简单的做法是train_test_splitfrom sklearn.model_selection import train_test_split # 按7:3划分训练集和测试集random_state保证划分结果可复现 X_train, X_test, y_train, y_test train_test_split(X_2d, y, test_size0.3, random_state42) model_cv LinearRegression() model_cv.fit(X_train, y_train) train_score model_cv.score(X_train, y_train) test_score model_cv.score(X_test, y_test) print(f训练集 R²: {train_score:.4f}) print(f测试集 R²: {test_score:.4f})如果训练集R²很高而测试集R²明显偏低说明模型过拟合了。不过在线性回归这种相对简单的模型里过拟合一般不太严重更常见的反而是欠拟合——数据本身不是线性关系你硬拿直线去拟合训练集和测试集的分数都不会高。4.5 保存模型训练一次随时使用实际项目中模型不能每次预测都重新训练一遍。sklearn提供了两个非常方便的序列化工具joblib和pickle。我用joblib更多因为它在numpy数组上的序列化效率更高。import joblib # 保存模型到文件 joblib.dump(model, linear_regression_model.pkl) # 加载模型用于后续预测 loaded_model joblib.load(linear_regression_model.pkl) y_loaded_pred loaded_model.predict(np.array([[9]])) print(f加载模型预测结果: {y_loaded_pred[0]:.4f})这种训练一次、到处预测的模式在微服务架构里很常见。你可以把训练好的模型文件放到服务器上用Flask或FastAPI包一个HTTP接口前端请求来了就直接喂给模型返回预测结果整个过程毫秒级完成。5. 评估回归效果R²、均方误差和残差分析5.1 R²到底是干什么用的R²决定系数是回归任务里最常看的指标之一。它的计算公式是R² 1 - RSS / TSS其中RSS是残差平方和模型没解释掉的那部分误差TSS是总平方和数据本身的波动。如果模型完美预测了所有数据RSS0R²就等于1如果模型的预测效果和直接用平均值预测一样差R²就接近0如果模型烂到连平均值都不如R²甚至可以是负数——这是很多新手没想到的。R²可以通俗地理解为模型解释了数据中多少比例的变异。比如R²0.86意味着你的模型能解释86%的数据波动剩下14%是模型没考虑到的因素或者随机噪声。但R²也有坑它永远随着特征数量的增加而增加——哪怕加进去一个完全无关的特征R²也会勉强涨一点点。所以如果要在多个模型之间比较光看R²不够还要参考后面讲的均方误差、以及考虑特征数量惩罚的调整R²。5.2 均方误差和均方根误差均方误差MSE就是残差平方和的平均值它直观地告诉你模型平均犯错多大from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score y_pred_full model.predict(X_2d) mse mean_squared_error(y, y_pred_full) mae mean_absolute_error(y, y_pred_full) r2 r2_score(y, y_pred_full) print(fMSE: {mse:.4f}) print(fMAE: {mae:.4f}) print(fR²: {r2:.4f})MSE因为做了平方运算单位是原来数据单位的平方可解释性差一点。所以实际报告结果时我们更常用均方根误差RMSE它等于MSE开根号单位回到原始量纲。比如RMSE0.85意味着你的预测值平均偏离真实值约0.85个单位。MAE是另一个常用指标它计算的是预测值和真实值之间绝对差的平均值。MSE会放大那些差很多的样本的影响MAE则对所有误差一视同仁。如果你想重点减少大错误就优化MSE如果不想让个别极端值主导模型MAE是更好的选择。5.3 残差分析模型靠不靠谱画图才看得出来评估回归模型不能只看数值指标一定要画残差图。残差是真实值和预测值的差理论上好的模型残差应该是随机分布的没有明显的规律。residual y - y_pred plt.figure(figsize(10, 5)) plt.scatter(y_pred, residual, alpha0.6) plt.axhline(y0, colorred, linestyle--, linewidth1.5) plt.xlabel(预测值) plt.ylabel(残差) plt.title(残差图观察是否存在明显模式) plt.show()如果残差随机分布在零线两侧说明模型捕捉到了数据的主要规律如果残差呈现出喇叭形——预测值越大残差散布越宽说明数据存在异方差性即不同区间的波动幅度不一致如果残差呈现出明显的曲线形状那基本可以判定数据不是线性的你需要考虑多项式回归或者其他非线性模型。残差分析是教学里反复强调、但实际工作中很少有人做的步骤。我见过太多人只盯着R²看R²是0.95就认为万事大吉结果残差图一画出来曲线形状极其明显说明模型根本不适合。评估一个模型数值指标和残差图缺一不可。这条经验在面试里也可以拿来讲面试官通常会认可你真的做过完整的模型诊断。6. 从一元到多元处理真实数据的完整流程6.1 准备数据集多特征怎么办真实项目几乎不会只有一个特征。比如预测房价你手上可能有面积、房龄、楼层、朝向、周边配套等十几个特征。多元线性回归的sklearn代码和一元完全一样只是输入X的维度变宽了。import pandas as pd # 构造一份模拟的房价数据三个特征 np.random.seed(100) n 200 area np.random.normal(100, 20, n) # 面积单位平方米 age np.random.uniform(1, 30, n) # 房龄单位年 floor np.random.randint(1, 35, n) # 楼层 noise np.random.normal(0, 15, n) price 50 3.2 * area - 1.5 * age 0.8 * floor noise # 组装成DataFrame便于查看数据 df pd.DataFrame({ area: area, age: age, floor: floor, price: price }) print(df.head())这里我们预设了真实的关系面积每增加1平方米价格涨3.2万房龄每增加1年价格降1.5万楼层每高1层价格涨0.8万。噪声让数据呈现真实的波动然后让多元线性回归去还原这些系数。6.2 特征缩放为什么需要标准化在sklearn里跑多元线性回归之前有一个步骤经常被新手忽略——特征缩放。如果不同特征的取值范围差得太大比如面积在100上下房龄在1到30之间楼层在1到35之间梯度下降类算法的收敛速度会受影响。好在LinearRegression的fit底层用了最小二乘法而不是梯度下降所以不标准化也能出结果。但如果换成Ridge、Lasso这些带正则化的线性模型或者你自己手写梯度下降标准化就是必须的了。标准化的做法用sklearn的StandardScaler就能搞定from sklearn.preprocessing import StandardScaler features [area, age, floor] X_multi df[features].values y_multi df[price].values # 标准化后再训练 scaler StandardScaler() X_multi_scaled scaler.fit_transform(X_multi) model_multi LinearRegression() model_multi.fit(X_multi_scaled, y_multi) print(系数:, model_multi.coef_) print(截距:, model_multi.intercept_) print(R²:, model_multi.score(X_multi_scaled, y_multi))有个细节需要特别说明标准化之后得到的系数不再是面积每增加1平方米价格涨多少这种原量纲的解释而是面积每增加1个标准差价格涨多少。如果你想向业务方解释模型应该用没标准化的系数如果你更在意模型训练的稳定性和收敛速度那就用标准化方案。6.3 特征选择的必要性多元回归里有个让人头疼的问题——多重共线性。简单说如果两个特征高度相关比如面积和卧室数量模型就很难分清到底是谁在影响价格导致某个系数的标准误变大甚至出现系数符号违背常理的情况比如面积系数变成负数。排查多重共线性的一个常用指标是方差膨胀因子VIFsklearn里没有现成函数但statsmodels提供了from statsmodels.stats.outliers_influence import variance_inflation_factor # 计算每个特征的VIF一般超过10就认为存在严重共线性 vif_data pd.DataFrame({ feature: features, VIF: [variance_inflation_factor(X_multi_scaled, i) for i in range(X_multi_scaled.shape[1])] }) print(vif_data)遇到高VIF的特征通常的做法是删除其中一个相关性较高的特征或者用主成分分析PCA降维后再训练。我个人的经验是先用业务直觉筛选特征再用数据方法验证完全丢给算法做特征选择往往会导致模型难以解释。6.4 多项式回归线性解决不了的问题怎么办如果残差图显示数据呈明显曲线关系最简单的升级方案是多项式回归——给特征加平方项、立方项把原来的直线变成曲线。from sklearn.preprocessing import PolynomialFeatures # 生成包括x和x平方、x立方的特征 poly PolynomialFeatures(degree3, include_biasFalse) X_poly poly.fit_transform(X_2d) model_poly LinearRegression() model_poly.fit(X_poly, y) print(多项式模型的R²:, model_poly.score(X_poly, y))多项式回归本质上还是线性回归——它对系数来说是线性的只是特征里包含了幂次项。不过多项式回归很容易过拟合degree设置得过大模型会拼命弯折去贴合训练数据的每一个点泛化能力反而变差。我建议从小到大逐步试degree配合交叉验证来选择最优阶数不要一上来就degree10。7. 常见问题与排查技巧实录7.1 问题一报错Expected 2D array, got 1D array instead这个问题几乎每个从NumPy转sklearn的人都会遇到。原因是sklearn规定特征必须是二维的而你传入了一维数组。解决方案把一维数组reshape成二维或者用X[:, np.newaxis]增加一个维度。X_fix X.reshape(-1, 1)7.2 问题二R²是负数是怎么回事很多新手看到R²是负的就以为代码写错了。实际上当你的模型比直接用均值预测还差的时候R²就会变成负数。这在数据本身没有线性关系、或者模型严重欠拟合的情况下很常见。排查思路是先画散点图观察数据形态如果根本不是直线关系就换模型。7.3 问题三numpy的广播维度对不上手写最小二乘法或梯度下降时np.sum(error * X)如果维度不匹配会直接报错。我的经验是写代码前先用print确认X和y的形状然后用注释在代码里标出每个变量预期的shape发现问题能快很多。7.4 问题四梯度下降loss震荡不收敛这基本是学习率设太大的典型表现。可以尝试把学习率调小10倍再跑比如从0.01降到0.001观察loss是不是平稳下降了。如果调小之后还是震荡检查一下数据是否做了标准化——未标准化的数据在高维空间里梯度方向会很扭曲标准化后收敛会顺畅很多。7.5 问题五模型泛化能力差训练集和测试集分数差太大最可能的原因是数据量太少或者特征过拟合了。处理思路一是增加训练数据量二是减少特征数量三是引入正则化sklearn里的Ridge或Lasso。Ridge通过在损失函数里加L2惩罚项防止权重过大Lasso则使用L1惩罚项它还能自动把一部分特征的权重压缩到0起到特征选择的作用。8. 我的几个小经验和后续可以怎么玩8.1 我踩过的坑和你未必知道的小技巧先说一个最不起眼但让我印象深刻的事。有一回我在处理一份真实的销售数据文件里有几十万行还带空值。我图省事直接dropna()把有缺失的行删了结果训练出来的模型效果特别差。后来排查发现缺失行不均匀地分布在数据里相当于我指导模型忽略了一批特定场景的样本。从那以后我做特征工程之前一定会先看缺失值分布而不是直接删。再分享一个小技巧训练线性回归之前先算一下特征和标签的皮尔逊相关系数。sklearn和pandas都支持一行代码查看相关系数高的特征大概率是重要特征相关系数接近0的特征基本可以提前考虑删掉。这一步花不到一分钟但能帮你避免很多无效尝试。最后处理数据时我习惯先画散点图矩阵。pandas里一行pd.plotting.scatter_matrix(df, figsize(12, 12))就能同时看到所有特征两两之间的关系是不是线性关系、有没有异常点一眼就能看出来。视觉检查永远是第一步数值计算放后面。8.2 线性回归学完之后下一步可以玩什么如果你已经掌握了线性回归接下来有几个很自然的方向可以继续深入。第一个方向是正则化。把LinearRegression换成Ridge和Lasso看看在特征很多、数据很吵的情况下正则化如何压住过拟合。这个过渡非常平滑代码几乎不用改。第二个方向是逻辑回归。当你的预测目标不再是连续数值而是分类标签比如判断用户会不会流失线性回归就派不上用场了逻辑回归是它的概率版天然延伸。由于你已经懂线性回归了学逻辑回归会发现大部分概念都是相通的。第三个方向是往机器学习全流程走。把线性回归当成第一个站点后面学习数据清洗、特征工程、交叉验证、模型解释逐步搭起一套自己的建模流程。我自己的学习路径就是线性回归→逻辑回归→决策树→随机森林→XGBoost每走一步都在回头温习线性回归里那些基本功收益非常大。按照我的经验一行一行手敲一遍本文的代码在你能不看答案独立写出完整的训练、评估、诊断流程之前不用着急学下一个算法。基础越扎实后面走得越稳。等你真的把这个流程跑熟了你就有能力处理绝大部分给两组数据找规律的现实问题了。