This project is designed to test your current knowledge on applying LSTMS to the Cornell Movie Review dataset provided by Cornell University. This dataset contains movie reviews introduced in Pang & Lee (2004) with 2000 total observations. Detailed information about the data can be found here.

Your goal is to develop and compare the performance of a word embedding deep learning classifier to one that incorporates LSTM sequence embedding. I will guide you along the way but this project expects you to do most of the work from importing and preprocessing text, to building the models.

Nearly all the code that you need can be found in these notebooks:

Good luck!

Requirements

library(keras)
library(tidyverse)
library(fs)
library(glue)
library(testthat)

Import the data

For those in the workshop we have already downloaded the movie review data for you into the "/materials/data/cornell_reviews" directory. Outside of the workshop, you can find the download instructions here.

# get path to data
if (stringr::str_detect(here::here(), "conf-2020-user")) {
  movie_dir <- "/home/conf-2020-user/data/cornell_reviews/data"
} else {
  movie_dir <- here::here("materials", "data", "cornell_reviews", "data")
}

fs::dir_tree(movie_dir, recurse = FALSE)
/Users/b294776/Desktop/Workspace/Training/rstudio-conf-2020/dl-keras-tf/materials/data/cornell_reviews/data
├── neg
└── pos

Step 1: You can see the data have already been separated into positive vs negative sets. The actual reviews are contained in individual .txt files. Similar to Intro to word embeddings, let’s go ahead use this structure to our advantage by iterating over each review and…

  1. creating the path to each individual review file,
  2. creating a label based on the “neg” or “pos” folder the review is in, and
  3. saving the output as a data frame with each review on an individual row.
training_files <- movie_dir %>%
  dir_ls() %>%
  map(dir_ls) %>%
  set_names(basename) %>%
  plyr::ldply(data_frame) %>%
  set_names(c("label", "path"))

# you should have 2000 total observations
expect_equal(nrow(training_files), 2000)

Go ahead and take a look at your data frame

training_files

Step 2: How many obseravations are in each response label (i.e. “neg” vs “pos”)?

count(training_files, label)

Step 3: Next, let’s iterate over each row and

  1. save the label in a labels vector,
  2. import the movie review, and
  3. save in a texts vector.
obs <- nrow(training_files)
labels <- vector(mode = "integer", length = obs)
texts <- vector(mode = "character", length = obs)

for (file in seq_len(obs)) {
  label <- training_files[[file, "label"]]
  path <- training_files[[file, "path"]]
  
  labels[file] <- ifelse(label == "neg", 0, 1)
  texts[file] <- readChar(path, nchars = file.size(path)) 
}

The number of observations in your text should be equal to the number of responses.

expect_equal(length(texts), length(labels))

Go ahead and check out the text of a couple reviews.

texts[1]
[1] "plot : two teen couples go to a church party , drink and then drive . \nthey get into an accident . \none of the guys dies , but his girlfriend continues to see him in her life , and has nightmares . \nwhat's the deal ? \nwatch the movie and \" sorta \" find out . . . \ncritique : a mind-fuck movie for the teen generation that touches on a very cool idea , but presents it in a very bad package . \nwhich is what makes this review an even harder one to write , since i generally applaud films which attempt to break the mold , mess with your head and such ( lost highway & memento ) , but there are good and bad ways of making all types of films , and these folks just didn't snag this one correctly . \nthey seem to have taken this pretty neat concept , but executed it terribly . \nso what are the problems with the movie ? \nwell , its main problem is that it's simply too jumbled . \nit starts off \" normal \" but then downshifts into this \" fantasy \" world in which you , as an audience member , have no idea what's going on . \nthere are dreams , there are characters coming back from the dead , there are others who look like the dead , there are strange apparitions , there are disappearances , there are a looooot of chase scenes , there are tons of weird things that happen , and most of it is simply not explained . \nnow i personally don't mind trying to unravel a film every now and then , but when all it does is give me the same clue over and over again , i get kind of fed up after a while , which is this film's biggest problem . \nit's obviously got this big secret to hide , but it seems to want to hide it completely until its final five minutes . \nand do they make things entertaining , thrilling or even engaging , in the meantime ? \nnot really . \nthe sad part is that the arrow and i both dig on flicks like this , so we actually figured most of it out by the half-way point , so all of the strangeness after that did start to make a little bit of sense , but it still didn't the make the film all that more entertaining . \ni guess the bottom line with movies like this is that you should always make sure that the audience is \" into it \" even before they are given the secret password to enter your world of understanding . \ni mean , showing melissa sagemiller running away from visions for about 20 minutes throughout the movie is just plain lazy ! ! \nokay , we get it . . . there \nare people chasing her and we don't know who they are . \ndo we really need to see it over and over again ? \nhow about giving us different scenes offering further insight into all of the strangeness going down in the movie ? \napparently , the studio took this film away from its director and chopped it up themselves , and it shows . \nthere might've been a pretty decent teen mind-fuck movie in here somewhere , but i guess \" the suits \" decided that turning it into a music video with little edge , would make more sense . \nthe actors are pretty good for the most part , although wes bentley just seemed to be playing the exact same character that he did in american beauty , only in a new neighborhood . \nbut my biggest kudos go out to sagemiller , who holds her own throughout the entire film , and actually has you feeling her character's unraveling . \noverall , the film doesn't stick because it doesn't entertain , it's confusing , it rarely excites and it feels pretty redundant for most of its runtime , despite a pretty cool ending and explanation to all of the craziness that came before it . \noh , and by the way , this is not a horror or teen slasher flick . . . it's \njust packaged to look that way because someone is apparently assuming that the genre is still hot with the kids . \nit also wrapped production two years ago and has been sitting on the shelves ever since . \nwhatever . . . skip \nit ! \nwhere's joblo coming from ? \na nightmare of elm street 3 ( 7/10 ) - blair witch 2 ( 7/10 ) - the crow ( 9/10 ) - the crow : salvation ( 4/10 ) - lost highway ( 10/10 ) - memento ( 10/10 ) - the others ( 9/10 ) - stir of echoes ( 8/10 ) \n"

Data exploration

Step 4: Before preprocessing, let’s get a sense of two attributes that will help us set two of our preprocessing hyperparameters:

  1. How many unique words exist across all our reviews? We’ll use this to determine a good starting point for preprocessing our text.

  2. What is the distribution of word count across all movie reviews (i.e. mean, median)? We’ll use this to determine a good starting point for preprocessing our text.

text_df <- texts %>%
  tibble(.name_repair = ~ "text") %>%
  mutate(text_length = str_count(text, "\\w+"))

unique_words <- text_df %>%
  tidytext::unnest_tokens(word, text) %>%
  pull(word) %>%
  n_distinct()

avg_review_length <- median(text_df$text_length, na.rm = TRUE)
  
ggplot(text_df, aes(text_length)) +
  geom_histogram(bins = 100, fill = "grey70", color = "grey40") +
  geom_vline(xintercept = avg_review_length, color = "red", lty = "dashed") +
  scale_x_log10("# words") +
  ggtitle(glue("Median review length is {avg_review_length} words"),
          subtitle = glue("Total number of unique words is {unique_words}"))

Data preprocessing

Step 5: Now let’s tokenize our text sequences. To do so we:

  1. Specify how many words we want to include. Remember, a good starting point to use roughly 50% of the number of unique words in the data. This is a hyper- parameter that you can always come back to and adjust.
  2. Create a text_tokenizer object which defines how we want to preprocess the text. The defaults are sufficient.
  3. Apply the tokenizer to our text with fit_text_tokenizer().
  4. Extract our vectorized review data with texts_to_sequences().
# 1
top_n_words <- 20000

# 2-3
tokenizer <- text_tokenizer(num_words = top_n_words) %>% 
  fit_text_tokenizer(texts)

# 4
sequences <- texts_to_sequences(tokenizer, texts)

Go ahead and check out the first vectorized sequence. Should look familiar from earlier modules.

# The vectorized first instance:
sequences[[1]]
  [1]    97    76   948  4621   130     5     2  2015   788  3763     3   102  1313
 [14]    36    72    55    24  1340    23     4     1   518  1324    16    13   677
 [27]  1570     5    83    54     7    33    89     3    32  4476   603     1   637
 [40]   241     1    26     3  8853   189    40  2185     2   301  6461    26    12
 [53]     1   948  1457     8  2401    19     2    79   690   361    16  1767     9
 [66]     7     2    79   105  3979    45     6    48   142    14   465    24    56
 [79]  3667    23     5  1174   194    18  1341  8854    95    45   526     5   830
 [92]     1  7097   902    11   169   362     3   117   341  4203  8855    16    52
[105]    20    61     3   105   752     4   242    35  2694     4    95     3   112
[118]  1878    50   248    14    23  6179    36   249     5    31   624    14   271
[131]  3764  1223    16  2794     9  2283    39    48    20     1   469    11     1
[144]    26    77    64   348   353     6     8    37   321    88  7839     9   436
[157]    87  1314    16   102    55    14  1445   145     7    45    34    10    24
[170]   161  1122    31    59   361   603   159    19    52    20  1076    52    20
[183]    81   504   135    28     1   328    52    20   485    27   177    38     1
[196]   328    52    20   769 13435    52    20 17472    52    20     2     4   910
[209]   114    52    20  5277     4  1386   171     8   632     3    63     4     9
[222]     6   321    25  1957   143    18  2926   120   301   254     5  9489     2
[235]    15   150   143     3   102    16    44    35     9    91     6   256   103
[248]     1   164  2851   100     3   100   208    18    72   259     4  4077    42
[261]    82     2    94    45     6    14   247   954   353    37   613   294    14
[274]   133   801     5  3146    16     9   138     5   257     5  3146     9   308
[287]   313    64   369   493   226     3    75    36    86   171   439  2795    46
[300]    56  1747     7     1  5675    25    92     1  1280   204     6     8     1
[313]  4078     3    18   158  5676    19  1711    38    14    39    62   175  4079
[326]    63     4     9    40    21     1   268    84   212    39    35     4     1
[339] 12156    82     8   197   442     5    86     2    98   251     4   261    16
[352]     9   137   248     1    86     1    15    35     8    43   439    18   618
[365]     1  1486   311    11   121    38    14     6     8    34   167   245    86
[378]   276     8     1   161     6    55     9    56   141    36    20   284     1
[391]   801 15147     5  2016   169   145     4  2329    18   570   963  7840 10194
[404]   426   221    28  3863    12    41  1430   226   457     1    26     6    50
[417]  1684  3592  1123    62    72     9    52    20   101  3024    33     3    62
[430]   120   118    27    36    20    75    62    92   437     5    83     9   100
[443]     3   100   208    96    41   665   132   316   114  3288  1044  2440    55
[456]    35     4     1 12156   159   149     7     1    26   682     1   929   874
[469]    14    15   221    28    64   123     3 13436     9    42   491     3     9
[482]   336    52  8856    69     2   271   875   948   301  6461    26     7   128
[495]  1102    16    18   618     1  2852  1325     8  1729     9    55     2   289
[508]   431    11    98  1326    68    86    43   261     1   209    20   271    61
[521]    12     1    63   204   187  2594 10195    50   675     5    22   388     1
[534]  2109   164    74     8    17   197     7   258  1034    57     7     2   113
[547]  5278    16   106   954  5279   130    40     5 10194    27  1543    33   181
[560]   457     1   339    15     3   175    32    34   621    33  1526   898     1
[573]    15   116  1993    85     9   116  2476    37  1471     9  1358     3     9
[586]   648   271  7098    12    63     4    64 10196   400     2   271   690   324
[599]     3  1994     5    35     4     1 17473     8   770   141     9   657     3
[612]    21     1    84    14     6    25     2   295    46   948  1649   731    37
[625]    50 17474     5   177     8    84    85   371     6   682  6792     8     1
[638]   521     6   137  1132    11     1   440     9    71  3210   460    76   174
[651]   714     3    32    69  1487    19     1  7443   193   194  1045  2853     9
[664]  3980  4204   504    28     2  2214     4  7099  1003   640  1211   302  1446
[677]  1315   314  1211   302     1  2518  1853   302     1  2518  7100   749   302
[690]   341  4203   302   302  8855   302   302     1   485  1853   302  4341     4
[703]  3864  1022   302

We can see how our tokenizer converted our original text to a cleaned up version:

cat(crayon::blue("Original text:\n"))
Original text:

texts[[1]]
[1] "plot : two teen couples go to a church party , drink and then drive . \nthey get into an accident . \none of the guys dies , but his girlfriend continues to see him in her life , and has nightmares . \nwhat's the deal ? \nwatch the movie and \" sorta \" find out . . . \ncritique : a mind-fuck movie for the teen generation that touches on a very cool idea , but presents it in a very bad package . \nwhich is what makes this review an even harder one to write , since i generally applaud films which attempt to break the mold , mess with your head and such ( lost highway & memento ) , but there are good and bad ways of making all types of films , and these folks just didn't snag this one correctly . \nthey seem to have taken this pretty neat concept , but executed it terribly . \nso what are the problems with the movie ? \nwell , its main problem is that it's simply too jumbled . \nit starts off \" normal \" but then downshifts into this \" fantasy \" world in which you , as an audience member , have no idea what's going on . \nthere are dreams , there are characters coming back from the dead , there are others who look like the dead , there are strange apparitions , there are disappearances , there are a looooot of chase scenes , there are tons of weird things that happen , and most of it is simply not explained . \nnow i personally don't mind trying to unravel a film every now and then , but when all it does is give me the same clue over and over again , i get kind of fed up after a while , which is this film's biggest problem . \nit's obviously got this big secret to hide , but it seems to want to hide it completely until its final five minutes . \nand do they make things entertaining , thrilling or even engaging , in the meantime ? \nnot really . \nthe sad part is that the arrow and i both dig on flicks like this , so we actually figured most of it out by the half-way point , so all of the strangeness after that did start to make a little bit of sense , but it still didn't the make the film all that more entertaining . \ni guess the bottom line with movies like this is that you should always make sure that the audience is \" into it \" even before they are given the secret password to enter your world of understanding . \ni mean , showing melissa sagemiller running away from visions for about 20 minutes throughout the movie is just plain lazy ! ! \nokay , we get it . . . there \nare people chasing her and we don't know who they are . \ndo we really need to see it over and over again ? \nhow about giving us different scenes offering further insight into all of the strangeness going down in the movie ? \napparently , the studio took this film away from its director and chopped it up themselves , and it shows . \nthere might've been a pretty decent teen mind-fuck movie in here somewhere , but i guess \" the suits \" decided that turning it into a music video with little edge , would make more sense . \nthe actors are pretty good for the most part , although wes bentley just seemed to be playing the exact same character that he did in american beauty , only in a new neighborhood . \nbut my biggest kudos go out to sagemiller , who holds her own throughout the entire film , and actually has you feeling her character's unraveling . \noverall , the film doesn't stick because it doesn't entertain , it's confusing , it rarely excites and it feels pretty redundant for most of its runtime , despite a pretty cool ending and explanation to all of the craziness that came before it . \noh , and by the way , this is not a horror or teen slasher flick . . . it's \njust packaged to look that way because someone is apparently assuming that the genre is still hot with the kids . \nit also wrapped production two years ago and has been sitting on the shelves ever since . \nwhatever . . . skip \nit ! \nwhere's joblo coming from ? \na nightmare of elm street 3 ( 7/10 ) - blair witch 2 ( 7/10 ) - the crow ( 9/10 ) - the crow : salvation ( 4/10 ) - lost highway ( 10/10 ) - memento ( 10/10 ) - the others ( 9/10 ) - stir of echoes ( 8/10 ) \n"
cat(crayon::blue("\nRevised text:\n"))

Revised text:

paste(unlist(tokenizer$index_word)[sequences[[1]]] , collapse = " ")
[1] "plot two teen couples go to a church party drink and then drive they get into an accident one of the guys dies but his girlfriend continues to see him in her life and has nightmares what's the deal watch the movie and sorta find out critique a mind fuck movie for the teen generation that touches on a very cool idea but presents it in a very bad package which is what makes this review an even harder one to write since i generally applaud films which attempt to break the mold mess with your head and such lost highway memento but there are good and bad ways of making all types of films and these folks just didn't this one correctly they seem to have taken this pretty neat concept but executed it terribly so what are the problems with the movie well its main problem is that it's simply too jumbled it starts off normal but then into this fantasy world in which you as an audience member have no idea what's going on there are dreams there are characters coming back from the dead there are others who look like the dead there are strange apparitions there are disappearances there are a of chase scenes there are tons of weird things that happen and most of it is simply not explained now i personally don't mind trying to unravel a film every now and then but when all it does is give me the same clue over and over again i get kind of fed up after a while which is this film's biggest problem it's obviously got this big secret to hide but it seems to want to hide it completely until its final five minutes and do they make things entertaining thrilling or even engaging in the meantime not really the sad part is that the arrow and i both dig on flicks like this so we actually figured most of it out by the half way point so all of the strangeness after that did start to make a little bit of sense but it still didn't the make the film all that more entertaining i guess the bottom line with movies like this is that you should always make sure that the audience is into it even before they are given the secret password to enter your world of understanding i mean showing melissa sagemiller running away from visions for about 20 minutes throughout the movie is just plain lazy okay we get it there are people chasing her and we don't know who they are do we really need to see it over and over again how about giving us different scenes offering further insight into all of the strangeness going down in the movie apparently the studio took this film away from its director and chopped it up themselves and it shows there might've been a pretty decent teen mind fuck movie in here somewhere but i guess the suits decided that turning it into a music video with little edge would make more sense the actors are pretty good for the most part although wes bentley just seemed to be playing the exact same character that he did in american beauty only in a new neighborhood but my biggest kudos go out to sagemiller who holds her own throughout the entire film and actually has you feeling her character's overall the film doesn't stick because it doesn't entertain it's confusing it rarely and it feels pretty redundant for most of its runtime despite a pretty cool ending and explanation to all of the craziness that came before it oh and by the way this is not a horror or teen slasher flick it's just packaged to look that way because someone is apparently assuming that the genre is still hot with the kids it also wrapped production two years ago and has been sitting on the shelves ever since whatever skip it where's joblo coming from a nightmare of elm street 3 7 10 blair witch 2 7 10 the crow 9 10 the crow salvation 4 10 lost highway 10 10 memento 10 10 the others 9 10 stir of echoes 8 10"

Step 6: Next, since each review is a different length, we need to limit ourselves to a certain number of words so that all our text sequences are the same length.

To do so we:

  1. Specify the max length for each sequence. You can start out with 500 and then tune this hyperparameter later.
  2. Use pad_sequences() to truncate or pad reviews to the specified max_len.
max_len <- 500
features <- pad_sequences(sequences, maxlen = max_len)

Your now have your preprocessed feature data that is a 2D tensor (aka matrix) and contains 2000 observations (rows) and max_len columns.

dim(features)
[1] 2000  500
expect_equal(class(features), "matrix")
expect_equal(dim(features), c(obs, max_len))

You can see how the final preprocessed sequence looks for the first movie review with the following code:

paste(unlist(tokenizer$index_word)[features[1,]], collapse = " ")
[1] "a of chase scenes there are tons of weird things that happen and most of it is simply not explained now i personally don't mind trying to unravel a film every now and then but when all it does is give me the same clue over and over again i get kind of fed up after a while which is this film's biggest problem it's obviously got this big secret to hide but it seems to want to hide it completely until its final five minutes and do they make things entertaining thrilling or even engaging in the meantime not really the sad part is that the arrow and i both dig on flicks like this so we actually figured most of it out by the half way point so all of the strangeness after that did start to make a little bit of sense but it still didn't the make the film all that more entertaining i guess the bottom line with movies like this is that you should always make sure that the audience is into it even before they are given the secret password to enter your world of understanding i mean showing melissa sagemiller running away from visions for about 20 minutes throughout the movie is just plain lazy okay we get it there are people chasing her and we don't know who they are do we really need to see it over and over again how about giving us different scenes offering further insight into all of the strangeness going down in the movie apparently the studio took this film away from its director and chopped it up themselves and it shows there might've been a pretty decent teen mind fuck movie in here somewhere but i guess the suits decided that turning it into a music video with little edge would make more sense the actors are pretty good for the most part although wes bentley just seemed to be playing the exact same character that he did in american beauty only in a new neighborhood but my biggest kudos go out to sagemiller who holds her own throughout the entire film and actually has you feeling her character's overall the film doesn't stick because it doesn't entertain it's confusing it rarely and it feels pretty redundant for most of its runtime despite a pretty cool ending and explanation to all of the craziness that came before it oh and by the way this is not a horror or teen slasher flick it's just packaged to look that way because someone is apparently assuming that the genre is still hot with the kids it also wrapped production two years ago and has been sitting on the shelves ever since whatever skip it where's joblo coming from a nightmare of elm street 3 7 10 blair witch 2 7 10 the crow 9 10 the crow salvation 4 10 lost highway 10 10 memento 10 10 the others 9 10 stir of echoes 8 10"

Model training

Step 7: To train our model we will use the validation_split procedure within fit(). Remember, this takes the last XX% of our data to be used as our validation set. But if you recall, our data was organized in “neg” and “pos” folders so we should randomize our data to make sure our validation set doesn’t end up being all positive or negative reviews!

set.seed(123)
index <- sample(1:nrow(features))

x_train <- features[index, ]
y_train <- labels[index]

# there should be 2 unique values (0 - neg, 1 - pos) in last 30% of data
expect_equal(
  length(unique(y_train[floor(length(y_train) * 0.7):length(y_train)])), 
  2
  )

Word embedding model

Step 8: We’re now ready to do modeling. For our first model, let’s create a model that:

  1. applies a word embedding layer
    • input_dim should equal top_n_words
    • input_length should equal max_len
    • start with output_dim = 16
  2. flattens the embeddings
  3. classifies with a dense layer

You can use early stopping if you’d like but for the first model:

  • use the default learning rate
  • 20 epochs is more than enough
  • use a batch size of 32
  • use a validation split of 30%
model_basic <- keras_model_sequential() %>%
  layer_embedding(
    input_dim = top_n_words,  # number of words we are considering
    input_length = max_len,   # length that we have set each review to
    output_dim = 16           # length of our word embeddings
    ) %>%  
  layer_flatten() %>%
  layer_dense(units = 1, activation = "sigmoid")
  
model_basic %>% compile(
  optimizer = "rmsprop",
  loss = "binary_crossentropy",
  metrics = "accuracy"
)

history_basic <- model_basic %>% 
  fit(
    x_train, y_train,
    epochs = 20,
    batch_size = 32,
    validation_split = 0.3,
    callbacks = list(
      callback_early_stopping(patience = 3, restore_best_weights = TRUE)
      )
    )

Run the following code to check out your optimal loss and corresponding accuracy.

best_epoch <- which.min(history_basic$metrics$val_loss)
best_loss <- history_basic$metrics$val_loss[best_epoch] %>% round(3)
best_acc <- history_basic$metrics$val_accuracy[best_epoch] %>% round(3)

glue("Our optimal loss is {best_loss} with an accuracy of {best_acc}")
Our optimal loss is 0.565 with an accuracy of 0.705

Word embedding + LSTM model

Step 9: Now let’s build on to the previous model by adding an LSTM layer after the layer_embedding layer. When feeding an embedding layer into an LSTM layer you do not need to flatten the layer. Reference the Intro to LSTMs notebook. For this first LSTM model use units = 32.

model_lstm <- keras_model_sequential() %>%
  layer_embedding(
    input_dim = top_n_words,  # number of words we are considering
    input_length = max_len,   # length that we have set each review to
    output_dim = 16            # length of our word embeddings
    ) %>%  
  layer_lstm(units = 32) %>%
  layer_dense(units = 1, activation = "sigmoid") 

model_lstm %>% compile(
  optimizer = "rmsprop",
  loss = "binary_crossentropy",
  metrics = "accuracy"
)

history_lstm <- model_lstm %>% fit(
  x_train, y_train,
  epochs = 20,
  batch_size = 32,
  validation_split = 0.3,
  callbacks = list(
    callback_early_stopping(patience = 3, restore_best_weights = TRUE)
    )
)

Run the following code to check out your optimal loss and corresponding accuracy.

  1. How does it compare to the word embedding only model?
  2. Why do you think there is a difference?
best_epoch <- which.min(history_lstm$metrics$val_loss)
best_loss <- history_lstm$metrics$val_loss[best_epoch] %>% round(3)
best_acc <- history_lstm$metrics$val_accuracy[best_epoch] %>% round(3)

glue("Our optimal loss is {best_loss} with an accuracy of {best_acc}")
Our optimal loss is 0.445 with an accuracy of 0.795

Search for a better model

Step 10: Spend the rest of the time tuning hyperparameters and see if you can find a better model. Things you can try:

  • Preprocessing hyperparameters
    • adjust the number of words to retain in the word index (top_n_words)
    • adjust the size of the sequences (max_len)
  • Word embedding layer
    • adjust the output_dim
  • LSTM layer
  • Other
    • adjust the learning rate (or even the optimizer (i.e. try “adam”))
    • adjust the batch_size
    • add a callback to adjust the learning upon plateauing

🏠

LS0tCnRpdGxlOiAiQ2FuIFlvdSBJbXByb3ZlIFNlbnRpbWVudCBQb2xhcml0eSB3aXRoIExTVE1zPyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UpCmBgYAoKVGhpcyBwcm9qZWN0IGlzIGRlc2lnbmVkIHRvIHRlc3QgeW91ciBjdXJyZW50IGtub3dsZWRnZSBvbiBhcHBseWluZyBMU1RNUyB0byB0aGUKW0Nvcm5lbGwgTW92aWUgUmV2aWV3IGRhdGFzZXRdKGh0dHA6Ly93d3cuY3MuY29ybmVsbC5lZHUvcGVvcGxlL3BhYm8vbW92aWUtcmV2aWV3LWRhdGEvKQpwcm92aWRlZCBieSBDb3JuZWxsIFVuaXZlcnNpdHkuIFRoaXMgZGF0YXNldCBjb250YWlucyBtb3ZpZSByZXZpZXdzIGludHJvZHVjZWQKaW4gW1BhbmcgJiBMZWUgKDIwMDQpXShodHRwczovL2JpdC5seS8yU1dHVkJaKSB3aXRoIDIwMDAgdG90YWwgb2JzZXJ2YXRpb25zLgpEZXRhaWxlZCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgZGF0YSBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vYml0Lmx5LzJOMDhvMjIpLgoKWW91ciBnb2FsIGlzIHRvIGRldmVsb3AgYW5kIGNvbXBhcmUgdGhlIHBlcmZvcm1hbmNlIG9mIGEgd29yZCBlbWJlZGRpbmcgZGVlcApsZWFybmluZyBjbGFzc2lmaWVyIHRvIG9uZSB0aGF0IGluY29ycG9yYXRlcyBMU1RNIHNlcXVlbmNlIGVtYmVkZGluZy4gSSB3aWxsCmd1aWRlIHlvdSBhbG9uZyB0aGUgd2F5IGJ1dCB0aGlzIHByb2plY3QgZXhwZWN0cyB5b3UgdG8gZG8gbW9zdCBvZiB0aGUgd29yayBmcm9tCmltcG9ydGluZyBhbmQgcHJlcHJvY2Vzc2luZyB0ZXh0LCB0byBidWlsZGluZyB0aGUgbW9kZWxzLgoKTmVhcmx5IGFsbCB0aGUgY29kZSB0aGF0IHlvdSBuZWVkIGNhbiBiZSBmb3VuZCBpbiB0aGVzZSBub3RlYm9va3M6CgoqIFtJbnRybyB0byB3b3JkIGVtYmVkZGluZ3NdKGh0dHA6Ly9iaXQubHkvZGwtaW1kYi1lbWJlZGRpbmdzKQoqIFtJbnRybyB0byBMU1RNc10oaHR0cDovL2JpdC5seS9kbC1sc3RtLWludHJvKQoKX19fR29vZCBsdWNrIV9fXwoKIyBSZXF1aXJlbWVudHMKCmBgYHtyfQpsaWJyYXJ5KGtlcmFzKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShmcykKbGlicmFyeShnbHVlKQpsaWJyYXJ5KHRlc3R0aGF0KQpgYGAKCiMgSW1wb3J0IHRoZSBkYXRhCgpGb3IgdGhvc2UgaW4gdGhlIHdvcmtzaG9wIHdlIGhhdmUgYWxyZWFkeSBkb3dubG9hZGVkIHRoZSBtb3ZpZSByZXZpZXcgZGF0YSBmb3IKeW91IGludG8gdGhlIGAiL21hdGVyaWFscy9kYXRhL2Nvcm5lbGxfcmV2aWV3cyJgIGRpcmVjdG9yeS4gT3V0c2lkZSBvZiB0aGUKd29ya3Nob3AsIHlvdSBjYW4gZmluZCB0aGUgZG93bmxvYWQgaW5zdHJ1Y3Rpb25zIFtoZXJlXShodHRwOi8vYml0Lmx5L2RsLXJxbXRzKS4KCmBgYHtyfQojIGdldCBwYXRoIHRvIGRhdGEKaWYgKHN0cmluZ3I6OnN0cl9kZXRlY3QoaGVyZTo6aGVyZSgpLCAiY29uZi0yMDIwLXVzZXIiKSkgewogIG1vdmllX2RpciA8LSAiL2hvbWUvY29uZi0yMDIwLXVzZXIvZGF0YS9jb3JuZWxsX3Jldmlld3MvZGF0YSIKfSBlbHNlIHsKICBtb3ZpZV9kaXIgPC0gaGVyZTo6aGVyZSgibWF0ZXJpYWxzIiwgImRhdGEiLCAiY29ybmVsbF9yZXZpZXdzIiwgImRhdGEiKQp9Cgpmczo6ZGlyX3RyZWUobW92aWVfZGlyLCByZWN1cnNlID0gRkFMU0UpCmBgYAoKX19TdGVwIDFfXzogWW91IGNhbiBzZWUgdGhlIGRhdGEgaGF2ZSBhbHJlYWR5IGJlZW4gc2VwYXJhdGVkIGludG8gIHBvc2l0aXZlIHZzIApuZWdhdGl2ZSBzZXRzLiBUaGUgYWN0dWFsIHJldmlld3MgYXJlIGNvbnRhaW5lZCBpbiBpbmRpdmlkdWFsIC50eHQgZmlsZXMuIFNpbWlsYXIKdG8gW0ludHJvIHRvIHdvcmQgZW1iZWRkaW5nc10oaHR0cDovL2JpdC5seS9kbC1pbWRiLWVtYmVkZGluZ3MpLCBsZXQncyBnbyBhaGVhZAp1c2UgdGhpcyBzdHJ1Y3R1cmUgdG8gb3VyIGFkdmFudGFnZSBieSBpdGVyYXRpbmcgb3ZlciBlYWNoIHJldmlldyBhbmQuLi4KCjEuIGNyZWF0aW5nIHRoZSBwYXRoIHRvIGVhY2ggaW5kaXZpZHVhbCByZXZpZXcgZmlsZSwKMi4gY3JlYXRpbmcgYSBsYWJlbCBiYXNlZCBvbiB0aGUg4oCcbmVn4oCdIG9yIOKAnHBvc+KAnSBmb2xkZXIgdGhlIHJldmlldyBpcyBpbiwgYW5kCjMuIHNhdmluZyB0aGUgb3V0cHV0IGFzIGEgZGF0YSBmcmFtZSB3aXRoIGVhY2ggcmV2aWV3IG9uIGFuIGluZGl2aWR1YWwgcm93LgoKYGBge3J9CnRyYWluaW5nX2ZpbGVzIDwtIG1vdmllX2RpciAlPiUKICBkaXJfbHMoKSAlPiUKICBtYXAoZGlyX2xzKSAlPiUKICBzZXRfbmFtZXMoYmFzZW5hbWUpICU+JQogIHBseXI6OmxkcGx5KGRhdGFfZnJhbWUpICU+JQogIHNldF9uYW1lcyhjKCJsYWJlbCIsICJwYXRoIikpCgojIHlvdSBzaG91bGQgaGF2ZSAyMDAwIHRvdGFsIG9ic2VydmF0aW9ucwpleHBlY3RfZXF1YWwobnJvdyh0cmFpbmluZ19maWxlcyksIDIwMDApCmBgYAoKR28gYWhlYWQgYW5kIHRha2UgYSBsb29rIGF0IHlvdXIgZGF0YSBmcmFtZQoKYGBge3J9CnRyYWluaW5nX2ZpbGVzCmBgYAoKX19TdGVwIDJfXzogSG93IG1hbnkgb2JzZXJhdmF0aW9ucyBhcmUgaW4gZWFjaCByZXNwb25zZSBsYWJlbCAoaS5lLiAibmVnIiB2cyAicG9zIik/CgpgYGB7cn0KY291bnQodHJhaW5pbmdfZmlsZXMsIGxhYmVsKQpgYGAKCl9fU3RlcCAzX186IE5leHQsIGxldCdzIGl0ZXJhdGUgb3ZlciBlYWNoIHJvdyBhbmQKCjEuIHNhdmUgdGhlIGxhYmVsIGluIGEgbGFiZWxzIHZlY3RvciwKMi4gaW1wb3J0IHRoZSBtb3ZpZSByZXZpZXcsIGFuZAozLiBzYXZlIGluIGEgdGV4dHMgdmVjdG9yLgoKYGBge3J9Cm9icyA8LSBucm93KHRyYWluaW5nX2ZpbGVzKQpsYWJlbHMgPC0gdmVjdG9yKG1vZGUgPSAiaW50ZWdlciIsIGxlbmd0aCA9IG9icykKdGV4dHMgPC0gdmVjdG9yKG1vZGUgPSAiY2hhcmFjdGVyIiwgbGVuZ3RoID0gb2JzKQoKZm9yIChmaWxlIGluIHNlcV9sZW4ob2JzKSkgewogIGxhYmVsIDwtIHRyYWluaW5nX2ZpbGVzW1tmaWxlLCAibGFiZWwiXV0KICBwYXRoIDwtIHRyYWluaW5nX2ZpbGVzW1tmaWxlLCAicGF0aCJdXQogIAogIGxhYmVsc1tmaWxlXSA8LSBpZmVsc2UobGFiZWwgPT0gIm5lZyIsIDAsIDEpCiAgdGV4dHNbZmlsZV0gPC0gcmVhZENoYXIocGF0aCwgbmNoYXJzID0gZmlsZS5zaXplKHBhdGgpKSAKfQpgYGAKClRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIHlvdXIgdGV4dCBzaG91bGQgYmUgZXF1YWwgdG8gdGhlIG51bWJlciBvZiByZXNwb25zZXMuCgpgYGB7cn0KZXhwZWN0X2VxdWFsKGxlbmd0aCh0ZXh0cyksIGxlbmd0aChsYWJlbHMpKQpgYGAKCkdvIGFoZWFkIGFuZCBjaGVjayBvdXQgdGhlIHRleHQgb2YgYSBjb3VwbGUgcmV2aWV3cy4KCmBgYHtyfQp0ZXh0c1sxXQpgYGAKCiMgRGF0YSBleHBsb3JhdGlvbgoKX19TdGVwIDRfXzogQmVmb3JlIHByZXByb2Nlc3NpbmcsIGxldCdzIGdldCBhIHNlbnNlIG9mIHR3byBhdHRyaWJ1dGVzIHRoYXQgd2lsbApoZWxwIHVzIHNldCB0d28gb2Ygb3VyIHByZXByb2Nlc3NpbmcgaHlwZXJwYXJhbWV0ZXJzOgoKMS4gSG93IG1hbnkgdW5pcXVlIHdvcmRzIGV4aXN0IGFjcm9zcyBhbGwgb3VyIHJldmlld3M/IFdlJ2xsIHVzZSB0aGlzIHRvIGRldGVybWluZQphIGdvb2Qgc3RhcnRpbmcgcG9pbnQgZm9yIHByZXByb2Nlc3Npbmcgb3VyIHRleHQuCgoyLiBXaGF0IGlzIHRoZSBkaXN0cmlidXRpb24gb2Ygd29yZCBjb3VudCBhY3Jvc3MgYWxsIG1vdmllIHJldmlld3MgKGkuZS4gbWVhbiwgCm1lZGlhbik/IFdlJ2xsIHVzZSB0aGlzIHRvIGRldGVybWluZSBhIGdvb2Qgc3RhcnRpbmcgcG9pbnQgZm9yIHByZXByb2Nlc3Npbmcgb3VyCnRleHQuCgpgYGB7cn0KdGV4dF9kZiA8LSB0ZXh0cyAlPiUKICB0aWJibGUoLm5hbWVfcmVwYWlyID0gfiAidGV4dCIpICU+JQogIG11dGF0ZSh0ZXh0X2xlbmd0aCA9IHN0cl9jb3VudCh0ZXh0LCAiXFx3KyIpKQoKdW5pcXVlX3dvcmRzIDwtIHRleHRfZGYgJT4lCiAgdGlkeXRleHQ6OnVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lCiAgcHVsbCh3b3JkKSAlPiUKICBuX2Rpc3RpbmN0KCkKCmF2Z19yZXZpZXdfbGVuZ3RoIDwtIG1lZGlhbih0ZXh0X2RmJHRleHRfbGVuZ3RoLCBuYS5ybSA9IFRSVUUpCiAgCmdncGxvdCh0ZXh0X2RmLCBhZXModGV4dF9sZW5ndGgpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMCwgZmlsbCA9ICJncmV5NzAiLCBjb2xvciA9ICJncmV5NDAiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYXZnX3Jldmlld19sZW5ndGgsIGNvbG9yID0gInJlZCIsIGx0eSA9ICJkYXNoZWQiKSArCiAgc2NhbGVfeF9sb2cxMCgiIyB3b3JkcyIpICsKICBnZ3RpdGxlKGdsdWUoIk1lZGlhbiByZXZpZXcgbGVuZ3RoIGlzIHthdmdfcmV2aWV3X2xlbmd0aH0gd29yZHMiKSwKICAgICAgICAgIHN1YnRpdGxlID0gZ2x1ZSgiVG90YWwgbnVtYmVyIG9mIHVuaXF1ZSB3b3JkcyBpcyB7dW5pcXVlX3dvcmRzfSIpKQpgYGAKCiMgRGF0YSBwcmVwcm9jZXNzaW5nCgpfX1N0ZXAgNV9fOiBOb3cgbGV0J3MgdG9rZW5pemUgb3VyIHRleHQgc2VxdWVuY2VzLiBUbyBkbyBzbyB3ZToKCjEuIFNwZWNpZnkgaG93IG1hbnkgd29yZHMgd2Ugd2FudCB0byBpbmNsdWRlLiBSZW1lbWJlciwgYSBnb29kIHN0YXJ0aW5nIHBvaW50CiAgIHRvIHVzZSByb3VnaGx5IDUwJSBvZiB0aGUgbnVtYmVyIG9mIHVuaXF1ZSB3b3JkcyBpbiB0aGUgZGF0YS4gVGhpcyBpcyBhIGh5cGVyLQogICBwYXJhbWV0ZXIgdGhhdCB5b3UgY2FuIGFsd2F5cyBjb21lIGJhY2sgdG8gYW5kIGFkanVzdC4KMi4gQ3JlYXRlIGEgYHRleHRfdG9rZW5pemVyYCBvYmplY3Qgd2hpY2ggZGVmaW5lcyBob3cgd2Ugd2FudCB0byBwcmVwcm9jZXNzIHRoZQogICB0ZXh0LiBUaGUgZGVmYXVsdHMgYXJlIHN1ZmZpY2llbnQuCjMuIEFwcGx5IHRoZSB0b2tlbml6ZXIgdG8gb3VyIHRleHQgd2l0aCBgZml0X3RleHRfdG9rZW5pemVyKClgLgo0LiBFeHRyYWN0IG91ciB2ZWN0b3JpemVkIHJldmlldyBkYXRhIHdpdGggYHRleHRzX3RvX3NlcXVlbmNlcygpYC4KCmBgYHtyfQojIDEKdG9wX25fd29yZHMgPC0gMjAwMDAKCiMgMi0zCnRva2VuaXplciA8LSB0ZXh0X3Rva2VuaXplcihudW1fd29yZHMgPSB0b3Bfbl93b3JkcykgJT4lIAogIGZpdF90ZXh0X3Rva2VuaXplcih0ZXh0cykKCiMgNApzZXF1ZW5jZXMgPC0gdGV4dHNfdG9fc2VxdWVuY2VzKHRva2VuaXplciwgdGV4dHMpCmBgYAoKR28gYWhlYWQgYW5kIGNoZWNrIG91dCB0aGUgZmlyc3QgdmVjdG9yaXplZCBzZXF1ZW5jZS4gU2hvdWxkIGxvb2sgZmFtaWxpYXIgZnJvbQplYXJsaWVyIG1vZHVsZXMuCgpgYGB7cn0KIyBUaGUgdmVjdG9yaXplZCBmaXJzdCBpbnN0YW5jZToKc2VxdWVuY2VzW1sxXV0KYGBgCgpXZSBjYW4gc2VlIGhvdyBvdXIgdG9rZW5pemVyIGNvbnZlcnRlZCBvdXIgb3JpZ2luYWwgdGV4dCB0byBhIGNsZWFuZWQgdXAgCnZlcnNpb246CgpgYGB7cn0gCmNhdChjcmF5b246OmJsdWUoIk9yaWdpbmFsIHRleHQ6XG4iKSkKdGV4dHNbWzFdXQoKY2F0KGNyYXlvbjo6Ymx1ZSgiXG5SZXZpc2VkIHRleHQ6XG4iKSkKcGFzdGUodW5saXN0KHRva2VuaXplciRpbmRleF93b3JkKVtzZXF1ZW5jZXNbWzFdXV0gLCBjb2xsYXBzZSA9ICIgIikKYGBgCgpfX1N0ZXAgNl9fOiBOZXh0LCBzaW5jZSBlYWNoIHJldmlldyBpcyBhIGRpZmZlcmVudCBsZW5ndGgsIHdlIG5lZWQgdG8gbGltaXQKb3Vyc2VsdmVzIHRvIGEgY2VydGFpbiBudW1iZXIgb2Ygd29yZHMgc28gdGhhdCBhbGwgb3VyIHRleHQgc2VxdWVuY2VzIGFyZSB0aGUKc2FtZSBsZW5ndGguIAoKVG8gZG8gc28gd2U6CgoxLiBTcGVjaWZ5IHRoZSBtYXggbGVuZ3RoIGZvciBlYWNoIHNlcXVlbmNlLiBZb3UgY2FuIHN0YXJ0IG91dCB3aXRoIDUwMCBhbmQgdGhlbgp0dW5lIHRoaXMgaHlwZXJwYXJhbWV0ZXIgbGF0ZXIuCjIuIFVzZSBgcGFkX3NlcXVlbmNlcygpYCB0byB0cnVuY2F0ZSBvciBwYWQgcmV2aWV3cyB0byB0aGUgc3BlY2lmaWVkIGBtYXhfbGVuYC4KCmBgYHtyfQptYXhfbGVuIDwtIDUwMApmZWF0dXJlcyA8LSBwYWRfc2VxdWVuY2VzKHNlcXVlbmNlcywgbWF4bGVuID0gbWF4X2xlbikKYGBgCgpZb3VyIG5vdyBoYXZlIHlvdXIgcHJlcHJvY2Vzc2VkIGZlYXR1cmUgZGF0YSB0aGF0IGlzIGEgMkQgdGVuc29yIChha2EgbWF0cml4KQphbmQgY29udGFpbnMgMjAwMCBvYnNlcnZhdGlvbnMgKHJvd3MpIGFuZCBgbWF4X2xlbmAgY29sdW1ucy4KCmBgYHtyfQpkaW0oZmVhdHVyZXMpCgpleHBlY3RfZXF1YWwoY2xhc3MoZmVhdHVyZXMpLCAibWF0cml4IikKZXhwZWN0X2VxdWFsKGRpbShmZWF0dXJlcyksIGMob2JzLCBtYXhfbGVuKSkKYGBgCgpZb3UgY2FuIHNlZSBob3cgdGhlIGZpbmFsIHByZXByb2Nlc3NlZCBzZXF1ZW5jZSBsb29rcyBmb3IgdGhlIGZpcnN0IG1vdmllIHJldmlldwp3aXRoIHRoZSBmb2xsb3dpbmcgY29kZToKCmBgYHtyfQpwYXN0ZSh1bmxpc3QodG9rZW5pemVyJGluZGV4X3dvcmQpW2ZlYXR1cmVzWzEsXV0sIGNvbGxhcHNlID0gIiAiKQpgYGAKCiMgTW9kZWwgdHJhaW5pbmcKCl9fU3RlcCA3X186IFRvIHRyYWluIG91ciBtb2RlbCB3ZSB3aWxsIHVzZSB0aGUgYHZhbGlkYXRpb25fc3BsaXRgIHByb2NlZHVyZQp3aXRoaW4gYGZpdCgpYC4gUmVtZW1iZXIsIHRoaXMgdGFrZXMgdGhlIGxhc3QgWFglIG9mIG91ciBkYXRhIHRvIGJlIHVzZWQgYXMgb3VyCnZhbGlkYXRpb24gc2V0LiBCdXQgaWYgeW91IHJlY2FsbCwgb3VyIGRhdGEgd2FzIG9yZ2FuaXplZCBpbiAibmVnIiBhbmQgInBvcyIKZm9sZGVycyBzbyB3ZSBzaG91bGQgcmFuZG9taXplIG91ciBkYXRhIHRvIG1ha2Ugc3VyZSBvdXIgdmFsaWRhdGlvbiBzZXQgZG9lc27igJl0CmVuZCB1cCBiZWluZyBhbGwgcG9zaXRpdmUgb3IgbmVnYXRpdmUgcmV2aWV3cyEKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCmluZGV4IDwtIHNhbXBsZSgxOm5yb3coZmVhdHVyZXMpKQoKeF90cmFpbiA8LSBmZWF0dXJlc1tpbmRleCwgXQp5X3RyYWluIDwtIGxhYmVsc1tpbmRleF0KCiMgdGhlcmUgc2hvdWxkIGJlIDIgdW5pcXVlIHZhbHVlcyAoMCAtIG5lZywgMSAtIHBvcykgaW4gbGFzdCAzMCUgb2YgZGF0YQpleHBlY3RfZXF1YWwoCiAgbGVuZ3RoKHVuaXF1ZSh5X3RyYWluW2Zsb29yKGxlbmd0aCh5X3RyYWluKSAqIDAuNyk6bGVuZ3RoKHlfdHJhaW4pXSkpLCAKICAyCiAgKQpgYGAKCiMjIFdvcmQgZW1iZWRkaW5nIG1vZGVsCgpfX1N0ZXAgOF9fOiBXZSdyZSBub3cgcmVhZHkgdG8gZG8gbW9kZWxpbmcuIEZvciBvdXIgZmlyc3QgbW9kZWwsIGxldCdzIGNyZWF0ZSBhCm1vZGVsIHRoYXQ6CgoxLiBhcHBsaWVzIGEgd29yZCBlbWJlZGRpbmcgbGF5ZXIKICAgLSBgaW5wdXRfZGltYCBzaG91bGQgZXF1YWwgYHRvcF9uX3dvcmRzYAogICAtIGBpbnB1dF9sZW5ndGhgIHNob3VsZCBlcXVhbCBgbWF4X2xlbmAKICAgLSBzdGFydCB3aXRoIGBvdXRwdXRfZGltYCA9IDE2CjIuIGZsYXR0ZW5zIHRoZSBlbWJlZGRpbmdzCjMuIGNsYXNzaWZpZXMgd2l0aCBhIGRlbnNlIGxheWVyCgpZb3UgY2FuIHVzZSBlYXJseSBzdG9wcGluZyBpZiB5b3UnZCBsaWtlIGJ1dCBmb3IgdGhlIGZpcnN0IG1vZGVsOgoKKiB1c2UgdGhlIGRlZmF1bHQgbGVhcm5pbmcgcmF0ZQoqIDIwIGVwb2NocyBpcyBtb3JlIHRoYW4gZW5vdWdoCiogdXNlIGEgYmF0Y2ggc2l6ZSBvZiAzMgoqIHVzZSBhIHZhbGlkYXRpb24gc3BsaXQgb2YgMzAlCgpgYGB7cn0KbW9kZWxfYmFzaWMgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQogIGxheWVyX2VtYmVkZGluZygKICAgIGlucHV0X2RpbSA9IHRvcF9uX3dvcmRzLCAgIyBudW1iZXIgb2Ygd29yZHMgd2UgYXJlIGNvbnNpZGVyaW5nCiAgICBpbnB1dF9sZW5ndGggPSBtYXhfbGVuLCAgICMgbGVuZ3RoIHRoYXQgd2UgaGF2ZSBzZXQgZWFjaCByZXZpZXcgdG8KICAgIG91dHB1dF9kaW0gPSAxNiAgICAgICAgICAgIyBsZW5ndGggb2Ygb3VyIHdvcmQgZW1iZWRkaW5ncwogICAgKSAlPiUgIAogIGxheWVyX2ZsYXR0ZW4oKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEsIGFjdGl2YXRpb24gPSAic2lnbW9pZCIpCiAgCm1vZGVsX2Jhc2ljICU+JSBjb21waWxlKAogIG9wdGltaXplciA9ICJybXNwcm9wIiwKICBsb3NzID0gImJpbmFyeV9jcm9zc2VudHJvcHkiLAogIG1ldHJpY3MgPSAiYWNjdXJhY3kiCikKCmhpc3RvcnlfYmFzaWMgPC0gbW9kZWxfYmFzaWMgJT4lIAogIGZpdCgKICAgIHhfdHJhaW4sIHlfdHJhaW4sCiAgICBlcG9jaHMgPSAyMCwKICAgIGJhdGNoX3NpemUgPSAzMiwKICAgIHZhbGlkYXRpb25fc3BsaXQgPSAwLjMsCiAgICBjYWxsYmFja3MgPSBsaXN0KAogICAgICBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDMsIHJlc3RvcmVfYmVzdF93ZWlnaHRzID0gVFJVRSkKICAgICAgKQogICAgKQpgYGAKClJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gY2hlY2sgb3V0IHlvdXIgb3B0aW1hbCBsb3NzIGFuZCBjb3JyZXNwb25kaW5nIGFjY3VyYWN5LgoKYGBge3J9CmJlc3RfZXBvY2ggPC0gd2hpY2gubWluKGhpc3RvcnlfYmFzaWMkbWV0cmljcyR2YWxfbG9zcykKYmVzdF9sb3NzIDwtIGhpc3RvcnlfYmFzaWMkbWV0cmljcyR2YWxfbG9zc1tiZXN0X2Vwb2NoXSAlPiUgcm91bmQoMykKYmVzdF9hY2MgPC0gaGlzdG9yeV9iYXNpYyRtZXRyaWNzJHZhbF9hY2N1cmFjeVtiZXN0X2Vwb2NoXSAlPiUgcm91bmQoMykKCmdsdWUoIk91ciBvcHRpbWFsIGxvc3MgaXMge2Jlc3RfbG9zc30gd2l0aCBhbiBhY2N1cmFjeSBvZiB7YmVzdF9hY2N9IikKYGBgCgojIyBXb3JkIGVtYmVkZGluZyArIExTVE0gbW9kZWwKCl9fU3RlcCA5X186IE5vdyBsZXQncyBidWlsZCBvbiB0byB0aGUgcHJldmlvdXMgbW9kZWwgYnkgYWRkaW5nIGFuIExTVE0gbGF5ZXIKYWZ0ZXIgdGhlIGBsYXllcl9lbWJlZGRpbmdgIGxheWVyLiBXaGVuIGZlZWRpbmcgYW4gZW1iZWRkaW5nIGxheWVyIGludG8gYW4gTFNUTQpsYXllciB5b3UgX19kbyBub3RfXyBuZWVkIHRvIGZsYXR0ZW4gdGhlIGxheWVyLiBSZWZlcmVuY2UgdGhlIFtJbnRybyB0byBMU1RNcyBub3RlYm9va10oaHR0cDovL2JpdC5seS9kbC1sc3RtLWludHJvI3RyYWluLWFuLWxzdG0pLiBGb3IgdGhpcyBmaXJzdCBMU1RNIG1vZGVsCnVzZSBgdW5pdHMgPSAzMmAuCgpgYGB7cn0KbW9kZWxfbHN0bSA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZW1iZWRkaW5nKAogICAgaW5wdXRfZGltID0gdG9wX25fd29yZHMsICAjIG51bWJlciBvZiB3b3JkcyB3ZSBhcmUgY29uc2lkZXJpbmcKICAgIGlucHV0X2xlbmd0aCA9IG1heF9sZW4sICAgIyBsZW5ndGggdGhhdCB3ZSBoYXZlIHNldCBlYWNoIHJldmlldyB0bwogICAgb3V0cHV0X2RpbSA9IDE2ICAgICAgICAgICAgIyBsZW5ndGggb2Ygb3VyIHdvcmQgZW1iZWRkaW5ncwogICAgKSAlPiUgIAogIGxheWVyX2xzdG0odW5pdHMgPSAzMikgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxLCBhY3RpdmF0aW9uID0gInNpZ21vaWQiKSAKCm1vZGVsX2xzdG0gJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gInJtc3Byb3AiLAogIGxvc3MgPSAiYmluYXJ5X2Nyb3NzZW50cm9weSIsCiAgbWV0cmljcyA9ICJhY2N1cmFjeSIKKQoKaGlzdG9yeV9sc3RtIDwtIG1vZGVsX2xzdG0gJT4lIGZpdCgKICB4X3RyYWluLCB5X3RyYWluLAogIGVwb2NocyA9IDIwLAogIGJhdGNoX3NpemUgPSAzMiwKICB2YWxpZGF0aW9uX3NwbGl0ID0gMC4zLAogIGNhbGxiYWNrcyA9IGxpc3QoCiAgICBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDMsIHJlc3RvcmVfYmVzdF93ZWlnaHRzID0gVFJVRSkKICAgICkKKQpgYGAKClJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gY2hlY2sgb3V0IHlvdXIgb3B0aW1hbCBsb3NzIGFuZCBjb3JyZXNwb25kaW5nIGFjY3VyYWN5LgoKMS4gSG93IGRvZXMgaXQgY29tcGFyZSB0byB0aGUgd29yZCBlbWJlZGRpbmcgb25seSBtb2RlbD8KMi4gV2h5IGRvIHlvdSB0aGluayB0aGVyZSBpcyBhIGRpZmZlcmVuY2U/CgpgYGB7cn0KYmVzdF9lcG9jaCA8LSB3aGljaC5taW4oaGlzdG9yeV9sc3RtJG1ldHJpY3MkdmFsX2xvc3MpCmJlc3RfbG9zcyA8LSBoaXN0b3J5X2xzdG0kbWV0cmljcyR2YWxfbG9zc1tiZXN0X2Vwb2NoXSAlPiUgcm91bmQoMykKYmVzdF9hY2MgPC0gaGlzdG9yeV9sc3RtJG1ldHJpY3MkdmFsX2FjY3VyYWN5W2Jlc3RfZXBvY2hdICU+JSByb3VuZCgzKQoKZ2x1ZSgiT3VyIG9wdGltYWwgbG9zcyBpcyB7YmVzdF9sb3NzfSB3aXRoIGFuIGFjY3VyYWN5IG9mIHtiZXN0X2FjY30iKQpgYGAKCiMjIFNlYXJjaCBmb3IgYSBiZXR0ZXIgbW9kZWwKCl9fU3RlcCAxMF9fOiBTcGVuZCB0aGUgcmVzdCBvZiB0aGUgdGltZSB0dW5pbmcgaHlwZXJwYXJhbWV0ZXJzIGFuZCBzZWUgaWYgeW91CmNhbiBmaW5kIGEgYmV0dGVyIG1vZGVsLiBUaGluZ3MgeW91IGNhbiB0cnk6CgoqIFByZXByb2Nlc3NpbmcgaHlwZXJwYXJhbWV0ZXJzCiAgIC0gYWRqdXN0IHRoZSBudW1iZXIgb2Ygd29yZHMgdG8gcmV0YWluIGluIHRoZSB3b3JkIGluZGV4IChgdG9wX25fd29yZHNgKQogICAtIGFkanVzdCB0aGUgc2l6ZSBvZiB0aGUgc2VxdWVuY2VzIChgbWF4X2xlbmApCiogV29yZCBlbWJlZGRpbmcgbGF5ZXIKICAgLSBhZGp1c3QgdGhlIGBvdXRwdXRfZGltYAoqIExTVE0gbGF5ZXIKICAgLSBhZGp1c3QgdGhlIG51bWJlciBvZiBgdW5pdHNgCiAgIC0gYWRkIGRyb3BvdXQgKHJlZiBodHRwOi8vYml0Lmx5L2RsLWxzdG0taW50cm8jeW91ci10dXJuLTVtaW4tMSkKICAgLSBtYXliZSBldmVuIGFkZCBhIDJuZCBMU1RNIGxheWVyCiogT3RoZXIKICAgLSBhZGp1c3QgdGhlIGxlYXJuaW5nIHJhdGUgKG9yIGV2ZW4gdGhlIG9wdGltaXplciAoaS5lLiB0cnkgImFkYW0iKSkKICAgLSBhZGp1c3QgdGhlIGBiYXRjaF9zaXplYAogICAtIGFkZCBhIGNhbGxiYWNrIHRvIGFkanVzdCB0aGUgbGVhcm5pbmcgdXBvbiBwbGF0ZWF1aW5nCgpb8J+PoF0oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8tY29uZi0yMDIwL2RsLWtlcmFzLXRmKQ==