ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python手写BP神经网络实现加州房价预测实战教程

Python手写BP神经网络实现加州房价预测实战教程 简介波士顿房价预测是BP神经网络回归任务的经典案例该实现用Python完成前向与反向传播适合正在学习机器学习、希望掌握反向传播细节的初学者。压缩包仅2个文件一个Python源码负责前向传播、反向传播、方差损失计算及权重更新另一个CSV数据集包含犯罪率、房间数量、房龄等14个特征预测目标为1978年波士顿郊区房屋的中位价格整个资源包仅8KB结构简洁易于阅读。目前已有21718人学习下载代码覆盖从数据加载、预处理到网络训练与评估的完整流程。整体来看通过这份实现读者可以直观对照BP算法每一步的矩阵运算理解梯度如何从输出层反向传递并可直接替换其他回归数据集进行拓展实验是初学者入门神经网络回归预测的实用参考。 房价预测是个特别适合练手的回归问题而且BP神经网络刚好是入门深度学习的第一道坎。我最近抽空用Python手写了一个BP神经网络用真实的加州房价数据跑了一遍预测顺便把Scikit-learn里的MLPRegressor也对比了一下。这篇文章把我踩过的坑、调参思路、完整代码都整理出来适合刚学完Python基础、想搞懂BP神经网络到底怎么“学习”的朋友也适合需要用代码快速跑通一个回归任务的人。1. 房价预测BP神经网络的整体思路1.1 为什么房价预测适合用BP神经网络房价预测本质上是一个多元回归问题输入房屋面积、卧室数量、地理位置等特征输出一个连续的价格数值。传统线性回归能处理简单关系但真实房价和特征之间往往存在非线性比如面积对价格的边际影响在不同区间差异很大这时BP神经网络就派上用场了。BP神经网络通过隐藏层和非线性激活函数可以逼近任意复杂的函数关系这也是它在表格数据回归任务里依然有生命力的原因。我选择这个项目还有一个考虑数据容易获取。以前大家常用波士顿房价数据集但Scikit-learn因为数据存在伦理问题已经移除了它现在最顺手的是fetch_california_housing里面有20640个样本、8个特征目标是街区房价中位数。这个数据集大小适中BP神经网络跑起来很快非常适合学习和做实验。1.2 项目技术选型手写NumPy还是现成框架这个项目我做了两版。第一版用纯NumPy从零手写BP神经网络目的不是重复造轮子而是真正理解反向传播的每一步在干什么。第二版用MLPRegressor三行代码就能跑完适合快速验证效果和对比结果。手写版的优势在于透明网络权重怎么初始化、梯度怎么回传、学习率怎么影响收敛你都能直观看到。缺点是代码里需要对矩阵维度格外小心一个转置写错可能调一晚上。框架版的优势是封装了Adam优化器、Relu激活函数、自动的权重初始化效率和稳定性都更好。所以我建议新手先把手写版跑通再用框架版做正式实验两条路都走一遍基础会扎实很多。1.3 整体流程与核心步骤整个项目的流程大概是加载数据、划分训练集和测试集、特征标准化、标签标准化、搭建BP网络、训练、反标准化预测结果、用RMSE和R²评价效果。这里最重要的一个环节是数据归一化后面我会单独讲。先把流程框架放在心里后面代码就能对号入座。2. BP神经网络核心原理与数据预处理2.1 正向传播与反向传播到底在做什么BP神经网络的核心就是“预测-算误差-反推梯度-更新权重”四个动作。正向传播时输入特征从输入层进入经过隐藏层每层先用矩阵乘法算出加权和再经过激活函数做非线性变换最后在输出层得到预测值。对于回归任务输出层通常不加激活函数直接把最后一个线性输出作为预测价格。反向传播则是把预测值和真实值的误差从输出层向输入层一层一层传回去。每次传一层就根据链式法则算出每个权重对误差的贡献量这个贡献量就是梯度。然后沿着梯度的反方向调整权重让误差变小。你可以把训练过程想象成下山梯度告诉你哪个方向上升最陡反方向就是下山方向而学习率是每一步迈多大。2.2 数据归一化预测是否准确的关键一步这一步极其重要。加州房价数据里MedInc收入中位数在单位量级可能是几而HouseAge可能是几十Population可能是几百甚至上千。如果不做归一化BP网络在计算梯度时会被大数值特征主导小数值特征几乎学不到信息导致模型收敛慢甚至不收敛。常用做法是Z-score标准化也就是每个特征减去均值再除以标准差让数据变成均值为0、方差为1的分布。需要注意的是fit_transform只能用在训练集上测试集要直接用训练集拟合好的scaler做transform否则测试数据的信息会泄漏进模型评估结果会虚高。这个小细节很多人会忽视但它是评估可信度的基础。2.3 训练集/验证集划分与评估指标我习惯用80%的数据做训练20%做测试并用random_state42固定随机种子保证实验可复现。回归任务里最常用的两个指标是RMSE和R²。RMSE表示预测值和真实值的平均误差单位就是美元方便直观理解R²表示模型解释了目标变量多少比例的方差越接近1说明拟合越好。这里我建议只保留训练集和测试集不需要单独划分验证集。因为BP网络规模不大不是用来做超参数比赛的划分测试集只是检验泛化能力。如果后面做正经调参再引入交叉验证也不迟。3. Python完整实现从零手写BP网络3.1 环境准备与依赖安装我使用的环境是Python 3.10配合VSCode和Jupyter Notebook。核心依赖只有numpy、pandas、scikit-learn、matplotlib如果你还想做SHAP分析再加一个shap。在命令行里执行下面命令即可pip install numpy pandas scikit-learn matplotlib shap不建议直接装最新的Python 3.13部分库的预编译包可能还没跟上。老老实实用3.10或3.11最省事。如果你用的是Anaconda可以在Jupyter Notebook里直接跑依赖基本都齐全了。3.2 加载并预处理房价数据代码先从scikit-learn加载数据集然后做标准化。这部分是整个项目的“地基”标准化做不好后面模型再高级也没用。我顺手把标签也做了标准化因为手写BP网络输出层用的是线性输出标签如果数值特别大反向传播时的梯度就会出现数值不稳定的情况。import numpy as np from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler data fetch_california_housing() X data.data y data.target.reshape(-1, 1) # 变成一列方便矩阵运算 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler_X StandardScaler() X_train_s scaler_X.fit_transform(X_train) X_test_s scaler_X.transform(X_test) scaler_y StandardScaler() y_train_s scaler_y.fit_transform(y_train) y_test_s scaler_y.transform(y_test)这段代码里最需要注意的是y的reshape如果不把它变成(样本数, 1)后面矩阵运算时维度会对不上。我第一次写的时候就在这里栽过跟头报错了半天才反应过来。3.3 手写BP神经网络类下面是我手写的一个极简但完整的BP网络。它只有一个隐藏层激活函数用Sigmoid。真实项目里Sigmoid用得越来越少主要是容易梯度消失但作为教学完全够用而且能让你直观感受到非线性的作用。class BPNetwork: def __init__(self, n_input, n_hidden, n_output, lr0.01): self.lr lr self.w1 np.random.randn(n_input, n_hidden) * 0.1 self.b1 np.zeros((1, n_hidden)) self.w2 np.random.randn(n_hidden, n_output) * 0.1 self.b2 np.zeros((1, n_output)) def sigmoid(self, x): return 1 / (1 np.exp(-x)) def forward(self, X): self.z1 X self.w1 self.b1 self.a1 self.sigmoid(self.z1) self.z2 self.a1 self.w2 self.b2 self.a2 self.z2 # 回归问题输出层不加激活函数 return self.a2 def backward(self, X, y): m X.shape[0] delta2 (self.a2 - y) / m delta1 (delta2 self.w2.T) * self.a1 * (1 - self.a1) self.w2 - self.lr * (self.a1.T delta2) self.b2 - self.lr * np.sum(delta2, axis0, keepdimsTrue) self.w1 - self.lr * (X.T delta1) self.b1 - self.lr * np.sum(delta1, axis0, keepdimsTrue) def train(self, X, y, epochs): for i in range(epochs): pred self.forward(X) self.backward(X, y) loss np.mean((pred - y) ** 2) if i % 100 0: print(fepoch {i}, loss {loss:.6f})权重初始化我固定乘以0.1目的是让初始权重保持在小范围避免一上来就进入Sigmoid的饱和区。反向传播里的delta1是隐藏层的误差项a1 * (1 - a1)就是Sigmoid的导数。这些细节如果你第一次见建议拿笔和纸把矩阵维度一步步写出来比直接抄代码有用得多。3.4 训练模型并预测房价训练时隐藏层节点数我选了16。输入特征8个输出1个价格值隐藏层16个节点已经能学到不少非线性关系。学习率0.01跑1000轮。预测结果是经过标准化后的值所以最终价格要再用scaler_y.inverse_transform还原成真实美元价格。bp BPNetwork(X_train_s.shape[1], 16, 1, lr0.01) bp.train(X_train_s, y_train_s, epochs1000) pred_s bp.forward(X_test_s) pred scaler_y.inverse_transform(pred_s)训练过程里如果loss一直下降说明网络在正常学习如果loss原地抖动大概率是学习率太大或者数据标准化没做好。手写版最大的麻烦是训练速度慢1000轮在普通电脑上也就几秒钟完全可以接受。3.5 性能评估与可视化训练完后一定要算量化指标。RMSE和R²是两个最直接的数值能告诉你模型到底行不行。画图则能看到预测值和真实值的分布如果点都集中在对角线附近说明预测质量好如果点乱成一团那就要回头检查数据或网络结构了。from sklearn.metrics import mean_squared_error, r2_score import matplotlib.pyplot as plt rmse np.sqrt(mean_squared_error(y_test, pred)) r2 r2_score(y_test, pred) print(fRMSE: {rmse:.2f} 美元) print(fR²: {r2:.4f}) plt.scatter(y_test, pred, alpha0.5) plt.xlabel(真实房价) plt.ylabel(预测房价) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], r--) plt.show()我第一次跑出来的RMSE大约在0.8万到0.9万美元之间R²在0.6左右。对于只有一个隐藏层、没有做任何调参的手写BP网络来说这个结果已经能说明模型学到了一些规律同时还有很大的提升空间。4. 速成方案Scikit-learn实现MLP回归4.1 三行代码完成训练与预测如果你不想从零手写只想要一个能稳定出结果的方案直接用Scikit-learn的MLPRegressor。它底层帮你实现了多层感知机还带Adam优化器、Relu激活函数和自适应学习率性能比我手写版稳定很多。from sklearn.neural_network import MLPRegressor mlp MLPRegressor( hidden_layer_sizes(32, 16), activationrelu, solveradam, max_iter1000, random_state42 ) mlp.fit(X_train_s, y_train_s.ravel()) pred_mlp scaler_y.inverse_transform( mlp.predict(X_test_s).reshape(-1, 1) ) rmse_mlp np.sqrt(mean_squared_error(y_test, pred_mlp)) r2_mlp r2_score(y_test, pred_mlp) print(fMLP RMSE: {rmse_mlp:.2f} 美元, R²: {r2_mlp:.4f})这里y_train_s.ravel()是必须的因为fit方法要求目标变量是一维数组不像手写版需要列向量。如果忘记转换会出现奇怪的维度错误。4.2 关键参数解析MLPRegressor里最核心的参数有三个。第一个是hidden_layer_sizes我写的是(32, 16)表示两层隐藏层第一层32个节点第二层16个节点层数越多拟合能力越强但也越容易过拟合。第二个是activation这里用的relu它比Sigmoid更适合多层网络能缓解梯度消失。第三个是solveradam是自适应学习率优化器对新手最友好基本不用手动调学习率。还有一个容易忽略的参数是max_iter。它不一定代表真实迭代次数因为模型达到收敛条件后会提前停止。如果你发现loss还在下降但训练结束了可以把max_iter调大或者降低tol默认值让模型多跑一会儿。我常用tol1e-4效果不错。4.3 手写版和框架版怎么选一句话总结想搞清楚原理选手写版想快速实验和部署选框架版。手写版适合学习因为你能看到每一步计算框架版适合出结果因为它封装了大量工程优化。手写版在隐藏层为16个节点、Sigmoid激活函数、学习率0.01的条件下R²大概在0.6而MLPRegressor用两层隐藏层加ReluR²能到0.75左右。差距主要来自Relu和Adam而不是“框架”本身。所以我的建议是学习阶段两个都写一遍项目阶段直接上框架。等以后接触PyTorch你会发现MLPRegressor的核心概念其实一脉相承。5. 参数调优与模型解释进阶5.1 隐藏层节点数与激活函数选择隐藏层节点数没有标准答案但有一些经验法则。节点太少网络学不到复杂规律欠拟合节点太多会把训练集中的噪声也背下来过拟合。对8个输入特征的房价任务一层隐藏层16到32个节点就够用了如果数据量更大、特征更多再考虑两层结构。激活函数方面Sigmoid适合浅层网络Relu适合深层网络。在房价预测这种连续回归任务里Relu通常比Sigmoid效果好而且训练速度快。输出层依然保持线性不要加任何激活函数否则会限制预测范围。5.2 学习率、正则化与早停学习率控制着权重更新步长。太大会导致loss震荡太小则收敛极慢。手写版里我用0.01用框架版可以放心交给Adam自适应学习率。另一个重要的防过拟合手段是正则化MLPRegressor的alpha参数对应L2惩罚项数值越大正则化越强默认0.0001。如果你发现训练集R²很高但测试集明显变差那就把alpha调大一点。早停是另一个实用技巧。MLPRegressor自带early_stoppingTrue参数它会在验证集loss不再下降时提前终止训练避免白白浪费算力。这个参数在跑大数据集时特别有用。5.3 用SHAP解释哪些因素影响房价模型训练好以后不理解它为什么这样预测是很多新手的通病。SHAP是目前最主流的模型解释工具可以算出每个特征对预测结果贡献的方向和大小。对于MLPRegressor可以这样用import shap explainer shap.Explainer(mlp, X_train_s) shap_values explainer(X_test_s) shap.summary_plot(shap_values, X_test_s, feature_namesdata.feature_names)运行后你会看到一张图特征按重要性从高到低排列。通常MedInc收入中位数会排在最前面说明它和房价关联最强。用SHAP不是为了炫技而是帮你判断模型是否学到了符合常识的规律如果某个特征的影响方向和直觉不符就值得回头检查数据。6. 常见问题与排查技巧6.1 损失不降或出现NaN训练时loss如果一直是NaN最常见的原因是学习率过大导致梯度更新把权重推到数值溢出。解决方法是把学习率调小比如从0.01变成0.001或者使用更小的权重初始化。手写版里我初始化乘0.1你要是还遇到NaN可以再乘0.5或0.1。还有一个原因是输入数据没有标准化。特征数值太大加权和就会很大经过激活函数后梯度要么饱和要么爆炸。先标准化再谈调参。如果你用框架版可以试试max_iter调大一点同时把learning_rate_init设为0.001。6.2 预测值全部集中在均值附近这是回归模型最常见的“摆烂”表现预测值都在训练集均值附近R²接近0。原因是网络太简单或者特征和目标之间的关系没有被充分学习。我遇到过这种情况多半是只有一层隐藏层节点数太少或者Sigmoid到了输出层附近过于饱和。排查思路是看训练集loss有没有降下来。如果训练集loss也很高说明欠拟合增加隐藏层节点数、加一层隐藏层、换Relu激活函数如果训练集loss很低但测试集预测还是像均值那可能是过拟合或数据泄漏需要加正则化或重新检查数据划分。6.3 特征量纲影响大这个问题在没做归一化时特别明显。比如人口数值是几千收入是几如果不归一化权重更新会被人口主导收入特征几乎学不到东西。我在前面反复强调标准化就是因为这个坑太普遍了。另外一个容易被忽视的细节是测试集标准化必须复用训练集的scaler不能自己重新算均值和标准差。如果你发现某个特征对结果的影响力明显异常先检查是不是标准化范围不对再看SHAP图里这个特征的真实贡献。6.4 常见问题速查表现象可能原因解决方法loss为NaN学习率过大、权重初始化过大、输入未标准化调小学习率、减小初始化倍数、做Z-score标准化预测值集中在均值附近网络容量不足、欠拟合增加隐藏层节点数、增加层数、换Relu激活训练集R²高、测试集R²低过拟合增大alpha正则化、加early_stopping、减少隐藏层节点训练loss不下降数据未归一化、特征量纲差异大标准化所有输入特征标签也最好标准化代码报维度错误忘记把y变成列向量或行向量统一用reshape(-1, 1)或ravel()根据库的要求来最后再分享一个我自己的实操技巧每次改代码时先把随机种子固定住然后只改一个变量对比前后两次的结果。比如这次只改学习率下次只改隐藏层节点数否则你很难判断到底是哪个改动让结果变好了。BP神经网络的可复现性本来就不算高不固定随机种子的话后台每次跑出来的指标都不同很容易干扰判断。这个项目做完之后我对反向传播的恐惧基本消失了也明白了为什么框架能“一键训练”。如果你也想练手建议在加州房价数据上跑通之后把数据源换成自己城市最近几年的二手房成交记录特征加上地段、楼层、装修程度你会更直观地感受到特征工程和模型调参的魅力。本文还有配套的精品资源点击获取
返回列表