跳到主要内容

项目一:房价预测(回归问题)

房价预测项目流程图

项目定位

这是你的第一个完整 ML 回归项目。从数据探索到模型部署,走完完整流程。使用 sklearn 内置的加州房价数据集。

项目概览

信息说明
任务类型回归
数据集California Housing(sklearn 内置)
评估指标RMSE、R²
涉及技能EDA、特征工程、多模型对比、调参

先建立一张地图

这个项目最适合拿来练“一个回归项目到底应该怎么长出来”。

一个更适合新人的总类比

你可以把房价预测项目理解成:

  • 给一批房子先做估价,再回头看自己哪里估偏了

这和很多真实业务很像:

  • 不是只要给出一个数字
  • 还要知道这个数字为什么大概合理
  • 以及自己最容易在哪些房子上判断失准

如果你第一次做回归项目,就按这条线走,通常最稳。

这题你真正要练什么

这个项目不只是“把回归模型跑通”,更重要的是练这 4 件事:

  1. 从数据探索里找到有用线索
  2. 先立一个简单 baseline
  3. 通过特征工程和模型对比提升效果
  4. 用误差分析解释模型哪里做得好、哪里做得差

这题为什么特别适合当第一个完整项目?

因为它同时有几个优点:

  • 任务类型简单清楚,就是回归
  • 指标直观,RMSE 和 R² 容易解释
  • baseline 很容易立起来
  • 后面也有很清楚的改进空间

推荐推进顺序

更适合新人的顺序通常是:

  1. 先做一个最简单的线性回归 baseline
  2. 再做基本特征工程
  3. 再上树模型或 GBDT
  4. 最后才做调参

如果一开始就直接上复杂模型,你通常会失去对问题本身的感觉。

第一版最重要的目标,不是高分

这一题第一版最重要的目标其实只有两个:

  1. 确认这个问题用回归建模是通的
  2. 建一个后面所有改进都能对照的 baseline

也就是说,第一版做得“简单但完整”,比一开始就做得“复杂但说不清楚”更有价值。

Step 1:数据加载与探索

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import fetch_california_housing

# 加载数据
data = fetch_california_housing()
df = pd.DataFrame(data.data, columns=data.feature_names)
df['MedHouseVal'] = data.target # 房价中位数(万美元)

print(f"数据形状: {df.shape}")
print(df.describe())

# 目标分布
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
df['MedHouseVal'].hist(bins=50, ax=axes[0], color='steelblue', edgecolor='white')
axes[0].set_title('房价分布')
axes[0].set_xlabel('房价中位数(万美元)')

# 相关性
corr = df.corr()['MedHouseVal'].drop('MedHouseVal').sort_values()
corr.plot.barh(ax=axes[1], color='coral')
axes[1].set_title('各特征与房价的相关性')
plt.tight_layout()
plt.show()

Step 1.1 这一步最该问什么

第一次看数据时,不要只急着画图。先问这几个问题:

  • 目标值分布是否偏斜
  • 有没有特别明显的异常值区间
  • 哪些特征可能和价格强相关
  • 哪些特征很可能只是弱信号

这几问会直接决定你后面:

  • 先上什么 baseline
  • 特征工程优先做哪几项
  • 误差分析该切哪些维度看

Step 1.2 一个新人很值得先记的判断

第一次做回归题时,最容易犯的错是:

  • 一上来就急着换模型

但更稳的顺序通常是:

  • 先看数据
  • 先理解目标值
  • 先知道自己到底在预测什么分布

Step 2:特征工程

# 构造新特征
df['rooms_per_household'] = df['AveRooms'] / df['AveOccup']
df['bedrooms_ratio'] = df['AveBedrms'] / df['AveRooms']
df['population_per_household'] = df['Population'] / df['HouseAge']

# 准备数据
from sklearn.model_selection import train_test_split

feature_cols = [c for c in df.columns if c != 'MedHouseVal']
X = df[feature_cols]
y = df['MedHouseVal']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"训练集: {X_train.shape}, 测试集: {X_test.shape}")

Step 2.1 第一次做特征工程时,为什么要克制一点

回归项目里最常见的错误之一,就是一口气构造太多特征,最后自己也说不清到底哪个有效。
更稳的做法是:

  • 先只加 2~3 个最有解释力的新特征
  • 每次加完都和 baseline 对比
  • 如果没有明显收益,就不要因为“看起来高级”而硬留

Step 2.2 一个更像真实业务的特征思路

房价问题里很值得优先想到的特征,通常都和“单位成本”和“相对位置”有关,例如:

  • 每房间面积
  • 每户人口
  • 房龄和位置的组合

这些都比“机械堆更多列”更像真实建模思路。


Step 3:多模型对比

from sklearn.linear_model import LinearRegression, Ridge
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.metrics import mean_squared_error, r2_score

models = {
'线性回归': make_pipeline(StandardScaler(), LinearRegression()),
'Ridge': make_pipeline(StandardScaler(), Ridge(alpha=1.0)),
'随机森林': RandomForestRegressor(n_estimators=100, random_state=42),
'GBDT': GradientBoostingRegressor(n_estimators=100, random_state=42),
}

results = {}
for name, model in models.items():
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
results[name] = {'RMSE': rmse, 'R²': r2}
print(f"{name:10s} | RMSE: {rmse:.4f} | R²: {r2:.4f}")

# 可视化
fig, ax = plt.subplots(figsize=(8, 5))
names = list(results.keys())
r2s = [v['R²'] for v in results.values()]
bars = ax.bar(names, r2s, color=['steelblue', 'coral', 'seagreen', 'gold'], alpha=0.8)
for bar, score in zip(bars, r2s):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.005,
f'{score:.4f}', ha='center')
ax.set_ylabel('R²')
ax.set_title('模型 R² 对比')
ax.grid(axis='y', alpha=0.3)
plt.show()

Step 3.1 模型对比时最该看什么

不要只看哪个 最高。更稳的对比方式是同时看:

  • RMSE 有没有明显下降
  • 模型复杂度是不是高了很多
  • 可解释性有没有明显变差

第一次做回归项目时,最值得珍惜的不是“最高分模型”,而是:

  • 你知道为什么它比 baseline 好
  • 你知道好在什么地方

Step 3.2 一个新人可直接照抄的模型对比表

模型优点第一次做项目时你最该看什么
线性回归最好解释baseline 到底稳不稳
Ridge更稳一点正则化是否有帮助
随机森林非线性更强特征重要性和过拟合风险
GBDT往往效果更强RMSE 是否明显下降

这个表很适合新人,因为它会把“模型名”重新拉回成“为什么要试它”。


Step 4:模型调优

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform

param_dist = {
'n_estimators': randint(100, 500),
'max_depth': randint(5, 20),
'learning_rate': uniform(0.01, 0.2),
'subsample': uniform(0.7, 0.3),
}

rs = RandomizedSearchCV(
GradientBoostingRegressor(random_state=42),
param_dist, n_iter=30, cv=5,
scoring='neg_root_mean_squared_error',
random_state=42, n_jobs=-1
)
rs.fit(X_train, y_train)

y_pred_best = rs.predict(X_test)
print(f"调优后 RMSE: {np.sqrt(mean_squared_error(y_test, y_pred_best)):.4f}")
print(f"调优后 R²: {r2_score(y_test, y_pred_best):.4f}")
print(f"最佳参数: {rs.best_params_}")

Step 5:结果分析

# 预测 vs 实际
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

axes[0].scatter(y_test, y_pred_best, s=5, alpha=0.3)
axes[0].plot([0, 5], [0, 5], 'r--')
axes[0].set_xlabel('实际房价')
axes[0].set_ylabel('预测房价')
axes[0].set_title('预测 vs 实际')

# 特征重要性
importance = rs.best_estimator_.feature_importances_
sorted_idx = np.argsort(importance)
axes[1].barh(range(len(sorted_idx)), importance[sorted_idx], color='coral')
axes[1].set_yticks(range(len(sorted_idx)))
axes[1].set_yticklabels(np.array(feature_cols)[sorted_idx])
axes[1].set_title('特征重要性')

plt.tight_layout()
plt.show()

Step 5.1 残差分析比最终分数更能体现你会不会做项目

很多人做到这里就停在 RMSE。但真正能拉开项目质量的,往往是残差分析:

  • 哪些价格区间误差特别大
  • 模型是不是系统性低估高价房
  • 哪些区域特征组合最容易预测错

这一步会直接决定你下一轮到底该:

  • 补特征
  • 换模型
  • 还是重新看数据切分方式

Step 5.2 再看一个最小“误差分桶”示例

errors = pd.DataFrame({
"y_true": [1.0, 2.5, 4.2, 3.8],
"y_pred": [1.2, 2.0, 3.1, 4.5],
})
errors["abs_error"] = (errors["y_true"] - errors["y_pred"]).abs()
errors["bucket"] = pd.cut(errors["y_true"], bins=[0, 2, 4, 6], labels=["low", "mid", "high"])

print(errors.groupby("bucket")["abs_error"].mean())

这个例子很适合初学者,因为它会帮助你先建立一个关键习惯:

  • 误差不是只看总体均值
  • 还要看哪一类样本更容易错

一个很适合新人的最小复盘表

你可以直接做这样一张表:

版本做了什么改动RMSE我的判断
baseline线性回归--先建立下界
v2加 2~3 个特征--看特征工程是否真有帮助
v3换树模型 / GBDT--看非线性模型是否更合适

这张表会让你的项目从“跑过代码”变成“有清楚迭代过程”。

一个新人很值得照抄的项目检查表

第一次做房价预测项目时,最稳的检查表通常是:

  1. baseline 是否已经立住
  2. 特征工程是否有明确业务含义
  3. 模型对比是否不仅看一个分数
  4. 残差分析是否已经告诉你下一步该改哪里

如果这 4 件事都做到位,
这个项目就已经不是“跑过一个回归脚本”,而是真正做过一次完整建模了。

如果继续把这个项目往上做,最值得补什么

更值得优先补的通常是:

  1. 残差分布分析
  2. 不同区域 / 房价区间上的误差对比
  3. baseline 到最优模型的完整版本演化说明

这样项目会更像一个真正做过建模和复盘的回归作品。

项目交付时最好补上的内容

  • 一张“真实值 vs 预测值”的图
  • 一段对误差来源的说明
  • 一份 baseline 和改进版的对比表
  • 一段“如果继续做,我会优先改什么”的总结

做成作品集时,最值得展示什么

如果你想把这题做成作品集页,最值得展示的不是一长串模型名字,而是:

  1. 你的 baseline 是什么
  2. 你做了哪一轮最有效的改进
  3. 改进前后 RMSE / R² 怎么变了
  4. 你通过残差分析发现了什么
  5. 你下一步准备怎么继续提升

项目检查清单

  • 完成 EDA:分布、相关性、缺失值
  • 特征工程:构造至少 2 个新特征
  • 至少对比 3 种模型
  • 对最佳模型做超参数调优
  • 残差分析和特征重要性分析

版本路线建议

版本目标交付重点
基础版跑通最小闭环能输入、能处理、能输出,并保留一组示例
标准版形成可展示项目增加配置、日志、错误处理、README 和截图
挑战版接近作品集质量增加评估、对比实验、失败样本分析和下一步路线

建议先完成基础版,不要一开始就追求大而全。每提升一个版本,都要把“新增了什么能力、怎么验证、还有什么问题”写进 README。