关于机器学习:单变量时间序列平滑方法介绍

4次阅读

共计 6853 个字符,预计需要花费 18 分钟才能阅读完成。

工夫序列是由按工夫排序的察看单位组成的数据。可能是天气数据、股市数据。,也就是说它是由按工夫排序的察看值组成的数据。

在本文中将介绍和解释工夫序列的平滑办法,工夫序列统计办法在另一篇文章中进行了解释。本文将解释以下 4 个构造概念:

1、稳态(Stationary)

稳态是指零碎的状态不再随工夫产生扭转的一种状态。换句话说,如果一个工夫序列的均值、方差和协方差随工夫放弃不变,则该序列被称为安稳的。

为什么稳态很重要呢?: 实践上有一种解释,即工夫序列的构造在肯定的平稳性下,即在肯定的模式下,更容易预测。也就是说,如果是安稳的,则静止下一步也是能够预测的。因而通常会有一个冀望: 工夫序列是安稳的吗? 如果它是安稳的,咱们能够更容易地做出预测。为了捕获这一点,能够通过查看工夫序列图像而不是统计测试来理解这一点。

2、趋势(Trend)

它是一个工夫序列的长期增减构造。如果存在趋势,则序列不太可能是安稳的,因为周期的统计数据 (平均值、标准偏差等) 将以减少或缩小的趋势变动。

3、季节性(Seasonality)

季节性是指一个工夫序列以肯定的距离反复某种行为。

4、周期(Cycle)

它蕴含相似于季节性的反复模式,然而这两个问题可能会互相混同。季节性能够映射到特定的时间段。它与日、周、年、季等时间段重叠。例如,市场在周末有更多的生意,或者一个产品在冬天更受关注等等。

周期性产生在更长的工夫,更不确定的构造,以不与日、周等构造重叠的形式产生。它的产生次要是出于结构性起因,并具备周期性变动。例如,一些促销流动尽管这不是齐全季节性的,它是在肯定期间内产生的,但具体会在什么期间产生,会依据不同营销策略来决定。

了解工夫序列模型的实质: 咱们曾经看到了下面工夫序列的根本构造。工夫序列的假如是: 工夫序列在 t 时间段内的值受前一个时间段 (t-1) 的值影响最大。例如明天是星期天,它后面的值最能解释星期天工夫序列的值。有了这些基础知识,咱们能够开始进行平滑办法的介绍

单变量的平滑办法

1、单指数平滑法(Simple Exponential Smoothing – SES)

它只在安稳的工夫序列中体现良好,因为它要求序列中不应该有趋势和季节性。它能够对 Level(程度)进行建模(Level 能够认为是序列的平均值)。过来的影响是在“将来与最近的过来更相干”的假如上进行加权。SES 实用于没有趋势和季节性的单变量工夫序列,它在安稳序列中是最胜利的。

2、双指数平滑法(Double Exponential Smoothing – DES)

它在 SES 的根底上减少了趋势的判断。所以它实用于具备和不具备季节性的单变量工夫序列。

3、三重指数平滑(TES — Holt-Winters):

它是目前最先进的平滑办法。该办法通过动静评估 Level(程度)、趋势和季节性的影响来进行预测。它能够用于具备趋势和 / 或季节性的单变量序列。

平滑办法应用样例

咱们这里将应用来自 sm 模块的数据集。它依据工夫显示夏威夷大气中的二氧化碳。记录期间:1958 年 3 月 — 2001 年 12 月

 import itertools
 import warnings
 import matplotlib.pyplot as plt
 import numpy as np
 import pandas as pd
 import statsmodels.api as sm
 from sklearn.metrics import mean_absolute_error
 
 from statsmodels.tsa.holtwinters import ExponentialSmoothing
 from statsmodels.tsa.holtwinters import SimpleExpSmoothing
 from statsmodels.tsa.seasonal import seasonal_decompose
 # to split the time series into components
 import statsmodels.tsa.api as smt
 
 warnings.filterwarnings('ignore')data = sm.datasets.co2.load_pandas()
 
 # A dataset from the sm module
 y = data.data
 # target varible
 y

索引为日期,通过查看日期,能够确定这个数据集是每周一次!如果须要查看每月数据则须要进行转换。

 y = y['co2'].resample('MS').mean()
 y.isnull().sum()

数据中有缺失值。然而工夫序列中的缺失值无奈用平均值,中位数填充。所以咱们这里用在它之前或之后的值填充,它也能够通过均匀它之前和之后的值来填充。

 y = y.fillna(y.bfill())
 y.isnull().sum() # output => 0
 
 y.plot(figsize=(10, 7))
 plt.show()

能够看到在这个序列中有一个趋势,也蕴含了一个很显著的季节性。

咱们这里应用 Hold-out 办法是因为模型在训练模型时往往会适度拟合,无论是工夫序列还是深度学习办法。因而,咱们应该应用一些办法来避免这种状况并更精确地评估谬误并验证模型。这就是咱们将数据集拆分为训练测试的起因。

 train = y[:'1997-12-01']
 print("Lenght of train", len(train))  # 478 months
 test = y['1998-01-01':]
 print("Lenght of test", len(test))  # 48 months

1、SES

SES = Level (Unsuccessful if Trend and Seasonality).

这里有一个“smoothing_level”参数。如果没有输出 smoothing_level,办法也不会报错出错。这是因为前面还会有更多的参数,所以这里先临时设定一个值

 ses_model = SimpleExpSmoothing(train).fit(smoothing_level=0.5)
 y_pred = ses_model.forecast(48)

可视化函数

 def plot_co2(train, test, y_pred, title):
     mae = mean_absolute_error(test, y_pred)
     train["1985":].plot(legend=True, label="TRAIN", title=f"{title}, MAE: {round(mae,2)}")
     test.plot(legend=True, label="TEST", figsize=(6, 4))
     y_pred.plot(legend=True, label="PREDICTION")
     plt.show()
 
 plot_co2(train, test, y_pred, "Single Exponential Smoothing")

能够看到 SES 预测的并不好,这是因为咱们的序列中蕴含了季节性相干的信息, 咱们再做一个微调

 def ses_optimizer(train, alphas, step=48):  # step for length of test set
 
     best_alpha, best_mae = None, float("inf")
 
     for alpha in alphas:
         ses_model = SimpleExpSmoothing(train).fit(smoothing_level=alpha)
         y_pred = ses_model.forecast(step)
         mae = mean_absolute_error(test, y_pred)
 
         if mae < best_mae:
             best_alpha, best_mae = alpha, mae
 
         print("alpha:", round(alpha, 2), "mae:", round(mae, 4))
     print("best_alpha:", round(best_alpha, 2), "best_mae:", round(best_mae, 4))
     return best_alpha, best_mae
 
 alphas = np.arange(0.8, 1, 0.01)

从 0.8 到 1 的 0.01 步生成 alpha。如果违心,能够以 0.1 为增量从 0.1 写到 1,但 SES 曾经是一个弱模型,靠近历史实在值将产生更好的后果,所以咱们从 0.8 开始。

 best_alpha, best_mae = ses_optimizer(train, alphas) 
 # best_alpha: 0.99 best_mae: 4.5451

最初的模型参数是这样的:

 ses_model = SimpleExpSmoothing(train).fit(smoothing_level=best_alpha)
 y_pred = ses_model.forecast(48)
 plot_co2(train, test, y_pred, "Single Exponential Smoothing")

MAE 有所升高然而其实还是不怎么样, 对吧.

2、DES

DES: Level (SES) + Trend

这些级数能够是乘法也能够是加法, 例如

  • y(t) = Level + Trend + Seasonality + Noise => 加
  • y(t) = Level Trend Seasonality * Noise => 乘

在乘法运算中,构造的变动更具依赖性。如果季节性和残差与趋势无关,则该级数是可加的。如果季节性和残差依据趋势造成,则是相乘的。

季节性和残差随机散布在 0 左近。所以能够确定趋势并没有影响残差,所以这个咱们确定这个级数是加性的。

通常咱们应该建设两个模型,并决定应用有较低的误差的模型。然而在这里确认残差和季节性与趋势无关。所以间接应用“add”参数。

 des_model = ExponentialSmoothing(train, trend="add").fit(smoothing_level=0.5, smoothing_trend=0.5)
 y_pred = des_model.forecast(48)
 plot_co2(train, test, y_pred, "Double Exponential Smoothing")

尽管后果看起来不错,曾经捕捉到了趋势,但因为不足季节性,这个预计也不太好, 咱们持续优化

 def des_optimizer(train, alphas, betas, step=48):
     best_alpha, best_beta, best_mae = None, None, float("inf")
     for alpha in alphas:
         for beta in betas:
             des_model = ExponentialSmoothing(train, trend="add").fit(smoothing_level=alpha, smoothing_slope=beta)
             y_pred = des_model.forecast(step)
             mae = mean_absolute_error(test, y_pred)
             if mae < best_mae:
                 best_alpha, best_beta, best_mae = alpha, beta, mae
             print("alpha:", round(alpha, 2), "beta:", round(beta, 2), "mae:", round(mae, 4))
     print("best_alpha:", round(best_alpha, 2), "best_beta:", round(best_beta, 2), "best_mae:", round(best_mae, 4))
     return best_alpha, best_beta, best_maealphas = np.arange(0.01, 1, 0.10)
 betas = np.arange(0.01, 1, 0.10)
 
 best_alpha, best_beta, best_mae = des_optimizer(train, alphas, betas)

最终的模型参数:

 final_des_model = ExponentialSmoothing(train, trend="add").fit(smoothing_level=best_alpha, smoothing_slope=best_beta)
 y_pred = final_des_model.forecast(48)
 plot_co2(train, test, y_pred, "Double Exponential Smoothing")

MAE 小了很多,趋势也很安稳,然而没有季节性

3、TES

TES = SES + DES + Seasonality

当咱们输出趋势和季节性作为参数时,将应用 TES。

 tes_model = ExponentialSmoothing(train,
                                  trend="add",
                                  seasonal="add",
                                  seasonal_periods=12)
 .fit(smoothing_level=0.5,smoothing_slope=0.5,smoothing_seasonal=0.5)

咱们把季节性周期 设为 12,因为数据集中的年份是由月份决定的。也就是说咱们确定季节性的周期是 12 个月(1 年)

 y_pred = tes_model.forecast(48)
 plot_co2(train, test, y_pred, "Triple Exponential Smoothing")

能够看到,趋势和季节性都有了,咱们持续优化

 alphas = betas = gammas = np.arange(0.20, 1, 0.10)

这里有蕴含了 3 个超参数:

 abg = list(itertools.product(alphas, betas, gammas))
 def tes_optimizer(train, abg, step=48):
     best_alpha, best_beta, best_gamma, best_mae = None, None, None, float("inf")
     for comb in abg:
         tes_model = ExponentialSmoothing(train, trend="add", seasonal="add", seasonal_periods=12).\
             fit(smoothing_level=comb[0], smoothing_slope=comb[1], smoothing_seasonal=comb[2])
         # her satırın 0., 1., 2. elemanlarını seç ve model kur
         y_pred = tes_model.forecast(step)
         mae = mean_absolute_error(test, y_pred)
         if mae < best_mae:
             best_alpha, best_beta, best_gamma, best_mae = comb[0], comb[1], comb[2], mae
         # print([round(comb[0], 2), round(comb[1], 2), round(comb[2], 2), round(mae, 2)])
 
     print("best_alpha:", round(best_alpha, 2), "best_beta:", round(best_beta, 2), "best_gamma:", round(best_gamma, 2),
           "best_mae:", round(best_mae, 4))
 
     return best_alpha, best_beta, best_gamma, best_mae
 best_alpha, best_beta, best_gamma, best_mae = tes_optimizer(train, abg)
 
 # best_alpha: 0.8 best_beta: 0.5 best_gamma: 0.7 best_mae: 0.606

下面给出的参数 smoothing_slope 与 smoothing_trend 雷同。此处应用 smoothing_trend 来表明它们是雷同的,看看后果:

 final_tes_model = ExponentialSmoothing(train, trend="add", seasonal="add", seasonal_periods=12).\
             fit(smoothing_level=best_alpha, smoothing_trend=best_beta, smoothing_seasonal=best_gamma)
             
 y_pred = final_tes_model.forecast(48)
 plot_co2(train, test, y_pred, "Triple Exponential Smoothing")

无论从 MAE 还是可视化的后果看,都很不错,对吧。

总结

以上就是三个平滑办法的介绍,本文的残缺代码:

https://avoid.overfit.cn/post/8b39befe01644d1388b307d7a81fc50d

作者:Furkan Akdağ

正文完
 0