在上一篇文章中,我们简略介绍了与时间序列相关的应用,这次我们聚焦于时间序列的预测,讲讲与之相关的那些事。

1. 效果评估

设 y 是时间序列的真实值, yhat 是模型的预测值。在分类模型中由于y是离散的,有很多维度可以去刻画预测的效果。但现在的y是连续的,工具一下子就少了很多。时间序列里比较常用的是 MAPE(mean absolute percentage error) 和 RMSE (root mean square error)

\[MAPE = \frac{100\%}{n} \sum_{i=1}^{n} \big|\frac{y-yhat}{y}\big|

\]

\[RMSE = \sqrt{\frac{\sum(y-yhat)^2}{n}}

\]

个人比较喜欢MAPE,适合汇报,它揭示了预测值在真实值基础上的波动范围。比如,MAPE=10%,其代表着预测值的范围大致是 0.9y 至 1.1y

这两个指标或多或少都会有一些缺点,因此也有了各种变体,比如SMAPE, NRMSE等,感兴趣的可以自己去Wiki看看。

\[SMAPE = \frac{200\%}{n} \sum_{i=1}^{n} \frac{|y-yhat|}{|y|+|yhat|}

\]

2. 交叉验证

因为时间序列的样本之间是无法交换的,所以没办法随机的像 KFold 一样把数据集切分成若干份训练集和测试集。但没关系,方法还是有的。

一个比较好的思路是按照时间顺序设置 cutoff time T_c :

这里有三个参数:

horizon: 模型预测的范围,如从cutoff点开始数未来30天

period: 每两个 cutoff 点之间的间隔,如60天

initial: 用于训练的日期范围,如730天

假设我们有2015-01-01 至 2017-12-31 共三年的数据。且三个参数的值如下: horizon=30,period=120,initial=730,则我们有

\[K=\text{floor} \big(\frac{365*3 - initial -period}{period}\big) = 5

\]

5组训练集和测试集。

有了交叉验证,我们就能获得评估模型效果和进行超参数优化了.

3. 时间序列预测模型

时间序列的预测有很多方法,本文不准备罗列的很细致,只展示三种模型:ARIMA,PROPHET,ETS,且都只用默认参数。在之后的一篇文章中,我会详细讲讲prophet的应用。

时间序列的预测模型,不好说一个模型绝对的好,跟数据集的类型有很大关系。根据更新的频率,可以分为Tick数据(小于1s,金融中的高频交易)、分钟数据、日数据、月数据等;根据波动情况,可以分为平稳序列和对日期/事件敏感的季节性序列等。一般数据的颗粒度越小,预测效果越差,因为随机性太大了,想通过模型来预测

我找了一个对季节和节假日敏感的日数据来测试这几个模型,时间范围是5.3年。可以看到数据有一个整体的趋势,也有季节性趋势,还有那些尖峰对应的节假日/事件影响。

3.1 ARIMA 模型

在讲ARIMA模型之前得先熟悉ARMA模型(autoregressive moving average model)

ARMA 模型是经典的预测模型,有着成熟的理论基础,但条件也比较严格,就是要求时间序列是平稳的。这里讲的平稳性条件(一般指弱平稳)是指时间序列的均值与时间无关和方差仅与时间差有关。

在ARMA模型中,时序可以由如下方程表示:

\[Y_t = c+\varepsilon_{t}+\sum_{i=1}^{p}\alpha_iY_{t-i} + \sum_{i=1}^{q}\theta_{i}\varepsilon_{t-i}

\]

其中 \epsilon 是高斯白噪声序列。

可以从线性系统的角度去看ARMA 模型,已证明其是一个离散线性时不变系统,系统的输入是高斯白噪声序列(标准的平稳序列),输出是我们需要的时序y。此时上述式子可以写为

\[(1-\sum_{i=1}^{p} \alpha_i L^i)Y_t =(1+\sum_{i=1}^{q} \theta_i L^i )\varepsilon_t

\]

其中 L 是单位平移算子(lag operator).

有定理指出,对于一个离散线性平移不变系统而言,当输入是平稳序列时,可以证明输出也是平稳的,且输入序列和输出序列是平稳相关的。(这里面的东西还比较复杂,本科学的,现在已经忘了差不多了,感兴趣的同学可以自己去看看线性系统的理论)

商业中的数据大都不满足平稳性条件的,为了解决这个问题,所以就衍生了ARIMA 模型,中间的字母I代表的是差分。通过一些差分/季节性差分操作,我们能消除掉大部分趋势,使得原有不平稳的序列变得平稳(当然也不绝对,一般需要在差分后作平稳性检验)。

这里我设置交叉验证的参数如下:horizon,initial,period=30,1500,30 , 并生成12组训练集和测试集用来评估模型效果。

import pandas as pd

import numpy as np

from pyecharts import Bar

from pyecharts import Line

import matplotlib.pyplot as plt

import seaborn as sns

sns.set()

%matplotlib inline

from pyramid.arima import auto_arima

from fbprophet import Prophet

import ts_model_selection ##自己写的轮子,还没整理好,之后会放出来

df=pd.read_excel('.\\data\\tehr.xlsx')

df['ds']=pd.to_datetime(df['ds'])

df['logy']=np.log(df['y'])

horizon,initial,period=30,1500,30

df_train_list,df_test_list=ts_model_selection.train_test_split(\

df,horizon='{} days'.format(horizon),initial='{} days'.format(initial),period='{} days'.format(period))

# 记录各个模型的分数

result={} #method,mape,cv

arima 有很多需要调参的地方,如p(AR),d(差分),q(MA)等。在 statsmodels 包中就有很多 ARIMA 相关的函数,但遗憾都需要手动调参。无奈找了一个仿照R中auto.arima写的包,还不错,名字是 pyramid-arima。接下来我们就用它来预测。

forecasts=pd.DataFrame(columns=['y','yhat','k'])

for k in range(len(df_train_list)):

#print(k)

df_train,df_test=df_train_list[k],df_test_list[k]

stepwise_fit = auto_arima(df_train['y'],seasonal=True,trace=False,error_action='ignore',suppress_warnings=True,stepwise=True)

yhat = stepwise_fit.predict(n_periods=horizon)

y=np.array(df_test['y'])

forecasts=pd.concat([forecasts,pd.DataFrame({'y':y,'yhat':yhat,'k':k})],axis=0)

result['auto.arima']={}

result['auto.arima']['mape']=np.mean(np.abs((forecasts['y']-forecasts['yhat'])/forecasts['y']))## 0.285

result['auto.arima']['rmse']=np.sqrt(np.sum((forecasts['y']-forecasts['yhat'])**2)/len(forecasts['y'])) ## 166943

print(result['auto.arima'])

forecasts_arima=forecasts.copy()#备份用

看了下,模型的MAPE是 0.285 ,RMSE 高达166943. 可能是由于数据过长,再加上没有调参,所以导致严重过拟合了。我也不打算深究了,感兴趣的可以细致的调一下。

3.2 Prophet 模型

facebook 在设计 prophet 之初,综合考虑了与时序预测相关的主要因素,具体包含:

季节性趋势,具体包含 yearly、weekly等

节假日和特殊事件的影响

changepoint

outlier

所以在商业上的应用场景会比较广,据说对于日数据的效果还不错。从大框架上讲,其是一个加法模型(经过log处理也可以变成一个乘法模型),一个时序会被分解为三部分之和

\[y(t) = g(t) +s(t) +h(t)+\varepsilon_t

\]

其中g(t)是趋势项, s(t) 是周期项,如 weekly 和 yearly 等,h(t)是节假日趋势

对于趋势项 g(t) 部分,可以由带changepoint的线性模型或者logistic growth (高中学的物种增长S型曲线?)模型来拟合:

\[\text{logistic:}\quad g(t)=\frac{C(t)}{1+exp(-k(t-m))}

\]

\[\text{linear:}\quad g(t)=kt+m

\]

其中 C 是carrying capacity,指明环境能容许的最大值,比如手机销售量最大也不会超过人口太多。k 是增长率。m是offset parameter。当加了changepoint 影响后,方程会有一些变化,具体可参见论文。

对于周期项 s(t) 部分,可以由Fourier series 来逼近

\[s(t)=\sum_{n=1}^{N}\big( a_{n}\cos(\frac{2\pi nt}{P}+b_{n}\sin(\frac{2\pi nt}{P})) \big)

\]

这里参数P的设置很有意思,facebook没有粗暴的直接用n,而是分别对yearly data,weekly data等作拟合。例如当P=7时, 序列 s(t) 的周期有 {7/n}

对于 节假日/事件 项 h(t) 部分,可以由示性函数来逼近

\[h(t)=Z(t)\cdot\boldsymbol{\kappa}=[\boldsymbol{1}(t\in D_1),\cdots,\boldsymbol{1}(t \in D_{L})]\cdot \boldsymbol{\kappa}

\]

其中 D_i 是指节假日i的日期集合,Z(t) 是一个矩阵,\kappa 是一个正态分布,拟合每一个节假日的影响程度

prophet 有很多参数可调,这里我们先都用默认的。

forecasts=pd.DataFrame(columns=['y','yhat','k'])

horizon,initial,period=30,1500,30

df_train_list,df_test_list=ts_model_selection.train_test_split(\

df,horizon='{} days'.format(horizon),initial='{} days'.format(initial),period='{} days'.format(period))

for k in range(len(df_train_list)):

df_train,df_test=df_train_list[k],df_test_list[k]

m=Prophet()

m.fit(df_train[['ds','logy']].rename(columns={'logy':'y'}))

yhat=np.array(m.predict(df_test[['ds']])['yhat'])

y=np.array(df_test['logy'])

y,yhat=np.exp(y),np.exp(yhat)

forecasts=pd.concat([forecasts,pd.DataFrame({'y':y,'yhat':yhat,'k':k})],axis=0)

result['prophet']={}

result['prophet']['mape']=np.mean(np.abs((forecasts['y']-forecasts['yhat'])/forecasts['y'])) #平均MAPE ,0.126

result['prophet']['rmse']=np.sqrt(np.sum((forecasts['y']-forecasts['yhat'])**2)/len(forecasts['y'])) ##137291

print(result['prophet'])

forecasts_prophet=forecasts.copy()#备份用

这里可以看到我把Y做了log化处理,这是因为log后,相当于把加法模型变成了乘法模型,对于我用的这份数据,乘法模型会更好一些。

可以看到模型的效果是 MAPE =0.13, RMSE=137293, 相比ARIMA要好很多了。

3.3 其他模型

除下上述两个,还有一些传统的可以用来预测时序的模型。本文就不再一一细讲,感兴趣的可以去看看R中的forecast包及其文档,介绍的很详细。

找了很久都没找到好的python包,所以这里提供一个在python中调用R中的ETS模型的code。

# 不保证这段代码能在任何环境中运行,需要大家自己去调

import os

os.environ['R_HOME'] = 'C:\Program Files\R\R-3.5.0'

os.environ['R_USER'] = 'C:\Anaconda3\Lib\site-packages\rpy2'

import rpy2.robjects as robjects

from rpy2.robjects.packages import importr

ts=robjects.r('ts')

# import R's "base" package

base = importr('base')

# import R's "utils" package

utils = importr('utils')

forecast=importr('forecast',lib_loc = "C:/Users/gason/Documents/R/win-library/3.5")

from rpy2.robjects import pandas2ri

pandas2ri.activate()

df_train=df_train_list[1].loc[:,['y']]

rdata=ts(df_train)

horizon,initial,period=30,1500,30

df_train_list,df_test_list=ts_model_selection.train_test_split(\

df,horizon='{} days'.format(horizon),initial='{} days'.format(initial),period='{} days'.format(period))

forecasts=pd.DataFrame(columns=['y','yhat','k'])

for k in range(len(df_train_list)):

#print(k)

df_train,df_test=df_train_list[k],df_test_list[k]

rdata=ts(df_train.loc[:,'y'])

yhat=forecast.forecast(forecast.ets(rdata),h=horizon)

yhat = np.asarray(yhat[1])

y=np.array(df_test['y'])

forecasts=pd.concat([forecasts,pd.DataFrame({'y':y,'yhat':yhat,'k':k})],axis=0)

result['ets']={}

result['ets']['mape']=np.mean(np.abs((forecasts['y']-forecasts['yhat'])/forecasts['y']))

result['ets']['rmse']=np.sqrt(np.sum((forecasts['y']-forecasts['yhat'])**2)/len(forecasts['y']))

print(result['ets'])

4. prphet 的调参

prophet 可调的参数有很多,个人比较比较调的有:

holidays: 需要注意的是,不是所有的节假日或时间都需要标记,以及windows的设置很考究;

growth: 可选 linear 模型或者 logistic模型。个人意见,数据长度不长的话,直接用linear就好

seasonality_prior_scale: 季节性趋势的强弱,默认为10

holidays_prior_scale: 节假日趋势的强弱,默认为10

changepoint_prior_scale:节changepoit的强弱,默认为0.05

对于分类模型,我们可以直接用sklearn的grid_search来调参,但对于时序,由于交叉验证的问题,我们没办法直接用。这里提供了一个函数供参考。

from fbprophet import Prophet

from fbprophet.diagnostics import cross_validation

from sklearn.model_selection import ParameterGrid

from sklearn.model_selection import ParameterSampler

def ts_evaluation(df,param,horizon=30,period=120,initial=1095,exp=True):

'''

利用交叉验证评估效果

'''

#param={'holidays':holidays,'growth':'linear','seasonality_prior_scale':50,'holidays_prior_scale':20}

m=Prophet(**param)

m.fit(df)

forecasts=m.predict(df[['ds']])

forecasts['y']=df['y']

df_cv = cross_validation(m, horizon='{} days'.format(horizon), period='{} days'.format(period), initial='{} days'.format(initial))

if exp:

df_cv['yhat']=np.exp(df_cv['yhat'])

df_cv['y']=np.exp(df_cv['y'])

mape=np.mean(np.abs((df_cv['y'] - df_cv['yhat']) / df_cv['y']))

rmse=np.sqrt(np.mean((df_cv['y'] - df_cv['yhat'])**2))

scores={'mape':mape,'rmse':rmse}

return scores

def ts_grid_search(df,holidays,param_grid=None,cv_param=None,RandomizedSearch=True,random_state=None):

'''网格搜索

时间序列需要特殊的交叉验证

df:

holidays: 需要实现调好

'''

df=df.copy()

if param_grid is None:

param_grid={'growth':['linear']

,'seasonality_prior_scale':np.round(np.logspace(0,2.2,10))

,'holidays_prior_scale':np.round(np.logspace(0,2.2,10))

,'changepoint_prior_scale':[0.005,0.01,0.02,0.03,0.05,0.008,0.10,0.13,0.16,0.2]

}

if RandomizedSearch:

param_list=list(ParameterSampler(param_grid,n_iter=10,random_state=random_state))

else:

param_list=list(ParameterGrid(param_grid))

if cv_param is None:

cv_param={'horizon':30,'period':120,'initial':1095}

scores=[]

for i,param in enumerate(param_list):

print('{}/{}:'.format(i,len(param_list)),param)

param.update({'holidays':holidays})

scores_tmp=ts_evaluation(df,param,exp=True,**cv_param)

param.pop('holidays')

tmp=param.copy()

tmp.update({'mape':scores_tmp['mape'],'rmse':scores_tmp['rmse']})

scores.append(tmp)

print('mape : {:.5f}%'.format(100*scores_tmp['mape']))

scores=pd.DataFrame(scores)

best_param_=scores.loc[scores['mape'].argmin(),:].to_dict()

best_scores_=best_param_['mape']

best_param_.pop('mape')

best_param_.pop('rmse')

return best_param_,best_scores_,scores

当然不是说上来直接暴力调参就好,我看了下,对于这份数据而言,changepoint_prior_scale 的影响最大,在一定范围内,越大效果越好。

下面附上我最后的效果。

python rpy2时间序列_时间序列(二):时序预测那些事儿相关推荐

  1. 异常检测时间序列_时间序列的无监督异常检测

    异常检测时间序列 To understand the normal behaviour of any flow on time axis and detect anomaly situations i ...

  2. python白噪声检验_时间序列 平稳性检验 白噪声 峰度 偏度

    时间序列 简而言之,时间序列就是带时间戳的数值序列.股票,期货等金融数据就是典型的时间序列.量化的过程,很多时间都是在分析时间序列,找到稳定赚钱因子. 平稳性定义 所谓时间序列的平稳性,是指时间序列的 ...

  3. python 特征工程_[译] 基于时序数据的特征工程 --- Python实现

    基于时序数据的回归预测问题,在工作中经常遇到的.它与一般的监督学习的回归模型的区别在于数据本身是基于时序的.而常用的时序预测模型,比如arima等,添加其他特征时又不方便,不得不求助于经典的监督学习预 ...

  4. python 拉普拉斯锐化_(二十四)用二阶微分(拉普拉斯算子)实现图像锐化

    时间为友,记录点滴. 我们已经了解过了梯度(一阶微分)的作用,那么为什么要引入二阶微分呢?二阶微分的作用是什么? 还是看图说话: 很明显,一阶微分已经可以把轮廓辨识出来,但是,对于变化较缓的地方,一阶 ...

  5. python球鞋怎么样_抢球鞋?预测股市走势?淘宝秒杀?Python表示要啥有啥

    球鞋那么难抢,有没有抢限量版球鞋的神器? 每当限量版球鞋开售的时候,几十万人一拥而入,能抽中的却是少数. 朋友圈刷到别人中标的消息,心里又羡慕又有点酸......这种时候只能去找黄牛了. 黄牛党都是靠 ...

  6. python数据库模块_十二、Python高级功能之Mysql数据库模块

    Python高级功能之Mysql数据库模块 安装python mysql组件 # yum -y install MySQL-python.x86_64 以下根据实例来说明: >>> ...

  7. python 轮廓矩阵_二进制二维矩阵的python轮廓

    我想计算一个二元NxM矩阵中一个形状的凸壳.凸壳算法需要一个坐标列表,所以我采用纽比.阿尔格何处(im)具有所有形状点坐标.但是,这些点中的大多数并没有对凸包起作用(它们位于形状的内部).因为凸包计算 ...

  8. python平稳性检验_时间序列预测基础教程系列(14)_如何判断时间序列数据是否是平稳的(Python)...

    时间序列预测基础教程系列(14)_如何判断时间序列数据是否是平稳的(Python) 发布时间:2019-01-10 00:02, 浏览次数:620 , 标签: Python 导读: 本文介绍了数据平稳 ...

  9. python cnn 时间序列_有什么好的模型可以做高精度的时间序列预测呢?

    近期整理了一下 Facebook 的 Prophet,个人感觉这是一个非常不错的时间序列预测工具. Prophet 简介 Facebook 去年开源了一个时间序列预测的算法,叫做 fbprophet, ...

最新文章

  1. SpringMVC中数据库链接配置
  2. Java基础/利用fastjson序列化对象为JSON
  3. IDEA中cannot resolve method getBean in applicationContext的解决方法
  4. r 语言 ggplot上添加平均值_R语言自定义两种统计量度:平均值和中位数,何时去使用?
  5. sklearn自学指南(part52)--潜在狄利克雷分配(LDA)
  6. 前端学习(2833):样式rpx
  7. Java注释:类、方法和字段注释
  8. AngularJS-模型和控制器
  9. php使用curl发送 json数据
  10. Ksplice:不再重启你的Linux
  11. PMC 任命Edward Sharp为首席战略及技术官
  12. idea 修改前后端代码自动运行
  13. 第九届蓝桥杯大赛个人赛决赛(CB软件类)真题
  14. idea打包jar包后java运行jar命令提示jar中没有主清单属性的解决方案
  15. Linux 中防火墙命令
  16. python ppt 图片_Python批量导出多个PPT\/PPTX文件中每个幻灯片为独立JPG图片
  17. https-CA证书申请
  18. 前端常说的优化之图片优化
  19. 测绘资质在线处理资质问题
  20. java 汉字转拼音原理_java 汉字转拼音

热门文章

  1. POJ 3308 Paratroopers
  2. 《和声学教程》学习笔记(一):原位正三和弦及连接
  3. 寻找中国的乔布斯 第二届达内发现杯软件大赛决赛开启
  4. TTL与CMOS集成电路
  5. CHM电子书反向编译器及注册机
  6. 利用有向图模型检测社交网络上的欺诈账户
  7. 利用Python实现图像的二值化
  8. One minute io hang when Add ISL Trunking License to Brocade Fabric
  9. 西门子1200三轴机械手结构化编程5轴伺服项目
  10. 以太网物理层(MAC)接口协议