我入行时做的第一个真正意义上的机器学习模型,不是神经网络,而是线性回归。后来带新人,我也总是先让他们把线性回归从头撸一遍。不是因为简单,而是因为它把机器学习的核心逻辑全串在了一起:数据怎么处理、模型怎么定义、误差怎么量化、参数怎么更新、结果怎么评估。这篇文章就把这一整套流程手把手讲清楚,从环境安装开始,到手写梯度下降加深理解,再落到用 scikit-learn 跑一个完整的房价预测案例,最后把那些能折腾人一下午的坑提前给你指出来。
不管你现在的 Python 基础是刚能打印“Hello World”,还是已经能熟练操作 DataFrame,都可以跟着这篇走一遍。我先给你一个整体路线图:先搞懂线性回归在解决什么问题,再准备好环境,接着手写一个最小实现,然后换成工业级实现的 sklearn 版本,最后做模型诊断和优化。看完之后,你不仅能跑通代码,还能回答“为什么这么写”这个问题。
1. 线性回归到底在做什么:一个既能预测又能解释的模型
1.1 从“猜房价”说起
假设你手里有一批房屋交易记录,每套房子的面积、卧室数量、地理位置等信息都记在一行数据里,还有一个你想预测的目标——成交价格。线性回归的基本假设非常朴素:目标值近似等于各个特征的加权和,再加一个偏置。
写成数学形式就是:
y = w1·x1 + w2·x2 + ... + wd·xd + b
其中 x1 到 xd 是特征(面积、卧室数、房龄等),w1 到 wd 是每个特征的权重,b 是截距。训练模型的过程,本质就是找到一组让“预测值尽可能接近真实值”的 (w, b)。
为什么这个简单的模型值得认真学?因为它同时具备两个能力:预测和解释。拿到一组新特征,把值代进去就能算出预测价格,这是预测能力;看一眼权重系数,就知道“卧室数量每增加一个,房价大约变化多少钱”,这是解释能力。后面你接触更复杂的模型,解释能力会越来越弱,但线性回归始终是可解释性最强的模型之一,很多风控、金融场景到现在仍然在用它做基线。
1.2 怎么衡量“预测得好不好”:损失函数
现在问题来了:给定一组权重,怎么量化它到底准不准?最常用的方式是均方误差(Mean Squared Error, MSE):
MSE = (1/n) Σ (y_i - ŷ_i)²
这里 y_i 是第 i 个样本的真实值,ŷ_i 是模型给出的预测值。为什么用平方而不是绝对值?有两点考虑。第一,平方让正负误差不会相互抵消,一个有正有负的“平均误差为 0”完全可能是垃圾模型。第二,平方放大了大误差的惩罚力度,同样是差 2 和差 4,平方后后者被惩罚四倍而不是两倍,这会让优化过程更警惕大偏差。
你可能听说过最小二乘法这个名字,所谓“最小二乘”指的就是最小化误差的平方和。在只有一两个特征时,你可以直接根据微积分求出最优权重的闭式解,这条路径也叫正规方程。不过当特征数量很多、样本量很大的时候,直接求矩阵逆的计算成本很高,而且矩阵可能是奇异的,没法求逆。于是就有了另一条更通用的路:梯度下降。
1.3 为什么梯度下降是核心
梯度下降的思路特别像“大雾天在山里摸黑下山”。你不知道整座山的地形,只能感知到脚下哪里最陡、往哪个方向走能最快降低海拔。损失函数就是这座山的“海拔”,模型参数 w 和 b 就是你的水平坐标。每次计算损失对参数的导数,沿着导数方向的反方向(也就是最陡下降的方向)迈一小步,反复迭代,直到走进山谷。
这一步迈多大,就是那个著名的“学习率”。学习率太大,可能一脚踩空,直接越过谷底冲到对面的山坡上去;学习率太小,就得挪无数步才能到达谷底。这个直觉你会在第 3 节亲手写代码时体会得特别深。
1.4 拿什么评估模型:R² 分数
训练完了,模型到底好不好?不能光看损失函数值,因为它受数据尺度影响很大。业界更常看 R²(R-squared,决定系数),它回答的是“我的模型比直接拿均值当预测值,好多少”。公式是:
R² = 1 - SS_res / SS_tot
SS_res 是残差平方和,即真实值减预测值的平方和;SS_tot 是总平方和,即真实值减真实值均值的平方和。R² 等于 1 说明模型完美拟合所有点;等于 0 说明模型跟“无脑用均值预测”效果一样的;如果出现负数,说明模型比均值预测还差,基本可以判定是在乱来。实际项目里 R² 能到 0.6 到 0.8 已经算很能打了,不要盲目追求接近 1,那往往是过拟合的信号。
2. 先从环境坑里爬出来:Python、编辑器与依赖库的安装细节
很多教程默认你已经装好了环境,但现实中大量读者卡在第一步。据我观察,微信群和论坛上问得最多的一句话就是“python 不是内部或外部命令”,这基本可以锁定是安装时没勾选 PATH 选项。这里我把环境步骤拆细,顺手把这些高频问题一并解决。
2.1 Python 本体:下载与 PATH
去 python.org 的 Download 页面找到你系统对应的安装包。Windows 用户在安装向导第一屏时,切记勾选最底下的Add python.exe to PATH。这一步至关重要,它决定了你在命令行里敲python时系统能不能找到它。
安装完成后,打开一个新的命令行窗口(旧的窗口不会自动获取新环境变量,所以一定要新开),输入:
python --version如果显示类似Python 3.12.4的输出,说明安装成功。如果跳出了 Microsoft Store 的下载页面,或者提示“python was not found”,大概率就是 PATH 没勾上。重新运行安装程序,选择 Modify,勾选 Add Python to PATH 即可。
顺手说一下,Linux 或 macOS 用户一般系统自带 Python 3,不过为了版本可控,建议用系统包管理器装一个最新的 3.x,或者直接上 Miniconda。我不太建议新手一上来就装 Anaconda 全家桶,体积太大,很多包根本用不上,而且 conda 和 pip 混着装容易把环境弄乱。用 Miniconda 或者直接原生 Python + venv 就够了。
2.2 编辑器:VS Code 还是 Jupyter
编辑器我推荐 VS Code,配合官方 Python 插件,调试体验相当顺滑。装好 VS Code 后,在扩展市场搜“Python”,装那个由微软发布的、下载量最高的插件就行。
写算法代码做探索性分析,我更喜欢 Jupyter Notebook,因为它能一段一段地跑,画图也直接嵌在页面里,非常适合学习阶段。用 VS Code 打开一个.ipynb文件就能编辑运行,不需要额外安装 Jupyter 桌面版。两条路不冲突,入口都是 VS Code,你以后上手会很快。
还有一个特别容易被忽略的问题:安装了多个 Python 版本时,VS Code 右下角会显示当前解释器。一定要确认当前选中的是你刚装好的那个环境,否则会出现“终端能 import,但编辑器里 import 报错”的灵异现象。处理办法是Ctrl+Shift+P,输入 “Python: Select Interpreter”,手动选一下。
2.3 依赖库:一行命令装齐
线性回归会用到四个库:NumPy 做数值计算,pandas 处理表格数据,matplotlib 画图,scikit-learn 提供成熟的机器学习模型和数据处理工具。一次装齐:
pip install numpy pandas matplotlib scikit-learn如果你在国内,默认 PyPI 源可能慢得让人抓狂,建议直接指定清华镜像:
pip install numpy pandas matplotlib scikit-learn -i https://pypi.tuna.tsinghua.edu.cn/simple验证安装是否成功,命令行执行:
python -c "import numpy, pandas, matplotlib, sklearn; print('deps ok')"能输出deps ok,环境就彻底没问题了。我写这篇文章时用的版本是 Python 3.11、NumPy 1.26、pandas 2.2、scikit-learn 1.5,代码兼容性主要考虑这一代版本。如果你用的是老版本 sklearn,个别 API 可能会有点出入,遇到报错时优先看报错信息的最后几行,Google 一下错误原文通常五分钟能解决。
3. 手写梯度下降:把原理吃到肚子里
我知道你是冲着“用 Python 实现线性回归”来的,肯定手痒想直接调 sklearn。但先别急,我强烈建议你亲手写一遍梯度下降。原因很简单:当你以后用 PyTorch 之类的框架时,会看到一个叫optimizer.step()的调用,它背后干的事就是w -= lr * grad_w。现在把这个动作刻进脑子里,将来学习任何深度学习模型都会轻松很多。
3.1 构造一份带噪声的测试数据
手写模型的目的是观察原理,数据用简单的合成数据最合适。这里我生成 100 个样本点,真实关系设定为 y = 4 + 3x,再叠加一个服从正态分布的随机噪声:
import numpy as np np.random.seed(42) X = np.random.rand(100, 1) * 5 # 特征,范围 0~5 true_w, true_b = 3.0, 4.0 y = true_w * X + true_b + np.random.randn(100, 1) * 2 # 目标值,加了噪声np.random.seed(42)是为了让随机结果可复现。否则每次运行拿到的数据都不一样,你调参时很难判断改善是来自参数还是运气。这是一个值得养成的习惯:任何实验性代码,第一行先固定随机种子。
如果你现在就把数据点画出来,会看到一条明显的正向趋势带。接下来我们要做的事,就是从这堆点中反推出 w 约等于 3、b 约等于 4。注意,因为有噪声,我们不可能精确地推出 3 和 4,能逼近就已经是很好的结果,这也呼应了前面讲的“拟合而非考古”的理念。
3.2 核心代码与梯度推导
要更新参数,必须知道损失函数对参数的导数方向。这里还是用 MSE 作为损失:
L = (1/n) Σ (y_i - ŷ_i)²,其中 ŷ_i = w·x_i + b
对 w 求偏导和对 b 求偏导的过程如下:
∂L/∂w = (2/n) Σ (ŷ_i - y_i) · x_i
∂L/∂b = (2/n) Σ (ŷ_i - y_i)
这个结果不用死记,它有一条很直观的解释:预测偏高了(ŷ_i - y_i > 0),梯度为正,更新时w -= lr * grad会让 w 减小,从而压低预测值;预测偏低了,梯度为负,w 向相反方向增大。整个过程像是一个始终在自我纠错的闭环。
写成代码就是:
w = np.random.randn(1, 1) # 初始化权重 b = 0.0 # 初始化偏置 lr = 0.1 # 学习率 epochs = 100 N = len(y) losses = [] for epoch in range(epochs): y_pred = X @ w + b # 1. 前向计算:预测值 loss = np.mean((y_pred - y) ** 2) # 2. 计算损失 losses.append(loss) grad_w = 2 * X.T @ (y_pred - y) / N # 3. 计算梯度 grad_b = 2 * np.sum(y_pred - y) / N w -= lr * grad_w # 4. 更新参数 b -= lr * grad_b if epoch % 10 == 0: print(f"epoch {epoch}, loss {loss:.4f}, w {w.ravel()[0]:.4f}, b {b:.4f}")这里我特意用X.T @ (y_pred - y)而不是逐元素乘法,好处是将来特征不止一个时,这段代码依然成立,梯度计算会自动把所有样本、所有特征的贡献累加起来。
3.3 学习率与特征缩放:两个最容易翻车的点
你先用 lr = 0.1 跑一遍,全程 100 轮,loss 会从初始的某个值一路下降,最终 w 和 b 会稳定在 3 附近和 4 附近。接下来做两个实验。
第一个实验,把 lr 改成 0.8。你会看到 loss 不降反升,甚至直接输出nan。这就是“下山步子迈太大,滚到了山沟另一边”的直观演示。处理办法没有银弹,只能靠观察 loss 曲线去调:如果曲线一开始下降很快但很快震荡,就把学习率调小一个量级;如果下降得像蜗牛爬,就适当调大。我的习惯是先 0.1,跑 50 轮,看曲线形态,再用 0.01 和 0.3 各跑一次,对比收敛速度。
第二个实验,把特征范围从 0~5 改成 0~5000。你会立刻发现 loss 下降变得异常困难,甚至几乎不动。原因是特征尺度过大导致梯度绝对值非常大,更新出现震荡。实际项目里各个特征的量纲往往差异巨大,一列是单价几千元,另一列是数量个位数,这时强烈建议做标准化,也就是 z-score 变换:
mu, sigma = X.mean(), X.std() X_scaled = (X - mu) / sigma标准化后特征均值归 0、标准差归 1,梯度下降会稳得多。一句经验之谈:线性回归和梯度下降是天然的好朋友,但它的收敛效率极度依赖特征尺度。后面用 sklearn 时我们会用StandardScaler处理,原理和这里手动写的一模一样。
跑完代码后,顺手画一张 loss 曲线:
import matplotlib.pyplot as plt plt.plot(losses) plt.xlabel("Epoch") plt.ylabel("MSE Loss") plt.title("Training Loss Curve") plt.show()正常情况你会看到一条陡降后趋平的曲线,像滑雪时先俯冲再进入平缓谷底。如果曲线像心电图一样来回跳,参考上面学习率的思路去调。
4. 用 scikit-learn 跑通一个完整的房价回归案例
手写梯度下降能帮你理解原理,但进入真实项目你就会发现,自己写优化器纯属重复造轮子。scikit-learn 里的LinearRegression经过高度优化,底层用的是最小二乘的闭式解或矩阵分解,速度和稳定性都远非手写可比。这一节我们直接上真实数据集。
4.1 关于波士顿房价数据集,先泼一盆冷水
很多教程还在用波士顿房价(Boston Housing)数据,但它已经从新版 scikit-learn 中移除了。原因有两个:一是当年采集数据时部分特征涉及敏感信息,存在公平性的争议,不适合继续当教学示范;二是数据存在些历史遗留问题。如果你在旧教程里看到load_boston(),在新版本 sklearn 里直接运行会报AttributeError。
想复现老教程,可以这样加载:
from sklearn.datasets import fetch_openml boston = fetch_openml(name="boston", as_frame=True, parser="pandas") df = boston.frame但我个人更推荐改用加州房价数据(California Housing),它内置在 sklearn 里,数据同样适合回归演示:
from sklearn.datasets import fetch_california_housing housing = fetch_california_housing(as_frame=True) df = housing.frame这个数据集的预测目标是加州街区的房价中位数,有 8 个特征,一共 20640 个样本,规模比波士顿大得多,拿来做回归练习的体验更好。下面所有代码都用这个数据集。
4.2 数据处理与训练的核心流程
标准流程分为五步:读数据、切分、标准化、训练、评估。
import pandas as pd import numpy as np from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score, mean_squared_error # 1. 加载数据 housing = fetch_california_housing(as_frame=True) df = housing.frame # 2. 切分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( df.drop(columns=["MedHouseVal"]), df["MedHouseVal"], test_size=0.2, random_state=42 ) # 3. 标准化的正确打开方式:和模型一起打包进 Pipeline pipe = make_pipeline(StandardScaler(), LinearRegression()) # 4. 训练 pipe.fit(X_train, y_train) # 5. 评估 y_pred = pipe.predict(X_test) print(f"R2: {r2_score(y_test, y_pred):.3f}") print(f"RMSE: {np.sqrt(mean_squared_error(y_test, y_pred)):.3f}")这段代码里有几个细节很关键。第一,train_test_split必须放在标准化之前。如果你先对整个数据集做StandardScaler().fit_transform(X)再切分,标准化过程会“偷看”测试集的均值方差,这就是数据泄漏,会让测试集指标虚高。正确的做法是只对训练集 fit,得到均值方差后再用它去 transform 测试集。用Pipeline可以自动保证这一点:pipe.fit(X_train, y_train)执行时,内部只会用训练集的数据计算均值和标准差。
第二,数据里如果有缺失值,sklearn 的线性回归会直接报错。训练之前先看一眼df.isna().sum(),有缺失就按情况填充或删除,不要直接塞给模型。
第三,评估指标选 RMSE 而不是 MSE 的原因,是 RMSE 和目标变量的量纲一致。加州房价中位数以十万美元计,MSE 是它的平方,数字大得没直觉,开个根号就好理解多了。
4.3 模型可解释性:把系数拿出来看看
LinearRegression的一个巨大优势是训练出来的权重可以直接读出业务含义。我们可以看一下每个特征对房价的影响方向:
coefs = pd.Series( pipe.named_steps["linearregression"].coef_, index=housing.feature_names ).sort_values(key=np.abs, ascending=False) print(coefs)你会发现MedInc(该街区收入中位数)的系数最大且为正,说明收入越高的街区,房价中位数越高,这符合常识;Latitude(纬度)的系数通常为负,说明在某些区域模型学到了“越往某个方向,房价越低”的空间规律。
需要提醒的是,这里解释的是“在线性假设下,其他条件不变时的边际效应”。多重共线性存在时,系数会变得不稳定甚至符号反直觉,这一点在第 5 节的正则化部分会再提。
4.4 可视化:预测值 vs 真实值
画一张散点图,把测试集的真实房价放在横轴、模型预测值放在纵轴。如果模型表现好,点会密集分布在对角线 y = x 附近:
import matplotlib.pyplot as plt plt.scatter(y_test, y_pred, alpha=0.4, s=10) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], "r--") plt.xlabel("True Median House Price") plt.ylabel("Predicted Median House Price") plt.show()我跑出来的结果,R² 大约是 0.6,RMSE 在 0.7 左右。说实话这个精度相当一般,因为加州房价数据里存在明显的非线性关系和空间效应,纯线性模型已经接近它的能力上限。看到这个结果不用灰心,这恰恰说明应该进入下一步:诊断和优化。
5. 回归模型诊断与优化:从“能跑”到“靠谱”
很多人跑通模型后最常犯的错误,是只看测试集指标高就不管了。我见过太多训练 R² 到了 0.95、测试 R² 却只有 0.3 的案例,这就是过拟合的典型表现。一个合格的建模流程,至少要有诊断环节。
5.1 残差图:一眼看出模型漏掉了什么
所谓残差,就是真实值减去预测值。把残差画在纵轴、预测值画在横轴,靠谱的模型应该呈现“一堆随机散落在 0 线附近的点”。四种典型的不正常形态分别意味着不同问题:
| 残差图形态 | 含义 | 可行对策 |
|---|---|---|
| 有明显弯曲(U 型/倒 U 型) | 漏掉了非线性关系 | 加入多项式特征或换成非线性模型 |
| 残差随预测值增大而发散(扇形) | 异方差,误差不恒定 | 对目标变量做 log 变换或加权最小二乘 |
| 残差在一侧聚集、另一侧稀疏 | 模型系统性高估或低估 | 检查是否缺失关键特征 |
| 残差呈现明显周期性 | 可能遗漏时间等结构化信息 | 加入时间相关特征或改用时序模型 |
诊断代码很简单:
residuals = y_test - y_pred plt.scatter(y_pred, residuals, alpha=0.4, s=10) plt.axhline(0, color="red", linestyle="--") plt.xlabel("Predicted Value") plt.ylabel("Residuals") plt.show()画完如果发现明显的弯曲,接下来可以试着用PolynomialFeatures把特征升到二阶:
from sklearn.preprocessing import PolynomialFeatures from sklearn.pipeline import Pipeline pipe_poly = make_pipeline( StandardScaler(), PolynomialFeatures(degree=2, include_bias=False), LinearRegression() ) pipe_poly.fit(X_train, y_train)二阶多项式会把 x1²、x2²、x1·x2 这些组合都加入模型。注意,千万别把 degree 调太高。degree=3 可能还有救,degree=10 基本就是过拟合收割机,训练 R² 冲到 0.99,测试集却惨不忍睹。要判断是不是过拟合,最方便的方式是看训练集和测试集的指标差距:如果训练 R² 比测试 R² 高出一大截,就是在背答案而不是在学规律。
5.2 交叉验证:别让一次随机切分决定命运
train_test_split只有一次切分,结果受随机种子影响很大。更稳妥的做法是 K 折交叉验证:把数据分成 K 份,轮流拿其中 K-1 份做训练、剩下 1 份做验证,最后取平均。sklearn 一行搞定:
from sklearn.model_selection import cross_val_score scores = cross_val_score(pipe, df.drop(columns=["MedHouseVal"]), df["MedHouseVal"], cv=5, scoring="r2") print(scores) print(f"mean: {scores.mean():.3f}, std: {scores.std():.3f}")注意,我把pipe直接传进去了,它内部会保证每个折里都独立做标准化,不会发生数据泄漏。如果 5 折分数的均值高但标准差也高,说明模型对数据切分敏感,数据本身的分布可能不够稳定。
5.3 正则化:给系数“上紧箍咒”
线性回归在特征很多或特征高度相关时,会出现系数很大但相互抵消的情况,模型方差极高。解决办法是给损失函数加上正则化惩罚项:
- L2 正则化(Ridge):损失项加 λ·Σw_i²,强制所有系数整体变小,适合特征相关性强的情况。
- L1 正则化(Lasso):损失项加 λ·Σ|w_i|,会让一部分系数被精确压缩到 0,相当于自动特征选择。
如果你发现变量相关性很强,或者系数解释性很差,优先试 Ridge。如果特征非常多、怀疑大量特征没意义,优先试 Lasso。sklearn 里分别对应Ridge和Lasso,用法和LinearRegression完全一致,也能塞进 Pipeline。实际写项目时,我常把RidgeCV或LassoCV放进管道,让它在交叉验证里自动挑一个合理的 λ,省去手动调参的功夫。
6. 实战中最容易踩的五个数据坑
最后这部分,是我帮别人调 bug 时最常遇到的坑。每一个我都亲自踩过,或者说亲眼看着身边的人踩过,列出来给你排雷。
6.1 数据泄漏:标准化时机不对
这是新手最隐蔽的坑。错误示范是先把整个 X 放进StandardScaler().fit_transform(),然后再切分训练测试集。这样标准化时用了全量数据的均值和方差,这部分“知识”就渗进了测试信息。最终测试集指标会虚高,上线后模型实际表现断崖式下降。
正确顺序始终是:先切分,再单独在训练集上 fit、在测试集上 transform。如果你嫌麻烦,就用Pipeline自动封装。这句话我再说一遍:任何需要在训练阶段计算的数据依赖(标准化、填充均值、降维、特征选择)都必须只碰训练数据。
6.2 把索引列当成了特征
从 CSV 读数据时,如果导入前没指定index_col,第一列很可能是一串 0 到 n 的序号。它把一个“行号”当成真实特征变成 X 的一列,模型会尝试学出“行号越高房价越高”这种毫无意义的规律。读数据时习惯性检查df.head()和前几列的 dtype,一旦发现第一列是整型且是从 0 开始递增的那种,基本就是索引。
6.3 对时间序列数据乱 shuffle
train_test_split默认会随机打乱数据。对普通业务数据没问题,但对股票、天气、销售这类时间序列数据,打乱后会直接造成未来信息泄漏。因为模型在训练时已经见过“未来”的点了,测试集里再出现“过去”的点,评估自然虚高。时间序列要做的是按时间顺序切分,比如前 80% 训练、后 20% 测试。sklearn 的时间序列切分可以手动切片,不用train_test_split。
6.4 手写梯度下降时出现 nan
这是第 3 节实验里学习率过大的结果,但真实项目里还有另一个来源:数据本身有 inf 或 NaN 没清理。模型在计算时,NaN 会像病毒一样沿矩阵运算传播,最终系数全变成 nan。正确做法是先df.replace([np.inf, -np.inf], np.nan),再根据业务决定填充策略。把“先清洗后建模”挂在嘴边,能省下很多深夜 debug 的时间。
6.5 反复看测试集调参,测试集慢慢失去了意义
最后一个坑很少有人意识到:如果你反复用同一份测试集评估不同参数,并且根据结果调整模型,你的大脑其实也在“训练”。你已经在无意识地记忆测试集特征了,最终整个流程跑完后,测试集指标不再能代表真实泛化能力。更专业的做法是划分出训练集、验证集、测试集三层:训练集学参数,验证集调超参数,测试集只许用一次。小数据集上可以用嵌套交叉验证实现,但至少你要知道“反复看测试集”这个行为本身是有问题的。
写在最后的实战心得
按我的习惯,一个完整的线性回归建模流程跑完,代码可能只有几十行,但背后的决策链条很长:画图看分布、处理缺失值、切分数据、标准化、建模、评估、画残差图、再决定要不要加多项式或正则化。这套流程你在第一遍可能觉得繁琐,但相信我,它是所有机器学习项目都通用的粗鲁骨架。
再送一个小技巧:每拿到一份数据,第一件事永远是df.describe()与df.info(),把特征类型、缺失值、均值方差扫一遍。这两个方法虽然基础,但能帮你避开太多“看起来莫名其妙的 bug”。等这一套线性回归的流程真正跑通了,你再去学逻辑回归、决策树、神经网络,会发现很多环节都是相通的——理解线性回归,就是在为你未来所有的机器学习旅程打地基。