Customization with Keras

library(processpredictR)
library(bupaR)
library(keras)
library(purrr)

We can also opt for setting up and training our model manually, instead of using the provided processpredictR methods. Note that after defining a model with keras::keras_model() the model no longer is of class ppred_model (as opposed to processpredictR::create_model()).

Defining a new custom model

The layers on top of a custom model (previously defined in adapting your model) can be added as follows:

new_outputs <- custom_model$model$output %>% # custom_model$model to access a model and $output to access the outputs of that model
  keras::layer_dropout(rate = 0.1) %>%
  keras::layer_dense(units = custom_model$num_outputs, activation = 'softmax')

custom_model <- keras::keras_model(inputs = custom_model$model$input, 
                                   outputs = new_outputs, 
                                   name = "new_custom_model")
custom_model
#> Model: "new_custom_model"
#> ________________________________________________________________________________
#>  Layer (type)                       Output Shape                    Param #     
#> ================================================================================
#>  input_2 (InputLayer)               [(None, 9)]                     0           
#>  token_and_position_embedding_1 (To  (None, 9, 36)                  828         
#>  kenAndPositionEmbedding)                                                       
#>  transformer_block_1 (TransformerBl  (None, 9, 36)                  26056       
#>  ock)                                                                           
#>  global_average_pooling1d_1 (Global  (None, 36)                     0           
#>  AveragePooling1D)                                                              
#>  dropout_6 (Dropout)                (None, 36)                      0           
#>  dense_6 (Dense)                    (None, 64)                      2368        
#>  dropout_8 (Dropout)                (None, 64)                      0           
#>  dense_8 (Dense)                    (None, 11)                      715         
#> ================================================================================
#> Total params: 29,967
#> Trainable params: 29,967
#> Non-trainable params: 0
#> ________________________________________________________________________________
# class of the model
custom_model %>% class
#> [1] "keras.engine.functional.Functional"                     
#> [2] "keras.engine.training.Model"                            
#> [3] "keras.engine.base_layer.Layer"                          
#> [4] "tensorflow.python.module.module.Module"                 
#> [5] "tensorflow.python.trackable.autotrackable.AutoTrackable"
#> [6] "tensorflow.python.trackable.base.Trackable"             
#> [7] "keras.utils.version_utils.LayerVersionSelector"         
#> [8] "keras.utils.version_utils.ModelVersionSelector"         
#> [9] "python.builtin.object"

Preprocessing

processpredictR::tokenize()

Before training the model we first must prepare the data. processpredictR provides the tokenize() function, but other alternatives (see working with preprocessing layers) can also be used.

# the trace of activities must be tokenized
tokens_train <- df$train_df %>% tokenize()
map(tokens_train, head) # the output of tokens is a list
#> $token_x
#> $token_x[[1]]
#> [1] 2
#> 
#> $token_x[[2]]
#> [1] 2 3
#> 
#> $token_x[[3]]
#> [1] 2
#> 
#> $token_x[[4]]
#> [1] 2 4
#> 
#> $token_x[[5]]
#> [1] 2 4 5
#> 
#> $token_x[[6]]
#> [1] 2 4 5 6
#> 
#> 
#> $numeric_features
#> NULL
#> 
#> $categorical_features
#> NULL
#> 
#> $token_y
#> [1] 0 1 2 3 4 5

Pad sequences

Padding of sequences is required to make every input sequence of an equal length.

# make sequences of equal length
x <- tokens_train$token_x %>% pad_sequences(maxlen = max_case_length(df$train_df), value = 0)
y <- tokens_train$token_y

compile, fit, predict & evaluate

Note that keras::keras_model() no longer returns a list, so we simply refer to the model as to an object.

Do not forget to compile the model

# compile
compile(object=custom_model, optimizer = "adam", 
        loss = loss_sparse_categorical_crossentropy(), 
        metrics = metric_sparse_categorical_crossentropy())

We are now ready to train our custom model (the code below is not being evaluated).

# train
fit(object = custom_model, x, y, epochs = 10, batch_size = 10) 
# see also ?keras::fit.keras.engine.training.Model

# predict
tokens_test <- df$test_df %>% tokenize()
x <- tokens_test$token_x %>% pad_sequences(maxlen = max_case_length(df$train_df), value = 0)
predict(custom_model, x)

# evaluate
tokens_test <- df$test_df %>% tokenize()
x <- tokens_test$token_x
# normalize by dividing y_test over the standard deviation of y_train
y <- tokens_test$token_y / sd(tokens_train$token_y)
evaluate(custom_model, x, y)

Read more:


Copyright © 2023 bupaR - Hasselt University