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.
Hidden chapter requirements used in the book to set the plotting theme and load packages used in hidden code chunks:
# Set global R options
options(scipen = 999)
# Set the graphical theme
ggplot2::theme_set(ggplot2::theme_light())
# Set global knitr chunk options
knitr::opts_chunk$set(
warning = FALSE,
message = FALSE
)
# Load required packages
library(ggplot2)
library(rsample)
Prerequisites
In this chapter we’ll make use of the following packages:
# Helper packages
library(dplyr) # for data wrangling
library(ggplot2) # for awesome plotting
library(doParallel) # for parallel backend to foreach
library(foreach) # for parallel processing with for loops
# Modeling packages
library(caret) # for general model fitting
library(rpart) # for fitting decision trees
library(ipred) # for fitting bagged decision trees
We’ll continue to illustrate the main concepts with the ames_train
data:
# create Ames training data
set.seed(123)
ames <- AmesHousing::make_ames()
split <- initial_split(ames, prop = 0.7, strata = "Sale_Price")
ames_train <- training(split)
ames_test <- testing(split)
Why and when bagging works
Figure 10.1:
# Simulate some nonlinear monotonic data
set.seed(123) # for reproducibility
x <- seq(from = 0, to = 2 * pi, length = 500)
y <- sin(x) + rnorm(length(x), sd = 0.3)
df <- data.frame(x, y) %>%
filter(x < 4.5)
# bootstrapped polynomial model fit
bootstrap_n <- 100
bootstrap_results <- NULL
for(i in seq_len(bootstrap_n)) {
# reproducible sampled data frames
set.seed(i)
index <- sample(seq_len(nrow(df)), nrow(df), replace = TRUE)
df_sim <- df[index, ]
# fit model and add predictions to results data frame
fit <- lm(y ~ I(x^3), data = df_sim)
df_sim$predictions <- predict(fit, df_sim)
df_sim$model <- paste0("model", i)
df_sim$ob <- index
bootstrap_results <- rbind(bootstrap_results, df_sim)
}
p1 <- ggplot(bootstrap_results, aes(x, predictions)) +
geom_point(data = df, aes(x, y), alpha = .25) +
geom_line(aes(group = model), show.legend = FALSE, size = .5, alpha = .2) +
stat_summary(fun.y = "mean", colour = "red", size = 1, geom = "line") +
scale_y_continuous("Response", limits = c(-2, 2), expand = c(0, 0)) +
scale_x_continuous(limits = c(0, 5), expand = c(0, 0)) +
ggtitle("A) Polynomial regression")
# bootstrapped MARS model fit
bootstrap_n <- 100
bootstrap_results <- NULL
for(i in seq_len(bootstrap_n)) {
# reproducible sampled data frames
set.seed(i)
index <- sample(seq_len(nrow(df)), nrow(df), replace = TRUE)
df_sim <- df[index, ]
# fit model and add predictions to results data frame
fit <- earth::earth(y ~ x, data = df_sim)
df_sim$predictions <- predict(fit, df_sim)
df_sim$model <- paste0("model", i)
df_sim$ob <- index
bootstrap_results <- rbind(bootstrap_results, df_sim)
}
p2 <- ggplot(bootstrap_results, aes(x, predictions)) +
geom_point(data = df, aes(x, y), alpha = .25) +
geom_line(aes(group = model), show.legend = FALSE, size = .5, alpha = .2) +
stat_summary(fun.y = "mean", colour = "red", size = 1, geom = "line") +
scale_y_continuous(NULL, limits = c(-2, 2), expand = c(0, 0)) +
scale_x_continuous(limits = c(0, 5), expand = c(0, 0)) +
ggtitle("B) MARS")
# bootstrapped decision trees fit
bootstrap_n <- 100
bootstrap_results <- NULL
for(i in seq_len(bootstrap_n)) {
# reproducible sampled data frames
set.seed(i)
index <- sample(seq_len(nrow(df)), nrow(df), replace = TRUE)
df_sim <- df[index, ]
# fit model and add predictions to results data frame
fit <- rpart::rpart(y ~ x, data = df_sim)
df_sim$predictions <- predict(fit, df_sim)
df_sim$model <- paste0("model", i)
df_sim$ob <- index
bootstrap_results <- rbind(bootstrap_results, df_sim)
}
p3 <- ggplot(bootstrap_results, aes(x, predictions)) +
geom_point(data = df, aes(x, y), alpha = .25) +
geom_line(aes(group = model), show.legend = FALSE, size = .5, alpha = .2) +
stat_summary(fun.y = "mean", colour = "red", size = 1, geom = "line") +
scale_y_continuous(NULL, limits = c(-2, 2), expand = c(0, 0)) +
scale_x_continuous(limits = c(0, 5), expand = c(0, 0)) +
ggtitle("C) Decision trees")
gridExtra::grid.arrange(p1, p2, p3, nrow = 1)
Implementation
# make bootstrapping reproducible
set.seed(123)
# train bagged model
ames_bag1 <- bagging(
formula = Sale_Price ~ .,
data = ames_train,
nbagg = 100,
coob = TRUE,
control = rpart.control(minsplit = 2, cp = 0)
)
ames_bag1
Bagging regression trees with 100 bootstrap replications
Call: bagging.data.frame(formula = Sale_Price ~ ., data = ames_train,
nbagg = 100, coob = TRUE, control = rpart.control(minsplit = 2,
cp = 0))
Out-of-bag estimate of root mean squared error: 27767.13
Figure 10.2:
# # assess 10-200 bagged trees
# ntree <- seq(10, 200, by = 2)
#
# # create empty vector to store OOB RMSE values
# rmse <- vector(mode = "numeric", length = length(ntree))
#
# for (i in seq_along(ntree)) {
# # reproducibility
# set.seed(123)
# # perform bagged model
# model <- bagging(
# formula = Sale_Price ~ .,
# data = ames_train,
# coob = TRUE,
# control = rpart.control(minsplit = 2, cp = 0),
# nbagg = ntree[i]
# )
# # get OOB error
# rmse[i] <- model$err
# }
#
# bagging_errors <- data.frame(ntree, rmse)
# using ranger to do the same as above. Will allow for bagging under 10 trees
# and is much faster!
ntree <- seq(1, 200, by = 2)
# create empty vector to store OOB RMSE values
rmse <- vector(mode = "numeric", length = length(ntree))
for (i in seq_along(ntree)) {
# reproducibility
set.seed(123)
# perform bagged model
model <- ranger::ranger(
formula = Sale_Price ~ .,
data = ames_train,
num.trees = ntree[i],
mtry = ncol(ames_train) - 1,
min.node.size = 1
)
# get OOB error
rmse[i] <- sqrt(model$prediction.error)
}
bagging_errors <- data.frame(ntree, rmse)
ggplot(bagging_errors, aes(ntree, rmse)) +
geom_line() +
geom_hline(yintercept = 41019, lty = "dashed", color = "grey50") +
annotate("text", x = 100, y = 41385, label = "Best individual pruned tree", vjust = 0, hjust = 0, color = "grey50") +
annotate("text", x = 100, y = 26750, label = "Bagged trees", vjust = 0, hjust = 0) +
ylab("RMSE") +
xlab("Number of trees")
Do to output size, the code chunks that follow should not be ran on RStudio Cloud
ames_bag2 <- train(
Sale_Price ~ .,
data = ames_train,
method = "treebag",
trControl = trainControl(method = "cv", number = 10),
nbagg = 200,
control = rpart.control(minsplit = 2, cp = 0)
)
ames_bag2
Bagged CART
2053 samples
80 predictor
No pre-processing
Resampling: Cross-Validated (10 fold)
Summary of sample sizes: 1847, 1849, 1847, 1847, 1848, 1847, ...
Resampling results:
RMSE Rsquared MAE
27460.99 0.8862784 16755.65
Easily parallelize
# Create a parallel socket cluster
cl <- makeCluster(8) # use 8 workers
registerDoParallel(cl) # register the parallel backend
# Fit trees in parallel and compute predictions on the test set
predictions <- foreach(
icount(160),
.packages = "rpart",
.combine = cbind
) %dopar% {
# bootstrap copy of training data
index <- sample(nrow(ames_train), replace = TRUE)
ames_train_boot <- ames_train[index, ]
# fit tree to bootstrap copy
bagged_tree <- rpart(
Sale_Price ~ .,
control = rpart.control(minsplit = 2, cp = 0),
data = ames_train_boot
)
predict(bagged_tree, newdata = ames_test)
}
predictions[1:5, 1:7]
result.1 result.2 result.3 result.4 result.5 result.6 result.7
1 220000 284700 176500 235000 190000 243000 244000
2 191000 173000 140500 195500 189500 146900 188000
3 189000 294000 170000 200000 180000 130000 211500
4 173000 144000 158000 173000 180400 173000 173000
5 189000 270000 254000 209500 279700 190000 201000
predictions %>%
as.data.frame() %>%
mutate(
observation = 1:n(),
actual = ames_test$Sale_Price) %>%
tidyr::gather(tree, predicted, -c(observation, actual)) %>%
group_by(observation) %>%
mutate(tree = stringr::str_extract(tree, '\\d+') %>% as.numeric()) %>%
ungroup() %>%
arrange(observation, tree) %>%
group_by(observation) %>%
mutate(avg_prediction = cummean(predicted)) %>%
group_by(tree) %>%
summarize(RMSE = RMSE(avg_prediction, actual)) %>%
ggplot(aes(tree, RMSE)) +
geom_line() +
xlab('Number of trees')
# Shutdown parallel cluster
stopCluster(cl)
Feature interpretation
vip::vip(ames_bag2, num_features = 40, bar = FALSE)
# Construct partial dependence plots
p1 <- pdp::partial(
ames_bag2,
pred.var = "Lot_Area",
grid.resolution = 20
) %>%
autoplot()
p2 <- pdp::partial(
ames_bag2,
pred.var = "Lot_Frontage",
grid.resolution = 20
) %>%
autoplot()
gridExtra::grid.arrange(p1, p2, nrow = 1)
Final thoughts
Figure 10.6:
library(caret)
library(randomForest)
iter = 6
par(mfrow = c(3, 3))
for(i in 1:iter){
set.seed(i+30)
# create train/test sets
train_index <- caret::createDataPartition(pdp::boston$cmedv, p = .6333,
list = FALSE,
times = 1)
train_DF <- pdp::boston[train_index,]
validate_DF <- pdp::boston[-train_index,]
train_y <- train_DF$cmedv
train_x <- train_DF[, setdiff(names(train_DF), "cmedv")]
validate_y <- validate_DF$cmedv
validate_x <- validate_DF[, setdiff(names(validate_DF), "cmedv")]
d_tree <- rpart::rpart(cmedv ~ ., train_DF)
# graphs
rpart.plot::rpart.plot(d_tree, main = paste0("Decision Tree ", i), type = 0, extra = 0)
}
LS0tCnRpdGxlOiAiQ2hhcHRlciAxMDogQmFnZ2luZyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKX19Ob3RlX186IFNvbWUgcmVzdWx0cyBtYXkgZGlmZmVyIGZyb20gdGhlIGhhcmQgY29weSBib29rIGR1ZSB0byB0aGUgY2hhbmdpbmcgb2Ygc2FtcGxpbmcgcHJvY2VkdXJlcyBpbnRyb2R1Y2VkIGluIFIgMy42LjAuIFNlZSBodHRwOi8vYml0Lmx5LzM1RDFTVzcgZm9yIG1vcmUgZGV0YWlscy4gQWNjZXNzIGFuZCBydW4gdGhlIHNvdXJjZSBjb2RlIGZvciB0aGlzIG5vdGVib29rIFtoZXJlXShodHRwczovL3JzdHVkaW8uY2xvdWQvcHJvamVjdC84MDExODUpLiAKCkhpZGRlbiBjaGFwdGVyIHJlcXVpcmVtZW50cyB1c2VkIGluIHRoZSBib29rIHRvIHNldCB0aGUgcGxvdHRpbmcgdGhlbWUgYW5kIGxvYWQgcGFja2FnZXMgdXNlZCBpbiBoaWRkZW4gY29kZSBjaHVua3M6CgpgYGB7ciBzZXR1cH0KIyBTZXQgZ2xvYmFsIFIgb3B0aW9ucwpvcHRpb25zKHNjaXBlbiA9IDk5OSkKCiMgU2V0IHRoZSBncmFwaGljYWwgdGhlbWUKZ2dwbG90Mjo6dGhlbWVfc2V0KGdncGxvdDI6OnRoZW1lX2xpZ2h0KCkpCgojIFNldCBnbG9iYWwga25pdHIgY2h1bmsgb3B0aW9ucwprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgd2FybmluZyA9IEZBTFNFLCAKICBtZXNzYWdlID0gRkFMU0UKKQoKIyBMb2FkIHJlcXVpcmVkIHBhY2thZ2VzCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShyc2FtcGxlKQpgYGAKCiMjIFByZXJlcXVpc2l0ZXMKCkluIHRoaXMgY2hhcHRlciB3ZSdsbCBtYWtlIHVzZSBvZiB0aGUgZm9sbG93aW5nIHBhY2thZ2VzOgoKYGBge3J9CiMgSGVscGVyIHBhY2thZ2VzCmxpYnJhcnkoZHBseXIpICAgICAgICMgZm9yIGRhdGEgd3JhbmdsaW5nCmxpYnJhcnkoZ2dwbG90MikgICAgICMgZm9yIGF3ZXNvbWUgcGxvdHRpbmcKbGlicmFyeShkb1BhcmFsbGVsKSAgIyBmb3IgcGFyYWxsZWwgYmFja2VuZCB0byBmb3JlYWNoCmxpYnJhcnkoZm9yZWFjaCkgICAgICMgZm9yIHBhcmFsbGVsIHByb2Nlc3Npbmcgd2l0aCBmb3IgbG9vcHMKCiMgTW9kZWxpbmcgcGFja2FnZXMKbGlicmFyeShjYXJldCkgICAgICAgIyBmb3IgZ2VuZXJhbCBtb2RlbCBmaXR0aW5nCmxpYnJhcnkocnBhcnQpICAgICAgICMgZm9yIGZpdHRpbmcgZGVjaXNpb24gdHJlZXMKbGlicmFyeShpcHJlZCkgICAgICAgIyBmb3IgZml0dGluZyBiYWdnZWQgZGVjaXNpb24gdHJlZXMKYGBgCgpXZSdsbCBjb250aW51ZSB0byBpbGx1c3RyYXRlIHRoZSBtYWluIGNvbmNlcHRzIHdpdGggdGhlIGBhbWVzX3RyYWluYCBkYXRhOgoKYGBge3IgMDgtYW1lcy10cmFpbiwgZWNobz1UUlVFfQojIGNyZWF0ZSBBbWVzIHRyYWluaW5nIGRhdGEKc2V0LnNlZWQoMTIzKQphbWVzIDwtIEFtZXNIb3VzaW5nOjptYWtlX2FtZXMoKQpzcGxpdCAgPC0gaW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43LCBzdHJhdGEgPSAiU2FsZV9QcmljZSIpCmFtZXNfdHJhaW4gIDwtIHRyYWluaW5nKHNwbGl0KQphbWVzX3Rlc3QgIDwtIHRlc3Rpbmcoc3BsaXQpCmBgYAoKIyMgV2h5IGFuZCB3aGVuIGJhZ2dpbmcgd29ya3MKCkZpZ3VyZSAxMC4xOgoKYGBge3IgYmFnZ2luZy1tdWx0aXBsZS1tb2RlbHMsIGVjaG89VFJVRSwgZmlnLndpZHRoPTEyLCBmaWcuY2FwPSJUaGUgZWZmZWN0IG9mIGJhZ2dpbmcgMTAwIGJhc2UgbGVhcm5lcnMuIEhpZ2ggdmFyaWFuY2UgbW9kZWxzIHN1Y2ggYXMgZGVjaXNpb24gdHJlZXMgKEMpIGJlbmVmaXQgdGhlIG1vc3QgZnJvbSB0aGUgYWdncmVnYXRpb24gZWZmZWN0IGluIGJhZ2dpbmcsIHdoZXJlYXMgbG93IHZhcmlhbmNlIG1vZGVscyBzdWNoIGFzIHBvbHlub21pYWwgcmVncmVzc2lvbiAoQSkgc2hvdyBsaXR0bGUgaW1wcm92ZW1lbnQuICJ9CiMgU2ltdWxhdGUgc29tZSBub25saW5lYXIgbW9ub3RvbmljIGRhdGEKc2V0LnNlZWQoMTIzKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnggPC0gc2VxKGZyb20gPSAwLCB0byA9IDIgKiBwaSwgbGVuZ3RoID0gNTAwKQp5IDwtIHNpbih4KSArIHJub3JtKGxlbmd0aCh4KSwgc2QgPSAwLjMpCmRmIDwtIGRhdGEuZnJhbWUoeCwgeSkgJT4lCiAgZmlsdGVyKHggPCA0LjUpCgojIGJvb3RzdHJhcHBlZCBwb2x5bm9taWFsIG1vZGVsIGZpdApib290c3RyYXBfbiA8LSAxMDAKYm9vdHN0cmFwX3Jlc3VsdHMgPC0gTlVMTApmb3IoaSBpbiBzZXFfbGVuKGJvb3RzdHJhcF9uKSkgewogICMgcmVwcm9kdWNpYmxlIHNhbXBsZWQgZGF0YSBmcmFtZXMKICBzZXQuc2VlZChpKQogIGluZGV4IDwtIHNhbXBsZShzZXFfbGVuKG5yb3coZGYpKSwgbnJvdyhkZiksIHJlcGxhY2UgPSBUUlVFKQogIGRmX3NpbSA8LSBkZltpbmRleCwgXQogIAogICMgZml0IG1vZGVsIGFuZCBhZGQgcHJlZGljdGlvbnMgdG8gcmVzdWx0cyBkYXRhIGZyYW1lCiAgZml0IDwtIGxtKHkgfiBJKHheMyksIGRhdGEgPSBkZl9zaW0pCiAgZGZfc2ltJHByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LCBkZl9zaW0pCiAgZGZfc2ltJG1vZGVsIDwtIHBhc3RlMCgibW9kZWwiLCBpKQogIGRmX3NpbSRvYiA8LSBpbmRleAogIGJvb3RzdHJhcF9yZXN1bHRzIDwtIHJiaW5kKGJvb3RzdHJhcF9yZXN1bHRzLCBkZl9zaW0pCn0KCnAxIDwtIGdncGxvdChib290c3RyYXBfcmVzdWx0cywgYWVzKHgsIHByZWRpY3Rpb25zKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGRmLCBhZXMoeCwgeSksIGFscGhhID0gLjI1KSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IG1vZGVsKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSwgc2l6ZSA9IC41LCBhbHBoYSA9IC4yKSArCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gIm1lYW4iLCBjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDEsIGdlb20gPSAibGluZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIlJlc3BvbnNlIiwgbGltaXRzID0gYygtMiwgMiksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA1KSwgZXhwYW5kID0gYygwLCAwKSkgKwogIGdndGl0bGUoIkEpIFBvbHlub21pYWwgcmVncmVzc2lvbiIpCgojIGJvb3RzdHJhcHBlZCBNQVJTIG1vZGVsIGZpdApib290c3RyYXBfbiA8LSAxMDAKYm9vdHN0cmFwX3Jlc3VsdHMgPC0gTlVMTApmb3IoaSBpbiBzZXFfbGVuKGJvb3RzdHJhcF9uKSkgewogICMgcmVwcm9kdWNpYmxlIHNhbXBsZWQgZGF0YSBmcmFtZXMKICBzZXQuc2VlZChpKQogIGluZGV4IDwtIHNhbXBsZShzZXFfbGVuKG5yb3coZGYpKSwgbnJvdyhkZiksIHJlcGxhY2UgPSBUUlVFKQogIGRmX3NpbSA8LSBkZltpbmRleCwgXQogIAogICMgZml0IG1vZGVsIGFuZCBhZGQgcHJlZGljdGlvbnMgdG8gcmVzdWx0cyBkYXRhIGZyYW1lCiAgZml0IDwtIGVhcnRoOjplYXJ0aCh5IH4geCwgZGF0YSA9IGRmX3NpbSkKICBkZl9zaW0kcHJlZGljdGlvbnMgPC0gcHJlZGljdChmaXQsIGRmX3NpbSkKICBkZl9zaW0kbW9kZWwgPC0gcGFzdGUwKCJtb2RlbCIsIGkpCiAgZGZfc2ltJG9iIDwtIGluZGV4CiAgYm9vdHN0cmFwX3Jlc3VsdHMgPC0gcmJpbmQoYm9vdHN0cmFwX3Jlc3VsdHMsIGRmX3NpbSkKfQoKcDIgPC0gZ2dwbG90KGJvb3RzdHJhcF9yZXN1bHRzLCBhZXMoeCwgcHJlZGljdGlvbnMpKSArCiAgZ2VvbV9wb2ludChkYXRhID0gZGYsIGFlcyh4LCB5KSwgYWxwaGEgPSAuMjUpICsKICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gbW9kZWwpLCBzaG93LmxlZ2VuZCA9IEZBTFNFLCBzaXplID0gLjUsIGFscGhhID0gLjIpICsKICBzdGF0X3N1bW1hcnkoZnVuLnkgPSAibWVhbiIsIGNvbG91ciA9ICJyZWQiLCBzaXplID0gMSwgZ2VvbSA9ICJsaW5lIikgKwogIHNjYWxlX3lfY29udGludW91cyhOVUxMLCBsaW1pdHMgPSBjKC0yLCAyKSwgZXhwYW5kID0gYygwLCAwKSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDUpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgZ2d0aXRsZSgiQikgTUFSUyIpCgojIGJvb3RzdHJhcHBlZCBkZWNpc2lvbiB0cmVlcyBmaXQKYm9vdHN0cmFwX24gPC0gMTAwCmJvb3RzdHJhcF9yZXN1bHRzIDwtIE5VTEwKZm9yKGkgaW4gc2VxX2xlbihib290c3RyYXBfbikpIHsKICAjIHJlcHJvZHVjaWJsZSBzYW1wbGVkIGRhdGEgZnJhbWVzCiAgc2V0LnNlZWQoaSkKICBpbmRleCA8LSBzYW1wbGUoc2VxX2xlbihucm93KGRmKSksIG5yb3coZGYpLCByZXBsYWNlID0gVFJVRSkKICBkZl9zaW0gPC0gZGZbaW5kZXgsIF0KICAKICAjIGZpdCBtb2RlbCBhbmQgYWRkIHByZWRpY3Rpb25zIHRvIHJlc3VsdHMgZGF0YSBmcmFtZQogIGZpdCA8LSBycGFydDo6cnBhcnQoeSB+IHgsIGRhdGEgPSBkZl9zaW0pCiAgZGZfc2ltJHByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LCBkZl9zaW0pCiAgZGZfc2ltJG1vZGVsIDwtIHBhc3RlMCgibW9kZWwiLCBpKQogIGRmX3NpbSRvYiA8LSBpbmRleAogIGJvb3RzdHJhcF9yZXN1bHRzIDwtIHJiaW5kKGJvb3RzdHJhcF9yZXN1bHRzLCBkZl9zaW0pCn0KCnAzIDwtIGdncGxvdChib290c3RyYXBfcmVzdWx0cywgYWVzKHgsIHByZWRpY3Rpb25zKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGRmLCBhZXMoeCwgeSksIGFscGhhID0gLjI1KSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IG1vZGVsKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSwgc2l6ZSA9IC41LCBhbHBoYSA9IC4yKSArCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gIm1lYW4iLCBjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDEsIGdlb20gPSAibGluZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoTlVMTCwgbGltaXRzID0gYygtMiwgMiksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA1KSwgZXhwYW5kID0gYygwLCAwKSkgKwogIGdndGl0bGUoIkMpIERlY2lzaW9uIHRyZWVzIikKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIG5yb3cgPSAxKQpgYGAKCiMjIEltcGxlbWVudGF0aW9uCgpgYGB7ciBmaXJzdC1iYWdnZWQtYW1lcy1tb2RlbH0KIyBtYWtlIGJvb3RzdHJhcHBpbmcgcmVwcm9kdWNpYmxlCnNldC5zZWVkKDEyMykKCiMgdHJhaW4gYmFnZ2VkIG1vZGVsCmFtZXNfYmFnMSA8LSBiYWdnaW5nKAogIGZvcm11bGEgPSBTYWxlX1ByaWNlIH4gLiwKICBkYXRhID0gYW1lc190cmFpbiwKICBuYmFnZyA9IDEwMCwgIAogIGNvb2IgPSBUUlVFLAogIGNvbnRyb2wgPSBycGFydC5jb250cm9sKG1pbnNwbGl0ID0gMiwgY3AgPSAwKQopCgphbWVzX2JhZzEKYGBgCgpGaWd1cmUgMTAuMjoKCmBgYHtyIG4tYmFncy1wbG90LCBlY2hvPVRSVUUsIGZpZy5jYXA9IkVycm9yIGN1cnZlIGZvciBiYWdnaW5nIDEtMjAwIGRlZXAsIHVucHJ1bmVkIGRlY2lzaW9uIHRyZWVzLiBUaGUgYmVuZWZpdCBvZiBiYWdnaW5nIGlzIG9wdGltaXplZCBhdCAxODcgdHJlZXMgYWx0aG91Z2ggdGhlIG1ham9yaXR5IG9mIGVycm9yIHJlZHVjdGlvbiBvY2N1cnJlZCB3aXRoaW4gdGhlIGZpcnN0IDEwMCB0cmVlcy4ifQojICMgYXNzZXNzIDEwLTIwMCBiYWdnZWQgdHJlZXMKIyBudHJlZSA8LSBzZXEoMTAsIDIwMCwgYnkgPSAyKQojIAojICMgY3JlYXRlIGVtcHR5IHZlY3RvciB0byBzdG9yZSBPT0IgUk1TRSB2YWx1ZXMKIyBybXNlIDwtIHZlY3Rvcihtb2RlID0gIm51bWVyaWMiLCBsZW5ndGggPSBsZW5ndGgobnRyZWUpKQojIAojIGZvciAoaSBpbiBzZXFfYWxvbmcobnRyZWUpKSB7CiMgICAjIHJlcHJvZHVjaWJpbGl0eQojICAgc2V0LnNlZWQoMTIzKQojICAgIyBwZXJmb3JtIGJhZ2dlZCBtb2RlbAojICAgbW9kZWwgPC0gYmFnZ2luZygKIyAgIGZvcm11bGEgPSBTYWxlX1ByaWNlIH4gLiwKIyAgIGRhdGEgICAgPSBhbWVzX3RyYWluLAojICAgY29vYiAgICA9IFRSVUUsCiMgICBjb250cm9sID0gcnBhcnQuY29udHJvbChtaW5zcGxpdCA9IDIsIGNwID0gMCksCiMgICBuYmFnZyAgID0gbnRyZWVbaV0KIyApCiMgICAjIGdldCBPT0IgZXJyb3IKIyAgIHJtc2VbaV0gPC0gbW9kZWwkZXJyCiMgfQojIAojIGJhZ2dpbmdfZXJyb3JzIDwtIGRhdGEuZnJhbWUobnRyZWUsIHJtc2UpCgojIHVzaW5nIHJhbmdlciB0byBkbyB0aGUgc2FtZSBhcyBhYm92ZS4gIFdpbGwgYWxsb3cgZm9yIGJhZ2dpbmcgdW5kZXIgMTAgdHJlZXMKIyBhbmQgaXMgbXVjaCBmYXN0ZXIhCm50cmVlIDwtIHNlcSgxLCAyMDAsIGJ5ID0gMikKIyBjcmVhdGUgZW1wdHkgdmVjdG9yIHRvIHN0b3JlIE9PQiBSTVNFIHZhbHVlcwpybXNlIDwtIHZlY3Rvcihtb2RlID0gIm51bWVyaWMiLCBsZW5ndGggPSBsZW5ndGgobnRyZWUpKQoKZm9yIChpIGluIHNlcV9hbG9uZyhudHJlZSkpIHsKICAjIHJlcHJvZHVjaWJpbGl0eQogIHNldC5zZWVkKDEyMykKICAjIHBlcmZvcm0gYmFnZ2VkIG1vZGVsCiAgbW9kZWwgPC0gcmFuZ2VyOjpyYW5nZXIoCiAgZm9ybXVsYSA9IFNhbGVfUHJpY2UgfiAuLAogIGRhdGEgICAgPSBhbWVzX3RyYWluLAogIG51bS50cmVlcyA9IG50cmVlW2ldLAogIG10cnkgPSBuY29sKGFtZXNfdHJhaW4pIC0gMSwKICBtaW4ubm9kZS5zaXplID0gMQopCiAgIyBnZXQgT09CIGVycm9yCiAgcm1zZVtpXSA8LSBzcXJ0KG1vZGVsJHByZWRpY3Rpb24uZXJyb3IpCn0KCmJhZ2dpbmdfZXJyb3JzIDwtIGRhdGEuZnJhbWUobnRyZWUsIHJtc2UpCgpnZ3Bsb3QoYmFnZ2luZ19lcnJvcnMsIGFlcyhudHJlZSwgcm1zZSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNDEwMTksIGx0eSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmV5NTAiKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMTAwLCB5ID0gNDEzODUsIGxhYmVsID0gIkJlc3QgaW5kaXZpZHVhbCBwcnVuZWQgdHJlZSIsIHZqdXN0ID0gMCwgaGp1c3QgPSAwLCBjb2xvciA9ICJncmV5NTAiKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMTAwLCB5ID0gMjY3NTAsIGxhYmVsID0gIkJhZ2dlZCB0cmVlcyIsIHZqdXN0ID0gMCwgaGp1c3QgPSAwKSArCiAgeWxhYigiUk1TRSIpICsKICB4bGFiKCJOdW1iZXIgb2YgdHJlZXMiKQpgYGAKCl9fX0RvIHRvIG91dHB1dCBzaXplLCB0aGUgY29kZSBjaHVua3MgdGhhdCBmb2xsb3cgc2hvdWxkIG5vdCBiZSByYW4gb24gUlN0dWRpbyBDbG91ZF9fXwoKYGBge3IgYW1lcy1iYWctd2l0aC1jYXJldH0KYW1lc19iYWcyIDwtIHRyYWluKAogIFNhbGVfUHJpY2UgfiAuLAogIGRhdGEgPSBhbWVzX3RyYWluLAogIG1ldGhvZCA9ICJ0cmVlYmFnIiwKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApLAogIG5iYWdnID0gMjAwLCAgCiAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAyLCBjcCA9IDApCikKYW1lc19iYWcyCmBgYAoKIyMgRWFzaWx5IHBhcmFsbGVsaXplCgpgYGB7ciBiYWdnaW5nLXBhcmFsbGVsfQojIENyZWF0ZSBhIHBhcmFsbGVsIHNvY2tldCBjbHVzdGVyCmNsIDwtIG1ha2VDbHVzdGVyKDgpICMgdXNlIDggd29ya2VycwpyZWdpc3RlckRvUGFyYWxsZWwoY2wpICMgcmVnaXN0ZXIgdGhlIHBhcmFsbGVsIGJhY2tlbmQKCiMgRml0IHRyZWVzIGluIHBhcmFsbGVsIGFuZCBjb21wdXRlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldApwcmVkaWN0aW9ucyA8LSBmb3JlYWNoKAogIGljb3VudCgxNjApLCAKICAucGFja2FnZXMgPSAicnBhcnQiLCAKICAuY29tYmluZSA9IGNiaW5kCiAgKSAlZG9wYXIlIHsKICAgICMgYm9vdHN0cmFwIGNvcHkgb2YgdHJhaW5pbmcgZGF0YQogICAgaW5kZXggPC0gc2FtcGxlKG5yb3coYW1lc190cmFpbiksIHJlcGxhY2UgPSBUUlVFKQogICAgYW1lc190cmFpbl9ib290IDwtIGFtZXNfdHJhaW5baW5kZXgsIF0gIAogIAogICAgIyBmaXQgdHJlZSB0byBib290c3RyYXAgY29weQogICAgYmFnZ2VkX3RyZWUgPC0gcnBhcnQoCiAgICAgIFNhbGVfUHJpY2UgfiAuLCAKICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAyLCBjcCA9IDApLAogICAgICBkYXRhID0gYW1lc190cmFpbl9ib290CiAgICAgICkgCiAgICAKICAgIHByZWRpY3QoYmFnZ2VkX3RyZWUsIG5ld2RhdGEgPSBhbWVzX3Rlc3QpCn0KCnByZWRpY3Rpb25zWzE6NSwgMTo3XQpgYGAKCmBgYHtyIHBsb3R0aW5nLXBhcmFsbGVsLWJhZywgZmlnLmNhcD0iRXJyb3IgY3VydmUgZm9yIGN1c3RvbSBwYXJhbGxlbCBiYWdnaW5nIG9mIDEtMTYwIGRlZXAsIHVucHJ1bmVkIGRlY2lzaW9uIHRyZWVzLiJ9CnByZWRpY3Rpb25zICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBtdXRhdGUoCiAgICBvYnNlcnZhdGlvbiA9IDE6bigpLAogICAgYWN0dWFsID0gYW1lc190ZXN0JFNhbGVfUHJpY2UpICU+JQogIHRpZHlyOjpnYXRoZXIodHJlZSwgcHJlZGljdGVkLCAtYyhvYnNlcnZhdGlvbiwgYWN0dWFsKSkgJT4lCiAgZ3JvdXBfYnkob2JzZXJ2YXRpb24pICU+JQogIG11dGF0ZSh0cmVlID0gc3RyaW5ncjo6c3RyX2V4dHJhY3QodHJlZSwgJ1xcZCsnKSAlPiUgYXMubnVtZXJpYygpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgYXJyYW5nZShvYnNlcnZhdGlvbiwgdHJlZSkgJT4lCiAgZ3JvdXBfYnkob2JzZXJ2YXRpb24pICU+JQogIG11dGF0ZShhdmdfcHJlZGljdGlvbiA9IGN1bW1lYW4ocHJlZGljdGVkKSkgJT4lCiAgZ3JvdXBfYnkodHJlZSkgJT4lCiAgc3VtbWFyaXplKFJNU0UgPSBSTVNFKGF2Z19wcmVkaWN0aW9uLCBhY3R1YWwpKSAlPiUKICBnZ3Bsb3QoYWVzKHRyZWUsIFJNU0UpKSArCiAgZ2VvbV9saW5lKCkgKwogIHhsYWIoJ051bWJlciBvZiB0cmVlcycpCmBgYAoKYGBge3Igc2h1dGRvd24tY2x1c3Rlcn0KIyBTaHV0ZG93biBwYXJhbGxlbCBjbHVzdGVyCnN0b3BDbHVzdGVyKGNsKQpgYGAKCiMjIEZlYXR1cmUgaW50ZXJwcmV0YXRpb24gCgpgYGB7ciBiYWctdmlwLCBmaWcuaGVpZ2h0PTUuNzUsIGZpZy5jYXA9IlZhcmlhYmxlIGltcG9ydGFuY2UgZm9yIDIwMCBiYWdnZWQgdHJlZXMgZm9yIHRoZSBBbWVzIEhvdXNpbmcgZGF0YS4ifQp2aXA6OnZpcChhbWVzX2JhZzIsIG51bV9mZWF0dXJlcyA9IDQwLCBiYXIgPSBGQUxTRSkKYGBgCgpgYGB7ciBiYWctcGRwLCBmaWcud2lkdGg9MTAsIGZpZy5jYXA9IlBhcnRpYWwgZGVwZW5kZW5jZSBwbG90cyB0byB1bmRlcnN0YW5kIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBzYWxlcyBwcmljZSBhbmQgdGhlIGxvdCBhcmVhIGFuZCBmcm9udGFnZSBzaXplIGZlYXR1cmVzLiJ9CiMgQ29uc3RydWN0IHBhcnRpYWwgZGVwZW5kZW5jZSBwbG90cwpwMSA8LSBwZHA6OnBhcnRpYWwoCiAgYW1lc19iYWcyLCAKICBwcmVkLnZhciA9ICJMb3RfQXJlYSIsCiAgZ3JpZC5yZXNvbHV0aW9uID0gMjAKICApICU+JSAKICBhdXRvcGxvdCgpCgpwMiA8LSBwZHA6OnBhcnRpYWwoCiAgYW1lc19iYWcyLCAKICBwcmVkLnZhciA9ICJMb3RfRnJvbnRhZ2UiLCAKICBncmlkLnJlc29sdXRpb24gPSAyMAogICkgJT4lIAogIGF1dG9wbG90KCkKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDEpCmBgYAoKCiMjIEZpbmFsIHRob3VnaHRzCgpGaWd1cmUgMTAuNjoKCmBgYHtyIHRyZWUtY29ycmVsYXRpb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmNhcD0iU2l4IGRlY2lzaW9uIHRyZWVzIGJhc2VkIG9uIGRpZmZlcmVudCBib290c3RyYXAgc2FtcGxlcy4iLCBlY2hvPVRSVUUsIGRldj0ncG5nJ30KbGlicmFyeShjYXJldCkKbGlicmFyeShyYW5kb21Gb3Jlc3QpCml0ZXIgPSA2CnBhcihtZnJvdyA9IGMoMywgMykpCmZvcihpIGluIDE6aXRlcil7CiAgc2V0LnNlZWQoaSszMCkKICAjIGNyZWF0ZSB0cmFpbi90ZXN0IHNldHMKICB0cmFpbl9pbmRleCA8LSBjYXJldDo6Y3JlYXRlRGF0YVBhcnRpdGlvbihwZHA6OmJvc3RvbiRjbWVkdiwgcCA9IC42MzMzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXMgPSAxKQogIAogIHRyYWluX0RGIDwtIHBkcDo6Ym9zdG9uW3RyYWluX2luZGV4LF0KICB2YWxpZGF0ZV9ERiA8LSBwZHA6OmJvc3RvblstdHJhaW5faW5kZXgsXQogIAogIHRyYWluX3kgPC0gdHJhaW5fREYkY21lZHYKICB0cmFpbl94IDwtIHRyYWluX0RGWywgc2V0ZGlmZihuYW1lcyh0cmFpbl9ERiksICJjbWVkdiIpXQogIAogIHZhbGlkYXRlX3kgPC0gdmFsaWRhdGVfREYkY21lZHYKICB2YWxpZGF0ZV94IDwtIHZhbGlkYXRlX0RGWywgc2V0ZGlmZihuYW1lcyh2YWxpZGF0ZV9ERiksICJjbWVkdiIpXQogIAogIGRfdHJlZSA8LSBycGFydDo6cnBhcnQoY21lZHYgfiAuLCB0cmFpbl9ERikKICAKICAjIGdyYXBocwogIAogIHJwYXJ0LnBsb3Q6OnJwYXJ0LnBsb3QoZF90cmVlLCBtYWluID0gcGFzdGUwKCJEZWNpc2lvbiBUcmVlICIsIGkpLCB0eXBlID0gMCwgZXh0cmEgPSAwKSAKICAKfQpgYGA=