尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

时间序列预测实战:从SARIMA到Prophet与TBATS的多重季节性建模

时间序列预测实战:从SARIMA到Prophet与TBATS的多重季节性建模 1. 项目概述从“看天吃饭”到“算天吃饭”的预测艺术做数据分析或者业务预测最头疼的莫过于那些“有规律地波动”的数据。比如电商的销售额每逢周末就飙升电力消耗在夏季和冬季形成两个高峰交通流量在早晚高峰规律性拥堵。这些现象背后就是“季节性”。如果数据里藏着不止一种周期规律比如既有“周内效应”周一到周日又有“年度效应”不同月份甚至还有“日内效应”一天24小时问题就复杂了。这就是“季节性时间序列模型”和它的进阶版——“多重季节性模型”要解决的核心问题。简单来说季节性时间序列模型是预测领域的“基础语法”它教会我们如何识别和建模数据中单一的、固定的周期性规律。而多重季节性模型则是“高阶语法”用于处理那些同时受多种周期力量驱动的复杂数据比如同时考虑小时、天、周、月、年等多个维度的周期性。这次分享我将结合具体的代码和分析带你从原理到实战彻底搞懂如何用这些模型把看似杂乱无章的波动数据变成可预测、可规划的“宝藏”。无论你是数据分析师、量化研究员、运维工程师还是产品经理只要你手头的数据带有时间标签和周期性波动这篇文章都能给你一套可直接上手的工具箱。我们会从最经典的SARIMA模型讲起再深入到处理多重季节性的TBATS和Prophet模型并用真实的案例和代码展示从数据准备、模型选择、参数调优到结果评估的全过程。我会把踩过的坑、调参的心得、以及如何解读那些令人困惑的模型输出都毫无保留地分享出来。2. 核心思路与模型选型为什么是它们面对一个时间序列预测问题模型选择不是拍脑袋决定的它取决于数据的特性。我们的思路是一个递进的过程先判断有没有季节性再判断是单一季节性还是多重季节性最后根据数据量、趋势复杂度等因素选择具体模型。2.1 第一层判断经典季节性模型SARIMA当你的数据呈现出明显的、单一的周期性比如明显的年度周期或季度周期并且趋势相对稳定或可通过差分消除时SARIMA (Seasonal AutoRegressive Integrated Moving Average)模型通常是首选。它是ARIMA模型的季节性扩展在业界经过了数十年的检验理论扎实解释性强。SARIMA模型的核心参数可以表示为SARIMA(p, d, q)(P, D, Q)[s]这看起来复杂但拆解开来就清晰了(p, d, q): 这是非季节性部分。p(自回归阶数)当前值受过去多少个时间点的值影响。好比说今天的温度很大程度上受昨天和前天的温度影响。d(差分阶数)为了让序列变得平稳即均值和方差基本不随时间变化需要进行差分的次数。这是处理趋势的关键。q(移动平均阶数)当前值受过去多少个时间点的预测误差残差影响。好比说今天的预测误差会影响明天的预测。(P, D, Q)[s]: 这是季节性部分结构与非季节性部分类似但作用在季节性周期上。P: 季节性自回归阶数。D: 季节性差分阶数。Q: 季节性移动平均阶数。[s]: 季节性周期长度。例如月度数据且具有年度季节性则s12日度数据且具有周季节性则s7。为什么选SARIMA它的优势在于模型透明每个参数都有明确的统计意义。通过观察自相关图(ACF)和偏自相关图(PACF)我们可以对p, q, P, Q进行初步定阶。模型拟合后我们可以检验残差是否像白噪声随机、无规律从而判断模型是否充分提取了数据中的信息。注意SARIMA的一个主要限制是它只能处理单一的季节性周期。如果你的数据同时受周和年影响传统的SARIMA就力不从心了。虽然可以通过将周期设为s365来强行建模年度季节性但这会忽略周内模式且模型会变得极其复杂参数难以估计。2.2 第二层判断应对复杂周期的多重季节性模型当数据中存在两种或以上显著的季节性周期时我们就需要请出更强大的武器。这里主要有两个主流选择TBATS和Facebook Prophet。2.2.1 TBATS模型灵活且强大的“数学引擎”TBATS是一个缩写代表 Trigonometric seasonality, Box-Cox transformation, ARMA errors, Trend and Seasonal components。这个名字几乎概括了它的所有特性。它特别擅长处理高频数据比如每小时、每半小时的数据。复杂季节性可以同时指定多个季节性周期例如seasonal_periods[24, 24*7]来表示“日周期24小时”和“周周期168小时”。非整数周期有些周期可能不是整数TBATS也能处理。时变季节性季节性模式本身会随着时间缓慢变化TBATS也能捕捉。它的核心思想是用一组傅里叶级数正弦余弦函数的组合来拟合季节性成分。傅里叶项的数量是一个可调参数项数越多拟合的季节性形状越复杂。TBATS会自动通过信息准则如AIC来选择最优的傅里叶项数和模型其他参数。实操心得TBATS模型非常强大但计算量也相对较大尤其是当数据量很大、季节性周期很长时。在Python中我们可以使用tbats库。它的一个优点是能输出每个季节性成分的分解图让你清晰地看到“日效应”、“周效应”等分别对整体预测贡献了多少。2.2.2 Prophet模型直观易用的“黑盒神器”由Facebook开源的Prophet其设计哲学与TBATS不同。它更像一个“分析师友好”的工具将时间序列分解为三个主要成分趋势项 (g(t))模拟非周期性的变化支持线性或饱和增长趋势并能自动检测变点。季节性项 (s(t))使用傅里叶级数来拟合周期性变化。你可以通过add_seasonality函数非常方便地添加多个季节性例如# 添加年度季节性周期365.25天考虑闰年 model.add_seasonality(nameyearly, period365.25, fourier_order10) # 添加周季节性 model.add_seasonality(nameweekly, period7, fourier_order3) # 甚至添加“工作日vs周末”这种定制季节性 model.add_seasonality(namedaily, period1, fourier_order5) # 如果数据是日粒度节假日效应 (h(t))这是Prophet的一大亮点可以显式地加入已知的节假日或事件模型会单独估计这些日子的影响。为什么选Prophet它的API极其简单对缺失值、异常值有较好的鲁棒性并且能生成带有不确定性的预测区间。更重要的是它的预测结果非常容易向业务方解释“我们的预测考虑了长期趋势、每周的波动规律、每年的旺季淡季以及国庆节这种特殊节日的影响。” 这在实际项目中价值巨大。选型总结数据频率低、季节单一、需强解释性-SARIMA数据频率高、季节多重且复杂、需精确数学模型-TBATS追求快速部署、需要加入节假日因素、希望结果易于解释和沟通-Prophet在实际项目中我通常会先用Prophet快速出一个基线模型和可视化结果了解数据的大致特性。如果对精度要求极高再考虑用TBATS或更复杂的集成方法进行精细调优。3. 实战演练用电负荷预测代码分析我们用一个模拟的“小时级用电负荷数据”来串联整个流程。假设数据包含明显的日周期24小时和周周期24*7168小时并且存在一个长期的缓慢上升趋势。3.1 数据准备与探索性分析任何时间序列分析的第一步都是“看”数据。我们使用pandas和statsmodels库。import pandas as pd import numpy as np import matplotlib.pyplot as plt from statsmodels.tsa.seasonal import seasonal_decompose from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import warnings warnings.filterwarnings(ignore) # 1. 生成模拟数据 np.random.seed(42) date_range pd.date_range(start2023-01-01, end2023-12-31, freqH) n len(date_range) # 基础趋势缓慢线性增长 trend np.linspace(100, 150, n) # 日季节性以24小时为周期 daily_seasonality 20 * np.sin(2 * np.pi * np.arange(n) / 24) # 周季节性以168小时为周期幅度更大 weekly_seasonality 30 * np.sin(2 * np.pi * np.arange(n) / 168 0.5) # 噪声 noise np.random.normal(0, 5, n) # 合成序列 load trend daily_seasonality weekly_seasonality noise df pd.DataFrame({timestamp: date_range, load: load}) df.set_index(timestamp, inplaceTrue) # 2. 可视化 fig, axes plt.subplots(3, 1, figsize(14, 10)) df.plot(axaxes[0], titleHourly Load Data (Raw)) axes[0].set_ylabel(Load) # 查看一周的数据观察日周期 df.loc[2023-06-01:2023-06-07].plot(axaxes[1], titleOne Week of Data (Highlights Daily Cycle)) axes[1].set_ylabel(Load) # 季节性分解假设乘性模型周期为24*7168小时 result seasonal_decompose(df[load], modeladditive, period168) result.plot(axaxes[2]) plt.tight_layout() plt.show()这段代码会生成三张图。第一张是全年数据可以看到长期趋势和波动。第二张是一周的数据可以清晰看到以天为单位的波形起伏。第三张是经典分解将序列拆分为趋势、季节性和残差三部分。如果季节性成分的波动幅度相对稳定用additive加法模型如果波动幅度随趋势增大而增大则用multiplicative乘法模型。这里我们假设是加法模型。关键分析点从分解图中我们可以直观地看到趋势是否明显季节性是否稳定以及残差是否看起来是随机的白噪声。如果残差还有明显模式说明模型这里指简单的分解模型未能完全捕捉数据中的信息。3.2 单一季节性建模SARIMA实战我们先尝试用SARIMA建模只考虑最主要的周季节性s168。from statsmodels.tsa.statespace.sarimax import SARIMAX from sklearn.metrics import mean_absolute_error, mean_squared_error # 划分训练集和测试集最后一周作为测试集 train df.iloc[:-168] test df.iloc[-168:] # 最后一周168小时 # 通过ACF/PACF图初步定阶 fig, axes plt.subplots(1, 2, figsize(12, 4)) plot_acf(train[load], lags50, axaxes[0]) # 观察自相关 plot_pacf(train[load], lags50, axaxes[1]) # 观察偏自相关 plt.show()ACF图在滞后24 48 72...以及168 336...处有显著峰值证实了日周期和周周期的存在。PACF图可以帮助我们初步判断p和P的阶数。但更可靠的方法是使用网格搜索或自动定阶工具如pmdarima.auto_arima。# 手动尝试一个SARIMA模型 (这是一个示例实际中需要调参) order (1, 1, 1) # (p, d, q) seasonal_order (1, 1, 1, 168) # (P, D, Q, s) model_sarima SARIMAX(train[load], orderorder, seasonal_orderseasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse) results_sarima model_sarima.fit(dispFalse) print(results_sarima.summary()) # 预测 forecast_sarima results_sarima.get_forecast(stepslen(test)) pred_mean_sarima forecast_sarima.predicted_mean pred_ci_sarima forecast_sarima.conf_int() # 评估 mae_sarima mean_absolute_error(test[load], pred_mean_sarima) rmse_sarima np.sqrt(mean_squared_error(test[load], pred_mean_sarima)) print(fSARIMA MAE: {mae_sarima:.2f}, RMSE: {rmse_sarima:.2f}) # 绘制预测结果 plt.figure(figsize(12, 6)) plt.plot(train.index[-500:], train[load].iloc[-500:], labelTrain (Last 500h)) plt.plot(test.index, test[load], labelTest (Actual), colororange) plt.plot(test.index, pred_mean_sarima, labelTest (SARIMA Forecast), colorred) plt.fill_between(test.index, pred_ci_sarima.iloc[:, 0], pred_ci_sarima.iloc[:, 1], colorred, alpha0.2) plt.legend() plt.title(SARIMA Forecast vs Actuals) plt.show()查看summary()输出重点关注系数显著性P|z|列值小于0.05通常认为该系数显著。模型诊断残差检验Ljung-Box检验的p值应大于0.05说明残差无自相关正态性检验Jarque-Bera检验。信息准则AIC, BIC值用于模型比较值越小越好但要在相同数据集上比较。踩坑提醒SARIMA模型对(p,d,q)(P,D,Q,s)的参数非常敏感。手动调参如同大海捞针。强烈建议在正式建模时使用pmdarima库的auto_arima函数进行自动参数搜索它能节省大量时间并找到更优的组合。另外季节性差分D通常设为1就足够了设置过高可能导致过拟合或数值不稳定。3.3 多重季节性建模Prophet实战现在我们用Prophet来同时建模日周期和周周期。from prophet import Prophet # 1. 准备数据Prophet要求两列ds和y df_prophet df.reset_index() df_prophet.columns [ds, y] train_p df_prophet.iloc[:-168] test_p df_prophet.iloc[-168:] # 2. 创建并配置模型 model_prophet Prophet( yearly_seasonalityFalse, # 我们模拟数据没有年周期所以关闭 weekly_seasonalityTrue, # 启用内置的周季节性傅里叶阶数可调 daily_seasonalityTrue, # 启用内置的日季节性 seasonality_modeadditive # 加法模型与生成数据一致 ) # 也可以更精细地控制季节性例如修改傅里叶阶数 # model_prophet.add_seasonality(nameweekly_custom, period7, fourier_order5) # 3. 拟合模型 model_prophet.fit(train_p) # 4. 构建未来时间框并预测 future model_prophet.make_future_dataframe(periods168, freqH) forecast model_prophet.predict(future) # 5. 提取预测结果最后168小时 pred_prophet forecast.iloc[-168:][[ds, yhat, yhat_lower, yhat_upper]].set_index(ds) # 6. 评估 mae_prophet mean_absolute_error(test_p[y], pred_prophet[yhat]) rmse_prophet np.sqrt(mean_squared_error(test_p[y], pred_prophet[yhat])) print(fProphet MAE: {mae_prophet:.2f}, RMSE: {rmse_prophet:.2f}) # 7. 可视化 fig1 model_prophet.plot(forecast) fig2 model_prophet.plot_components(forecast)Prophet的plot_components函数是神器它能将趋势、周季节性和日季节性分别可视化出来。你可以清晰地看到模型学到的“周模式”比如周末负荷较低和“日模式”白天高夜晚低是什么样子。与SARIMA对比Prophet的预测曲线通常会更加平滑并且能直接给出预测区间。在这个模拟案例中由于我们显式加入了日周期和周周期Prophet的表现很可能会优于只考虑了周周期的SARIMA模型。3.4 多重季节性建模TBATS实战最后我们使用TBATS来处理同样的多重季节性问题。from tbats import TBATS # 1. 准备数据TBATS接受数组或Series train_series train[load] # 2. 定义多重季节性周期 seasonal_periods [24, 168] # 日周期24小时周周期168小时 # 3. 创建估计器并进行网格搜索寻找最优傅里叶项数等 estimator TBATS(seasonal_periodsseasonal_periods, use_arma_errorsTrue, # 使用ARMA模型拟合残差 use_box_coxFalse) # 数据无明显指数趋势暂不进行Box-Cox变换 model_tbats estimator.fit(train_series) # 4. 预测 pred_tbats model_tbats.forecast(steps168) pred_tbats_series pd.Series(pred_tbats, indextest.index) # 5. 评估 mae_tbats mean_absolute_error(test[load], pred_tbats_series) rmse_tbats np.sqrt(mean_squared_error(test[load], pred_tbats_series)) print(fTBATS MAE: {mae_tbats:.2f}, RMSE: {rmse_tbats:.2f}) # 6. 可视化 plt.figure(figsize(12, 6)) plt.plot(train.index[-200:], train[load].iloc[-200:], labelTrain) plt.plot(test.index, test[load], labelTest Actual, alpha0.7) plt.plot(test.index, pred_tbats_series, labelTBATS Forecast, colorgreen) plt.legend() plt.title(TBATS Forecast) plt.show() # 7. 查看模型摘要和成分分解 print(model_tbats.summary()) # TBATS模型通常也提供成分分解但可视化可能需要自己根据模型参数计算。TBATS模型的summary()会输出非常详细的信息包括选择的傅里叶项数、趋势参数、ARMA参数等。你可以看到它为24小时周期和168小时周期分别选择了多少个傅里叶项来拟合季节性。模型对比小结 在这个模拟案例中我们可能会发现SARIMA (单季节)由于只建模了周季节性它在捕捉日内波动时表现不佳预测的日曲线可能过于平滑或相位有偏差。Prophet能够很好地捕捉日和周双重模式预测曲线更贴合实际且自带不确定性区间和直观的成分分解。TBATS作为纯统计模型其预测精度可能与Prophet相当甚至更优因为它对季节性的数学刻画非常灵活。但它的输出不如Prophet直观调参也更依赖于统计知识。实际项目中需要根据评估指标如MAE, RMSE, MAPE和业务解释性需求来最终选择模型。4. 调参心得与常见陷阱模型跑起来只是第一步让模型跑得好才是关键。这里分享几个核心的调参和验证经验。4.1 如何确定季节性周期s这是最基础也最容易出错的一步。业务常识首先从业务角度判断。零售销售有周周期7天和年周期365天电力负荷有日周期24小时、周周期168小时和年周期8760小时交通流量有早/晚高峰日周期和周末效应周周期。数据可视化绘制序列图特别是较短时间窗口的图如几周的数据肉眼观察重复模式。ACF/PACF图自相关图在滞后s,2s,3s... 处会出现峰值。如果存在多重季节性你会看到多组峰值。例如小时数据中在滞后2448...和168336...处都有峰值。频谱分析使用快速傅里叶变换FFT计算功率谱密度峰值对应的频率的倒数就是潜在周期。4.2 SARIMA模型调参实战技巧使用auto_arima这是最省力的方法。pmdarima库可以自动搜索(p,d,q)(P,D,Q,s)的最优组合。import pmdarima as pm auto_model pm.auto_arima(train[load], seasonalTrue, m168, # 季节性周期 start_p0, start_q0, max_p3, max_q3, start_P0, start_Q0, max_P2, max_Q2, d1, D1, # 通常差分阶数设为1 traceTrue, # 打印搜索过程 error_actionignore, suppress_warningsTrue, stepwiseTrue) # 使用逐步搜索更快 print(auto_model.summary())关注残差诊断一个好的SARIMA模型其残差应该近似于白噪声。务必检查plot_diagnostics()图确保残差没有自相关ACF图大致服从正态分布直方图/Q-Q图。避免过拟合不要盲目追求高的p, q, P, Q值。更复杂的模型在训练集上表现好但在测试集上可能泛化能力差。用AIC/BIC准则和样本外测试来验证。4.3 Prophet模型调参核心要点趋势变化点ChangepointsProphet会自动检测趋势发生变化的点。changepoint_prior_scale参数控制趋势变化的灵活性值越大趋势越灵活但也可能对噪声过拟合。通常设置在0.01到0.5之间用交叉验证选择。季节性强度seasonality_prior_scale参数控制季节性成分的强度。如果认为季节性模式非常稳定和强烈可以适当调高此值如10.0如果季节性较弱或不稳定可以调低如0.01。节假日效应这是Prophet的杀手锏。务必创建一个DataFrame来明确指定节假日。holidays pd.DataFrame({ holiday: Spring Festival, ds: pd.to_datetime([2023-01-22, 2024-02-10]), # 春节日期 lower_window: -2, # 节前2天受影响 upper_window: 5, # 节后5天受影响 }) model Prophet(holidaysholidays)不确定性区间预测区间yhat_lower和yhat_upper的宽度由interval_width参数控制默认0.8即80%区间。生产环境中可能需要更宽的区间如0.95来覆盖更多不确定性。4.4 模型评估与验证不要只看一个指标永远不要只依赖训练集上的表现。一定要进行严格的样本外测试。简单划分按时间顺序划分训练集和测试集如最后20%的数据。时间序列交叉验证Time Series Cross-Validation更稳健的方法。例如使用sklearn的TimeSeriesSplit或 Prophet内置的cross_validation函数。这能更好地评估模型在不同时间段的泛化能力。多指标评估MAE (平均绝对误差)对异常值不敏感解释直观。RMSE (均方根误差)对大的误差惩罚更重。MAPE (平均绝对百分比误差)相对误差便于不同量级序列的比较但在实际值接近0时不稳定。可视化将预测值和实际值画在一起是发现系统性偏差如相位偏移、幅度不准的最佳方式。5. 生产环境部署与监控要点模型在笔记本里跑通只是万里长征第一步要让它持续产生价值还需要工程化。自动化重训练数据在持续更新模型需要定期如每周、每月用最新的数据重新训练以捕捉最新的模式和趋势。可以编写脚本结合定时任务如Linux的cron或Airflow来实现。模型版本化与回滚每次训练出的新模型都应该有版本号并与对应的代码、数据快照一起保存。如果新模型上线后效果暴跌要能快速回滚到旧版本。预测监控与预警建立监控看板跟踪关键指标预测偏差实时预测值与实际值的差异是否在预期范围内预测区间覆盖率实际值落在预测区间内的比例是否与设定的置信水平如80%相符如果远低于说明模型低估了不确定性。业务指标基于预测所做的决策如备货量、资源调度最终带来的业务效果如何处理实时数据对于需要实时预测的场景如下一小时的负荷模型需要能够接收最新的数据点并快速更新预测。Prophet和SARIMA的滚动预测模式需要仔细设计TBATS等模型可能需要进行增量更新或使用在线学习算法。最后我想强调的是没有“银弹”模型。SARIMA、Prophet、TBATS各有优劣。在实际项目中我常常会运行多个模型将它们的结果进行集成例如取简单平均或加权平均这往往能获得比单一模型更稳定、更鲁棒的预测效果。时间序列预测是一门结合了统计、算法和业务理解的技艺多动手实验多思考数据背后的故事你的模型才会越来越“聪明”。
返回列表