news 2026/10/15 21:03:38

机器学习入门实践:线性回归模型实现波士顿房价预测全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
机器学习入门实践:线性回归模型实现波士顿房价预测全流程

如果你想自学机器学习,又不知道从哪个项目下手,我劝你别一上来就碰深度学习、图像识别那些花活儿。先把最简单的线性回归模型,在一份公开数据集上完整跑通一遍,比看十遍理论都有用。今天要拆的这个项目——机器学习入门实践:线性回归模型(波士顿房价预测),就是很多人公认的“机器学习第一站”,我前后带过不少新人做这个练习,每次都能聊出点新东西,今天干脆把它掰开揉碎讲一遍。

这个项目要解决的事情很朴素:给你一堆房子的历史数据,比如房间数量、犯罪率、到市中心的距离、房龄等等,然后让你预测这栋房子的房价。这里面的核心模型是线性回归,它既是统计学里的老朋友,也是机器学习里最基础的模型之一。适合谁呢?刚看完机器学习课程理论、想动手敲代码的同学,或者已经会用Python但对“训练、评估、调参”没有完整感知的初学者。项目本身门槛低、见效快,跑完你会对机器学习流程有一个特别清晰的骨架。下面我按自己做项目时的思路,从设计、原理、实操、避坑四个部分展开。

1. 项目整体设计与思路拆解

1.1 为什么机器学习入门第一课都是线性回归

很多人觉得线性回归太简单,不值得花时间。恰恰相反,正因为简单,它才是最好的“教学载体”。线性回归的本质是“特征加权求和”,你理解了它,后续学的逻辑回归、支持向量机、神经网络,本质上都是在“加权求和”外面套了不同的壳。拿生活里的例子打比方:你想估算一套房子的租金,脑子里自然会想——面积大一点租金高一点,离地铁近一点租金高一点,楼龄老一点租金低一点。这个过程就是线性回归在做的:给每个因素分配一个权重,然后把所有因素的贡献加起来,得到最终预测值。

这个项目的另一个价值在于“完整流程”的体验。从加载数据、清洗数据、特征分析,到划分训练集测试集、训练模型、评估指标、再迭代优化,这整条流水线在深度学习项目里会被各种细节掩盖,但在线性回归项目里特别清晰。波士顿房价预测的经典之处就在这里:数据量不大、特征维数适中、任务直观,新手不会因为数据太大跑不动,也不会因为任务太抽象而迷茫。

1.2 波士顿房价数据集全解析:13个特征与1个目标

波士顿房价数据集包含506个样本,每个样本是一块区域的房屋信息,一共14列。其中13列是特征,1列是目标(MEDV),即该地区自住房屋房价的中位数,单位是千美元。比如MEDV等于24,意味着该地区有一半的房子价格在24000美元以下——注意这是几十年前的数据,价格水平别拿今天的行情去对标。

这13个特征分别是:CRIM(城镇人均犯罪率)、ZN(占地面积超过25000平方英尺的住宅用地比例)、INDUS(非零售商业用地比例)、CHAS(是否邻近查尔斯河)、NOX(一氧化氮浓度)、RM(平均房间数)、AGE(建于1940年前的自住单位比例)、DIS(到五大就业中心的加权距离)、RAD(到环状高速公路的便利指数)、TAX(每10000美元房产税)、PTRATIO(师生比例)、B(黑人比例相关的统计量)、LSTAT(低收入人群比例)。每个特征都有自己的取值范围和分布形状,比如RM基本在3到9之间,LSTAT从1%到38%不等,CHAS则是0/1的二值变量。

说句实在话,这个数据集虽然经典,但它采集于上世纪70年代,部分特征的设计放到今天来看,确实存在一些争议,所以近几年主流机器学习库已经不再把它默认打包进安装包里了。我理解那些争议,但从教学角度说,它的数值形态、特征间的相关性、与目标变量的关系,都非常适合拿来理解回归模型。如果你遇到代码里直接load_boston()报错,不要慌,用本地CSV文件方式同样能复现,后面实操部分我会给出具体方案。

1.3 项目方案选型:手写梯度下降还是直接调库

做这个项目时,很多人问我:直接用sklearn一行代码做线性回归不就行了,为什么要费劲去写梯度下降?我的答案是:两个都要做。手写梯度下降能让你理解模型在“学习”什么,调库能让你体会工程上的高效与规范。先手写再调库,顺序不要反。

手写版我一般控制在五六十行以内,核心就是三件事:前向计算预测值、计算损失、沿梯度方向更新参数。调库版本用LinearRegression一行训练、一行预测,几行代码就能出指标。两者对比之后你会发现,手写版跑出来的结果和库版本差不多,但你对“权重到底怎么更新”“学习率调大了会发生什么”有了切肤之感。另外我还建议把交叉验证也加上,训练集再从中切出一部分作验证,而不是只在固定的一次训练测试集划分上看结果,这一点对后续理解模型稳定性非常有帮助。

2. 线性回归的原理,从公式到损失函数

2.1 线性回归的数学本质:特征加权求和

线性回归的数学形式,最初是大家都会的一元一次方程:

y = ax + b

一个特征x时就是一条直线,两个特征时是一个平面,超过三个特征人就想象不出来了,只能靠矩阵。样本有13个特征,模型就是:

y_pred = w1x1 + w2x2 + ... + w13*x13 + b

用矩阵写更干净:

y_pred = X @ w + b,其中X是样本矩阵,w是权重向量,b是偏置。写代码时,我习惯把b并到w里,给X加一列全1,这样整条公式就简化成y_pred = X @ theta,代码更少,循环更清晰。

每个权重w_i的含义值得说透:在其他特征固定不变的情况下,该特征每增加一个单位,预测值就变化w_i个单位。比如RM(房间数)对应的权重如果接近3,就表示房间数每多1间,预测房价大约多3000美元。这就是线性回归的可解释性,也是它在金融风控、医疗预测等领域仍然被大量使用的原因。

2.2 损失函数与优化:为什么是MSE,梯度下降怎么走

也许你已经知道,线性回归常用的损失函数是均方误差(MSE),它的公式是:

MSE = (1/m) * Σ(y_i - y_pred_i)^2

为什么不用绝对误差(MAE)?直观上,两者都衡量预测值和真实值的差距,但MSE对“差得远”的样本惩罚更狠。比如一个样本误差是2,另一个是10,平方之后分别是4和100,后者在损失里占据主导地位,模型会更用力去修正那个“离谱”的错误。MSE还有个好处是处处可导,求梯度方便。

有了损失函数,优化就是一个“下山”的过程。把损失函数看成一座山,我们要找到山脚的最低点。梯度就是最陡的方向,沿着反方向迈一步,就下降一点。这“一步”的大小就是学习率(lr)。学习率太大,一步跨过山脚,来回震荡甚至发散;学习率太小,半天走不到头。我手写梯度下降时经常把lr设为0.05到0.1,观察损失曲线:如果前几百轮稳定下降、最后平缓,说明这个学习率可用;如果损失爆成nan,先减小学习率再试。

这里有个细节:标准的线性回归其实有闭式解,即( XᵀX )⁻¹Xᵀy,一步算出来,不用迭代。那为什么还要梯度下降?因为闭式解涉及矩阵求逆,当特征数量到几千几万甚至更大时,求逆的计算量爆炸;梯度下降按批次迭代,成本可控,是深度学习时代的主流优化范式。入门阶段把梯度下降跑通,等于给后面学神经网络打地基。

2.3 评估指标:MAE、RMSE、R²怎么配合着看

训练完模型,怎么判断好坏?波士顿房价项目里,我一般同时看四个指标:MAE、MSE、RMSE、R²。

MAE(平均绝对误差)最直观:预测值和真实值差多少的绝对值,平均一下。比如MAE等于3,就意味着平均每个预测房价和真实房价差3000美元。MSE和RMSE本质一样,都是对较大误差更敏感的指标,RMSE开根号后单位跟MAE一致,方便比较。R²稍微抽象点,它衡量的是“模型比直接猜平均值好了多少”:R²等于0.9,可理解为模型解释了90%的波动;R²接近0,说明模型基本没学到东西,不如一律预测平均值。

必须提醒的是,单一指标都有盲区。R²高不代表模型处处都准,可能局部存在明显偏差;MAE低也可能被大量普通样本“平均”掉了,个别极端房价差得离谱。所以在项目里,我都是把几张指标放一起输出,再补一张预测值vs真实值的散点图,一句“R²是0.7多”永远不如一张图来得直观。

3. 完整实操流程:从数据到模型评估

3.1 环境准备与数据还原

先说一下环境。我建议你用Python 3.9以上版本,装上numpy、pandas、matplotlib、scikit-learn这四个库就够了。环境装好后,最大的坎是数据获取。由于前面提到新版工具库不内置波士顿房价数据,最省心的做法是直接用本地CSV文件,把它放在项目的data目录下,列名包含:CRIM、ZN、INDUS、CHAS、NOX、RM、AGE、DIS、RAD、TAX、PTRATIO、B、LSTAT、MEDV。如果你是从旧教程里看到load_boston()报错,只要换成pd.read_csv()读取本地文件即可。

我经历过一个特别常见的现场:学生照着网上教程抄代码,前面运行得好好的,到加载数据一步直接红字报错,然后整个人就卡住了。所以这个项目第一步反而是培养“遇到问题查文档、换方案”的能力。数据准备好后,用下面这段代码确认一下:

import pandas as pd df = pd.read_csv("data/boston.csv") print(df.shape) print(df.head())

输出会显示(506, 14)和一个数据表格,说明数据加载成功。如果没有输出,先检查CSV路径是否正确,再检查文件名是否完全一致。

3.2 数据探索与可视化

拿到数据别急着开训,先做探索性分析(EDA),这是很多新手跳过的关键步骤。我用df.info()查看缺失值和数据类型,再用df.describe()查看每列的均值、标准差、最小最大值,大致了解量纲差异。比如TAX是几百到几千的量级,RM是3到9的量级,CHAS是0和1。这种量纲不同,为后面的标准化提前做了铺垫。

可视化上,第一步是画目标变量MEDV的分布直方图,看看房价是不是正态分布。实际画出来会发现它有点“右偏”,尾部有一些高房价样本。第二步是画特征和房价的关系图,我重点看两个:RM(房间数)散点图,应该能看到明显的正相关;LSTAT(低收入人群比例)散点图,负相关。这两个特征对这个预测任务影响很大,后面算权重时系数也会比较大。

我还会顺手画一张相关性热力图,不仅看特征与MEDV的相关性,还要看特征与特征之间的相关性。你可能发现RAD和TAX的相关性极高,这意味着两个特征携带了相似的信息,同时放入模型可能导致系数不稳定。这些观察在后面做特征筛选时都用得上。

3.3 数据预处理与训练集测试集划分

准备工作两大步:划分数据和标准化。

先划分。用train_test_split把数据切成80%训练集、20%测试集,并设置random_state=42。42这个数字是很多示例代码里的“惯例种子”,目的是让结果可复现。如果你不设随机种子,每次运行切出来的数据都不一样,模型指标每次也都不一样,很难判断改代码是变好了还是随机波动。

再标准化。线性回归本身对量纲不敏感,算出来的权重照样有解释性,但是一旦你后面做正则化、做梯度下降,特征尺度不一致就会出问题。比如TAX数值到几百,RM数值只有个位数,梯度下降时更新方向会被大尺度特征主导,收敛变慢。用StandardScaler把每个特征变成均值0、标准差1的分布:

from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X = df.drop("MEDV", axis=1) y = df["MEDV"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)

注意一个关键动作:fit_transform只对训练集调用,测试集只用transform。原理很简单:fit操作会计算训练集每列的均值和标准差,如果测试集也fit_transform,相当于让模型“偷看”了测试集的分布信息,这在实际生产中等于作弊,会让评估结果虚高。这一条算是我带新项目时反复强调的“数据泄漏”问题,你在所有涉及预处理的机器学习流程里都要记得。

3.4 训练模型与结果评估

先手写一个梯度下降版,代码如下:

import numpy as np def train_pure_gd(X, y, lr=0.05, epochs=500): m, n = X.shape theta = np.zeros(n) loss_history = [] for _ in range(epochs): y_pred = X @ theta error = y_pred - y gradient = (X.T @ error) / m theta -= lr * gradient loss = np.mean((y - y_pred) ** 2) loss_history.append(loss) return theta, loss_history X_bias_train = np.c_[np.ones(X_train_scaled.shape[0]), X_train_scaled] X_bias_test = np.c_[np.ones(X_test_scaled.shape[0]), X_test_scaled] theta, loss_list = train_pure_gd(X_bias_train, y_train.values, lr=0.05, epochs=500)

跑完后,看一眼损失列表里最后一个值,再打印前50轮和后50轮的损失变化,你会发现前期下降快、后期趋缓。这是梯度下降收敛的正常形态。如果损失不减反增,基本就是学习率太大或者数据没标准化。

再上sklearn版本:

from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score model = LinearRegression() model.fit(X_train_scaled, y_train) y_pred = model.predict(X_test_scaled) mae = mean_absolute_error(y_test, y_pred) mse = mean_squared_error(y_test, y_pred) rmse = np.sqrt(mse) r2 = r2_score(y_test, y_pred) print(f"MAE: {mae:.3f}") print(f"RMSE: {rmse:.3f}") print(f"R²: {r2:.3f}")

在我随机种子42、20%测试集的划分下,运行结果大约是R²在0.65到0.75之间,RMSE在4.5到6之间。这个分数在波士顿数据上属于正常水平,不要指望一上来就训出R²=0.99,毕竟影响房价的因素远远不止这13个特征。接下来,我还会在训练集上做5折交叉验证:

from sklearn.model_selection import cross_val_score from sklearn.pipeline import make_pipeline pipe = make_pipeline(StandardScaler(), LinearRegression()) scores = cross_val_score(pipe, X_train, y_train, cv=5, scoring="r2") print(scores) print(f"平均 R²: {scores.mean():.3f},标准差: {scores.std():.3f}")

为什么用Pipeline?因为交叉验证时每一折都要重新做标准化,直接把标准化放在Pipeline里可以避免“每一折偷看全量训练集统计量”的问题。这个细节我一开始也忽略过,后来对比了一次,发现泄漏和非泄漏的交叉验证结果差了0.03左右,看似不多,但在严谨评估时是不能接受的。

4. 常见问题与优化方向

4.1 波士顿房价项目里必踩的坑

我先整理一个踩坑速查表,都是我见过或自己掉进去过的:

问题现象原因解决办法
load_boston()报错ImportError或加载失败新版工具库移除了该数据集改用CSV本地读取
结果每次都不一样每次运行指标浮动大没有固定随机种子train_test_split里设random_state
梯度下降损失变成nan损失列表出现空值学习率过大或数据未标准化先标准化,再调低学习率
测试集R²虚高验证效果好到可疑标准化时对测试集也做了fit测试集只用transform
权重系数很怪符号与常识相反特征间存在共线性看相关性矩阵,考虑删特征

其中“测试集数据泄漏”和“不固定随机种子”是新人最隐蔽的两个坑。前者只要记住“所有基于训练集的变换,测试集都不许重新fit”就能避开;后者则是一个习惯,设置随机种子不仅帮助复现,还能让你在调参时更公平地比较结果。

4.2 从基线模型继续优化:多项式、正则化与特征筛选

跑通基线之后,项目还可以往下走,我一般给新人三条路:多项式特征、正则化、特征筛选。

多项式特征是给线性模型增加非线性能力,比如把RM变成RM²、RM³等加入模型。我用PolynomialFeatures(degree=2)试过,训练集R²会明显提高,但测试集R²提升有限,甚至可能下降,这就是过拟合:模型把训练数据里的噪声都背下来了。此时用Ridge正则化能有效惩罚过大系数:

from sklearn.linear_model import Ridge ridge = Ridge(alpha=1.0) ridge.fit(X_train_scaled, y_train)

alpha越大,惩罚越重,模型越保守。我一般会写一个循环,遍历alpha从0.1到10,画一张R²随alpha变化的折线图,找一个平衡点。再用Lasso做特征选择——它能把不重要的特征系数直接压成0。跑完后打印lasso.coef_,你会发现某些列系数变0了,这比人工删特征更客观。不过Lasso的特性是随机选择相关性高的特征中的一个,所以结果不一定唯一,理解它的行为比纠结具体删了哪列更重要。

4.3 数据集的局限性与迁移思路

最后必须说清楚一件事:波士顿房价数据本身已经过时,用它训练出来的模型放在今天没有实际预测价值。它最大的价值是“练习品”。就像练字用字帖,字帖内容不是让你直接拿去做合同,而是让你练好笔画结构。理解了这一点,你会发现这套流程完全可以平移到任何回归任务上。

我在带新人完成波士顿项目后,通常会让他们把同一套代码迁移到另一个回归数据集上,比如加利福尼亚房价数据、二手车价格数据,或者干脆自己爬一批本地租房数据。操作一模一样:加载数据、EDA、标准化、训练、评估、画散点图,唯一要改的是特征名和目标列名。做了这个迁移,你才算真正把流程内化了,而不是只会抄波士顿案例。

我个人在实际操作中还有一个很喜欢的小习惯:把所有测试集样本的“真实房价”和“预测房价”放进同一张表格,新增一列“绝对误差”,按误差从大到小排序,然后找出误差最大的前十名,逐行分析是哪些特征导致了预测失真。这个动作比单纯盯R²有用得多,你会看到模型在哪些情况下系统性低估、哪些情况下健壮性差,也更能体会数据质量对模型上限的约束。学完波士顿房价预测,再回头看这个过程,你会觉得机器学习入门这件事,其实没那么多玄学,就是认真把一个流程吃透。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 21:00:53

R语言散点图加边际分布:ggplot2与ggExtra实战指南

1. 散点图搭配边际分布图,到底解决了什么痛点做过数据分析的人都有这种体会:手里攥着两个连续变量,想看看它们之间有没有关系,第一反应就是画个散点图。散点图确实直观,一个点代表一个样本,横轴纵轴各放一个…

作者头像 李华
网站建设 2026/10/15 21:00:49

美赛M奖论文实战:水资源分配赛题的建模、求解与写作全解析

简介:这份资源是2022年美国大学生数学建模竞赛(MCM/ICM)B题M奖优秀论文的完整PDF,面向备战美赛的高校学生与建模爱好者,聚焦美国西部千年一遇干旱背景下的水资源公平分配难题。论文提出FHSD公平分层供需模型&#xff0…

作者头像 李华
网站建设 2026/10/15 21:00:27

HP-Socket:高并发TCP/UDP/HTTP通信框架实战与性能调优

做网络通信开发这些年,HP-Socket 是我反复拿来当底座的少数几个库之一。最早在某个物联网网关项目里需要同时维持上万条 TCP 长连接,还要做透传和协议解析,自己用 epoll 撸了一套,连接管理、粘包拆包、缓冲池全部手写,…

作者头像 李华
网站建设 2026/10/15 20:59:58

markdownlint 规则 MD021 深度解析:closed atx 标题内多余的井号间距

开发工具代码质量Lint静态分析 【免费下载链接】markdownlint A Node.js style checker and lint tool for Markdown/CommonMark files. 项目地址: https://gitcode.com/gh_mirrors/ma/markdownlint 点击查看 免费下载 本文围绕 markdownlint 仓库中的 MD021 规则文…

作者头像 李华
网站建设 2026/10/15 20:58:57

SAP库存调拨MB1B移动类型详解:从311/313到在途库存与转储订单

简介:一份系统讲解SAP MM模块库存调拨与发货流程的图文资料,面向SAP顾问、ERP实施人员及供应链相关从业者,帮助理解MB1B事务代码在库存地间、工厂间及状态转变场景中的实际用法。资料以单个Word文档形式提供,压缩包内共1个docx文件…

作者头像 李华