news 2026/10/21 15:11:51

Keras回归实战:波士顿房价预测与K折交叉验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Keras回归实战:波士顿房价预测与K折交叉验证

1. 从一个几乎被遗忘的数据集说起

波士顿房价数据集,英文原名 boston_housing,是机器学习入门阶段最经典的回归任务数据集之一。它最早来源于上世纪七十年代某地区的人口普查与房产调查数据,样本量只有506条,特征维度13个,目标变量是自住房的中位数房价。别看它小,它几乎涵盖了回归问题里所有值得练手的坑:特征量纲差异大、存在非线性关系、有离群点、样本量偏少容易过拟合。Keras 把它内置在keras.datasets里,一行load_data()就能拿到,省去了找数据、清洗格式的麻烦,非常适合拿来打通“数据加载—建模—训练—评估—预测”这条完整链路。

这篇文章面向的是已经跑通过 Keras 分类任务(比如手写数字识别)、想进一步搞懂回归问题怎么做的读者。分类和回归在代码结构上看着差不多,但损失函数、输出层设计、评估指标、标准化处理这几个地方差别很大,稍不注意就会踩坑。我会把 boston_housing 这个数据集从加载到调参的完整流程拆开讲,重点解释每一步为什么这么做,而不是只给一段能跑的代码。文中所有代码基于 TensorFlow 2.x 内置的 Keras 接口,实测可直接复现。

需要提前说明一点:这个数据集在较新的 Keras 版本里因为伦理争议被标记为 deprecated,调用时可能会看到弃用警告。这不影响使用,但如果你追求“干净”的输出,可以自行准备一份同结构的 CSV 数据替代,处理逻辑完全一致。下面进入正题。

2. 回归任务的整体设计思路拆解

2.1 为什么回归和分类不能套用同一套模板

很多人第一次做回归,习惯性把分类那套搬过来:输出层用 softmax、损失用交叉熵、评估看准确率。结果训练半天 loss 不降,还以为是模型太浅。根本原因在于两者的目标本质不同。分类预测的是“属于哪一类”的离散标签,输出是概率分布;回归预测的是“值是多少”的连续实数,输出就是一个或多个标量。这个差异直接决定了三件事:

第一,输出层不能有激活函数(或者只能用线性激活)。分类用 softmax 是为了把输出压成概率且和为1,回归如果也压,值域就被限制死了,房价预测出来的数永远在某个区间内,显然不合理。

第二,损失函数要用均方误差(MSE)或其变体,而不是交叉熵。MSE 衡量的是预测值和真实值之间距离的平方,对连续量来说这才是合理的“惩罚”。

第三,评估指标要看 MAE(平均绝对误差)或 MSE,而不是 accuracy。回归里没有“预测对/预测错”的概念,只有“误差多大”。

理解这三点,回归任务的骨架就立起来了。boston_housing 正好是练这个的最佳样本,因为它的目标值范围大概在 5 到 50 之间(单位是千美元),是个典型的连续量。

2.2 数据规模决定了模型不能太复杂

506 条样本,按 8:2 划分训练集和测试集,训练集也就 400 条出头。这个量级下,如果你搭一个几十万参数的网络,几乎必然过拟合——训练 loss 一路降到接近0,验证 loss 却开始往上走。所以模型设计的原则是“小而精”:两到三个隐藏层,每层几十个神经元就够了。

我个人的经验是,隐藏层神经元数量从 64 开始试,逐层递减(比如 64→32),比逐层递增效果更稳。原因是输入经过第一层已经做了初步特征组合,后面层数越深越应该做抽象压缩,而不是继续膨胀。这个思路在样本量少的时候尤其管用。

另外,样本少意味着验证集的划分方式很关键。如果只做一次 train/test split,测试集只有100条左右,评估结果的方差会很大,换个随机种子可能 MAE 差好几个点。所以更稳妥的做法是用 K 折交叉验证,把每一折的验证误差平均一下,得到的评估更可靠。Keras 本身不直接提供交叉验证,但可以配合 sklearn 的 KFold 手动实现,后面我会给完整代码。

2.3 特征标准化的必要性

boston_housing 的13个特征量纲差异极大。比如“城镇犯罪率”是小数(0到1之间),而“到市中心距离”可能是几十,“房间数”平均6左右,“人口比例”又是几百的量级。如果不做标准化直接喂给网络,梯度下降会非常难收敛——量纲大的特征对应的权重更新幅度大,量纲小的几乎不动,训练过程就像一个人左脚穿拖鞋右脚穿登山靴跑步,别扭且低效。

标准化的做法是每个特征减去均值再除以标准差,让所有特征都变成均值0、方差1的分布。这样梯度在各维度上的尺度就统一了,收敛速度和稳定性都会明显改善。注意:均值和标准差必须只用训练集计算,然后应用到测试集,否则会造成数据泄露——测试集的信息提前“污染”了训练过程,评估结果会虚高。

这一步是回归任务里最容易被忽略、但对结果影响最大的环节之一。我见过不少人模型结构调了半天没效果,最后发现是忘了标准化。

3. 核心细节解析与实操要点

3.1 数据加载与初步探查

Keras 加载 boston_housing 非常简单:

from tensorflow.keras.datasets import boston_housing (train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()

加载后先别急着建模,花两分钟做探查。看几个关键信息:训练集形状、特征取值范围、目标值分布。

print(train_data.shape) # (404, 13) print(test_data.shape) # (102, 13) print(train_targets[:10]) # 目标值样例 print(train_targets.min(), train_targets.max())

实测下来,训练集404条,测试集102条,目标值大致在5到50之间。这个探查动作看着简单,但能帮你快速判断要不要做标准化、目标值要不要缩放。比如目标值范围是5到50,量级不大,输出层直接用线性激活就行,不需要额外缩放;但如果目标值是几百万,就得考虑对目标也做归一化,否则 loss 初始值巨大,训练不稳定。

注意:load_data()默认按 8:2 切分,且切分是固定的。如果你想要不同的切分比例,可以传test_split参数,比如load_data(test_split=0.3)。但要注意,改变切分后训练集和测试集的样本会变,评估结果不能和默认切分直接对比。

3.2 标准化的正确姿势

标准化的代码不长,但细节决定成败:

mean = train_data.mean(axis=0) std = train_data.std(axis=0) train_data = (train_data - mean) / std test_data = (test_data - mean) / std

这里axis=0表示沿着样本维度求均值和标准差,也就是对每个特征单独算。算出来是13个均值、13个标准差。然后训练集和测试集都用这同一组统计量来变换。

为什么测试集不能用自己算的均值标准差?因为在实际部署时,你拿到一条新数据,不可能等攒够一批再算统计量,只能用训练阶段确定的参数。如果测试集用了自己的统计量,相当于提前知道了测试集的分布信息,评估结果会偏乐观,上线后表现会打折扣。

还有一个坑:如果某个特征的标准差是0(所有样本该特征值相同),除法会得到 inf 或 nan。boston_housing 里没有这种情况,但处理真实数据时一定要加个保护,比如std[std == 0] = 1。

3.3 模型结构的设计取舍

前面说了模型要小,具体怎么搭?我给一个实测稳定的结构:

from tensorflow.keras import models, layers def build_model(): model = models.Sequential([ layers.Dense(64, activation='relu', input_shape=(13,)), layers.Dense(32, activation='relu'), layers.Dense(1) ]) model.compile(optimizer='rmsprop', loss='mse', metrics=['mae']) return model

逐层解释一下。输入维度13,对应13个特征。第一层64个神经元用 relu,负责初步特征组合。第二层32个,做进一步压缩抽象。最后一层1个神经元,不加激活函数,直接输出预测值——这就是回归的输出层写法。

优化器选 rmsprop 是 Keras 回归任务的常见默认,它对学习率自适应,样本少的时候比 SGD 稳。损失用 mse,评估指标加一个 mae 方便直观理解误差大小(mae 单位就是千美元,比如 mae=2.5 意味着平均预测偏差2500美元)。

隐藏层激活为什么用 relu 而不是 sigmoid 或 tanh?relu 计算简单、梯度不容易消失,在中小网络上表现稳定。sigmoid 在输入较大或较小时梯度趋近0,深层网络容易训不动。tanh 比 sigmoid 好一些但仍有饱和问题。除非有特殊需求,隐藏层默认 relu 基本不会错。

实操心得:如果你发现训练 loss 下降很慢,先把优化器换成 adam 试试,它通常收敛更快。但 adam 有时会在最后阶段震荡,rmsprop 相对平滑。我的习惯是先用 adam 快速验证模型能不能学,再用 rmsprop 精调。

3.4 训练轮数与早停策略

样本少,训练轮数不能太多。我一般先设 epochs=100,batch_size=16,然后观察验证 loss 曲线。如果验证 loss 在某个轮次后开始上升,说明过拟合了,应该在那之前停。

更优雅的做法是用 EarlyStopping 回调:

from tensorflow.keras import callbacks early_stop = callbacks.EarlyStopping( monitor='val_loss', patience=10, restore_best_weights=True )

patience=10表示验证 loss 连续10轮没改善就停,restore_best_weights=True表示回滚到验证 loss 最低那一轮的权重。这样既省时间,又避免过拟合。实测在 boston_housing 上,通常30到60轮就会触发早停。

batch_size 选16而不是32或64,是因为样本本来就少,batch 太大会导致每轮更新次数太少,收敛慢。16是个折中值,既能利用矩阵运算的效率,又保证每轮有足够多的梯度更新。

4. 完整实操流程与K折交叉验证实现

4.1 单次切分的完整流程

先把单次切分的流程走通,这是基础。完整代码如下:

import numpy as np from tensorflow.keras.datasets import boston_housing from tensorflow.keras import models, layers, callbacks # 1. 加载数据 (train_data, train_targets), (test_data, test_targets) = boston_housing.load_data() # 2. 标准化 mean = train_data.mean(axis=0) std = train_data.std(axis=0) std[std == 0] = 1 train_data = (train_data - mean) / std test_data = (test_data - mean) / std # 3. 构建模型 def build_model(): model = models.Sequential([ layers.Dense(64, activation='relu', input_shape=(13,)), layers.Dense(32, activation='relu'), layers.Dense(1) ]) model.compile(optimizer='rmsprop', loss='mse', metrics=['mae']) return model model = build_model() # 4. 训练 early_stop = callbacks.EarlyStopping( monitor='val_loss', patience=10, restore_best_weights=True ) history = model.fit( train_data, train_targets, epochs=100, batch_size=16, validation_split=0.2, callbacks=[early_stop], verbose=1 ) # 5. 评估 test_mse, test_mae = model.evaluate(test_data, test_targets, verbose=0) print(f"测试集 MSE: {test_mse:.2f}, MAE: {test_mae:.2f}")

跑下来,测试集 MAE 通常在 2.5 到 3.5 之间(单位千美元),也就是平均预测偏差两三千美元。这个结果对于506条样本、13个特征的简单网络来说算合理。

validation_split=0.2是从训练集里再切20%做验证,用于早停监控。注意这个切分是在标准化之后做的,顺序不能反——如果先切分再标准化,验证集的统计量就和训练集不一致了。

4.2 K折交叉验证的代码实现

单次切分的结果波动大,用 K 折交叉验证能拿到更稳的评估。思路是把训练集分成 K 份,每次拿 K-1 份训练、1份验证,循环 K 次,最后把 K 个验证分数平均。

from sklearn.model_selection import KFold k = 4 num_val_samples = len(train_data) // k num_epochs = 100 all_scores = [] kf = KFold(n_splits=k, shuffle=True, random_state=42) for fold, (train_idx, val_idx) in enumerate(kf.split(train_data)): print(f"处理第 {fold + 1} 折") x_train = train_data[train_idx] y_train = train_targets[train_idx] x_val = train_data[val_idx] y_val = train_targets[val_idx] model = build_model() early_stop = callbacks.EarlyStopping( monitor='val_loss', patience=10, restore_best_weights=True ) model.fit( x_train, y_train, epochs=num_epochs, batch_size=16, validation_data=(x_val, y_val), callbacks=[early_stop], verbose=0 ) val_mse, val_mae = model.evaluate(x_val, y_val, verbose=0) all_scores.append(val_mae) print(f"各折 MAE: {all_scores}") print(f"平均 MAE: {np.mean(all_scores):.2f}")

这里用 sklearn 的 KFold 而不是手动切,是因为它能保证每折样本分布相对均匀,还能设随机种子复现。shuffle=True很重要,boston_housing 原始数据是按某种顺序排列的,不 shuffle 可能导致某折的样本分布偏差大。

实测4折下来,各折 MAE 大概在 2.2 到 3.0 之间波动,平均约 2.6。这个波动范围说明单次切分的评估确实不够可靠,交叉验证的平均值更有参考价值。

4.3 训练过程的监控与可视化

训练时盯着 loss 曲线能发现很多问题。用 history 对象可以画出训练和验证的 loss 变化:

import matplotlib.pyplot as plt loss = history.history['loss'] val_loss = history.history['val_loss'] mae = history.history['mae'] val_mae = history.history['val_mae'] epochs = range(1, len(loss) + 1) plt.plot(epochs, loss, 'bo', label='训练损失') plt.plot(epochs, val_loss, 'b', label='验证损失') plt.xlabel('轮次') plt.ylabel('MSE') plt.legend() plt.show()

健康的曲线是训练 loss 和验证 loss 都下降,且两者差距不大。如果训练 loss 一直降、验证 loss 先降后升,就是过拟合,早停能解决。如果两条线都居高不下,说明模型容量不够或学习率有问题,可以加宽隐藏层或换优化器。

我习惯在调参阶段把 verbose 设成0,只看最后的评估数字,避免刷屏。等确定结构后再开 verbose=1 看过程。

4.4 预测新样本的注意事项

模型训好后,预测新样本要记得做同样的标准化:

def predict_price(model, new_sample, mean, std): new_sample = (new_sample - mean) / std return model.predict(new_sample)

new_sample的形状要和训练数据一致,是 (13,) 或 (n, 13)。如果只传一条,记得用np.expand_dims升维,否则 Keras 会报维度错误。这个坑我踩过不止一次,尤其是从 pandas 读单行数据的时候。

另外,预测出来的值是标准化空间里的吗?不是。因为目标值没有做标准化,所以模型输出的就是原始尺度(千美元)。如果你对目标也做了标准化,预测后要乘回标准差加回均值,别漏了这步。

5. 常见问题与排查技巧实录

5.1 loss 变成 nan 怎么办

这是回归任务里最吓人的问题,训练几轮后 loss 突然变成 nan。常见原因有三个:

第一,学习率太大。rmsprop 默认学习率0.001,一般没事,但如果你手动调大了,梯度爆炸就会 nan。解决方法是换回默认或降到0.0001。

第二,数据里有 nan 或 inf。标准化时如果某个特征标准差为0,除法就会产生 inf。前面加的std[std == 0] = 1就是防这个。

第三,目标值量级太大。boston_housing 目标值最大50,没问题。但如果你的目标值是百万级,MSE 会非常大,梯度也大,容易炸。这时候对目标做归一化就能解决。

排查顺序:先检查数据有没有 nan/inf,再看学习率,最后看目标值量级。

5.2 验证 loss 比训练 loss 低很多

正常情况下验证 loss 应该略高于训练 loss,因为模型没见过验证数据。如果验证 loss 反而更低,通常是这两个原因:

一是验证集太小或太简单。样本少的时候,验证集可能恰好都是容易预测的样本。解决办法是增大验证集比例或做交叉验证。

二是训练时用了 dropout 或正则化,而验证时这些层被关闭了。训练阶段 dropout 会随机丢弃神经元,导致训练 loss 偏高;验证阶段不丢弃,模型表现更好。这是正常现象,不用慌。

5.3 MAE 降不下去的排查思路

如果 MAE 卡在某个值下不去,按这个顺序排查:

排查项检查方法可能的问题与对策
标准化打印标准化后数据的均值和标准差均值应接近0,标准差接近1;否则标准化有误
模型容量增加隐藏层神经元数量容量不足导致欠拟合,可加到128
学习率换 adam 或调整 rmsprop 学习率学习率不当导致收敛慢或震荡
特征工程检查是否有冗余或无关特征可尝试特征选择或构造交叉特征
目标值分布画目标值直方图若严重偏态,可对目标做对数变换

我遇到最多的情况是标准化没做对,尤其是测试集用了自己的统计量,导致评估虚高但实际预测差。其次是模型太小,13个特征用16个神经元的隐藏层,容量明显不够。

5.4 关于数据集弃用的处理

新版 Keras 调用 boston_housing 会提示该数据集因伦理原因被弃用。如果你不想看到警告,有两个办法:一是用warnings.filterwarnings('ignore')屏蔽,二是自己准备一份同结构的 CSV。自己准备的话,把13个特征和1个目标值整理成两列文件,用 pandas 读进来,后续处理逻辑完全一样。这样还能顺便练习数据加载和清洗,一举两得。

避坑技巧:无论用哪个数据集,养成“先探查、再标准化、后建模”的习惯。探查阶段花的时间,会在排查问题时加倍省回来。我见过太多人跳过探查直接建模,结果调了半天发现是数据里有异常值。

5.5 提升预测精度的小技巧

在 boston_housing 上想把 MAE 再压一压,可以试这几个方向:

一是加一层隐藏层,变成 64→64→32→1,但要注意过拟合,配合早停使用。

二是把优化器换成 adam,学习率设0.001,有时能降0.1到0.2的 MAE。

三是对目标值做对数变换,因为房价分布通常右偏,取对数后更接近正态,回归效果可能更好。预测后再用指数还原。

四是集成多个模型的预测结果,比如训练5个不同初始化的模型,取平均。样本少的时候,集成能有效降低方差,实测 MAE 能降0.2左右。

这些技巧不是每个都有效,得根据你的具体数据和评估结果试。我的建议是先把基础流程跑通、评估稳定,再逐个加技巧,每次只改一个变量,这样才能知道哪个改动真正起了作用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 4:15:00

120_不同工况下FOC与方波驱动效率差异的对比分析

一个被温升数据打脸的下午 早些年做一款便携式电动工具的无刷驱动方案,团队里分成两拨人吵了快一周。一拨坚持用方波驱动,理由很直接:算法简单、六步换相就能跑、对主控算力几乎没要求,成本压得下来。另一拨主张上FOC,说法是矢量控制转矩平稳、噪声小、效率高。两边谁也不…

作者头像 李华
网站建设 2026/10/18 7:47:28

FPGA流水灯实战:从Vivado约束到ILA调试的完整工程链

简介:本资源是一份面向FPGA初学者与数字电路实践者的Verilog入门级项目教程,聚焦流水灯这一经典时序控制实验,系统讲解从时钟管理、分频使能到状态机驱动LED的完整设计流程。内容覆盖PLL锁相环配置(含Vivado中Clocking Wizard调用…

作者头像 李华
网站建设 2026/10/18 17:57:19

JavaWeb毕设实战:SpringBoot+Vue林业产品推荐系统全解析

每年到了毕设季,后台私信里出现频率最高的问题就是:有没有合适的 Java Web 毕设项目推荐?SpringBoot Vue 的项目模式已经成了绝大多数同学的选择,因为它足够主流、资料多、上手快,又能同时体现完整的前后端开发能力。…

作者头像 李华
网站建设 2026/10/21 8:48:45

VL53L0X嵌入式实操指南:供电、I²C与寄存器级调优

简介:本资源是面向嵌入式开发工程师、物联网硬件爱好者及高校电子类专业学生的VL53L0X飞行时间(ToF)激光测距传感器中文技术文档,解决国产化资料匮乏、英文手册理解门槛高导致的选型难、驱动调试慢、光学串扰补偿不明确等实际问题…

作者头像 李华
网站建设 2026/10/21 7:50:58

Google AI Pro免费试用一个月完整攻略:从开通到取消避坑指南

最近后台收到不少消息,都在问同一个事:Google AI Pro 是不是真能“白嫖”一个月,怎么操作才能在上车之后稳稳下车,不让钱包受伤。先说结论,“白嫖”是真实存在的,官方确实给新订阅用户提供了免费试用期&…

作者头像 李华
网站建设 2026/10/19 12:08:56

全民健身App开发实战:SpringBoot后端与Android客户端全栈落地

做这个“全民健身App”项目的时候,我们当时的情况是:后端一个人、Android端一个人,两周要交出可演示的版本。所以从一开始,我就把“协议先行、前后端并行、接口尽量简单”定成了项目铁律。整个系统基于SpringBoot搭建服务端&#…

作者头像 李华