简介面向机器学习初学者与高校毕业设计、期末大作业场景的实战项目聚焦北京新房与二手房房价预测。项目覆盖数据采集链家、安居客爬虫、房价分析与机器学习建模全流程代码含详细注释下载后简单配置即可运行便于快速理解与二次开发。资源压缩包共26个文件主要以15个Python脚本、2个CSV数据集、1个Jupyter Notebook及HTML分析报告构成另附说明文档与可视化图片整体大小仅1.29MB轻量完整。目前已有161人学习使用项目为作者获评98分的个人高分作业适合用作毕业设计、课程设计或期末大作业的高分参考。通过本项目可获得从爬虫抓取、数据清洗、特征处理到模型评估的完整实践链路搭配可视化结果与README文档能帮助读者快速上手机器学习房价预测任务提升项目完成度与答辩说服力。1. 机器学习房价预测这个题目为什么适合当“人工智能大作业”的练手项目作为人工智能大作业房价预测几乎是每个练手的人绕不开的题目。它不需要 GPU不需要复杂的网络结构核心是用机器学习方法从表格数据里拟合出房屋价格与几十个特征之间的映射关系。无论是波士顿房价数据还是二手房的挂牌记录回归任务的完整路径都是同一套拿数据、做清洗、构造特征、训练模型、评估解释。标题里附带的数据集、源码和文档资料正好对应大作业评分的三个关注点数据完整、代码可运行、报告规范。适合从听课过渡到独立做完整项目的本科生也适合想用真实案例证明动手能力的求职者。保守估计一台普通笔记本一周能独立做完如果只想把源码跑通、改改参数两三天也能交付。下面按数据准备、特征工程、模型调参、文档整理和避坑五条线拆开讲每一步都给出能直接改用的代码。2. 数据准备从拿到数据集到跑起来的第一步解析字段与隐藏陷阱房价预测项目里最容易被低估的就是数据准备环节。很多人拿到一个 CSV 就直接扔给 RandomForest结果分数忽高忽低还说不清原因。实际上房价数据集的特点决定了你必须先做一次彻底的数据体检字段类型是否统一、缺失值分布是否随机、价格标签是否长尾、时间顺序是否影响样本划分。以经典的波士顿房价数据为例它包含犯罪率、平均房间数、一氧化氮浓度、房龄等 13 个特征每个特征都有明确的物理含义和取值范围。这种数据已经高度结构化适合快速验证机器学习算法的基本流程。但如果做二手房房价预测公开数据集往往来自房产交易平台的挂售记录字段会包括户型、面积、朝向、楼层、总价、单价、建筑年份、所在商圈。这类数据没有清洗好字符串和数值混在一起直接跑模型一定会翻车。所以第一步不是建模而是把表格结构弄清楚。2.1 数据导入与字段解读这张表里有哪些列会影响价格我一般会先写一个极短的脚本把数据读进来打印 shape、dtypes 和 describe先不看具体业务含义只看统计口径。import pandas as pd import numpy as np df pd.read_csv(house_data.csv, encodingutf-8) print(df.shape) # 样本数和列数确认数据有没有被截断 print(df.dtypes) # 检查数值列、字符串列、时间列的分布 print(df.describe(percentiles[.25, .5, .75]))这段代码里shape能帮你发现是不是读错了文件dtypes决定后面哪些列需要做类型转换describe则暴露出取值范围异常的特征比如面积出现 3000 平方米或单价出现负数。percentiles 参数比默认的四分位更细能看出价格分布是否集中在某个区间。看字段时不要只盯着数值特征。二手房数据里的“户型”“朝向”“装修情况”都是字符串但它们对价格影响很大。比如同样面积三室一厅通常比两室一厅贵南北通透比朝北的房源单价高。这些字段在后续特征工程里会变成编码特征所以现在就要记录下来哪些是主键、哪些是业务标签、哪些是噪音列。我的习惯是先给每一列写一句业务含义再标注数据处理方式相当于给自己留一份字段说明。这一步看似多余却能在后期写文档资料时直接复用。2.2 数据清洗与划分缺失值、异常值和时间顺序的三个处理顺序数据清洗最忌讳“拿到就填填完就跑”。我总结了一套固定顺序先看缺失值再处理异常值最后才是划分训练集和验证集。# 1. 统计缺失率低于5%的列直接删除行高于30%的列考虑删列 missing_rate df.isnull().mean() print(missing_rate[missing_rate 0]) # 2. 异常值裁剪面积超过三个标准差以上的记录可能是录入错误 area_mean df[area].mean() area_std df[area].std() df df[(df[area] area_mean - 3 * area_std) (df[area] area_mean 3 * area_std)] # 3. 按时间排序后再切分防止未来信息泄露 df df.sort_values(listing_date).reset_index(dropTrue) split_idx int(len(df) * 0.8) train_df df.iloc[:split_idx] test_df df.iloc[split_idx:]缺失率的统计是第一步因为不同列的缺失机制完全不同。价格、面积这类关键字段缺失直接删行最安全朝向、装修这类分类字段缺失可以单独建一个“未知”类别不要用均值去填充。异常值裁剪的 3 倍标准差是一个经验值具体项目里建议先用df[area].hist()看一眼分布如果明显是长尾改用百分位数裁剪更稳妥比如只保留 1% 到 99% 分位内的样本。最后的时间排序非常关键二手房价格受市场波动影响同一个小区 2023 年的成交价和 2020 年差异很大随机切分会让模型偷偷用未来的数据“复习”过去的价格造成验证集分数虚高。3. 特征工程与基线模型先把原始表格变成机器学习能吃的格式数据清洗完成后进入整个项目里最花时间的特征工程阶段。机器学习算法本质上只能处理数值所以要把“户型”“朝向”“所在商圈”这类信息转成数值同时把面积、房龄、单价这些原始数值变成更有表达力的特征。这里的原则是先做单位统一再做变换最后考虑组合特征。很多初学者跳过了特征工程直接拿原始列去训练模型也能跑通但 R2 往往停留在 0.5 到 0.6 之间。原因很简单房价和面积之间通常不是线性增长关系面积从 60 平涨到 90 平的边际价格变化和从 150 平涨到 180 平完全不同。应对方式是对面积和总价做对数化或平方变换把这种非线性关系“拉直”。3.1 数值特征变换与组合从平方项到面积单价数值特征变换最常用的方法有三个取对数、构造平方项、计算派生比例。取对数适合价格、面积这类右偏分布平方项适合捕捉“面积并非越大越好”的倒 U 型关系比例特征则能体现房源的综合性价比。import numpy as np # 价格标签存在明显右偏做对数变换让分布更接近正态 df[price_log] np.log1p(df[price]) # 面积平方项捕捉大面积房源的边际递减效应 df[area_squared] df[area] ** 2 # 单位面积价格常被当作房价预测的强特征 df[unit_price] df[price] / df[area] # 房龄当前年份-建筑年份注意老破小的特殊处理 df[house_age] 2025 - df[build_year] df.loc[df[house_age] 0, house_age] 0log1p是log(1x)好处是当 x 为 0 时结果也是 0不会产生负无穷。价格取对数后训练目标从“预测原价”变成“预测 log 价”在评估时要记得做np.expm1还原否则 RMSE 的数值会非常小容易让人误以为模型效果极好。area_squared平方项是否有效可以在做完后面线性回归后看它的系数显著性。unit_price是房价预测项目里一个“性价比很高”的特征能直接把区域差异带进模型但它和价格标签高度相关如果用它去预测总价要留意是否造成数据泄漏。3.2 类别特征编码与缺失值处理让机器学习算法能理解“户型”和“朝向”数据集里的“户型”“朝向”“所在商圈”都属于类别特征。常见的处理方式是把低频类别归并成“其他”再做 one-hot 编码或标签编码。LightGBM、XGBoost 这类树模型可以直接处理类别特征线性回归则必须要 one-hot。对于大作业来说最稳妥的做法是统一用 one-hot因为后续换模型不用重新处理数据。# 把低频类别合并避免 one-hot 后维度爆炸 for col in [layout, orientation]: value_counts df[col].value_counts() rare_categories value_counts[value_counts 30].index df.loc[df[col].isin(rare_categories), col] other # 缺失的朝向单独作为一类不让算法把缺失当成某种固定朝向 df[orientation] df[orientation].fillna(unknown) # 用 pandas 一次性完成 one-hot 编码 df_encoded pd.get_dummies(df, columns[layout, orientation, district], drop_firstTrue)合并低频类别是防止 one-hot 后训练集和验证集出现不一致的关键。假设一个“五室三厅”的户型只在训练集里出现一次验证集里没有模型就会把这个维度当成全零影响不大但反过来如果是验证集里有、训练集里没有预测时就会出现维度不匹配的报错。把出现次数少于 30 的类别归入“other”能避免这种问题。fillna(unknown)则是在告诉模型朝向信息缺失本身就是一个特征很多老房源不会填写朝向但这不代表它毫无价值。drop_firstTrue是为了消除多重共线性在线性回归里尤其重要对树模型则没有太大区别。3.3 训练一个可解释的基线模型先用线性回归定下限再用随机森林找非线性特征工程做完后我会立刻训练一个简单模型作为基线而不是一上来就调参。基线的意义不是追求最高分数而是给你后续所有操作提供一个对照值。from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error, r2_score # 这里用随机切分做基线对比后续做正式实验再换成按时间切分 feature_cols [c for c in df_encoded.columns if c not in [price, price_log, listing_date]] X df_encoded[feature_cols].values y df_encoded[price_log].values X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 线性回归基线 lr LinearRegression() lr.fit(X_train, y_train) y_pred lr.predict(X_test) print(Linear R2:, r2_score(y_test, y_pred)) print(Linear RMSE:, np.sqrt(mean_squared_error(y_test, y_pred)))线性回归在这个任务里表现通常不会太差因为它能给出一个可解释的基准。如果线性回归的 R2 都无限接近 1那就要怀疑是不是把价格相关的字段直接放进特征里了如果 R2 只有 0.3 到 0.4说明特征与目标之间存在明显的非线性关系这时可以再补一个随机森林对比。# 随机森林基线max_depth 设置为 10限制单棵树复杂度 rf RandomForestRegressor(n_estimators300, max_depth10, min_samples_leaf5, random_state42) rf.fit(X_train, y_train) y_pred rf.predict(X_test) print(RandomForest R2:, r2_score(y_test, y_pred)) print(RandomForest RMSE:, np.sqrt(mean_squared_error(y_test, y_pred)))这里的max_depth和min_samples_leaf都是控制过拟合的参数。房价数据集通常只有几千条样本树太深会记住训练集里每一条记录的噪声。用 300 棵树已经足够稳定更多树并不会带来显著提升只会拖慢网格搜索的速度。4. 模型选择与调参从线性回归到集成学习代码、源码与文档怎么配合当基线建立起来之后就要开始面对一个问题该选哪个模型作为最终方案。房价预测任务里线性回归、决策树、随机森林、XGBoost、LightGBM 都是常用选项但它们的适用条件和训练速度差异很大。一个大作业如果想要好看的评价指标通常会落在集成模型上如果想要好写文档线性回归和随机森林的组合可以讲得更清楚。4.1 场景化选模型什么时候该用线性回归什么时候换 LightGBM选模型的依据不是“哪个比较新”而是特征规模和样本量。我通常按以下方式判断模型优势劣势典型使用场景线性回归可解释性强训练快无法自动处理非线性特征数量少且与价格呈线性关系决策树非线性可解释易过拟合单个模型不稳定用于快速分析特征重要性随机森林抗过拟合调参空间小预测值不能超出训练集范围样本量适中特征类别多LightGBM精度高支持类别特征调参复杂容易过拟合样本量大特征交互关系复杂波士顿房价这类小数据集用线性回归就够了但二手房价格受区位、楼层、朝向的交叉影响线性模型很难学到“同一商圈里高层朝南比低层朝北贵多少”这种规则。随机森林是性价比最高的选择树模型自动处理特征交互不需要额外构造交叉项。LightGBM 在几千条样本上也能跑但需要更仔细地设置学习率和叶节点数否则测试集分数会明显低于训练集。import lightgbm as lgb train_data lgb.Dataset(X_train, labely_train) valid_data lgb.Dataset(X_test, labely_test, referencetrain_data) params { objective: regression, metric: rmse, learning_rate: 0.05, num_leaves: 31, min_data_in_leaf: 20, feature_fraction: 0.8, verbose: -1 } model lgb.train(params, train_data, num_boost_round1000, valid_sets[valid_data], callbacks[lgb.early_stopping(50)])代码里num_leaves31对应 LightGBM 的默认树复杂度min_data_in_leaf20是防止某个叶子节点上样本太少导致过拟合。early_stopping(50)表示如果验证集 RMSE 连续 50 轮没有下降就停止训练这是集成模型里最实用的防过拟合手段。4.2 参数调优与过拟合监控先管学习率再管树复杂度调参的顺序决定了你是在有效搜索还是纯粹碰运气。我一般先固定学习率再用网格搜索找树的复杂度最后回到学习率做一次精调。千万不要一上来就把所有参数丢进 GridSearchCV组合数太多跑一晚上也未必收敛。from sklearn.model_selection import GridSearchCV from lightgbm import LGBMRegressor lgb_model LGBMRegressor(objectiveregression, learning_rate0.05, random_state42) param_grid { num_leaves: [15, 31, 63], min_data_in_leaf: [10, 20, 50], feature_fraction: [0.7, 0.8, 1.0] } grid GridSearchCV(lgb_model, param_grid, cv5, scoringneg_root_mean_squared_error, n_jobs-1) grid.fit(X_train, y_train) print(best params:, grid.best_params_) print(best cv score:, -grid.best_score_)这里的neg_root_mean_squared_error是 sklearn 里“负 RMSE”的写法因为 GridSearchCV 默认要求分数越大越好所以 RMSE 取相反数。五折交叉验证可以帮助判断参数是不是只在某一个随机划分上表现好。如果最优参数落在了搜索边界上比如num_leaves63说明模型还有提升空间应该扩大搜索范围如果落在中间值说明当前范围合理。4.3 项目源码结构与文档资料怎么用一份完整大作业的交付目录拿到别人整理好的项目源码后第一件事不是读模型代码而是看目录结构。一个合格的房价预测大作业源码应该至少包含data/、src/、notebooks/、docs/这四个目录。data/里是清洗前后的 CSVsrc/按功能拆分成数据加载、特征工程、模型训练、评估预测几个模块notebooks/适合做探索性分析和中间结果展示docs/放文档资料和答辩 PPT。如果你的大作业要求提交“数据集项目源码文档资料”我建议把文档写成五个部分选题背景与问题定义、数据说明与探索性分析、特征工程方法与代码索引、模型对比与参数选择、结论与改进方向。其中“模型对比”是最容易拿分的位置用表格把三个模型的 R2、RMSE、训练时间列出来每行配一两句分析说明为什么随机森林比线性回归好了几个点。答辩时老师最喜欢问的问题通常是“你觉得哪个特征最重要”所以文档里最好包含一张特征重要性图这个在第六章会展开讲。5. 避坑指南房价预测项目中反复出现的 5 个常见错误与自查方法这一章单独拎出来是因为房价预测项目里太多人栽在看起来不起眼的数据处理细节上。每一组错误我都会按“现象、原因、解决”来写。如果你照着做还是分数异常先看这里。5.1 把“标签”当普通特征做填充造成“未来数据”混进训练集现象训练集 RMSE 很低但验证集 RMSE 突然变得非常大而且特征重要性排名里总价相关字段排在第一位。原因数据准备阶段用整张表的均值填充了缺失值而填充过程发生在 train/test 划分之前。如果price的缺失值是用全量均值填充相当于把验证集的价格信息泄漏给了训练集。模型在训练时见过“全局平均水平”到验证集上自然能猜得八九不离十。解决先把缺失行拆开用训练集的统计量填充验证集。正确做法是fill_value train_df[price].median()然后用这个值分别填充训练集和验证集。更稳妥的是对需要填充的列单独建模预测但大作业场景下没必要。5.2 不做时间切分随机打乱数据集导致验证分数虚高现象模型在测试集上 R2 达到 0.92但换到新一批房源数据上预测误差直接翻倍。原因房价数据是典型的时间序列。同一个小区 2024 年的成交价受当年市场供需影响和 2021 年的价格差可能在 20% 以上。随机切分会让训练集和验证集都包含不同年份的样本模型学到的是“年份平均值”而不是真正的价格规律。解决按照挂牌时间或成交时间排序用前面的 80% 做训练后面的 20% 做验证。如果数据里没有时间字段至少要把城市、商圈、小区作为分组条件用GroupShuffleSplit按小区划分否则同一个小区的房源会同时出现在训练和验证里。5.3 特征缩放时统一 fit 整个数据集验证集信息泄漏到训练现象线性回归模型的特征系数非常大预测结果对某一个特征极其敏感。原因许多人习惯先对整个特征矩阵做StandardScaler().fit_transform(X)再做训练测试划分。fit_transform会计算全量数据的均值和标准差验证集的分布信息从源头上混进了训练阶段。这不算最严重的泄漏但会让模型对验证集有“偏好”。解决先划分再在训练集上scaler.fit(X_train)然后分别transform训练集和测试集。随机森林和 LightGBM 对特征缩放不敏感但线性回归、岭回归和 SVM 必须按这个顺序处理。5.4 对长尾价格直接建模R2 永远上不去现象模型对普通房源预测很准但对高价豪宅的预测严重偏低整体 R2 只有 0.6 左右。原因房价分布是典型的右偏长尾。绝大多数样本集中在 100 万到 300 万之间少数豪宅在千万以上。模型在 RMSE 的驱动下会把全部注意力放在“主流样本”上豪宅的偏差对总损失的贡献被平均掉了。解决对价格标签做log1p变换然后在评估指标里同样使用 log 空间的 RMSE最后在报告里用expm1还原成真实价格再展示。加了 log 变换后模型对高低价样本的误差权重更均匀R2 通常能提升 0.05 到 0.1。5.5 网格搜索不看评估指标跟着默认 score 走现象GridSearchCV 跑完best_score_很大但手动计算测试集 R2 时却对不上。原因回归模型的默认 score 是 R2但如果你在构造LGBMRegressor时没指定eval_metricLightGBM 的默认目标函数可能是 RMSE。网格搜索内部的得分和你自己的评估函数不一致导致选出来的参数不是针对你真正关心的指标。解决在 GridSearchCV 的scoring参数里显式写成neg_root_mean_squared_error或r2并保证模型自身的metric与之一致。每次跑完网格搜索不要直接采用best_params_要用它重新在验证集上跑一遍记录最终的 R2 和 RMSE。6. 进阶实战从能跑通到答辩能讲好用 SHAP 解释和模型融合拉满完成度一个房价预测大作业如果只是把模型跑出来老师大概率只能给一个基础分。真正拉开差距的部分在于你能不能解释模型为什么这样预测以及有没有尝试多种方案做融合。这一章我写两个常用技巧都是在答辩现场能让老师眼睛一亮的东西。6.1 用 SHAP 把黑匣子打开哪个特征在推动价格上浮树模型最被诟病的地方是解释性差但 SHAP 可以解决这个问题。它会为每一个样本的每一个特征计算一个贡献值贡献值为正代表把价格往上推为负代表往下压。import shap shap_explainer shap.TreeExplainer(model) shap_values shap_explainer.shap_values(X_test) shap.summary_plot(shap_values, X_test, feature_namesfeature_cols, max_display15)summary_plot输出的是每个特征的全局重要性分布。在文档里放这张图再配一段话比如“面积和单位价格对预测结果影响最大且面积较大时边际贡献递减”这句话直接把特征工程阶段的思考串起来了。答辩时如果老师问“你觉得哪个特征最重要”不要只回答“面积”要说“从 SHAP 图来看面积是主要驱动因素但它在 120 平之后的边际贡献明显放缓因此模型并没有简单地按面积线性外推”。6.2 模型融合与残差图加权平均不是玄学是最后一层提分模型融合是很多大作业里被忽视的部分。简单做法是把随机森林和 LightGBM 的预测结果做加权平均权重用网格搜索确定。这样做的原理是不同模型的误差分布不同融合后可以抵消一部分偏差。from scipy.optimize import minimize def loss_func(weight): return np.mean((weight * pred_rf (1 - weight) * pred_lgb - y_test) ** 2) result minimize(loss_func, x00.5, bounds[(0, 1)]) best_w result.x[0] print(optimal weight for RF:, best_w)这段代码用 scipy 的minimize找最优权重目标函数是最小化融合预测的均方误差。注意只把测试集当融合权重的选择依据不要再继续调模型参数否则又过拟合了。融合后我会画一张残差图横轴是真实价格纵轴是预测值减去真实值。如果残差图呈现明显的扇形说明高价位样本的方差更大这本身又是一个可写进文档里的发现。6.3 把项目改造成“新题目”的三个方向换城市、换目标、换窗口如果你的大作业要求不能直接套用现成数据集我通常建议往三个方向改。第一个方向是换成另一个城市的二手房数据集特征大体相似但房价分布不同模型和文档框架可以直接迁移。第二个方向是把回归任务改成分类任务比如预测“未来三个月房价是涨是跌”这样模型从回归器换成分类器文档内容也会焕然一新。第三个方向是改变预测窗口比如不做点预测而做区间预测用分位数回归输出价格的 90% 置信区间。这三个方向都在原项目的骨架里改动不大但能明显增加项目的原创性。做完这六章的内容这个项目就不再是“跑通别人的代码”而是一套能讲清楚前因后果的完整作品。我自己的习惯是每个大作业都先花一天时间把数据里的字段含义写进文档再开始建模最后的 SHAP 图一定截图保存因为答辩 PPT 里它是比所有理论文字都更有说服力的证据。希望这些经验能帮你少踩几个坑把这份房价预测大作业做成你简历上拿得出手的东西也希望每个正在赶作业的人都能有惊无险地顺利提交。本文还有配套的精品资源点击获取