
1. 从房价预测切入为什么回归任务是深度学习的另一条腿很多人入门深度学习第一个接触的都是分类任务——手写数字识别、猫狗分类、情感判断输出的是离散的类别标签。但真实世界里大量问题要的不是“属于哪一类”而是“值是多少”。房价、销量、温度、股价、设备剩余寿命这些连续数值的预测才是工业场景中更常见的需求。波士顿房价数据集就是用来练这个的经典入口。这个数据集在Keras里内置加载一行代码就能用看起来简单到不像话。但恰恰因为它简单很多人跑完一个模型拿到一个MSE数值就结束了完全没搞明白回归任务和分类任务在建模思路上的根本差异。我见过不少做了半年深度学习的人换个回归场景就不知道损失函数该选什么、输出层要不要激活函数、评估指标怎么看。所以这篇东西不打算只给你一段能跑的代码而是把回归任务从数据理解到模型调优的完整链路拆开讲。适合谁看如果你已经跑过Keras的分类Demo想搞清楚回归任务到底哪里不一样这篇能帮你把认知补齐。如果你完全没碰过Keras跟着走也能跑通但建议先把Sequential模型的基本用法过一遍。整篇内容围绕波士顿房价数据集展开但讲的方法论可以迁移到任何tabular数据的回归场景。2. 波士顿房价数据集到底长什么样2.1 506条数据、13个特征的真实含义Keras内置的boston_housing数据集来自上世纪70年代波士顿周边城镇的房价统计一共506条样本每条有13个特征目标值是该区域自有住房价格的中位数单位是千美元。13个特征覆盖了城镇的犯罪率、住宅用地比例、是否临河、空气质量指标、平均房间数、到就业中心的距离、公路可达性、房产税率、师生比等维度。先加载看一眼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]) # [15.2 42.3 50. 21.1 17.7 ...]训练集404条测试集102条Keras已经帮你切好了。目标值范围大概在5到50之间单位千美元。注意这里的目标值不是原始价格而是中位数所以不会出现极端值把分布拉爆的情况。2.2 回归任务和分类任务在数据层面的关键差异分类任务里标签是类别编号比如0到9十个数字。回归任务里标签是连续实数。这个差异看起来只是标签类型不同但它会连锁影响到后面每一个环节。第一标签的尺度变得重要。分类任务的标签是离散的模型输出经过softmax后比较的是概率分布。回归任务的标签是连续的如果目标值范围是5到50而你的模型初始输出可能在0附近波动那梯度信号会非常不稳定。所以回归任务通常需要对目标值做标准化。第二特征尺度的影响被放大。分类任务里特征尺度不一致虽然也有影响但类别边界相对鲁棒。回归任务里一个特征范围是0到1另一个是0到1000模型在梯度下降时会被大尺度特征主导收敛极慢甚至不收敛。所以特征标准化在回归任务里几乎是必做项。第三评估指标完全不同。分类看准确率回归看MSE、MAE、RMSE。MSE对异常值敏感MAE更鲁棒RMSE和原始目标同量纲更直观。选哪个指标取决于你对误差的容忍方式。2.3 数据标准化为什么必须做怎么做前面说了回归任务对尺度敏感具体操作就是对每个特征减去均值再除以标准差mean train_data.mean(axis0) std train_data.std(axis0) train_data (train_data - mean) / std test_data (test_data - mean) / std注意这里的关键细节均值和标准差只能用训练集算然后应用到测试集。如果你用全量数据算均值和标准差测试集的信息就泄漏到了训练过程中评估结果会偏乐观。这是很多人容易忽略的一个坑。目标值要不要标准化在这个数据集里目标值范围是5到50不算特别大但标准化之后训练会更稳。如果做标准化预测完之后记得反变换回来才能解释实际价格。我个人的习惯是目标值也做标准化因为这样损失函数的初始值不会太大学习率好设。3. 搭建回归模型的几个关键决策点3.1 输出层为什么不能加激活函数这是回归任务里最容易被搞错的地方。分类任务的输出层通常用softmax或sigmoid把输出压缩到概率范围。但回归任务预测的是连续值如果加了sigmoid输出被限制在0到1之间而房价可能是5到50模型永远预测不对。如果加了relu输出非负但房价理论上不可能是负数这个约束倒不算错但没必要。所以回归任务的输出层就是一个Dense(1)不加任何激活函数让它线性输出。Keras里Dense层默认activation就是linear所以直接写Dense(1)就行。from tensorflow.keras import models, layers def build_model(): model models.Sequential([ layers.Dense(64, activationrelu, input_shape(13,)), layers.Dense(64, activationrelu), layers.Dense(1) ]) model.compile(optimizerrmsprop, lossmse, metrics[mae]) return model3.2 损失函数选MSE还是MAEMSE是均方误差对误差取平方大误差的惩罚被放大。MAE是平均绝对误差对每个误差一视同仁。选哪个取决于你的业务场景。如果你预测房价偶尔出现一个偏差很大的预测MSE会狠狠惩罚这个样本迫使模型去拟合那些难样本。但如果你数据里有异常值MSE会被异常值带偏。MAE更鲁棒但梯度在零点不可导训练时收敛可能慢一些。实践中MSE是回归任务的默认选择因为它的梯度性质好优化起来稳定。MAE通常作为评估指标而不是训练损失。我一般用MSE做loss同时监控MAE这样既保证训练稳定又能看到实际误差的平均水平。3.3 隐藏层规模和层数的取舍这个数据集只有506条样本、13个特征属于小规模tabular数据。隐藏层不需要太深两层64单元的全连接网络足够了。层数太多、单元太多参数量上去之后几百条样本根本喂不饱直接过拟合。我试过一层32、两层64、三层128几种配置在这个数据集上两层64的表现最稳。一层32欠拟合训练loss和验证loss都降不下去。三层128训练loss能降得很低但验证loss在几个epoch之后就开始回升典型的过拟合。对于tabular数据经验法则是先从一个隐藏层开始逐步加层加宽直到验证loss不再改善为止。不要一上来就堆深层网络小数据集撑不住。4. 训练过程中必须盯住的几个信号4.1 用K折交叉验证代替单次划分506条数据如果只切一次训练集和验证集验证集可能只有几十条评估结果的方差很大。同样的模型换个随机种子验证loss可能差很多。这种情况下K折交叉验证是更可靠的做法。import numpy as np k 4 num_val_samples len(train_data) // k num_epochs 100 all_scores [] for i in range(k): print(fProcessing fold #{i}) val_data train_data[i * num_val_samples: (i 1) * num_val_samples] val_targets train_targets[i * num_val_samples: (i 1) * num_val_samples] partial_train_data np.concatenate( [train_data[:i * num_val_samples], train_data[(i 1) * num_val_samples:]], axis0) partial_train_targets np.concatenate( [train_targets[:i * num_val_samples], train_targets[(i 1) * num_val_samples:]], axis0) model build_model() model.fit(partial_train_data, partial_train_targets, epochsnum_epochs, batch_size16, verbose0) val_mse, val_mae model.evaluate(val_data, val_targets, verbose0) all_scores.append(val_mae) print(fMAE scores: {all_scores}) print(fMean MAE: {np.mean(all_scores):.2f})4折交叉验证每折验证集约101条比单次划分稳定得多。跑完之后看平均MAE而不是某一折的结果。如果各折之间MAE差异很大说明数据分布不均匀或者模型不稳定需要进一步排查。4.2 训练loss下降但验证loss上升意味着什么这是过拟合的典型信号。训练loss持续下降说明模型在记忆训练数据。验证loss在某个epoch之后开始上升说明模型学到的模式在新数据上不适用了。遇到这种情况几个处理方向减小网络容量减少层数或单元数、加Dropout、加L2正则化、提前停止EarlyStopping。在这个数据集上因为样本少加Dropout效果比较明显。在隐藏层后面加layers.Dropout(0.5)训练时随机丢弃一半神经元能有效抑制过拟合。model models.Sequential([ layers.Dense(64, activationrelu, input_shape(13,)), layers.Dropout(0.5), layers.Dense(64, activationrelu), layers.Dropout(0.5), layers.Dense(1) ])注意Dropout只在训练时生效评估和预测时会自动关闭。所以加了Dropout之后训练loss会比不加时高一些这是正常的不要误以为模型变差了。4.3 学习率设多少合适RMSprop的默认学习率是0.001在这个数据集上通常够用。如果你发现训练loss震荡厉害或者下降太慢可以调整。学习率太大loss会震荡甚至发散学习率太小收敛太慢100个epoch可能还没降到合理范围。我一般会跑一次默认学习率看loss曲线。如果前10个epoch loss下降很慢说明学习率偏小可以调到0.01试试。如果loss上下跳动说明学习率偏大调到0.0001。这个数据集规模小试错成本低跑几次就能找到合适的值。5. 模型评估与预测结果的反标准化5.1 MAE数值怎么解读假设你跑完交叉验证平均MAE是2.5。这个2.5的单位是千美元意思是模型预测房价平均偏差2500美元。目标值范围是5到50千美元MAE 2.5大概相当于平均偏差5%左右对于这个数据集来说算不错的水平。如果你对目标值做了标准化MAE的单位就变成了标准差倍数需要乘以目标值的标准差才能还原成原始单位。所以评估的时候要么不做目标标准化要么记得反变换。5.2 用训练好的模型做实际预测训练完模型之后拿测试集跑一遍预测model build_model() model.fit(train_data, train_targets, epochs80, batch_size16, verbose0) predictions model.predict(test_data) print(predictions[:10].flatten()) print(test_targets[:10])对比预测值和真实值能直观感受模型的表现。如果某些样本偏差特别大可以单独看这些样本的特征分析是哪些特征导致了误判。这种案例分析比只看一个MSE数值有价值得多。5.3 反标准化与结果解释如果训练时对目标值做了标准化预测结果需要反变换# 假设训练时做了 target (target - target_mean) / target_std predictions_original predictions * target_std target_mean反变换之后预测值的单位回到千美元可以直接和真实房价对比。这一步很容易忘忘了的话预测值会在0附近波动看起来完全不对。6. 从波士顿房价数据集能迁移出什么通用经验6.1 小规模tabular数据的建模套路波士顿房价数据集虽然小但它代表了一类非常常见的场景几百到几千条样本、十几个到几十个特征、目标是连续值。这类场景在工业界大量存在比如设备参数预测、销量预估、能耗预测。这类数据的建模套路可以总结为特征标准化是必做项网络不要超过三层每层单元数控制在32到128之间Dropout和EarlyStopping是防过拟合的主力K折交叉验证用来获得可靠的评估结果。不要试图用深层网络去拟合小数据参数量远超样本量的时候正则化手段只能缓解过拟合不能根治。6.2 回归任务中特征工程的不可替代性深度学习在图像、文本、语音领域能自动提取特征但在tabular数据上特征工程的价值依然很大。波士顿房价数据集里的13个特征已经是经过筛选的如果你拿到的是原始数据可能需要做缺失值处理、类别特征编码、特征交叉、异常值处理。我做过一个能耗预测的项目原始特征只有温度、湿度、时间戳直接扔给全连接网络效果一般。后来加了温度湿度交互项、时间的周期编码sin/cos变换、过去几小时的滑动平均MAE直接降了30%。所以不要迷信“深度学习不需要特征工程”在tabular场景下好的特征比复杂的模型更管用。6.3 常见踩坑清单最后列几个我在这个数据集上踩过的坑供你参考忘记对测试集用训练集的均值和标准差做标准化导致评估结果不可信。输出层加了激活函数模型怎么训都训不好。目标值做了标准化但预测时忘了反变换预测值全在0附近。用全部数据算标准化参数造成数据泄漏。网络层数太深训练loss很低但验证loss很高过拟合严重。学习率用默认值但epoch设太少模型还没收敛就停了。只看MSE不看MAEMSE被个别大误差样本主导误以为模型很差。这些坑都不复杂但每一个都会让你多花几个小时甚至几天去排查。提前知道能省不少时间。