11.4 Bootstrapping 和 bagging

Bootstrapping 时间序列

在上一节和 3.5 节中,我们描述了如何 bootstrap 时间序列的残差项,以便使用模型模拟序列的未来值。

更一般地,我们可以使用另一种类型的 bootstrap 生成与观测时间序列数据相似的新的时间序列。

首先,对时间序列进行 Box-Cox 变换,然后使用 STL 将其分解为趋势,季节变动和剩余成分。之后,我们使用另一种bootstrap获得 boostrap 后的剩余成分序列。由于在 STL 得到的剩余成分中可能存在自相关,因此我们不能简单地使用 3.5 节中描述的重抽取过程。相反,我们使用“ blocked bootstrap ”,在这里随机选择时间序列的连续部分,并将其连接在一起。这些 bootstrap 得到的剩余成分和趋势成分和季节成分相加,使用 Box-Cox 变换的逆变换得到变化后的时间序列。

11.16描述了在2000年1月至2013年8月期间冰岛零售借记卡的月支出。

bootseries <- bld.mbb.bootstrap(debitcards, 10) %>%
  as.data.frame() %>% ts(start=2000, frequency=12)
autoplot(debitcards) +
  autolayer(bootseries, colour=TRUE) +
  autolayer(debitcards, colour=FALSE) +
  xlab("时间") + ylab("Bootstrap 序列") +
  guides(colour="none") +
  theme(text = element_text(family = "STHeiti"))
冰岛零售借记卡每月支出的10个bootstrap结果。

图 11.16: 冰岛零售借记卡每月支出的10个bootstrap结果。

这种类型的 bootstrap 有两个作用。首先,它可以帮助我们更好的衡量预测的不确定性,其次通过它我们可以使用“ bagging ”改进点预测。

bootstrap 序列的预测区间

几乎所有时间序列模型得到的预测区间都很窄。这是一个众所周知的现象,它的出现是因为这些模型都没有考虑到所有的不确定因素。在 Hyndman, Koehler, Snyder, & Grose (2002) 中,我们通过计算预测区间对测试数据的实际覆盖率来衡量这个问题的严重性。我们发现,对于 ETS 模型,95%的预测区间可能只提供了71%至87%的覆盖率。这之间的差异是由于没有考虑到部分不确定因素而引起的。

使用时间序列模型进行预测至少有四种不确定性来源:

  1. 随机误差项;
  2. 参数估计;
  3. 模型的选择;
  4. 将历史数据生成过程推广到未来。

当我们根据时间序列模型产生预测区间时,我们通常只考虑第一个不确定性来源。哪怕我们忽略了模型的不确定性和由于数据生成过程的变化引起的不确定性(来源3和来源4),仅仅允许参数以及随机误差项的不确定性(来源1和来源2),除了一些简单的特殊情况,也无法计算出预测区间。

我们可以使用 bootstrap 得到的时间序列来尝试解决这个问题。我们使用debitcards数据来演示这个过程。首先,我们使用上面描述的 block-bootstrap 来模拟许多与原始数据相似的时间序列。

nsim <- 1000L
sim <- bld.mbb.bootstrap(debitcards, nsim)

对于每一个模拟得到的时间序列,我们拟合一个 ETS 模型,并从该模型中模拟一个样本路径。每种情况下都可能会选择不同的 ETS 模型,由于模拟得到的时间序列都非常相似,因此它们很有可能会选择相同的模型。但是,得到的参数估计结果会有所不同。因此,模拟得到的样本路径可以考虑到模型的不确定性,参数的不确定性以及随机误差项的不确定性。当然,由于需要对大量的时间序列进行建模,这个过程需要花费一定的时间。

h <- 36L
future <- matrix(0, nrow=nsim, ncol=h)
for(i in seq(nsim))
  future[i,] <- simulate(ets(sim[[i]]), nsim=h)

最后,我们利用这些模拟样本路径的均值和分位数来计算点预测和预测区间。

start <- tsp(debitcards)[2]+1/12
simfc <- structure(list(
    mean = ts(colMeans(future), start=start, frequency=12),
    lower = ts(apply(future, 2, quantile, prob=0.025), 
               start=start, frequency=12),
    upper = ts(apply(future, 2, quantile, prob=0.975),
               start=start, frequency=12),
    level=95),
  class="forecast")

这些预测区间会比直接从原始数据拟合的 ETS 模型计算的预测区间更宽。

etsfc <- forecast(ets(debitcards), h=h, level=95)
autoplot(debitcards) +
  ggtitle("冰岛零售借记卡月支出") +
  xlab("年") + ylab("百万 ISK") +
  autolayer(simfc, series="Simulated") +
  autolayer(etsfc, series="ETS") +
  theme(text = element_text(family = "STHeiti"))
使用 ETS 模型对冰岛借记卡使用情况进行预测,包含普通预测区间和根据模拟序列计算的可以考虑模型和参数不确定性的预测区间。

图 11.17: 使用 ETS 模型对冰岛借记卡使用情况进行预测,包含普通预测区间和根据模拟序列计算的可以考虑模型和参数不确定性的预测区间。

Baggged 预测

Bootstrap 得到的时间序列的另一个用途是用来提高预测的准确性。如果我们对每一个 Bootstrap 时间序列进行预测,并对预测结果进行平均,那么我们会得到相比直接对原始时间序列进行预测更好的预测结果。这个过程称为“ bagging ”,它代表" bootstrap aggregating "。

我们可以简单地对先前得到的模拟未来样本路径进行平均。然而,如果我们只想提高点预测的准确性,而不是获得改进的预测区间时,直接对每个时间序列的点预测进行平均会更高效。速度更快的原因在于我们不再需要产生那么多的模拟序列。

我们将使用ets()对每个时间序列进行预测。图11.18 展示了以这种方式得到的10个预测值。

sim <- bld.mbb.bootstrap(debitcards, 10) %>%
  as.data.frame() %>%
  ts(frequency=12, start=2000)
fc <- purrr::map(as.list(sim), 
           function(x){forecast(ets(x))$mean}) %>%
      as.data.frame() %>%
      ts(frequency=12, start=start)
autoplot(debitcards) +
  autolayer(sim, colour=TRUE) +
  autolayer(fc, colour=TRUE) +
  autolayer(debitcards, colour=FALSE) +
  xlab("时间") + ylab("Bootstrap 序列") +
  guides(colour="none") +
  theme(text = element_text(family = "STHeiti"))
使用 ETS 模型获得的10个 bootstrap 序列的预测。

图 11.18: 使用 ETS 模型获得的10个 bootstrap 序列的预测。

这些预测的平均值给出了原始数据的 bagged 预测。整个过程可以使用baggedETS()函数来处理,默认情况会使用100个 bootstrap 序列,对于月度数据,通过block bootstrap获取残差的block长度默认为24。预测结果如图11.19所示。

etsfc <- debitcards %>% ets() %>% forecast(h=36)
baggedfc <- debitcards %>% baggedETS() %>% forecast(h=36)
autoplot(debitcards) +
  autolayer(baggedfc$mean, series="BaggedETS") +
  autolayer(etsfc$mean, series="ETS") +
  guides(colour=guide_legend(title="Forecasts")) +
  xlab("时间") + ylab("借记卡月支出")
比较 bagged ETS 预测(100个 bootstrap 预测的平均值)与普通 ETS 预测。

图 11.19: 比较 bagged ETS 预测(100个 bootstrap 预测的平均值)与普通 ETS 预测。

  theme(text = element_text(family = "STHeiti"))
#> List of 1
#>  $ text:List of 11
#>   ..$ family       : chr "STHeiti"
#>   ..$ face         : NULL
#>   ..$ colour       : NULL
#>   ..$ size         : NULL
#>   ..$ hjust        : NULL
#>   ..$ vjust        : NULL
#>   ..$ angle        : NULL
#>   ..$ lineheight   : NULL
#>   ..$ margin       : NULL
#>   ..$ debug        : NULL
#>   ..$ inherit.blank: logi FALSE
#>   ..- attr(*, "class")= chr [1:2] "element_text" "element"
#>  - attr(*, "class")= chr [1:2] "theme" "gg"
#>  - attr(*, "complete")= logi FALSE
#>  - attr(*, "validate")= logi TRUE

在这个例子中,两者之间几乎没什么区别。 Bergmeir, Hyndman, & Benítez (2016) 提出,平均而言,bagging给出的预测比直接使用ets()函数得到的预测结果要好。当然,因为需要进行更多的计算因此速度会较慢。

参考文献

Bergmeir, C., Hyndman, R. J., & Benítez, J. M. (2016). Bagging exponential smoothing methods using STL decomposition and Box-Cox transformation. International Journal of Forecasting, 32(2), 303–312. https://robjhyndman.com/publications/bagging-ets/

Hyndman, R. J., Koehler, A. B., Snyder, R. D., & Grose, S. (2002). A state space framework for automatic forecasting using exponential smoothing methods. International Journal of Forecasting, 18(3), 439–454. https://robjhyndman.com/publications/hksg/