简介:这套基于BP神经网络的数据回归预测Python代码,面向机器学习初学者、课程设计或有回归预测需求的开发者,解决用Excel数据集快速验证BP网络预测效果的问题。代码完全基于numpy手动实现BP网络,无需依赖TensorFlow、PyTorch等深度学习框架,便于从零理解反向传播和梯度更新过程。示例以Boston房价数据为基础,将训练集和测试集分别存入两个xlsx文件,运行 dp_nn.py 后会自动加载数据、完成网络训练、输出预测结果,并使用matplotlib生成散点图与对比折线图,直观呈现真实值与预测值的偏差。压缩包共包含六个文件,包括一个Python脚本、一份README说明文档、两个Excel数据文件与两张结果图片,整体仅208KB,结构精简、便于替换和复用。已有2255人学习。结合说明文档和现成模板,可快速将BP回归流程迁移到自己的Excel数据集上,适合用于实验、作业或入门参考。
1. BP神经网络回归预测:这个Excel数据集项目到底怎么玩
拿到一份波士顿房价的Excel数据,想用Python手写一个BP神经网络做回归预测,又不想一上来就装TensorFlow或PyTorch这种重型框架,那你大概率会喜欢这个资源。它不是调包侠的玩具,而是用numpy从零实现的完整回归训练流程,数据、网络、绘图都给你配齐了。这个项目能帮你把“BP神经网络是三层结构、反向传播怎么更新权重”这些概念落到能跑的代码上。你只需要把Excel文件放好,直接运行dp_nn.py,就能看到训练loss下降、得到预测值,并画出真实值和预测值的散点图与折线图。适合正在做课程设计、学习神经网络原理的初学者,也适合想快速把手头Excel回归数据跑出一个基线结果的工程师。下面我会按“原理→数据→运行→避坑→替换”的顺序把它拆开讲透。
2. 手写BP神经网络:三层结构、激活函数与梯度下降的取舍
2.1 从Boston房价到Excel数据:为什么回归任务用BP
BP神经网络(Backpropagation Neural Network)是应用最广的前馈网络之一。它的学习过程分成两步:前向传播计算预测值,反向传播根据误差修改权重。对回归任务来说,输出层不需要sigmoid或ReLU激活函数,直接输出线性值,或者用恒等映射;损失函数则用均方误差(MSE)。波士顿房价数据集有13个特征,比如犯罪率、房间数、低收入比例等,目标是预测房价的连续值。这正是典型的“多特征→单输出”回归场景,用BP网络再合适不过。
这个项目里,数据已经拆成boston_housing_train_data.xlsx和boston_housing_test_data.xlsx两个Excel文件。训练集用来更新权重,测试集用来验证泛化能力。相比在线随机梯度下降,这里更可能是全批量(batch)训练,也就是每次迭代都用全部训练样本计算梯度。好处是梯度方向稳定,缺点是计算量大一些,但波士顿房价数据量不大,完全没问题。
2.2 网络结构设计与参数初始化:隐藏层神经元数量怎么定
经典的BP网络结构是“输入层-隐藏层-输出层”。对于波士顿房价数据集,输入层节点数等于特征数13,输出层节点数等于目标维度1。隐藏层节点数没有统一公式,常见的经验取值在输入层一半到两倍之间,比如8到20。资源里的代码大概率把隐藏层节点数设成了一个固定值,比如10或12。你可以把它当超参数调,后面我讲到的收敛速度和过拟合问题都和它有关系。
参数初始化很关键。如果把权重全部初始化为0,那反向传播时每个神经元会得到相同的梯度,网络就无法学习到不同特征。所以常用均匀分布或正态分布的小随机数,比如np.random.randn()乘以一个尺度因子。下面这段初始化代码是常见做法:
import numpy as np def initialize_parameters(n_input, n_hidden, n_output): # 小随机数初始化,避免对称失效 W1 = np.random.randn(n_input, n_hidden) * 0.5 b1 = np.zeros((1, n_hidden)) W2 = np.random.randn(n_hidden, n_output) * 0.5 b2 = np.zeros((1, n_output)) return W1, b1, W2, b2这段代码里,np.random.randn产生标准正态分布随机数,乘以0.5是为了把初始权重压小一点。b1和b2偏置初始化为0不会出现对称破坏问题。初始化尺度如果太大,前向传播时神经元的加权和会过大,导致sigmoid饱和,梯度接近0,训练基本停滞;尺度太小则训练速度慢。0.5这个值对波士顿这类小规模数据是个不错的起点,但换成你自己的数据后可能要调小到0.1甚至更低。
2.3 前向传播与反向传播的numpy实现逻辑
核心代码就在dp_nn.py里。我用典型实现来还原主干逻辑,前向传播是这样:
def sigmoid(x): return 1 / (1 + np.exp(-x)) def sigmoid_derivative(x): # x是经过sigmoid后的值 return x * (1 - x) def forward(X, W1, b1, W2, b2): z1 = np.dot(X, W1) + b1 a1 = sigmoid(z1) # 隐藏层激活 z2 = np.dot(a1, W2) + b2 output = z2 # 回归任务输出层不激活 return a1, output这里隐藏层用了sigmoid激活函数,输出层直接返回线性累加结果。注意sigmoid_derivative里传入的是a1而不是z1,因为sigmoid的导数可以写成a1*(1-a1),这样在反向传播时可以直接复用隐藏层的输出,减少一次重复计算。
反向传播的更新规则如下:
def backward(X, y, a1, output, W2, lr): m = X.shape[0] # 输出层误差:MSE的导数 (output - y) / m delta2 = (output - y) / m # 隐藏层误差 delta1 = np.dot(delta2, W2.T) * sigmoid_derivative(a1) # 梯度 grad_W2 = np.dot(a1.T, delta2) grad_b2 = np.sum(delta2, axis=0, keepdims=True) grad_W1 = np.dot(X.T, delta1) grad_b1 = np.sum(delta1, axis=0, keepdims=True) # 更新 W1 -= lr * grad_W1 b1 -= lr * grad_b1 W2 -= lr * grad_W2 b2 -= lr * grad_b2 return W1, b1, W2, b2这里lr是学习率,m是样本数。MSE对输出的导数是2*(output-y)/m,但乘以2可以合并到学习率里,所以很多实现直接省略2。delta1的计算是反向传播的精髓:np.dot(delta2, W2.T)把输出层误差传递回隐藏层,再乘上sigmoid的导数。每个grad对应一个权重矩阵的偏导数,批量取平均后再更新,这样训练曲线会更平滑。参数更新就是经典的随机梯度下降公式:W = W - lr * grad。
3. 把Excel数据集喂给神经网络:数据读取、归一化与训练集划分
3.1 用numpy读取Excel?实际是pandas读取,numpy处理
dp_nn.py里并没有直接用numpy去读Excel,因为numpy本身不支持Excel解析。常见做法是用pandas.read_excel读取,再通过.values转成numpy数组。资源中文件名带.xlsx后缀,注意xlrd库从2.0起不再支持xlsx,所以实际代码可能依赖openpyxl或pandas。读取代码长这样:
import pandas as pd train_df = pd.read_excel('boston_housing_train_data.xlsx') X_train = train_df.iloc[:, :-1].values.astype(float) y_train = train_df.iloc[:, -1].values.reshape(-1, 1)iloc[:, :-1]取所有行但除了最后一列,iloc[:, -1]取最后一列作为目标值。.reshape(-1, 1)把一维数组变成列向量,因为矩阵运算要求(m,1)形状。读取测试集时用同一套代码。如果你换成自己的Excel,只要保证“特征列在前,目标列在最后”就行。
3.2 特征归一化方法:min-max还是标准化,对BP收敛的影响
BP神经网络对特征量纲非常敏感。波士顿房价数据里,犯罪率可能只有个位数,而房间数平均是6左右,低收入比例是0到1之间,但特征“城镇学生教师比例”可能到20。如果直接喂原始值,数值大的特征在加权和里会占主导地位,梯度更新方向被大数值特征带着跑,收敛很慢。常见的两种归一化方式:
- min-max归一化:把数值缩放到[0,1]区间,公式是
(x-min)/(max-min)。 - 标准化(z-score):把数据变成均值为0、标准差为1,公式是
(x-mean)/std。
对回归任务,我更推荐标准化,因为它对异常值的容忍度比min-max好。下面这段是标准化代码:
def standardize(X): mu = np.mean(X, axis=0) sigma = np.std(X, axis=0) sigma[sigma == 0] = 1 # 防止常数列除零 return (X - mu) / sigma计算mu和sigma时axis=0表示按列计算。处理测试集时,必须使用训练集的均值和标准差,不能重新计算测试集的统计量,否则会引入数据泄露,导致评估结果虚高。下面这句是常见坑:
X_train_scaled = standardize(X_train) X_test_scaled = (X_test - mu_train) / sigma_train # 必须沿用训练集参数代码里的mu_train和sigma_train应该在训练集上算好。如果你把测试集单独标准化,模型看到的测试数据分布被改变,训练时学到的权重就不适用了。
3.3 训练集和测试集的划分:为什么波士顿房价要分两个文件
资源里已经提供了划分好的两个Excel文件,这个设计很省心。自己动手时要注意:划分数据前必须打乱顺序,否则如果原始数据按时间排列,训练集和测试集分别代表不同时间段,模型会学到时间相关性,测试结果失真。常见做法是用train_test_split,但这里既然分好了,直接读文件就行。
如果以后你自己手头只有一份Excel,可以用sklearn.model_selection.train_test_split来划分。不过这个资源没有依赖sklearn,你自己装一个也无妨:
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.2, random_state=42 )注意random_state固定一个值,这样每次运行划分结果一致,方便复现。这个项目没有强制要求你固定随机种子,但后面章节我会说,不固定的话结果会有波动。
4. 运行dp_nn.py:从命令行到绘图的完整流程与参数调优
4.1 环境准备:numpy、matplotlib、pandas缺一不可
跑这个项目前先确认Python环境里装了三个库:numpy、pandas、matplotlib。numpy做矩阵运算,pandas读Excel,matplotlib画图。安装方法很简单:
pip install numpy pandas matplotlib openpyxlopenpyxl是pandas读取xlsx文件的底层引擎,不装上会报“Missing optional dependency ‘openpyxl’”。另外,如果你的Excel文件是.xls老格式,那要装xlrd>=1.2.0,但xlrd 2.0以后不支持xlsx了,所以直接用openpyxl最稳。检查安装结果可以运行:
python -c "import numpy, pandas, matplotlib; print('ok')"如果报错,多半是路径或者虚拟环境问题。建议用python -m pip install而不是pip install,避免系统Python和你当前终端环境的混乱。
4.2 修改学习率、迭代次数和隐藏层节点数
dp_nn.py里会有几个超参数需要你手动改。典型的设置是这样:
input_size = X_train.shape[1] # 13 hidden_size = 10 # 你自己调 output_size = 1 learning_rate = 0.01 # 网上很多例子用0.1,但这会炸 epochs = 1000 # 迭代次数迭代次数建议从1000开始。如果loss还在下降,就加大到2000或5000;如果loss已经平稳,再加大只会浪费时间。学习率是调参的重头戏,我见过有人直接套用分类问题里的0.1,结果loss跳到nan。对于这个波士顿数据集的批量训练,0.01是一个比较安全的起点。隐藏层神经元个数,我一般先用输入层个数(13)试试,如果训练accuracy不错但测试loss高,那就是过拟合,把hidden_size降到8或5。
修改代码时不要只改一个参数,每次只改一个,然后看loss曲线和测试误差变化。同时把权重初始化时的随机种子固定,否则你对比的两次结果可能只是因为随机性不同,而不是因为参数改得好。
4.3 解读Figure_1和Figure_2:散点图与折线图看什么
训练结束后会生成两张图:Figure_1.png和Figure_2.png。第一张是测试集真实值与预测值的散点图,第二张是两者的对比折线图。绘图逻辑大概是:
import matplotlib.pyplot as plt # 散点图:理想情况下点都落在y=x直线上 plt.scatter(y_test, y_pred) plt.xlabel('True Values') plt.ylabel('Predictions') plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--') plt.show() # 折线图:真实值和预测值按样本序号排列 plt.plot(y_test, label='True') plt.plot(y_pred, label='Pred') plt.legend() plt.show()散点图上看的是分布形态:如果点紧密围绕对角线,说明预测准确;如果点在对角线下方偏离严重,说明系统性地低估了房价。折线图上看的是趋势:当真实值波动大时,预测值往往更平缓,这是回归模型常见的均值回归现象。如果折线图里预测值的线条明显滞后或幅度小,说明模型没有学到高频变化,可以考虑增加隐藏层节点或缩小学习率再训练几轮。
还有一个小技巧:如果你觉得图中横坐标太密集,尤其是波士顿测试集有100多个样本时,可以给x轴加步长控制:
import numpy as np indices = np.arange(len(y_test)) plt.plot(indices, y_test, alpha=0.7) plt.plot(indices, y_pred, alpha=0.7) plt.xticks(indices[::20]) # 每隔20个显示刻度这样出来的折线图不会糊成一团。不过这不影响模型本身。
5. BP神经网络训练避坑指南:归一化、学习率与随机种子的经典翻车现场
5.1 忘做归一化:训练loss降不下去,预测值全挤在一起
现象:训练20轮后loss还在0.5左右徘徊,可视化时预测值几乎是一条水平线,或者所有预测值都在同一个数值附近。
原因:波士顿房价的特征量纲差异太大。比如“一氧化氮浓度”是0.4左右,“距离就业中心加权距离”到5,而“低收入比例”是0.6。不加归一化时,大的特征值会让权重更新方向被少数特征绑架,sigmoid的输入落到饱和区,梯度接近0,网络学不动。
解决:一定要先对特征做标准化或min-max归一化。并且要用训练集的均值和标准差去归一化测试集,我见过有人对测试集重新算均值和标准差,导致评估数据分布和训练数据不一致,测试loss永远降不下去。从那以后我每次都会把归一化参数存下来,跑测试时直接load。
5.2 学习率设成0.1:迭代几次后loss直接变nan
现象:前几次epoch loss还在下降,突然某一次loss输出变成nan,之后全是nan。
原因:学习率太大,权重更新步长跨越了损失函数的“悬崖”,导致梯度爆炸。具体看,lr * grad的乘积超过了数值表示范围,或者经过sigmoid导数的连乘后梯度消失/爆炸。
解决:把学习率从0.1降到0.01,或者0.005。如果仍然出现nan,可以检查np.isnan(grad).any(),找出哪个梯度矩阵出了问题。还有一个办法是给梯度加一个裁剪,把梯度限制在[-1,1]之间:
np.clip(grad_W1, -1, 1)但最根本的还是把学习率调小。批量训练的稳定学习率一般比小批量要低,因为全批量的梯度方差小但步长不能太大。
5.3 numpy和pandas版本不匹配:读取Excel报错
现象:运行df = pd.read_excel('xxx.xlsx')时,报错ModuleNotFoundError: No module named 'xlrd',或者ImportError提示xlrd不支持xlsx格式。
原因:pandas读取xlsx文件需要openpyxl后台支持,但很多老的教程代码里写的是xlrd。xlrd 2.0及以上版本只支持.xls文件,如果你手里的数据是.xlsx,老接口就用不了。
解决:直接用pip install openpyxl,然后在代码里指定引擎:
pd.read_excel('boston_housing_train_data.xlsx', engine='openpyxl')如果你非要兼容老代码,可以先检查xlrd版本:xlrd.__version__,如果大于等于2.0,就换openpyxl。这个坑特别常见,一堆课程设计代码跑不起来都是卡在这里。
5.4 隐藏层节点设太多或epochs设太高:过拟合
现象:训练集loss降得很低,比如0.01,但测试集loss又回到0.3以上。折线图里预测值在训练集上和真实值几乎重合,在测试集上明显震荡。
原因:模型容量过大,或者训练迭代次数过多,把训练集的噪声也背下来了。尤其当数据量只有几百条时,隐藏层设20个节点加上10000次epoch,基本必然过拟合。
解决:把hidden_size降到5~8,训练epochs降到2000以内,同时观察训练集和测试集的loss曲线。如果训练集loss下降但测试集loss在某个点开始上升,那就说明该早停了。可以在代码里加一个简单的早停:
if test_loss > previous_test_loss: break这个资源本身没有做早停,但你自己改几行很容易。
5.5 随机种子不固定:每次运行结果不一样
现象:同一次运行,日志打印的初始loss和最终loss每次都不一样,画出来的预测图也不一样。
原因:权重初始化用了np.random.randn(),没有固定随机种子。随机性让模型每次从不同起点优化,可能落到不同的局部最优点。
解决:在dp_nn.py开头加上np.random.seed(42),让每次运行都一样。固定种子还有一个好处:调参数时你能确认效果差异来自参数,而不是运气。如果你为了公平对比不同隐藏层数量,种子必须完全一致,否则实验结果没有说服力。
6. 换自己的数据集:Excel格式对齐与回归效果验证的小技巧
这套代码最实用的地方是替换数据集。你不需要理解每一行数学推导,只要按规则把Excel准备好,就能用在别的回归任务上。我一般按下面这套流程来替换。
第一,Excel格式必须对齐。“特征列在左,目标列在右”,训练和测试分开两个文件。假如你自己的数据只有一个文件,那就先打乱行顺序,然后按80/20的比例切成两份Excel,用to_excel存出去:
df_shuffled = df.sample(frac=1, random_state=42) train = df_shuffled.iloc[:int(len(df_shuffled)*0.8)] test = df_shuffled.iloc[int(len(df_shuffled)*0.8):] train.to_excel('my_train.xlsx', index=False) test.to_excel('my_test.xlsx', index=False)注意sample(frac=1, random_state=42)会把整个数据框打乱,而且固定了种子,保证每次切分一致。如果你的数据是按时间排序的,这步打乱很有必要。
第二,确认没有缺失值和字符串列。dp_nn.py里astype(float)会在遇到字符串时直接报错。你可以在训练前加一次检查:
if X_train.isnull().any().any(): print('训练集存在缺失值,请先处理')如果真的有空值,最简单的处理是删除这些行,或者用该列均值填充。这个代码没有内置缺失值处理,所以数据质量要自己保证。
第三,隐藏层节点数要跟着特征数调整。你换到另一个数据集,特征数可能不是13。比如你有20个特征,输入节点会自动取X_train.shape[1],但隐藏层节点不会自动跟随,建议把隐藏层设成特征数的一半以上。我用过的一个经验公式:hidden_size = int((input_size + output_size) * 2 / 3),对于中等规模数据往往是个不错的起点。如果测试效果差,再往大或往小调。
第四,用测试集上的均方根误差(RMSE)来验证模型效果。代码里一般只画图,不会打印指标,但我自己都会加上:
rmse = np.sqrt(np.mean((y_test - y_pred) ** 2)) print('RMSE:', rmse)RMSE的单位和你的目标变量一致,波士顿房价的RMSE在4到6左右算正常。如果你换成房价(万元)或者温度(摄氏度),RMSE能直观告诉你误差有多大。对比不同参数时,只看RMSE,不要盯着训练loss,因为训练loss低不等于测试误差小。
我最初拿自己的数据跑这个项目时,忘了检查特征列里有一个字符串ID列,astype(float)直接报错,当时还以为是代码有bug。后来养成习惯,每次换数据集先print(X_train.shape)确认形状,再看一眼dtypes,把非数值列删掉。从那以后我每次跑回归都强制先做“形状检查、缺失值检查、归一化参数复用”这三步,再进去调网络参数。希望帮到你。
本文还有配套的精品资源,点击获取