简介面向机器学习和数据科学入门学习者的波士顿房价数据集压缩包适用于需要开展回归预测练习、特征工程或基础模型训练的开发者与学生。该数据集围绕经典房价预测场景展开在压缩包内整合了数据说明、处理脚本和原始数据便于快速搭建实验环境节省查找与整理数据的时间。压缩包共包含3个文件分别为Markdown说明文档、Python数据处理脚本和CSV格式原始数据集说明文档用于描述字段含义与使用注意事项Python脚本示范了从数据读取、特征查看、训练集拆分到简单回归建模的完整流程CSV文件则提供包含多条区域样本记录和多个特征字段的规范数据整体体积约15KB结构轻量、上手门槛低。目前已有1361人学习或下载具备较好的通用参考价值。借助该资源读者可以完成从数据加载、特征观察到基础模型构建的闭环练习既能用于课程作业和毕业设计实验也能作为算法对比与数据分析报告的支撑素材。1. 波士顿房价数据集.zip一个套娃压缩包里藏着的回归入门样本刚接触机器学习的开发者十有八九都下过这个文件波士顿房价数据集波士顿房价数据集.zip.zip。名字看着像压缩包套压缩包解压后里面还真是一个同名 zip。这套数据在 UCI、教学课件和各类网盘里流传了二十多年506 条样本、14 个字段13 个特征加一个目标列 MEDV数据来自 1978 年波士顿地区的房屋普查。它的价值不在“新”而在“小而全”单机秒跑完线性回归、决策树和随机森林特征含义清楚适合用来搞懂回归任务的完整链路。如果你正在找一份能快速验证环境、练习特征工程或跑通模型流程的数据集它比人工构造的 toy data 更真实也比大规模业务数据更省心。不过动手前有个坑要先知道sklearn 早就把load_boston()移除了直接调 API 会报错。这篇笔记就从这个双层 zip 讲起一路拆到建模、评估和踩坑。2. 从双层 zip 到干净的 CSV解压、编码与表头确认2.1 为什么会出现“套娃 zip”这种结构这套数据在中文技术社区里流传时经常被二次打包。原发布者一般传一个波士顿房价数据集.zip下载站或网盘转存时又在外面套一层同名压缩包就成了标题里这种“波士顿房价数据集.zip.zip”。其实里面就是一份 CSV偶尔还会附带 README 或 PDF 说明。所以第一步别急着写代码先把文件老老实实解压出来确认里面到底有几个文件、多大、是不是 CSV。在 Linux 或 macOS 终端里我习惯先把外层解压unzip 波士顿房价数据集波士顿房价数据集.zip.zip ls -la file 波士顿房价数据集.zipfile命令用于确认内层文件真实类型。如果输出显示Zip archive data说明确实还有一个 zip。接着解内层unzip 波士顿房价数据集.zip ls -la解压后预期得到一个.csv文件行数约 507 行506 条样本加一行表头。Windows 下如果双击解压遇到中文名乱码或路径过长报错用 7-Zip 打开后选择“解压到当前文件夹”通常能绕过去乱码多由文件名编码不一致引起后面读 CSV 时再处理。我一般会顺手跑一下md5sum把文件指纹记下来防止后续换机器时弄混版本。2.2 先看表头再训练列名、行数与编码的一次性确认不要急着建模先确认 CSV 的列名和行数。不同渠道流传的版本列名不完全一样常见的有CRIM, ZN, INDUS, CHAS, NOX, RM, AGE, DIS, RAD, TAX, PTRATIO, B, LSTAT, MEDV有些版本第一列是序号还有些版本末尾多了一列文本DATA这一列在pandas里会被读成 object 类型直接用它算相关性会报错或算出一堆 NaN。先用系统命令扫一眼再进 Python 确认结构head -5 波士顿房价数据集.csv wc -l 波士顿房价数据集.csv如果表头里出现DATA或在head输出里看到整行数据被重复放在最后一列说明是“带文本列的教学版”。接下用pandas读进来先打印形状和列名不急于建模import pandas as pd df pd.read_csv(波士顿房价数据集.csv) print(df.shape) print(df.columns.tolist()) print(df.head())shape应显示(506, 14)或(506, 15)——多出来的一列往往是序号或DATA。如果df.head()里最后一列是一长串文本那是把原始行数据原样复制了一份需要裁掉。列名带首尾空格也很常见比如MEDV 不清理的话df[MEDV]会直接 KeyError。此时执行df.columns df.columns.str.strip() df df.loc[:, ~df.columns.duplicated()]第一句去掉列名首尾空白第二句去掉重复列。读 CSV 时如果中文路径报编码错误加encodinggbk或encodingutf-8试一遍。这一阶段的目标只有一个让df.shape、df.dtypes和列名都符合预期再进入预处理。3. 把 506 条样本装进 DataFrame13 个特征的含义与预处理3.1 加载并强制数值化去掉文本列统一列名这套数据最容易被忽略的坑是CSV 版本里除了 14 个字段经常混进一列文本DATA导致df.info()里所有列都显示为 object。直接用df.corr()会得到一堆 NaN。按前面说的把列名清理干净之后第二步是只保留我们需要的 14 列并把它们全部转成数值类型。cols [CRIM, ZN, INDUS, CHAS, NOX, RM, AGE, DIS, RAD, TAX, PTRATIO, B, LSTAT, MEDV] df df[cols].copy() for c in cols: df[c] pd.to_numeric(df[c], errorscoerce) print(df.dtypes) print(df.isna().sum())errorscoerce的作用是任何无法解析的字符串都变成 NaN而不是让整列升格成 object。这样做的代价是引入了缺失值所以紧接着要用isna().sum()看每一列的缺失数量。正常版本应该没有缺失如果发现某列大面积 NaN多半是表头对不上或该列原本就是文本列被误留了。此时有两种处理一是删除该列二是用df[c].fillna(df[c].median())填充中位数。506 条样本本身不大缺一两个值用中位数填充可以接受缺几十个就要回去检查列名映射是否错位。3.2 13 个特征的中文含义和三个必看边界这个数据集里的特征来自 1978 年波士顿标准都市统计区的普查数据。列名是缩写建模前最好把含义写出来否则后面做特征筛选时只能靠猜。列名含义典型范围备注CRIM城镇人均犯罪率0.006–89右偏严重ZN占地面积超过 25000 平方英尺的住宅用地比例0–100大量 0INDUS城镇非零售商业用地比例0.46–27.74CHAS是否邻近查尔斯河0 或 1哑变量NOX一氧化氮浓度每千万分之一0.38–0.87与 INDUS 相关性高RM平均每栋住宅的房间数3.56–8.78与 MEDV 强正相关AGE自住房屋中建于 1940 年前的比例2.9–100DIS到波士顿五个就业中心的加权距离1.13–12.13RAD高速公路可达性指数1–24是索引值不是连续度量TAX每 10000 美元的不动产税率188–711与 RAD 高度相关PTRATIO城镇学生教师比12.6–22.0B修正后的黑人比例指数0.32–396.9教学常用解读需谨慎LSTAT低地位人口百分比1.98–37.97与 MEDV 强负相关MEDV自住房屋房价中位数千美元5–50目标列上限被截断三个必看边界第一CHAS 是哑变量只有 0 和 1不要对它做标准化第二RAD 虽然看起来是数值实际是分组索引1 到 24 不代表“辐射强度”线性模型把它当连续变量用会引入假关联第三MEDV 被截断在 50 千美元也就是说超过 50 的样本都被记成了 50这让模型残差天然右偏预测值逼近 50 时会压缩。还有一个争议点B 列是“修正后的黑人比例指数”来自原始论文教学代码里沿用了这个缩写。如今用它做特征建议概念上把它当作“人口构成比例”处理同时在报告中注明数据年代避免误读。预处理上我一般会做两件事对线性模型用StandardScaler标准化全部数值特征CHAS 除外对树模型则不标准化。量纲差异在岭回归和线性回归里影响很大TAX是几百、RM是个位数不缩放会让梯度下降和正则化都偏向大数值列。标准化要在切分训练集和测试集之后做只对训练集fit_transform再对测试集transform这一条放到第 5 章详细说。4. 先跑线性回归再用交叉验证最适合接手这套数据的第一步4.1 切分训练集与测试集跑一个最小线性回归这套数据是回归任务的基准样本第一个模型没必要上神经网络。先用线性回归建立基线如果线性回归表现正常后续再考虑决策树、随机森林或带正则化的岭回归。切分时固定random_state保证结果可复现。from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score import numpy as np X df.drop(columns[MEDV]) y df[MEDV] 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) rmse np.sqrt(mean_squared_error(y_test, y_pred)) print(RMSE:, round(rmse, 3)) print(R2:, round(r2_score(y_test, y_pred), 3))test_size0.2表示留出 101 条样本做测试random_state42不是玄学是为了让多次运行的结果完全一致。RMSE的单位是千美元跑出来的典型值在 4.5 到 6 之间R2在 0.7 上下波动。如果 R2 低于 0.6先检查预处理有没有文本列混进 X有没有对全量数据做标准化。此处的LinearRegression默认带截距不需要手动加常数项但特征列如果有单位矩阵问题OLS 会直接报“singular matrix”警告一般由完全线性相关的两列引起如 RAD 与 TAX后面会用岭回归处理。4.2 用 10 折交叉验证看稳定性别被一次切分骗了单次切分的结果受随机种子影响很大。同样是random_state42换成random_state0可能 R2 就掉了 0.05。因此我习惯紧接着跑交叉验证把数据切成 10 份轮流拿 9 份训练、1 份验证最终得到 10 个 RMSE看均值和方差。from sklearn.model_selection import KFold, cross_val_score kf KFold(n_splits10, shuffleTrue, random_state42) neg_mse cross_val_score( LinearRegression(), X, y, cvkf, scoringneg_mean_squared_error ) cv_rmse np.sqrt(-neg_mse) print(CV RMSE mean:, round(cv_rmse.mean(), 3)) print(CV RMSE std:, round(cv_rmse.std(), 3))cross_val_score默认返回负的均方误差因为 sklearn 的评分函数遵循“越大越好”所以取负号之后再开方得到 RMSE。对照组实现中10 折 RMSE 的均值通常比单次切分的 RMSE 略高这是数据量小导致的正常现象如果 std 超过 1.5说明模型对数据划分非常敏感这时更适合用KFold(shuffleTrue)的多次重复来平滑波动。n_splits在 506 条样本上用 5 或 10 均可10 折每折只有 50 条验证样本会稍微放大方差5 折跑起来更快但验证集更大、结果更稳。做基线验证时我一般先用 10 折后续调参再切回 5 折省时间。真正执行时还有一层常见问题如果对全量 X 先做了标准化再切分交叉验证就产生了信息泄露。正确做法是把标准化放进Pipeline让每一折单独 fit 标准化器from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler pipe Pipeline([ (scaler, StandardScaler()), (lr, LinearRegression()) ]) neg_mse_pipe cross_val_score( pipe, X, y, cvkf, scoringneg_mean_squared_error )这样每一折只在本折的训练数据上计算均值和方差测试数据不会被“偷看”。很多人在 Kaggle 新手阶段都犯过这个错先scaler.fit(X)再train_test_split结果是交叉验证分数虚高上线就翻车。Pipeline 的写法把顺序锁死在流程里是最稳妥的选择。5. 避坑加载、预处理和评估中最常见的 5 个翻车现场5.1 现象load_boston()直接抛 AttributeError原因scikit-learn 1.2 起移除了load_boston()数据集接口。网上大量 2021 年前的教程还在用from sklearn.datasets import load_boston照抄必翻车。解决改用pandas.read_csv读取你解压出来的 CSV这也是标题里这个 zip 存在的意义——数据文件远比 API 长寿。如果确实想体验从 sklearn 拉取同源数据用fetch_openml(boston, parserauto, as_frameTrue)但注意 OpenML 版本的列名和 CSV 版不完全一致接口参数也需要适配。别跟load_boston较劲一条read_csv就绕过了整个坑。5.2 现象df.info()显示所有列都是 objectdf.corr()输出全是 NaN原因CSV 版本里混入了文本列DATA或表头与数据行之间有空行导致 pandas 把整张表都推断为字符串。解决先按第 2 章的usecols或手动cols列表把 14 列选出来再用pd.to_numeric(errorscoerce)强制转换。转换后立即执行df.isna().sum()排查缺失如果某一列全 NaN多半是列名映射错位例如原表头末尾有看不见的\r用df.columns.tolist()打印真实列名手动修正后再转换。另一个有效手段是pd.read_csv(..., skip_blank_linesTrue)它能自动忽略空行避免表头后多一行空行导致类型推断失败。5.3 现象随机森林跑出来的 R2 反而比线性回归低原因506 条样本对随机森林来说太少默认超参下每棵树都深到叶节点逼近个位数训练集拟合得很夸张、测试集直接过拟合。线性回归只有 14 个系数反而限制了方差。解决限制树模型复杂度。随机森林里调max_depth4, min_samples_leaf5让模型学大趋势而不是背样本。用交叉验证对比默认参数和受限参数你会发现受限后测试集 RMSE 明显下降。这个现象本身就是很好的课堂数据集规模决定模型复杂度的上限小数据上别迷信“越复杂越强”。5.4 现象交叉验证第一折就报Input X contains NaN原因预处理阶段没有处理缺失值或CHAS列在某个版本里全是空字符串pd.to_numeric(errorscoerce)把它们全部转成了 NaN。解决df.isna().sum()逐列确认缺失位置。对于 CHAS正确的做法是检查df[CHAS].value_counts()正常应该输出 0 和 1 两类比例大约 9:1。如果发现该列只有 0 没有 1那是流传版本丢失了少数样本处理方式是直接删除这一列而不是用中位数把它填成全是 0如果缺失集中在某几列用中位数填充即可。记住先把缺失问题解决干净再进交叉验证否则每一折报错位置还不一样看起来非常像“玄学”。5.5 现象预测出来的房价出现负数或者全部挤在 50 附近原因MEDV 本身被截断在 50线性模型在特征外推时很容易预测出 50 以上的值某些极端特征组合例如高犯罪率、高 TAX会推出负房价。这不是模型“坏了”而是线性模型没有边界约束。解决第一查看预测值分布如果大量堆在 50 附近说明数据里超过 50 的真实值都被截成了 50模型在学“封顶”。此时可以把目标列做对数变换即对np.log1p(y)建模评估时再用np.expm1还原第二改用带 L2 正则化的Ridge系数被压缩后极端预测会变少第三如果业务上只关心相对排序而不是绝对值可以用 RMSE 之外再算一个秩相关指标避免被几个极端样本带偏评估结论。6. 让它更可信相关性排序、残差诊断与一次合格的模型验收跑完基线模型后先别急着调参。我会先看特征与目标的相关性排序这一步能快速暴露数据版本问题如果RM与MEDV的相关性不是最高之一说明列名映射或预处理有错。执行corr df.corr()[MEDV].sort_values(ascendingFalse) print(corr)正常版本里RM是最大的正相关约 0.7LSTAT是最大的负相关约 -0.74。这两个特征基本主导了模型预测走向。基于此做一次精简实验只用RM、LSTAT、PTRATIO三个特征重新跑线性回归R2 通常能保住全特征版本的八成以上。这既是特征选择练习也是理解模型机制的最快路径——多特征版本很多系数只是互相抵消不具解释价值。然后是残差诊断。用测试集画出预测值与真实值的散点图理想情况是点均匀落在yx对角线两侧如果散点呈喇叭状即预测值越大残差越分散说明模型存在异方差常见对策是对目标列做对数变换。因为 MEDV 在 5 到 50 之间取值低房价区间样本多且波动大高房价区间被截断残差图天然不会太干净。我习惯把预测值大于 48 的样本单独打印出来看它们对应的RM、LSTAT原始值——这些往往是原始论文里被截断的“超 50 千美元”房屋模型无法还原真实价格不是缺陷而是数据本身的边界。这套 506 条样本的数据集虽然房价数字早已不符合当下波士顿市场但回归建模的流程到今天一点没过时。我到现在接一个新环境、换一套新库都会先拿它当“冒烟测试”能在一个小时里跑通加载、预处理、建模、交叉验证、残差检查全流程才敢把代码搬到大项目上。早年我也迷信过更大更新的数据集后来发现小数据能把每个步骤的黑匣子都拆开看清楚。希望帮到你。本文还有配套的精品资源点击获取