Note: Some results may differ from the hard copy book due to the changing of sampling procedures introduced in R 3.6.0. See http://bit.ly/35D1SW7 for more details. Access and run the source code for this notebook here. Do to output size, most of this chapter’s code chunks should not be ran on RStudio Cloud.
Hidden chapter requirements used in the book to set the plotting theme and load packages used in hidden code chunks:
Prerequisites
This chapter leverages the following packages, with the emphasis on h2o:
# Helper packages
library(rsample) # for creating our train-test splits
library(recipes) # for minor feature engineering tasks
# Modeling packages
library(h2o) # for fitting stacked models
h2o.no_progress()
h2o.init()
To illustrate key concepts we continue with the Ames housing example from previous chapters:
# Load and split the Ames housing data
ames <- AmesHousing::make_ames()
set.seed(123) # for reproducibility
split <- initial_split(ames, strata = "Sale_Price")
ames_train <- training(split)
ames_test <- testing(split)
# Make sure we have consistent categorical levels
blueprint <- recipe(Sale_Price ~ ., data = ames_train) %>%
step_other(all_nominal(), threshold = 0.05)
# Create training & test sets for h2o
train_h2o <- prep(blueprint, training = ames_train, retain = TRUE) %>%
juice() %>%
as.h2o()
test_h2o <- prep(blueprint, training = ames_train) %>%
bake(new_data = ames_test) %>%
as.h2o()
# Get response and feature names
Y <- "Sale_Price"
X <- setdiff(names(ames_train), Y)
Stacking existing models
# Train & cross-validate a GLM model
best_glm <- h2o.glm(
x = X, y = Y, training_frame = train_h2o, alpha = 0.1,
remove_collinear_columns = TRUE, nfolds = 10, fold_assignment = "Modulo",
keep_cross_validation_predictions = TRUE, seed = 123
)
# Train & cross-validate a RF model
best_rf <- h2o.randomForest(
x = X, y = Y, training_frame = train_h2o, ntrees = 1000, mtries = 20,
max_depth = 30, min_rows = 1, sample_rate = 0.8, nfolds = 10,
fold_assignment = "Modulo", keep_cross_validation_predictions = TRUE,
seed = 123, stopping_rounds = 50, stopping_metric = "RMSE",
stopping_tolerance = 0
)
# Train & cross-validate a GBM model
best_gbm <- h2o.gbm(
x = X, y = Y, training_frame = train_h2o, ntrees = 5000, learn_rate = 0.01,
max_depth = 7, min_rows = 5, sample_rate = 0.8, nfolds = 10,
fold_assignment = "Modulo", keep_cross_validation_predictions = TRUE,
seed = 123, stopping_rounds = 50, stopping_metric = "RMSE",
stopping_tolerance = 0
)
# Train & cross-validate an XGBoost model
best_xgb <- h2o.xgboost(
x = X, y = Y, training_frame = train_h2o, ntrees = 5000, learn_rate = 0.05,
max_depth = 3, min_rows = 3, sample_rate = 0.8, categorical_encoding = "Enum",
nfolds = 10, fold_assignment = "Modulo",
keep_cross_validation_predictions = TRUE, seed = 123, stopping_rounds = 50,
stopping_metric = "RMSE", stopping_tolerance = 0
)
# Train a stacked tree ensemble
ensemble_tree <- h2o.stackedEnsemble(
x = X, y = Y, training_frame = train_h2o, model_id = "my_tree_ensemble",
base_models = list(best_glm, best_rf, best_gbm, best_xgb),
metalearner_algorithm = "drf"
)
# Get results from base learners
get_rmse <- function(model) {
results <- h2o.performance(model, newdata = test_h2o)
results@metrics$RMSE
}
list(best_glm, best_rf, best_gbm, best_xgb) %>%
purrr::map_dbl(get_rmse)
[1] 36834.07 23635.47 19236.09 19725.81
# Stacked results
h2o.performance(ensemble_tree, newdata = test_h2o)@metrics$RMSE
[1] 20446.95
data.frame(
GLM_pred = as.vector(h2o.getFrame(best_glm@model$cross_validation_holdout_predictions_frame_id$name)),
RF_pred = as.vector(h2o.getFrame(best_rf@model$cross_validation_holdout_predictions_frame_id$name)),
GBM_pred = as.vector(h2o.getFrame(best_gbm@model$cross_validation_holdout_predictions_frame_id$name)),
XGB_pred = as.vector(h2o.getFrame(best_xgb@model$cross_validation_holdout_predictions_frame_id$name))
) %>% cor()
GLM_pred RF_pred GBM_pred XGB_pred
GLM_pred 1.0000000 0.9600988 0.9537208 0.9539898
RF_pred 0.9600988 1.0000000 0.9936554 0.9851487
GBM_pred 0.9537208 0.9936554 1.0000000 0.9915265
XGB_pred 0.9539898 0.9851487 0.9915265 1.0000000
Stacking a grid search
# Define GBM hyperparameter grid
hyper_grid <- list(
max_depth = c(1, 3, 5),
min_rows = c(1, 5, 10),
learn_rate = c(0.01, 0.05, 0.1),
learn_rate_annealing = c(0.99, 1),
sample_rate = c(0.5, 0.75, 1),
col_sample_rate = c(0.8, 0.9, 1)
)
# Define random grid search criteria
search_criteria <- list(
strategy = "RandomDiscrete",
max_models = 25
)
# Build random grid search
random_grid <- h2o.grid(
algorithm = "gbm", grid_id = "gbm_grid", x = X, y = Y,
training_frame = train_h2o, hyper_params = hyper_grid,
search_criteria = search_criteria, ntrees = 5000, stopping_metric = "RMSE",
stopping_rounds = 10, stopping_tolerance = 0, nfolds = 10,
fold_assignment = "Modulo", keep_cross_validation_predictions = TRUE,
seed = 123
)
# Sort results by RMSE
h2o.getGrid(
grid_id = "gbm_grid",
sort_by = "rmse"
)
H2O Grid Details
================
Grid ID: gbm_grid
Used hyper parameters:
- col_sample_rate
- learn_rate
- learn_rate_annealing
- max_depth
- min_rows
- sample_rate
Number of models: 25
Number of failed models: 0
Hyper-Parameter Search Summary: ordered by increasing rmse
---
# Grab the model_id for the top model, chosen by validation error
best_model_id <- random_grid@model_ids[[1]]
best_model <- h2o.getModel(best_model_id)
h2o.performance(best_model, newdata = test_h2o)
H2ORegressionMetrics: gbm
MSE: 387885567
RMSE: 19694.81
MAE: 12982.36
RMSLE: 0.1244279
Mean Residual Deviance : 387885567
# Train a stacked ensemble using the GBM grid
ensemble <- h2o.stackedEnsemble(
x = X, y = Y, training_frame = train_h2o, model_id = "ensemble_gbm_grid",
base_models = random_grid@model_ids, metalearner_algorithm = "gbm"
)
# Eval ensemble performance on a test set
h2o.performance(ensemble, newdata = test_h2o)
H2ORegressionMetrics: stackedensemble
MSE: 379961705
RMSE: 19492.61
MAE: 13131.34
RMSLE: 0.1234258
Mean Residual Deviance : 379961705
Automated machine learning
# Use AutoML to find a list of candidate models (i.e., leaderboard)
auto_ml <- h2o.automl(
x = X, y = Y, training_frame = train_h2o, nfolds = 5,
max_runtime_secs = 60 * 120, max_models = 50,
keep_cross_validation_predictions = TRUE, sort_metric = "RMSE", seed = 123,
stopping_rounds = 50, stopping_metric = "RMSE", stopping_tolerance = 0
)
# Assess the leader board; the following truncates the results to show the top
# 25 models. You can get the top model with auto_ml@leader
auto_ml@leaderboard %>%
as.data.frame() %>%
dplyr::select(model_id, rmse) %>%
dplyr::slice(1:25)
h2o.shutdown(prompt = FALSE)
[1] TRUE
LS0tCnRpdGxlOiAiQ2hhcHRlciAxNTogU3RhY2tlZCBNb2RlbHMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCl9fTm90ZV9fOiBTb21lIHJlc3VsdHMgbWF5IGRpZmZlciBmcm9tIHRoZSBoYXJkIGNvcHkgYm9vayBkdWUgdG8gdGhlIGNoYW5naW5nIG9mIHNhbXBsaW5nIHByb2NlZHVyZXMgaW50cm9kdWNlZCBpbiBSIDMuNi4wLiBTZWUgaHR0cDovL2JpdC5seS8zNUQxU1c3IGZvciBtb3JlIGRldGFpbHMuIEFjY2VzcyBhbmQgcnVuIHRoZSBzb3VyY2UgY29kZSBmb3IgdGhpcyBub3RlYm9vayBbaGVyZV0oaHR0cHM6Ly9yc3R1ZGlvLmNsb3VkL3Byb2plY3QvODAxMTg1KS4gRG8gdG8gb3V0cHV0IHNpemUsIG1vc3Qgb2YgdGhpcwpjaGFwdGVyJ3MgY29kZSBjaHVua3Mgc2hvdWxkIG5vdCBiZSByYW4gb24gUlN0dWRpbyBDbG91ZC4KCkhpZGRlbiBjaGFwdGVyIHJlcXVpcmVtZW50cyB1c2VkIGluIHRoZSBib29rIHRvIHNldCB0aGUgcGxvdHRpbmcgdGhlbWUgYW5kIGxvYWQgcGFja2FnZXMgdXNlZCBpbiBoaWRkZW4gY29kZSBjaHVua3M6CgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIG1lc3NhZ2UgPSBGQUxTRSwgCiAgd2FybmluZyA9IEZBTFNFLCAKICBjYWNoZSA9IEZBTFNFCikKYGBgCgojIyBQcmVyZXF1aXNpdGVzCgpUaGlzIGNoYXB0ZXIgbGV2ZXJhZ2VzIHRoZSBmb2xsb3dpbmcgcGFja2FnZXMsIHdpdGggdGhlIGVtcGhhc2lzIG9uIF9faDJvX186CgpgYGB7ciBwa2ctcmVxLTEyfQojIEhlbHBlciBwYWNrYWdlcwpsaWJyYXJ5KHJzYW1wbGUpICAgIyBmb3IgY3JlYXRpbmcgb3VyIHRyYWluLXRlc3Qgc3BsaXRzCmxpYnJhcnkocmVjaXBlcykgICAjIGZvciBtaW5vciBmZWF0dXJlIGVuZ2luZWVyaW5nIHRhc2tzCgojIE1vZGVsaW5nIHBhY2thZ2VzCmxpYnJhcnkoaDJvKSAgICAgICAjIGZvciBmaXR0aW5nIHN0YWNrZWQgbW9kZWxzCmBgYAoKYGBge3J9Cmgyby5ub19wcm9ncmVzcygpCmgyby5pbml0KCkKYGBgCgpUbyBpbGx1c3RyYXRlIGtleSBjb25jZXB0cyB3ZSBjb250aW51ZSB3aXRoIHRoZSBBbWVzIGhvdXNpbmcgZXhhbXBsZSBmcm9tIHByZXZpb3VzIGNoYXB0ZXJzOgoKYGBge3IgZGF0YS1yZXEtMTJ9CiMgTG9hZCBhbmQgc3BsaXQgdGhlIEFtZXMgaG91c2luZyBkYXRhCmFtZXMgPC0gQW1lc0hvdXNpbmc6Om1ha2VfYW1lcygpCnNldC5zZWVkKDEyMykgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpzcGxpdCA8LSBpbml0aWFsX3NwbGl0KGFtZXMsIHN0cmF0YSA9ICJTYWxlX1ByaWNlIikKYW1lc190cmFpbiA8LSB0cmFpbmluZyhzcGxpdCkKYW1lc190ZXN0IDwtIHRlc3Rpbmcoc3BsaXQpCgojIE1ha2Ugc3VyZSB3ZSBoYXZlIGNvbnNpc3RlbnQgY2F0ZWdvcmljYWwgbGV2ZWxzCmJsdWVwcmludCA8LSByZWNpcGUoU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBhbWVzX3RyYWluKSAlPiUKICBzdGVwX290aGVyKGFsbF9ub21pbmFsKCksIHRocmVzaG9sZCA9IDAuMDUpCgojIENyZWF0ZSB0cmFpbmluZyAmIHRlc3Qgc2V0cyBmb3IgaDJvCnRyYWluX2gybyA8LSBwcmVwKGJsdWVwcmludCwgdHJhaW5pbmcgPSBhbWVzX3RyYWluLCByZXRhaW4gPSBUUlVFKSAlPiUKICBqdWljZSgpICU+JQogIGFzLmgybygpCnRlc3RfaDJvIDwtIHByZXAoYmx1ZXByaW50LCB0cmFpbmluZyA9IGFtZXNfdHJhaW4pICU+JQogIGJha2UobmV3X2RhdGEgPSBhbWVzX3Rlc3QpICU+JQogIGFzLmgybygpCgojIEdldCByZXNwb25zZSBhbmQgZmVhdHVyZSBuYW1lcwpZIDwtICJTYWxlX1ByaWNlIgpYIDwtIHNldGRpZmYobmFtZXMoYW1lc190cmFpbiksIFkpCmBgYAoKIyMgU3RhY2tpbmcgZXhpc3RpbmcgbW9kZWxzCgpgYGB7cn0KIyBUcmFpbiAmIGNyb3NzLXZhbGlkYXRlIGEgR0xNIG1vZGVsCmJlc3RfZ2xtIDwtIGgyby5nbG0oCiAgeCA9IFgsIHkgPSBZLCB0cmFpbmluZ19mcmFtZSA9IHRyYWluX2gybywgYWxwaGEgPSAwLjEsCiAgcmVtb3ZlX2NvbGxpbmVhcl9jb2x1bW5zID0gVFJVRSwgbmZvbGRzID0gMTAsIGZvbGRfYXNzaWdubWVudCA9ICJNb2R1bG8iLAogIGtlZXBfY3Jvc3NfdmFsaWRhdGlvbl9wcmVkaWN0aW9ucyA9IFRSVUUsIHNlZWQgPSAxMjMKKQoKIyBUcmFpbiAmIGNyb3NzLXZhbGlkYXRlIGEgUkYgbW9kZWwKYmVzdF9yZiA8LSBoMm8ucmFuZG9tRm9yZXN0KAogIHggPSBYLCB5ID0gWSwgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbl9oMm8sIG50cmVlcyA9IDEwMDAsIG10cmllcyA9IDIwLAogIG1heF9kZXB0aCA9IDMwLCBtaW5fcm93cyA9IDEsIHNhbXBsZV9yYXRlID0gMC44LCBuZm9sZHMgPSAxMCwKICBmb2xkX2Fzc2lnbm1lbnQgPSAiTW9kdWxvIiwga2VlcF9jcm9zc192YWxpZGF0aW9uX3ByZWRpY3Rpb25zID0gVFJVRSwKICBzZWVkID0gMTIzLCBzdG9wcGluZ19yb3VuZHMgPSA1MCwgc3RvcHBpbmdfbWV0cmljID0gIlJNU0UiLAogIHN0b3BwaW5nX3RvbGVyYW5jZSA9IDAKKQoKIyBUcmFpbiAmIGNyb3NzLXZhbGlkYXRlIGEgR0JNIG1vZGVsCmJlc3RfZ2JtIDwtIGgyby5nYm0oCiAgeCA9IFgsIHkgPSBZLCB0cmFpbmluZ19mcmFtZSA9IHRyYWluX2gybywgbnRyZWVzID0gNTAwMCwgbGVhcm5fcmF0ZSA9IDAuMDEsCiAgbWF4X2RlcHRoID0gNywgbWluX3Jvd3MgPSA1LCBzYW1wbGVfcmF0ZSA9IDAuOCwgbmZvbGRzID0gMTAsCiAgZm9sZF9hc3NpZ25tZW50ID0gIk1vZHVsbyIsIGtlZXBfY3Jvc3NfdmFsaWRhdGlvbl9wcmVkaWN0aW9ucyA9IFRSVUUsCiAgc2VlZCA9IDEyMywgc3RvcHBpbmdfcm91bmRzID0gNTAsIHN0b3BwaW5nX21ldHJpYyA9ICJSTVNFIiwKICBzdG9wcGluZ190b2xlcmFuY2UgPSAwCikKCiMgVHJhaW4gJiBjcm9zcy12YWxpZGF0ZSBhbiBYR0Jvb3N0IG1vZGVsCmJlc3RfeGdiIDwtIGgyby54Z2Jvb3N0KAogIHggPSBYLCB5ID0gWSwgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbl9oMm8sIG50cmVlcyA9IDUwMDAsIGxlYXJuX3JhdGUgPSAwLjA1LAogIG1heF9kZXB0aCA9IDMsIG1pbl9yb3dzID0gMywgc2FtcGxlX3JhdGUgPSAwLjgsIGNhdGVnb3JpY2FsX2VuY29kaW5nID0gIkVudW0iLAogIG5mb2xkcyA9IDEwLCBmb2xkX2Fzc2lnbm1lbnQgPSAiTW9kdWxvIiwgCiAga2VlcF9jcm9zc192YWxpZGF0aW9uX3ByZWRpY3Rpb25zID0gVFJVRSwgc2VlZCA9IDEyMywgc3RvcHBpbmdfcm91bmRzID0gNTAsCiAgc3RvcHBpbmdfbWV0cmljID0gIlJNU0UiLCBzdG9wcGluZ190b2xlcmFuY2UgPSAwCikKYGBgCgpgYGB7cn0KIyBUcmFpbiBhIHN0YWNrZWQgdHJlZSBlbnNlbWJsZQplbnNlbWJsZV90cmVlIDwtIGgyby5zdGFja2VkRW5zZW1ibGUoCiAgeCA9IFgsIHkgPSBZLCB0cmFpbmluZ19mcmFtZSA9IHRyYWluX2gybywgbW9kZWxfaWQgPSAibXlfdHJlZV9lbnNlbWJsZSIsCiAgYmFzZV9tb2RlbHMgPSBsaXN0KGJlc3RfZ2xtLCBiZXN0X3JmLCBiZXN0X2dibSwgYmVzdF94Z2IpLAogIG1ldGFsZWFybmVyX2FsZ29yaXRobSA9ICJkcmYiCikKYGBgCgpgYGB7cn0KIyBHZXQgcmVzdWx0cyBmcm9tIGJhc2UgbGVhcm5lcnMKZ2V0X3Jtc2UgPC0gZnVuY3Rpb24obW9kZWwpIHsKICByZXN1bHRzIDwtIGgyby5wZXJmb3JtYW5jZShtb2RlbCwgbmV3ZGF0YSA9IHRlc3RfaDJvKQogIHJlc3VsdHNAbWV0cmljcyRSTVNFCn0KCmxpc3QoYmVzdF9nbG0sIGJlc3RfcmYsIGJlc3RfZ2JtLCBiZXN0X3hnYikgJT4lCiAgcHVycnI6Om1hcF9kYmwoZ2V0X3Jtc2UpCgojIFN0YWNrZWQgcmVzdWx0cwpoMm8ucGVyZm9ybWFuY2UoZW5zZW1ibGVfdHJlZSwgbmV3ZGF0YSA9IHRlc3RfaDJvKUBtZXRyaWNzJFJNU0UKYGBgCgpgYGB7cn0KZGF0YS5mcmFtZSgKICBHTE1fcHJlZCA9IGFzLnZlY3RvcihoMm8uZ2V0RnJhbWUoYmVzdF9nbG1AbW9kZWwkY3Jvc3NfdmFsaWRhdGlvbl9ob2xkb3V0X3ByZWRpY3Rpb25zX2ZyYW1lX2lkJG5hbWUpKSwKICBSRl9wcmVkID0gYXMudmVjdG9yKGgyby5nZXRGcmFtZShiZXN0X3JmQG1vZGVsJGNyb3NzX3ZhbGlkYXRpb25faG9sZG91dF9wcmVkaWN0aW9uc19mcmFtZV9pZCRuYW1lKSksCiAgR0JNX3ByZWQgPSBhcy52ZWN0b3IoaDJvLmdldEZyYW1lKGJlc3RfZ2JtQG1vZGVsJGNyb3NzX3ZhbGlkYXRpb25faG9sZG91dF9wcmVkaWN0aW9uc19mcmFtZV9pZCRuYW1lKSksCiAgWEdCX3ByZWQgPSBhcy52ZWN0b3IoaDJvLmdldEZyYW1lKGJlc3RfeGdiQG1vZGVsJGNyb3NzX3ZhbGlkYXRpb25faG9sZG91dF9wcmVkaWN0aW9uc19mcmFtZV9pZCRuYW1lKSkKKSAlPiUgY29yKCkKYGBgCgojIyBTdGFja2luZyBhIGdyaWQgc2VhcmNoCgpgYGB7cn0KIyBEZWZpbmUgR0JNIGh5cGVycGFyYW1ldGVyIGdyaWQKaHlwZXJfZ3JpZCA8LSBsaXN0KAogIG1heF9kZXB0aCA9IGMoMSwgMywgNSksCiAgbWluX3Jvd3MgPSBjKDEsIDUsIDEwKSwKICBsZWFybl9yYXRlID0gYygwLjAxLCAwLjA1LCAwLjEpLAogIGxlYXJuX3JhdGVfYW5uZWFsaW5nID0gYygwLjk5LCAxKSwKICBzYW1wbGVfcmF0ZSA9IGMoMC41LCAwLjc1LCAxKSwKICBjb2xfc2FtcGxlX3JhdGUgPSBjKDAuOCwgMC45LCAxKQopCgojIERlZmluZSByYW5kb20gZ3JpZCBzZWFyY2ggY3JpdGVyaWEKc2VhcmNoX2NyaXRlcmlhIDwtIGxpc3QoCiAgc3RyYXRlZ3kgPSAiUmFuZG9tRGlzY3JldGUiLAogIG1heF9tb2RlbHMgPSAyNQopCgojIEJ1aWxkIHJhbmRvbSBncmlkIHNlYXJjaCAKcmFuZG9tX2dyaWQgPC0gaDJvLmdyaWQoCiAgYWxnb3JpdGhtID0gImdibSIsIGdyaWRfaWQgPSAiZ2JtX2dyaWQiLCB4ID0gWCwgeSA9IFksCiAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbl9oMm8sIGh5cGVyX3BhcmFtcyA9IGh5cGVyX2dyaWQsCiAgc2VhcmNoX2NyaXRlcmlhID0gc2VhcmNoX2NyaXRlcmlhLCBudHJlZXMgPSA1MDAwLCBzdG9wcGluZ19tZXRyaWMgPSAiUk1TRSIsICAgICAKICBzdG9wcGluZ19yb3VuZHMgPSAxMCwgc3RvcHBpbmdfdG9sZXJhbmNlID0gMCwgbmZvbGRzID0gMTAsIAogIGZvbGRfYXNzaWdubWVudCA9ICJNb2R1bG8iLCBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fcHJlZGljdGlvbnMgPSBUUlVFLAogIHNlZWQgPSAxMjMKKQpgYGAKCmBgYHtyfQojIFNvcnQgcmVzdWx0cyBieSBSTVNFCmgyby5nZXRHcmlkKAogIGdyaWRfaWQgPSAiZ2JtX2dyaWQiLCAKICBzb3J0X2J5ID0gInJtc2UiCikKYGBgCgpgYGB7cn0KIyBHcmFiIHRoZSBtb2RlbF9pZCBmb3IgdGhlIHRvcCBtb2RlbCwgY2hvc2VuIGJ5IHZhbGlkYXRpb24gZXJyb3IKYmVzdF9tb2RlbF9pZCA8LSByYW5kb21fZ3JpZEBtb2RlbF9pZHNbWzFdXQpiZXN0X21vZGVsIDwtIGgyby5nZXRNb2RlbChiZXN0X21vZGVsX2lkKQpoMm8ucGVyZm9ybWFuY2UoYmVzdF9tb2RlbCwgbmV3ZGF0YSA9IHRlc3RfaDJvKQpgYGAKCmBgYHtyfQojIFRyYWluIGEgc3RhY2tlZCBlbnNlbWJsZSB1c2luZyB0aGUgR0JNIGdyaWQKZW5zZW1ibGUgPC0gaDJvLnN0YWNrZWRFbnNlbWJsZSgKICB4ID0gWCwgeSA9IFksIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5faDJvLCBtb2RlbF9pZCA9ICJlbnNlbWJsZV9nYm1fZ3JpZCIsCiAgYmFzZV9tb2RlbHMgPSByYW5kb21fZ3JpZEBtb2RlbF9pZHMsIG1ldGFsZWFybmVyX2FsZ29yaXRobSA9ICJnYm0iCikKCiMgRXZhbCBlbnNlbWJsZSBwZXJmb3JtYW5jZSBvbiBhIHRlc3Qgc2V0Cmgyby5wZXJmb3JtYW5jZShlbnNlbWJsZSwgbmV3ZGF0YSA9IHRlc3RfaDJvKQpgYGAKCiMjIEF1dG9tYXRlZCBtYWNoaW5lIGxlYXJuaW5nCgpgYGB7cn0KIyBVc2UgQXV0b01MIHRvIGZpbmQgYSBsaXN0IG9mIGNhbmRpZGF0ZSBtb2RlbHMgKGkuZS4sIGxlYWRlcmJvYXJkKQphdXRvX21sIDwtIGgyby5hdXRvbWwoCiAgeCA9IFgsIHkgPSBZLCB0cmFpbmluZ19mcmFtZSA9IHRyYWluX2gybywgbmZvbGRzID0gNSwgCiAgbWF4X3J1bnRpbWVfc2VjcyA9IDYwICogMTIwLCBtYXhfbW9kZWxzID0gNTAsCiAga2VlcF9jcm9zc192YWxpZGF0aW9uX3ByZWRpY3Rpb25zID0gVFJVRSwgc29ydF9tZXRyaWMgPSAiUk1TRSIsIHNlZWQgPSAxMjMsCiAgc3RvcHBpbmdfcm91bmRzID0gNTAsIHN0b3BwaW5nX21ldHJpYyA9ICJSTVNFIiwgc3RvcHBpbmdfdG9sZXJhbmNlID0gMAopCmBgYAoKYGBge3J9CiMgQXNzZXNzIHRoZSBsZWFkZXIgYm9hcmQ7IHRoZSBmb2xsb3dpbmcgdHJ1bmNhdGVzIHRoZSByZXN1bHRzIHRvIHNob3cgdGhlIHRvcCAKIyAyNSBtb2RlbHMuIFlvdSBjYW4gZ2V0IHRoZSB0b3AgbW9kZWwgd2l0aCBhdXRvX21sQGxlYWRlcgphdXRvX21sQGxlYWRlcmJvYXJkICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgZHBseXI6OnNlbGVjdChtb2RlbF9pZCwgcm1zZSkgJT4lCiAgZHBseXI6OnNsaWNlKDE6MjUpCmBgYAoKYGBge3J9Cmgyby5zaHV0ZG93bihwcm9tcHQgPSBGQUxTRSkKYGBgCgo=