numpy手写BP神经网络回归:Excel数据预测与可视化实战
发布时间:2026/10/2 2:37:13来源:尧图网络
简介这份资源面向希望入门神经网络回归预测的Python学习者与数据分析人员提供一套可直接运行的BP神经网络数据回归预测方案用于解决房价等连续值预测问题。压缩包共6个文件约208KB包含1个Python主脚本、2个xlsx格式的训练与测试数据集、2张结果图及1份说明文档脚本基于numpy实现网络结构无需复杂框架即可完成数据加载、训练与预测全流程。运行后可通过matplotlib绘制散点图与对比折线图直观查看预测值与真实值的拟合效果便于分析模型表现。数据集采用Excel形式存储读者可方便地替换为自己的数据快速迁移到其他回归场景。目前已有2255人学习下载适合作为课程作业、入门实验或算法验证的参考案例帮助理解BP网络的前向传播、误差反传与参数更新等核心环节。1. 从一份 Excel 到一条预测曲线这套 BP 回归代码到底能干什么手里有一批 Excel 数据想预测一个连续值——房价、销量、温度、良品率——但不想一上来就装 PyTorch、配 GPU、调 DataLoader只想用 numpy 把 BP 神经网络的每一步算清楚。这份dp_neural_network资源就是为这个场景准备的dp_nn.py单文件实现boston_housing_train_data.xlsx和boston_housing_test_data.xlsx两个 Excel 直接喂进去训练完用 matplotlib 画出预测值和真实值的散点图与对比折线图。它适合两类人一类是刚学完反向传播公式、想找一份能跑通的最小实现对照代码的新手另一类是手头只有几百到几千条小样本仿真数据、需要快速验证回归可行性的工程师。核心逻辑不依赖深度学习框架全部用 numpy 矩阵运算手写改结构、换激活函数、替换数据集都只需要动几个参数。2. 拆开dp_nn.pynumpy 手写 BP 的四个关键模块2.1 数据加载与 Excel 读取路径这份代码用 Excel 而不是 CSV 存数据好处是列名、表头、多 sheet 都能保留坏处是读取多一层依赖。常见做法是用 pandas 的read_excel底层走 openpyxl。先确认环境里有没有这两个包pip install numpy pandas openpyxl matplotlib如果安装 numpy 时卡在installing backend dependencies多半是网络源的问题换国内镜像即可pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple读取逻辑一般长这样注意最后一列通常是标签列前面是特征列import pandas as pd import numpy as np def load_data(train_path, test_path): # 读取 Excelheader0 表示第一行是列名 train_df pd.read_excel(train_path, header0) test_df pd.read_excel(test_path, header0) # 最后一列为预测目标其余为特征 x_train train_df.iloc[:, :-1].values.astype(np.float64) y_train train_df.iloc[:, -1].values.astype(np.float64).reshape(-1, 1) x_test test_df.iloc[:, :-1].values.astype(np.float64) y_test test_df.iloc[:, -1].values.astype(np.float64).reshape(-1, 1) return x_train, y_train, x_test, y_testiloc[:, :-1]取所有行、除最后一列外的所有列reshape(-1, 1)把一维标签变成列向量方便后面做矩阵减法。astype(np.float64)是血泪经验Excel 里只要有一格是文本或空值整列会变成 object 类型矩阵乘法直接报错。替换成自己的数据集时只要保证最后一列是连续数值标签、前面列全是数值特征这个函数不用改。2.2 网络结构定义与前向传播BP 回归的网络结构不复杂输入层节点数等于特征数一到两个隐藏层输出层一个节点。dp_nn.py里通常把权重和偏置初始化成小随机数避免全部置零导致对称失效。class BPNetwork: def __init__(self, n_input, n_hidden, n_output1, lr0.01): # 权重用小随机数初始化打破对称性 self.w1 np.random.randn(n_input, n_hidden) * 0.01 self.b1 np.zeros((1, n_hidden)) self.w2 np.random.randn(n_hidden, n_output) * 0.01 self.b2 np.zeros((1, n_output)) self.lr lr def forward(self, x): # 隐藏层用 tanh输出层线性适合回归 self.z1 x self.w1 self.b1 self.a1 np.tanh(self.z1) self.z2 self.a1 self.w2 self.b2 return self.z2n_input由数据特征数决定n_hidden是唯一需要调的容量参数小样本回归一般 8 到 32 就够。隐藏层激活用tanh而不是sigmoid是因为tanh输出以零为中心梯度更稳输出层必须线性回归预测的是任意实数套 sigmoid 会把结果压到 0 到 1 之间这是新手最常见的翻车点。lr学习率默认 0.01后面会讲怎么判断它合不合适。2.3 损失函数与反向传播的矩阵写法回归任务用均方误差 MSE反向传播就是对它求导链式展开。numpy 手写的价值在这一段体现得最清楚——每一步梯度的形状都能打印出来核对。def train_step(self, x, y): # 前向 pred self.forward(x) # MSE 损失 loss np.mean((pred - y) ** 2) # 反向输出层梯度 dz2 2 * (pred - y) / y.shape[0] dw2 self.a1.T dz2 db2 np.sum(dz2, axis0, keepdimsTrue) # 隐藏层梯度tanh 导数 1 - a1^2 da1 dz2 self.w2.T dz1 da1 * (1 - self.a1 ** 2) dw1 x.T dz1 db1 np.sum(dz1, axis0, keepdimsTrue) # 梯度下降更新 self.w2 - self.lr * dw2 self.b2 - self.lr * db2 self.w1 - self.lr * dw1 self.b1 - self.lr * db1 return lossdz2里的2 / y.shape[0]是 MSE 求导后对 batch 取平均的系数漏掉它梯度会大一个数量级训练直接发散。dw2 self.a1.T dz2是矩阵形式的链式法则a1形状是(batch, hidden)dz2是(batch, 1)转置相乘得到(hidden, 1)正好和w2形状对齐。1 - self.a1 ** 2是 tanh 的导数如果换成 ReLU这里要改成(self.z1 0).astype(float)。每一步更新都用-别写成否则梯度下降变成梯度覆盖。2.4 训练循环与预测输出把前向、反向、更新串起来跑固定轮数每轮打印损失观察收敛。def fit(self, x, y, epochs2000, print_every200): for i in range(epochs): loss self.train_step(x, y) if i % print_every 0: print(fepoch {i}, loss {loss:.6f}) def predict(self, x): return self.forward(x)epochs设 2000 是这类小样本的常见量级print_every用来观察损失是否单调下降。如果损失前几百轮就卡住不动多半是学习率太小或数据没归一化如果损失变成 nan多半是学习率太大。预测阶段直接调forward不做任何额外处理因为输出层已经是线性的。2.5 结果可视化散点图与对比折线图资源里Figure_1.png和Figure_2.png就是这两张图。散点图看整体拟合趋势折线图看逐样本误差。import matplotlib.pyplot as plt def plot_result(y_true, y_pred): # 散点图横轴真实值纵轴预测值越贴近对角线越准 plt.figure() plt.scatter(y_true, y_pred, s10) lim [min(y_true.min(), y_pred.min()), max(y_true.max(), y_pred.max())] plt.plot(lim, lim, r--) # 理想对角线 plt.xlabel(True) plt.ylabel(Pred) plt.savefig(Figure_1.png) # 折线图逐样本对比 plt.figure() plt.plot(y_true, labelTrue) plt.plot(y_pred, labelPred) plt.legend() plt.savefig(Figure_2.png)散点图里点越靠近红色虚线说明预测越准如果点分布成一条水平带说明网络没学到东西输出接近常数。折线图适合看测试集上哪些样本偏差大通常极端值附近误差最大这是回归任务的通病不是代码 bug。3. 从零跑通环境配置、训练与替换自己的 Excel3.1 环境准备与 numpy 版本匹配这份代码只依赖 numpy、pandas、openpyxl、matplotlib 四个包。Python 3.8 到 3.11 都能跑numpy 建议 1.21 以上。如果遇到ModuleNotFoundError: No module named numpy说明当前解释器没装先确认用的是哪个 Pythonpython -c import sys; print(sys.executable)输出的路径要和 pip 安装的目标一致。用 VS Code 的话右下角选解释器别让编辑器用一个环境、终端用另一个这是新手最常踩的环境错位坑。numpy 版本不匹配的典型报错是A module that was compiled using NumPy 1.x cannot be run in NumPy 2.x解决办法是锁版本pip install numpy2.03.2 运行dp_nn.py的完整流程把资源解压后目录结构是dp_nn.py加两个 xlsx 加 README。在目录下执行python dp_nn.py脚本内部流程是加载两个 Excel → 初始化网络 → 训练 2000 轮 → 在测试集上预测 → 画两张图。终端会打印每 200 轮的损失正常情况损失从大逐渐变小并趋于平稳。跑完当前目录会生成Figure_1.png和Figure_2.png打开看拟合效果。如果脚本里写死了文件名换数据集时要么改代码里的路径要么把自己的文件改成同名。3.3 替换成自己的数据集要改哪几处替换数据集是这份资源最实用的地方但有几个硬约束。第一Excel 最后一列必须是数值标签前面列全是数值特征中间不能有文本列。第二训练集和测试集列数必须一致列顺序也要一致。第三特征量纲差异大时要做归一化否则梯度会被大量纲特征主导。# 在 load_data 之后加一段归一化 def normalize(x_train, x_test): mu x_train.mean(axis0) sigma x_train.std(axis0) sigma[sigma 0] 1 # 防止除零 return (x_train - mu) / sigma, (x_test - mu) / sigma注意归一化参数只能用训练集的均值和标准差再套用到测试集不能各算各的否则测试集信息泄漏评估结果虚高。标签列如果数值范围很大比如房价几十万也建议归一化预测完再反归一化回来。3.4 关键参数对照表参数位置典型值作用与调整方向n_hidden网络初始化8~32隐藏层节点数太小欠拟合太大过拟合lr网络初始化0.001~0.05学习率损失震荡就调小收敛慢就调大epochsfit 调用1000~5000训练轮数看损失曲线决定print_everyfit 调用100~500打印间隔只影响观察不影响训练这张表里的范围是小样本回归的经验值不是定律。特征多、样本多可以适当加大n_hidden但几百条数据配几百个隐藏节点基本必过拟合。4. 避坑与排查五个真实翻车现场4.1 损失变成 nan 或 inf现象训练几轮后终端打印loss nan后续全是 nan。原因学习率过大梯度更新一步跨太远权重爆炸或者数据没归一化大量纲特征让梯度数量级失控。解决先把lr降到 0.001 试再检查数据是否归一化必要时对梯度做裁剪把dw限制在[-1, 1]区间。4.2 损失不下降输出接近常数现象损失从第一轮到最后几乎不变散点图是一条水平带。原因权重初始化太小加上学习率太小或者隐藏层激活全落在饱和区。解决把权重初始化从* 0.01改成* 0.1或使用 Xavier 初始化学习率调到 0.01 以上确认隐藏层用的是 tanh 而不是已经饱和的 sigmoid。4.3 Excel 读取报类型错误现象could not convert string to float或矩阵乘法报object类型。原因Excel 某列混入了文本、空格或空单元格pandas 推断成 object。解决打开 Excel 检查有没有空行、合并单元格、单位后缀如“100万”用pd.to_numeric(df[col], errorscoerce)强制转数值并检查 NaN 数量。4.4 训练集准、测试集差现象训练集散点贴着对角线测试集散点散开。原因过拟合隐藏层节点太多或训练轮数太多也可能是归一化用了全量数据导致信息泄漏。解决减少n_hidden减少epochs归一化严格只用训练集统计量样本太少时可以加 L2 正则在损失里加0.001 * (w1**2).sum()。4.5 图形不显示或中文乱码现象跑完没有图弹出或标题中文变方块。原因matplotlib 后端在无显示环境默认不弹窗中文字体没配。解决确认脚本里有plt.savefig而不是只plt.show无显示环境直接看保存的 png中文乱码加plt.rcParams[font.sans-serif] [SimHei]或者干脆用英文标签。5. 进阶用损失曲线和残差图判断模型到底学没学会跑通只是第一步判断这份 BP 实现有没有真正学到规律靠两张图不够我一般会补两条曲线。第一条是损失随轮数的变化曲线把每轮 loss 存进列表再画出来losses [] for i in range(epochs): loss net.train_step(x_train, y_train) losses.append(loss) plt.figure() plt.plot(losses) plt.xlabel(epoch) plt.ylabel(loss) plt.yscale(log) # 对数轴更容易看出后期收敛 plt.savefig(loss_curve.png)健康的损失曲线是前期快速下降、后期缓慢趋平。如果曲线一直抖学习率偏大如果几千轮还在缓慢下降轮数不够或学习率偏小如果训练损失降但测试损失升过拟合。对数纵轴能把后期的小变化放大比线性轴好用。第二条是残差图残差等于真实值减预测值residual y_test.flatten() - y_pred.flatten() plt.figure() plt.scatter(y_pred.flatten(), residual, s10) plt.axhline(0, colorr, linestyle--) plt.xlabel(Pred) plt.ylabel(Residual) plt.savefig(residual.png)理想残差应该随机分布在零线两侧没有明显形状。如果残差呈喇叭形说明大值预测误差更大标签可能需要做对数变换如果残差呈弯曲趋势说明线性输出层加单隐藏层的容量不够得加隐藏层或换更复杂的结构。这两张图配合原来的散点图和折线图四个视角基本能定位大部分问题。还有一个容易被忽略的点随机种子。np.random.randn每次运行结果不同同一份数据两次跑出来的损失曲线可能差不少。做对比实验前先固定种子np.random.seed(42)固定之后改一个参数看一次结果才知道到底是哪个改动起了作用。不固定种子就调参等于在噪声里找规律纯玄学。从那以后我每次拿到这种手写 BP 的小项目都强制先固定种子、先画损失曲线、先看残差分布再谈调参。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网