tbl_time(index = index)
df
# A time tibble: 1,800 x 3
# Index: index
index value key
<date> <dbl> <chr>
1 1849-06-01 81.1 training
2 1849-07-01 78 training
3 1849-08-01 67.7 training
4 1849-09-01 93.7 training
5 1849-10-01 71.5 training
6 1849-11-01 99 training
7 1849-12-01 97 training
8 1850-01-01 78 training
9 1850-02-01 89.4 training
10 1850-03-01 82.6 training
# ... with 1,790 more rows
用 recipe
做数据预处理
LSTM 算法通常要求输入数据经过中心化并标度化。我们可以使用 recipe
包预处理数据。我们用 step_sqrt
来转换数据以减少异常值的影响,再结合 step_center
和 step_scale
对数据进行中心化和标度化。最后,数据使用 bake()
函数实现处理转换。
rec_obj <- recipe(
value ~ ., df) %>%
step_sqrt(value) %>%
step_center(value) %>%
step_scale(value) %>%
prep()
df_processed_tbl <- bake(rec_obj, df)
df_processed_tbl
# A tibble: 1,800 x 3
index value key
<date> <dbl> <fct>
1 1849-06-01 0.714 training
2 1849-07-01 0.660 training
3 1849-08-01 0.473 training
4 1849-09-01 0.922 training
5 1849-10-01 0.544 training
6 1849-11-01 1.01 training
7 1849-12-01 0.974 training
8 1850-01-01 0.660 training
9 1850-02-01 0.852 training
10 1850-03-01 0.739 training
# ... with 1,790 more rows
接着,记录中心化和标度化的信息,以便在建模完成之后可以将数据逆向转换回去。平方根转换可以通过乘方运算逆转回去,但要在逆转中心化和标度化之后。
center_history <- rec_obj$steps[[2]]$means["value"]
scale_history <- rec_obj$steps[[3]]$sds["value"]
c("center" = center_history, "scale" = scale_history)
center.value scale.value
6.694468 3.238935
调整数据形状
Keras LSTM 希望输入和目标数据具有特定的形状。输入必须是 3 维数组,维度大小为 num_samples
、num_timesteps
、num_features
。
这里,num_samples
是集合中观测的数量。这将以每份 batch_size
大小的分量分批提供给模型。第二个维度 num_timesteps
是我们上面讨论的隐含状态的长度。最后,第三个维度是我们正在使用的预测变量的数量。对于单变量时间序列,这是 1。
隐含状态的长度应该选择多少?这通常取决于数据集和我们的目标。如果我们提前一步预测,即仅预测下个月,我们主要关注的是选择一个状态长度,以便学习数据中存在的任何模式。
现在说我们想要预测 12 个月的数据,就像 SILSO(世界数据中心,用于生产、保存和传播国际太阳黑子观测)所做的。我们通过 Keras 实现这一目标的方法是使 LSTM 隐藏状态与后续输出有相同的长度。因此,如果我们想要产生 12 个月的预测,我们的 LSTM 隐藏状态长度应该是 12。(译注:原始数据的周期大约是 10 到 11 年,使用相邻两年的数据不能涵盖一个完整的周期,致使模型无法学习到和周期有关的模式,最终表现可能不佳。)
然后使用 time_distributed()
包装器将这 12 个时间步连接到 12 个线性预测器单元。该包装器的任务是将相同的计算(即,相同的权重矩阵)应用于它接收的每个状态输入。
目标数组的格式应该是什么?由于我们在这里预测了几个时间步,目标数据也需要是 3 维的。维度 1 同样是批量维度,维度 2 同样对应于时间步(被预测的),维度 3 是包装层的大小。在我们的例子中,包装层是单个单元的 layer_dense()
,因为我们希望每个时间点只有一个预测。
所以,让我们调整数据形状。这里的主要动作是用滑动窗口创建长度 12 的输入,后续 12 个观测作为输出。举个更简单的例子,假设我们的输入是从 1 到 10 的数字,我们选择的序列长度(状态大小)是 4,下面就是我们希望我们的训练输入看起来的样子:
1,2,3,4
2,3,4,5
3,4,5,6
相应的目标数据:
5,6,7,8
6,7,8,9
7,8,9,10
我们将定义一个简短的函数,对给定的数据集进行调整。最后,我们形式上添加了需要的第三个维度(即使在我们的例子中该维度的大小为 1)。
# these variables are being defined just because of the order in which
# we present things in this post (first the data, then the model)
# they will be superseded by FLAGS$n_timesteps, FLAGS$batch_size and n_predictions
# in the following snippet
n_timesteps <- 12
n_predictions <- n_timesteps
batch_size <- 10
# functions used
build_matrix <- function(tseries,
overall_timesteps)
{
t(sapply(
1:(length(tseries) - overall_timesteps + 1),
function(x) tseries[x:(x + overall_timesteps - 1)]))
}
reshape_X_3d <- function(X)
{
dim(X) <- c(dim(X)[1], dim(X)[2], 1)
X
}
# extract values from data frame
train_vals <- df_processed_tbl %>%
filter(key == "training") %>%
select(value) %>%
pull()
valid_vals <- df_processed_tbl %>%
filter(key == "validat