Nós pegamos onde o primeiro publish desta série nos deixou: enfrentar a tarefa de previsão de séries temporais em várias etapas.
Nossa primeira tentativa foi uma espécie de solução alternativa. O modelo foi treinado para fornecer uma única previsão, correspondente ao próximo momento. Assim, se precisássemos de uma previsão mais longa, tudo o que poderíamos fazer seria usar essa previsão e alimentá-la de volta ao modelo, movendo a sequência de entrada em um valor (de ((x_{tn}, …, x_t)) para ((x_{tn-1}, …, x_{t+1}))dizer).
Em contraste, o novo modelo será concebido – e treinado – para prever um número configurável de observações de uma só vez. A arquitetura ainda será básica – tão básica quanto possível, dada a tarefa – e, portanto, poderá servir como base para tentativas posteriores.
Trabalhamos com os mesmos dados de antes, vic_elec
de tsibbledata
.
Em comparação com a última vez, porém, o dataset
a aula tem que mudar. Embora, anteriormente, para cada merchandise do lote o destino (y
) period um valor único, agora é um vetor, assim como a entrada, x
. E assim como n_timesteps
foi (e ainda é) usado para especificar o comprimento da sequência de entrada, agora existe um segundo parâmetro, n_forecast
para configurar o tamanho do destino.
Em nosso exemplo, n_timesteps
e n_forecast
são definidos com o mesmo valor, mas não há necessidade de que seja esse o caso. Você poderia igualmente treinar em sequências de uma semana e depois prever os desenvolvimentos em um único dia ou mês.
Além do fato de que .getitem()
agora retorna um vetor para y
assim como x
não há muito a ser dito sobre a criação de conjuntos de dados. Aqui está o código completo para configurar o pipeline de entrada de dados:
n_timesteps <- 7 * 24 * 2
n_forecast <- 7 * 24 * 2
batch_size <- 32
vic_elec_get_year <- perform(yr, month = NULL) {
vic_elec %>%
filter(yr(Date) == yr, month(Date) == if (is.null(month)) month(Date) else month) %>%
as_tibble() %>%
choose(Demand)
}
elec_train <- vic_elec_get_year(2012) %>% as.matrix()
elec_valid <- vic_elec_get_year(2013) %>% as.matrix()
elec_test <- vic_elec_get_year(2014, 1) %>% as.matrix()
train_mean <- imply(elec_train)
train_sd <- sd(elec_train)
elec_dataset <- dataset(
identify = "elec_dataset",
initialize = perform(x, n_timesteps, n_forecast, sample_frac = 1) {
self$n_timesteps <- n_timesteps
self$n_forecast <- n_forecast
self$x <- torch_tensor((x - train_mean) / train_sd)
n <- size(self$x) - self$n_timesteps - self$n_forecast + 1
self$begins <- type(pattern.int(
n = n,
dimension = n * sample_frac
))
},
.getitem = perform(i) {
begin <- self$begins(i)
finish <- begin + self$n_timesteps - 1
pred_length <- self$n_forecast
checklist(
x = self$x(begin:finish),
y = self$x((finish + 1):(finish + pred_length))$squeeze(2)
)
},
.size = perform() {
size(self$begins)
}
)
train_ds <- elec_dataset(elec_train, n_timesteps, n_forecast, sample_frac = 0.5)
train_dl <- train_ds %>% dataloader(batch_size = batch_size, shuffle = TRUE)
valid_ds <- elec_dataset(elec_valid, n_timesteps, n_forecast, sample_frac = 0.5)
valid_dl <- valid_ds %>% dataloader(batch_size = batch_size)
test_ds <- elec_dataset(elec_test, n_timesteps, n_forecast)
test_dl <- test_ds %>% dataloader(batch_size = 1)
O modelo substitui a única camada linear que, no publish anterior, tinha a tarefa de gerar a previsão remaining, por uma pequena rede, completa com duas camadas lineares e – opcional – dropout.
Em ahead()
primeiro aplicamos o RNN, e assim como no publish anterior, utilizamos o outputs
apenas; ou mais especificamente, o output
correspondente ao passo de tempo remaining. (Veja a postagem anterior para um discussão detalhada do que um torch
RNN retorna.)
mannequin <- nn_module(
initialize = perform(sort, input_size, hidden_size, linear_size, output_size,
num_layers = 1, dropout = 0, linear_dropout = 0) {
self$sort <- sort
self$num_layers <- num_layers
self$linear_dropout <- linear_dropout
self$rnn <- if (self$sort == "gru") {
nn_gru(
input_size = input_size,
hidden_size = hidden_size,
num_layers = num_layers,
dropout = dropout,
batch_first = TRUE
)
} else {
nn_lstm(
input_size = input_size,
hidden_size = hidden_size,
num_layers = num_layers,
dropout = dropout,
batch_first = TRUE
)
}
self$mlp <- nn_sequential(
nn_linear(hidden_size, linear_size),
nn_relu(),
nn_dropout(linear_dropout),
nn_linear(linear_size, output_size)
)
},
ahead = perform(x) {
x <- self$rnn(x)
x((1))( ,-1, ..) %>%
self$mlp()
}
)
Para instanciação do modelo, agora temos um parâmetro de configuração adicional, relacionado à quantidade de dropout entre as duas camadas lineares.
internet <- mannequin(
"gru", input_size = 1, hidden_size = 32, linear_size = 512, output_size = n_forecast, linear_dropout = 0
)
# coaching RNNs on the GPU at present prints a warning which will litter
# the console
# see https://github.com/mlverse/torch/points/461
# alternatively, use
# machine <- "cpu"
machine <- torch_device(if (cuda_is_available()) "cuda" else "cpu")
internet <- internet$to(machine = machine)
O procedimento de treinamento permanece completamente inalterado.
optimizer <- optim_adam(internet$parameters, lr = 0.001)
num_epochs <- 30
train_batch <- perform(b) {
optimizer$zero_grad()
output <- internet(b$x$to(machine = machine))
goal <- b$y$to(machine = machine)
loss <- nnf_mse_loss(output, goal)
loss$backward()
optimizer$step()
loss$merchandise()
}
valid_batch <- perform(b) {
output <- internet(b$x$to(machine = machine))
goal <- b$y$to(machine = machine)
loss <- nnf_mse_loss(output, goal)
loss$merchandise()
}
for (epoch in 1:num_epochs) {
internet$prepare()
train_loss <- c()
coro::loop(for (b in train_dl) {
loss <-train_batch(b)
train_loss <- c(train_loss, loss)
})
cat(sprintf("nEpoch %d, coaching: loss: %3.5f n", epoch, imply(train_loss)))
internet$eval()
valid_loss <- c()
coro::loop(for (b in valid_dl) {
loss <- valid_batch(b)
valid_loss <- c(valid_loss, loss)
})
cat(sprintf("nEpoch %d, validation: loss: %3.5f n", epoch, imply(valid_loss)))
}
# Epoch 1, coaching: loss: 0.65737
#
# Epoch 1, validation: loss: 0.54586
#
# Epoch 2, coaching: loss: 0.43991
#
# Epoch 2, validation: loss: 0.50588
#
# Epoch 3, coaching: loss: 0.42161
#
# Epoch 3, validation: loss: 0.50031
#
# Epoch 4, coaching: loss: 0.41718
#
# Epoch 4, validation: loss: 0.48703
#
# Epoch 5, coaching: loss: 0.39498
#
# Epoch 5, validation: loss: 0.49572
#
# Epoch 6, coaching: loss: 0.38073
#
# Epoch 6, validation: loss: 0.46813
#
# Epoch 7, coaching: loss: 0.36472
#
# Epoch 7, validation: loss: 0.44957
#
# Epoch 8, coaching: loss: 0.35058
#
# Epoch 8, validation: loss: 0.44440
#
# Epoch 9, coaching: loss: 0.33880
#
# Epoch 9, validation: loss: 0.41995
#
# Epoch 10, coaching: loss: 0.32545
#
# Epoch 10, validation: loss: 0.42021
#
# Epoch 11, coaching: loss: 0.31347
#
# Epoch 11, validation: loss: 0.39514
#
# Epoch 12, coaching: loss: 0.29622
#
# Epoch 12, validation: loss: 0.38146
#
# Epoch 13, coaching: loss: 0.28006
#
# Epoch 13, validation: loss: 0.37754
#
# Epoch 14, coaching: loss: 0.27001
#
# Epoch 14, validation: loss: 0.36636
#
# Epoch 15, coaching: loss: 0.26191
#
# Epoch 15, validation: loss: 0.35338
#
# Epoch 16, coaching: loss: 0.25533
#
# Epoch 16, validation: loss: 0.35453
#
# Epoch 17, coaching: loss: 0.25085
#
# Epoch 17, validation: loss: 0.34521
#
# Epoch 18, coaching: loss: 0.24686
#
# Epoch 18, validation: loss: 0.35094
#
# Epoch 19, coaching: loss: 0.24159
#
# Epoch 19, validation: loss: 0.33776
#
# Epoch 20, coaching: loss: 0.23680
#
# Epoch 20, validation: loss: 0.33974
#
# Epoch 21, coaching: loss: 0.23070
#
# Epoch 21, validation: loss: 0.34069
#
# Epoch 22, coaching: loss: 0.22761
#
# Epoch 22, validation: loss: 0.33724
#
# Epoch 23, coaching: loss: 0.22390
#
# Epoch 23, validation: loss: 0.34013
#
# Epoch 24, coaching: loss: 0.22155
#
# Epoch 24, validation: loss: 0.33460
#
# Epoch 25, coaching: loss: 0.21820
#
# Epoch 25, validation: loss: 0.33755
#
# Epoch 26, coaching: loss: 0.22134
#
# Epoch 26, validation: loss: 0.33678
#
# Epoch 27, coaching: loss: 0.21061
#
# Epoch 27, validation: loss: 0.33108
#
# Epoch 28, coaching: loss: 0.20496
#
# Epoch 28, validation: loss: 0.32769
#
# Epoch 29, coaching: loss: 0.20223
#
# Epoch 29, validation: loss: 0.32969
#
# Epoch 30, coaching: loss: 0.20022
#
# Epoch 30, validation: loss: 0.33331
Pela forma como a perda diminui no conjunto de treinamento, concluímos que sim, o modelo está aprendendo alguma coisa. Provavelmente continuaria melhorando por ainda algumas épocas. No entanto, vemos menos melhorias no conjunto de validação.
Naturalmente, agora estamos curiosos sobre as previsões dos conjuntos de testes. (Lembre-se, para os testes escolhemos o mês “particularmente difícil” de janeiro de 2014 – particularmente difícil devido a uma onda de calor que resultou numa procura excepcionalmente elevada.)
Sem nenhum loop a ser codificado, a avaliação agora se torna bastante simples:
internet$eval()
test_preds <- vector(mode = "checklist", size = size(test_dl))
i <- 1
coro::loop(for (b in test_dl) {
enter <- b$x
output <- internet(enter$to(machine = machine))
preds <- as.numeric(output)
test_preds((i)) <- preds
i <<- i + 1
})
vic_elec_jan_2014 <- vic_elec %>%
filter(yr(Date) == 2014, month(Date) == 1)
test_pred1 <- test_preds((1))
test_pred1 <- c(rep(NA, n_timesteps), test_pred1, rep(NA, nrow(vic_elec_jan_2014) - n_timesteps - n_forecast))
test_pred2 <- test_preds((408))
test_pred2 <- c(rep(NA, n_timesteps + 407), test_pred2, rep(NA, nrow(vic_elec_jan_2014) - 407 - n_timesteps - n_forecast))
test_pred3 <- test_preds((817))
test_pred3 <- c(rep(NA, nrow(vic_elec_jan_2014) - n_forecast), test_pred3)
preds_ts <- vic_elec_jan_2014 %>%
choose(Demand) %>%
add_column(
mlp_ex_1 = test_pred1 * train_sd + train_mean,
mlp_ex_2 = test_pred2 * train_sd + train_mean,
mlp_ex_3 = test_pred3 * train_sd + train_mean) %>%
pivot_longer(-Time) %>%
update_tsibble(key = identify)
preds_ts %>%
autoplot() +
scale_colour_manual(values = c("#08c5d1", "#00353f", "#ffbf66", "#d46f4d")) +
theme_minimal()

Figura 1: Previsões com uma semana de antecedência para janeiro de 2014.
Evaluate isso com a previsão obtida pelo suggestions das previsões. Os perfis de demanda ao longo do dia parecem muito mais realistas agora. E as fases de extrema demanda? Evidentemente, estes não se reflectem na previsão, tal como não acontece na “técnica do loop”. Na verdade, a previsão permite insights interessantes sobre a personalidade deste modelo: aparentemente, ele realmente gosta de flutuar em torno da média – “prepará-lo” com dados que oscilam em torno de um nível significativamente mais alto, e ele retornará rapidamente à sua zona de conforto.
Vendo como acima fornecemos uma opção para usar o dropout dentro do MLP, você pode estar se perguntando se isso ajudaria nas previsões no conjunto de testes. Acontece que não, em meus experimentos. Talvez isso também não seja tão estranho: como, na ausência de sinais externos (temperatura), a rede deveria saber que uma alta demanda está chegando?
Em nossa análise, podemos fazer uma distinção adicional. Com a primeira semana de previsões, o que vemos é uma falha em antecipar algo que não poderia razoavelmente previstos (dois ou dois dias e meio, digamos, de procura excepcionalmente elevada). No segundo, tudo o que a rede teria de fazer seria permanecer no nível atual e elevado. Será interessante ver como isso é tratado pelas arquiteturas que discutiremos a seguir.
Finalmente, uma ideia adicional que você pode ter tido é: e se usássemos a temperatura como uma segunda variável de entrada? Na verdade, o desempenho do treinamento melhorou, mas nenhum impacto no desempenho foi observado nos conjuntos de validação e teste. Ainda assim, você pode achar o código útil – ele é facilmente estendido para conjuntos de dados com mais preditores. Portanto, reproduzimos em apêndice.
Obrigado por ler!
# Information enter code modified to accommodate two predictors
n_timesteps <- 7 * 24 * 2
n_forecast <- 7 * 24 * 2
vic_elec_get_year <- perform(yr, month = NULL) {
vic_elec %>%
filter(yr(Date) == yr, month(Date) == if (is.null(month)) month(Date) else month) %>%
as_tibble() %>%
choose(Demand, Temperature)
}
elec_train <- vic_elec_get_year(2012) %>% as.matrix()
elec_valid <- vic_elec_get_year(2013) %>% as.matrix()
elec_test <- vic_elec_get_year(2014, 1) %>% as.matrix()
train_mean_demand <- imply(elec_train( , 1))
train_sd_demand <- sd(elec_train( , 1))
train_mean_temp <- imply(elec_train( , 2))
train_sd_temp <- sd(elec_train( , 2))
elec_dataset <- dataset(
identify = "elec_dataset",
initialize = perform(knowledge, n_timesteps, n_forecast, sample_frac = 1) {
demand <- (knowledge( , 1) - train_mean_demand) / train_sd_demand
temp <- (knowledge( , 2) - train_mean_temp) / train_sd_temp
self$x <- cbind(demand, temp) %>% torch_tensor()
self$n_timesteps <- n_timesteps
self$n_forecast <- n_forecast
n <- nrow(self$x) - self$n_timesteps - self$n_forecast + 1
self$begins <- type(pattern.int(
n = n,
dimension = n * sample_frac
))
},
.getitem = perform(i) {
begin <- self$begins(i)
finish <- begin + self$n_timesteps - 1
pred_length <- self$n_forecast
checklist(
x = self$x(begin:finish, ),
y = self$x((finish + 1):(finish + pred_length), 1)
)
},
.size = perform() {
size(self$begins)
}
)
### relaxation equivalent to single-predictor code above
Foto de Mônica Bourgeau sobre Remover respingo