恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

BP神经网络房价预测实战:NumPy从零实现与调优避坑指南

  • 首页
  • 资讯中心
  • /
  • BP神经网络房价预测实战:NumPy从零实现与调优避坑指南

相关资讯

用NocoBase 2.0低代码搭建智能工单系统的完整实践笔记 2026/10/21 8:18:29
大模型面试必备:Transformer、Token与RAG核心概念精讲 2026/10/21 8:18:29
深度解析InnoDB Buffer Pool:从工作原理到性能调优实战 2026/10/21 8:13:29

最新资讯

智慧社区解决方案哪家好?买系统不等于买服务
Markdown写数学公式:用LaTeX语法构建可计算的数学表达系统
两级式光伏逆变器LVRT仿真模型与改进控制策略解析
开放式代码审查:从流程设计到自动化落地的完整实践
open-code-review:构建开放协作的代码评审流程实践指南
Playwright动态网页爬虫实战:滚动加载与点击翻页的通用处理套路

今日推荐

SAP 屏幕上的金额未必存在数据库表里,从 WB2R_SV 的 Net Amount 看懂运行时计算
具身智能中‘指哪打哪’的视觉语言动作联合建模
AI时代人工翻译技术博客的价值与实践指南

本周热门

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

BP神经网络房价预测实战:NumPy从零实现与调优避坑指南

发布时间:2026/10/21 8:18:29
BP神经网络房价预测实战:NumPy从零实现与调优避坑指南 简介这份资源面向机器学习入门者与需要完成回归预测实战的开发者围绕经典波士顿房价数据集用Python从零实现BP神经网络完成房价预测。压缩包共2个文件含1个py脚本与1个csv数据文件整体约8KB其中csv提供14维特征与中位房价目标变量py脚本承载网络搭建与训练全流程。代码覆盖数据读取与预处理、输入层到输出层的结构定义、前向传播计算、以方差为损失函数的反向传播权重更新以及迭代训练与测试集评估等环节并给出均方误差、决定系数等指标帮助读者理解神经网络回归的完整链路。已有21746人学习下载适合希望脱离框架、亲手推导梯度并调试训练过程的读者可作为课程作业或项目练手的参考实现。1. 从一份房价预测代码说起BP神经网络做回归到底靠不靠谱前阵子帮一个做房产数据的朋友看模型他手里有一批二手房成交记录想用机器学习估个价。试了线性回归误差大得离谱换了梯度提升树效果还行但调参调到怀疑人生。后来翻出一份用 BP 神经网络做房价预测的 Python 代码跑了一遍发现——在特征维度不高、样本量几千条的场景下一个三层 BP 网络居然能打平调了半天参的树模型。这让我重新审视了 BP 网络在回归任务里的定位它不是什么过时的老古董而是小规模结构化数据上性价比极高的方案。这份代码要解决的问题很明确输入房屋面积、房间数、楼层、地段评分等特征输出预测房价。适合两类人——刚接触神经网络、想找一个完整可跑通的回归项目练手的新手以及手头有表格数据、想快速搭一个基线模型做对比的开发者。代码不依赖深度学习框架用 NumPy 从零实现前向传播和反向传播这意味着你能看清每一层权重是怎么更新的而不是调一个model.fit()就完事。下面我从项目结构开始拆一路讲到怎么改网络结构、换损失函数、加正则化。2. 项目结构与数据流从 CSV 到预测值的完整链路2.1 目录组织与模块职责拿到代码包后先别急着跑。花两分钟看清楚文件是怎么分的后面改起来才不迷路。典型的目录结构长这样house_price_bp/ ├── data/ │ ├── raw/ │ │ └── house_prices.csv # 原始数据含特征列和价格列 │ └── processed/ │ ├── train.npz # 归一化后的训练集 │ └── test.npz # 归一化后的测试集 ├── src/ │ ├── config.py # 超参数、路径、特征列名配置 │ ├── data_loader.py # 读取CSV、缺失值处理、归一化、划分数据集 │ ├── network.py # BP网络类初始化、前向、反向、更新 │ ├── train.py # 训练循环、损失记录、模型保存 │ ├── evaluate.py # 加载模型、计算MAE/RMSE/R²、画预测对比图 │ └── utils.py # 归一化工具、指标计算、随机种子设置 ├── models/ │ └── bp_model.npz # 训练好的权重和偏置 ├── notebooks/ │ └── exploration.ipynb # 数据探索看分布、相关性 ├── requirements.txt └── README.md这个分法把「数据」「模型」「训练」「评估」四件事拆开了。config.py单独拎出来很关键——学习率、隐藏层神经元数、训练轮数、批大小全放这里改参数不用翻遍所有文件。network.py是核心只负责网络本身的计算逻辑不掺和数据加载和训练循环。这种职责分离在后续做实验对比时特别省事想换激活函数只动network.py想换数据划分比例只动data_loader.py。2.2 数据预处理归一化为什么不能省房价数据里面积可能是 80~200房间数 1~5地段评分 1~10。如果直接喂给网络面积这个特征因为数值大梯度更新时会主导权重调整房间数几乎被淹没。所以输入特征和标签都要做归一化。代码里用的是 Min-Max 归一化# src/utils.py import numpy as np class MinMaxScaler: def __init__(self): self.min_ None self.max_ None def fit(self, X): 计算每列的最小值和最大值用于后续变换 self.min_ X.min(axis0) self.max_ X.max(axis0) def transform(self, X): 将数据缩放到 [0, 1] 区间分母加 1e-8 防止除零 return (X - self.min_) / (self.max_ - self.min_ 1e-8) def fit_transform(self, X): self.fit(X) return self.transform(X) def inverse_transform(self, X_scaled): 把归一化后的预测值还原回原始房价量纲 return X_scaled * (self.max_ - self.min_ 1e-8) self.min_这里有个容易翻车的点归一化参数必须用训练集算然后应用到测试集。如果先把全量数据一起归一化再划分测试集的极值会「泄露」到训练过程里评估结果虚高。代码里data_loader.py的做法是先在训练集上fit拿到min_和max_再用同一组参数去transform测试集。预测完之后用inverse_transform把输出还原成真实房价否则你算出来的 MAE 是归一化尺度下的没有实际意义。2.3 网络结构定义输入层、隐藏层、输出层怎么定房价预测是回归任务输出层就一个神经元不加激活函数或者用线性激活。隐藏层数量和每层神经元数属于超参数代码默认给的是「输入特征数 → 10 → 1」的两层结构。为什么是 10这是个小数据集上的经验值隐藏层神经元太少欠拟合太多过拟合且训练慢。config.py里可以改# src/config.py INPUT_DIM 8 # 特征列数量根据实际CSV调整 HIDDEN_DIM 10 # 隐藏层神经元数 OUTPUT_DIM 1 # 回归任务输出一个值 LEARNING_RATE 0.01 EPOCHS 2000 BATCH_SIZE 32network.py里初始化权重用的是 He 初始化针对 ReLU 激活或 Xavier 初始化针对 tanh。代码默认用 ReLU因为它在隐藏层能缓解梯度消失训练更快# src/network.py import numpy as np class BPNetwork: def __init__(self, input_dim, hidden_dim, output_dim): # He初始化标准差 sqrt(2 / 输入维度)适合ReLU self.W1 np.random.randn(input_dim, hidden_dim) * np.sqrt(2.0 / input_dim) self.b1 np.zeros((1, hidden_dim)) self.W2 np.random.randn(hidden_dim, output_dim) * np.sqrt(2.0 / hidden_dim) self.b2 np.zeros((1, output_dim)) def forward(self, X): 前向传播线性变换 ReLU输出层不加激活 self.z1 X self.W1 self.b1 self.a1 np.maximum(0, self.z1) # ReLU self.z2 self.a1 self.W2 self.b2 return self.z2 def backward(self, X, y_true, y_pred): 反向传播MSE损失对各参数的梯度 m X.shape[0] # 输出层梯度dL/dz2 (y_pred - y_true) * 2 / m dz2 (y_pred - y_true) * 2 / m self.dW2 self.a1.T dz2 self.db2 np.sum(dz2, axis0, keepdimsTrue) # 隐藏层梯度ReLU导数在z10处为1否则为0 da1 dz2 self.W2.T dz1 da1 * (self.z1 0) self.dW1 X.T dz1 self.db1 np.sum(dz1, axis0, keepdimsTrue) def update(self, lr): 梯度下降更新参数 self.W1 - lr * self.dW1 self.b1 - lr * self.db1 self.W2 - lr * self.dW2 self.b2 - lr * self.db2逐段看forward里z1是隐藏层线性输出a1是 ReLU 激活后的结果z2直接作为预测值。backward里dz2是损失函数对输出层输入的梯度MSE 的导数是2*(y_pred - y_true)/m除以m是做批平均。da1是把输出层梯度回传到隐藏层再乘 ReLU 的导数z1 0时为 1。update就是最朴素的 SGD没有动量、没有自适应学习率。这套实现虽然简单但每一步都能对应到公式适合拿来理解 BP 的本质。2.4 训练循环批大小、轮数、损失记录train.py把数据按BATCH_SIZE切分每个 epoch 跑完所有批次记录平均损失。关键代码# src/train.py from src.network import BPNetwork from src.data_loader import load_and_preprocess from src.config import INPUT_DIM, HIDDEN_DIM, OUTPUT_DIM, LEARNING_RATE, EPOCHS, BATCH_SIZE import numpy as np X_train, y_train, X_test, y_test, scaler load_and_preprocess() model BPNetwork(INPUT_DIM, HIDDEN_DIM, OUTPUT_DIM) loss_history [] for epoch in range(EPOCHS): # 每个epoch打乱顺序避免批次间相关性过强 indices np.random.permutation(len(X_train)) X_shuffled, y_shuffled X_train[indices], y_train[indices] epoch_loss 0 for i in range(0, len(X_train), BATCH_SIZE): X_batch X_shuffled[i:iBATCH_SIZE] y_batch y_shuffled[i:iBATCH_SIZE] y_pred model.forward(X_batch) loss np.mean((y_pred - y_batch) ** 2) epoch_loss loss * len(X_batch) model.backward(X_batch, y_batch, y_pred) model.update(LEARNING_RATE) avg_loss epoch_loss / len(X_train) loss_history.append(avg_loss) if epoch % 200 0: print(fEpoch {epoch}, Loss: {avg_loss:.6f}) # 保存权重 np.savez(models/bp_model.npz, W1model.W1, b1model.b1, W2model.W2, b2model.b2)每个 epoch 先np.random.permutation打乱索引这是为了防止同一批数据总是以相同顺序出现导致梯度更新有偏。epoch_loss按样本数加权累加最后除以总样本数得到平均损失。每 200 轮打印一次方便观察收敛趋势。训练完把四个参数矩阵存成.npz评估时直接加载不用重新训练。2.5 评估与可视化MAE、RMSE、R² 怎么看evaluate.py加载模型后在测试集上预测再反归一化计算三个指标# src/evaluate.py import numpy as np from src.network import BPNetwork from src.data_loader import load_and_preprocess from src.config import INPUT_DIM, HIDDEN_DIM, OUTPUT_DIM X_train, y_train, X_test, y_test, scaler load_and_preprocess() params np.load(models/bp_model.npz) model BPNetwork(INPUT_DIM, HIDDEN_DIM, OUTPUT_DIM) model.W1, model.b1 params[W1], params[b1] model.W2, model.b2 params[W2], params[b2] y_pred_scaled model.forward(X_test) y_pred scaler.inverse_transform(y_pred_scaled) y_true scaler.inverse_transform(y_test) mae np.mean(np.abs(y_pred - y_true)) rmse np.sqrt(np.mean((y_pred - y_true) ** 2)) ss_res np.sum((y_true - y_pred) ** 2) ss_tot np.sum((y_true - np.mean(y_true)) ** 2) r2 1 - ss_res / ss_tot print(fMAE: {mae:.2f} 万元) print(fRMSE: {rmse:.2f} 万元) print(fR²: {r2:.4f})MAE 告诉你平均预测偏差多少万RMSE 对大误差更敏感R² 看模型解释了房价多少比例的方差。房价数据里 R² 能到 0.8 以上就算不错因为影响房价的因素太多户型、装修、学区这些没进模型的特征会拉低上限。如果 R² 低于 0.6先别急着调网络回去检查特征工程和异常值。3. 避坑与排查训练不收敛、预测全是一个值怎么办3.1 损失震荡不下降现象训练日志里 loss 一会儿大一会儿小整体趋势平缓甚至上升。原因学习率太大梯度更新步子迈过头在最优解附近来回跳。解决把LEARNING_RATE从 0.01 降到 0.001 或 0.0005观察 loss 是否平滑下降。如果降得太慢可以试 0.005。另一个可能是批大小太小比如 4 或 8梯度噪声大调到 32 或 64 试试。3.2 预测值全部接近均值现象测试集上不管输入什么特征输出都在房价均值附近浮动R² 接近 0。原因网络没学到东西权重初始化后几乎没更新或者隐藏层神经元太少导致欠拟合。解决先检查归一化是不是漏了——如果输入特征没归一化梯度会被大数值特征主导小数值特征对应的权重几乎不动。再检查隐藏层维度8 个特征配 10 个隐藏神经元偏少可以加到 32 或 64。最后看训练轮数2000 轮不够就加到 5000。3.3 训练集表现好、测试集崩了现象训练集 MAE 很低测试集 MAE 是训练集的三四倍。原因过拟合。网络把训练样本的噪声也记住了。解决三个方向——减少隐藏层神经元数比如从 64 降到 16、加 L2 正则化在损失里加lambda * (||W1||² ||W2||²)、或者加 Dropout训练时随机丢弃一部分隐藏层输出。代码里没内置这些但改起来不难下一章讲怎么加。3.4 梯度爆炸导致 NaN现象训练几轮后 loss 变成nan参数矩阵里出现inf。原因学习率过大加上权重初始化不当梯度在反向传播时指数级放大。解决先降学习率到 0.0001如果还不行就加梯度裁剪——在update之前把梯度范数限制在一个阈值内# 在model.update之前加 for grad in [model.dW1, model.db1, model.dW2, model.db2]: np.clip(grad, -1.0, 1.0, outgrad)3.5 数据泄露导致评估虚高现象R² 高得离谱0.98 以上但实际用新数据预测时偏差很大。原因归一化时用了全量数据的 min/max或者特征里混入了跟标签强相关的「未来信息」。解决确认MinMaxScaler只在训练集上fit测试集用transform。再检查特征列里有没有不该出现的字段比如「成交总价」这种直接泄露标签的列。4. 二次开发换损失函数、加正则化、调网络深度4.1 把 MSE 换成 Huber Loss房价数据里常有极端值——豪宅成交价可能是均价的十倍。MSE 对极端值敏感一个异常样本就能把梯度拉偏。Huber Loss 在误差小的时候用平方误差大的时候用线性对异常值更鲁棒。改network.py的backwarddef backward_huber(self, X, y_true, y_pred, delta1.0): Huber损失反向传播delta控制平方/线性的切换阈值 m X.shape[0] error y_pred - y_true # 误差绝对值小于delta时导数为error否则为delta*sign(error) dz2 np.where(np.abs(error) delta, error, delta * np.sign(error)) * 2 / m self.dW2 self.a1.T dz2 self.db2 np.sum(dz2, axis0, keepdimsTrue) da1 dz2 self.W2.T dz1 da1 * (self.z1 0) self.dW1 X.T dz1 self.db1 np.sum(dz1, axis0, keepdimsTrue)delta默认 1.0对应归一化后的误差尺度。如果归一化到 [0,1]delta 取 0.1 到 0.2 更合适。改完之后训练损失用 Huber 公式算评估指标还是 MAE/RMSE方便跟 MSE 版本对比。4.2 加 L2 正则化抑制过拟合在损失函数里加权重平方和反向传播时梯度也要加上对应的正则项导数。改backwarddef backward_with_l2(self, X, y_true, y_pred, l2_lambda0.001): m X.shape[0] dz2 (y_pred - y_true) * 2 / m self.dW2 self.a1.T dz2 l2_lambda * self.W2 / m self.db2 np.sum(dz2, axis0, keepdimsTrue) da1 dz2 self.W2.T dz1 da1 * (self.z1 0) self.dW1 X.T dz1 l2_lambda * self.W1 / m self.db1 np.sum(dz1, axis0, keepdimsTrue)l2_lambda从 0.0001 开始试太大模型会欠拟合。判断标准训练集和测试集的 MAE 差距缩小了但测试集 MAE 没有明显上升。4.3 加深网络从两层到四层两层网络表达能力有限特征交互复杂时可以加到四层。改network.py的初始化和前向传播class DeepBPNetwork: def __init__(self, dims): dims [input_dim, h1, h2, h3, output_dim] self.params {} for i in range(len(dims) - 1): self.params[fW{i1}] np.random.randn(dims[i], dims[i1]) * np.sqrt(2.0 / dims[i]) self.params[fb{i1}] np.zeros((1, dims[i1])) self.dims dims def forward(self, X): self.activations [X] self.zs [] a X for i in range(len(self.dims) - 2): z a self.params[fW{i1}] self.params[fb{i1}] self.zs.append(z) a np.maximum(0, z) self.activations.append(a) # 输出层不加激活 z_out a self.params[fW{len(self.dims)-1}] self.params[fb{len(self.dims)-1}] self.zs.append(z_out) return z_out层数加深后学习率要相应调小比如 0.001否则容易梯度爆炸。另外每层神经元数建议逐层递减比如[8, 64, 32, 16, 1]这样网络先提取高维特征再逐步压缩。4.4 用早停法省训练时间训练轮数设 5000 轮但模型可能 1500 轮就收敛了后面全是白跑。加一个早停逻辑每 50 轮检查验证集损失如果连续 5 次没下降就停。best_loss float(inf) patience 5 wait 0 for epoch in range(EPOCHS): # ... 训练代码 ... val_loss compute_val_loss(model, X_val, y_val) if val_loss best_loss: best_loss val_loss wait 0 np.savez(models/best_model.npz, **model.params) else: wait 1 if wait patience: print(fEarly stop at epoch {epoch}) break验证集从训练集里再切 10% 出来不参与梯度更新。早停保存的是验证集上最好的模型不是最后一轮的模型这点很关键——最后一轮可能已经过拟合了。4.5 一个我踩过的坑有次改完网络结构训练 loss 降得特别漂亮测试集 R² 也有 0.92我差点直接交差。后来不放心把预测值按房价从低到高排序画了散点图发现低价房预测偏高、高价房预测偏低——典型的回归均值现象。原因是隐藏层用了 ReLU输出层没加激活但训练数据里高价样本太少网络倾向于输出中间值来降低平均损失。解决办法是在损失函数里给高价样本加权或者对房价取对数再预测。从那以后我每次跑完回归模型都强制画一遍「预测值 vs 真实值」的散点图光看指标数字真的会骗人。希望这份拆解能帮你少走点弯路代码跑起来之后先从改学习率和隐藏层维度开始试感受一下超参数对结果的影响再往深了改结构。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号