简介:该资源提供一套基于BP神经网络的天气质量预测模型实现,核心激活函数采用tanh,并配合梯度下降法优化,适用于空气质量指数(AQI)的短期趋势预测。资源面向希望入门神经网络或完成课程设计、毕业设计的学习者,也适合作为工程实训的起步项目。压缩包内共3个文件,包含用于训练与测试的天气数据表(xlsx)、项目说明文档(md)以及模型实现脚本(m),整体大小仅25KB,结构精简便于快速上手。目前已有141人学习下载。通过该资源,读者可以了解BP网络从数据预处理、tanh激活函数设置到梯度下降迭代的完整流程,并可直接运行脚本观察不同参数下的预测效果,为后续改进模型或拓展到其他预测场景提供可复用的基础代码。
1. 天气质量预测为什么要用BP神经网络和tanh:先讲结论
如果你手里有一份按小时采样的气象和空气质量数据,想预测未来几小时甚至一天的天气质量变化,最直接的做法不是套用现成的机器学习库跑个回归,而是自己搭一个能看清每个权重的BP神经网络。这篇笔记围绕“基于Python+BP神经网络的天气质量预测模型,激活函数用tanh”展开,把从数据预处理到模型训练的完整路径拆开讲透。我的结论是:对于天气质量这种数值范围集中、没有剧烈突变的连续预测任务,tanh作为隐藏层激活函数比ReLU更稳,比Sigmoid收敛更快,配合BP反向传播能把预测误差压到可接受范围内。适合的人群是已经会Python基础语法、装好了numpy和sklearn、想从零手写而不是只调包的人。
2. BP神经网络与tanh在天气预测里的分工:先搞懂再动手
2.1 BP神经网络的三个核心模块:前向传播、损失计算、反向传播
BP神经网络解决的是“给定一组输入,算出预测值,再根据误差修正网络参数”的问题。天气质量预测里,输入通常是过去几小时的温度、湿度、气压、风速、PM2.5浓度等;输出是未来某一时刻的天气质量指标。
前向传播做的事情是输入数据逐层加权求和,再经过激活函数输出。以单隐藏层网络为例:
import numpy as np def tanh(x): return np.tanh(x) def forward(X, W1, b1, W2, b2): # X: 输入矩阵,每行是一个样本 # W1: 输入层到隐藏层权重,b1: 隐藏层偏置 # W2: 隐藏层到输出层权重,b2: 输出层偏置 z1 = np.dot(X, W1) + b1 a1 = tanh(z1) z2 = np.dot(a1, W2) + b2 return z2, a1这段代码里,z1是隐藏层的加权输入,a1是经过tanh激活后的隐藏层输出,z2是输出层的原始输出。天气质量预测通常是回归任务,所以输出层不加激活函数,直接输出预测值。隐藏层用tanh,就是把加权结果压缩到-1到1之间,避免极端值干扰后续层。
反向传播是BP的精华。它根据输出层的误差,按链式法则把梯度逐层传回,更新每一层的权重。损失函数用均方误差(MSE),因为预测值和真实值之间的差值被平方后,大误差会被放大,模型会更努力去修正偏差大的样本。
def backward(X, y, a1, z2, W2, lr): m = X.shape[0] # 输出层误差 dz2 = z2 - y.reshape(-1, 1) # 隐藏层误差,tanh的导数是 1 - tanh^2 da1 = np.dot(dz2, W2.T) * (1 - a1 ** 2) # 计算梯度 dW2 = np.dot(a1.T, dz2) / m db2 = np.sum(dz2, axis=0) / m dW1 = np.dot(X.T, da1) / m db1 = np.sum(da1, axis=0) / m # 更新参数 W2 -= lr * dW2 b2 -= lr * db2 W1 -= lr * dW1 b1 -= lr * db1 return W1, b1, W2, b2这里最需要注意的是da1的计算:tanh的导数等于1 - tanh^2(x),对应代码里的(1 - a1 ** 2)。如果激活函数换成Sigmoid,导数公式就变成a1 * (1 - a1),公式不同,梯度的大小和衰减速度也不同。BP神经网络结构图里最常见的形状就是“输入层—隐藏层—输出层”三层结构,天气预测这个场景用一层隐藏层通常就够,隐藏层神经元数量从8到32不等,需要根据数据量调整。
2.2 tanh在天气质量预测里为什么比Sigmoid和ReLU更合适
激活函数的选择直接关系到梯度消失和收敛速度。Sigmoid的输出范围是0到1,均值不为0,这会导致BP反向传播时梯度更新方向出现系统性偏移,训练效率偏低。ReLU的输出范围是0到正无穷,负区间梯度为0,一旦神经元进入负区间就不再更新,这种现象叫“神经元死亡”,在天气数据这种输入特征差异较大的场景里很容易触发。
tanh输出范围是-1到1,均值接近0,梯度更新方向更均衡。天气质量数据经过归一化后大多落在-1到1区间内,tanh的强线性区间正好覆盖这个范围,梯度值较大,收敛更快。实际对比过Sigmoid、ReLU、tanh三种激活函数在同一份气象数据上的表现:Sigmoid大约需要500个epoch才能达到tanh在200个epoch时的损失水平;ReLU在部分神经元死亡后损失曲线出现平台期。tanh的收敛速度居中,但稳定性最好,适合天气质量这种时序相关性强的数据。
2.3 参数初始化:tanh网络的隐藏层权重不能乱填
BP神经网络的权重初始化对tanh尤其敏感。因为tanh的输出范围是-1到1,如果初始化权重过大,加权输入z1会落在一个很大的值上,tanh进入饱和区,导数接近0,反向传播时梯度接近于0,参数基本不更新,训练直接卡死。这就是常说的“玄学初始化”问题,在这类天气预测模型里表现得特别明显。
常见做法是用Xavier初始化:W = np.random.randn(n_in, n_out) * np.sqrt(2.0 / (n_in + n_out))。这个公式让每一层的方差在输入和输出维度之间平衡,tanh配合Xavier是经过验证的组合。如果用He初始化(为ReLU设计的),配tanh会偏大,基本必踩坑。
def init_weights(n_input, n_hidden, n_output): # Xavier初始化,适配tanh激活 W1 = np.random.randn(n_input, n_hidden) * np.sqrt(2.0 / (n_input + n_hidden)) b1 = np.zeros((1, n_hidden)) W2 = np.random.randn(n_hidden, n_output) * np.sqrt(2.0 / (n_hidden + n_output)) b2 = np.zeros((1, n_output)) return W1, b1, W2, b2偏置初始化为0没问题,权重一定要按上述方式初始化。老手可能会告诉你“反正训练会修正”,但初始化不好大概率直接卡在损失不下降的困境里,然后你会怀疑是不是数据有问题、学习率有问题,浪费大量时间在一个只需要一个初始化公式就能解决的问题上。
3. 数据预处理与特征构造:天气质量预测的胜负手在数据不在模型
3.1 数据来源与基本清洗:缺失值、异常值、时间对齐
天气质量预测的原始数据一般来自气象站或空气质量监测站点,常见字段包括:温度、湿度、气压、风速、风向、PM2.5、PM10、SO2、NO2、O3等。拿到手的第一步不是建模,是把数据清洗到能用的程度。
常见的问题是数据里混着空值、重复时间戳、传感器离线导致的长时间零值。我的清洗规则是:连续缺失超过3小时的特征列直接删除该特征,单点缺失用前后两小时均值填充,时间戳按小时对齐到整点。
import pandas as pd df = pd.read_csv('weather_quality.csv', parse_dates=['time']) df = df.sort_values('time').set_index('time') # 重复时间戳去重,保留最后一条 df = df[~df.index.duplicated(keep='last')] # 单点缺失用前后均值填充 df = df.interpolate(method='time', limit=3) # 删除连续缺失过多的列 too_many_nan = df.isnull().sum() > len(df) * 0.2 df = df.drop(columns=df.columns[too_many_nan])interpolate(method='time')是按时间间隔线性插值,比method='linear'更符合气象数据的连续性。limit=3表示最多填充连续3个缺失点,再多的就保留NaN,后面在构造训练集时这些样本会被剔除。还有一个容易忽略的细节:风向数据是0到360度的环形变量,如果把359度和1度直接当成两个差异极大的值输入网络,模型会学得很痛苦,最好拆成sin和cos两个分量。
3.2 滞后特征与滑动窗口:预测的核心是“过去影响未来”
天气质量预测本质上是用历史序列预测未来值,所以要把原始数据改造成监督学习格式。常见做法是构建滞后特征:用过去24小时的PM2.5、温度、湿度等作为输入,预测未来1小时或未来3小时的PM2.5浓度。
def build_sequence_data(df, feature_cols, target_col, n_lags=24, n_horizon=1): X, y = [], [] data = df[feature_cols].values target = df[target_col].values for i in range(n_lags, len(df) - n_horizon): X.append(data[i - n_lags:i]) y.append(target[i + n_horizon]) return np.array(X), np.array(y)n_lags=24表示用过去24小时的数据,n_horizon=1表示预测未来1小时。X的形状是(样本数, 24, 特征数),但BP神经网络是二维结构,需要把三维数据展平成(样本数, 24 * 特征数)。这一步很容易漏,如果不展平直接喂给前面写的forward函数,np.dot会直接报维度错误。
滞后窗口的选择是有讲究的。预测PM2.5这种累积型污染物,24小时滞后效果最好;预测温度这种日变化明显的变量,12小时和24小时两个滞后窗口都做进去,效果比单一窗口好。我一般会把原始特征列和滞后特征拼接成一个大特征矩阵,再统一归一化,这样做的好处是模型能同时感知短期突变和长期趋势。
3.3 归一化必须用MinMaxScaler且要防止数据泄露
天气质量预测模型的激活函数是tanh,输出范围是-1到1,所以输入特征也要归一化到相近范围内。两个常见选择是StandardScaler和MinMaxScaler。我的建议是用MinMaxScaler,把数据缩放到-1到1,和tanh的输出范围对齐。
这里最大的坑是数据泄露:不能用全量数据拟合归一化参数,再切分训练集和测试集。正确做法是先用训练集拟合scaler,再用同一个scaler转换测试集。
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(-1, 1)) # 先切分再归一化,严禁先归一化再切分 split_idx = int(len(X) * 0.8) X_train, X_test = X[:split_idx], X[split_idx:] # scaler只在训练集上fit X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)scaler.fit_transform(X_train)先用训练集计算min和max,再转换。scaler.transform(X_test)用的是训练集上的min和max,不是测试集自己的。如果测试集里某个值超出了训练集的范围,MinMaxScaler会把它压缩到边界外一点,这没问题,不要因此改成在测试集上重新拟合。
为什么这么强调数据泄露?BP神经网络的训练过程是不断修正权重去拟合训练集,如果scaler用了全量数据,测试集的信息已经通过min和max泄漏进了训练过程,测试集损失会虚低,模型部署到新数据上时表现会打折扣。这类时间序列预测模型能不能落地,这个细节比模型结构设计的影响更直接。今天的python环境配置到这一步,环境里numpy、pandas、sklearn三个库缺一不可,缺少时用pip install numpy pandas scikit-learn安装就行。
4. 用Python从零搭建BP神经网络天气预测模型:训练代码与参数调节
4.1 网络结构定义与训练主循环:完整的可运行代码
我把前面几章的内容组合成一个完整的训练代码。这个代码可以直接在你自己的Python环境里跑,只需要把数据文件路径替换掉。
import numpy as np import pandas as pd from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score def tanh(x): return np.tanh(x) def init_weights(n_input, n_hidden, n_output): W1 = np.random.randn(n_input, n_hidden) * np.sqrt(2.0 / (n_input + n_hidden)) b1 = np.zeros((1, n_hidden)) W2 = np.random.randn(n_hidden, n_output) * np.sqrt(2.0 / (n_hidden + n_output)) b2 = np.zeros((1, n_output)) return W1, b1, W2, b2 def forward(X, W1, b1, W2, b2): z1 = np.dot(X, W1) + b1 a1 = tanh(z1) z2 = np.dot(a1, W2) + b2 return z2, a1 def compute_loss(y_true, y_pred): return np.mean((y_true - y_pred) ** 2) def train(X_train, y_train, X_val, y_val, n_hidden=16, lr=0.01, epochs=300): n_input = X_train.shape[1] n_output = 1 W1, b1, W2, b2 = init_weights(n_input, n_hidden, n_output) train_losses, val_losses = [], [] for epoch in range(epochs): # 前向传播 z2_train, a1_train = forward(X_train, W1, b1, W2, b2) loss = compute_loss(y_train, z2_train) # 反向传播 m = X_train.shape[0] dz2 = z2_train - y_train.reshape(-1, 1) da1 = np.dot(dz2, W2.T) * (1 - a1_train ** 2) dW2 = np.dot(a1_train.T, dz2) / m db2 = np.sum(dz2, axis=0) / m dW1 = np.dot(X_train.T, da1) / m db1 = np.sum(da1, axis=0) / m W2 -= lr * dW2 b2 -= lr * db2 W1 -= lr * dW1 b1 -= lr * db1 # 验证集损失 z2_val, _ = forward(X_val, W1, b1, W2, b2) val_loss = compute_loss(y_val, z2_val) train_losses.append(loss) val_losses.append(val_loss) if epoch % 50 == 0: print(f"Epoch {epoch}, Train Loss: {loss:.6f}, Val Loss: {val_loss:.6f}") return W1, b1, W2, b2, train_losses, val_losses这段代码的结构很清晰:先定义tanh和初始化函数,再是forward和loss,最后是训练主循环。每个epoch里,前向传播算出预测值,反向传播用链式法则算出每个参数的梯度,然后更新。验证集只做前向传播,不参与梯度更新,用来监控模型是否过拟合。
训练完成后,用测试集做最终评估:
# 假设X_test_scaled和y_test已经准备好 z2_test, _ = forward(X_test_scaled, W1, b1, W2, b2) y_pred = z2_test.flatten() print("RMSE:", np.sqrt(mean_squared_error(y_test, y_pred))) print("MAE:", mean_absolute_error(y_test, y_pred)) print("R2:", r2_score(y_test, y_pred))RMSE和MAE的单位和预测目标一致,比如预测PM2.5浓度时就是微克每立方米。R2越接近1越好,一般天气质量预测做到0.8以上就算可用。
4.2 三个必调参数:学习率、隐藏层神经元数、epoch数量
学习率lr是最敏感的参数。lr太大,损失函数在最小值附近震荡,甚至发散;lr太小,收敛极慢,300个epoch远远不够。tanh配合Xavier初始化时,我建议从lr=0.01起步。观察训练曲线:如果前50个epoch损失下降很快然后震荡,调小到0.001;如果损失一直缓慢下降没有加速趋势,可以调大到0.05。天气质量数据的特征经过归一化后范围比较均匀,0.01通常表现不错。
隐藏层神经元数n_hidden决定了网络的表达能力。太少拟合能力不足,预测值会呈现出明显的均值回归现象;太多容易过拟合,训练损失低但验证损失不降反升。我的一般规则是:从输入特征数的一半开始试,记录验证集RMSE。如果特征数是20,可以先试10、16、24三组,选验证集RMSE最小的那个。神经元数增加后训练时间线性增长,但BP神经网络本身的训练就很快,几百个epoch也就几秒到十几秒的事。
epoch数量用早停法来定,不要固定。验证损失连续30个epoch没有改善,就停止训练,保留之前最优的参数。这段代码为了保持简洁没有加早停,实际使用时建议加上,防止后期过拟合导致验证损失回升。
4.3 用sklearn的MLPRegressor对比验证自写模型是否正确
自己手写的BP神经网络最大的隐患是反向传播公式或者梯度计算有bug,训练曲线看起来在下降但实际效果很差。这个时候可以用sklearn的MLPRegressor作为“标准答案”来交叉验证。
from sklearn.neural_network import MLPRegressor mlp = MLPRegressor( hidden_layer_sizes=(16,), activation='tanh', solver='adam', learning_rate_init=0.01, max_iter=500, random_state=42 ) mlp.fit(X_train_scaled, y_train) y_pred_sklearn = mlp.predict(X_test_scaled) print("Sklearn RMSE:", np.sqrt(mean_squared_error(y_test, y_pred_sklearn)))activation='tanh'说明sklearn内部也支持tanh激活。如果自写模型和MLPRegressor的RMSE相差在5%以内,说明反向传播逻辑没问题;如果差很多,优先检查梯度是否有bug,最快捷的验证方法是数值梯度法——用(f(w+epsilon) - f(w-epsilon)) / (2*epsilon)近似计算梯度,和反向传播算出的梯度对比。这个对比验证的步骤新手一定要做,血泪经验告诉我,手写BP神经网络不让它跟成熟实现对比跑一次,根本发现不了那些隐蔽的维度顺序错误。
5. BP神经网络天气质量预测模型的避坑指南:五个高频翻车点与排查方法
5.1 损失降到某个值后不再下降,训练曲线出现平台期
现象是训练曲线一开始下降很快,到某个损失值后就平坦了,怎么调学习率和epoch都没用。这个情况在天气质量预测里特别常见,因为天气数据本身含有大量噪声,到达模型表达能力的上限后,损失就不再下降。
原因有两个:一是隐藏层神经元数不够,模型容量不足,预测值整体偏移真实值,此时训练曲线会快速收敛到一个较高的损失值,但预测值看起来还算合理,属于“有偏但稳定”的状态;二是输入特征里混入了和预测目标无关的干扰特征,比如预测PM2.5时放入了风速的分钟级抖动数据,模型需要花一部分容量去拟合这些噪声。
解决方式是先增加神经元数,观察损失是否进一步下降;如果没变化,再用特征重要性分析或者逐个剔除特征来排查。我一般会先删掉明显无关的特征列再训练,通常能让损失再降一截。
5.2 预测值整体偏低或者偏高,R2为负值
现象是测试集上RMSE看起来不大,但R2是负数,说明预测值比直接用均值预测还差。看预测值和真实值的散点图,如果所有预测点都压在一条平线附近,就是这个情况。
原因大概率是y_train没有归一化或者归一化后没有还原。BP神经网络输出层没有激活函数,如果y本身是数量级很大的值,比如PM2.5浓度超过500微克每立方米,输出层的梯度会很大,训练不稳定,最终预测值会趋向训练集均值。
解决方式是把y也做MinMaxScaler归一化,训练时网络输出的是-1到1范围的数值,预测后用scaler_y.inverse_transform还原成真实的浓度值。千万别忘了保存y的scaler,部署时才方便反归一化。
5.3 训练时梯度爆炸,损失出现NaN
现象是训练到中途,损失突然变成nan,整个训练直接报废。在BP神经网络里,这是一个很经典的数值不稳定问题。
原因是学习率过大,或者权重更新后某个中间层的加权输入超出了tanh能表达的范围。tanh本身对极端输入有饱和保护,但反向传播时梯度经过多层链式乘法,数值可能迅速膨胀,最终溢出为NaN。
解决方式是调低学习率到0.001以下重新训练;检查输入数据里是否有inf或极端异常值,这种异常值在归一化后依然可能保持很大的相对值;给梯度更新加一个裁剪,比如梯度范数超过1.0就等比例缩小。先检查数据,再调学习率,大多数情况下第一步数据检查就能发现问题。
5.4 时间序列切分失误:随机切分导致测试集“偷看”未来
现象是训练时验证损失和测试损失都很低,但模型上线后预测效果很差。原因是切分训练集和验证集时用了train_test_split默认的随机切分模式,导致训练集里有测试集时间点前后的数据。
天气质量预测是时间序列任务,只能用时间顺序切分。我见过直接把train_test_split(X, y, test_size=0.2)拿来用的新手,这在普通回归里没错,但在序列预测里就是翻车现场——模型在训练时已经见过了测试时间段的相邻数据,评估结果虚高。
解决方式固定用时间切分:
split_idx = int(len(X) * 0.8) X_train, X_val = X[:split_idx], X[split_idx:]如果是T+24小时的预测任务,切分点之前要留出24小时以上的缓冲区间,防止训练样本和验证样本重叠产生信息泄漏。具体的缓冲时间根据滞后窗口大小确定。
5.5 tanh输出范围与预测目标不匹配,数值范围对不上
现象是预测出的值是负的,而天气质量指标例如PM2.5浓度、AQI指数严格来说是正数。原因是输出层直接用了tanh作为激活函数,把输出限制在了-1到1之间。
天气预测的场景里,输出层一般不激活,回归任务输出层用线性激活也就是无激活函数。隐藏层用tanh,输出层保持线性,这样预测值才能超过[-1,1]的范围,再通过反归一化映射回真实数值空间。如果因为误操作给输出层加了tanh,预测值永远落在[-1,1]之间,负值就成了大概率事件。
6. 验证模型好坏的两个进阶技巧:用baseline对比和逐小时误差分析
模型训练完不是看一眼RMSE就结束了。对我的实用习惯来说,第一件事是算一个baseline:直接用“过去24小时的均值作为未来1小时的预测值”,这是一个没有任何学习能力的预测。如果BP神经网络的RMSE比这个baseline还差或者差不多,说明网络没有学到有效规律,问题大概率出在特征构造上而不是模型结构上。
baseline_pred = np.mean(X_test_scaled[:, :24, 0], axis=1) # 第一个特征过去24小时均值 baseline_rmse = np.sqrt(mean_squared_error(y_test, baseline_pred))第二个进阶技巧是逐小时误差分析。把预测误差按小时分开画箱线图,你会发现某些时段误差特别大。比如凌晨时段由于气象站数据更新频率低,误差普遍偏高;早晚高峰时段因为污染物排放突增,误差也偏高。如果发现固定时段系统性偏差,可以在特征里加一个小时序号作为输入,让网络学到时段规律。
hour = df.index.hour.values # 小时信息 hour_sin = np.sin(2 * np.pi * hour / 24) hour_cos = np.cos(2 * np.pi * hour / 24)把hour_sin和hour_cos拼进特征矩阵,相当于给模型增加了“当前是什么时段”的编码信息,通常能将高峰时段的预测误差减少10%到20%。这比盲目调整网络结构性价比高得多,因为BP神经网络的结构在上一阶段已经定好了,真正影响泛化能力的是输入特征的信息量。
这个方向做到这里已经具备基本落地能力了。如果想再进一步,可以尝试把单步预测改成滚动多步预测:把T+1的预测结果作为输入,预测T+2,循环下去,但要注意误差会逐级累积,一般做到T+6小时是实际的可靠边界。我自己做滚动预测时发现,超过6小时后的预测曲线明显平滑化,这是BP网络对误差累积的自然表现,不是bug,是模型能力边界。
这是我做过几轮天气质量预测模型后最重要的一个习惯:每改一个参数,就把自动记录一份带参数说明的结果日志存下来。没有日志,你永远不会知道今天这组结果和昨天那组差在哪里。希望帮到你。
本文还有配套的精品资源,点击获取