美文网首页
初识Prophet模型(二)-- 应用篇

初识Prophet模型(二)-- 应用篇

作者: 小小孩儿的碎碎念 | 来源:发表于2020-10-03 00:06 被阅读0次

    相关学习: 初识Prophet模型(一)--理论篇

    7、Prophet 模型应用

    7.0 背景描述

    • 该案例使用的是wiki网站日访问量(数值经过log处理)的csv数据文件
    • 描述的是美国著名橄榄球四分卫的维基页面浏览量,他是美国球员,一年里的周期规律会起很大作用,而一周里的周期规律也很明显。

    7.1 导入数据

    import pandas as pd
    import numpy as np
    from fbprophet import Prophet
    import matplotlib.pyplot as plt
    %matplotlib inline
    
    df = pd.read_csv('data.csv')
    df.head()
    
    df.dtypes #检查下df的数据类型
    df['ds'] = df['ds'].apply(pd.to_datetime)# ds列必须是pandas的datetime数据类型,使用pandas自带的pd.to_datetime将日期转为datetime类型
    
    plt.rcParams['figure.figsize']=(20,10)
    plt.style.use('ggplot')
    df.set_index('ds').y.plot()
    

    7.2 拟合模型

    model = Prophet(daily_seasonality=True)
    model.fit(df)
    
    <fbprophet.forecaster.Prophet at 0x10715a0b8>
    

    7.3 预测(使用默认参数)

    • 生成一个未来的日期的dataframe,然后用训练好的模型prophet来predict。
    future = model.make_future_dataframe(periods=730)
    future.tail()
    
    • 有了未来的日期,就可以使用学习到的趋势来预测未来日期的走势。

    • 预测的结果包括如下变量

    'ds', 'trend', 'yhat_lower', 'yhat_upper', 'trend_lower', 'trend_upper',
           'additive_terms', 'additive_terms_lower', 'additive_terms_upper',
           'weekly', 'weekly_lower', 'weekly_upper', 'yearly', 'yearly_lower',
           'yearly_upper', 'multiplicative_terms', 'multiplicative_terms_lower',
           'multiplicative_terms_upper', 'yhat'
    
    • 我们只用 'ds', 'yhat', 'yhat_lower', 'yhat_upper'
    forecast=model.predict(future)
    
    forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].head()
    
    print(fig1)
    

    成分分析**

    趋势是由不同的成分组成,比如总趋势、年、季节、月、周等等,我们要将这些成分从趋势中抽取出来看看不同成分的趋势情况

    • 预测的结果包括如下变量
    'ds', 'trend', 'yhat_lower', 'yhat_upper', 'trend_lower', 'trend_upper',
           'additive_terms', 'additive_terms_lower', 'additive_terms_upper',
           'weekly', 'weekly_lower', 'weekly_upper', 'yearly', 'yearly_lower',
           'yearly_upper', 'multiplicative_terms', 'multiplicative_terms_lower',
           'multiplicative_terms_upper', 'yhat'
    
    • 下面图1是根据trend画出来的,图2是根据weekly画出来的,图3是根据yearly画出来的,图4是根据daily画出来的

    • 因为是加法模型,所以:

      • forecast['additive_terms'] = forecast['daily']+forecast['weekly'] + forecast['yearly'];
      • forecast['yhat'] = forecast['trend'] + forecast['additive_terms']
      • forecast['yhat'] = forecast['trend'] +forecast['daily']+ forecast['weekly'] + forecast['yearly']。
    • 如果有节假日因素,那么就会有:

      • forecast['yhat'] = forecast['trend'] +forecast['daily']+forecast['weekly'] + forecast['yearly'] + forecast['holidays']。
    • 对于那些是节假日的天数, forecast['holidays']才会有值

    • 不是节假日的天数,forecast['holidays']为0

    • 因为是加法模型,'multiplicative_terms', 'multiplicative_terms_lower', 'multiplicative_terms_upper'这3列为空。

    因此,在下面的拆解图中,weekly中的Monday为0.3的意思就是,在trend的基础上,加0.3;Saturday为-0.3的意思就是,在trend的基础上,减0.3。因此,这条线的高低也在一定程度上反应了“销量的趋势“。

    fig2 = model.plot_components(forecast)
    print(fig2)
    
    • 第一幅趋势图里,可以看到按页面浏览量的总趋势。是因为作者最近退休,所以浏览量逐渐下降。
    • 第二幅按周的周期规律图里能看出,在比赛当天和赛后(周日和周一)访问量明显较高。
    • 美国NFL橄榄球比赛主要集中在九月到次年1月初,和二月初的超级碗。这也反映在第三幅图中,按年的周期规律图。
    forecast_df = forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]
    df = pd.merge(df, forecast_df, on='ds', how='right')
    df.set_index('ds').plot(figsize=(16,8), color=['royalblue', "green", "pink", "yellow"], grid=True);
    
    x1 = forecast['ds']
    y1 = forecast['yhat']
    y2 = forecast['yhat_lower']
    y3 = forecast['yhat_upper']
    plt.plot(x1,y1)
    plt.plot(x1,y2)
    plt.plot(x1,y3)
    plt.show()
    
    print(forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail())
    

    7.4 趋势突变点

    自动检测变化点

    • 步骤:
      • 1、通过大量的速率改变的点检测变化点
      • 2、对这些点做稀疏先验
    • 默认情况下会检测出25个变化点,这些点均匀的分布在前80%的时间序列中
    fig = model.plot(forecast)
    for cp in model.changepoints:
        plt.axvline(cp, c='pink', ls='--', lw=2)
    
    • 因为稀疏先验,大部分的变化点并没有用到,看看每个变化点的速率变化图:
    deltas = model.params['delta'].mean(0)
    fig = plt.figure(facecolor='w', figsize=(10, 6))
    ax = fig.add_subplot(111)
    ax.bar(range(len(deltas)), deltas, facecolor='#0072B2', edgecolor='#0072B2')
    ax.grid(True, which='major', c='gray', ls='-', lw=1, alpha=0.2)
    ax.set_ylabel('Rate change')
    ax.set_xlabel('Potential changepoint')
    fig.tight_layout()
    
    • 变化点的数量可以通过参数n_changepoints指定,但最好还是通过调整正则化来修改
    • 看看比较明显的变化点
    from fbprophet.plot import add_changepoints_to_plot
    fig = model.plot(forecast)
    a = add_changepoints_to_plot(fig.gca(), model, forecast) #虚线处为给定时间序列中的变点
    

    调整趋势灵活性

    • 当趋势出现过拟合或者欠拟合的情况下,可以通过参数changepoint_prior_scale调整稀疏先验的程度,默认为0.05
    • 该参数值越大,则趋势越灵活
    增大灵活性
    m = Prophet(changepoint_prior_scale=0.5)
    forecast = m.fit(df).predict(future)
    fig = m.plot(forecast)
    
    减少灵活性
    forecast = m.fit(df).predict(future)
    fig = m.plot(forecast)
    

    指定变化点的位置

    • 通过参数changepoints手动指定位置,只有指定的这些点可以有速率变化
    m = Prophet(changepoints=['2014-01-01'])
    forecast = m.fit(df).predict(future)
    fig = m.plot(forecast)
    

    7.5季节性、假期效应和回归因子

    假期和特殊事件建模

    • 要给假期或者其他重复事件建模,就需要创建一个包含holiday和ds列的DataFrame
    • 需要包含过去和将来所有的特殊日子,如果这些特殊日子没有出现在将来(要预测的日期),那预测就不会用到
    • 可以在这个数据框基础上再新建两列 lower_window 和 upper_window ,从而将节假日的时间扩展成一个区间 [ lower_window , upper_window ] ,例如:
      • 如果想将平安夜也加入到 “圣诞节” 里,就设置 lower_window = -1 , upper_window = 0 ;
      • 如果想将黑色星期五加入到 “感恩节” 里,就设置 lower_window = 0 , upper_window = 1
    playoffs = pd.DataFrame({
      'holiday': 'playoff',
      'ds': pd.to_datetime(['2008-01-13', '2009-01-03', '2010-01-16',
                            '2010-01-24', '2010-02-07', '2011-01-08',
                            '2013-01-12', '2014-01-12', '2014-01-19',
                            '2014-02-02', '2015-01-11', '2016-01-17',
                            '2016-01-24', '2016-02-07']),
      'lower_window': 0,
      'upper_window': 1,
    })
    superbowls = pd.DataFrame({
      'holiday': 'superbowl',
      'ds': pd.to_datetime(['2010-02-07', '2014-02-02', '2016-02-07']),
      'lower_window': 0,
      'upper_window': 1,
    })
    holidays = pd.concat((playoffs, superbowls))
    

    上面superbowl的日期也包含在playoff的日期中,也就是superbowl日期的影响会有个叠加效应

    m = Prophet(holidays=holidays)
    forecast = m.fit(df).predict(future)
    
    • 可以通过forecast看看假期效应
    forecast[(forecast['playoff'] + forecast['superbowl']).abs() > 0][['ds', 'playoff', 'superbowl']][-15:]
    
    • 看看假期效应在图上的显示,playoff日期有高峰,superbowl日期有更明显的高峰
    fig = m.plot_components(forecast)
    
    • 对假期单独画图
    from fbprophet.plot import plot_forecast_component
    plot_forecast_component(m, forecast, 'superbowl')
    

    内置假期

    • 可以通过add_country_holidays使用内置假期
    • 通过模型的train_holiday_names方法查看哪些假期
    m = Prophet(holidays=holidays)
    m.add_country_holidays(country_name='CN')
    m.fit(df)
    
    m.train_holiday_names
    
    INFO:fbprophet:Disabling daily seasonality. Run prophet with daily_seasonality=True to override this.
    
    0                 playoff
    1               superbowl
    2          New Year's Day
    3        Chinese New Year
    4       Tomb-Sweeping Day
    5               Labor Day
    6    Dragon Boat Festival
    7     Mid-Autumn Festival
    8            National Day
    dtype: object
    
    m = Prophet(holidays=holidays)
    m.add_country_holidays(country_name='US')
    m.fit(df)
    
    forecast = m.predict(future)
    fig = m.plot_components(forecast)
    

    季节性的傅里叶级数

    • 年度季节性的傅里叶级数默认是10
    from fbprophet.plot import plot_yearly
    m = Prophet().fit(df)
    a = plot_yearly(m)
    
    • 默认值大多数是没问题的,但是季节性可能有更高频率的变化,并且通常没有这么平滑,这时可以增加这个值
    • 增加这个值也可能导致过拟合,这里增加到20
    from fbprophet.plot import plot_yearly
    m = Prophet(yearly_seasonality=20).fit(df)
    a = plot_yearly(m)
    

    自定义季节性

    • 时间序列超过两个周期时,Prophet默认训练星期和年的季节性

    • 在sub-daily的时间序列时,会训练每天的季节性

    • 可以使用函数add_seasonality添加小时/月/季度等其他季节性

    • 函数add_seasonality的参数:

      • name 哪种周期
      • period 季节性的周期
      • fourier_order 季节性的傅里叶级数
      • prior_scale 可选参数
      • 默认情况下,周的季节性傅里叶级数为3,年的季节性傅里叶级数为10
    • 这里将每周的季节性替换为每月的季节性(period=30.5)

    m = Prophet(weekly_seasonality=False)
    m.add_seasonality(name='monthly', period=30.5, fourier_order=5)
    forecast = m.fit(df).predict(future)
    fig = m.plot_components(forecast)
    

    依赖于其他因素的季节性

    • 有时候季节性依赖于其他一些因素,比如每周的季节性在夏天和其他季节表现不一致,每天的季节性在周末和周内表现不一致,这种季节性可以使用带条件的季节性训练模型

    • 在一年中默认每周的季节性表现是一致的,但是可能希望每周的季节性在淡季和旺季(每周末有比赛)表现不一致

    • 可以使用带条件的季节性为淡季和旺季单独构建每周的季节性

    这里先增加一列布尔类型的数据,来表示日期在淡季还是旺季:

    def is_nfl_season(ds):
        date = pd.to_datetime(ds)
        return (date.month > 8 or date.month < 2)
    
    df['on_season'] = df['ds'].apply(is_nfl_season)
    df['off_season'] = ~df['ds'].apply(is_nfl_season)
    
    • 接着禁用内置的每周季节性,使用淡季的周季节性和旺季的周季节性代替

    • 因此,只有condition_name列为True的时候季节性才有日期

    • 在预测的DataFrame上,也要做同样的操作

    m = Prophet(weekly_seasonality=False)
    m.add_seasonality(name='weekly_on_season', period=7, fourier_order=3, condition_name='on_season')
    m.add_seasonality(name='weekly_off_season', period=7, fourier_order=3, condition_name='off_season')
    
    future['on_season'] = future['ds'].apply(is_nfl_season)
    future['off_season'] = ~future['ds'].apply(is_nfl_season)
    forecast = m.fit(df).predict(future)
    fig = m.plot_components(forecast)
    

    从图中可以看到,在旺季的时候每周末都会打球,周日和周一都有大幅度增长,但在淡季则完全没有。

    假期和季节性的prior scale

    • 如果发现假期过拟合,可以设置参数holidays_prior_scale调整假期的prior scale使之平滑
    • 这个参数默认是10,减少可以限制假期效果
    m = Prophet(holidays=holidays, holidays_prior_scale=0.05).fit(df)
    forecast = m.predict(future)
    forecast[(forecast['playoff'] + forecast['superbowl']).abs() > 0][['ds', 'playoff', 'superbowl']][-10:]
    

    可以看到,比起之前假期效应被减弱了,特别是在观看最少的superbowls上

    可以用下面的方式设置每周季节性的prior_scale

    额外的回归特征

    • 可以使用函数add_regressor将其他回归特征添加到模型的线性部分
    • 训练和预测的数据集上都需要包含这些回归特征的值

    下面,为NFL赛季的每周日添加这样一个回归特征,再画图看看这个特征的效果

    def nfl_sunday(ds):
        date = pd.to_datetime(ds)
        if date.weekday() == 6 and (date.month > 8 or date.month < 2):
            return 1
        else:
            return 0
    df['nfl_sunday'] = df['ds'].apply(nfl_sunday)
    
    m = Prophet()
    m.add_regressor('nfl_sunday')
    m.fit(df)
    
    future['nfl_sunday'] = future['ds'].apply(nfl_sunday)
    
    forecast = m.predict(future)
    fig = m.plot_components(forecast)
    
    • 也可以使用前面说过的holidays的接口,通过创建一个过去和未来的这些周日的list来处理NFL赛季周日的这种情况
    • 函数add_regressor为定义额外的线性回归提供了一个更加通用的接口

    7.6 模型诊断(内置方法)

    • Prophet有交叉验证功能,具体做法是通过在历史数据中选择一些截断点,对于这些截断点,只使用这些点之前的数据来拟合模型,然后比较真实值和预测值

    下面模型使用前五年的数据训练,预测后一年的数据

    m = Prophet()
    m.fit(df)
    future = m.make_future_dataframe(periods=366)
    
    from fbprophet.diagnostics import cross_validation
    
    df_cv = cross_validation(m, '365 days', initial='1825 days', period='365 days')
    cutoff = df_cv['cutoff'].unique()[0]
    df_cv = df_cv[df_cv['cutoff'].values == cutoff]
    
    fig = plt.figure(facecolor='w', figsize=(10, 6))
    ax = fig.add_subplot(111)
    ax.plot(m.history['ds'].values, m.history['y'], 'k.')
    ax.plot(df_cv['ds'].values, df_cv['yhat'], ls='-', c='#0072B2')
    ax.fill_between(df_cv['ds'].values, df_cv['yhat_lower'],
                    df_cv['yhat_upper'], color='#0072B2',
                    alpha=0.2)
    ax.axvline(x=pd.to_datetime(cutoff), c='gray', lw=4, alpha=0.5)
    ax.set_ylabel('y')
    ax.set_xlabel('ds')
    ax.text(x=pd.to_datetime('2010-01-01'),y=12, s='Initial', color='black',
           fontsize=16, fontweight='bold', alpha=0.8)
    ax.text(x=pd.to_datetime('2012-08-01'),y=12, s='Cutoff', color='black',
           fontsize=16, fontweight='bold', alpha=0.8)
    ax.axvline(x=pd.to_datetime(cutoff) + pd.Timedelta('365 days'), c='gray', lw=4,
               alpha=0.5, ls='--')
    ax.text(x=pd.to_datetime('2013-01-01'),y=6, s='Horizon', color='black',
           fontsize=16, fontweight='bold', alpha=0.8);
    
    • 可以使用函数cross_validation给这些历史截断点自动完成交叉验证,参数如下:
      • horizon:代表每次从cutoff往后预测多少天
      • initial :一开始的时间是多少
      • period :代表每隔多长时间设置一个cutoff
      • 默认情况下,period是horizon的三倍,并且每隔半个horizon设置一个截断点
    • 交叉验证的输出是一个DataFrame,包含真实的y和预测的yhat,可以用来评判效果

    下面的交叉验证,horizon=365天,initial=730天,period=180天,在八年的时间序列中,等于有11((365*8-730-365)/180)个总的预测

    from fbprophet.diagnostics import cross_validation
    df_cv = cross_validation(m, initial='730 days', period='180 days', horizon = '365 days')
    df_cv.head()
    
    • 函数performance_metrics可以用来评判模型效果,计算得到:
      • 统计信息包括均方误差(mean squared error, MSE)
      • 均方根误差(root mean squared error, RMSE)
      • 平均绝对误差(mean absolute error, MAE)
      • 平均绝对误差(mean absolute percent error, MAPE)
      • yhat_lower和yhat_upper估计的覆盖率
    from fbprophet.diagnostics import performance_metrics
    df_p = performance_metrics(df_cv)
    df_p.head()
    
    • 可以使用plot_cross_validation_metric来可视化这些指标,查看mape指标下的可视化,可以看到对未来一个月的预测有5%的误差,一年后的预测误差增加到了11%
    from fbprophet.plot import plot_cross_validation_metric
    fig = plot_cross_validation_metric(df_cv, metric='mape')
    

    7.7 模型评估

    • 通过历史数据对已知的最后一年数据进行预测并评估模型
    prediction_size = 365
    train_df = df[:-prediction_size]
    train_df.tail()
    
    model2 = Prophet(daily_seasonality=True)
    model2.fit(train_df)
    future2 = model2.make_future_dataframe(periods=365)
    forecast2 = model2.predict(future2)
    
    model2 .plot(forecast2);
    
    • 定义一个辅助函数,用于从原数据集df中获取实际值y,然后和forecast对象中的预测值比较
    def make_comparison_dataframe(historical, forecast):
        return forecast.set_index('ds')[['yhat', 'yhat_lower', 'yhat_upper']].join(historical.set_index('ds'))
    
    cmp_df = make_comparison_dataframe(df, forecast2)
    cmp_df.tail()
    
    • 预测评价指标:

      • MAE:平均绝对误差,
        df_all.plot()
        plt.rcParams['figure.figsize']=(30,20)
        plt.style.use('ggplot')
        plt.legend(['true', 'yhat'])
        plt.show()
        

    相关文章

      网友评论

          本文标题:初识Prophet模型(二)-- 应用篇

          本文链接:https://www.haomeiwen.com/subject/lgviuktx.html