In this case study, our objective is to predict the sales price of a home. This is a regression problem since the goal is to predict any real number across some spectrum ($119,201, $168,594, $301,446, etc). To predict the sales price, we will use numeric and categorical features of the home.

As you proceed, you’ll work through the steps we discussed in the last module:

  1. Prepare data
  2. Balance batch size with a default learning rate
  3. Tune the adaptive learning rate optimizer
  4. Add callbacks to control training
  5. Explore model capacity
  6. Regularize overfitting
  7. Repeat steps 1-6
  8. Evaluate final model results

Package Requirements

library(keras)     # for deep learning
library(testthat)  # unit testing
library(tidyverse) # for dplyr, ggplot2, etc.
library(rsample)   # for data splitting
library(recipes)   # for feature engineering

Step 0: Our Data

The Ames housing dataset

For this case study we will use the Ames housing dataset provided by the AmesHousing package.

ames <- AmesHousing::make_ames()
dim(ames)

Understanding our data

This data has been partially cleaned up and has no missing data:

sum(is.na(ames))

But this tabular data is a combination of numeric and categorical data that we need to address.

str(ames)

The numeric variables are on different scales. For example:

ames %>%
  select(Lot_Area, Lot_Frontage, Year_Built, Gr_Liv_Area, Garage_Cars, Mo_Sold) %>%
  gather(feature, value) %>%
  ggplot(aes(feature, value)) +
  geom_boxplot() +
  scale_y_log10(labels = scales::comma)

There are categorical features that could be ordered:

ames %>%
  select(matches("(Qual|Cond|QC|Qu)$")) %>%
  str()

And some of the categorical features have many levels:

ames %>%
  select_if(~ is.factor(.) & length(levels(.)) > 8) %>%
  glimpse()

Consequently, our first challenge is transforming this dataset into numeric tensors that our model can use.

Step 1: Prep the Data

Create train & test splits

One of the first things we want to do is create a train and test set as you probably noticed that we do not have a train and test set similar to how MNIST was already set up for us. We can use the rsample package to create our train and test datasets.

Note: This will randomly select the 70/30 split so we are randomizing our data with this process.

set.seed(123)
ames_split <- initial_split(ames, prop = 0.7)
ames_train <- analysis(ames_split)
ames_test <- assessment(ames_split)

dim(ames_train)
dim(ames_test)

Vectorize and scaling

All inputs and response values in a neural network must be tensors of either floating-point or integer data. Moreover, our feature values should not be relatively large compared to the randomized initial weights and all our features should take values in roughly the same range.

Consequently, we need to vectorize our data into a format conducive to neural networks ℹ️. For this data set, we’ll transform our data by:

  1. removing any zero-variance (or near zero-variance) features
  2. condensing unique levels of categorical features to “other”
  3. ordinal encoding the quality features
  4. normalize numeric feature distributions
  5. standardizing numeric features to mean = 0, std dev = 1
  6. one-hot encoding remaining categorical features

Note: we’re using the recipes package (https://tidymodels.github.io/recipes)

blueprint <- recipe(Sale_Price ~ ., data = ames_train) %>%
  step_nzv(all_nominal()) %>%                                       # step #1
  step_other(all_nominal(), threshold = .01, other = "other") %>%   # step #2
  step_integer(matches("(Qual|Cond|QC|Qu)$")) %>%                   # step #3
  step_YeoJohnson(all_numeric(), -all_outcomes()) %>%               # step #4
  step_center(all_numeric(), -all_outcomes()) %>%                   # step #5
  step_scale(all_numeric(), -all_outcomes()) %>%                    # step #5
  step_dummy(all_nominal(), -all_outcomes(), one_hot = TRUE)        # step #6

blueprint

This next step computes any relavent information (mean and std deviation of numeric features, names of one-hot encoded features) on the training data so there is no information leakage from the test data.

prepare <- prep(blueprint, training = ames_train)
prepare

We can now vectorize our training and test data. If you scroll through the data you will notice that all features are now numeric and are either 0/1 (one hot encoded features) or have mean 0 and generally range between -3 and 3.

baked_train <- bake(prepare, new_data = ames_train)
baked_test <- bake(prepare, new_data = ames_test)

# unit testing to ensure all columns are numeric
expect_equal(map_lgl(baked_train, ~ !is.numeric(.)) %>% sum(), 0)
expect_equal(map_lgl(baked_test, ~ !is.numeric(.)) %>% sum(), 0)

baked_train

Lastly, we need to create the final feature and response objects for train and test data. Since keras and tensorflow require our features & labels to be seperate objects we need to separate them. In doing so, our features need to be a 2D tensor which is why we apply as.matrix and our response needs to be a vector which is why we apply pull.

x_train <- select(baked_train, -Sale_Price) %>% as.matrix()
y_train <- baked_train %>% pull(Sale_Price)

x_test <- select(baked_test, -Sale_Price) %>% as.matrix()
y_test <- baked_test %>% pull(Sale_Price)

# unit testing to x & y tensors have same number of observations
expect_equal(nrow(x_train), length(y_train))
expect_equal(nrow(x_test), length(y_test))

Our final feature set now has 188 input variables:

dim(x_train)
dim(x_test)

Step 2: Balance batch size with a default learning rate

To get started, let’s build a simple model with…

  • a single layer model with 128 units in the hidden layer. We have 188 features and 1 response node so a good starting point is mean(c(188, 1)) and then round up to the nearest value in the \(2^s\) range (i.e. 32, 64, 128, 256, 512).
  • a basic SGD optimizer
  • use a mean square logarithmic error (“msle”)
  • also track the mean absolute error metric (“mae”)
  • 20% validation split

Now, start with the default batch size of 32 and then compare with smaller values (i.e. 16) and larger values (i.e. 128). You’re looking to balance the progression of the loss learning curve and the training spead.

n_feat <- ncol(x_train)

model <- keras_model_sequential() %>% 
  layer_dense(units = ____, activation = "relu", input_shape = ____) %>%
  layer_dense(units = ____)

model %>% compile(
    optimizer = ____,
    loss = ____,
    metrics = ____
  )

history <- model %>% fit(
  x_train,
  y_train,
  ____ ,
  validation_split = 0.2
)
history
plot(history)

Step 3: Tune the adaptive learning rate optimizer

Now go head and start assessing different adaptive learning rates such as:

  • SGD+momentum
  • RMSprop
  • Adamp

Try a variety of learning rates. Recall that we typically start assessing rates on a logarithmic scale (i.e. 0.1, 0.01, …, 0.0001).

model <- keras_model_sequential() %>% 
  layer_dense(units = ____, activation = ____, input_shape = ____) %>%
  layer_dense(units = 1)

model %>% compile(
    optimizer = ____,
    loss = "msle",
    metrics = "mae"
  )

history <- model %>% fit(
  x_train,
  y_train,
  batch_size = ____,
  validation_split = 0.2
)
history
plot(history)

Step 4: Add callbacks to control training

Add the following callbacks and see if your performance improves:

  • early stopping with patience = 3 and min_delta = 0.00001
  • learning rate reduction upon a plateau with patience = 1
model <- keras_model_sequential() %>% 
  layer_dense(units = 128, activation = "relu", input_shape = ____) %>%
  layer_dense(units = 1)

model %>% compile(
    optimizer = ____,
    loss = "msle",
    metrics = "mae"
  )

history <- model %>% fit(
  x_train,
  y_train,
  batch_size = ____,
  epochs = ____,
  validation_split = 0.2,
  callbacks = list(
    # <add early stopping callback>,
    # <add learning rate reduction callback>
  )
)
history
plot(history)

Plotting the learning rate shows that it reduced multiple times during training:

plot(history$metrics$lr)

Step 5: Explore model capacity

Now start to explore different widths and depths to your model.

  • Assess a single layer with 128, 256, 512, and 1024 nodes
  • Assess 1, 2, and 3 hidden layers
model <- keras_model_sequential() %>% 
  layer_dense(units = ____, activation = "relu", input_shape = n_feat) %>%
  # <add more layers>
  layer_dense(units = 1)

model %>% compile(
    optimizer = ____,
    loss = "msle",
    metrics = "mae"
  )

history <- model %>% fit(
  x_train,
  y_train,
  batch_size = ____,
  epochs = ____,
  validation_split = 0.2,
  callbacks = list(
    callback_early_stopping(patience = ____, min_delta = _____),
    callback_reduce_lr_on_plateau(patience = ____)
  )
)
history
plot(history) + scale_y_log10()

Step 6: Regularize overfitting

If your model is overfitting, try to add…

  • weight decay (i.e. kernel_regularizer = regularizer_l2(l = xxx)). Remember, we typically start by assessing values on logarithmic scale [0.1, 0.00001].
  • dropout (layer_dropout()) between each layer. Remember, dropout rates typically range from 20-50%.
model <- ____() %>% 
  layer_dense() %>%
  # <add more layers to match your last model>
  layer_dense(units = 1)

model %>% ____(
    optimizer = ____,
    loss = ____,
    metrics = ____
  )

history <- model %>% ____(
  x_train,
  y_train,
  batch_size = ____,
  epochs = ____,
  validation_split = 0.2,
  callbacks = list(
    callback_____,
    callback_____
  )
)
history

Step 7: Repeat steps 1-6

As this point we could repeat the process and…

  1. Prepare data
    • try to find additional data to add
    • try new feature engineering approaches
  2. Balance batch size with a default learning rate
    • reassess batch size
  3. Tune the adaptive learning rate optimizer
    • fine tune our learning rate
    • see if the current optimizer still outperforms others
  4. Add callbacks to control training
    • maybe assess more sophisticated learning rate schedulers (i.e. cyclical learning rates)
  5. Explore model capacity
    • after some tweaks we may want to reassess model capacity combinations
  6. Regularize overfitting

🏠

LS0tCnRpdGxlOiAiTWluaS1wcm9qZWN0OiBBbWVzIC0tIFJlZ3Jlc3Npb24gdG8gcHJlZGljdCBBbWVzLCBJQSBIb21lIFNhbGVzIFByaWNlcyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UpCmdncGxvdDI6OnRoZW1lX3NldChnZ3Bsb3QyOjp0aGVtZV9taW5pbWFsKCkpCmBgYAoKSW4gdGhpcyBjYXNlIHN0dWR5LCBvdXIgb2JqZWN0aXZlIGlzIHRvIHByZWRpY3QgdGhlIHNhbGVzIHByaWNlIG9mIGEgaG9tZS4gVGhpcyAKaXMgYSBfcmVncmVzc2lvbl8gcHJvYmxlbSBzaW5jZSB0aGUgZ29hbCBpcyB0byBwcmVkaWN0IGFueSByZWFsIG51bWJlciBhY3Jvc3MKc29tZSBzcGVjdHJ1bSAoXCQxMTksMjAxLCBcJDE2OCw1OTQsIFwkMzAxLDQ0NiwgZXRjKS4gVG8gcHJlZGljdCB0aGUgc2FsZXMgCnByaWNlLCB3ZSB3aWxsIHVzZSBudW1lcmljIGFuZCBjYXRlZ29yaWNhbCBmZWF0dXJlcyBvZiB0aGUgaG9tZS4KCkFzIHlvdSBwcm9jZWVkLCB5b3UnbGwgd29yayB0aHJvdWdoIHRoZSBzdGVwcyB3ZSBkaXNjdXNzZWQgaW4gdGhlIGxhc3QgbW9kdWxlOgoKMS4gUHJlcGFyZSBkYXRhCjIuIEJhbGFuY2UgYmF0Y2ggc2l6ZSB3aXRoIGEgZGVmYXVsdCBsZWFybmluZyByYXRlCjMuIFR1bmUgdGhlIGFkYXB0aXZlIGxlYXJuaW5nIHJhdGUgb3B0aW1pemVyCjQuIEFkZCBjYWxsYmFja3MgdG8gY29udHJvbCB0cmFpbmluZwo1LiBFeHBsb3JlIG1vZGVsIGNhcGFjaXR5CjYuIFJlZ3VsYXJpemUgb3ZlcmZpdHRpbmcKNy4gUmVwZWF0IHN0ZXBzIDEtNgo4LiBFdmFsdWF0ZSBmaW5hbCBtb2RlbCByZXN1bHRzCgojIFBhY2thZ2UgUmVxdWlyZW1lbnRzCgpgYGB7ciBsb2FkLXBrZ3N9CmxpYnJhcnkoa2VyYXMpICAgICAjIGZvciBkZWVwIGxlYXJuaW5nCmxpYnJhcnkodGVzdHRoYXQpICAjIHVuaXQgdGVzdGluZwpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBmb3IgZHBseXIsIGdncGxvdDIsIGV0Yy4KbGlicmFyeShyc2FtcGxlKSAgICMgZm9yIGRhdGEgc3BsaXR0aW5nCmxpYnJhcnkocmVjaXBlcykgICAjIGZvciBmZWF0dXJlIGVuZ2luZWVyaW5nCmBgYAoKIyBTdGVwIDA6IE91ciBEYXRhCgojIyBUaGUgQW1lcyBob3VzaW5nIGRhdGFzZXQKCkZvciB0aGlzIGNhc2Ugc3R1ZHkgd2Ugd2lsbCB1c2UgdGhlIFtBbWVzIGhvdXNpbmcgZGF0YXNldF0oaHR0cDovL2pzZS5hbXN0YXQub3JnL3YxOW4zL2RlY29jay5wZGYpIApwcm92aWRlZCBieSB0aGUgX19BbWVzSG91c2luZ19fIHBhY2thZ2UuCgpgYGB7ciBnZXQtZGF0YX0KYW1lcyA8LSBBbWVzSG91c2luZzo6bWFrZV9hbWVzKCkKZGltKGFtZXMpCmBgYAoKIyMgVW5kZXJzdGFuZGluZyBvdXIgZGF0YQoKVGhpcyBkYXRhIGhhcyBiZWVuIHBhcnRpYWxseSBjbGVhbmVkIHVwIGFuZCBoYXMgbm8gbWlzc2luZyBkYXRhOgoKYGBge3J9CnN1bShpcy5uYShhbWVzKSkKYGBgCgpCdXQgdGhpcyB0YWJ1bGFyIGRhdGEgaXMgYSBjb21iaW5hdGlvbiBvZiBudW1lcmljIGFuZCBjYXRlZ29yaWNhbCBkYXRhIHRoYXQgd2UKbmVlZCB0byBhZGRyZXNzLgoKYGBge3IgYW1lcy1zdHJ1Y3R1cmV9CnN0cihhbWVzKQpgYGAKClRoZSBudW1lcmljIHZhcmlhYmxlcyBhcmUgb24gZGlmZmVyZW50IHNjYWxlcy4gRm9yIGV4YW1wbGU6CgpgYGB7ciBudW1lcmljLXJhbmdlc30KYW1lcyAlPiUKICBzZWxlY3QoTG90X0FyZWEsIExvdF9Gcm9udGFnZSwgWWVhcl9CdWlsdCwgR3JfTGl2X0FyZWEsIEdhcmFnZV9DYXJzLCBNb19Tb2xkKSAlPiUKICBnYXRoZXIoZmVhdHVyZSwgdmFsdWUpICU+JQogIGdncGxvdChhZXMoZmVhdHVyZSwgdmFsdWUpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX3lfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpjb21tYSkKYGBgCgpUaGVyZSBhcmUgY2F0ZWdvcmljYWwgZmVhdHVyZXMgdGhhdCBjb3VsZCBiZSBvcmRlcmVkOgoKYGBge3IgbnVtZXJpYy1jYXRlZ29yaWVzfQphbWVzICU+JQogIHNlbGVjdChtYXRjaGVzKCIoUXVhbHxDb25kfFFDfFF1KSQiKSkgJT4lCiAgc3RyKCkKYGBgCgpBbmQgc29tZSBvZiB0aGUgY2F0ZWdvcmljYWwgZmVhdHVyZXMgaGF2ZSBtYW55IGxldmVsczoKCmBgYHtyfQphbWVzICU+JQogIHNlbGVjdF9pZih+IGlzLmZhY3RvciguKSAmIGxlbmd0aChsZXZlbHMoLikpID4gOCkgJT4lCiAgZ2xpbXBzZSgpCmBgYAoKQ29uc2VxdWVudGx5LCBvdXIgZmlyc3QgY2hhbGxlbmdlIGlzIHRyYW5zZm9ybWluZyB0aGlzIGRhdGFzZXQgaW50byBudW1lcmljCnRlbnNvcnMgdGhhdCBvdXIgbW9kZWwgY2FuIHVzZS4KCiMgU3RlcCAxOiBQcmVwIHRoZSBEYXRhCgojIyBDcmVhdGUgdHJhaW4gJiB0ZXN0IHNwbGl0cwoKT25lIG9mIHRoZSBmaXJzdCB0aGluZ3Mgd2Ugd2FudCB0byBkbyBpcyBjcmVhdGUgYSB0cmFpbiBhbmQgdGVzdCBzZXQgYXMgeW91CnByb2JhYmx5IG5vdGljZWQgdGhhdCB3ZSBkbyBub3QgaGF2ZSBhIHRyYWluIGFuZCB0ZXN0IHNldCBzaW1pbGFyIHRvIGhvdyBNTklTVCAKd2FzIGFscmVhZHkgc2V0IHVwIGZvciB1cy4gV2UgY2FuIHVzZSB0aGUgX19yc2FtcGxlX18gcGFja2FnZSB0byBjcmVhdGUgb3VyCnRyYWluIGFuZCB0ZXN0IGRhdGFzZXRzLgoKX19Ob3RlX186IFRoaXMgd2lsbCByYW5kb21seSBzZWxlY3QgdGhlIDcwLzMwIHNwbGl0IHNvIHdlIGFyZSByYW5kb21pemluZyBvdXIKZGF0YSB3aXRoIHRoaXMgcHJvY2Vzcy4KCmBgYHtyfQpzZXQuc2VlZCgxMjMpCmFtZXNfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43KQphbWVzX3RyYWluIDwtIGFuYWx5c2lzKGFtZXNfc3BsaXQpCmFtZXNfdGVzdCA8LSBhc3Nlc3NtZW50KGFtZXNfc3BsaXQpCgpkaW0oYW1lc190cmFpbikKZGltKGFtZXNfdGVzdCkKYGBgCgojIyBWZWN0b3JpemUgYW5kIHNjYWxpbmcKCkFsbCBpbnB1dHMgYW5kIHJlc3BvbnNlIHZhbHVlcyBpbiBhIG5ldXJhbCBuZXR3b3JrIG11c3QgYmUgdGVuc29ycyBvZiBlaXRoZXIgCmZsb2F0aW5nLXBvaW50IG9yIGludGVnZXIgZGF0YS4gTW9yZW92ZXIsIG91ciBmZWF0dXJlIHZhbHVlcyBzaG91bGQgbm90IGJlCnJlbGF0aXZlbHkgbGFyZ2UgY29tcGFyZWQgdG8gdGhlIHJhbmRvbWl6ZWQgaW5pdGlhbCB3ZWlnaHRzIF9hbmRfIGFsbCBvdXIgCmZlYXR1cmVzIHNob3VsZCB0YWtlIHZhbHVlcyBpbiByb3VnaGx5IHRoZSBzYW1lIHJhbmdlLgoKQ29uc2VxdWVudGx5LCB3ZSBuZWVkIHRvIF9fX3ZlY3Rvcml6ZV9fXyBvdXIgZGF0YSBpbnRvIGEgZm9ybWF0IGNvbmR1Y2l2ZSB0byBuZXVyYWwgCm5ldHdvcmtzIFvihLnvuI9dKGh0dHA6Ly9iaXQubHkvZGwtMDIjMykuIEZvciB0aGlzIGRhdGEgc2V0LCB3ZSdsbCB0cmFuc2Zvcm0gb3VyCmRhdGEgYnk6CgoxLiByZW1vdmluZyBhbnkgemVyby12YXJpYW5jZSAob3IgbmVhciB6ZXJvLXZhcmlhbmNlKSBmZWF0dXJlcwoyLiBjb25kZW5zaW5nIHVuaXF1ZSBsZXZlbHMgb2YgY2F0ZWdvcmljYWwgZmVhdHVyZXMgdG8gIm90aGVyIgozLiBvcmRpbmFsIGVuY29kaW5nIHRoZSBxdWFsaXR5IGZlYXR1cmVzCjQuIG5vcm1hbGl6ZSBudW1lcmljIGZlYXR1cmUgZGlzdHJpYnV0aW9ucwo1LiBzdGFuZGFyZGl6aW5nIG51bWVyaWMgZmVhdHVyZXMgdG8gbWVhbiA9IDAsIHN0ZCBkZXYgPSAxCjYuIG9uZS1ob3QgZW5jb2RpbmcgcmVtYWluaW5nIGNhdGVnb3JpY2FsIGZlYXR1cmVzCgpfX05vdGVfXzogd2UncmUgdXNpbmcgdGhlIHJlY2lwZXMgcGFja2FnZSAoaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9yZWNpcGVzKQoKYGBge3J9CmJsdWVwcmludCA8LSByZWNpcGUoU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBhbWVzX3RyYWluKSAlPiUKICBzdGVwX256dihhbGxfbm9taW5hbCgpKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHN0ZXAgIzEKICBzdGVwX290aGVyKGFsbF9ub21pbmFsKCksIHRocmVzaG9sZCA9IC4wMSwgb3RoZXIgPSAib3RoZXIiKSAlPiUgICAjIHN0ZXAgIzIKICBzdGVwX2ludGVnZXIobWF0Y2hlcygiKFF1YWx8Q29uZHxRQ3xRdSkkIikpICU+JSAgICAgICAgICAgICAgICAgICAjIHN0ZXAgIzMKICBzdGVwX1llb0pvaG5zb24oYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUgICAgICAgICAgICAgICAjIHN0ZXAgIzQKICBzdGVwX2NlbnRlcihhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpICU+JSAgICAgICAgICAgICAgICAgICAjIHN0ZXAgIzUKICBzdGVwX3NjYWxlKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lICAgICAgICAgICAgICAgICAgICAjIHN0ZXAgIzUKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSwgb25lX2hvdCA9IFRSVUUpICAgICAgICAjIHN0ZXAgIzYKCmJsdWVwcmludApgYGAKClRoaXMgbmV4dCBzdGVwIGNvbXB1dGVzIGFueSByZWxhdmVudCBpbmZvcm1hdGlvbiAobWVhbiBhbmQgc3RkIGRldmlhdGlvbiBvZgpudW1lcmljIGZlYXR1cmVzLCBuYW1lcyBvZiBvbmUtaG90IGVuY29kZWQgZmVhdHVyZXMpIG9uIHRoZSB0cmFpbmluZyBkYXRhIHNvCnRoZXJlIGlzIG5vIGluZm9ybWF0aW9uIGxlYWthZ2UgZnJvbSB0aGUgdGVzdCBkYXRhLgoKYGBge3J9CnByZXBhcmUgPC0gcHJlcChibHVlcHJpbnQsIHRyYWluaW5nID0gYW1lc190cmFpbikKcHJlcGFyZQpgYGAKCldlIGNhbiBub3cgdmVjdG9yaXplIG91ciB0cmFpbmluZyBhbmQgdGVzdCBkYXRhLiBJZiB5b3Ugc2Nyb2xsIHRocm91Z2ggdGhlIGRhdGEKeW91IHdpbGwgbm90aWNlIHRoYXQgYWxsIGZlYXR1cmVzIGFyZSBub3cgbnVtZXJpYyBhbmQgYXJlIGVpdGhlciAwLzEgKG9uZSBob3QKZW5jb2RlZCBmZWF0dXJlcykgb3IgaGF2ZSBtZWFuIDAgYW5kIGdlbmVyYWxseSByYW5nZSBiZXR3ZWVuIC0zIGFuZCAzLgoKYGBge3J9CmJha2VkX3RyYWluIDwtIGJha2UocHJlcGFyZSwgbmV3X2RhdGEgPSBhbWVzX3RyYWluKQpiYWtlZF90ZXN0IDwtIGJha2UocHJlcGFyZSwgbmV3X2RhdGEgPSBhbWVzX3Rlc3QpCgojIHVuaXQgdGVzdGluZyB0byBlbnN1cmUgYWxsIGNvbHVtbnMgYXJlIG51bWVyaWMKZXhwZWN0X2VxdWFsKG1hcF9sZ2woYmFrZWRfdHJhaW4sIH4gIWlzLm51bWVyaWMoLikpICU+JSBzdW0oKSwgMCkKZXhwZWN0X2VxdWFsKG1hcF9sZ2woYmFrZWRfdGVzdCwgfiAhaXMubnVtZXJpYyguKSkgJT4lIHN1bSgpLCAwKQoKYmFrZWRfdHJhaW4KYGBgCgpMYXN0bHksIHdlIG5lZWQgdG8gY3JlYXRlIHRoZSBmaW5hbCBmZWF0dXJlIGFuZCByZXNwb25zZSBvYmplY3RzIGZvciB0cmFpbiBhbmQgCnRlc3QgZGF0YS4gU2luY2UgX19rZXJhc19fIGFuZCBfX3RlbnNvcmZsb3dfXyByZXF1aXJlIG91ciBmZWF0dXJlcyAmIGxhYmVscyB0byBiZSAKc2VwZXJhdGUgb2JqZWN0cyB3ZSBuZWVkIHRvIHNlcGFyYXRlIHRoZW0uIEluIGRvaW5nIHNvLCBvdXIgZmVhdHVyZXMgbmVlZCB0byBiZSAKYSAyRCB0ZW5zb3Igd2hpY2ggaXMgd2h5IHdlIGFwcGx5IGBhcy5tYXRyaXhgIGFuZCBvdXIgcmVzcG9uc2UgbmVlZHMgdG8gYmUgYSAKdmVjdG9yIHdoaWNoIGlzIHdoeSB3ZSBhcHBseSBgcHVsbGAuCgpgYGB7cn0KeF90cmFpbiA8LSBzZWxlY3QoYmFrZWRfdHJhaW4sIC1TYWxlX1ByaWNlKSAlPiUgYXMubWF0cml4KCkKeV90cmFpbiA8LSBiYWtlZF90cmFpbiAlPiUgcHVsbChTYWxlX1ByaWNlKQoKeF90ZXN0IDwtIHNlbGVjdChiYWtlZF90ZXN0LCAtU2FsZV9QcmljZSkgJT4lIGFzLm1hdHJpeCgpCnlfdGVzdCA8LSBiYWtlZF90ZXN0ICU+JSBwdWxsKFNhbGVfUHJpY2UpCgojIHVuaXQgdGVzdGluZyB0byB4ICYgeSB0ZW5zb3JzIGhhdmUgc2FtZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zCmV4cGVjdF9lcXVhbChucm93KHhfdHJhaW4pLCBsZW5ndGgoeV90cmFpbikpCmV4cGVjdF9lcXVhbChucm93KHhfdGVzdCksIGxlbmd0aCh5X3Rlc3QpKQpgYGAKCk91ciBmaW5hbCBmZWF0dXJlIHNldCBub3cgaGFzIDE4OCBpbnB1dCB2YXJpYWJsZXM6CgpgYGB7cn0KZGltKHhfdHJhaW4pCmRpbSh4X3Rlc3QpCmBgYAoKIyBTdGVwIDI6IEJhbGFuY2UgYmF0Y2ggc2l6ZSB3aXRoIGEgZGVmYXVsdCBsZWFybmluZyByYXRlCgpUbyBnZXQgc3RhcnRlZCwgbGV0J3MgYnVpbGQgYSBzaW1wbGUgbW9kZWwgd2l0aC4uLgoKLSBhIHNpbmdsZSBsYXllciBtb2RlbCB3aXRoIDEyOCB1bml0cyBpbiB0aGUgaGlkZGVuIGxheWVyLiBXZSBoYXZlIDE4OCBmZWF0dXJlcwogIGFuZCAxIHJlc3BvbnNlIG5vZGUgc28gYSBnb29kIHN0YXJ0aW5nIHBvaW50IGlzIG1lYW4oYygxODgsIDEpKSBhbmQgdGhlbiByb3VuZAogIHVwIHRvIHRoZSBuZWFyZXN0IHZhbHVlIGluIHRoZSAkMl5zJCByYW5nZSAoaS5lLiAzMiwgNjQsIDEyOCwgMjU2LCA1MTIpLgotIGEgYmFzaWMgU0dEIG9wdGltaXplcgotIHVzZSBhIG1lYW4gc3F1YXJlIGxvZ2FyaXRobWljIGVycm9yICgibXNsZSIpCi0gYWxzbyB0cmFjayB0aGUgbWVhbiBhYnNvbHV0ZSBlcnJvciBtZXRyaWMgKCJtYWUiKQotIDIwJSB2YWxpZGF0aW9uIHNwbGl0CgpOb3csIHN0YXJ0IHdpdGggdGhlIGRlZmF1bHQgYmF0Y2ggc2l6ZSBvZiAzMiBhbmQgdGhlbiBjb21wYXJlIHdpdGggc21hbGxlcgp2YWx1ZXMgKGkuZS4gMTYpIGFuZCBsYXJnZXIgdmFsdWVzIChpLmUuIDEyOCkuIFlvdSdyZSBsb29raW5nIHRvIGJhbGFuY2UgdGhlCnByb2dyZXNzaW9uIG9mIHRoZSBsb3NzIGxlYXJuaW5nIGN1cnZlIGFuZCB0aGUgdHJhaW5pbmcgc3BlYWQuCgpgYGB7cn0Kbl9mZWF0IDwtIG5jb2woeF90cmFpbikKCm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSBfX19fLCBhY3RpdmF0aW9uID0gInJlbHUiLCBpbnB1dF9zaGFwZSA9IF9fX18pICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gX19fXykKCm1vZGVsICU+JSBjb21waWxlKAogICAgb3B0aW1pemVyID0gX19fXywKICAgIGxvc3MgPSBfX19fLAogICAgbWV0cmljcyA9IF9fX18KICApCgpoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXQoCiAgeF90cmFpbiwKICB5X3RyYWluLAogIF9fX18gLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIKKQpgYGAKCmBgYHtyfQpoaXN0b3J5CmBgYAoKYGBge3J9CnBsb3QoaGlzdG9yeSkKYGBgCgojIFN0ZXAgMzogVHVuZSB0aGUgYWRhcHRpdmUgbGVhcm5pbmcgcmF0ZSBvcHRpbWl6ZXIKCk5vdyBnbyBoZWFkIGFuZCBzdGFydCBhc3Nlc3NpbmcgZGlmZmVyZW50IGFkYXB0aXZlIGxlYXJuaW5nIHJhdGVzIHN1Y2ggYXM6CgotIFNHRCttb21lbnR1bQotIFJNU3Byb3AKLSBBZGFtcAoKVHJ5IGEgdmFyaWV0eSBvZiBsZWFybmluZyByYXRlcy4gUmVjYWxsIHRoYXQgd2UgdHlwaWNhbGx5IHN0YXJ0IGFzc2Vzc2luZyByYXRlcwpvbiBhIGxvZ2FyaXRobWljIHNjYWxlIChpLmUuIDAuMSwgMC4wMSwgLi4uLCAwLjAwMDEpLgoKCmBgYHtyXX0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IF9fX18sIGFjdGl2YXRpb24gPSBfX19fLCBpbnB1dF9zaGFwZSA9IF9fX18pICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSkKCm1vZGVsICU+JSBjb21waWxlKAogICAgb3B0aW1pemVyID0gX19fXywKICAgIGxvc3MgPSAibXNsZSIsCiAgICBtZXRyaWNzID0gIm1hZSIKICApCgpoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXQoCiAgeF90cmFpbiwKICB5X3RyYWluLAogIGJhdGNoX3NpemUgPSBfX19fLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIKKQpgYGAKCmBgYHtyfQpoaXN0b3J5CmBgYAoKYGBge3J9CnBsb3QoaGlzdG9yeSkKYGBgCgojIFN0ZXAgNDogQWRkIGNhbGxiYWNrcyB0byBjb250cm9sIHRyYWluaW5nCgpBZGQgdGhlIGZvbGxvd2luZyBjYWxsYmFja3MgYW5kIHNlZSBpZiB5b3VyIHBlcmZvcm1hbmNlIGltcHJvdmVzOgoKLSBlYXJseSBzdG9wcGluZyB3aXRoIGBwYXRpZW5jZSA9IDNgIGFuZCBgbWluX2RlbHRhID0gMC4wMDAwMWAKLSBsZWFybmluZyByYXRlIHJlZHVjdGlvbiB1cG9uIGEgcGxhdGVhdSB3aXRoIGBwYXRpZW5jZSA9IDFgCgoKYGBge3JdfQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gInJlbHUiLCBpbnB1dF9zaGFwZSA9IF9fX18pICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSkKCm1vZGVsICU+JSBjb21waWxlKAogICAgb3B0aW1pemVyID0gX19fXywKICAgIGxvc3MgPSAibXNsZSIsCiAgICBtZXRyaWNzID0gIm1hZSIKICApCgpoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXQoCiAgeF90cmFpbiwKICB5X3RyYWluLAogIGJhdGNoX3NpemUgPSBfX19fLAogIGVwb2NocyA9IF9fX18sCiAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMiwKICBjYWxsYmFja3MgPSBsaXN0KAogICAgIyA8YWRkIGVhcmx5IHN0b3BwaW5nIGNhbGxiYWNrPiwKICAgICMgPGFkZCBsZWFybmluZyByYXRlIHJlZHVjdGlvbiBjYWxsYmFjaz4KICApCikKYGBgCgpgYGB7cn0KaGlzdG9yeQpgYGAKCmBgYHtyfQpwbG90KGhpc3RvcnkpCmBgYAoKUGxvdHRpbmcgdGhlIGxlYXJuaW5nIHJhdGUgc2hvd3MgdGhhdCBpdCByZWR1Y2VkIG11bHRpcGxlIHRpbWVzIGR1cmluZyB0cmFpbmluZzoKCmBgYHtyfQpwbG90KGhpc3RvcnkkbWV0cmljcyRscikKYGBgCgoKIyBTdGVwIDU6IEV4cGxvcmUgbW9kZWwgY2FwYWNpdHkKCk5vdyBzdGFydCB0byBleHBsb3JlIGRpZmZlcmVudCB3aWR0aHMgYW5kIGRlcHRocyB0byB5b3VyIG1vZGVsLgoKLSBBc3Nlc3MgYSBzaW5nbGUgbGF5ZXIgd2l0aCAxMjgsIDI1NiwgNTEyLCBhbmQgMTAyNCBub2RlcwotIEFzc2VzcyAxLCAyLCBhbmQgMyBoaWRkZW4gbGF5ZXJzCgpgYGB7cn0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IF9fX18sIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gbl9mZWF0KSAlPiUKICAjIDxhZGQgbW9yZSBsYXllcnM+CiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxKQoKbW9kZWwgJT4lIGNvbXBpbGUoCiAgICBvcHRpbWl6ZXIgPSBfX19fLAogICAgbG9zcyA9ICJtc2xlIiwKICAgIG1ldHJpY3MgPSAibWFlIgogICkKCmhpc3RvcnkgPC0gbW9kZWwgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgYmF0Y2hfc2l6ZSA9IF9fX18sCiAgZXBvY2hzID0gX19fXywKICB2YWxpZGF0aW9uX3NwbGl0ID0gMC4yLAogIGNhbGxiYWNrcyA9IGxpc3QoCiAgICBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IF9fX18sIG1pbl9kZWx0YSA9IF9fX19fKSwKICAgIGNhbGxiYWNrX3JlZHVjZV9scl9vbl9wbGF0ZWF1KHBhdGllbmNlID0gX19fXykKICApCikKYGBgCgpgYGB7cn0KaGlzdG9yeQpgYGAKCmBgYHtyfQpwbG90KGhpc3RvcnkpICsgc2NhbGVfeV9sb2cxMCgpCmBgYAoKIyBTdGVwIDY6IFJlZ3VsYXJpemUgb3ZlcmZpdHRpbmcKCklmIHlvdXIgbW9kZWwgaXMgb3ZlcmZpdHRpbmcsIHRyeSB0byBhZGQuLi4KCi0gd2VpZ2h0IGRlY2F5IChpLmUuIGBrZXJuZWxfcmVndWxhcml6ZXIgPSByZWd1bGFyaXplcl9sMihsID0geHh4KWApLiBSZW1lbWJlciwKICB3ZSB0eXBpY2FsbHkgc3RhcnQgYnkgYXNzZXNzaW5nIHZhbHVlcyBvbiBsb2dhcml0aG1pYyBzY2FsZSBbMC4xLCAwLjAwMDAxXS4KLSBkcm9wb3V0IChgbGF5ZXJfZHJvcG91dCgpYCkgYmV0d2VlbiBlYWNoIGxheWVyLiBSZW1lbWJlciwgZHJvcG91dCByYXRlcwogIHR5cGljYWxseSByYW5nZSBmcm9tIDIwLTUwJS4KICAKYGBge3J9Cm1vZGVsIDwtIF9fX18oKSAlPiUgCiAgbGF5ZXJfZGVuc2UoKSAlPiUKICAjIDxhZGQgbW9yZSBsYXllcnMgdG8gbWF0Y2ggeW91ciBsYXN0IG1vZGVsPgogIGxheWVyX2RlbnNlKHVuaXRzID0gMSkKCm1vZGVsICU+JSBfX19fKAogICAgb3B0aW1pemVyID0gX19fXywKICAgIGxvc3MgPSBfX19fLAogICAgbWV0cmljcyA9IF9fX18KICApCgpoaXN0b3J5IDwtIG1vZGVsICU+JSBfX19fKAogIHhfdHJhaW4sCiAgeV90cmFpbiwKICBiYXRjaF9zaXplID0gX19fXywKICBlcG9jaHMgPSBfX19fLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsCiAgY2FsbGJhY2tzID0gbGlzdCgKICAgIGNhbGxiYWNrX19fX18sCiAgICBjYWxsYmFja19fX19fCiAgKQopCmBgYAoKYGBge3J9Cmhpc3RvcnkKYGBgCgojIFN0ZXAgNzogUmVwZWF0IHN0ZXBzIDEtNgoKQXMgdGhpcyBwb2ludCB3ZSBjb3VsZCByZXBlYXQgdGhlIHByb2Nlc3MgYW5kLi4uCgoxLiBQcmVwYXJlIGRhdGEKICAgLSB0cnkgdG8gZmluZCBhZGRpdGlvbmFsIGRhdGEgdG8gYWRkCiAgIC0gdHJ5IG5ldyBmZWF0dXJlIGVuZ2luZWVyaW5nIGFwcHJvYWNoZXMKMi4gQmFsYW5jZSBiYXRjaCBzaXplIHdpdGggYSBkZWZhdWx0IGxlYXJuaW5nIHJhdGUKICAgLSByZWFzc2VzcyBiYXRjaCBzaXplCjMuIFR1bmUgdGhlIGFkYXB0aXZlIGxlYXJuaW5nIHJhdGUgb3B0aW1pemVyCiAgIC0gZmluZSB0dW5lIG91ciBsZWFybmluZyByYXRlCiAgIC0gc2VlIGlmIHRoZSBjdXJyZW50IG9wdGltaXplciBzdGlsbCBvdXRwZXJmb3JtcyBvdGhlcnMKNC4gQWRkIGNhbGxiYWNrcyB0byBjb250cm9sIHRyYWluaW5nCiAgIC0gbWF5YmUgYXNzZXNzIG1vcmUgc29waGlzdGljYXRlZCBsZWFybmluZyByYXRlIHNjaGVkdWxlcnMgKGkuZS4gY3ljbGljYWwKICAgICBsZWFybmluZyByYXRlcykKNS4gRXhwbG9yZSBtb2RlbCBjYXBhY2l0eQogICAtIGFmdGVyIHNvbWUgdHdlYWtzIHdlIG1heSB3YW50IHRvIHJlYXNzZXNzIG1vZGVsIGNhcGFjaXR5IGNvbWJpbmF0aW9ucwo2LiBSZWd1bGFyaXplIG92ZXJmaXR0aW5nCgpb8J+PoF0oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8tY29uZi0yMDIwL2RsLWtlcmFzLXRmKQ==