Prophet模型网格搜索寻找最优参数组合
Prophet模型的参数多到离谱,手动去调,会让你很绝望。但是不管是ARIMA还是Prophet,它们对于时间序列都有一个隐含的要求,那就是你要预测的时间序列,得有一定的周期性;时间序列的随机性越大、周期性越不明显,模型即便在训练集上拟合的不错,在测试集上表现也可以,但是实际去做预测的时候,模型还是会表现的很随机。这种情况出现的时候,可能就需要做一些先验性的判断,比如用具有相同或类似趋势的数据训练出的模型去对可能具有同样趋势的时间段的值做预测。当然,如果你的数据本身很棒,其实用ARIMA还是Prophet区别并不会太大,Prophet能加入一些经验性的参数,比如市场最高容量(cap)、市场最低容量(floor)、趋势突变点(changepoints)、节假日(holidays)等,这些如果是周期内性的突变点或者是只出现一次的特殊情况,那么你在Prophet定义了这些突变点,才有可能会提升模型的性能。
模型简单的介绍:
时间序列(Time Series Analysis)作为计量经济学的三大数据形态之一, 比较主流的观点认为,时间序列受四种成分影响:
· 趋势:宏观、长期、持续性的作用力
· 周期:比如商品价格在较短时间内,围绕某个均值上下波动;
· 季节:变化规律相对固定,并呈现某种周期特征。“季节”不一定按年计。每周、每天的不同时段的规律,也可称作季节性。
· 随机:随机的不确定性,也是人们常说的随机过程 (Stochastic Process)。
这四种成分对时间序列的影响,常归纳为累积和相乘两种。累积意味着四种成分相互叠加,它们之间相对独立,相互影响较小。而相乘意味着它们相互影响更为明显。/Prophet模型/
① 更灵活,相对ARIMA可以人为的加入一些经验性的指标,比如增长率、节假日、趋势等。可以将问题背景知识与统计分析融合起来进行分析。
② Prophet模型构成: y(t) = g(t) + s(t) + h(t) + e
· g(t) 是趋势函数(宏观、长期、持续性的作用力),用来分析时间序列中非周期性的变化。增长趋势是整个模型的核心组件,它表示认为整个时间序列是如何增长的,以及预期未来时间里是如何增长的。提供了两种模型:Non-linear growth(非线性增长:逻辑回归)和Linear growth(分段线性增长)
· s(t) 代表周期性的变化(在确定的时间间隔内,围绕某个均值上下波动),比如月、年、季度等。用傅里叶级数近似表达。它用来定义模型中是否考虑高频变化。对时间序列来说,如果分析师认为高频变化的成分只是噪声,没必要在模型中考虑,可以把N设为较低的值。如果不是,N可以被设置为较高的值并用于提升预测精度。
· h(t) 代表节假日一天或几天造成的影响。非周期性因素,但是对时间序列的趋势有较大的影响。节假日模型将不同节假日在不同时间点下的影响视作独立的模型。同时为每个模型设置了时间窗口,这主要是考虑到节假日的影响有窗口期(例如中秋节的前几天与后几天),模型将同一个窗口期中的影响设置为相同的值。
· e 是随机误差③ Prophet预测过程
· Modeling :建立时间序列模型。分析师根据预测问题的背景选择一个合适的模型
·Forecast Evaluation:模型评估。根据模型对历史数据进行仿真,在模型的参数不确定的情况下,我们可以进行多种尝试,并根据对应的仿真效果评估哪种模型更适合。
·Surface Problems:呈现问题。如果尝试了多种参数后,模型的整体表现依然不理想,这个时候可以将误差较大的潜在原因呈现给分析师。
·Visually Inspect Forecasts:以可视化的方式反馈整个预测结果。当问题反馈给分析师后,分析师考虑是否进一步调整和构建模型。
④适用场景
·有至少几个月(最好是一年)的每小时、每天或每周观察的历史数据;
·有多种人类规模级别的较强的季节性趋势:每周的一些天和每年的一些时间;
·有事先知道的以不定期的间隔发生的重要节假日(比如国庆节);
·缺失的历史数据或较大的异常数据的数量在合理范围内;
·有历史趋势的变化(比如因为产品发布);
·对于数据中蕴含的非线性增长的趋势都有一个自然极限或饱和状态。⑤参数使用
g(t):·growth: linear与logistic,分别代表线性与非线性的增长,默认值:linear。
·cap: 承载量(饱和值),预测值将在该点达到饱和。
·changepoints(用于增长模型g(t)): 趋势突变点。使用者可以自主填写已知时刻的标示着增长率发生改变的时间点,如果不填则系统自动识别。默认值:None。
·n_changepoints: 用户指定潜在的changepoint的个数,默认值:25(从数据集的前80%中选,范围可调)。
·changepoint_prior_scale(用于增长模型g(t)): 增长趋势模型的灵活度。调节changepoint选择的灵活度,值越大,选择的changepoint越多,从而使模型对历史数据的拟合程度变强,然而也增加了过拟合的风险。默认值:0.05。s(t):
seasonality_prior_scale: 调节季节性组件的强度。值越大,模型将适应更强的季节性波动,值越小,越抑制季节性波动,默认值:10.0。
h(t):
holidays_prior_scale: 调节节假日模型组件的强度。值越大,该节假日对模型的影响越大,值越小,节假日的影响越小,默认值:10.0。
holidays: 节假日的定义,设置节假日的json格式的配置文件或者通过DataFrame创建。做预测的时候可能会用到的参数:
freq: 数据中时间的统计单位(频率),默认为D,按天统计;H是小时,M是月末,MS月初
periods: 需要预测的未来时间节点的个数。其他参数:
mcmc_samples: mcmc采样,用于获得预测未来的不确定性。若大于0,将做mcmc样本的全贝叶斯推理,如果为0,将做最大后验估计,默认值:0。
interval_width: 衡量未来时间内趋势改变的程度。表示预测未来时使用的趋势间隔出现的频率和幅度与历史数据的相似度,值越大越相似,默认值:0.80。当mcmc_samples = 0时,该参数仅用于增长趋势模型的改变程度,当mcmc_samples > 0时,该参数也包括了季节性趋势改变的程度。
uncertainty_samples: 用于估计未来时间的增长趋势间隔的仿真绘制数,默认值:1000。/Prophet建模基本步骤/
①数据预处理,主要是处理异常点
②确定模型的参数,这是最核心的一步
③建模预测
④模型评估: MAE、MAPE、MSE都行
⑤达不到要求就返回②,开启下一轮/Prophet优势/
①对时间序列质量的要求有所降低,基本不需要特征工程
②将问题背景知识与统计分析融合起来进行分析
③更快的拟合速度
④支持成分分析,就是可以对g、s、h单独拿出来分析,便于定位问题/可借鉴的经验/
①如果预测结果的误差很大,考虑选取的模型是否准确,尝试调整增长率模型(growth)的参数,在必要的情况下也需要调整季节性(seasonality)参数。
②如果在尝试的大多数方法中,某些日期的预测依然存在很大的误差,这就说明历史数据中存在异常值。最好的办法就是找到这些异常值并剔除掉。使用者无需像其他方法那样对剔除的数据进行插值拟合,可以仅保留异常值对应的时间, 并将异常值修改为空值(NA),模型在预测时依然可以给出这个时间点对应的预测结果。
③如果对历史数据进行仿真预测时发现,从一个节点到下一个节点误差急剧的增加,这说明在两个节点期间数据的产生过程发生了较大的变化,此时两个节点之间应该增加一个”changepoint”,来对这期间的不同阶段分别建模。
完整的过程就不贴了,只贴一下网格搜索。
网格搜索找最优参数组合:
这只是方法论,对模型的理解、对数据的理解、对数据的处理才是核心
# 平均绝对百分比误差
# 和上面的MAE相比,在预测值和真实值的差值下面分母多了一项,除以真实值。
# 范围[0,+∞),MAPE 为0%表示完美模型,MAPE 大于 1则表示劣质模型。
from sklearn.metrics import mean_absolute_percentage_error as mape
from tqdm import tqdm_notebook # 迭代过程可视化
#***********************************************************#
# gen_params: 用于生成所有可能的模型参数组合
def gen_params(ls=[]):
# 突变点采样比例
changepoint_range = [i / 10 for i in range(3, 10)]
# 季节性模式 // 加法或乘法
seasonality_mode = ['additive', 'multiplicative']
# 季节性组件的强度。值越大,模型将适应更强的季节性波动,值越小,越抑制季节性波动
seasonality_prior_scale = [0.05, 0.1, 0.5, 1, 5, 10, 15]
# 调节节假日模型组件的强度。值越大,该节假日对模型的影响越大,值越小,节假日的影响越小
holidays_prior_scale = [0.05, 0.1, 0.5, 1, 5, 10, 15]
# 迭代生成参数组合
for sm in seasonality_mode:
for cp in changepoint_range:
for sp in seasonality_prior_scale:
for hp in holidays_prior_scale:
for nc in (50, 105, 5):
# 将参数组合添加到参数列表
ls.append(
{
"seasonality_mode": sm,
"changepoint_range": cp,
"seasonality_prior_scale": sp,
"holidays_prior_scale": hp,
"holidays": holidays,
"n_changepoints": nc,
}
)
return ls
#***********************************************************#
# grid_search: 建模、测试、得到mape
def grid_search(holidays, data, scale, params):
"""
holidays: 节假日
data: 数据集
scale:训练集和测试集的分割点 这里是int(data.shape[0]*0.8)
params: 参数组合
返回的是mape
"""
# 实例化模型
model = Prophet(**params)
# 拟合模型
model.fit(data[['ds', 'y', 'cap', 'floor']].iloc[:scale])
# 测试
predict = model.predict(data[['ds','y', 'cap', 'floor']].iloc[scale:])
return mape(data['y'].tolist()[scale:], predict['yhat'])
#***********************************************************#
# main: 主函数,用来得到使mape最小的参数组合
def main():
results = {}
params_list = gen_params()
# 网格搜索
for para in tqdm_notebook(params_list):
# 计算mape
mape_ = grid_search(holidays, origin_data, scale, para)
results.setdefault(str(mape_), para)
# 返回使mape最小的参数组合
return results[min(results.keys())]
#***********************************************************#
if __name__ == "__main__":
best_params = main()
由于每个时间序列各不相同,所以模型评估不一定用mape;最后找到的最优参数组合也不一定能在生产环境有好的表现,还是得根据实际情况而做适当的调整。