• 机器学习笔记 - 时间序列预测研究:法国香槟的月销量


    1、问题描述

            问题是预测 Perrin Freres 标签(以法国的一个地区命名)的香槟月销量。该数据集提供了从 1964 年 1 月到 1972 年 9 月的香槟月销售量,或不到 10 年的数据。这些值是对数百万销售额的计数,有 105 个观察值。

    1. 链接:https://pan.baidu.com/s/1DyoZ_xFZeItCfrpX1RTG2g
    2. 提取码:1f2u

            将数据集下载为 CSV 文件,并将其放在当前工作目录中,文件名为“ champagne.csv ”。

    2、划分数据集

            假设现在是 1971 年 9 月,并在分析和模型选择中保留最后一年的数据。最后一年的数据将用于验证最终模型

            下面的代码会将数据集作为 Pandas 系列加载并分成两部分,一个用于模型开发(dataset.csv),另一个用于验证(validation.csv)。

    1. from pandas import read_csv
    2. series = read_csv('champagne.csv', header=0, index_col=0, parse_dates=True, squeeze=True)
    3. split_point = len(series) - 12
    4. dataset, validation = series[0:split_point], series[split_point:]
    5. print('Dataset %d, Validation %d' % (len(dataset), len(validation)))
    6. dataset.to_csv('dataset.csv', header=False)
    7. validation.to_csv('validation.csv', header=False)

            运行该示例会创建两个文件并打印每个文件中的数据数量。

    Dataset 93, Validation 12

            这些文件的具体内容是:

            dataset.csv:从 1964 年 1 月到 1971 年 9 月的观测值(93 个观测值)

            validation.csv:从 1971 年 10 月到 1972 年 9 月的观察(12 个观察)

            验证数据集大约是原始数据集的 11%。

    3、数据分析

            我们可以使用汇总统计数据和数据图来快速了解预测问题的结构。

    (1)汇总统计

            汇总统计数据可以快速查看观察值的限制。它有助于快速了解我们正在使用的内容。

    1. from pandas import read_csv
    2. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    3. print(series.describe())

            这些统计数据的一些观察结果包括:

            观察次数(计数)符合我们的预期,这意味着我们正在正确处理数据。

            平均值约为 4,641,我们可能会考虑我们在本系列中的水平。

            标准差(与平均值的平均差)相对较大,为 2,486 次销售。

            百分位数和标准偏差确实表明数据存在很大的差异。

    1. count 93.000000
    2. mean 4641.118280
    3. std 2486.403841
    4. min 1573.000000
    5. 25% 3036.000000
    6. 50% 4016.000000
    7. 75% 5048.000000
    8. max 13916.000000

    (2)线图

    1. from pandas import read_csv
    2. from matplotlib import pyplot
    3. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    4. series.plot()
    5. pyplot.show()

            该图的一些观察结果包括:

            随着时间的推移,销售额可能会呈上升趋势。

            每年的销售似乎都存在系统性的季节性。

            季节性信号似乎随着时间的推移而增长,表明存在乘法关系(增加变化)。

            似乎没有任何明显的异常值。

            季节性表明该系列几乎可以肯定是非平稳的。

    香槟销售线图

             显式建模季节性组件并将其移除可能会有好处。您还可以探索使用具有一个或两个级别的差分来使系列静止。

            季节性成分的增加趋势或增长可能表明使用对数或其他幂变换。

    (3)季节线性图

            我们可以通过逐年观察数据集的线图来确认季节性是一年周期的假设。

            下面的示例将 7 整年的数据作为单独的组,并为每个组创建一个线图。线图垂直对齐,以帮助发现任何年度模式。

    1. from pandas import read_csv
    2. from pandas import DataFrame
    3. from pandas import Grouper
    4. from matplotlib import pyplot
    5. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    6. groups = series['1964':'1970'].groupby(Grouper(freq='A'))
    7. years = DataFrame()
    8. pyplot.figure()
    9. i = 1
    10. n_groups = len(groups)
    11. for name, group in groups:
    12. pyplot.subplot((n_groups*100) + 10 + i)
    13. i += 1
    14. pyplot.plot(group)
    15. pyplot.show()

            运行该示例会创建 7 个线图的堆栈。

            我们可以清楚地看到每年 8 月的下降和每年 8 月到 12 月的上升。这种模式每年都出现相同的情况,尽管水平不同。

            这将有助于以后任何明确的基于季节的建模。

    每年季节性线图

    (4)密度图

            查看观察密度图可以进一步了解数据的结构。

    1. from pandas import read_csv
    2. from matplotlib import pyplot
    3. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    4. pyplot.figure(1)
    5. pyplot.subplot(211)
    6. series.hist()
    7. pyplot.subplot(212)
    8. series.plot(kind='kde')
    9. pyplot.show()

            图中的一些观察结果包括:

            分布不是高斯分布。

            该形状有一条长长的右尾,可能表明呈指数分布

     (5)箱线图

            我们可以按年份对月度数据进行分组,并了解每年观察的分布情况以及这种情况可能会如何变化。

            我们确实希望看到一些趋势(增加平均值或中位数),但看看其余分布可能如何变化可能会很有趣。

            下面的示例按年份对观察结果进行分组,并为每一年的观察结果创建一个箱须图。去年(1971 年)仅包含 9 个月,可能无法与其他年份的 12 个月观察结果进行有用的比较。因此,仅绘制了 1964 年至 1970 年之间的数据。

    1. from pandas import read_csv
    2. from pandas import DataFrame
    3. from pandas import Grouper
    4. from matplotlib import pyplot
    5. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    6. groups = series['1964':'1970'].groupby(Grouper(freq='A'))
    7. years = DataFrame()
    8. for name, group in groups:
    9. years[name.year] = group.values
    10. years.boxplot()
    11. pyplot.show()

            运行该示例会创建 7 个并排的箱线图,每一个对应 7 年的选定数据。

            回顾这些图的一些观察结果包括:

            每年的中值(红线)可能呈上升趋势。

            分散或中间 50% 的数据(蓝色框)确实看起来相当稳定。

            每年都有异常值(黑色十字);这些可能是季节性周期的顶部或底部。

            去年,1970 年,看起来确实与往年的趋势不同

    香槟销售盒和晶须图

             观察结果表明,多年来可能存在一些增长趋势以及可能是季节性周期一部分的异常值。

    4、ARIMA 模型

            在本节中,我们将针对该问题开发自回归综合移动平均线或 ARIMA 模型。

            我们将通过手动和自动配置 ARIMA 模型来进行建模。接下来是调查所选模型的残差的第三步。

    (1)手动配置的 ARIMA

            ARIMA( p,d,q ) 模型需要三个参数,并且传统上是手动配置的。

            时间序列数据的分析假设我们使用的是固定时间序列。

            时间序列几乎可以肯定是非平稳的。我们可以通过首先对序列进行差分并使用统计测试来确认结果是平稳的,从而使其平稳。

            该系列的季节性似乎是逐年的。季节性数据可以通过从上一个周期的同一时间减去观测值来区分,在这种情况下是上一年的同一个月。这确实意味着我们将失去第一年的观察结果,因为与前一年没有区别。

            下面的示例创建了该系列的去季节化版本并将其保存到文件stationary.csv中。

    1. from pandas import read_csv
    2. from pandas import Series
    3. from statsmodels.tsa.stattools import adfuller
    4. from matplotlib import pyplot
    5. # create a differenced series
    6. def difference(dataset, interval=1):
    7. diff = list()
    8. for i in range(interval, len(dataset)):
    9. value = dataset[i] - dataset[i - interval]
    10. diff.append(value)
    11. return Series(diff)
    12. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    13. X = series.values
    14. X = X.astype('float32')
    15. # difference data
    16. months_in_year = 12
    17. stationary = difference(X, months_in_year)
    18. stationary.index = series.index[months_in_year:]
    19. # check if stationary
    20. result = adfuller(stationary)
    21. print('ADF Statistic: %f' % result[0])
    22. print('p-value: %f' % result[1])
    23. print('Critical Values:')
    24. for key, value in result[4].items():
    25. print('\t%s: %.3f' % (key, value))
    26. # save
    27. stationary.to_csv('stationary.csv', header=False)
    28. # plot
    29. stationary.plot()
    30. pyplot.show()

            运行该示例会输出差分序列是否平稳的统计显着性检验结果。具体来说,增强的 Dickey-Fuller 测试。

            结果表明,检验统计量值 -7.134898 小于临界值-3.515 的 1%。这表明我们可以拒绝显着性水平小于 1% 的原假设(即,结果是统计侥幸的概率很低)。

            拒绝原假设意味着过程没有单位根,进而表明时间序列是平稳的或不具有时间相关结构。

    1. ADF Statistic: -7.134898
    2. p-value: 0.000000
    3. Critical Values:
    4. 5%: -2.898
    5. 1%: -3.515
    6. 10%: -2.586

            作为参考,可以通过添加前一年同月的观测值来反转季节差异操作。如果预测是通过适合季节性差异数据的模型进行的,则需要这样做。为了完整起见,下面列出了反转季节差异运算的函数。

    1. # invert differenced value
    2. def inverse_difference(history, yhat, interval=1):
    3. return yhat + history[-interval]

            还创建了差异数据集的图。

            该图没有显示任何明显的季节性或趋势,表明季节性差异数据集是建模的良好起点。

            我们将使用这个数据集作为 ARIMA 模型的输入。它还表明可能不需要进一步的差分,并且可以将 d 参数设置为 0。

    季节性差异香槟销售线图

             接下来的第一步是分别选择自回归 (AR) 和移动平均 (MA) 参数的滞后值p和q。

            我们可以通过查看自相关函数 (ACF) 和偏自相关函数 (PACF) 图来做到这一点。

            请注意,我们现在使用季节性差异的stationary.csv 作为我们的数据集。

            下面的示例为该系列创建 ACF 和 PACF 图。

    1. from pandas import read_csv
    2. from statsmodels.graphics.tsaplots import plot_acf
    3. from statsmodels.graphics.tsaplots import plot_pacf
    4. from matplotlib import pyplot
    5. series = read_csv('stationary.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    6. pyplot.figure()
    7. pyplot.subplot(211)
    8. plot_acf(series, ax=pyplot.gca())
    9. pyplot.subplot(212)
    10. plot_pacf(series, ax=pyplot.gca())
    11. pyplot.show()

            运行示例并查看图表,了解如何为 ARIMA 模型设置p和q变量。

            以下是从图中得到的一些观察结果。

            ACF 显示明显滞后 1 个月。
            PACF 显示显着滞后 1 个月,可能在 12 个月和 13 个月有显着滞后。
            ACF 和 PACF 在同一点均显示下降,这可能表明 AR 和 MA 的混合。
            p 和 q 值的良好起点也是 1。

            PACF 图还表明差异数据中仍然存在一些季节性。

            我们可能会考虑更好的季节性模型,例如直接对其进行建模并明确地将其从模型中移除,而不是季节性差异。

    季节性差异香槟销售的 ACF 和 PACF 图

             这种快速分析表明,在固定数据上的 ARIMA(1,0,1) 可能是一个很好的起点。

            在拟合每个 ARIMA 模型之前,历史观测值将存在季节性差异。对于所做的所有预测,差异将被反转,以使它们与原始销售计数单位中的预期观察结果直接可比。

            实验表明,ARIMA 的这种配置不会收敛,并导致底层库出错。进一步的实验表明,向平稳数据添加一级差分可使模型更加稳定。该模型可以扩展到 ARIMA(1,1,1)。

            我们还将禁用模型中趋势常数的自动添加,方法是将“ trend ”参数设置为“ nc ”,因为在 fit() 调用中没有常数。通过实验,我发现这可以在某些问题上产生更好的预测性能。

            下面的示例演示了此 ARIMA 模型在测试工具上的性能。

    1. # evaluate manually configured ARIMA model
    2. from pandas import read_csv
    3. from sklearn.metrics import mean_squared_error
    4. from statsmodels.tsa.arima.model import ARIMA
    5. from math import sqrt
    6. # create a differenced series
    7. def difference(dataset, interval=1):
    8. diff = list()
    9. for i in range(interval, len(dataset)):
    10. value = dataset[i] - dataset[i - interval]
    11. diff.append(value)
    12. return diff
    13. # invert differenced value
    14. def inverse_difference(history, yhat, interval=1):
    15. return yhat + history[-interval]
    16. # load data
    17. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    18. # prepare data
    19. X = series.values
    20. X = X.astype('float32')
    21. train_size = int(len(X) * 0.50)
    22. train, test = X[0:train_size], X[train_size:]
    23. # walk-forward validation
    24. history = [x for x in train]
    25. predictions = list()
    26. for i in range(len(test)):
    27. # difference data
    28. months_in_year = 12
    29. diff = difference(history, months_in_year)
    30. # predict
    31. model = ARIMA(diff, order=(1,1,1))
    32. model_fit = model.fit()
    33. yhat = model_fit.forecast()[0]
    34. yhat = inverse_difference(history, yhat, months_in_year)
    35. predictions.append(yhat)
    36. # observation
    37. obs = test[i]
    38. history.append(obs)
    39. print('>Predicted=%.3f, Expected=%.3f' % (yhat, obs))
    40. # report performance
    41. rmse = sqrt(mean_squared_error(test, predictions))
    42. print('RMSE: %.3f' % rmse)

            运行此示例导致 RMSE 为 956.942,这明显优于 3186.501 的持久性 RMSE。

    1. >Predicted=3157.018, Expected=5010
    2. >Predicted=4615.082, Expected=4874
    3. >Predicted=4624.998, Expected=4633
    4. >Predicted=2044.097, Expected=1659
    5. >Predicted=5404.428, Expected=5951
    6. RMSE: 956.942

            可以通过配置更好的 ARIMA 模型获得改进的结果。

    (2)网格搜索 ARIMA 超参数

            ACF 和 PACF 图表明 ARIMA(1,0,1) 或类似的可能是我们能做的最好的。

            为了确认这一分析,我们可以对一组 ARIMA 超参数进行网格搜索,并检查是否没有模型能够产生更好的样本 RMSE 性能。

            在本节中,我们将在p、d和q的值中搜索组合(跳过那些无法收敛的组合),并找到在测试集上产生最佳性能的组合。我们将使用网格搜索来探索整数值子集中的所有组合。

            具体来说,我们将搜索以下参数的所有组合:

            p:0 到 6。
            d:0 到 2。
            q:0 到 6。
            这是 ( 7 * 3 * 7 ) 或 147 次测试工具的潜在运行,需要一些时间来执行。

            评估滞后为 12 或 13 的 MA 模型可能会很有趣,因为通过审查 ACF 和 PACF 图可能会很有趣。实验表明,这些模型可能不稳定,导致底层数学库出现错误。

            下面列出了测试工具的网格搜索版本的完整工作示例。

    1. # grid search ARIMA parameters for time series
    2. import warnings
    3. from pandas import read_csv
    4. from statsmodels.tsa.arima.model import ARIMA
    5. from sklearn.metrics import mean_squared_error
    6. from math import sqrt
    7. import numpy
    8. # create a differenced series
    9. def difference(dataset, interval=1):
    10. diff = list()
    11. for i in range(interval, len(dataset)):
    12. value = dataset[i] - dataset[i - interval]
    13. diff.append(value)
    14. return numpy.array(diff)
    15. # invert differenced value
    16. def inverse_difference(history, yhat, interval=1):
    17. return yhat + history[-interval]
    18. # evaluate an ARIMA model for a given order (p,d,q) and return RMSE
    19. def evaluate_arima_model(X, arima_order):
    20. # prepare training dataset
    21. X = X.astype('float32')
    22. train_size = int(len(X) * 0.50)
    23. train, test = X[0:train_size], X[train_size:]
    24. history = [x for x in train]
    25. # make predictions
    26. predictions = list()
    27. for t in range(len(test)):
    28. # difference data
    29. months_in_year = 12
    30. diff = difference(history, months_in_year)
    31. model = ARIMA(diff, order=arima_order)
    32. model_fit = model.fit()
    33. yhat = model_fit.forecast()[0]
    34. yhat = inverse_difference(history, yhat, months_in_year)
    35. predictions.append(yhat)
    36. history.append(test[t])
    37. # calculate out of sample error
    38. rmse = sqrt(mean_squared_error(test, predictions))
    39. return rmse
    40. # evaluate combinations of p, d and q values for an ARIMA model
    41. def evaluate_models(dataset, p_values, d_values, q_values):
    42. dataset = dataset.astype('float32')
    43. best_score, best_cfg = float("inf"), None
    44. for p in p_values:
    45. for d in d_values:
    46. for q in q_values:
    47. order = (p,d,q)
    48. try:
    49. rmse = evaluate_arima_model(dataset, order)
    50. if rmse < best_score:
    51. best_score, best_cfg = rmse, order
    52. print('ARIMA%s RMSE=%.3f' % (order,rmse))
    53. except:
    54. continue
    55. print('Best ARIMA%s RMSE=%.3f' % (best_cfg, best_score))
    56. # load dataset
    57. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    58. # evaluate parameters
    59. p_values = range(0, 7)
    60. d_values = range(0, 3)
    61. q_values = range(0, 7)
    62. warnings.filterwarnings("ignore")
    63. evaluate_models(series.values, p_values, d_values, q_values)

            运行该示例会遍历所有组合,并报告那些没有错误收敛的结果。该示例在现代硬件上运行需要 2 分钟多一点。

            结果表明,发现的最佳配置是 RMSE 为 939.464 的 ARIMA(0, 0, 1),略低于上一节中手动配置的 ARIMA。这种差异可能具有统计意义,也可能不具有统计意义。

    1. ARIMA(5, 1, 2) RMSE=1003.200
    2. ARIMA(5, 2, 1) RMSE=1053.728
    3. ARIMA(6, 0, 0) RMSE=996.466
    4. ARIMA(6, 1, 0) RMSE=1018.211
    5. ARIMA(6, 1, 1) RMSE=1023.762
    6. Best ARIMA(0, 0, 1) RMSE=939.464

    (3)查看预测错误

            一个好的模型最终检查是检查剩余的预测误差。

            理想情况下,残差分布应该是均值为零的高斯分布。

            我们可以通过使用汇总统计数据和绘图来检查 ARIMA(0, 0, 1) 模型的残差。下面的示例计算并总结了剩余预测误差。

    1. # summarize ARIMA forecast residuals
    2. from pandas import read_csv
    3. from pandas import DataFrame
    4. from statsmodels.tsa.arima.model import ARIMA
    5. from matplotlib import pyplot
    6. # create a differenced series
    7. def difference(dataset, interval=1):
    8. diff = list()
    9. for i in range(interval, len(dataset)):
    10. value = dataset[i] - dataset[i - interval]
    11. diff.append(value)
    12. return diff
    13. # invert differenced value
    14. def inverse_difference(history, yhat, interval=1):
    15. return yhat + history[-interval]
    16. # load data
    17. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    18. # prepare data
    19. X = series.values
    20. X = X.astype('float32')
    21. train_size = int(len(X) * 0.50)
    22. train, test = X[0:train_size], X[train_size:]
    23. # walk-forward validation
    24. history = [x for x in train]
    25. predictions = list()
    26. for i in range(len(test)):
    27. # difference data
    28. months_in_year = 12
    29. diff = difference(history, months_in_year)
    30. # predict
    31. model = ARIMA(diff, order=(0,0,1))
    32. model_fit = model.fit()
    33. yhat = model_fit.forecast()[0]
    34. yhat = inverse_difference(history, yhat, months_in_year)
    35. predictions.append(yhat)
    36. # observation
    37. obs = test[i]
    38. history.append(obs)
    39. # errors
    40. residuals = [test[i]-predictions[i] for i in range(len(test))]
    41. residuals = DataFrame(residuals)
    42. print(residuals.describe())
    43. # plot
    44. pyplot.figure()
    45. pyplot.subplot(211)
    46. residuals.hist(ax=pyplot.gca())
    47. pyplot.subplot(212)
    48. residuals.plot(kind='kde', ax=pyplot.gca())
    49. pyplot.show()

            运行示例首先描述残差的分布。

            我们可以看到分布有一个右移,并且平均值在 165.904728 处非零。

            这可能是预测存在偏差的迹象。

    1. count 47.000000
    2. mean 165.904728
    3. std 934.696199
    4. min -2164.247449
    5. 25% -289.651596
    6. 50% 191.759548
    7. 75% 732.992187
    8. max 2367.304748

            我们可以通过将 165.904728 的平均残差添加到所做的每个预测中来使用此信息来偏置正确的预测。

            下面的示例执行这种偏差相关。

    1. # plots of residual errors of bias corrected forecasts
    2. from pandas import read_csv
    3. from pandas import DataFrame
    4. from statsmodels.tsa.arima.model import ARIMA
    5. from matplotlib import pyplot
    6. from sklearn.metrics import mean_squared_error
    7. from math import sqrt
    8. # create a differenced series
    9. def difference(dataset, interval=1):
    10. diff = list()
    11. for i in range(interval, len(dataset)):
    12. value = dataset[i] - dataset[i - interval]
    13. diff.append(value)
    14. return diff
    15. # invert differenced value
    16. def inverse_difference(history, yhat, interval=1):
    17. return yhat + history[-interval]
    18. # load data
    19. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    20. # prepare data
    21. X = series.values
    22. X = X.astype('float32')
    23. train_size = int(len(X) * 0.50)
    24. train, test = X[0:train_size], X[train_size:]
    25. # walk-forward validation
    26. history = [x for x in train]
    27. predictions = list()
    28. bias = 165.904728
    29. for i in range(len(test)):
    30. # difference data
    31. months_in_year = 12
    32. diff = difference(history, months_in_year)
    33. # predict
    34. model = ARIMA(diff, order=(0,0,1))
    35. model_fit = model.fit()
    36. yhat = model_fit.forecast()[0]
    37. yhat = bias + inverse_difference(history, yhat, months_in_year)
    38. predictions.append(yhat)
    39. # observation
    40. obs = test[i]
    41. history.append(obs)
    42. # report performance
    43. rmse = sqrt(mean_squared_error(test, predictions))
    44. print('RMSE: %.3f' % rmse)
    45. # errors
    46. residuals = [test[i]-predictions[i] for i in range(len(test))]
    47. residuals = DataFrame(residuals)
    48. print(residuals.describe())
    49. # plot
    50. pyplot.figure()
    51. pyplot.subplot(211)
    52. residuals.hist(ax=pyplot.gca())
    53. pyplot.subplot(212)
    54. residuals.plot(kind='kde', ax=pyplot.gca())
    55. pyplot.show()

            预测的性能从 939.464 略微提高到 924.699,可能显着也可能不显着。

            预测残差的总结表明,平均值确实移动到了一个非常接近于零的值。

    1. RMSE: 924.699
    2. count 4.700000e+01
    3. mean 4.965016e-07
    4. std 9.346962e+02
    5. min -2.330152e+03
    6. 25% -4.555563e+02
    7. 50% 2.585482e+01
    8. 75% 5.670875e+02
    9. max 2.201400e+03

            最后,残差的密度图确实显示出向零的小幅偏移。

            这种偏差校正是否值得值得商榷,但我们现在将使用它。

             检查任何类型的自相关的残差的时间序列也是一个好主意。如果存在,则表明该模型有更多机会对数据中的时间结构进行建模。

            下面的示例重新计算残差并创建 ACF 和 PACF 图以检查任何显着的自相关。

    1. # ACF and PACF plots of residual errors of bias corrected forecasts
    2. from pandas import read_csv
    3. from pandas import DataFrame
    4. from statsmodels.tsa.arima.model import ARIMA
    5. from matplotlib import pyplot
    6. from statsmodels.graphics.tsaplots import plot_acf
    7. from statsmodels.graphics.tsaplots import plot_pacf
    8. # create a differenced series
    9. def difference(dataset, interval=1):
    10. diff = list()
    11. for i in range(interval, len(dataset)):
    12. value = dataset[i] - dataset[i - interval]
    13. diff.append(value)
    14. return diff
    15. # invert differenced value
    16. def inverse_difference(history, yhat, interval=1):
    17. return yhat + history[-interval]
    18. # load data
    19. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    20. # prepare data
    21. X = series.values
    22. X = X.astype('float32')
    23. train_size = int(len(X) * 0.50)
    24. train, test = X[0:train_size], X[train_size:]
    25. # walk-forward validation
    26. history = [x for x in train]
    27. predictions = list()
    28. for i in range(len(test)):
    29. # difference data
    30. months_in_year = 12
    31. diff = difference(history, months_in_year)
    32. # predict
    33. model = ARIMA(diff, order=(0,0,1))
    34. model_fit = model.fit()
    35. yhat = model_fit.forecast()[0]
    36. yhat = inverse_difference(history, yhat, months_in_year)
    37. predictions.append(yhat)
    38. # observation
    39. obs = test[i]
    40. history.append(obs)
    41. # errors
    42. residuals = [test[i]-predictions[i] for i in range(len(test))]
    43. residuals = DataFrame(residuals)
    44. print(residuals.describe())
    45. # plot
    46. pyplot.figure()
    47. pyplot.subplot(211)
    48. plot_acf(residuals, ax=pyplot.gca())
    49. pyplot.subplot(212)
    50. plot_pacf(residuals, ax=pyplot.gca())
    51. pyplot.show()

            结果表明,时间序列中存在的少量自相关已被模型捕获。

    5、模型验证

            在开发模型并选择最终模型后,必须对其进行验证和最终确定。

            验证是该过程的可选部分,但它提供“最后检查”以确保我们没有欺骗或误导自己。

    (1)完成模型

            最终确定模型涉及在整个数据集上拟合 ARIMA 模型,在本例中是在整个数据集的转换版本上。

            拟合后,可以将模型保存到文件中以备后用。

            下面的示例在数据集上训练 ARIMA(0,0,1) 模型并将整个拟合对象和偏差保存到文件中。

            下面的示例将拟合模型以正确的状态保存到文件中,以便以后可以成功加载。

    1. # save finalized model
    2. from pandas import read_csv
    3. from statsmodels.tsa.arima.model import ARIMA
    4. import numpy
    5. # create a differenced series
    6. def difference(dataset, interval=1):
    7. diff = list()
    8. for i in range(interval, len(dataset)):
    9. value = dataset[i] - dataset[i - interval]
    10. diff.append(value)
    11. return diff
    12. # load data
    13. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    14. # prepare data
    15. X = series.values
    16. X = X.astype('float32')
    17. # difference data
    18. months_in_year = 12
    19. diff = difference(X, months_in_year)
    20. # fit model
    21. model = ARIMA(diff, order=(0,0,1))
    22. model_fit = model.fit()
    23. # bias constant, could be calculated from in-sample mean residual
    24. bias = 165.904728
    25. # save model
    26. model_fit.save('model.pkl')
    27. numpy.save('model_bias.npy', [bias])

            运行该示例会创建两个本地文件:

            model.pkl这是来自ARIMA.fit()调用的 ARIMAResult 对象。这包括拟合模型时返回的系数和所有其他内部数据。

            model_bias.npy这是存储为单行单列 NumPy 数组的偏差值。

    (2)进行预测

            下面的示例加载模型,对下一个时间步进行预测,然后打印预测。

    1. # load finalized model and make a prediction
    2. from pandas import read_csv
    3. from statsmodels.tsa.arima.model import ARIMAResults
    4. import numpy
    5. # invert differenced value
    6. def inverse_difference(history, yhat, interval=1):
    7. return yhat + history[-interval]
    8. series = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    9. months_in_year = 12
    10. model_fit = ARIMAResults.load('model.pkl')
    11. bias = numpy.load('model_bias.npy')
    12. yhat = float(model_fit.forecast()[0])
    13. yhat = bias + inverse_difference(series.values, yhat, months_in_year)
    14. print('Predicted: %.3f' % yhat)

            运行该示例会打印出大约 6794 的预测。

    Predicted: 6794.773

            查看validation.csv,我们可以看到下一个时间段的第一行的值为 6981。预测是正确的。

    (3)验证模型

            我们可以加载模型并以假装的操作方式使用它。

            在测试工具部分,我们将原始数据集的最后 12 个月保存在一个单独的文件中,以验证最终模型。

            我们现在可以加载这个validation.csv文件并使用它来查看我们的模型在“未见”数据上的实际效果。

            我们可以通过两种方式进行:

            加载模型并使用它来预测未来 12 个月。最初一两个月之后的预测将很快开始降低技能。
            加载模型并以滚动预测的方式使用它,为每个时间步更新变换和模型。这是首选方法,因为这是在实践中使用此模型的方式,因为它可以实现最佳性能。
            与前几节中的模型评估一样,我们将以滚动预测的方式进行预测。这意味着我们将跨越验证数据集中的提前期,并将观察结果作为对历史的更新。

    1. # load and evaluate the finalized model on the validation dataset
    2. from pandas import read_csv
    3. from matplotlib import pyplot
    4. from statsmodels.tsa.arima.model import ARIMA
    5. from statsmodels.tsa.arima.model import ARIMAResults
    6. from sklearn.metrics import mean_squared_error
    7. from math import sqrt
    8. import numpy
    9. # create a differenced series
    10. def difference(dataset, interval=1):
    11. diff = list()
    12. for i in range(interval, len(dataset)):
    13. value = dataset[i] - dataset[i - interval]
    14. diff.append(value)
    15. return diff
    16. # invert differenced value
    17. def inverse_difference(history, yhat, interval=1):
    18. return yhat + history[-interval]
    19. # load and prepare datasets
    20. dataset = read_csv('dataset.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    21. X = dataset.values.astype('float32')
    22. history = [x for x in X]
    23. months_in_year = 12
    24. validation = read_csv('validation.csv', header=None, index_col=0, parse_dates=True, squeeze=True)
    25. y = validation.values.astype('float32')
    26. # load model
    27. model_fit = ARIMAResults.load('model.pkl')
    28. bias = numpy.load('model_bias.npy')
    29. # make first prediction
    30. predictions = list()
    31. yhat = float(model_fit.forecast()[0])
    32. yhat = bias + inverse_difference(history, yhat, months_in_year)
    33. predictions.append(yhat)
    34. history.append(y[0])
    35. print('>Predicted=%.3f, Expected=%.3f' % (yhat, y[0]))
    36. # rolling forecasts
    37. for i in range(1, len(y)):
    38. # difference data
    39. months_in_year = 12
    40. diff = difference(history, months_in_year)
    41. # predict
    42. model = ARIMA(diff, order=(0,0,1))
    43. model_fit = model.fit()
    44. yhat = model_fit.forecast()[0]
    45. yhat = bias + inverse_difference(history, yhat, months_in_year)
    46. predictions.append(yhat)
    47. # observation
    48. obs = y[i]
    49. history.append(obs)
    50. print('>Predicted=%.3f, Expected=%.3f' % (yhat, obs))
    51. # report performance
    52. rmse = sqrt(mean_squared_error(y, predictions))
    53. print('RMSE: %.3f' % rmse)
    54. pyplot.plot(y)
    55. pyplot.plot(predictions, color='red')
    56. pyplot.show()

            运行该示例会打印验证数据集中时间步长的每个预测值和期望值。

            验证期的最终 RMSE 预计为 361.110 百万销售额。

            这比预期的每月销售额略多于 9.24 亿的错误要好得多。

    1. >Predicted=6794.773, Expected=6981
    2. >Predicted=10101.763, Expected=9851
    3. >Predicted=13219.067, Expected=12670
    4. >Predicted=3996.535, Expected=4348
    5. >Predicted=3465.934, Expected=3564
    6. >Predicted=4522.683, Expected=4577
    7. >Predicted=4901.336, Expected=4788
    8. >Predicted=5190.094, Expected=4618
    9. >Predicted=4930.190, Expected=5312
    10. >Predicted=4944.785, Expected=4298
    11. >Predicted=1699.409, Expected=1413
    12. >Predicted=6085.324, Expected=5877
    13. RMSE: 361.110

            还提供了与验证数据集相比的预测图。

            在这个规模上,12 个月的预测销售数据看起来很棒。

  • 相关阅读:
    百华鞋业董事长牛兴华应邀出席德国前总统武尔夫欢迎宴会
    深入OpenCV Android应用开发
    # 粒子滤波 PF——三维匀速运动CV目标跟踪(粒子滤波VS扩展卡尔曼滤波)
    Python自定义可切片的类
    Windows 10上安装Docker
    天振股份上市首日破发:市值蒸发约8亿元,方庆华夫妇为实控人
    企业安全生产双重预防机制的构建和应用
    PIE-engine 教程 ——基于PIE-engine的水体频率变化长时序遥感监测自动计算平台
    2022年8月总结
    ElasticSearch 学习1:linux 安装 ElasticSearch7.6.1
  • 原文地址:https://blog.csdn.net/bashendixie5/article/details/125570009