简介面向刚接触神经网络与Python机器学习的开发者以波士顿房价经典数据集演示Keras回归建模的完整流程数据标准化、训练集与测试集划分、Sequential模型逐层搭建、损失函数与优化器设置以及训练过程中的损失和MAE曲线绘制帮助理解回归任务的核心步骤与过拟合判断。压缩包共2个文件均为可运行的py脚本整体约3KB分别对应基础神经网络建模和使用优化后的参数进行房价预测便于对照学习、修改参数并扩展实验。脚本内包含StandardScaler标准化处理、多隐藏层Dense设计、mean_squared_error与Adam组合同时输出训练/验证损失曲线及误差指标读者可直观看到模型在训练周期内的表现变化。已有2681人学习下载对希望快速上手Keras回归建模和波士顿房价预测实战的开发者有直接参考价值。1. 波士顿房价预测与Keras回归这不是分类而是连续值的“拟合”游戏许多第一次接触 Keras 的从业者都以为神经网络只做图像分类、情感识别这类离散标签任务。实际上回归问题才是工业界最常见的落地场景预测房价、销量、温度、设备剩余寿命输出是一个连续数值。标题里这个 keras 神经网络解决回归问题实例_波士顿房价预测正好是入门回归最稳妥的一条路数据集只有 13 个特征、506 条样本不需要分布式训练单机 CPU 跑几十秒就能看到完整的训练曲线和评估指标。波士顿房价预测的经典价值在于它把“特征工程—网络结构—损失函数—过拟合控制”这条链路完整串起来了适合想快速验证自己是否理解神经网络回归的读者。这篇文章会从选型讲到参数再到几处容易让新手翻车的细节最后收在能直接落地的验证技巧上。2. 回归问题与Keras选型为什么波士顿房价是入门首选以及前馈神经网络的定位2.1 回归 vs 分类损失函数和输出层怎么定同样的数据做分类和做回归神经网络的“出口”是完全不一样的。分类任务里最后一层通常用 Softmax 激活输出每个类别的概率总和为 1而回归任务的输出层只有一个神经元并且不能加激活函数或者说用线性激活这样网络才能输出任意范围的连续值。如果把回归当成分类做——比如把价格离散化成几个档位——你会丢掉数值之间的顺序关系和数值大小关系模型学到的只是“属于哪个桶”而不是“大概多少钱”。损失函数的选择同样由任务性质决定。分类常用 categorical_crossentropy回归则用 mean_squared_errorMSE或 mean_absolute_errorMAE。MSE 对大误差敏感梯度对离群点反应强烈MAE 更稳健但梯度在误差接近零时会抖动。波士顿房价数据集里有个别价格被截断在 50 的情况MEDV 最大值 50这种“人为截断”会让 MSE 在预测高价房时产生持续惩罚这也是我后来发现很多人对结果不满意的原因之一。后面在避坑章节再细说。2.2 波士顿房价数据集13个特征和MEDV的真实含义这个数据集最早来自 1978 年美国波士顿地区的房屋信息但它的意义不在“房价”本身而在它作为回归基准数据集的传播度。每条样本有 13 个特征包括犯罪率CRIM、住宅用地比例ZN、非零售商业用地比例INDUS、是否邻近河流CHAS0/1、氮氧化物浓度NOX、平均房间数RM、1940 年以前建成的自住房比例AGE、到就业中心距离DIS、高速公路可达性RAD、房产税率TAX、师生比PTRATIO、黑人比例B以及低社会地位人口比例LSTAT。目标 MEDV 是自有住房的中位数价格单位是千美元。13 个特征里既有连续值NOX、DIS也有二元值CHAS还有取值跨度极大的 RTAX从 1 到 24和 TAX从 187 到 711。这种量纲差异会让神经网络训练变得很慢甚至不收敛。波士顿房价这个案例之所以适合教学就是因为它的特征类型足够杂逼着你先做标准化再做训练而不是直接把原始数据丢进模型。Keras 本身不会帮你做特征归一化这是整个项目里最容易出错、也最容易出效果的一步。2.3 用Keras搭建前馈神经网络的常见做法从Sequential到模型参数Keras 的常见做法是用 tf.keras.Sequential 逐层堆叠前馈神经网络。对于波士顿房价这种 13 维输入的回归问题三层网络往往就够用了输入层-隐藏层-输出层。隐藏层可以加 ReLU 激活函数输出层不加激活。一个最小可跑版本只有十几行代码这是很多人选择 Keras 而不是 PyTorch 入门的原因API 风格接近“搭积木”fit 方法内置了训练循环不用自己写 for epoch 和 backward。但“简单”不意味着“照抄就行”。层数、神经元个数、Dropout 位置、优化器参数这些都需要你对数据规模有判断。506 条样本是个非常小的数据量级如果隐藏层堆到 1024 个神经元模型几乎一定会走上学“背答案”的路子验证集 loss 会一路飙升。我一般建议先从 64-64-1 这种规模起步确认基线能跑通再谈调宽或加深。3. 从零实现Keras波士顿房价回归数据预处理、模型构建与训练全流程3.1 环境准备Keras安装与依赖很多新手卡在第一步装好了 TensorFlow 却 import keras 失败。这里有一个容易混淆的点你现在用的 Keras 是 tf.keras它是 TensorFlow 的高层 API 的一部分安装 TensorFlow 就会自带。如果你写的是外部的 Keras 2.x 包需要独立安装并与 TensorFlow 版本匹配。推荐直接用 TensorFlow 内置的 Keras 模块。# 建议用虚拟环境避免依赖冲突 python -m venv keras_reg_env source keras_reg_env/bin/activate # Windows 下是 keras_reg_env\Scripts\activate pip install tensorflow2.10,2.16 pandas numpy matplotlib scikit-learn逻辑说明这条命令先创建虚拟环境再安装 TensorFlow、pandas、numpy、matplotlib 和 scikit-learn。指定 tensorflow 版本范围是为了防止新版本 API 变化影响复现2.10 到 2.15 的 tf.keras API 在本文涉及的函数上没有破坏性改动。scikit-learn 用来做数据切分和标准化matplotlib 用来画 loss 曲线和预测对比图。安装完成后用 python -c import tensorflow as tf; print(tf.version) 验证一下能打印版本号就说明环境没问题。3.2 加载和划分数据集train_test_split与标准化波士顿房价数据集在较新版本的 scikit-learn 里被移除了因为当年数据集存在一些争议。常见做法是从 keras.datasets 里拉取或者用 sklearn 的 fetch_openml 替代。这里我直接给出一个可跑的方案用 keras.datasets.boston_housing 加载如果你用的是 TensorFlow 2.15这个接口依然可用。import numpy as np import tensorflow as tf from tensorflow.keras.datasets import boston_housing from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 加载波士顿房价数据 (x_train, y_train), (x_test, y_test) boston_housing.load_data() # 合并后重新切分保证验证集也来自相同分布 x_all np.concatenate([x_train, x_test], axis0) y_all np.concatenate([y_train, y_test], axis0) # 按 80/10/10 切分 x_train, x_val, y_train, y_val train_test_split( x_all, y_all, test_size0.2, random_state42 ) x_val, x_test, y_val, y_test train_test_split( x_val, y_val, test_size0.5, random_state42 ) # 标准化只在训练集上计算均值和方差 scaler StandardScaler() x_train_scaled scaler.fit_transform(x_train) x_val_scaled scaler.transform(x_val) x_test_scaled scaler.transform(x_test)逻辑说明keras.datasets.boston_housing 自带训练测试切分但默认切分比例是 404/102样本量本身很小。我合并后重新切分是为了让验证集、测试集和训练集的分布更接近避免“运气好”或“运气差”导致对模型能力误判。random_state42 固定下来保证别人复现时结果一致。标准化这一步用的是 StandardScaler它在训练集上调用 fit_transform 计算均值和标准差然后把同样的数值变换应用到验证集和测试集。绝对不能对全量数据一起 fit否则验证集和测试集的信息就“剧透”进了训练阶段得到的是虚高的效果。Boston 各特征量纲差异极大RAD 和 TAX 动辄几百CRIM 又有小数不标准化时梯度更新会被大数值特征主导网络收敛很慢。3.3 构建回归网络Dense层、激活函数与编译参数这一步是用 Keras 构建神经网络解决回归问题的核心。模型结构上我用两个全连接隐藏层每层 64 个神经元激活函数选 ReLU。输出层不加激活函数因为我们要输出连续房价数值。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout from tensorflow.keras.optimizers import Adam model Sequential([ Dense(64, activationrelu, input_shape(13,)), Dropout(0.2), Dense(64, activationrelu), Dropout(0.2), Dense(1) # 输出层不要加激活函数 ]) model.compile( optimizerAdam(learning_rate0.001), lossmean_squared_error, # 回归任务用 MSE metrics[mae] ) model.summary()逻辑说明Dense 层的意思是全连接层每个神经元与上一层所有输出相连。第一个 Dense 要显式声明 input_shape(13,)匹配波士顿房价的 13 个特征。中间的 Dropout 层在训练时随机失活一部分神经元用来抑制过拟合——506 条样本太容易过拟合不加 Dropout 的话验证集 loss 后期几乎必然反弹。Dropout 只影响训练预测时它会自动恢复全量连接不需要你手动处理。编译时优化器用 Adamlearning_rate 先取 0.001。这是 Adam 论文里推荐的默认值对波士顿房价这个小数据集足够温和。loss 用 mean_squared_error因为房价预测是典型回归MSE 能放大离群点的错误让模型优先优化“偏得离谱”的样本。metrics 里额外加了 mae是为了看绝对误差的直观含义——单位是千美元如果 mae 是 2.5平均预测误差大约 2500 美元。3.4 训练与可视化fit返回Historyloss曲线怎么读训练时把验证集传进 validation_dataKeras 会在每个 epoch 结束后自动计算验证 loss 和验证 mae。fit 函数返回的 History 对象记录了全部训练曲线这是后续观察过拟合、判断是否早停的重要依据。history model.fit( x_train_scaled, y_train, validation_data(x_val_scaled, y_val), epochs200, batch_size16, verbose1 ) # 打印最终评估 test_loss, test_mae model.evaluate(x_test_scaled, y_test, verbose0) print(f测试集 MAE: {test_mae:.2f} 千美元)import matplotlib.pyplot as plt plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.xlabel(epoch) plt.ylabel(MSE) plt.legend() plt.yscale(log) # 用对数尺避免前面的大loss压扁后面曲线 plt.show()逻辑说明batch_size16 在 404 条训练样本上每个 epoch 大约有 25 个 batch梯度更新次数适中。epochs200 不是让你一定跑满而是配合 EarlyStopping 使用会更合理如果直观观察 loss 曲线你会发现 val_loss 在某个点后开始上升就是过拟合信号的开始。把 yscale 设为 log 是因为 MSE 在前几个 epoch 可能从几百掉到十几线性坐标下后面的细节根本看不清。测试集 MAE 如果在 2.5 到 3.5 之间千美元说明模型已经达到经典实现的常见水平如果高于 4优先检查标准化是否做对了。4. 关键参数与调优让模型不“玄学”的四个必调项4.1 归一化的尺度StandardScaler 与 MinMaxScaler 的差别很多人以为归一化只是“除以最大值”这种直觉在回归里会带来隐蔽的坑。波士顿房价的 13 个特征里有的偏态严重比如犯罪率 CRIM 极小值接近 0但最大值接近 90均值却只有 3.6。如果做 MinMaxScaler 把数据压到 [0,1]CRIM 的分布会被拉得很歪大部分样本都挤在靠近 0 的位置而 StandardScaler 用 (x - mean) / std 做变换保留了数据的分布形态更适合输入给 ReLU 这类非饱和激活函数。# MinMaxScaler 在异常值面前不稳定 # 最小值 0.006最大值接近 90均值 3.6 # 标准化之后大部分值会落在 -0.5 到 0.5 之间分布更平滑实际调参时我一般先用 StandardScaler 作为默认因为神经网络对均值为 0、方差为 1 的输入收敛最快。MinMaxScaler 对激活函数输出范围有硬约束的场景比如最后一层用 sigmoid更合适但回归输出层没有这个问题。还有一种做法是使用 RobustScaler它对离群点更鲁棒因为只基于中位数和四分位数波士顿房价的 MEDV 有截断特征RobustScaler 有时候比 StandardScaler 的测试误差低 5% 左右但代价是解释性稍差。你需要自己做一个 A/B 对比而不是听别人说哪个必好。4.2 神经元数量与层数为什么三层往往够用波士顿房价只有 506 条样本13 维输入模型容量需求很低。一个隐藏层 64 神经元在理论上就足够逼近任意连续函数万能近似定理但实际训练中单层容易出现过拟合所以加第二层让网络学习特征组合往往比只加宽第一层更有效。常见的组合有 64-32-1、32-32-1、64-64-1我推荐从 64-64-1 起步然后向下调。要注意的是神经网络的表达能力越强越容易把训练样本的噪声也记住。在 506 条样本上隐藏层神经元总数超过 200 几乎一定会触发过拟合表现为训练 loss 持续下降而验证 loss 在某个 epoch 后转头上升。用 Keras 检查模型参数量的方式很简单model.summary() 会打印每层参数个数。64-64-1 结构大约有 (13×64 64) (64×64 64) (64×1 1) 5100 个参数对于一个不到 500 条样本的数据集这个容量已经不小。如果加上 Dropout实际有效容量更小但稳定得多。4.3 学习率与优化器Adam 的默认值够用吗Adam 优化器的默认学习率是 0.001在很多小数据集上可以直接用。但波士顿房价存在特征尺度差异和输出量纲较大几十的问题0.001 的 Adam 在 MSE 损失下可能表现出两个阶段前期快速下降接近最低点时又变得很慢甚至抖振。你可以在训练日志里看到 loss 在 20 到 30 之间反复横跳不再下降。我的做法是先把固定学习率设成 0.0001 跑几轮看验证 loss 的下限再用学习率调度器或回调降低学习率。Keras 里最简单的实现是 ReduceLROnPlateau它会在验证 loss 连续若干 epoch 不下降时自动缩小学习率避免手动值守。from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping reduce_lr ReduceLROnPlateau( monitorval_loss, factor0.5, patience15, min_lr1e-6, verbose1 ) # 训练时把回调加进去 model.fit( x_train_scaled, y_train, validation_data(x_val_scaled, y_val), epochs300, batch_size16, callbacks[reduce_lr], verbose1 )逻辑说明factor0.5 表示验证 loss 15 个 epoch 没进步时学习率减半。min_lr1e-6 防止学习率降到零导致模型彻底不更新。配合这个回调初始学习率可以让它大一点比如 0.001因为机制会自动调整。手动调学习率容易陷入“加太大不收敛、加太小慢死”的恶性循环回调相当于给模型装了一个自动油门。4.4 验证集与早停如何用 EarlyStopping 防止过拟合EarlyStopping 是回归任务里最有效的防过拟合手段。它监控验证集指标连续 N 个 epoch 没有改善就停止训练并通过 restore_best_weights 把模型权重恢复到历史最优位置。early_stop EarlyStopping( monitorval_loss, patience30, restore_best_weightsTrue ) history model.fit( x_train_scaled, y_train, validation_data(x_val_scaled, y_val), epochs500, batch_size16, callbacks[early_stop, reduce_lr], verbose0 ) print(f早停在 epoch {len(history.history[loss])} 结束)逻辑说明patience30 表示允许验证 loss 连续 30 个 epoch 不优化才停止。小数据集噪声大patience 设太短比如 5容易在曲线刚刚波动时提前刹车模型欠拟合设太长又浪费算力。30 是波士顿房价这类数据上的经验值。restore_best_weightsTrue 很关键如果不写训练停止时会保留最后一个 epoch 的权重而这个权重往往已经过拟合了一段路程。5. Keras回归实战中的常见坑与排查从NaN到过拟合的5条血泪经验5.1 损失函数用错了MSE 还是 MAE现象模型训练时 loss 能降但测试集 MAE 偏高而且预测值整体偏向中位数极高价和极低价都测不准。原因用了 mean_squared_error 作为 loss但你的评估指标却是 MAE。MSE 对大误差的惩罚是平方级模型会优先规避那些离群点牺牲整体分布形状如果数据存在右侧长尾MSE 会把预测往均值方向拉。解决如果最终业务关心的是“中位误差”把 loss 直接改成 mean_absolute_error。Keras 支持在 compile 里直接指定 lossmae模型就会按 MAE 的梯度更新。波士顿房价数据集上用 MAE 训练的模型测试集中位数误差往往略低但极端值的预测误差可能更大。你需要根据业务场景选定价系统希望避免离谱报价就选 MSE如果希望大多数预测接近真实值就选 MAE。5.2 标准化时把测试集也“剧透”了现象训练曲线很漂亮验证集指标也很惊艳但换到真正未知数据时预测一塌糊涂。原因这是最常见的隐性错误——你拿到全部数据计算均值和方差然后一次性 fit_transform 所有样本。这样训练集里实际包含了测试集的信息相当于考试时提前看到了答案。Keras 和 sklearn 本身不会拦着你很多教程也偷懒这么写所以照抄容易翻车。解决严格遵循“先切分再在训练集上 fit再 transform 验证和测试集”。代码上看起来只差一行但数据泄漏的方向完全不同。如果你是随机森林或线性回归用户可能觉得“影响不大”但在神经网络上因为迭代多、容量大标准化的细微偏差会被放大最终线上效果和验证效果差距会很大。5.3 输出层没有去掉激活函数现象训练 loss 怎么调都不低于某个值预测值总是被限制在一个范围内比如 0 到 1 之间。原因输出层带了 sigmoid 或 tanh。sigmoid 输出范围是 (0,1)tanh 是 (-1,1)而波士顿房价中位数价格是以千美元为单位真实范围大约 5 到 50。如果输出层加了 sigmoid模型再怎么学也只能输出 0~1然后被 MSE 逼着往 50 倍的尺度上撞loss 不可能低。解决回归任务的输出层不加激活函数这是原则。代码里写成 Dense(1) 就够了不要写 Dense(1, activationlinear)——虽然 linear 本质上等于没有但更容易让人误以为这里必须指定什么。如果看到预测值范围被“卡”住第一反应就是检查模型输出层和激活函数配置。5.4 小数据集上验证集波动大怎么解读现象验证 loss 曲线像锯齿每个 epoch 上下跳跃模型训练没法判断到底有没有收敛。原因验证集只有 51 条样本batch 间波动很大单条样本的误差就能显著改变验证 loss。这不是模型坏了而是验证集太小统计噪声大。解决不要把每个 epoch 的验证 loss 当唯一标准。一种做法是增大验证集比例比如从 10% 改成 20%但删除训练样本会降低模型上限另一种做法是用 K-fold 交叉验证取平均。波士顿房价规模小训练一个模型只要几秒用 5 折交叉验证能得到更稳的估计。实现上可以用 scikit-learn 的 KFold在每一折上重新训练并记录验证 MAE最后输出均值和标准差。标准差如果超过 0.8 千美元说明模型对数据划分很敏感需要警惕结果的可复现性。5.5 训练轮次过多导致 loss 曲线“先降后升”现象训练 loss 持续下降验证 loss 到某个 epoch 达到最低点后开始明显上涨并且之后不再回来。原因这就是典型的过拟合。模型参数量相对数据量过大或训练时间过长。波士顿房价只有 506 条样本隐藏层 128 神经元 无 Dropout 500 轮训练100% 会出现这种现象。解决EarlyStopping 是直接手段但更根本的是降低模型容量或增强正则化。Dropout 加在隐藏层之后一般取 0.2 到 0.3如果还过拟合把神经元数量从 64 减到 32。也可以尝试 L2 正则Keras 里 Dense(64, kernel_regularizerl2(0.001))它会惩罚大权重让模型更“朴素”。我个人的顺序是先加 EarlyStopping再试 Dropout最后上 L2每一步都看验证 loss 的变化曲线而不是只看最终数字。6. 把回归模型用到生产前验证与解释技巧以及一个我常用的“后悔药”6.1 用交叉验证估计模型稳定性波士顿房价数据量小单次划分的验证 MAE 波动很大。我常用 sklearn 的 KFold 配合 Keras 的回归模型做 5 折验证。不要手动写训练循环这里有一个更省事的做法用 scikit-learn 的 KerasRegressor 封装器keras.wrappers.scikit_learn 在旧版中可用新版推荐自己封装一个简单类但如果你不想依赖封装器可以直接写一个 for 循环。from sklearn.model_selection import KFold kf KFold(n_splits5, shuffleTrue, random_state42) mae_scores [] for train_idx, val_idx in kf.split(x_all_scaled): x_tr, x_va x_all_scaled[train_idx], x_all_scaled[val_idx] y_tr, y_va y_all[train_idx], y_all[val_idx] model create_boston_model() # 你前面定义好的构建函数 history model.fit(x_tr, y_tr, validation_data(x_va, y_va), epochs300, batch_size16, callbacks[early_stop], verbose0) val_mae model.evaluate(x_va, y_va, verbose0)[1] mae_scores.append(val_mae) print(f5折验证 MAE: {np.mean(mae_scores):.2f} ± {np.std(mae_scores):.2f} 千美元)逻辑说明KDold 会把所有样本轮流作为验证集最终得到 5 个验证 MAE。均值代表模型的平均表现标准差代表数据切分带来的不稳定程度。如果标准差大于 1说明你的模型对数据选择太敏感即使改随机种子也可能得到差异很大的结果。这时候先别急着调参数回去看看是不是样本里有极端离群点或者是不是标准化时出了问题。6.2 回归结果可视化真实值 vs 预测值散点图光看 MAE 不够我习惯把测试集真实价格和预测价格画在同一个散点图里。理想情况下点应该分布在 y x 直线附近。如果点子在低价段堆在上方、高价段堆在下方说明模型存在系统性偏差——它倾向于把预测值往均值方向“拉”。这种情况下只看 MAE 会被掩盖因为正负误差抵消掉了。y_pred model.predict(x_test_scaled).flatten() plt.scatter(y_test, y_pred, alpha0.6) plt.plot([0, 50], [0, 50], r--, labelPerfect Prediction) plt.xlabel(True price (k$)) plt.ylabel(Predicted price (k$)) plt.legend() plt.show()逻辑说明scatter 的横轴是真实 MEDV纵轴是模型输出。如果散点明显偏离红线特别是大值端偏低、小值端偏高说明模型偏向均值回归。这个现象在小样本回归里很常见因为 MSE 的最优策略就是往均值收缩。看到这种形状时可以考虑用 MAE 损失或者把输出目标做 log 变换把右侧长尾压平让模型不再“偏爱”中位数。6.3 用交叉验证的“后悔药”保存最优模型权重训练过程中模型可能一度达到很好的验证 loss但后续又退化。EarlyStopping 的 restore_best_weights 已经帮你把权重恢复到了最优位置但只存在于内存里。如果之后你想重新评估或者部署最好手动保存一份。model.save(boston_regression.keras)Keras 3 新格式用 .keras 后缀保存的是完整模型结构和权重。加载时用 tf.keras.models.load_model不用重新定义网络。这是我在做多个对比实验时的习惯每次实验跑完第一件事就是保存最优权重再去做后续分析免得机器重启或跑崩后只能从头再来。6.4 一个实用技巧把训练历史存成 CSV方便回溯我个人血的教训是跑实验不记录历史过两周就忘了当时参数到底为什么好。fit 返回的 History 对象只在内存里退出脚本就没了。我通常会在训练结束后把 loss 和 val_loss 写入 CSV方便回看曲线也能对比不同参数组合的表现。import pandas as pd hist_df pd.DataFrame(history.history) hist_df.to_csv(history_64x2_dropout0.2_bs16.csv, indexFalse)这样哪怕模型效果不理想你也能从曲线里分析出是欠拟合还是过拟合而不是靠记忆。如果后续想用网格搜索找最优参数可以配合 sklearn 的 ParameterGrid 遍历不同 Dropout、层宽和 batch_size每次都保存历史记录。这套流程做完波士顿房价预测这个实例才算真正落地——你不仅跑通了一个模型还建立了一条“训练-验证-保存-复盘”的闭环。做回归项目久了我最深的体会是Keras 把神经网络的实现门槛压得很低但数据切分、标准化、过拟合控制这些“代码之外”的细节才是决定效果和稳定性的关键。遇到结果不对劲别急着堆神经元先画一条验证 loss 曲线看看。希望帮到你。本文还有配套的精品资源点击获取