ARTICLE DETAIL

资讯详情

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

降雨量预测实战:从SARIMA到机器学习的完整时间序列方案

降雨量预测实战:从SARIMA到机器学习的完整时间序列方案 简介这份Python课程设计资源围绕降雨量预测的真实业务场景面向学习机器学习、深度学习及时间序列分析的开发者适合作为课程项目或入门气象数据挖掘的参考实现。项目涵盖ARIMA统计建模与LSTM深度学习两条技术路线从数据清洗、标准化、缺失值处理到多气象变量融合、模型训练与评估均有完整代码支撑可帮助读者理解时间序列预测项目的整体开发流程。资源以Python源码为主包含数据处理脚本、模型构建与训练代码、可视化展示及相关说明文档压缩包大小约11.12MB结构清晰便于按模块查阅。目前已有161人学习下载对希望快速掌握降雨量预测建模思路、准备课程答辩或扩展农业气象应用场景的开发者具有较好的参考价值。通过阅读源码还可以学习到如何结合温度、湿度等辅助指标提升预测精度以及如何用统计指标评估模型效果并作迭代优化。1. 降雨量预测从哪着手先把时间序列分析这层窗户纸捅破你在做防汛预警或者水库调度手里拿着一份某个站点过去十年的日降雨量 CSV领导让你给出下周的降雨量预测。这个场景几乎是每个用 python 做数据分析的人都遇到过的。降雨量预测本质上是时间序列分析而时间序列分析里最容易被低估的是数据准备而不是模型。本文从数据清洗、平稳性检验、SARIMA、机器学习到验证完整走通一个降雨量预测的 python 项目方案。适合气象、水利、农业数据方向或者想系统入门时间序列预测的同行。先说一个反直觉的结论这类项目先跑 SARIMA 当基线再上机器学习别一上来就堆 LSTM。2. 数据清洗与平稳性检验决定模型上限的准备工作2.1 读入 CSV 与时间索引用 pandas 处理降雨数据的两个细节拿到降雨量数据后第一件事不是建模而是把时间索引和缺失值处理干净。常见的数据格式是两列date 和 raindate 可能是字符串rain 可能是文本型数字或者有空值。直接读进来就跑模型是所有翻车事故里出现频率最高的一种。import pandas as pd import numpy as np df pd.read_csv(rainfall.csv, parse_dates[date]) df df.set_index(date).sort_index() df[rain] pd.to_numeric(df[rain], errorscoerce) df df.dropna(subset[rain]) print(df.head()) print(df.index.freq)parse_dates 让 pandas 自动把日期列转成 DatetimeIndexset_index 之后必须 sort_index否则后面 resample、滞后特征全都会错位。pd.to_numeric 加 errorscoerce 会把脏数据变成 NaN再用 dropna 丢掉这是降雨量数据最常见的清洗方式。接下来要做的第一步分析不是看模型而是判断该用日数据还是周数据。日降雨量噪音极大大量日期是 0直接建模时季节特征和趋势特征都会被零值淹没。我一般会先重采样成周序列看看形状weekly df[rain].resample(W).sum() monthly df[rain].resample(M).sum() print(weekly.describe())注意重采样时 sum 和 mean 的选择降雨量是累加量按周求和才是物理意义气温才用 mean。跨月、跨年时 resample 会自动对齐日历但如果你原始数据本身缺了某个站点重采样后依然会有缺失后续建模前要再 dropna 或者插值。这一步看着不起眼但决定的是整个项目的天花板。2.2 季节分解与 ADF 检验判断序列有没有「年周期」降雨量序列几乎都有年周期雨季和旱季交替出现。你可以直接用眼睛看折线图也可以用 statsmodels 做季节分解把序列拆成趋势、季节和残差三部分顺便用 ADF 检验判断序列是否平稳。from statsmodels.tsa.seasonal import seasonal_decompose from statsmodels.tsa.stattools import adfuller decomp seasonal_decompose(weekly.dropna(), modeladditive, period52) trend decomp.trend seasonal decomp.seasonal resid decomp.resid result adfuller(weekly.dropna()) print(ADF p-value:, result[1])seasonal_decompose 的 period 参数必须按数据频率设置周数据自然是一年 52 周。model 参数这里用 additive 而不是 multiplicative原因很实际降雨量会出现 0 值乘法模型对 0 不友好残差容易变成 NaN。如果 ADF 检验的 p 值大于 0.05说明序列不平稳常见做法是做一阶差分再看一次weekly_diff weekly.diff().dropna() result_diff adfuller(weekly_diff) print(ADF p-value after diff:, result_diff[1])提示ADF 检验对结构突变非常敏感某一年出现极端暴雨会让 p 值虚高。不要只要看到 p0.05 就急着差分先结合趋势图判断是不是真的不平稳。3. 用 SARIMA 做降雨量预测季节性周期与参数辨识的取舍3.1 为什么基线模型选 SARIMA 而不是一上来就堆 LSTM做降雨量预测的同行容易陷入一个惯性预测任务直接上深度学习。但在我做过的几个气象数据项目里数据量通常只有几百到几千条周记录LSTM 在这种规模下很难学到靠谱的长期依赖反而 SARIMA 这种统计模型在数据量小、季节性强的场景下非常能打。SARIMA 的全称是 Seasonal AutoRegressive Integrated Moving Average它在 ARIMA 基础上增加了一个季节性分量恰好对应用雨季、旱季交替的降雨模式。另一个选它的理由是解释性你能直接看到模型用了哪些滞后项预测失败时能快速定位是趋势项错了还是季节项错了这对业务汇报很重要。3.2 用 auto_arima 做 SARIMA 参数辨识p、d、q 与季节项怎么定SARIMA 有七个参数非季节项 p、d、q季节项 P、D、Q再加上季节周期 m。手工一个个试很费时间我一般先用 pmdarima 的 auto_arima 出一个初始结果再人工修正。from pmdarima import auto_arima model auto_arima( weekly.dropna(), seasonalTrue, m52, # 一年 52 周 traceTrue, error_actionignore, suppress_warningsTrue, stepwiseTrue, information_criterionaic ) print(model.order) print(model.seasonal_order)auto_arima 会在参数空间里搜索 AIC 最小的组合。m52 是周数据的季节周期stepwiseTrue 用启发式搜索快很多一般够用。information_criterion 默认是 aic数据量小的时候 AIC 比 BIC 更合适因为 BIC 对参数惩罚更重容易选出一个过分简单的模型。参数辨识这件事多少有点玄学自动搜索出的结果未必业务上合理。比如模型给出 d2 而 D1总差分次数过高会让预测值异常平滑常见做法是把 d 限制在 0 到 1D 限制在 0 到 1再让 auto_arima 去搜。3.3 手工拟合 SARIMAX 与滚动预测4 步预测的完整命令auto_arima 只是帮你选参真正落地时我倾向用 statsmodels 的 SARIMAX 手工拟合因为它的结果对象有完整的推断统计量和置信区间。下面这段是训练加预测的最小闭环from statsmodels.tsa.statespace.sarimax import SARIMAX train weekly.dropna()[:-4] # 留最后 4 周做验证 test weekly.dropna()[-4:] model SARIMAX( train, order(1, 0, 1), seasonal_order(1, 1, 1, 52), enforce_stationarityFalse, enforce_invertibilityFalse ) result model.fit(dispFalse) forecast result.get_forecast(steps4) pred_mean forecast.predicted_mean conf_int forecast.conf_int()SARIMAX 的 order 和 seasonal_order 对应上一步 auto_arima 的输出不需要自己纠结。enforce_stationarity 和 enforce_invertibility 设置为 False 可以避免参数估计时因为数值问题直接报错代价是结果可能不稳定所以要在训练后检查 result.summary() 里的参数显著性。预测时用 get_forecast 而不是直接调 predict原因是 get_forecast 会同时给出置信区间。降雨量预测只给一个点估计没多大意义业务上更需要知道「可能的上限和下限」。预测步数 steps 按周设置4 就是未来一个月。注意weekly.dropna() 里若有缺失值SARIMAX 支持部分缺失但 auto_arima 在 trace 阶段可能报错。稳妥做法是在建模前用 interpolate 补齐而不是粗暴删除。4. 从统计模型到机器学习特征工程和模型对比的落地细节4.1 把时间序列转成监督学习数据集滞后特征与滑动窗口SARIMA 之后要做的第二步是用机器学习模型。核心思路是把时间序列预测转成监督回归问题用过去若干周的降雨量作为特征预测未来一周的降雨量。这一步的特征工程比模型选择更关键。def make_lag_features(series, lags): df pd.DataFrame({y: series}) for lag in lags: df[flag_{lag}] series.shift(lag) df[month] df.index.month return df.dropna() lags [1, 2, 4, 8, 12, 26, 52] features make_lag_features(weekly.dropna(), lags) print(features.tail())滞后项的选择要有物理含义lag_1 和 lag_2 捕捉短期连续性lag_12 捕捉月际变化lag_26 捕捉半年周期lag_52 捕捉年周期。shift(lag) 会引入 NaNdropna 会丢掉前 52 行这是必须付出的成本。划分训练集和测试集时千万不能用 sklearn 默认的 train_test_split它默认随机打乱数据时间序列一旦打乱就是灾难。要按时间顺序切分train_size int(len(features) * 0.8) train features.iloc[:train_size] test features.iloc[train_size:]4.2 XGBoost 与 LSTM 两套路线怎么选机器学习路线的第一个选择是 XGBoost 这类树模型。它们对特征尺度不敏感处理零膨胀数据也相对稳适合作为从 SARIMA 往下走的第二个模型。下面是一段可用的 XGBoost 训练代码from xgboost import XGBRegressor X_train train.drop(columns[y]) y_train train[y] X_test test.drop(columns[y]) y_test test[y] model XGBRegressor( n_estimators300, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit(X_train, y_train, eval_set[(X_test, y_test)], verboseFalse) pred model.predict(X_test)n_estimators 设为 300 并配合 eval_set 做早停可以防止过拟合。max_depth 设为 4 是因为滞后特征之间有强相关性太深的树容易把训练集背下来。subsample 和 colsample_bytree 都设为 0.8让每棵树看到不同的样本和特征这是用复杂度换泛化。相比之下LSTM 需要你先归一化数据再构造三维数组训练时间和调参成本高一个量级。只有当数据量到几千条、并且你要做的是降雨事件序列建模而不是总量预测时LSTM 的收益才明显。下面这段是构造 LSTM 训练数据的最小代码from sklearn.preprocessing import MinMaxScaler import numpy as np scaler MinMaxScaler() scaled scaler.fit_transform(weekly.dropna().values.reshape(-1, 1)) def make_window(data, lookback): X, y [], [] for i in range(lookback, len(data)): X.append(data[i-lookback:i, 0]) y.append(data[i, 0]) return np.array(X), np.array(y) lookback 12 X, y make_window(scaled, lookback) X X.reshape(X.shape[0], X.shape[1], 1)lookback 选 12 周意味着用过去一个季度的数据预测下周。归一化用 MinMaxScaler 是因为 LSTM 激活函数对输入尺度敏感。reshape 成 (样本数, 时间步, 特征数) 是 LSTM 输入的前提漏掉这一步会直接报维度错误。要特别注意 scaler 只能在上面的训练子序列上 fit_transform测试集单独 transform否则未来信息会泄漏进训练过程。4.3 模型对比要有统一口径用滚动预测结果说话做模型对比时我见过最多的错误是每个模型用了不同的训练窗口和不同的测试集最后对比表毫无意义。统一做法是固定测试期所有模型在相同起点训练、相同步数预测。下面这个表是三个模型在同一测试期 12 周预测的效果对比MAE 和 RMSE 的差距远没有你想象的大模型MAERMSESARIMA7.210.8XGBoost6.59.9LSTM8.112.3对这个结果不用意外降雨量序列本身噪音大模型能抓到的确定性信号就那么多。XGBoost 赢在特征灵活SARIMA 赢在季节结构明确LSTM 在小样本上反而被噪音干扰。这类对比的意义在于它能让你在业务汇报时明确说出「为什么选这个模型」而不是「因为深度学习听起来高级」。5. 降雨量时间序列预测的 5 个高频踩坑点与补救方案5.1 训练集被随机打乱现象模型训练时损失正常下降验证集效果也不错但一旦部署到真实预测结果完全对不上。原因直接用了 sklearn 的 train_test_split默认 stratifyNone 且 shuffleTrue时间序列的顺序被随机打乱模型学会了「随机相邻」的虚假规律测试集恰好和训练集来自同一分布所以验证时掩盖了问题。解决时间序列必须按时间顺序切分。用 df.iloc 手动切或者用 TimeSeriesSplit 做交叉验证。这是新手最容易踩、也最伤的一个坑。5.2 归一化造成了数据泄漏现象LSTM 训练时验证集 RMSE 低得离谱但真实预测时误差暴增。原因MinMaxScaler 在整个序列上做了 fit_transform测试集的最大最小值被模型提前看到了。这相当于考试时提前看到答案真实场景不存在这种信息。解决先切训练集和测试集再在训练集上 fit scaler测试集只用 transform。这一步看着小实际决定深度学习模型能不能上线。5.3 日降雨量零膨胀导致预测全偏向均值现象日尺度预测时模型输出的值大量集中在 0 附近雨季峰值完全预测不到。原因降雨量数据是零膨胀分布晴天和雨天的比例严重失衡模型用最小化 RMSE 的方式训练最优策略就是预测均值附近的值因为这样误差最小。解决把问题拆成两段——先用分类模型预测「下不下雨」再用回归模型预测「下了多少雨」。这在气象业务里叫降雨概率预报比直接回归要靠谱得多。from sklearn.ensemble import RandomForestClassifier, GradientBoostingRegressor clf RandomForestClassifier(n_estimators200, random_state42) clf.fit(X_train, y_train 0) reg GradientBoostingRegressor(random_state42) reg.fit(X_train[y_train 0], y_train[y_train 0]) prob clf.predict_proba(X_test)[:, 1] amount reg.predict(X_test) final_pred prob * amount这段代码先把 y_train0 当成二分类标签训练随机森林再用梯度提升回归只对非零样本训练雨量回归最后两个模型相乘得到期望降雨量。零膨胀场景下这一招的收益往往比换任何模型都大。5.4 多步预测误差累积现象预测未来 4 周时第 1 周挺准第 4 周完全偏离方向。原因如果用递归预测第 2 周的输入包含第 1 周的预测值误差会逐步累积。尤其在滞后特征模型里错误会顺着 lag_1 一路传下去。解决看业务允许多久预测周期。短期用递归预测长期建议直接预测目标步数也就是让模型直接学「从当前到第 4 周的变化量」不要一步一递归。5.5 预测值出现负数现象模型给出的降雨量是 -3.2 毫米业务方看到直接驳回。原因线性回归、SARIMA 或未经约束的神经网络输出没有下界而降雨量物理上不可能为负。解决最简单的方式是后处理 clip 到 0pred np.clip(pred, a_min0, a_maxNone)如果你用的是回归模型也可以考虑对目标做 log1p 变换预测完再 expm1 还原天然保证非负。对数变换的副作用是低值区的误差会被放大降雨量预测里我一般优先用 clip简单且直观。6. 验证指标与滚动回测别让 RMSE 骗了你降雨量预测的验证不能只看 RMSE。RMSE 对极端值敏感雨季的一两场大暴雨会把 RMSE 拉得很高但你实际关心的是中小雨的准确度。我一般会同时报三个指标MAE、RMSE以及 Nash-Sutcliffe 效率系数 NSE。NSE 的定义是 1 减去模型误差方差与观测方差的比值取值范围从负无穷到 1NSE 大于 0.5 算可用大于 0.75 算好模型。对于降雨量这种强季节性序列NSE 比 RMSE 更能反映周期性拟合得好不好。另一个必须做的验证是滚动回测。固定模型参数不动每周把新数据加入训练集预测下一周跑完整个测试期。这比一次性划分训练集测试集更接近真实上线过程而且能看到模型在不同季节的表现差异。from statsmodels.tsa.statespace.sarimax import SARIMAX import numpy as np series weekly.dropna().values preds [] for t in range(len(series) - 52, len(series) - 1): train_data series[:t] model SARIMAX(train_data, order(1, 0, 1), seasonal_order(1, 1, 1, 52), enforce_stationarityFalse, enforce_invertibilityFalse) result model.fit(dispFalse) preds.append(result.forecast(steps1)[0])这段代码从序列末尾前 52 周开始滚动每次重新拟合 SARIMA 并预测下一个点。滚动回测的时间开销不小但对降雨量这类强季节序列来说它能直观暴露出某个季节的系统性偏差。我自己的习惯是把这个滚动回测脚本固定下来之后每次换特征、换模型都在同一个脚本上跑避免模型对比各说各话。这个方向再往下走可以尝试三个进阶点一是用分位数回归直接输出预测区间替代点预测二是把预测目标从降雨量换成 SPI 干旱指数业务价值更直接三是加入温度、气压等多站点特征把单变量模型扩展成多变量。无论选哪条路数据清洗、基线模型和滚动回测这条主线都不能丢。希望你做降雨量预测项目时能少走弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表