在处理时间序列问题时,人们通常使用跟随算法(将前一个时间单位的观测值作为当前时间的预测值)预测的结果作为预测性能的基准。
在周期性时间序列问题中,这个时间单位可以改作一个周期的时长,预测结果可能会更好,这种方法被在本文中统一称作周期跟随预测(Seasonal Persistence)。
在本文中我们将探究如何在Python中实现周期跟随预测算法。
本文的主要内容:
在使用复杂的预测算法之前,准备一个用于对照的简单算法来作为参照是很有必要的。
这可以确保我们不在无预测性能的模型或者数据集上浪费时间。
在时间序列问题中,我们通常使用跟随预测的模型最为初始参照模型。
这种模型在通常情况下效果都是很好的,但是在具有明显周期特性的数据上这种模型的效果就不是很好了。一个合理的初始模型应该跟随的不是前一个时间单元的观测值,而是上一个周期中相同的时间窗口的观测值。
这就是“周期跟随”模型,它的实现十分简单,但是依旧十分高效。
在此基础上,我们可以不止取距离预测时间一个周期的观测值,还可以取前两个,三个以至n个周期,然后做一定处理,比如说取均值作为我们的预测值。一般来说,经过这样的处理后,模型的预测性能可以进一步提升。
在本文中,我们将演示这种周期跟随预测算法的实现并将之应用于三种不同的时间序列数据集上。
在本文中,我们将使用基于滑动时间窗的周期跟随预测模型进行预测。
每当我们将时间窗前移一个周期,我们就可以收集到上一个周期对应的观测值,我们可以将多次移动得到的观测值取均值作为跟随预测值。
通过调整时间窗的个数(移动次数),我们可以找到使误差最小化的时间窗个数。
举例来说,如果数据的观测频率是月,我们现在要预测二月的观测值,当设定时间窗的个数为1时,模型将使用去年二月的观测值作为预测值。
当设定的时间窗个数为2时,模型将使用过去两年的观测值取平均作为预测值。
除了取均值的方法外,你也可以只使用特定间隔的时间的观测值(比如说12月前,24月前),可以根据在数据集上的性能来决定到底使用哪一种方式。
在检验时间序列模型时,测试的一致性是非常重要的。
在本节中,我们将介绍本教程中的模型评估方法。
首先,我们将最后两年的数据分离出来作为测试集,用于评估模型的性能。这在我们下面使用的月/天为时间间隔的数据集上是同样适用的。
我们使用前向验证(walk-forward)的方式来评估模型性能。这意味着测试集中每个数据会被模型依次遍历,模型建立在历史数据上。模型的预测值和实际观测值会实时记录下来,之后新的观测值被添加进训练集,接着模型可以继续预测后续的观测值。
前向验证保证了在新的观测数据出现时模型可以跟着更新,这使得它成为了时间序列预测问题中实用的验证方案。
最后,模型的预测性能将通过均方根误差(RMSE)表征。使用RMSE的原因是它可以对大的预测偏差产生更大比例的罚值,这使得模型的预测值分布更接近真实观测值的分布。
总结一下,测试套件包括:
本文中用到的月度汽车销售数据集包括了1960年至1968年加拿大魁北克的汽车销售量(Abraham和Ledolter,1983)。
单位是销量个数,一共有108个。
将数据集下载并保存至为该教程示例准备的目录下,然后把文件名重命名为“car-sales.csv”,同时把数据集中不需要的页脚信息删除。
利用Pandas导入数据集。
# line plot of time series
import pandas as pd
from matplotlib import pyplot
# load dataset
series = pd.read_csv('car-sales.csv', header=0)
# display first few rows
print(series.head(5))
# line plot of dataset
series.plot()
pyplot.show()
代码正常运行结束时会输出前五行销量数据
Month
1960-01-01 6550
1960-02-01 8728
1960-03-01 12026
1960-04-01 14395
1960-05-01 14587
Name: Sales, dtype: int64
运行结束后会创建销量随时间变化的折线图。从图中我们既可以看到周期性的波动,也可以看到整体的上升趋势。
最后两年的数据将作为测试集,我们将在前几年数据的基础上建立周期跟随模型。
下面是完整的代码示例:
from pandas import Series
from sklearn.metrics import mean_squared_error
from math import sqrt
from numpy import mean
from matplotlib import pyplot
# load data
series = Series.from_csv('car-sales.csv', header=0)
# prepare data
X = series.values
train, test = X[0:-24], X[-24:]
# evaluate mean of different number of years
years = [1, 2, 3, 4, 5]
scores = list()
for year in years:
# walk-forward validation
history = [x for x in train]
predictions = list()
for i in range(len(test)):
# collect obs
obs = list()
for y in range(1, year+1):
obs.append(history[-(y*12)])
# make prediction
yhat = mean(obs)
predictions.append(yhat)
# observation
history.append(test[i])
# report performance
rmse = sqrt(mean_squared_error(test, predictions))
scores.append(rmse)
print('Years=%d, RMSE: %.3f' % (year, rmse))
pyplot.plot(years, scores)
pyplot.show()
代码运行过程中会打印出当前设定的时间窗个数以及相应的均方根误差。
从输出的计算结果可以看出,当时间窗个数设置为3即取过去三年的销量均值作为预测结果时均方根误差最小。
Years=1, RMSE: 1997.732
Years=2, RMSE: 1914.911
Years=3, RMSE: 1803.630
Years=4, RMSE: 2099.481
Years=5, RMSE: 2522.2351
从下图中也可以看出在测试集上时间窗个数为3时均方误差可以达到最小值,当超出三个时误差会急剧增大。
数据集的基本单元时一个月的信纸销量(数据来源:Makridakis和Wheelwright,1989),总共包括147个月的观测值。注意销量的值是分数,这意味着销量的单位可能是千/万等等。
下载数据集保存到相应目录并重命名为“writing-paper-sales.csv”,还是一样要删除多余的页脚信息。
数据集中的日期列只包含了年份标号和具体的年份。我们需要一个日期解析函数,它能够解析出日期数据并将年份标号转化为具体的年份。根据数据集的说明,年份1对应的是1900年,不过实际上起始年份的选取并不影响模型的参数。
下面的代码演示了如何利用pandas导入我们的数据集并完成年份格式的转换。
# load and plot dataset
from pandas import read_csv
from pandas import datetime
from matplotlib import pyplot
# load dataset
def parser(x):
if len(x) == 4:
return datetime.strptime('190'+x, '%Y-%m')
return datetime.strptime('19'+x, '%Y-%m')
series = read_csv('writing-paper-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
# summarize first few rows
print(series.head())
# line plot
series.plot()
pyplot.show()
数据导入成功后会在控制台输出转化后前五行的数据。
Month
1901-01-01 1359.795
1901-02-01 1278.564
1901-03-01 1508.327
1901-04-01 1419.710
1901-05-01 1440.510
运行结束后可以看到表现信纸销量与时间关系的折线图,与上一个例子一样可以看到数据波动的周期性和整体的上升趋势。
和上面的例子一样,选取最后两年的销量数据作为测试集。不过这里因为我们有着更多月份的数据所以可以尝试更宽的时间窗口设置范围(1-10)。
完整的实验代码
from pandas import read_csv
from pandas import datetime
from sklearn.metrics import mean_squared_error
from math import sqrt
from numpy import mean
from matplotlib import pyplot
# load dataset
def parser(x):
if len(x) == 4:
return datetime.strptime('190'+x, '%Y-%m')
return datetime.strptime('19'+x, '%Y-%m')
series = read_csv('writing-paper-sales.csv', header=0, parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
# prepare data
X = series.values
train, test = X[0:-24], X[-24:]
# evaluate mean of different number of years
years = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
scores = list()
for year in years:
# walk-forward validation
history = [x for x in train]
predictions = list()
for i in range(len(test)):
# collect obs
obs = list()
for y in range(1, year+1):
obs.append(history[-(y*12)])
# make prediction
yhat = mean(obs)
predictions.append(yhat)
# observation
history.append(test[i])
# report performance
rmse = sqrt(mean_squared_error(test, predictions))
scores.append(rmse)
print('Years=%d, RMSE: %.3f' % (year, rmse))
pyplot.plot(years, scores)
pyplot.show()
运行代码,可以在控制台看到滑动时间窗的个数与相应均方根误差的数据。
结果表明训练集上五个滑动时间窗是最合适的,此时均方根误差为554.660。
Years=1, RMSE: 606.089
Years=2, RMSE: 557.653
Years=3, RMSE: 555.777
Years=4, RMSE: 544.251
Years=5, RMSE: 540.317
Years=6, RMSE: 554.660
Years=7, RMSE: 569.032
Years=8, RMSE: 581.405
Years=9, RMSE: 602.279
Years=10, RMSE: 624.756
从绘制的折线图中也可以看出下降的拐点出现在横轴为5的位置,偏离后误差会增大。
该数据集描述了1981年到1990年间澳大利亚墨尔本市的最高气温(数据来源:澳大利亚气象局)。
表征温度的单位是摄氏度,一共有3650个观测值即10年的数据。
下载到相应目录并重命名为“max-daily-temps.csv”,随后记得删除页脚信息。
同样下面给出Pandas加载数据集的代码。
# line plot of time series
from pandas import Series
from matplotlib import pyplot
# load dataset
series = Series.from_csv('max-daily-temps.csv', header=0)
# display first few rows
print(series.head(5))
# line plot of dataset
series.plot()
pyplot.show()
加载数据集成功后会打印前五行的数据。
Date
1981-01-01 38.1
1981-01-02 32.4
1981-01-03 34.5
1981-01-04 20.7
1981-01-05 21.5
最后会创建表征温度与时间关系的折线图,可以看到相比前两个数据集,该数据集中的周期性趋势更明显,而且没有明显的增加或降低的趋势。
由于数据是每天的,所以我们要将上面代码中为月度数据指定的12更换为365。
这里忽略了闰年,读者可以在下面代码的基础上增加闰年的支持。
完整的代码示例(忽略闰年)
from pandas import Series
from sklearn.metrics import mean_squared_erro
from math import sqrt
from numpy import mean
from matplotlib import pyplot
# load data
series = Series.from_csv('max-daily-temps.csv', header=0)
# prepare data
X = series.values
train, test = X[0:-(2*365)], X[-(2*365):]
# evaluate mean of different number of years
years = [1, 2, 3, 4, 5, 6, 7, 8]
scores = list()
for year in years:
# walk-forward validation
history = [x for x in train]
predictions = list()
for i in range(len(test)):
# collect obs
obs = list()
for y in range(1, year+1):
obs.append(history[-(y*365)])
# make prediction
yhat = mean(obs)
predictions.append(yhat)
# observation
history.append(test[i])
# report performance
rmse = sqrt(mean_squared_error(test, predictions))
scores.append(rmse)
print('Years=%d, RMSE: %.3f' % (year, rmse))
pyplot.plot(years, scores)
pyplot.show()
运行代码,控制台将打印选取时间窗个数与均方根误差。
在前两种情况中,我们可以看到模型的预测性能在某个固定的时间窗个数达到最小值,增大或减小都会导致性能下降。而这个例子中不同,随着时间窗个数的增加,预测性能也在不断增加。
在训练集上最好的预测结果对应时间窗个数为8,均方根误差为4.271。
Years=1, RMSE: 5.950
Years=2, RMSE: 5.083
Years=3, RMSE: 4.664
Years=4, RMSE: 4.539
Years=5, RMSE: 4.448
Years=6, RMSE: 4.358
Years=7, RMSE: 4.371
Years=8, RMSE: 4.271
表现在图像上,均方根误差与时间窗的曲线呈下降趋势。
这表明,如果我们前面提到的假设成立,即当前的观测值可以由之前周期的观测值得出,那么由图像可知,我们拥有的历史数据越多,我们的预测结果就越准确。
想象一下,如果这里的数据集为月度的,我们同样也可以利用周期跟随模型达到很好的性能。这反映了在温度数据上,周期跟随模型是一个很不错的参照或优化的基础。
在本教程中,我们提出并探究使用了周期跟随模型。
总结一下本文解决的主要问题: