☰
房价预测BP神经网络实现:从Excel数据到可运行回归模型
2026/10/14 11:40:46 网站建设 项目流程

简介:这份资源面向机器学习入门者与需要完成回归预测实战的开发者,围绕经典波士顿房价数据集,用Python从零实现BP神经网络完成房价预测。压缩包共2个文件,含1个py脚本与1个csv数据文件,整体约8KB,其中csv提供14维房屋特征与中位房价目标变量,py脚本承载网络搭建与训练全流程。代码覆盖数据读取与预处理、输入层到输出层的结构定义、前向传播计算、以方差为损失函数的反向传播权重更新,以及迭代训练与测试集评估等环节,并预留新样本预测接口,便于读者理解神经网络回归的完整链路。目前已有21735人学习下载,适合希望脱离框架、亲手推导梯度与调参的读者对照实践,也可作为课程作业与项目原型的参考实现。

1. 房价预测的BP神经网络实现:从一份 Excel 到能跑的回归模型

很多人第一次接触房价预测的BP神经网络实现,都是被一份 Excel 表格逼出来的:几十列特征、几百到几千行样本,领导或导师丢过来一句“用 Python 跑个预测”。真上手才发现,难点根本不在“BP 神经网络”这四个字,而在数据怎么清洗、特征怎么对齐、输出层怎么设计、归一化放在哪一步。房价预测本质是一个连续值回归问题,不是分类,所以最后一层不能用 softmax,损失函数也不能用交叉熵,这是新手最容易翻车的地方。

这篇笔记面向两类人:一类是刚学完反向传播公式、想找个真实场景练手的同学;另一类是手头有房价数据、想快速搭一个能出结果的基线模型的工程师。我会用一份模拟的房价数据集,把 BP 神经网络从数据预处理、网络搭建、训练调参到结果验证整条链路走一遍,代码可以直接抄。中间会重点讲清楚几个参数为什么这么设,以及我踩过的坑。整套方案不依赖任何特定平台,本地 Python 环境就能复现。

2. 房价预测的数据准备与特征工程:先让数据能进网络

BP 神经网络对输入数据的“脾气”很挑:量纲不统一、缺失值不处理、类别特征不编码,训练出来的模型要么不收敛,要么收敛了但预测值离谱。所以在写网络之前,得先把数据整理成网络能吃的格式。

2.1 房价数据的典型字段与清洗思路

一份常见的房价数据,字段大致分三类:数值型(面积、房龄、楼层、房间数)、类别型(朝向、装修程度、是否有电梯、区域)、以及目标值(成交价或单价)。数值型字段里,面积和房龄的量纲差了几十倍,直接喂进网络会让梯度更新被大数值特征主导,小数值特征几乎不起作用。

清洗顺序我一般这样排:先去重,再处理缺失值,然后处理异常值,最后做编码和归一化。缺失值处理要看缺失比例,缺失超过 40% 的字段直接丢掉,缺失较少的数值字段用中位数填充,类别字段用众数填充。异常值用四分位距法(IQR)识别,超出上下界的样本先标记,不要急着删,因为有些高价房是真实存在的,删了反而让模型学不到高价区间。

import pandas as pd import numpy as np # 读取模拟房价数据 df = pd.read_csv("house_price_sim.csv") # 去重 df = df.drop_duplicates() # 数值字段缺失用中位数填充 num_cols = ["area", "age", "floor", "room_num", "hall_num"] for col in num_cols: df[col] = df[col].fillna(df[col].median()) # 类别字段缺失用众数填充 cat_cols = ["orientation", "decoration", "has_elevator", "region"] for col in cat_cols: df[col] = df[col].fillna(df[col].mode()[0]) # IQR 标记异常值,先不删,只打印数量 for col in ["area", "price"]: q1 = df[col].quantile(0.25) q3 = df[col].quantile(0.75) iqr = q3 - q1 low = q1 - 1.5 * iqr high = q3 + 1.5 * iqr outlier_count = ((df[col] < low) | (df[col] > high)).sum() print(f"{col} 异常值数量: {outlier_count}")

这段代码的逻辑是:先保证没有重复样本,再分类型填充缺失,最后用 IQR 只做识别不做删除。参数上,中位数比均值抗异常值,众数适合类别字段。IQR 的 1.5 倍是常规阈值,如果数据本身分布很偏,可以放宽到 3 倍,但要在日志里记下来。

2.2 类别特征编码与数值归一化的先后顺序

类别特征不能直接变成数字,因为“朝向=南”和“朝向=北”之间没有大小关系。常见做法是独热编码(One-Hot),把每个类别变成一个 0/1 列。如果类别取值很多(比如区域有几十个),独热后维度会爆炸,这时候可以用目标编码或嵌入,但基线模型先用独热就够了。

归一化必须放在训练集和测试集划分之后,用训练集的均值和标准差去变换测试集,否则测试集的信息会泄漏到训练过程里,评估结果会虚高。这是很多人容易忽略的一步。

from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 独热编码类别字段 df = pd.get_dummies(df, columns=cat_cols, drop_first=True) # 分离特征和目标 X = df.drop(columns=["price"]) y = df["price"].values.reshape(-1, 1) # 先划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) # 归一化:用训练集拟合,测试集只做变换 scaler_X = StandardScaler() X_train = scaler_X.fit_transform(X_train) X_test = scaler_X.transform(X_test) scaler_y = StandardScaler() y_train = scaler_y.fit_transform(y_train) y_test = scaler_y.transform(y_test)

这里对特征和目标都做了标准化。目标值标准化是为了让损失函数数值稳定,训练完预测时再用scaler_y.inverse_transform还原成真实房价。drop_first=True是为了避免独热编码的共线性,少一列不影响信息量。random_state=42保证每次划分一致,方便复现。

提示:归一化只对数值型特征做,独热后的 0/1 列不需要再标准化,否则会破坏稀疏性。

3. 用 PyTorch 搭一个能收敛的 BP 回归网络

数据准备好之后,网络结构本身其实不复杂。房价预测的 BP 网络通常是全连接结构,输入维度等于特征数,中间若干隐藏层,输出层一个神经元对应房价。真正决定能不能收敛的,是隐藏层宽度、激活函数、学习率和损失函数这几个选择。

3.1 网络层数、隐藏单元与激活函数的选择

输入维度由特征数决定,这个不用手动设。隐藏层我一般从两层起步,每层 64 或 128 个神经元。层数不是越多越好,房价数据样本量通常不大,层数太深容易过拟合,而且梯度在反向传播中会衰减。激活函数隐藏层用 ReLU,它计算简单、不容易饱和;输出层不能用 ReLU,因为房价可能是任意实数,输出层直接线性输出。

损失函数用均方误差(MSELoss),这是回归任务的标准选择。优化器用 Adam,学习率从 0.001 开始试。如果训练损失震荡,降到 0.0005;如果下降太慢,升到 0.005,但不要超过 0.01,否则容易跳过最优解。

import torch import torch.nn as nn class HousePriceBPNet(nn.Module): def __init__(self, input_dim): super().__init__() self.net = nn.Sequential( nn.Linear(input_dim, 128), nn.ReLU(), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 1) # 输出层线性,不加激活 ) def forward(self, x): return self.net(x) input_dim = X_train.shape[1] model = HousePriceBPNet(input_dim) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

网络定义里,nn.Linear(64, 1)是输出层,输出一个标量。nn.Sequential把层按顺序串起来,前向传播自动执行。参数上,128 和 64 是隐藏单元数,如果特征数超过 200,第一层可以加到 256;如果样本少于 500,第一层降到 64 更稳。

3.2 训练循环、批次大小与早停策略

训练循环要处理三件事:把 numpy 数据转成 tensor、按批次喂数据、每个 epoch 记录训练和验证损失。批次大小一般设 32 或 64,样本少就用 16。早停策略是防止过拟合的关键:如果验证损失连续 20 个 epoch 不下降,就停止训练并保留验证损失最低的模型参数。

from torch.utils.data import TensorDataset, DataLoader # 转成 tensor X_train_t = torch.tensor(X_train, dtype=torch.float32) y_train_t = torch.tensor(y_train, dtype=torch.float32) X_test_t = torch.tensor(X_test, dtype=torch.float32) y_test_t = torch.tensor(y_test, dtype=torch.float32) train_ds = TensorDataset(X_train_t, y_train_t) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True) best_val_loss = float("inf") patience = 20 wait = 0 best_state = None for epoch in range(500): model.train() for xb, yb in train_loader: pred = model(xb) loss = criterion(pred, yb) optimizer.zero_grad() loss.backward() optimizer.step() # 验证 model.eval() with torch.no_grad(): val_pred = model(X_test_t) val_loss = criterion(val_pred, y_test_t).item() if val_loss < best_val_loss: best_val_loss = val_loss best_state = model.state_dict() wait = 0 else: wait += 1 if wait >= patience: print(f"早停于 epoch {epoch}") break model.load_state_dict(best_state)

训练循环里,optimizer.zero_grad()必须在loss.backward()之前调用,否则梯度会累加。model.train()和model.eval()切换训练和评估模式,虽然这个网络没有 Dropout 和 BatchNorm,但养成习惯没坏处。早停的patience=20是经验值,数据噪声大可以调到 30,数据干净可以降到 10。

3.3 预测结果反归一化与评估指标计算

训练完的模型输出是标准化后的房价,必须用scaler_y.inverse_transform还原。评估指标用 MAE、RMSE 和 R²。MAE 反映平均绝对误差,RMSE 对大误差更敏感,R² 反映模型解释了多少方差。三个指标一起看,才能判断模型是整体偏差还是个别样本预测离谱。

from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score model.eval() with torch.no_grad(): pred_scaled = model(X_test_t).numpy() # 反归一化 pred_price = scaler_y.inverse_transform(pred_scaled) true_price = scaler_y.inverse_transform(y_test_t.numpy()) mae = mean_absolute_error(true_price, pred_price) rmse = np.sqrt(mean_squared_error(true_price, pred_price)) r2 = r2_score(true_price, pred_price) print(f"MAE: {mae:.2f}") print(f"RMSE: {rmse:.2f}") print(f"R2: {r2:.4f}")

反归一化这一步不能省,否则算出来的 MAE 是标准化空间的,没有业务意义。R² 在 0.8 以上说明模型对房价的解释力不错,低于 0.6 就要回头检查特征工程或网络结构。如果 RMSE 远大于 MAE,说明存在个别预测严重偏离的样本,需要单独看这些样本的特征。

4. 房价预测 BP 网络的避坑与排查清单

这一章是我自己踩过的坑,按“现象 → 原因 → 解决”整理。每一条都对应一个具体的失败场景,照着排查能省不少时间。

4.1 损失不下降或变成 NaN

现象:训练几个 epoch 后损失变成 NaN,或者一直停在某个值不动。

原因:最常见的是学习率太大,导致梯度爆炸;其次是输入数据没有归一化,数值大的特征把梯度带飞;还有一种情况是目标值没有标准化,MSELoss 算出来的数值过大。

解决:先把学习率降到 0.0001 试一轮,如果还 NaN,检查输入数据是否有 inf 或 nan。用np.isfinite(X_train).all()确认。目标值标准化一定要做,这一步省不得。

4.2 训练损失低但测试损失高

现象:训练集 MAE 很小,测试集 MAE 是训练集的两三倍。

原因:过拟合。房价数据样本少、特征多的时候特别容易发生。网络把训练样本背下来了,但没有学到泛化规律。

解决:先加早停,再考虑减小网络宽度,比如把 128 降到 64。还可以加 Dropout 层,nn.Dropout(0.2)放在两个隐藏层之间。如果数据量允许,做 K 折交叉验证比单次划分更可靠。

4.3 预测值全部集中在一个窄区间

现象:不管输入什么特征,模型输出的房价都差不多,方差很小。

原因:网络欠拟合,或者输出层被错误地加了激活函数。如果输出层用了 ReLU,负值会被截断成 0;如果用了 Sigmoid,输出会被压缩到 0 到 1 之间。

解决:确认输出层是nn.Linear且后面没有激活函数。检查隐藏层宽度是否太小,输入特征是否被归一化过度导致信息丢失。可以先把网络调宽一层,看预测方差是否变大。

4.4 类别特征独热后维度爆炸

现象:独热编码后特征数从几十变成几百,训练变慢,内存吃紧。

原因:某个类别字段取值太多,比如区域字段有上百个不同值。

解决:对高基数类别字段,改用目标编码(用目标均值替换类别),或者把低频类别合并成“其他”。目标编码要在训练集上计算,再应用到测试集,避免泄漏。合并阈值一般设 5%,出现次数少于 5% 的类别归为一类。

4.5 反归一化后房价出现负数

现象:预测出来的房价有负数,明显不合理。

原因:目标值标准化后,模型输出的是标准分数,反归一化时如果scaler_y的均值太小或标准差太大,极端预测值可能变成负数。

解决:在反归一化后加一个下限截断,pred_price = np.maximum(pred_price, 0)。但更根本的是检查模型是否欠拟合,负数通常出现在模型没学好、预测值偏离均值很远的时候。

5. 让房价预测 BP 网络更稳的几个进阶技巧

基线模型跑通之后,如果想把 R² 从 0.8 推到 0.9,或者想让模型在不同数据集上更稳,可以试几个进阶做法。这些不是必须的,但在我自己的项目里确实有效。

第一个技巧是特征交叉。房价和“面积 × 区域”这种组合特征关系很大,单纯把面积和区域分开喂给网络,网络需要自己学交互项,样本少的时候学不好。手动构造几个交叉特征,比如“面积/房间数”“房龄 × 装修程度”,能明显提升效果。构造完记得重新做归一化。

第二个技巧是学习率调度。固定学习率在后期容易在最优解附近震荡,用torch.optim.lr_scheduler.ReduceLROnPlateau在验证损失不下降时自动降学习率,比手动调更省心。

scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, mode="min", factor=0.5, patience=10 ) # 在验证损失计算后调用 scheduler.step(val_loss)

factor=0.5表示每次降一半,patience=10表示验证损失 10 个 epoch 不降才触发。这个调度器要和早停配合,早停负责停,调度器负责在停之前再抢救一下。

第三个技巧是集成。训练 3 到 5 个不同随机种子的网络,预测结果取平均。单个网络的方差会被平均掉,MAE 通常能降 5% 到 10%。代价是训练时间翻倍,但在房价这种样本量不大的场景里完全可接受。

技巧预期收益代价
特征交叉R² 提升 0.02 到 0.05需要领域知识构造
学习率调度训练更稳,后期不震荡多一个超参数
多模型集成MAE 降 5% 到 10%训练时间成倍增加

最后说一个验证方法:不要只看一次划分的测试集结果。用 K 折交叉验证,把数据分成 5 份,轮流做测试集,看 R² 的均值和标准差。如果标准差很大,说明模型对数据划分敏感,这时候集成和早停就更重要。

我自己做房价预测时,最大的教训是太早去调网络结构,忽略了数据本身。后来发现把缺失值处理干净、把异常值单独分析、把类别编码做对,比换激活函数、加层数有用得多。现在我的习惯是:先把数据管线和评估指标搭好,再动网络。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询