
简介基于BP神经网络的数据回归预测Python代码采用Excel数据集并由numpy实现面向需要快速掌握回归预测建模流程的Python学习者、数据分析和高校学生。资源以波士顿房价数据为例完整涵盖数据读取、网络初始化、前向传播、反向传播、迭代训练与结果可视化等环节运行主脚本即可得到预测结果并通过散点图与折线图直观对比真实值和预测值便于分析误差与拟合效果。压缩包共6个文件包括1个Python主程序、2个Excel训练/测试数据集、2张结果对比图及1份说明文档整体仅208KB结构轻量、便于查阅。目前已有2255人学习下载代码不依赖深度学习框架对理解BP网络原理尤为友好同时数据集可替换为用户自己的Excel数据适用于课程实验、毕业设计或回归预测入门项目。1. 为什么回归预测任务还在用BP神经网络和numpy在机器学习的回归任务里你当然可以用随机森林、XGBoost或者直接调TensorFlow/PyTorch建一个全连接网络。但当你手里只有一份Excel表格数据想快速验证“这组特征能不能预测某个连续值”时用numpy从零实现一个BP神经网络反而是最省事也最透明的路径——没有GPU依赖、没有框架版本坑几十行代码就能在普通办公笔记本上跑完还能清楚看到每一层权重是怎么更新的。这一思路也常被用于论文复现、教学演示以及作为理解深度学习结构图的起点。本文就按这个标题的完整路径来走从Excel数据集读入、到numpy手写BP网络、再到回归预测的评估与调参覆盖中间所有关键参数和常见坑点适合刚接触神经网络原理的Python用户也适合想脱离高层框架、把反向传播梯度推导落实到代码里的从业者。2. Excel数据集读入与预处理从pd.read_excel到numpy数组2.1 为什么不能直接把Excel丢给神经网络训练BP神经网络接收的是数值矩阵而Excel文件本身是带格式的表格容器。一个常规的回归数据集至少包含若干特征列和一个目标列源数据里往往还存在表头、空值、文本类型列和量纲差异。第一种常犯的错误是用openpyxl按单元格手工取值再拼成二维列表这种方法在小数据集上勉强可用但一旦列数增加、行数上万代码会变得难以维护。更常规的做法是用pandas读取Excel经过清洗后再转换为numpy数组进行训练。此外numpy库本身不包含Excel解析功能所有读取动作都需要依赖pandas。虽然后续全部由numpy完成矩阵运算但数据进网络的最后一道门必须是numpy数组。读者经常在搜索“numpy无法读取excel”时找不到直接答案就是因为这中间缺了pandas作为桥梁。2.2 最小读取代码与字段检查如果你还没有安装pandas和openpyxl先完成环境安装pip install pandas openpyxl numpy提示openpyxl是pandas读取.xlsx文件的后端引擎缺少它会出现ImportError: Missing optional dependency openpyxl。读取代码如下import pandas as pd import numpy as np # 读取Excel文件指定工作表名称 df pd.read_excel(data.xlsx, sheet_nameSheet1) # 查看表结构、缺失值、数据类型 print(df.head()) print(df.info()) print(df.isnull().sum())执行后你会看到类似这样的输出前5行数据用来确认表头名称和数值范围info()描述列类型和总行数isnull().sum()统计每一列的缺失数量。拿到这三个信息之后你再决定哪些列是特征、哪一列是回归目标。2.3 分箱、取特征、缺失值处理的三个常见做法回归预测里并不是所有列都要进入网络通常需要剔除ID列、日期列等无关特征。我一般会手动指定特征列名列表而不是按位置选列这样后期改数据表结构时不容易错位# 特征列名按实际Excel表头修改 feature_cols [feature1, feature2, feature3] target_col target X df[feature_cols].values.astype(np.float32) y df[target_col].values.astype(np.float32).reshape(-1, 1)这里有两个容易被忽略的细节。第一values返回的是二维numpy数组astype(np.float32)用来统一数值类型避免Excel里混入int和float导致numpy自动推断出错。第二y必须reshape成(n_samples, 1)的二维列向量因为BP网络里输入是行样本组成的二维矩阵输出层也应该保持二维结构后面做误差计算时代码更简洁。对于缺失值最简单和最常见的是均值填充又或者直接删除含有空值的行。回归任务样本量较大的情况下df.dropna(subsetfeature_cols [target_col], inplaceTrue)是优先选择。2.4 归一化方法与反归一化的保留BP神经网络使用梯度下降更新权重特征如果量纲不一致比如一列是年龄20-60另一列是收入5000-20000数值大的特征会主导梯度方向导致训练震荡或收敛极慢。做回归预测时常见的做法是使用极差归一化from sklearn.preprocessing import MinMaxScaler scaler_X MinMaxScaler() X_scaled scaler_X.fit_transform(X) scaler_y MinMaxScaler() y_scaled scaler_y.fit_transform(y)注意y这一步也做归一化是因为输出层使用sigmoid或tanh激活函数时输出值有固定范围0到1或-1到1目标值如果不压缩到这个区间误差会一直居高不下。然后训练完网络之后预测得到的y_pred_scaled必须通过scaler_y.inverse_transform()还原成真实量纲否则预测结果无法和实际业务数值对应。这一对操作是回归预测里最容易漏掉的步骤忘了反归一化你的预测结果看起来就会是一堆0到1之间的小数精度却完全无法解释。3. BP神经网络的numpy实现前向传播与反向传播手写3.1 网络结构与矩阵维度检查BP神经网络至少包含三层输入层、隐藏层、输出层。隐藏层可以逐渐加深但退化成多层的BP仍然靠误差反向传播完成权重调整。本标题明确实现目标是回归预测因此有一个重要的选型区别输出层不能加sigmoid而应使用线性激活即输出等于输入加权求和这样网络输出范围才不受限。常见的做法是隐藏层用tanh或relu输出层恒等映射。先手动定义网络结构层神经元数numpytensor形状输入层特征维度(如3)(批量, 3)隐藏层自行设定(如8)权重W1为(3,8)偏置b1为(8,)输出层1权重W2为(8,1)偏置b2为(1,)隐藏层神经元数是一个超参数太少拟合能力不足太多在小数据集上容易过拟合。一般我会取特征数的2到4倍再逐步衰减。3.2 前向传播的矩阵计算下面是一份结构紧凑的BP网络numpy实现包含了初始化、前向传播、反向传播和训练循环关键部分。不依赖torch全部基于numpy矩阵运算。import numpy as np class BPNet: def __init__(self, n_features, n_hidden8, n_output1, lr0.01): # 小随机数初始化权重避免全零更新对称 self.W1 np.random.randn(n_features, 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)) self.lr lr def tanh(self, x): return np.tanh(x) def tanh_deriv(self, x): return 1.0 - np.tanh(x) ** 2 def forward(self, X): # 隐藏层输出 self.z1 X self.W1 self.b1 self.a1 self.tanh(self.z1) # 输出层线性激活 self.z2 self.a1 self.W2 self.b2 self.output self.z2 return self.output def backward(self, X, y): m X.shape[0] # 输出层误差预测值减真实值 d_z2 self.output - y d_W2 (self.a1.T d_z2) / m d_b2 np.sum(d_z2, axis0, keepdimsTrue) / m # 隐藏层误差反向传播 d_a1 d_z2 self.W2.T d_z1 d_a1 * self.tanh_deriv(self.z1) d_W1 (X.T d_z1) / m d_b1 np.sum(d_z1, axis0, keepdimsTrue) / m # 梯度下降更新 self.W2 - self.lr * d_W2 self.b2 - self.lr * d_b2 self.W1 - self.lr * d_W1 self.b1 - self.lr * d_b1 def compute_loss(self, X, y): output self.forward(X) # 均方误差 return np.mean((output - y) ** 2) def train(self, X, y, epochs500, verboseTrue): for i in range(epochs): self.forward(X) # 计算当前输出 self.backward(X, y) # 反传梯度并更新权重 if verbose and (i1) % 100 0: loss self.compute_loss(X, y) print(fepoch {i1:04d}, loss {loss:.6f})3.3 各参数的含义与调整说明lr学习率控制每一步权重调整幅度。回归任务中我一般从0.01起手loss震荡太大就降到0.001收敛过慢就提到0.05。W1 np.random.randn(...) * 0.1用标准正态分布再缩小10倍是为了防止初始值过大导致tanh进入饱和区梯度接近零、更新停滞。m X.shape[0]把误差对梯度的影响除以样本数得到均值梯度。这样即使样本量从100变成10000学习率不需要随样本数调整。d_z2 self.output - y是线性输出层加均方误差时最简梯度如果输出层换用sigmoid这个式子会多乘一个导数项细节完全不同。batch设置上面全量梯度下降用全部样本做一次更新比较稳定数据量大时常见做法是每次随机取一小批样本更新即mini-batch。这段代码实际上就是一个最简单的bp神经网络结构图你打印W1.shape就能确认节点之间的连接关系W1的第i行第j列就是输入层第i个特征到隐藏层第j个神经元的连线权重。4. 训练与调参流程归一化、超参试验、numpy环境常见问题4.1 数据集划分与误差曲线观察回归预测不能只用全部数据训练再拿同一批数据自评否则你看不到泛化能力。常见做法是按7:3或8:2划分训练集和测试集为了复现还要固定随机种子np.random.seed(42) n X_scaled.shape[0] idx np.random.permutation(n) split int(n * 0.8) train_idx, test_idx idx[:split], idx[split:] X_train, X_test X_scaled[train_idx], X_scaled[test_idx] y_train, y_test y_scaled[train_idx], y_scaled[test_idx] net BPNet(n_featuresX_train.shape[1], n_hidden8, lr0.01) net.train(X_train, y_train, epochs1000)运行结果会输出每100轮的loss数值你要观察的是loss是否持续降低、下降到多少后趋平。如果loss在前几百轮就剧烈震荡通常是学习率太大如果始终保持在较高水平不降通常是网络容量不够或数据本身线性相关性弱。4.2 回归预测的评估指标R2与误差绝对值训练结束后在测试集上进行预测并与真值比较y_pred_scaled net.forward(X_test) # 反归一化还原真实量纲 y_test_inv scaler_y.inverse_transform(y_test) y_pred_inv scaler_y.inverse_transform(y_pred_scaled) # 计算R2和平均绝对误差 ss_res np.sum((y_test_inv - y_pred_inv) ** 2) ss_tot np.sum((y_test_inv - np.mean(y_test_inv)) ** 2) r2 1 - ss_res / ss_tot mae np.mean(np.abs(y_test_inv - y_pred_inv)) print(fR2 {r2:.4f}) print(fMAE {mae:.4f})R2越接近1说明模型对测试集的解释能力越强回归预测在0.8以上就算基本可用而MAE给出的是实际业务量纲下的平均误差绝对值。比如房价预测中MAE3000元说明平均每套房预测偏差约3000元。这里要注意R2必须基于反归一化后的真实值计算否则因目标被压缩到0到1之间结果会虚高。4.3 必调的3个超参数实践中BP网络需要调整的参数并不多优先看学习率、隐藏层神经元数和训练轮数。下表给出我常用的调整方向参数设置范围看什么指标做决策学习率lr0.001~0.05loss每100轮下降幅度是否平滑隐藏层大小4~32训练集和测试集R2差距是否过大训练轮数epochs500~5000loss接近0后是否继续下降一个重要的边界是如果训练集R2很高接近0.98而测试集R2很低0.6以下隐藏层可能过大或轮数太多导致过拟合此时应减少神经元数、增加训练数据量或提前停止训练。反过来两个集的R2都低则先调大隐藏层规模或增大训练轮数。4.4 numpy版本与环境配置的典型坑热词里高频出现的“modulenotfounderror no module named numpy”和“pycharm有numpy库但一直显示没有”都属于环境配置问题。这里区分两种情况。第一种是pip安装路径和Python解释器路径不一致。命令行执行pip install numpy安装到了系统Python但PyCharm创建的是虚拟环境互相看不见。在PyCharm终端里运行python -m pip install numpy pandas openpyxlpython -m pip确保安装到当前解释器对应的site-packages目录。第二种是numpy版本本身不匹配。比如某些旧代码依赖numpy.trapz新版本移除了这个API就会报module numpy has no attribute trapz。这时不要硬降numpy版本直接把调用改成np.trapezoid或换用scipy.integrate就可以。还有安装时卡在“installing backend dependencies”的情况大概率是pip尝试构建源码包换成预编译的wheel包即可pip install numpy --prefer-binary5. 固定随机种子让每次回归预测结果可复现BP网络权重初始化带随机性同一批数据在不同轮次训练出的模型测试集R2可能在0.75到0.82之间波动。分享一个常被忽略的做法在代码最开始处固定numpy随机种子并同时固定网络初始化和训练数据打乱顺序。import numpy as np seed 42 np.random.seed(seed) def seed_worker(seed): np.random.seed(seed) # 训练数据打乱用同一seed shuffle_idx np.random.permutation(len(X_scaled)) X_shuffle X_scaled[shuffle_idx] y_shuffle y_scaled[shuffle_idx]如果你在用PyTorch或TensorFlow还需要指定更多随机源但纯numpy实现只需要这一行np.random.seed(seed)就够了。这一行放在读取Excel之后、实例化BPNet之前能保证每轮结果几乎一致。另一个实务技巧是早停在train循环里记录每一轮测试集loss如果连续50轮测试loss都没有下降就中断训练并保存当前权重防止后期出现过拟合。修改方式是每50轮在测试集上前向传播一次比较最小loss。这样既省时间又比固定epochs得到的模型更稳。回归预测模型上线后如果要长期复用把训练好的权重保存下来是一种常见做法比如np.save(w1.npy, net.W1)下次预测直接加载W1、b1、W2、b2中等规模的网络完全不需要依赖框架就能用Python原生的numpy部署。把上述全部流程串起来你就得到了一个Excel数据输入、纯numpy训练、可复现的BP回归预测闭环。本文还有配套的精品资源点击获取