简介:面向深度学习与机器学习初学者,这是一份基于Keras的Python项目实战教程,聚焦波士顿房价预测这一经典回归问题。资源文件打包为1个PDF文档,大小约366KB,内容集中,便于配套学习。目前已有1348人学习浏览,是入门回归建模的实用参考。教程从回归问题描述入手,详细解析波士顿房价数据集中14个特征的含义,演示数据标准化(StandardScaler)、搭建单隐藏层(13个神经元,ReLU激活)的Keras神经网络,并通过KerasRegressor配合K折交叉验证评估基线模型,再借助GridSearchCV进行超参数搜索优化。读者可按步骤复现从构建基准模型到降低均方误差的完整过程,理解回归问题的建模思路与调参方法,为类似趋势预测任务提供可迁移的实践范式。
1. 为什么拿波士顿房价练手:一个 13 维特征的小数据集,足以跑通 Keras 全流程
如果你手里只有一两天时间,想从零把“深度学习”这个词落地成一次能跑的 Python 项目,波士顿房价预测绝对是最合适的第一站。这个项目用 Keras 搭一个回归网络,输入 13 个房屋特征,输出一个连续的房价数值。它不涉及图像、不涉及序列,数据量只有 506 条,几秒钟就能训练完。正因为小,你能把数据预处理、模型搭建、编译训练、评估预测这一整条链路看清楚,之后再上图像分类或文本任务,无非是换数据和换网络结构。
这份教程的受众很明确:已经会写基础 Python、知道 numpy 和 pandas 是什么,但还没真正写完一个深度学习项目的人。你不需要懂高等数学,只需要照着步骤复制代码,再看着注释理解每一行在干什么。跑通之后,你会得到一个能预测房价的模型,更重要的是你对 Keras 的 API 有了体感——什么样的数据能喂进去、训练时报错该看哪里、模型效果不行第一步查什么。下面我按照做这个项目最常见的落地路径,把从环境到调参的完整过程拆开讲。
2. 先把环境立起来:Python、Keras 与 TensorFlow 后端的装法与版本陷阱
动手写模型之前,最劝退新手的一步其实是环境配置。波士顿房价预测本身用不到 GPU,一台普通笔记本就能跑,但 Python 版本、Keras 版本、TensorFlow 版本之间的组合不对,代码还没开始写就先被报错淹没了。我一般建议用虚拟环境把项目隔离起来,而不是直接装到系统 Python 里,后面你就会知道这个习惯能省多少后悔药。
2.1 用虚拟环境隔离项目依赖
很多人图省事直接pip install keras,结果系统里同时存在旧版 TensorFlow 和新版 Keras,互相覆盖依赖,最后 import 都报错。常见做法是先用python -m venv建一个干净环境,之后再往里装东西。这样就算把环境搞坏了,删掉重建也只需要两分钟。
python -m venv boston_env source boston_env/bin/activate # Windows 下执行 boston_env\Scripts\activate pip install --upgrade pip setuptools wheel这三条命令做的事情是:创建一个名为boston_env的虚拟环境,激活它,然后把 pip 本身升级到较新版本。之后所有安装都发生在这个环境里,不会污染系统自带的 Python。注意source是 macOS 和 Linux 的写法,Windows 用户要切换到Scripts\activate。这一步跑通后,命令行提示符前面会出现(boston_env),说明环境已经激活。
2.2 Keras 2.x 与 TensorFlow 2.x 的安装参数
Keras 从 2.3 版本开始并入了 TensorFlow 2,现在你只需要安装 TensorFlow 就能用from tensorflow import keras。但网上很多旧教程还在教pip install keras单独装,然后keras.backend报错,多半就是版本混搭造成的。我建议统一走 TensorFlow 的 Keras API,不要单独安装 keras 包。
pip install tensorflow==2.15.0这里的版本号你可以根据自己的 Python 版本微调。TensorFlow 2.15 要求 Python 3.9 到 3.11,如果你用的是 Python 3.12,建议装 2.16 或更新的版本。装完以后验证一下:
python -c "import tensorflow as tf; print(tf.__version__); print(tf.keras.__version__)"这条命令会打印 TensorFlow 版本和附加的 Keras 版本。如果输出正常说明环境已经没有问题。常见的坑是只装了tensorflow-cpu或装成了tensorflow-gpu,这两个接口在导入时没有区别,但 GPU 版还需要 CUDA 和 cuDNN 环境,波士顿房价这种小模型用 CPU 版反而更省心。
2.3 验证 Keras 是否真的可用:跑一个最小 MLP
环境装完之后,我习惯先跑一个十行代码的“冒烟测试”,确认网络能前向传播、能反向传播,而不是一上来就写完整项目。这个习惯能帮你区分是环境问题还是代码逻辑问题。
from tensorflow import keras from tensorflow.keras import layers import numpy as np x = np.random.rand(64, 13).astype("float32") y = np.random.rand(64, 1).astype("float32") model = keras.Sequential([ layers.Dense(16, activation="relu", input_shape=(13,)), layers.Dense(1) ]) model.compile(optimizer="adam", loss="mse") model.fit(x, y, epochs=1, batch_size=16, verbose=0) print("Keras 冒烟测试通过")这里的input_shape=(13,)要和波士顿房价数据的特征数对齐,后面正式训练时同样是 13。layers.Dense(16)表示第一层有 16 个神经元,activation="relu"是激活函数,最后一层Dense(1)是输出层,因为回归任务输出一个标量。编译时用mse作为损失函数,adam是优化器。如果你的环境没问题,运行后应该直接打印“通过”。如果报错说缺 DLL 或者 libstdc++,那基本是 TensorFlow 安装时缺了系统依赖,回到上一步重新检查 Python 版本和 TensorFlow 版本匹配关系。
3. 读懂波士顿房价数据:13 个特征的含义与预处理边界
环境就绪之后,真正的工作是从数据开始的。波士顿房价数据集是 1978 年美国波士顿郊区房屋价格数据,包含 506 条样本。每条样本有 13 个特征,对应城镇犯罪率、房间数、房龄、到就业中心距离等指标。Keras 自带的keras.datasets.boston_housing可以直接加载,但你要理解每个特征的范围差异有多大——最小的是几个小数值,最大的是几百,这种数量级差异如果不做处理,神经网络很难正常训练。
3.1 数据从哪里拿,长什么样
在 Keras 2.x 里,数据加载一行就够了:
from tensorflow.keras.datasets import boston_housing (train_data, train_targets), (test_data, test_targets) = boston_housing.load_data() print("训练集形状:", train_data.shape) print("测试集形状:", test_data.shape) print("特征示例:", train_data[0]) print("对应房价:", train_targets[0])train_data是 (404, 13) 的二维数组,404 条训练样本,每条 13 个特征;test_data是 (102, 13)。train_targets是 (404,) 的一维数组,每个值是该样本的房价中位数,单位是千美元,比如 24 表示 24000 美元。打印出来的特征示例会是一串数字,从犯罪率到房龄都有,数量级差异很大,比如有的特征在 0 到 1 之间,有的在 300 以上。这直接决定了你必须做归一化。
3.2 归一化为什么是必须项,而不是可选项
神经网络对输入尺度非常敏感。如果某些特征在 0 到 1 之间,另一些在 100 到 500 之间,梯度更新会被大数值特征主导,小数值特征几乎学不到东西。更糟的是,激活函数在输入绝对值很大时容易进入饱和区,梯度变成 0,训练就停滞了。所以归一化不是“锦上添花”,是“不做就训练不出来”。
常见做法是用训练集的均值和标准差归一化,然后把同样的统计量应用到测试集。注意不能把测试集混进来计算均值和标准差,否则会造成数据泄露,让评估结果虚高。代码如下:
mean = train_data.mean(axis=0) std = train_data.std(axis=0) train_data_norm = (train_data - mean) / std test_data_norm = (test_data - mean) / std print("归一化后训练集均值趋近 0:", train_data_norm.mean(axis=0))关键点都在axis=0,也就是按特征列而不是按样本行做统计。每个特征都有自己的均值与标准差,归一化后每个特征都变成均值为 0、标准差为 1 的分布。这样模型在梯度下降时,对所有特征一视同仁。实际项目中,如果你换了一批新预测数据,也要用这个历史均值和标准差去归一化,而不是重新算,否则模型输入分布会变。
3.3 训练集/测试集划分的两种做法与选择
Keras 自带的数据集已经帮我们分好了 404 条训练、102 条测试,但实操中经常要自己划分。常见的做法有两种:一是用 scikit-learn 的train_test_split,二是直接用keras.utils的切分接口。如果你用的是自带数据,直接用训练集测试集的变量即可;如果你自己收集了数据,我建议用第一种:
from sklearn.model_selection import train_test_split X_train, X_temp, y_train, y_temp = train_test_split( train_data, train_targets, test_size=0.2, random_state=42 ) X_val, X_test, y_val, y_test = train_test_split( X_temp, y_temp, test_size=0.5, random_state=42 )这里先切出 20% 作为临时集,再把临时集对半分,分别得到验证集和测试集。random_state=42固定随机种子,保证每次运行切分结果一样,否则你换一次运行,模型评测的基准就变了。波士顿数据量小,切分时会损失训练样本,所以 Keras 自带的 404/102 划分已经够用,自己在做大型项目时再考虑更多重交叉验证。
4. 用 Keras 搭一个回归网络:从 Sequential 到模型训练
数据准备好之后,就到了核心环节:用 Keras 搭建和训练模型。回归任务和分类任务最大的区别在于输出层没有激活函数,神经元数量是 1,损失函数用均方误差。很多人套用分类模型的写法,最后一层加上 softmax,直接报错或损失永远不下降。这一章我把网络结构、编译参数、训练循环每个环节的参数都讲清楚。
4.1 网络结构设计:隐藏层数与神经元数的取舍
对于 13 个特征、506 条样本的数据量,网络不能太深。常见做法是两层隐藏层,第一层 64 个神经元,第二层 64 个神经元,或者更保守的 16-16。神经元越多,模型容量越大,但也越容易过拟合。我的经验是从小到大试:先试一层 16 个神经元,看验证 loss 是否下降,再逐步加深加宽。
from tensorflow import keras from tensorflow.keras import layers model = keras.Sequential([ layers.Dense(64, activation="relu", input_shape=(13,)), layers.Dense(64, activation="relu"), layers.Dense(1) # 回归输出,不带激活函数 ])input_shape=(13,)告诉模型输入是 13 维向量。layers.Dense(64, activation="relu")表示这一层有 64 个神经元,输出经过 ReLU 激活变成非线性。中间的隐藏层越多,模型能学到的组合特征越复杂,但在小数据集上很容易把训练集的噪声也背下来。最后一层Dense(1)不带激活函数,因为房价预测是回归,输出可以是任意实数,如果加了 sigmoid 或 relu 反而限制了输出的范围。
4.2 编译参数:loss、optimizer 与 metrics 怎么设
编译是模型训练前的配置步骤,这里要决定优化器、损失函数和衡量指标。对回归问题来说,mse均方误差是最常用的损失函数,优化器选adam基本不用调参就能收敛。衡量指标我习惯加一个mae平均绝对误差,因为 MAE 的单位和房价一致,比如mae=3说明平均预测偏差 3000 美元,比 MSE 更直观。
model.compile( optimizer="adam", loss="mse", metrics=["mae"] )optimizer参数可以只传字符串名字,Keras 会自动用默认学习率。如果你想微调学习率,可以换成keras.optimizers.Adam(learning_rate=0.001)。注意学习率 0.001 是 Adam 的默认值,小数据集上通常不需要改。metrics列表可以传多个指标,这里我们只需要 MAE。如果你用accuracy,运行时会报错或一直为 0,因为回归任务的输出是连续值,用准确率没有意义。
4.3 训练循环与验证曲线:epochs 和 batch_size 怎么调
训练过程用fit方法,传入训练数据、验证数据、迭代轮数和批次大小。这里最容易犯的错是 epochs 设太大导致过拟合,或者设太小还没收敛。我建议先设 100,同时看训练集和验证集的 loss 曲线再调整。
history = model.fit( train_data_norm, train_targets, validation_data=(test_data_norm, test_targets), epochs=100, batch_size=16, verbose=1 )epochs=100表示把整个数据集从头到尾训练 100 遍。batch_size=16表示每次从训练集取 16 个样本计算一次梯度。batch_size 越小,梯度更新越频繁,训练波动越大;越大,训练越平滑但需要更多显存。波士顿数据集只有 404 条,batch_size=16在每轮会产生 25 次更新,几秒钟就跑完。validation_data传入的是归一化后的测试集和未归一化的房价标签,注意验证集的特征必须用训练集的统计量归一化。
跑完 100 轮之后,你可以用history.history画出训练 loss 和验证 loss 的曲线。如果训练 loss 持续下降但验证 loss 在某个 epoch 后开始上升,那就是过拟合了,需要在后面加早停或正则化。
import matplotlib.pyplot as plt plt.plot(history.history["loss"], label="train_loss") plt.plot(history.history["val_loss"], label="val_loss") plt.legend() plt.xlabel("epochs") plt.ylabel("loss") plt.show()这张图上如果两条曲线越分越开,说明模型开始背诵训练集了。波士顿数据量小,过拟合是常态,不要追求训练 loss 降到 0,只要验证 loss 足够低并且不再明显下降就能接受。
5. 避坑/常见问题:波士顿房价预测里的 5 个实战踩坑记录
这个项目看起来简单,真正自己写过一遍的人基本都踩过下面这些坑。每个坑我都按“现象 → 原因 → 解决”的顺序来写,下次你遇到可以直接对照排查。
5.1 现象:训练 loss 正常下降,测试 MAE 却高得离谱
原因很可能是在加载数据后没有做归一化就直接训练。数值大的特征主导了梯度,模型学到了“只看犯罪率加权平均”这种假规律,测试集上一旦数值分布稍变,预测就完全跑偏。解决方法是按第 3.2 节的方式,用训练集的均值和标准差归一化,注意测试集也用同一组统计量。另一个原因是数据集划分不均匀,有可能测试集里恰好有几条极端房价样本,MAE 被拉高。这种时候用交叉验证看整体表现,不要纠结单次划分。
5.2 现象:Keras 报错 ValueError: Shape mismatch
报错信息里会提示Input shape和Dense层期望的形状不一致。最常见的来源是input_shape=(13,)写成了(13,1)。前者表示 13 个特征的一维向量,后者表示 13 行 1 列的二维矩阵。如果你的训练数据是 (404, 13),那必须用(13,)。另一个原因是自己导入 CSV 时把特征列数读错了,比如数据里混入了序号列。解决方法是训练前打印train_data.shape,确认第二维是 13,如果不是,检查原始数据文件是否包含多余列。
5.3 现象:归一化后预测值变成负数,评估指标失效
归一化本身不会让房价变负,但如果你加载了原始数据直接预测,而训练时用了归一化后的数据,输入分布不对,模型输出的数值范围自然就错了。更隐蔽的情况是你把标签也归一化了,导致预测值在 0 附近,再反归一化时公式写错。建议只对特征做归一化,标签保持原样。如果你确实对标签做了归一化,记得预测后乘标准差加均值。写在代码里就是:
predicted = model.predict(test_data_norm) predicted_in_original = predicted * target_std + target_mean这里的target_std和target_mean是训练标签的统计量,来自train_targets.std()和train_targets.mean()。不要把它们跟特征归一化混在一起。
5.4 现象:无法下载数据集,代码卡在加载数据
boston_housing.load_data()在首次运行时会从网上下载数据到本地缓存目录。如果网络受限或者下载地址被墙,代码会一直卡在连接或者超时。解决方法是手动下载数据文件,放到~/.keras/datasets/目录(Windows 下是C:\Users\你的用户名\.keras\datasets\)。从其他镜像下载后重命名为boston_housing.npz,放进去就能加载。另外注意 Keras 2.15 之后的版本可能已经不能直接下载,建议装旧版本或从本地路径读取。这属于环境边界问题,不是你的代码问题。
5.5 现象:模型训练重复运行结果不一致
即使同样的数据、同样的网络结构,fit多次运行结果也会有浮动。原因有两个:一是初始化权重是随机的,二是fit内部的数据打乱顺序随机。这个浮动在波士顿这类小数据集上尤其明显,有时 MAE 差 0.5 就会影响你的调参判断。解决方法是在代码开头固定随机种子:
import random import numpy as np from tensorflow import keras random.seed(42) np.random.seed(42) keras.utils.set_random_seed(42)keras.utils.set_random_seed(42)会同时固定 TensorFlow 和 Keras 的随机状态。注意即使固定了种子,某些 GPU 操作仍然有非确定性,但 CPU 上基本可以复现。做实验时固定随机种子,才能对比不同网络结构之间的优劣,否则你以为自己调参有效,其实是随机噪声。
6. 让预测结果可解释:回归指标解读与模型改进技巧
模型训练完不是终点,你要能说清楚“预测得怎么样”,以及“还能怎么让它更好”。这一章我讲三个最实用的步骤:解读回归指标、保存模型供以后使用、以及用小技巧让模型更稳。
6.1 用 MAE、RMSE 与 R2 理解模型到底准不准
回归任务最常用的三个指标:MAE 平均绝对误差、RMSE 均方根误差、R2 决定系数。MAE 的意义是平均每个预测量偏差多少千美元,比如 2.8 表示平均偏差 2800 美元。RMSE 和 MAE 的差距能反映预测误差的分布,如果 RMSE 比 MAE 大很多,说明存在少数极端误差很大的样本。R2 越接近 1 越好,0 表示模型和直接用平均值预测的效果一样。你可以用 scikit-learn 快速计算:
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score pred = model.predict(test_data_norm).flatten() mae = mean_absolute_error(test_targets, pred) rmse = mean_squared_error(test_targets, pred, squared=False) r2 = r2_score(test_targets, pred) print(f"MAE: {mae:.2f}, RMSE: {rmse:.2f}, R2: {r2:.2f}")model.predict返回的形状是 (102, 1),所以用.flatten()压成一维,再和测试标签做比较。r2_score在测试集上是 0.7 左右就能算一个合格的回归模型,波士顿历届常见结果在 0.6 到 0.8 之间,如果低于 0.4 说明预处理或网络结构有问题。
6.2 保存与加载模型:h5 格式与后续部署
训练好之后把模型保存成文件,下次直接加载,不用重新训练。Keras 的model.save会保存网络结构和权重,加载后用predict就能做推理。
model.save("boston_model.h5") loaded_model = keras.models.load_model("boston_model.h5") new_pred = loaded_model.predict(test_data_norm[:5])model.save支持 h5 格式,文件里包含结构、权重、优化器状态和编译配置。加载时注意必须和保存时的 Keras 版本兼容,跨大版本加载有时会报错。如果你只想部署不想要优化器状态,可以用model.save_weights("boston_weights.h5")只存权重,加载时需要先重新构建模型结构再load_weights。实际项目中,我一般保存完整模型,省得每次重建结构。
6.3 一个实用技巧:用早停与正则化拿到更稳的模型
最后一个改进技巧是早停。训练时如果验证 loss 不再下降,继续训练只会过拟合。用 Keras 的EarlyStopping回调可以在验证 loss 连续 N 轮没有改善时自动停止训练,同时保存最佳权重:
from tensorflow.keras.callbacks import EarlyStopping callbacks = [ EarlyStopping( monitor="val_loss", patience=10, restore_best_weights=True, verbose=1 ) ] model.fit( train_data_norm, train_targets, validation_data=(test_data_norm, test_targets), epochs=200, batch_size=16, callbacks=callbacks, verbose=0 )monitor="val_loss"表示监控验证集的 loss,patience=10表示连续 10 轮没有下降就停,restore_best_weights=True让模型恢复到验证 loss 最低时的权重。这是我最常用的一个参数组合,小数据集上非常管用。加早停后,你不再需要手动调 epochs 精确到 100 还是 150,只要设一个大上限,模型自己会找到合适的停止点。
我自己的习惯是:每次跑完这个项目,都会把归一化用的均值和标准差存成一个 JSON 文件,再把模型存成 h5,这样以后对任意新房源预测时,先用 JSON 里的参数归一化,再喂给模型。可能有人觉得波士顿房价预测太简单,但正是这份简单让我养成了“数据统计量不落地不训练”的习惯。如果你跑完这个项目,希望也能收获这种对数据尺度敏感的感觉,而不是只得到一个漂亮的 loss 数字。希望帮到你。
本文还有配套的精品资源,点击获取