library(tidyverse)
library(magrittr)
library(keras)
library(tidymodels) # Or only load the 'rsample' and recipes on its own
── Attaching packages ───────────────────────────────────────────── tidymodels 0.1.3 ──
✓ broom        0.7.9      ✓ rsample      0.1.0 
✓ dials        0.0.10     ✓ tune         0.1.6 
✓ infer        1.0.0      ✓ workflows    0.2.3 
✓ modeldata    0.1.1      ✓ workflowsets 0.1.0 
✓ parsnip      0.1.7      ✓ yardstick    0.0.8 
✓ recipes      0.1.16     
── Conflicts ──────────────────────────────────────────────── tidymodels_conflicts() ──
x scales::discard()        masks purrr::discard()
x magrittr::extract()      masks tidyr::extract()
x dplyr::filter()          masks stats::filter()
x xts::first()             masks dplyr::first()
x recipes::fixed()         masks stringr::fixed()
x yardstick::get_weights() masks keras::get_weights()
x dplyr::lag()             masks stats::lag()
x xts::last()              masks dplyr::last()
x dials::momentum()        masks TTR::momentum()
x magrittr::set_names()    masks purrr::set_names()
x yardstick::spec()        masks readr::spec()
x recipes::step()          masks stats::step()
• Use tidymodels_prefer() to resolve common conflicts.

3 Intro: Ways to create sequences…

Workshop Stock prediction

Task:

  1. Get some stock data (tip: Use tidyquant)
    • Limit yourself for now to on e stock
    • Limit yourself to one variable (preferably some price data)
  2. Develop a one-step ahead prediction of prices (or their movements)

Load some data

Select a stock abnd load the data

  • We will use the tidyquant package to download stock data
library(tidyquant) # My favorite package to get stock data
Loading required package: lubridate

Attaching package: ‘lubridate’

The following objects are masked from ‘package:base’:

    date, intersect, setdiff, union

Loading required package: PerformanceAnalytics
Loading required package: xts
Loading required package: zoo

Attaching package: ‘zoo’

The following objects are masked from ‘package:base’:

    as.Date, as.Date.numeric


Attaching package: ‘xts’

The following objects are masked from ‘package:dplyr’:

    first, last


Attaching package: ‘PerformanceAnalytics’

The following object is masked from ‘package:graphics’:

    legend

Loading required package: quantmod
Loading required package: TTR
Registered S3 method overwritten by 'quantmod':
  method            from
  as.zoo.data.frame zoo 
══ Need to Learn tidyquant? ═══════════════════════════════════════════════════════════
Business Science offers a 1-hour course - Learning Lab #9: Performance Analysis & Portfolio Optimization with tidyquant!
</> Learn more at: https://university.business-science.io/p/learning-labs-pro </>
library(timetk) 
Registered S3 method overwritten by 'tune':
  method                   from   
  required_pkgs.model_spec parsnip
tickers = c("NVO") # We can also try AAPL etc
            
data_stocks <- tq_get(tickers,
               from = "2000-01-01",
               to = "2021-11-17",
               get = "stock.prices" # What we want to get.... here prices
               )
Warning: `type_convert()` only converts columns of type 'character'.
- `df` has no columns of type 'character'

Some plots for exploration…

data_stocks %>% glimpse()
Rows: 5,505
Columns: 8
$ symbol   <chr> "NVO", "NVO", "NVO", "NVO", "NVO", "NVO", "NVO", "NVO", "NVO", "NVO"…
$ date     <date> 2000-01-03, 2000-01-04, 2000-01-05, 2000-01-06, 2000-01-07, 2000-01…
$ open     <dbl> 2.7000, 2.6775, 2.6325, 2.6275, 2.7400, 2.9025, 2.9275, 2.9200, 2.91…
$ high     <dbl> 2.7050, 2.6900, 2.6350, 2.6950, 2.7525, 2.9750, 2.9300, 2.9250, 2.91…
$ low      <dbl> 2.6700, 2.6475, 2.6025, 2.6275, 2.7000, 2.9025, 2.8850, 2.9150, 2.88…
$ close    <dbl> 2.6900, 2.6775, 2.6275, 2.6700, 2.7400, 2.9650, 2.9150, 2.9250, 2.88…
$ volume   <dbl> 385000, 197500, 210000, 287500, 245000, 530000, 762500, 290000, 1650…
$ adjusted <dbl> 1.841958, 1.833398, 1.799161, 1.828262, 1.876194, 2.030262, 1.996025…
data_stocks %>% head()
data_stocks %>% 
  plot_time_series(date, adjusted)
Warning: closing unused connection 3 (https://query2.finance.yahoo.com/v7/finance/download/NOVO?period1=946684800&period2=1637107200&interval=1d&events=history&crumb=obYTN1Sl/24)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
# # ggplot alternative
# data_stocks %>%
#   ggplot(aes(x = date, y = adjusted,)) +
#   geom_line() +
#   labs(x = 'Date', y = "Adjusted Price") 

Preprocessing

# Limit data
data <- data_stocks %>%
  rename(index = date, value = adjusted) %>%
  select(index, value) %>%
  arrange(index) 
  • It is always easier to model change rather than absolute prices, so we create a variable measuring the percentage change of price instead
# Remodel value as percentage change
data %<>%
  distinct(index, .keep_all = TRUE) %>%
  tidyr::fill(value, .direction = "downup") %>%
  mutate(value = (value - lag(value,1)) / lag(value,1) ) %>%
  drop_na()
data %>%
  ggplot(aes(x = index, y = value)) +
  geom_line() +
  labs(x = 'Date', y = "Price change in pct") 

data %>%
    plot_acf_diagnostics(date, value)

Train & Test split

  • We do a time-series split which keeps the sequencing of the data
# We use time_splits here to maintain the sequences
data_split <- data %>% initial_time_split(prop = 0.75)
data_train <- data_split %>% training()
data_test <- data_split %>% testing()
  • Lets see from where till when the train/test samples are
# See ehat we got
data_train %>% pull(index) %>% min()
[1] "2000-01-04"
data_train %>% pull(index) %>% max()
[1] "2016-06-01"
data_test %>% pull(index) %>% min()
[1] "2016-06-02"
data_test %>% pull(index) %>% max()
[1] "2021-11-16"
data_train %>% mutate(split = 'training') %>%
  bind_rows(data_test %>% mutate(split = 'testing')) %>%
  ggplot(aes(x = index, y = value, col = split)) +
  geom_line() 

Define a reciepe

  • We only apply min-max scaling herewith step_range
data_recipe <- data_train %>%
  recipe(value ~ .) %>% 
  step_normalize(value) %>%
  step_arrange(index) %>%
  prep()
  • We save the min and max to rescale later again
# Preserve the values for later (to reconstruct original values)
prep_history <- tibble(
  mean = data_recipe$steps[[1]]$means,
  sds = data_recipe$steps[[1]]$sds
)
prep_history

Get processedv train & test data

  • We now create a x and y split. Since we here always predict the next observation, that’s easy. We will just set y= lead(x, 1)
  • We replace the last missing observation with the lagged value
# Number of lags
n_lag = 1

# Train data
x_train <- data_recipe %>% juice() 

y_train <- data_recipe %>%  juice() %>%
  mutate(value = value %>% lead(n_lag)) %>%
  tidyr::fill(value, .direction = "downup") 

# And the same for the test data
x_test <- data_recipe %>% bake(data_test) 

y_test <- data_recipe %>%  bake(data_test) %>%  
  mutate(value = value %>% lead(n_lag)) %>%
  tidyr::fill(value, .direction = "downup") 

Transform to a 3d tensor for keras

# TRansforming the x sequence to a 3d tensor (necessary for LSTMs)
x_train_arr <- x_train %>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1, 1))
x_test_arr <- x_test %>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1, 1))

y_train_arr <- y_train %>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1))
y_test_arr <- y_test %>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1))
x_train_arr %>% dim()
[1] 4128    1    1
x_train_arr %>% glimpse()
 num [1:4128, 1, 1] -0.29 -1.011 0.781 1.298 4.173 ...

Setting up the LSTM

LSTM

Define model

model <- keras_model_sequential() %>%
  # LSTM layer
  layer_lstm(units = 32, 
             dropout=0.2, 
             recurrent_dropout=0.2,
             input_shape = c(1, 1), # dim(x_train_arr)[-1], # n timesteps, n feutures
             return_sequences = TRUE) %>%
  # LSTM layer
  layer_lstm(units = 32, 
             dropout=0.2, 
             recurrent_dropout=0.2,
             return_sequences = FALSE) %>%
  # A DENSE LAYER IN BETWEEN
  layer_dense(units = 32, activation = 'relu') %>%
  #Final prediction layer
  layer_dense(units = 1, activation = 'linear')
# Compile model
model %>% 
  compile(loss = "mse", 
          metric = 'mse', 
          optimizer = optimizer_adam())
model %>% summary()
Model: "sequential_2"
___________________________________________________________________________________________________
 Layer (type)                               Output Shape                            Param #        
===================================================================================================
 lstm_4 (LSTM)                              (None, 1, 32)                           4352           
                                                                                                   
 lstm_3 (LSTM)                              (None, 32)                              8320           
                                                                                                   
 dense_5 (Dense)                            (None, 32)                              1056           
                                                                                                   
 dense_4 (Dense)                            (None, 1)                               33             
                                                                                                   
===================================================================================================
Total params: 13,761
Trainable params: 13,761
Non-trainable params: 0
___________________________________________________________________________________________________

Fitting the model

  • Next, we can fit our LSTM using a for loop (we do this to manually reset states).
  • We set shuffle = FALSE to preserve sequences
hist_model <- model %>% fit(x          = x_train_arr, 
                            y          = y_train_arr, 
                            epochs     = 10,
                            verbose    = TRUE, 
                            batch_size = 64,
                            validation_split = 0.25, 
                            shuffle    = FALSE)
Epoch 1/10

 1/49 [..............................] - ETA: 3:37 - loss: 2.3393 - mse: 2.3393
16/49 [========>.....................] - ETA: 0s - loss: 1.6075 - mse: 1.6075  
28/49 [================>.............] - ETA: 0s - loss: 1.1546 - mse: 1.1546
42/49 [========================>.....] - ETA: 0s - loss: 1.1615 - mse: 1.1615
49/49 [==============================] - 5s 4ms/step - loss: 1.1273 - mse: 1.1273

49/49 [==============================] - 6s 25ms/step - loss: 1.1273 - mse: 1.1273 - val_loss: 0.6197 - val_mse: 0.6197
Epoch 2/10

 1/49 [..............................] - ETA: 0s - loss: 2.3410 - mse: 2.3410
13/49 [======>.......................] - ETA: 0s - loss: 1.8175 - mse: 1.8175
24/49 [=============>................] - ETA: 0s - loss: 1.2626 - mse: 1.2626
37/49 [=====================>........] - ETA: 0s - loss: 1.2325 - mse: 1.2325
49/49 [==============================] - 0s 4ms/step - loss: 1.1257 - mse: 1.1257

49/49 [==============================] - 0s 8ms/step - loss: 1.1257 - mse: 1.1257 - val_loss: 0.6196 - val_mse: 0.6196
Epoch 3/10

 1/49 [..............................] - ETA: 0s - loss: 2.3439 - mse: 2.3439
16/49 [========>.....................] - ETA: 0s - loss: 1.6042 - mse: 1.6042
28/49 [================>.............] - ETA: 0s - loss: 1.1526 - mse: 1.1526
38/49 [======================>.......] - ETA: 0s - loss: 1.2159 - mse: 1.2159
48/49 [============================>.] - ETA: 0s - loss: 1.1269 - mse: 1.1269
49/49 [==============================] - 0s 4ms/step - loss: 1.1238 - mse: 1.1238

49/49 [==============================] - 0s 8ms/step - loss: 1.1238 - mse: 1.1238 - val_loss: 0.6194 - val_mse: 0.6194
Epoch 4/10

 1/49 [..............................] - ETA: 0s - loss: 2.3396 - mse: 2.3396
13/49 [======>.......................] - ETA: 0s - loss: 1.8181 - mse: 1.8181
25/49 [==============>...............] - ETA: 0s - loss: 1.2322 - mse: 1.2322
39/49 [======================>.......] - ETA: 0s - loss: 1.1980 - mse: 1.1980
49/49 [==============================] - 0s 4ms/step - loss: 1.1244 - mse: 1.1244

49/49 [==============================] - 0s 8ms/step - loss: 1.1244 - mse: 1.1244 - val_loss: 0.6195 - val_mse: 0.6195
Epoch 5/10

 1/49 [..............................] - ETA: 0s - loss: 2.3547 - mse: 2.3547
13/49 [======>.......................] - ETA: 0s - loss: 1.8137 - mse: 1.8137
24/49 [=============>................] - ETA: 0s - loss: 1.2605 - mse: 1.2605
35/49 [====================>.........] - ETA: 0s - loss: 1.2059 - mse: 1.2059
45/49 [==========================>...] - ETA: 0s - loss: 1.1266 - mse: 1.1266
49/49 [==============================] - 0s 5ms/step - loss: 1.1226 - mse: 1.1226

49/49 [==============================] - 0s 8ms/step - loss: 1.1226 - mse: 1.1226 - val_loss: 0.6202 - val_mse: 0.6202
Epoch 6/10

 1/49 [..............................] - ETA: 0s - loss: 2.3570 - mse: 2.3570
11/49 [=====>........................] - ETA: 0s - loss: 1.8965 - mse: 1.8965
21/49 [===========>..................] - ETA: 0s - loss: 1.3975 - mse: 1.3975
32/49 [==================>...........] - ETA: 0s - loss: 1.1233 - mse: 1.1233
42/49 [========================>.....] - ETA: 0s - loss: 1.1592 - mse: 1.1592
49/49 [==============================] - 0s 5ms/step - loss: 1.1245 - mse: 1.1245

49/49 [==============================] - 0s 9ms/step - loss: 1.1245 - mse: 1.1245 - val_loss: 0.6199 - val_mse: 0.6199
Epoch 7/10

 1/49 [..............................] - ETA: 0s - loss: 2.3392 - mse: 2.3392
14/49 [=======>......................] - ETA: 0s - loss: 1.7256 - mse: 1.7256
21/49 [===========>..................] - ETA: 0s - loss: 1.3887 - mse: 1.3887
31/49 [=================>............] - ETA: 0s - loss: 1.1203 - mse: 1.1203
42/49 [========================>.....] - ETA: 0s - loss: 1.1551 - mse: 1.1551
49/49 [==============================] - 0s 5ms/step - loss: 1.1201 - mse: 1.1201

49/49 [==============================] - 0s 9ms/step - loss: 1.1201 - mse: 1.1201 - val_loss: 0.6203 - val_mse: 0.6203
Epoch 8/10

 1/49 [..............................] - ETA: 0s - loss: 2.3961 - mse: 2.3961
11/49 [=====>........................] - ETA: 0s - loss: 1.8939 - mse: 1.8939
20/49 [===========>..................] - ETA: 0s - loss: 1.4159 - mse: 1.4159
31/49 [=================>............] - ETA: 0s - loss: 1.1249 - mse: 1.1249
38/49 [======================>.......] - ETA: 0s - loss: 1.2146 - mse: 1.2146
48/49 [============================>.] - ETA: 0s - loss: 1.1260 - mse: 1.1260
49/49 [==============================] - 0s 6ms/step - loss: 1.1229 - mse: 1.1229

49/49 [==============================] - 0s 10ms/step - loss: 1.1229 - mse: 1.1229 - val_loss: 0.6203 - val_mse: 0.6203
Epoch 9/10

 1/49 [..............................] - ETA: 0s - loss: 2.3652 - mse: 2.3652
13/49 [======>.......................] - ETA: 0s - loss: 1.8149 - mse: 1.8149
25/49 [==============>...............] - ETA: 0s - loss: 1.2320 - mse: 1.2320
36/49 [=====================>........] - ETA: 0s - loss: 1.2170 - mse: 1.2170
47/49 [===========================>..] - ETA: 0s - loss: 1.1354 - mse: 1.1354
49/49 [==============================] - 0s 5ms/step - loss: 1.1212 - mse: 1.1212

49/49 [==============================] - 0s 9ms/step - loss: 1.1212 - mse: 1.1212 - val_loss: 0.6205 - val_mse: 0.6205
Epoch 10/10

 1/49 [..............................] - ETA: 0s - loss: 2.3811 - mse: 2.3811
13/49 [======>.......................] - ETA: 0s - loss: 1.8244 - mse: 1.8244
23/49 [=============>................] - ETA: 0s - loss: 1.3081 - mse: 1.3081
33/49 [===================>..........] - ETA: 0s - loss: 1.1167 - mse: 1.1167
43/49 [=========================>....] - ETA: 0s - loss: 1.1569 - mse: 1.1569
49/49 [==============================] - 0s 5ms/step - loss: 1.1253 - mse: 1.1253

49/49 [==============================] - 0s 9ms/step - loss: 1.1253 - mse: 1.1253 - val_loss: 0.6205 - val_mse: 0.6205
hist_model %>% plot()
`geom_smooth()` using formula 'y ~ x'

model %>% evaluate(x_test_arr, y_test_arr)

 1/43 [..............................] - ETA: 17s - loss: 0.6425 - mse: 0.6425
27/43 [=================>............] - ETA: 0s - loss: 0.6133 - mse: 0.6133 
43/43 [==============================] - 0s 2ms/step - loss: 0.6714 - mse: 0.6714

43/43 [==============================] - 0s 2ms/step - loss: 0.6714 - mse: 0.6714
     loss       mse 
0.6713502 0.6713502 

Predicting Stock changes

  • We first predict the output of our test data
model_pred <- model %>% predict(x_test_arr) %>% as.numeric()
  • However, we need to rescale the output. For min-max scaling, this function will do the trick
reverse_norm<- function(x, mean, sds) {
  x_re <- (x * sds) + mean
  return(x_re)
  }
  • We apply it with our data and the saved min and max values from the recipe
eval <- tibble(
  index = data_test %>% pull(index),
  truth = data_test %>% pull(value),
  pred = model_pred %>% reverse_norm(x = ., mean = prep_history$mean, sds = prep_history$sds)
) 
eval %>% 
  pivot_longer(-index) %>%
  ggplot(aes(x = index, y = value, col = name)) +
  geom_line()

Well… soso

Brief intro to working with time sequences and time series generators

Example timeseries:

  • Ok, lets take a brief look at how to work with sequention data i different ways, and prepare them as inputs for an LSTM
  • We, for the sake of illustration, just create a simple sequence with the numbers from 1-100 (its easier to inspect the sequence, in reality we would obviously feed it with different outputs)
data_example
  [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23
 [24]  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46
 [47]  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69
 [70]  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92
 [93]  93  94  95  96  97  98  99 100

Many-to-One predictions

  • In this setup, we will use several periods to predict one subsequent observations.
n_timesteps <- 5  # Define that we would like to have 5 timesteps
batch_size <- 6 # Batch size (somewhat arbitrary)
n_features <- 1 # Number of features. Since we only predict the outcome based on its own sequence, it will be 1
  • We will set up Keras timeseries_generator, which will feed the LSTM (or other architecture) with on-the-fly generated sequences
train_gen <- 
  timeseries_generator(
    data = data_example, # The data we will use to create the sequences.
    targets = data_example, # The putcome data, in this case the same, since we just want to predict the subsequent period
    length = n_timesteps, # How many previous steps in the sequence should be used for the prediction
    sampling_rate = 1, # Should we use every observation in the sequence or skip some?
    stride = 2, # How many steps should be skipped
    shuffle = FALSE, # Should the sequence be shuffled? In time-series prediction, we want to preserve the order of sequences, so always FALSE
    batch_size = batch_size # size of the batches generated. USe this batch size also later in the LSTM
    )
  • Remember, this is a lazy function, meaning it will generate the sequences on-the-fly when they are needed.
  • Therefore, it can not directly be inspected.
train_gen
<keras.preprocessing.sequence.TimeseriesGenerator>
  • However, we can extract single batches and inspect them.
  • This is helpful to get a feeling what the different arguments of the generator do, and to thest that they create the sequence you want.
  • Here, two arrrays will be returned, where the first one is the generated input sequences, the second one the corresponding output.
batch_0 <- train_gen[0]
batch_0
[[1]]
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5
[2,]    3    4    5    6    7
[3,]    5    6    7    8    9
[4,]    7    8    9   10   11
[5,]    9   10   11   12   13
[6,]   11   12   13   14   15

[[2]]
[1]  6  8 10 12 14 16
# create the model
model <- keras_model_sequential()  %>%
  # Add the layer. We will make it as simple as possible here with just one LSTM and an output layer.
  layer_lstm(
    units = 32, 
    batch_input_shape  = c(batch_size, n_timesteps, n_features), # the first layer in a model needs to know the shape of the input data
    #dropout = 0.1,
    #recurrent_dropout = 0.1,
    return_sequences = FALSE, # by default, an LSTM just returns the final state
    stateful = TRUE) %>% 
  # Final output layer
  layer_dense(units = 1)

model %>% compile(loss = 'mse', optimizer = optimizer_adam(), metrics = 'mse')
n_steps <- round((length(data_example) - n_timesteps) / batch_size, 1) 

hist <- model %>% fit_generator(
  generator = train_gen,
  steps_per_epoch = n_steps,
  epochs = 10
  )
Warning in fit_generator(., generator = train_gen, steps_per_epoch = n_steps,  :
  `fit_generator` is deprecated. Use `fit` instead, it now accept generators.
Epoch 1/10

 1/15 [=>............................] - ETA: 22s - loss: 5344.6724 - mse: 5344.6724
14/15 [===========================>..] - ETA: 0s - loss: 3911.8208 - mse: 3911.8208 
15/15 [==============================] - 2s 4ms/step - loss: 3690.8801 - mse: 3690.8801

15/15 [==============================] - 2s 41ms/step - loss: 3690.8801 - mse: 3690.8801
Epoch 2/10

 1/15 [=>............................] - ETA: 0s - loss: 3683.7498 - mse: 3683.7498
14/15 [===========================>..] - ETA: 0s - loss: 3854.2834 - mse: 3854.2834
15/15 [==============================] - 0s 4ms/step - loss: 3634.7844 - mse: 3634.7844

15/15 [==============================] - 0s 12ms/step - loss: 3634.7844 - mse: 3634.7844
Epoch 3/10

 1/15 [=>............................] - ETA: 0s - loss: 7048.9839 - mse: 7048.9839
15/15 [==============================] - 0s 4ms/step - loss: 3524.8340 - mse: 3524.8340

15/15 [==============================] - 0s 12ms/step - loss: 3524.8340 - mse: 3524.8340
Epoch 4/10

 1/15 [=>............................] - ETA: 0s - loss: 285.9961 - mse: 285.9961
15/15 [==============================] - 0s 3ms/step - loss: 3449.3716 - mse: 3449.3716

15/15 [==============================] - 1s 39ms/step - loss: 3449.3716 - mse: 3449.3716
Epoch 5/10

 1/15 [=>............................] - ETA: 0s - loss: 4960.4878 - mse: 4960.4878
13/15 [=========================>....] - ETA: 0s - loss: 3189.6267 - mse: 3189.6267
15/15 [==============================] - 0s 4ms/step - loss: 3391.7959 - mse: 3391.7954

15/15 [==============================] - 0s 12ms/step - loss: 3391.7959 - mse: 3391.7954
Epoch 6/10

 1/15 [=>............................] - ETA: 0s - loss: 2687.5127 - mse: 2687.5127
13/15 [=========================>....] - ETA: 0s - loss: 3154.0664 - mse: 3154.0664
15/15 [==============================] - 0s 4ms/step - loss: 3329.5364 - mse: 3329.5364

15/15 [==============================] - 0s 13ms/step - loss: 3329.5364 - mse: 3329.5364
Epoch 7/10

 1/15 [=>............................] - ETA: 0s - loss: 74.5161 - mse: 74.5161
15/15 [==============================] - 0s 3ms/step - loss: 3266.2383 - mse: 3266.2383

15/15 [==============================] - 0s 11ms/step - loss: 3266.2383 - mse: 3266.2383
Epoch 8/10

 1/15 [=>............................] - ETA: 0s - loss: 6527.1382 - mse: 6527.1382
15/15 [==============================] - 0s 4ms/step - loss: 3173.4714 - mse: 3173.4714

15/15 [==============================] - 0s 12ms/step - loss: 3173.4714 - mse: 3173.4714
Epoch 9/10

 1/15 [=>............................] - ETA: 0s - loss: 169.7121 - mse: 169.7121
12/15 [=======================>......] - ETA: 0s - loss: 3187.5933 - mse: 3187.5933
15/15 [==============================] - 0s 5ms/step - loss: 3050.2288 - mse: 3050.2288

15/15 [==============================] - 0s 13ms/step - loss: 3050.2288 - mse: 3050.2288
Epoch 10/10

 1/15 [=>............................] - ETA: 0s - loss: 2942.9104 - mse: 2942.9104
12/15 [=======================>......] - ETA: 0s - loss: 3208.3115 - mse: 3208.3115
15/15 [==============================] - 0s 5ms/step - loss: 2979.4160 - mse: 2979.4160

15/15 [==============================] - 0s 13ms/step - loss: 2979.4160 - mse: 2979.4160

Your turn

  • Play a bit around with the arguments in the generator, and se what outputs it produces. This will give you some intuition
  • For instance, what happens if you set stride to time_p + 1 ?

Many to many predictions

  • In case we want to predict a sequence of several timesteps.
  • Unfortunately, the generator has no option for that, so we have to prepare sepperate targets on our own.
  • Ih wrote a handy fun ction that does so, which you can use.
# Define a function that outputs time_p timesteps for y
gen_timeseries_output <- function(data, timesteps = 1, stride = 1, timesteps_out = 1){
  
  target <- matrix(nrow = length(data), ncol = timesteps_out)
  
  data <- data %>% as.numeric()
  
  for (i in seq(1, length(data), by = (timesteps + stride - 1) )) {
    target[i,] <- data[(i+1):(i+timesteps_out)]
  }
  
  return(target)
}
  • Let’s try it
n_timesteps_out <- 5

outcome_sequence <- data_example %>%
  gen_timeseries_output(timesteps_out = n_timesteps_out)
  • Lets inspect
outcome_sequence %>% head(20)
  • Seems to produce what we want
  • Now we can feed that as target into the generator
train_gen_seq <- 
  timeseries_generator(
    data = data,
    targets = outcome_sequence,
    length = 5,
  sampling_rate = 1,
  stride = 1,
  shuffle = FALSE,
  batch_size = 16
)
  • Lets instect
batch_0_seq = train_gen_seq[0]
batch_0_seq
  • Looks about right, dosnt it?
n_x <- 1 # number of features
time_x <- 4 # 4 days
time_y <- 1 # ... to predict one day ahead
# TRansforming the x sequence to a 3d tensor (necessary for LSTMs)
x_train_arr_n1 <- data_recipe %>% juice()  %>% pull(value) %>% as.matrix(ncol = time_x) %>% array_reshape(dim = c(nrow(.), ncol(.), 1))

x_test_arr_n1 <- x_test %>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1, 1))

y_train_arr2 <- y_train %>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1))
y_test_arr2 <- y_test %>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1))
x <- data_recipe %>% juice()  %>% pull(value) %>% matrix(ncol = time_x)
dim(x)[2]
length(x)[2]
x_train_arr %>% dim()
x_train_arr %>% glimpse()

Multi-episode LSTM

Transform to a 3d tensor for keras

tsteps_x = 5
tsteps_y = 5
train_arr <- x_train %>% pull(value) %>% as.numeric() %>% matrix(ncol = (tsteps_x + tsteps_y))
x_train_arr <- train_arr[,1:tsteps_x] %>% array_reshape(dim = c(length(.), 1, 1))
#x_train %<>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1, 1))
#x_test %<>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1, 1))

#y_train %<>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1))
#y_test %<>% pull(value) %>% as.numeric() %>% array_reshape(dim = c(length(.), 1))
LS0tCnRpdGxlOiAgJ1NlcXVlbmNlLTItU2VxdWVuY2UgZm9yZWNhc3RpbmcgKFIpJwphdXRob3I6ICJEYW5pZWwgUy4gSGFpbiAoZHNoQGJ1c2luZXNzLmFhdS5kaykiCmRhdGU6ICJVcGRhdGVkIGByIGZvcm1hdChTeXMudGltZSgpLCAnJUIgJWQsICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMgogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICB0aGVtZTogZmxhdGx5Ci0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CiMjIyBHZW5lcmljIHByZWFtYmxlCnJtKGxpc3Q9bHMoKSkKU3lzLnNldGVudihMQU5HID0gImVuIikgIyBGb3IgZW5nbGlzaCBsYW5ndWFnZQpvcHRpb25zKHNjaXBlbiA9IDUpICMgVG8gZGVhY3RpdmF0ZSBhbm5veWluZyBzY2llbnRpZmljIG51bWJlciBub3RhdGlvbgpgYGAKCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoa2VyYXMpCmxpYnJhcnkodGlkeW1vZGVscykgIyBPciBvbmx5IGxvYWQgdGhlICdyc2FtcGxlJyBhbmQgcmVjaXBlcyBvbiBpdHMgb3duCmBgYAoKMyBJbnRybzogV2F5cyB0byBjcmVhdGUgc2VxdWVuY2VzLi4uCgojIFdvcmtzaG9wIFN0b2NrIHByZWRpY3Rpb24KClRhc2s6CgoxLiBHZXQgc29tZSBzdG9jayBkYXRhICh0aXA6IFVzZSB0aWR5cXVhbnQpCiAgICAqIExpbWl0IHlvdXJzZWxmIGZvciBub3cgdG8gb24gZSBzdG9jawogICAgKiBMaW1pdCB5b3Vyc2VsZiB0byBvbmUgdmFyaWFibGUgKHByZWZlcmFibHkgc29tZSBwcmljZSBkYXRhKQoyLiBEZXZlbG9wIGEgb25lLXN0ZXAgYWhlYWQgcHJlZGljdGlvbiBvZiBwcmljZXMgKG9yIHRoZWlyIG1vdmVtZW50cykKCiMgTG9hZCBzb21lIGRhdGEKCiMjIFNlbGVjdCBhIHN0b2NrIGFibmQgbG9hZCB0aGUgZGF0YQoKKiBXZSB3aWxsIHVzZSB0aGUgdGlkeXF1YW50IHBhY2thZ2UgdG8gZG93bmxvYWQgc3RvY2sgZGF0YQoKYGBge3J9CmxpYnJhcnkodGlkeXF1YW50KSAjIE15IGZhdm9yaXRlIHBhY2thZ2UgdG8gZ2V0IHN0b2NrIGRhdGEKbGlicmFyeSh0aW1ldGspIApgYGAKCmBgYHtyfQp0aWNrZXJzID0gYygiTlZPIikgIyBXZSBjYW4gYWxzbyB0cnkgQUFQTCBldGMKICAgICAgICAgICAgCmRhdGFfc3RvY2tzIDwtIHRxX2dldCh0aWNrZXJzLAogICAgICAgICAgICAgICBmcm9tID0gIjIwMDAtMDEtMDEiLAogICAgICAgICAgICAgICB0byA9ICIyMDIxLTExLTE3IiwKICAgICAgICAgICAgICAgZ2V0ID0gInN0b2NrLnByaWNlcyIgIyBXaGF0IHdlIHdhbnQgdG8gZ2V0Li4uLiBoZXJlIHByaWNlcwogICAgICAgICAgICAgICApCmBgYAoKCiMjIFNvbWUgcGxvdHMgZm9yIGV4cGxvcmF0aW9uLi4uCgpgYGB7cn0KZGF0YV9zdG9ja3MgJT4lIGdsaW1wc2UoKQpgYGAKCmBgYHtyfQpkYXRhX3N0b2NrcyAlPiUgaGVhZCgpCmBgYAoKYGBge3J9CmRhdGFfc3RvY2tzICU+JSAKICBwbG90X3RpbWVfc2VyaWVzKGRhdGUsIGFkanVzdGVkKQoKIyAjIGdncGxvdCBhbHRlcm5hdGl2ZQojIGRhdGFfc3RvY2tzICU+JQojICAgZ2dwbG90KGFlcyh4ID0gZGF0ZSwgeSA9IGFkanVzdGVkLCkpICsKIyAgIGdlb21fbGluZSgpICsKIyAgIGxhYnMoeCA9ICdEYXRlJywgeSA9ICJBZGp1c3RlZCBQcmljZSIpIApgYGAKCiMgUHJlcHJvY2Vzc2luZwoKYGBge3J9CiMgTGltaXQgZGF0YQpkYXRhIDwtIGRhdGFfc3RvY2tzICU+JQogIHJlbmFtZShpbmRleCA9IGRhdGUsIHZhbHVlID0gYWRqdXN0ZWQpICU+JQogIHNlbGVjdChpbmRleCwgdmFsdWUpICU+JQogIGFycmFuZ2UoaW5kZXgpIApgYGAKCiogSXQgaXMgYWx3YXlzIGVhc2llciB0byBtb2RlbCBjaGFuZ2UgcmF0aGVyIHRoYW4gYWJzb2x1dGUgcHJpY2VzLCBzbyB3ZSBjcmVhdGUgYSB2YXJpYWJsZSBtZWFzdXJpbmcgdGhlIHBlcmNlbnRhZ2UgY2hhbmdlIG9mIHByaWNlIGluc3RlYWQKCmBgYHtyfQojIFJlbW9kZWwgdmFsdWUgYXMgcGVyY2VudGFnZSBjaGFuZ2UKZGF0YSAlPD4lCiAgZGlzdGluY3QoaW5kZXgsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQogIHRpZHlyOjpmaWxsKHZhbHVlLCAuZGlyZWN0aW9uID0gImRvd251cCIpICU+JQogIG11dGF0ZSh2YWx1ZSA9ICh2YWx1ZSAtIGxhZyh2YWx1ZSwxKSkgLyBsYWcodmFsdWUsMSkgKSAlPiUKICBkcm9wX25hKCkKYGBgCgpgYGB7cn0KZGF0YSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBpbmRleCwgeSA9IHZhbHVlKSkgKwogIGdlb21fbGluZSgpICsKICBsYWJzKHggPSAnRGF0ZScsIHkgPSAiUHJpY2UgY2hhbmdlIGluIHBjdCIpIApgYGAKCgpgYGB7cn0KZGF0YSAlPiUKICAgIHBsb3RfYWNmX2RpYWdub3N0aWNzKGRhdGUsIHZhbHVlKQpgYGAKCgojIyBUcmFpbiAmIFRlc3Qgc3BsaXQKCiogV2UgZG8gYSB0aW1lLXNlcmllcyBzcGxpdCB3aGljaCBrZWVwcyB0aGUgc2VxdWVuY2luZyBvZiB0aGUgZGF0YQoKYGBge3J9CiMgV2UgdXNlIHRpbWVfc3BsaXRzIGhlcmUgdG8gbWFpbnRhaW4gdGhlIHNlcXVlbmNlcwpkYXRhX3NwbGl0IDwtIGRhdGEgJT4lIGluaXRpYWxfdGltZV9zcGxpdChwcm9wID0gMC43NSkKYGBgCgpgYGB7cn0KZGF0YV90cmFpbiA8LSBkYXRhX3NwbGl0ICU+JSB0cmFpbmluZygpCmRhdGFfdGVzdCA8LSBkYXRhX3NwbGl0ICU+JSB0ZXN0aW5nKCkKYGBgCgoqIExldHMgc2VlIGZyb20gd2hlcmUgdGlsbCB3aGVuIHRoZSB0cmFpbi90ZXN0IHNhbXBsZXMgYXJlCgpgYGB7cn0KIyBTZWUgZWhhdCB3ZSBnb3QKZGF0YV90cmFpbiAlPiUgcHVsbChpbmRleCkgJT4lIG1pbigpCmRhdGFfdHJhaW4gJT4lIHB1bGwoaW5kZXgpICU+JSBtYXgoKQpkYXRhX3Rlc3QgJT4lIHB1bGwoaW5kZXgpICU+JSBtaW4oKQpkYXRhX3Rlc3QgJT4lIHB1bGwoaW5kZXgpICU+JSBtYXgoKQpgYGAKCmBgYHtyfQpkYXRhX3RyYWluICU+JSBtdXRhdGUoc3BsaXQgPSAndHJhaW5pbmcnKSAlPiUKICBiaW5kX3Jvd3MoZGF0YV90ZXN0ICU+JSBtdXRhdGUoc3BsaXQgPSAndGVzdGluZycpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBpbmRleCwgeSA9IHZhbHVlLCBjb2wgPSBzcGxpdCkpICsKICBnZW9tX2xpbmUoKSAKYGBgCgojIyBEZWZpbmUgYSByZWNpZXBlCgoqIFdlIG9ubHkgYXBwbHkgbWluLW1heCBzY2FsaW5nIGhlcmV3aXRoIGBzdGVwX3JhbmdlYAoKYGBge3J9CmRhdGFfcmVjaXBlIDwtIGRhdGFfdHJhaW4gJT4lCiAgcmVjaXBlKHZhbHVlIH4gLikgJT4lIAogIHN0ZXBfbm9ybWFsaXplKHZhbHVlKSAlPiUKICBzdGVwX2FycmFuZ2UoaW5kZXgpICU+JQogIHByZXAoKQpgYGAKCiogV2Ugc2F2ZSB0aGUgbWluIGFuZCBtYXggdG8gcmVzY2FsZSBsYXRlciBhZ2FpbgoKYGBge3J9CiMgUHJlc2VydmUgdGhlIHZhbHVlcyBmb3IgbGF0ZXIgKHRvIHJlY29uc3RydWN0IG9yaWdpbmFsIHZhbHVlcykKcHJlcF9oaXN0b3J5IDwtIHRpYmJsZSgKICBtZWFuID0gZGF0YV9yZWNpcGUkc3RlcHNbWzFdXSRtZWFucywKICBzZHMgPSBkYXRhX3JlY2lwZSRzdGVwc1tbMV1dJHNkcwopCmBgYAoKYGBge3J9CnByZXBfaGlzdG9yeQpgYGAKCiMjIEdldCBwcm9jZXNzZWR2IHRyYWluICYgdGVzdCBkYXRhCgoqIFdlIG5vdyBjcmVhdGUgYSB4IGFuZCB5IHNwbGl0LiBTaW5jZSB3ZSBoZXJlIGFsd2F5cyBwcmVkaWN0IHRoZSBuZXh0IG9ic2VydmF0aW9uLCB0aGF0J3MgZWFzeS4gV2Ugd2lsbCBqdXN0IHNldCB5PSBsZWFkKHgsIDEpCiogV2UgcmVwbGFjZSB0aGUgbGFzdCBtaXNzaW5nIG9ic2VydmF0aW9uIHdpdGggdGhlIGxhZ2dlZCB2YWx1ZQoKYGBge3J9CiMgTnVtYmVyIG9mIGxhZ3MKbl9sYWcgPSAxCgojIFRyYWluIGRhdGEKeF90cmFpbiA8LSBkYXRhX3JlY2lwZSAlPiUganVpY2UoKSAKCnlfdHJhaW4gPC0gZGF0YV9yZWNpcGUgJT4lICBqdWljZSgpICU+JQogIG11dGF0ZSh2YWx1ZSA9IHZhbHVlICU+JSBsZWFkKG5fbGFnKSkgJT4lCiAgdGlkeXI6OmZpbGwodmFsdWUsIC5kaXJlY3Rpb24gPSAiZG93bnVwIikgCgojIEFuZCB0aGUgc2FtZSBmb3IgdGhlIHRlc3QgZGF0YQp4X3Rlc3QgPC0gZGF0YV9yZWNpcGUgJT4lIGJha2UoZGF0YV90ZXN0KSAKCnlfdGVzdCA8LSBkYXRhX3JlY2lwZSAlPiUgIGJha2UoZGF0YV90ZXN0KSAlPiUgIAogIG11dGF0ZSh2YWx1ZSA9IHZhbHVlICU+JSBsZWFkKG5fbGFnKSkgJT4lCiAgdGlkeXI6OmZpbGwodmFsdWUsIC5kaXJlY3Rpb24gPSAiZG93bnVwIikgCmBgYAoKIyMgVHJhbnNmb3JtIHRvIGEgM2QgdGVuc29yIGZvciBrZXJhcwoKYGBge3J9CiMgVFJhbnNmb3JtaW5nIHRoZSB4IHNlcXVlbmNlIHRvIGEgM2QgdGVuc29yIChuZWNlc3NhcnkgZm9yIExTVE1zKQp4X3RyYWluX2FyciA8LSB4X3RyYWluICU+JSBwdWxsKHZhbHVlKSAlPiUgYXMubnVtZXJpYygpICU+JSBhcnJheV9yZXNoYXBlKGRpbSA9IGMobGVuZ3RoKC4pLCAxLCAxKSkKeF90ZXN0X2FyciA8LSB4X3Rlc3QgJT4lIHB1bGwodmFsdWUpICU+JSBhcy5udW1lcmljKCkgJT4lIGFycmF5X3Jlc2hhcGUoZGltID0gYyhsZW5ndGgoLiksIDEsIDEpKQoKeV90cmFpbl9hcnIgPC0geV90cmFpbiAlPiUgcHVsbCh2YWx1ZSkgJT4lIGFzLm51bWVyaWMoKSAlPiUgYXJyYXlfcmVzaGFwZShkaW0gPSBjKGxlbmd0aCguKSwgMSkpCnlfdGVzdF9hcnIgPC0geV90ZXN0ICU+JSBwdWxsKHZhbHVlKSAlPiUgYXMubnVtZXJpYygpICU+JSBhcnJheV9yZXNoYXBlKGRpbSA9IGMobGVuZ3RoKC4pLCAxKSkKYGBgCgoKYGBge3J9CnhfdHJhaW5fYXJyICU+JSBkaW0oKQpgYGAKCmBgYHtyfQp4X3RyYWluX2FyciAlPiUgZ2xpbXBzZSgpCmBgYAoKCiMgU2V0dGluZyB1cCB0aGUgTFNUTQoKIyBMU1RNCgojIyBEZWZpbmUgbW9kZWwKCmBgYHtyfQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgIyBMU1RNIGxheWVyCiAgbGF5ZXJfbHN0bSh1bml0cyA9IDMyLCAKICAgICAgICAgICAgIGRyb3BvdXQ9MC4yLCAKICAgICAgICAgICAgIHJlY3VycmVudF9kcm9wb3V0PTAuMiwKICAgICAgICAgICAgIGlucHV0X3NoYXBlID0gYygxLCAxKSwgIyBkaW0oeF90cmFpbl9hcnIpWy0xXSwgIyBuIHRpbWVzdGVwcywgbiBmZXV0dXJlcwogICAgICAgICAgICAgcmV0dXJuX3NlcXVlbmNlcyA9IFRSVUUpICU+JQogICMgTFNUTSBsYXllcgogIGxheWVyX2xzdG0odW5pdHMgPSAzMiwgCiAgICAgICAgICAgICBkcm9wb3V0PTAuMiwgCiAgICAgICAgICAgICByZWN1cnJlbnRfZHJvcG91dD0wLjIsCiAgICAgICAgICAgICByZXR1cm5fc2VxdWVuY2VzID0gRkFMU0UpICU+JQogICMgQSBERU5TRSBMQVlFUiBJTiBCRVRXRUVOCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAzMiwgYWN0aXZhdGlvbiA9ICdyZWx1JykgJT4lCiAgI0ZpbmFsIHByZWRpY3Rpb24gbGF5ZXIKICBsYXllcl9kZW5zZSh1bml0cyA9IDEsIGFjdGl2YXRpb24gPSAnbGluZWFyJykKYGBgCgpgYGB7cn0KIyBDb21waWxlIG1vZGVsCm1vZGVsICU+JSAKICBjb21waWxlKGxvc3MgPSAibXNlIiwgCiAgICAgICAgICBtZXRyaWMgPSAnbXNlJywgCiAgICAgICAgICBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfYWRhbSgpKQpgYGAKCgpgYGB7cn0KbW9kZWwgJT4lIHN1bW1hcnkoKQpgYGAKCiMjIEZpdHRpbmcgdGhlIG1vZGVsCgoqIE5leHQsIHdlIGNhbiBmaXQgb3VyIExTVE0gdXNpbmcgYSBmb3IgbG9vcCAod2UgZG8gdGhpcyB0byBtYW51YWxseSByZXNldCBzdGF0ZXMpLiAKKiBXZSBzZXQgYHNodWZmbGUgPSBGQUxTRWAgdG8gcHJlc2VydmUgc2VxdWVuY2VzCgpgYGB7cn0KaGlzdF9tb2RlbCA8LSBtb2RlbCAlPiUgZml0KHggICAgICAgICAgPSB4X3RyYWluX2FyciwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ICAgICAgICAgID0geV90cmFpbl9hcnIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZXBvY2hzICAgICA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSAgICA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hfc2l6ZSA9IDY0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMjUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2h1ZmZsZSAgICA9IEZBTFNFKQpgYGAKCmBgYHtyfQpoaXN0X21vZGVsICU+JSBwbG90KCkKYGBgCgoKYGBge3J9Cm1vZGVsICU+JSBldmFsdWF0ZSh4X3Rlc3RfYXJyLCB5X3Rlc3RfYXJyKQpgYGAKCiMjIFByZWRpY3RpbmcgU3RvY2sgY2hhbmdlcwoKKiBXZSBmaXJzdCBwcmVkaWN0IHRoZSBvdXRwdXQgb2Ygb3VyIHRlc3QgZGF0YQoKYGBge3J9Cm1vZGVsX3ByZWQgPC0gbW9kZWwgJT4lIHByZWRpY3QoeF90ZXN0X2FycikgJT4lIGFzLm51bWVyaWMoKQpgYGAKCiogSG93ZXZlciwgd2UgbmVlZCB0byByZXNjYWxlIHRoZSBvdXRwdXQuIEZvciBtaW4tbWF4IHNjYWxpbmcsIHRoaXMgZnVuY3Rpb24gd2lsbCBkbyB0aGUgdHJpY2sKCmBgYHtyfQpyZXZlcnNlX25vcm08LSBmdW5jdGlvbih4LCBtZWFuLCBzZHMpIHsKICB4X3JlIDwtICh4ICogc2RzKSArIG1lYW4KICByZXR1cm4oeF9yZSkKICB9CmBgYAoKKiBXZSBhcHBseSBpdCB3aXRoIG91ciBkYXRhIGFuZCB0aGUgc2F2ZWQgbWluIGFuZCBtYXggdmFsdWVzIGZyb20gdGhlIHJlY2lwZQoKYGBge3J9CmV2YWwgPC0gdGliYmxlKAogIGluZGV4ID0gZGF0YV90ZXN0ICU+JSBwdWxsKGluZGV4KSwKICB0cnV0aCA9IGRhdGFfdGVzdCAlPiUgcHVsbCh2YWx1ZSksCiAgcHJlZCA9IG1vZGVsX3ByZWQgJT4lIHJldmVyc2Vfbm9ybSh4ID0gLiwgbWVhbiA9IHByZXBfaGlzdG9yeSRtZWFuLCBzZHMgPSBwcmVwX2hpc3Rvcnkkc2RzKQopIApgYGAKCmBgYHtyLCBmaWcud2lkdGg9Ny41LCBmaWcuaGVpZ2h0PTV9CmV2YWwgJT4lIAogIHBpdm90X2xvbmdlcigtaW5kZXgpICU+JQogIGdncGxvdChhZXMoeCA9IGluZGV4LCB5ID0gdmFsdWUsIGNvbCA9IG5hbWUpKSArCiAgZ2VvbV9saW5lKCkKYGBgCldlbGwuLi4gc29zbwoKCiMgQnJpZWYgaW50cm8gdG8gd29ya2luZyB3aXRoIHRpbWUgc2VxdWVuY2VzIGFuZCB0aW1lIHNlcmllcyBnZW5lcmF0b3JzCgojIyBFeGFtcGxlIHRpbWVzZXJpZXM6CgoqIE9rLCBsZXRzIHRha2UgYSBicmllZiBsb29rIGF0IGhvdyB0byB3b3JrIHdpdGggc2VxdWVudGlvbiBkYXRhIGkgZGlmZmVyZW50IHdheXMsIGFuZCBwcmVwYXJlIHRoZW0gYXMgaW5wdXRzIGZvciBhbiBMU1RNCiogV2UsIGZvciB0aGUgc2FrZSBvZiBpbGx1c3RyYXRpb24sIGp1c3QgY3JlYXRlIGEgc2ltcGxlIHNlcXVlbmNlIHdpdGggdGhlIG51bWJlcnMgZnJvbSAxLTEwMCAoaXRzIGVhc2llciB0byBpbnNwZWN0IHRoZSBzZXF1ZW5jZSwgaW4gcmVhbGl0eSB3ZSB3b3VsZCBvYnZpb3VzbHkgZmVlZCBpdCB3aXRoIGRpZmZlcmVudCBvdXRwdXRzKQoKYGBge3J9CiMgR2VuZXJhdGUgYW4gZXhhbXBsZSBzZXF1ZW5jZQpkYXRhX2V4YW1wbGUgPC0gMToxMDAgICMlPiUgYXJyYXlfcmVzaGFwZShkaW0gPSBjKGxlbmd0aCguKSwgMSwgMSkpCmBgYAoKIyMgTWFueS10by1PbmUgcHJlZGljdGlvbnMKCiogSW4gdGhpcyBzZXR1cCwgd2Ugd2lsbCB1c2Ugc2V2ZXJhbCBwZXJpb2RzIHRvIHByZWRpY3Qgb25lIHN1YnNlcXVlbnQgb2JzZXJ2YXRpb25zLgoKYGBge3J9Cm5fdGltZXN0ZXBzIDwtIDUgICMgRGVmaW5lIHRoYXQgd2Ugd291bGQgbGlrZSB0byBoYXZlIDUgdGltZXN0ZXBzCmJhdGNoX3NpemUgPC0gNiAjIEJhdGNoIHNpemUgKHNvbWV3aGF0IGFyYml0cmFyeSkKbl9mZWF0dXJlcyA8LSAxICMgTnVtYmVyIG9mIGZlYXR1cmVzLiBTaW5jZSB3ZSBvbmx5IHByZWRpY3QgdGhlIG91dGNvbWUgYmFzZWQgb24gaXRzIG93biBzZXF1ZW5jZSwgaXQgd2lsbCBiZSAxCmBgYAoKKiBXZSB3aWxsIHNldCB1cCBLZXJhcyBgdGltZXNlcmllc19nZW5lcmF0b3JgLCB3aGljaCB3aWxsIGZlZWQgdGhlIExTVE0gKG9yIG90aGVyIGFyY2hpdGVjdHVyZSkgd2l0aCBvbi10aGUtZmx5IGdlbmVyYXRlZCBzZXF1ZW5jZXMgCgpgYGB7cn0KdHJhaW5fZ2VuIDwtIAogIHRpbWVzZXJpZXNfZ2VuZXJhdG9yKAogICAgZGF0YSA9IGRhdGFfZXhhbXBsZSwgIyBUaGUgZGF0YSB3ZSB3aWxsIHVzZSB0byBjcmVhdGUgdGhlIHNlcXVlbmNlcy4KICAgIHRhcmdldHMgPSBkYXRhX2V4YW1wbGUsICMgVGhlIHB1dGNvbWUgZGF0YSwgaW4gdGhpcyBjYXNlIHRoZSBzYW1lLCBzaW5jZSB3ZSBqdXN0IHdhbnQgdG8gcHJlZGljdCB0aGUgc3Vic2VxdWVudCBwZXJpb2QKICAgIGxlbmd0aCA9IG5fdGltZXN0ZXBzLCAjIEhvdyBtYW55IHByZXZpb3VzIHN0ZXBzIGluIHRoZSBzZXF1ZW5jZSBzaG91bGQgYmUgdXNlZCBmb3IgdGhlIHByZWRpY3Rpb24KICAgIHNhbXBsaW5nX3JhdGUgPSAxLCAjIFNob3VsZCB3ZSB1c2UgZXZlcnkgb2JzZXJ2YXRpb24gaW4gdGhlIHNlcXVlbmNlIG9yIHNraXAgc29tZT8KICAgIHN0cmlkZSA9IDIsICMgSG93IG1hbnkgc3RlcHMgc2hvdWxkIGJlIHNraXBwZWQKICAgIHNodWZmbGUgPSBGQUxTRSwgIyBTaG91bGQgdGhlIHNlcXVlbmNlIGJlIHNodWZmbGVkPyBJbiB0aW1lLXNlcmllcyBwcmVkaWN0aW9uLCB3ZSB3YW50IHRvIHByZXNlcnZlIHRoZSBvcmRlciBvZiBzZXF1ZW5jZXMsIHNvIGFsd2F5cyBGQUxTRQogICAgYmF0Y2hfc2l6ZSA9IGJhdGNoX3NpemUgIyBzaXplIG9mIHRoZSBiYXRjaGVzIGdlbmVyYXRlZC4gVVNlIHRoaXMgYmF0Y2ggc2l6ZSBhbHNvIGxhdGVyIGluIHRoZSBMU1RNCiAgICApCgpgYGAKCiogUmVtZW1iZXIsIHRoaXMgaXMgYSBsYXp5IGZ1bmN0aW9uLCBtZWFuaW5nIGl0IHdpbGwgZ2VuZXJhdGUgdGhlIHNlcXVlbmNlcyBvbi10aGUtZmx5IHdoZW4gdGhleSBhcmUgbmVlZGVkLgoqIFRoZXJlZm9yZSwgaXQgY2FuIG5vdCBkaXJlY3RseSBiZSBpbnNwZWN0ZWQuCgpgYGB7cn0KdHJhaW5fZ2VuCmBgYAoKKiBIb3dldmVyLCB3ZSBjYW4gZXh0cmFjdCBzaW5nbGUgYmF0Y2hlcyBhbmQgaW5zcGVjdCB0aGVtLgoqIFRoaXMgaXMgaGVscGZ1bCB0byBnZXQgYSBmZWVsaW5nIHdoYXQgdGhlIGRpZmZlcmVudCBhcmd1bWVudHMgb2YgdGhlIGdlbmVyYXRvciBkbywgYW5kIHRvIHRoZXN0IHRoYXQgdGhleSBjcmVhdGUgdGhlIHNlcXVlbmNlIHlvdSB3YW50LgoqIEhlcmUsIHR3byBhcnJyYXlzIHdpbGwgYmUgcmV0dXJuZWQsIHdoZXJlIHRoZSBmaXJzdCBvbmUgaXMgdGhlIGdlbmVyYXRlZCBpbnB1dCBzZXF1ZW5jZXMsIHRoZSBzZWNvbmQgb25lIHRoZSBjb3JyZXNwb25kaW5nIG91dHB1dC4KCmBgYHtyfQpiYXRjaF8wIDwtIHRyYWluX2dlblswXQpiYXRjaF8wCmBgYAoKYGBge3J9CiMgY3JlYXRlIHRoZSBtb2RlbAptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgICU+JQogICMgQWRkIHRoZSBsYXllci4gV2Ugd2lsbCBtYWtlIGl0IGFzIHNpbXBsZSBhcyBwb3NzaWJsZSBoZXJlIHdpdGgganVzdCBvbmUgTFNUTSBhbmQgYW4gb3V0cHV0IGxheWVyLgogIGxheWVyX2xzdG0oCiAgICB1bml0cyA9IDMyLCAKICAgIGJhdGNoX2lucHV0X3NoYXBlICA9IGMoYmF0Y2hfc2l6ZSwgbl90aW1lc3RlcHMsIG5fZmVhdHVyZXMpLCAjIHRoZSBmaXJzdCBsYXllciBpbiBhIG1vZGVsIG5lZWRzIHRvIGtub3cgdGhlIHNoYXBlIG9mIHRoZSBpbnB1dCBkYXRhCiAgICAjZHJvcG91dCA9IDAuMSwKICAgICNyZWN1cnJlbnRfZHJvcG91dCA9IDAuMSwKICAgIHJldHVybl9zZXF1ZW5jZXMgPSBGQUxTRSwgIyBieSBkZWZhdWx0LCBhbiBMU1RNIGp1c3QgcmV0dXJucyB0aGUgZmluYWwgc3RhdGUKICAgIHN0YXRlZnVsID0gVFJVRSkgJT4lIAogICMgRmluYWwgb3V0cHV0IGxheWVyCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxKQoKbW9kZWwgJT4lIGNvbXBpbGUobG9zcyA9ICdtc2UnLCBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfYWRhbSgpLCBtZXRyaWNzID0gJ21zZScpCmBgYAoKCgoKYGBge3J9Cm5fc3RlcHMgPC0gcm91bmQoKGxlbmd0aChkYXRhX2V4YW1wbGUpIC0gbl90aW1lc3RlcHMpIC8gYmF0Y2hfc2l6ZSwgMSkgCgpoaXN0IDwtIG1vZGVsICU+JSBmaXRfZ2VuZXJhdG9yKAogIGdlbmVyYXRvciA9IHRyYWluX2dlbiwKICBzdGVwc19wZXJfZXBvY2ggPSBuX3N0ZXBzLAogIGVwb2NocyA9IDEwCiAgKQpgYGAKCiMjIFlvdXIgdHVybgoKKiBQbGF5IGEgYml0IGFyb3VuZCB3aXRoIHRoZSBhcmd1bWVudHMgaW4gdGhlIGdlbmVyYXRvciwgYW5kIHNlIHdoYXQgb3V0cHV0cyBpdCBwcm9kdWNlcy4gVGhpcyB3aWxsIGdpdmUgeW91IHNvbWUgaW50dWl0aW9uCiogRm9yIGluc3RhbmNlLCB3aGF0IGhhcHBlbnMgaWYgeW91IHNldCBgc3RyaWRlYCB0byBgdGltZV9wICsgMWAgPwoKCiMjIE1hbnkgdG8gbWFueSBwcmVkaWN0aW9ucwoKKiBJbiBjYXNlIHdlIHdhbnQgdG8gcHJlZGljdCBhIHNlcXVlbmNlIG9mIHNldmVyYWwgdGltZXN0ZXBzLgoqIFVuZm9ydHVuYXRlbHksIHRoZSBnZW5lcmF0b3IgaGFzIG5vIG9wdGlvbiBmb3IgdGhhdCwgc28gd2UgaGF2ZSB0byBwcmVwYXJlIHNlcHBlcmF0ZSB0YXJnZXRzIG9uIG91ciBvd24uCiogSWggd3JvdGUgYSBoYW5keSBmdW4gY3Rpb24gdGhhdCBkb2VzIHNvLCB3aGljaCB5b3UgY2FuIHVzZS4KCgpgYGB7cn0KIyBEZWZpbmUgYSBmdW5jdGlvbiB0aGF0IG91dHB1dHMgdGltZV9wIHRpbWVzdGVwcyBmb3IgeQpnZW5fdGltZXNlcmllc19vdXRwdXQgPC0gZnVuY3Rpb24oZGF0YSwgdGltZXN0ZXBzID0gMSwgc3RyaWRlID0gMSwgdGltZXN0ZXBzX291dCA9IDEpewogIAogIHRhcmdldCA8LSBtYXRyaXgobnJvdyA9IGxlbmd0aChkYXRhKSwgbmNvbCA9IHRpbWVzdGVwc19vdXQpCiAgCiAgZGF0YSA8LSBkYXRhICU+JSBhcy5udW1lcmljKCkKICAKICBmb3IgKGkgaW4gc2VxKDEsIGxlbmd0aChkYXRhKSwgYnkgPSAodGltZXN0ZXBzICsgc3RyaWRlIC0gMSkgKSkgewogICAgdGFyZ2V0W2ksXSA8LSBkYXRhWyhpKzEpOihpK3RpbWVzdGVwc19vdXQpXQogIH0KICAKICByZXR1cm4odGFyZ2V0KQp9CmBgYAoKKiBMZXQncyB0cnkgaXQKCmBgYHtyfQpuX3RpbWVzdGVwc19vdXQgPC0gNQoKb3V0Y29tZV9zZXF1ZW5jZSA8LSBkYXRhX2V4YW1wbGUgJT4lCiAgZ2VuX3RpbWVzZXJpZXNfb3V0cHV0KHRpbWVzdGVwc19vdXQgPSBuX3RpbWVzdGVwc19vdXQpCmBgYAoKKiBMZXRzIGluc3BlY3QKCmBgYHtyfQpvdXRjb21lX3NlcXVlbmNlICU+JSBoZWFkKDIwKQpgYGAKCiogU2VlbXMgdG8gcHJvZHVjZSB3aGF0IHdlIHdhbnQKKiBOb3cgd2UgY2FuIGZlZWQgdGhhdCBhcyB0YXJnZXQgaW50byB0aGUgZ2VuZXJhdG9yCgpgYGB7cn0KdHJhaW5fZ2VuX3NlcSA8LSAKICB0aW1lc2VyaWVzX2dlbmVyYXRvcigKICAgIGRhdGEgPSBkYXRhLAogICAgdGFyZ2V0cyA9IG91dGNvbWVfc2VxdWVuY2UsCiAgICBsZW5ndGggPSA1LAogIHNhbXBsaW5nX3JhdGUgPSAxLAogIHN0cmlkZSA9IDEsCiAgc2h1ZmZsZSA9IEZBTFNFLAogIGJhdGNoX3NpemUgPSAxNgopCgpgYGAKCiogTGV0cyBpbnN0ZWN0CgpgYGB7cn0KYmF0Y2hfMF9zZXEgPSB0cmFpbl9nZW5fc2VxWzBdCmJhdGNoXzBfc2VxCmBgYAoKKiBMb29rcyBhYm91dCByaWdodCwgZG9zbnQgaXQ/CgoKCgo8IS0tLS0KCiMgTXVsdGlwbGUgdGltZXN0ZXBzIHN0b2MgcHJlZGljdGlvbgoKIyMgTWFueS10by1vbmUKCmBgYHtyfQpuX3ggPC0gMSAjIG51bWJlciBvZiBmZWF0dXJlcwp0aW1lX3ggPC0gNCAjIDQgZGF5cwp0aW1lX3kgPC0gMSAjIC4uLiB0byBwcmVkaWN0IG9uZSBkYXkgYWhlYWQKYGBgCgoKYGBge3J9CiMgVFJhbnNmb3JtaW5nIHRoZSB4IHNlcXVlbmNlIHRvIGEgM2QgdGVuc29yIChuZWNlc3NhcnkgZm9yIExTVE1zKQp4X3RyYWluX2Fycl9uMSA8LSBkYXRhX3JlY2lwZSAlPiUganVpY2UoKSAgJT4lIHB1bGwodmFsdWUpICU+JSBhcy5tYXRyaXgobmNvbCA9IHRpbWVfeCkgJT4lIGFycmF5X3Jlc2hhcGUoZGltID0gYyhucm93KC4pLCBuY29sKC4pLCAxKSkKCnhfdGVzdF9hcnJfbjEgPC0geF90ZXN0ICU+JSBwdWxsKHZhbHVlKSAlPiUgYXMubnVtZXJpYygpICU+JSBhcnJheV9yZXNoYXBlKGRpbSA9IGMobGVuZ3RoKC4pLCAxLCAxKSkKCnlfdHJhaW5fYXJyMiA8LSB5X3RyYWluICU+JSBwdWxsKHZhbHVlKSAlPiUgYXMubnVtZXJpYygpICU+JSBhcnJheV9yZXNoYXBlKGRpbSA9IGMobGVuZ3RoKC4pLCAxKSkKeV90ZXN0X2FycjIgPC0geV90ZXN0ICU+JSBwdWxsKHZhbHVlKSAlPiUgYXMubnVtZXJpYygpICU+JSBhcnJheV9yZXNoYXBlKGRpbSA9IGMobGVuZ3RoKC4pLCAxKSkKYGBgCgpgYGB7cn0KeCA8LSBkYXRhX3JlY2lwZSAlPiUganVpY2UoKSAgJT4lIHB1bGwodmFsdWUpICU+JSBtYXRyaXgobmNvbCA9IHRpbWVfeCkKZGltKHgpWzJdCmxlbmd0aCh4KVsyXQpgYGAKCmBgYHtyfQp4X3RyYWluX2FyciAlPiUgZGltKCkKYGBgCgpgYGB7cn0KeF90cmFpbl9hcnIgJT4lIGdsaW1wc2UoKQpgYGAKCgoKIyBNdWx0aS1lcGlzb2RlIExTVE0KCiMjIFRyYW5zZm9ybSB0byBhIDNkIHRlbnNvciBmb3Iga2VyYXMKCmBgYHtyfQp0c3RlcHNfeCA9IDUKdHN0ZXBzX3kgPSA1CmBgYAoKCmBgYHtyfQp0cmFpbl9hcnIgPC0geF90cmFpbiAlPiUgcHVsbCh2YWx1ZSkgJT4lIGFzLm51bWVyaWMoKSAlPiUgbWF0cml4KG5jb2wgPSAodHN0ZXBzX3ggKyB0c3RlcHNfeSkpCmBgYAoKYGBge3J9CnhfdHJhaW5fYXJyIDwtIHRyYWluX2FyclssMTp0c3RlcHNfeF0gJT4lIGFycmF5X3Jlc2hhcGUoZGltID0gYyhsZW5ndGgoLiksIDEsIDEpKQpgYGAKCgpgYGB7cn0KI3hfdHJhaW4gJTw+JSBwdWxsKHZhbHVlKSAlPiUgYXMubnVtZXJpYygpICU+JSBhcnJheV9yZXNoYXBlKGRpbSA9IGMobGVuZ3RoKC4pLCAxLCAxKSkKI3hfdGVzdCAlPD4lIHB1bGwodmFsdWUpICU+JSBhcy5udW1lcmljKCkgJT4lIGFycmF5X3Jlc2hhcGUoZGltID0gYyhsZW5ndGgoLiksIDEsIDEpKQoKI3lfdHJhaW4gJTw+JSBwdWxsKHZhbHVlKSAlPiUgYXMubnVtZXJpYygpICU+JSBhcnJheV9yZXNoYXBlKGRpbSA9IGMobGVuZ3RoKC4pLCAxKSkKI3lfdGVzdCAlPD4lIHB1bGwodmFsdWUpICU+JSBhcy5udW1lcmljKCkgJT4lIGFycmF5X3Jlc2hhcGUoZGltID0gYyhsZW5ndGgoLiksIDEpKQpgYGAK