Assignment
Consider the the Breast Cancer Coimbra data set (available from UCI repository).
Compare three different ensemble approaches, using the ‘caret’ package:
- Bagging (treebag and rf)
- Boosting (C5.0 and gdm)
- Stacking (at least three models)
- Useful packages: bag and caretEnsemble
Evaluation of derived models should follow a correct methodology, comparing different estimates of generalization error (i.e. holdout, cross-validation, bootstrap, …)
Submit a report (in PDF, generated from R) with the code and the resulting analysis.
Introduction
A quick review shows that Ensemble methods are not limited to the above referred. Nevertheless these are indeed the main ensemble learning methods.
Weak (or base) learners are the building block for more complex models. They normally do not perform well by themselves due to high variance or high bias. Ensemble methods therefore try to combine weak learners in order to create a strong learner.
Normally (in bagging and boosting methods), a single base learning algorithm is used to build what we call an “homogeneous” ensemble model. However, we can aggregate several base learning into an “heterogeneous” ensemble model. It is important to choose base models that are coherent to each other, i.e. one with high bias with anoter with high variance, so the ensemble will try to compensate each one.
These are the three main methods of ensemble:
Bagging - Uses the same learning algorithm several times, with changes in the training dataset or parameters and finally combine the models with some kind of deterministic averaging process. This method usually results in a model with less variance than before.
Boosting - Uses the same learning algorithm several times, but sequentially (one model depends on the previous ones), trying to compensate the error, and combines them following a deterministic strategy. This method usually results in a model with less bias than before (variance can also be affected).
Stacking - This often uses different algorithms and combines them using a meta-model to output a combined prediction. This method (as boosting) usually results in a model with less bias than before (variance can also be affected).
Bagging
Here we will compare two bagging algorithms, treebag and random forest against the usual rpart model.
set.seed(2020)
control <- trainControl(
method = "boot",
number = 25,
savePredictions = "final",
classProbs = TRUE,
index = createResample(training$Classification, 25),
summaryFunction = twoClassSummary
)
metric <- "ROC"
set.seed(2020)
fit.rpart <- train(Classification ~ ., data = training, method = "rpart", metric = metric, trControl = control)
set.seed(2020)
fit.treebag <- train(Classification ~ .,
data = training,
method = "treebag", trControl = control, verbose = FALSE
)
set.seed(2020)
fit.rf <- train(Classification ~ .,
data = training,
method = "rf", trControl = control, verbose = FALSE
)
Comparing training metrics
results_bag <- resamples(list(rpart = fit.rpart, treebag = fit.treebag, rf = fit.rf))
# Compare models
dotplot(results_bag)

Comparing predictions
pred_bag <- list()
pred_bag$rpart <- predict(fit.rpart, newdata = testing, type = "prob")[, "Patient"]
pred_bag$treebag <- predict(fit.treebag, newdata = testing, type = "prob")[, "Patient"]
pred_bag$rf <- predict(fit.rf, newdata = testing, type = "prob")[, "Patient"]
pred_bag <- data.frame(pred_bag)
caTools::colAUC(pred_bag, testing$Classification, plotROC = TRUE)
rpart treebag rf
Patient vs. Control 0.5673077 0.7644231 0.8052885

Boosting
Here we will compare two boosting algorithms, gbm and C5.0 against the usual rpart model.
set.seed(2020)
control <- trainControl(
method = "boot",
number = 25,
savePredictions = "final",
classProbs = TRUE,
index = createResample(training$Classification, 25),
summaryFunction = twoClassSummary
)
metric <- "ROC"
set.seed(2020)
fit.rpart <- train(Classification ~ ., data = training, method = "rpart", metric = metric, trControl = control)
set.seed(2020)
fit.gbm <- train(Classification ~ .,
data = training,
method = "gbm", trControl = control, verbose = FALSE, distribution = "adaboost"
)
set.seed(2020)
fit.c50 <- train(Classification ~ .,
data = training,
method = "C5.0", trControl = control, verbose = FALSE
)
Comparing training metrics
results_boost <- resamples(list(rpart = fit.rpart, gbm = fit.gbm, c50 = fit.c50))
# Compare models
dotplot(results_boost)

Comparing predictions
pred_boost <- list()
pred_boost$rpart <- predict(fit.rpart, newdata = testing, type = "prob")[, "Patient"]
pred_boost$gbm <- predict(fit.gbm, newdata = testing, type = "prob")[, "Patient"]
pred_boost$c50 <- predict(fit.c50, newdata = testing, type = "prob")[, "Patient"]
pred_boost <- data.frame(pred_boost)
caTools::colAUC(pred_boost, testing$Classification, plotROC = TRUE)
rpart gbm c50
Patient vs. Control 0.5673077 0.7548077 0.7644231

Stacking
Here we will create a stacking model with rpart, svmLinear and naive bayes and compare with rpart alone.
# DO NOT use the trainControl object used to fit the training models to fit the ensemble.
set.seed(2020)
control <- trainControl(
method = "boot",
number = 25,
savePredictions = "final",
classProbs = TRUE,
index = createResample(training$Classification, 25),
summaryFunction = twoClassSummary
)
metric <- "ROC"
model_list <- caretList(
Classification ~ .,
data = training,
trControl = control,
methodList = c("rpart", "svmLinear", "nb")
)
set.seed(2020)
fit_control <- trainControl(
method = "boot",
number = 25,
savePredictions = "final",
classProbs = TRUE,
index = createResample(training$Classification, 25),
summaryFunction = twoClassSummary
)
set.seed(2020)
fit.rpart <- train(Classification ~ .,
data = training, method = "rpart",
metric = metric, trControl = fit_control
)
set.seed(2020)
fit.svm <- train(Classification ~ .,
data = training, method = "svmLinear",
metric = metric, trControl = fit_control
)
set.seed(2020)
fit.nb <- train(Classification ~ .,
data = training, method = "nb",
metric = metric, trControl = fit_control
)
set.seed(2020)
glm_ensemble <- caretStack(
model_list,
method = "glm",
metric = metric,
trControl = trainControl(
method = "boot",
number = 25,
savePredictions = "final",
classProbs = TRUE,
summaryFunction = twoClassSummary
)
)
Comparing training metrics
results_stack <- resamples(list(
rpart = fit.rpart, svm = fit.svm, nb = fit.nb,
stack = glm_ensemble$ens_model
))
# Compare models
dotplot(results_stack)

Comparing predictions
model_preds <- lapply(model_list, predict, newdata = testing, type = "prob")
model_preds <- lapply(model_preds, function(x) x[, "Patient"])
model_preds <- data.frame(model_preds)
model_preds$stack <- predict(glm_ensemble, newdata = testing, type = "prob")
caTools::colAUC(model_preds, testing$Classification, plotROC = TRUE)
rpart svmLinear nb stack
Patient vs. Control 0.5673077 0.7740385 0.7932692 0.7596154

Comparing all models
Just as a summary, let’s compare all models together.
Comparing training metrics
It is interesting to see that although SVM is ranked as the best model, we clearly see that the stack ensemble is much more robust, having the shortest confidence interval. In general we can also notice that ensemble models are always better than rpart alone.
results_all <- resamples(list(
rpart = fit.rpart, svm = fit.svm, nb = fit.nb,
stack = glm_ensemble$ens_model, gbm = fit.gbm, c50 = fit.c50, treebag = fit.treebag, rf = fit.rf
))
# Compare models
dotplot(results_all)

Comparing predictions
In the “final” test, the predictions in an independent dataset, rpart shows it’s weakness in generalizing the prediction, while rf do a good job.
all_preds <- data.frame(cbind(rpart = model_preds$rpart, svm = model_preds$svmLinear, nb = model_preds$nb, stack = model_preds$stack, gbm = pred_boost$gbm, c50 = pred_boost$c50, treebag = pred_bag$treebag, rf = pred_bag$rf))
caTools::colAUC(all_preds, testing$Classification)
rpart svm nb stack gbm c50 treebag rf
Patient vs. Control 0.5673077 0.7740385 0.7932692 0.7596154 0.7548077 0.7644231 0.7644231 0.8052885
LS0tCnRpdGxlOiAiRW5zZW1ibGUgTW9kZWxzIgphdXRob3I6Ci0gRnJhbmNpc2NvIEJpc2Nob2ZmCi0gTWFudWVsYSBNaWxuZQpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0aGVtZTogdW5pdGVkCiAgICBmaWdfd2lkdGg6IDEwCiAgICBmaWdfaGVpZ2h0OiAxMAogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwotLS0KCiMgQXNzaWdubWVudAoKQ29uc2lkZXIgdGhlIHRoZSBCcmVhc3QgQ2FuY2VyIENvaW1icmEgZGF0YSBzZXQgKGF2YWlsYWJsZSBmcm9tIFVDSSByZXBvc2l0b3J5KS4KCkNvbXBhcmUgdGhyZWUgZGlmZmVyZW50IGVuc2VtYmxlIGFwcHJvYWNoZXMsIHVzaW5nIHRoZSDigJhjYXJldOKAmSBwYWNrYWdlOgoKLSBCYWdnaW5nICh0cmVlYmFnIGFuZCByZikKLSBCb29zdGluZyAoQzUuMCBhbmQgZ2RtKQotIFN0YWNraW5nIChhdCBsZWFzdCB0aHJlZSBtb2RlbHMpCi0gVXNlZnVsIHBhY2thZ2VzOiBiYWcgYW5kIGNhcmV0RW5zZW1ibGUKCkV2YWx1YXRpb24gb2YgZGVyaXZlZCBtb2RlbHMgc2hvdWxkIGZvbGxvdyBhIGNvcnJlY3QgbWV0aG9kb2xvZ3ksIGNvbXBhcmluZyBkaWZmZXJlbnQgZXN0aW1hdGVzIG9mIGdlbmVyYWxpemF0aW9uIGVycm9yIChpLmUuIGhvbGRvdXQsIGNyb3NzLXZhbGlkYXRpb24sIGJvb3RzdHJhcCwgLi4uKQoKU3VibWl0IGEgcmVwb3J0IChpbiBQREYsIGdlbmVyYXRlZCBmcm9tIFIpIHdpdGggdGhlIGNvZGUgYW5kIHRoZSByZXN1bHRpbmcgYW5hbHlzaXMuCgpgYGB7ciBzZXR1cCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShyZWFkcikKbGlicmFyeShjYXJldCkKbGlicmFyeShnYm0pCmxpYnJhcnkoY2FyZXRFbnNlbWJsZSkKCmtuaXRyOjpvcHRzX2NodW5rJHNldCh0aWR5Lm9wdHM9bGlzdCh3aWR0aC5jdXRvZmY9NjApLAogICAgICAgICAgICAgICAgICAgICAgdGlkeT1UUlVFKQoKZGF0YXNldCA8LSByZWFkX2NzdigiZGF0YVIyLmNzdiIpCmRhdGFzZXQkQ2xhc3NpZmljYXRpb24gPC0gZmFjdG9yKGRhdGFzZXQkQ2xhc3NpZmljYXRpb24sIGxhYmVscyA9IGMoIlBhdGllbnQiLCAiQ29udHJvbCIpKQoKc2V0LnNlZWQoMjAyMCkKaWR4c190cmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSBkYXRhc2V0JENsYXNzaWZpY2F0aW9uLCBwID0gMC43NSwgbGlzdCA9IEZBTFNFKQp0cmFpbmluZyA8LSBkYXRhc2V0W2lkeHNfdHJhaW4sIF0KdGVzdGluZyA8LSBkYXRhc2V0Wy1pZHhzX3RyYWluLCBdCmBgYAoKIyBJbnRyb2R1Y3Rpb24KCkEgcXVpY2sgcmV2aWV3IHNob3dzIHRoYXQgRW5zZW1ibGUgbWV0aG9kcyBhcmUgbm90IGxpbWl0ZWQgdG8gdGhlIGFib3ZlIHJlZmVycmVkLiBOZXZlcnRoZWxlc3MgdGhlc2UgYXJlIGluZGVlZCB0aGUgbWFpbiBlbnNlbWJsZSBsZWFybmluZyBtZXRob2RzLiAKCldlYWsgKG9yIGJhc2UpIGxlYXJuZXJzIGFyZSB0aGUgYnVpbGRpbmcgYmxvY2sgZm9yIG1vcmUgY29tcGxleCBtb2RlbHMuIFRoZXkgbm9ybWFsbHkgZG8gbm90IHBlcmZvcm0gd2VsbCBieSB0aGVtc2VsdmVzIGR1ZSB0byBoaWdoIHZhcmlhbmNlIG9yIGhpZ2ggYmlhcy4gRW5zZW1ibGUgbWV0aG9kcyB0aGVyZWZvcmUgdHJ5IHRvIGNvbWJpbmUgd2VhayBsZWFybmVycyBpbiBvcmRlciB0byBjcmVhdGUgYSBzdHJvbmcgbGVhcm5lci4KCk5vcm1hbGx5IChpbiBiYWdnaW5nIGFuZCBib29zdGluZyBtZXRob2RzKSwgYSBzaW5nbGUgYmFzZSBsZWFybmluZyBhbGdvcml0aG0gaXMgdXNlZCB0byBidWlsZCB3aGF0IHdlIGNhbGwgYW4gImhvbW9nZW5lb3VzIiBlbnNlbWJsZSBtb2RlbC4gSG93ZXZlciwgd2UgY2FuIGFnZ3JlZ2F0ZSBzZXZlcmFsIGJhc2UgbGVhcm5pbmcgaW50byBhbiAiaGV0ZXJvZ2VuZW91cyIgZW5zZW1ibGUgbW9kZWwuIEl0IGlzIGltcG9ydGFudCB0byBjaG9vc2UgYmFzZSBtb2RlbHMgdGhhdCBhcmUgY29oZXJlbnQgdG8gZWFjaCBvdGhlciwgaS5lLiBvbmUgd2l0aCBoaWdoIGJpYXMgd2l0aCBhbm90ZXIgd2l0aCBoaWdoIHZhcmlhbmNlLCBzbyB0aGUgZW5zZW1ibGUgd2lsbCB0cnkgdG8gY29tcGVuc2F0ZSBlYWNoIG9uZS4KClRoZXNlIGFyZSB0aGUgdGhyZWUgbWFpbiBtZXRob2RzIG9mIGVuc2VtYmxlOgoKLSAqKkJhZ2dpbmcqKiAtIFVzZXMgdGhlIHNhbWUgbGVhcm5pbmcgYWxnb3JpdGhtIHNldmVyYWwgdGltZXMsIHdpdGggY2hhbmdlcyBpbiB0aGUgdHJhaW5pbmcgZGF0YXNldCBvciBwYXJhbWV0ZXJzIGFuZCBmaW5hbGx5IGNvbWJpbmUgdGhlIG1vZGVscyB3aXRoIHNvbWUga2luZCBvZiBkZXRlcm1pbmlzdGljIGF2ZXJhZ2luZyBwcm9jZXNzLiBUaGlzIG1ldGhvZCB1c3VhbGx5IHJlc3VsdHMgaW4gYSBtb2RlbCB3aXRoIGxlc3MgdmFyaWFuY2UgdGhhbiBiZWZvcmUuCgotICoqQm9vc3RpbmcqKiAtIFVzZXMgdGhlIHNhbWUgbGVhcm5pbmcgYWxnb3JpdGhtIHNldmVyYWwgdGltZXMsIGJ1dCBzZXF1ZW50aWFsbHkgKG9uZSBtb2RlbCBkZXBlbmRzIG9uIHRoZSBwcmV2aW91cyBvbmVzKSwgdHJ5aW5nIHRvIGNvbXBlbnNhdGUgdGhlIGVycm9yLCBhbmQgY29tYmluZXMgdGhlbSBmb2xsb3dpbmcgYSBkZXRlcm1pbmlzdGljIHN0cmF0ZWd5LiBUaGlzIG1ldGhvZCB1c3VhbGx5IHJlc3VsdHMgaW4gYSBtb2RlbCB3aXRoIGxlc3MgYmlhcyB0aGFuIGJlZm9yZSAodmFyaWFuY2UgY2FuIGFsc28gYmUgYWZmZWN0ZWQpLgoKLSAqKlN0YWNraW5nKiogLSBUaGlzIG9mdGVuIHVzZXMgZGlmZmVyZW50IGFsZ29yaXRobXMgYW5kIGNvbWJpbmVzIHRoZW0gdXNpbmcgYSBtZXRhLW1vZGVsIHRvIG91dHB1dCBhIGNvbWJpbmVkIHByZWRpY3Rpb24uIFRoaXMgbWV0aG9kIChhcyBib29zdGluZykgdXN1YWxseSByZXN1bHRzIGluIGEgbW9kZWwgd2l0aCBsZXNzIGJpYXMgdGhhbiBiZWZvcmUgKHZhcmlhbmNlIGNhbiBhbHNvIGJlIGFmZmVjdGVkKS4KCiMgQmFnZ2luZwoKSGVyZSB3ZSB3aWxsIGNvbXBhcmUgdHdvIGJhZ2dpbmcgYWxnb3JpdGhtcywgKnRyZWViYWcqIGFuZCAqcmFuZG9tIGZvcmVzdCogYWdhaW5zdCB0aGUgdXN1YWwgKnJwYXJ0KiBtb2RlbC4KCmBgYHtyIGJhZ2dpbmcsIHdhcm5pbmc9RkFMU0V9CnNldC5zZWVkKDIwMjApCmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJib290IiwKICBudW1iZXIgPSAyNSwKICBzYXZlUHJlZGljdGlvbnMgPSAiZmluYWwiLAogIGNsYXNzUHJvYnMgPSBUUlVFLAogIGluZGV4ID0gY3JlYXRlUmVzYW1wbGUodHJhaW5pbmckQ2xhc3NpZmljYXRpb24sIDI1KSwKICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnkKKQptZXRyaWMgPC0gIlJPQyIKCnNldC5zZWVkKDIwMjApCmZpdC5ycGFydCA8LSB0cmFpbihDbGFzc2lmaWNhdGlvbiB+IC4sIGRhdGEgPSB0cmFpbmluZywgbWV0aG9kID0gInJwYXJ0IiwgbWV0cmljID0gbWV0cmljLCB0ckNvbnRyb2wgPSBjb250cm9sKQoKc2V0LnNlZWQoMjAyMCkKZml0LnRyZWViYWcgPC0gdHJhaW4oQ2xhc3NpZmljYXRpb24gfiAuLAogIGRhdGEgPSB0cmFpbmluZywKICBtZXRob2QgPSAidHJlZWJhZyIsIHRyQ29udHJvbCA9IGNvbnRyb2wsIHZlcmJvc2UgPSBGQUxTRQopCgpzZXQuc2VlZCgyMDIwKQpmaXQucmYgPC0gdHJhaW4oQ2xhc3NpZmljYXRpb24gfiAuLAogIGRhdGEgPSB0cmFpbmluZywKICBtZXRob2QgPSAicmYiLCB0ckNvbnRyb2wgPSBjb250cm9sLCB2ZXJib3NlID0gRkFMU0UKKQpgYGAKCiMjIENvbXBhcmluZyB0cmFpbmluZyBtZXRyaWNzCgpgYGB7ciByZXNfYmFnZ2luZywgd2FybmluZz1GQUxTRX0KcmVzdWx0c19iYWcgPC0gcmVzYW1wbGVzKGxpc3QocnBhcnQgPSBmaXQucnBhcnQsIHRyZWViYWcgPSBmaXQudHJlZWJhZywgcmYgPSBmaXQucmYpKQoKIyBDb21wYXJlIG1vZGVscwpkb3RwbG90KHJlc3VsdHNfYmFnKQpgYGAKCiMjIENvbXBhcmluZyBwcmVkaWN0aW9ucwoKYGBge3IgcHJlZF9iYWdnaW5nLCB3YXJuaW5nPUZBTFNFfQpwcmVkX2JhZyA8LSBsaXN0KCkKcHJlZF9iYWckcnBhcnQgPC0gcHJlZGljdChmaXQucnBhcnQsIG5ld2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInByb2IiKVssICJQYXRpZW50Il0KcHJlZF9iYWckdHJlZWJhZyA8LSBwcmVkaWN0KGZpdC50cmVlYmFnLCBuZXdkYXRhID0gdGVzdGluZywgdHlwZSA9ICJwcm9iIilbLCAiUGF0aWVudCJdCnByZWRfYmFnJHJmIDwtIHByZWRpY3QoZml0LnJmLCBuZXdkYXRhID0gdGVzdGluZywgdHlwZSA9ICJwcm9iIilbLCAiUGF0aWVudCJdCnByZWRfYmFnIDwtIGRhdGEuZnJhbWUocHJlZF9iYWcpCgpjYVRvb2xzOjpjb2xBVUMocHJlZF9iYWcsIHRlc3RpbmckQ2xhc3NpZmljYXRpb24sIHBsb3RST0MgPSBUUlVFKQpgYGAKCiMgQm9vc3RpbmcKCkhlcmUgd2Ugd2lsbCBjb21wYXJlIHR3byBib29zdGluZyBhbGdvcml0aG1zLCAqZ2JtKiBhbmQgKkM1LjAqIGFnYWluc3QgdGhlIHVzdWFsICpycGFydCogbW9kZWwuCgpgYGB7ciBib29zdGluZywgd2FybmluZz1GQUxTRX0Kc2V0LnNlZWQoMjAyMCkKY29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgbWV0aG9kID0gImJvb3QiLAogIG51bWJlciA9IDI1LAogIHNhdmVQcmVkaWN0aW9ucyA9ICJmaW5hbCIsCiAgY2xhc3NQcm9icyA9IFRSVUUsCiAgaW5kZXggPSBjcmVhdGVSZXNhbXBsZSh0cmFpbmluZyRDbGFzc2lmaWNhdGlvbiwgMjUpLAogIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeQopCm1ldHJpYyA8LSAiUk9DIgoKc2V0LnNlZWQoMjAyMCkKZml0LnJwYXJ0IDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwgZGF0YSA9IHRyYWluaW5nLCBtZXRob2QgPSAicnBhcnQiLCBtZXRyaWMgPSBtZXRyaWMsIHRyQ29udHJvbCA9IGNvbnRyb2wpCgpzZXQuc2VlZCgyMDIwKQpmaXQuZ2JtIDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsCiAgbWV0aG9kID0gImdibSIsIHRyQ29udHJvbCA9IGNvbnRyb2wsIHZlcmJvc2UgPSBGQUxTRSwgZGlzdHJpYnV0aW9uID0gImFkYWJvb3N0IgopCgpzZXQuc2VlZCgyMDIwKQpmaXQuYzUwIDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsCiAgbWV0aG9kID0gIkM1LjAiLCB0ckNvbnRyb2wgPSBjb250cm9sLCB2ZXJib3NlID0gRkFMU0UKKQpgYGAKCiMjIENvbXBhcmluZyB0cmFpbmluZyBtZXRyaWNzCgpgYGB7ciByZXNfYm9vc3QsIHdhcm5pbmc9RkFMU0V9CnJlc3VsdHNfYm9vc3QgPC0gcmVzYW1wbGVzKGxpc3QocnBhcnQgPSBmaXQucnBhcnQsIGdibSA9IGZpdC5nYm0sIGM1MCA9IGZpdC5jNTApKQoKIyBDb21wYXJlIG1vZGVscwpkb3RwbG90KHJlc3VsdHNfYm9vc3QpCmBgYAoKIyMgQ29tcGFyaW5nIHByZWRpY3Rpb25zCgpgYGB7ciBwcmVkX2Jvb3N0LCB3YXJuaW5nPUZBTFNFfQpwcmVkX2Jvb3N0IDwtIGxpc3QoKQpwcmVkX2Jvb3N0JHJwYXJ0IDwtIHByZWRpY3QoZml0LnJwYXJ0LCBuZXdkYXRhID0gdGVzdGluZywgdHlwZSA9ICJwcm9iIilbLCAiUGF0aWVudCJdCnByZWRfYm9vc3QkZ2JtIDwtIHByZWRpY3QoZml0LmdibSwgbmV3ZGF0YSA9IHRlc3RpbmcsIHR5cGUgPSAicHJvYiIpWywgIlBhdGllbnQiXQpwcmVkX2Jvb3N0JGM1MCA8LSBwcmVkaWN0KGZpdC5jNTAsIG5ld2RhdGEgPSB0ZXN0aW5nLCB0eXBlID0gInByb2IiKVssICJQYXRpZW50Il0KcHJlZF9ib29zdCA8LSBkYXRhLmZyYW1lKHByZWRfYm9vc3QpCgpjYVRvb2xzOjpjb2xBVUMocHJlZF9ib29zdCwgdGVzdGluZyRDbGFzc2lmaWNhdGlvbiwgcGxvdFJPQyA9IFRSVUUpCmBgYAoKIyBTdGFja2luZwoKSGVyZSB3ZSB3aWxsIGNyZWF0ZSBhIHN0YWNraW5nIG1vZGVsIHdpdGggKnJwYXJ0KiwgKnN2bUxpbmVhciogYW5kICpuYWl2ZSBiYXllcyogYW5kIGNvbXBhcmUgd2l0aCAqcnBhcnQqIGFsb25lLgoKYGBge3Igc3RhY2tpbmcsIHdhcm5pbmc9RkFMU0V9CiMgRE8gTk9UIHVzZSB0aGUgdHJhaW5Db250cm9sIG9iamVjdCB1c2VkIHRvIGZpdCB0aGUgdHJhaW5pbmcgbW9kZWxzIHRvIGZpdCB0aGUgZW5zZW1ibGUuCnNldC5zZWVkKDIwMjApCmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJib290IiwKICBudW1iZXIgPSAyNSwKICBzYXZlUHJlZGljdGlvbnMgPSAiZmluYWwiLAogIGNsYXNzUHJvYnMgPSBUUlVFLAogIGluZGV4ID0gY3JlYXRlUmVzYW1wbGUodHJhaW5pbmckQ2xhc3NpZmljYXRpb24sIDI1KSwKICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnkKKQptZXRyaWMgPC0gIlJPQyIKCm1vZGVsX2xpc3QgPC0gY2FyZXRMaXN0KAogIENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsCiAgdHJDb250cm9sID0gY29udHJvbCwKICBtZXRob2RMaXN0ID0gYygicnBhcnQiLCAic3ZtTGluZWFyIiwgIm5iIikKKQoKc2V0LnNlZWQoMjAyMCkKZml0X2NvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJib290IiwKICBudW1iZXIgPSAyNSwKICBzYXZlUHJlZGljdGlvbnMgPSAiZmluYWwiLAogIGNsYXNzUHJvYnMgPSBUUlVFLAogIGluZGV4ID0gY3JlYXRlUmVzYW1wbGUodHJhaW5pbmckQ2xhc3NpZmljYXRpb24sIDI1KSwKICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnkKKQoKc2V0LnNlZWQoMjAyMCkKZml0LnJwYXJ0IDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJycGFydCIsCiAgbWV0cmljID0gbWV0cmljLCB0ckNvbnRyb2wgPSBmaXRfY29udHJvbAopCgpzZXQuc2VlZCgyMDIwKQpmaXQuc3ZtIDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJzdm1MaW5lYXIiLAogIG1ldHJpYyA9IG1ldHJpYywgdHJDb250cm9sID0gZml0X2NvbnRyb2wKKQoKc2V0LnNlZWQoMjAyMCkKZml0Lm5iIDwtIHRyYWluKENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJuYiIsCiAgbWV0cmljID0gbWV0cmljLCB0ckNvbnRyb2wgPSBmaXRfY29udHJvbAopCgpzZXQuc2VlZCgyMDIwKQpnbG1fZW5zZW1ibGUgPC0gY2FyZXRTdGFjaygKICBtb2RlbF9saXN0LAogIG1ldGhvZCA9ICJnbG0iLAogIG1ldHJpYyA9IG1ldHJpYywKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2woCiAgICBtZXRob2QgPSAiYm9vdCIsCiAgICBudW1iZXIgPSAyNSwKICAgIHNhdmVQcmVkaWN0aW9ucyA9ICJmaW5hbCIsCiAgICBjbGFzc1Byb2JzID0gVFJVRSwKICAgIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeQogICkKKQpgYGAKCiMjIENvbXBhcmluZyB0cmFpbmluZyBtZXRyaWNzCgpgYGB7ciByZXNfc3RhY2tpbmcsIHdhcm5pbmc9RkFMU0V9CnJlc3VsdHNfc3RhY2sgPC0gcmVzYW1wbGVzKGxpc3QoCiAgcnBhcnQgPSBmaXQucnBhcnQsIHN2bSA9IGZpdC5zdm0sIG5iID0gZml0Lm5iLAogIHN0YWNrID0gZ2xtX2Vuc2VtYmxlJGVuc19tb2RlbAopKQoKIyBDb21wYXJlIG1vZGVscwpkb3RwbG90KHJlc3VsdHNfc3RhY2spCmBgYAoKIyMgQ29tcGFyaW5nIHByZWRpY3Rpb25zCgpgYGB7ciBwcmVkX3N0YWNraW5nLCB3YXJuaW5nPUZBTFNFfQptb2RlbF9wcmVkcyA8LSBsYXBwbHkobW9kZWxfbGlzdCwgcHJlZGljdCwgbmV3ZGF0YSA9IHRlc3RpbmcsIHR5cGUgPSAicHJvYiIpCm1vZGVsX3ByZWRzIDwtIGxhcHBseShtb2RlbF9wcmVkcywgZnVuY3Rpb24oeCkgeFssICJQYXRpZW50Il0pCm1vZGVsX3ByZWRzIDwtIGRhdGEuZnJhbWUobW9kZWxfcHJlZHMpCgptb2RlbF9wcmVkcyRzdGFjayA8LSBwcmVkaWN0KGdsbV9lbnNlbWJsZSwgbmV3ZGF0YSA9IHRlc3RpbmcsIHR5cGUgPSAicHJvYiIpCgpjYVRvb2xzOjpjb2xBVUMobW9kZWxfcHJlZHMsIHRlc3RpbmckQ2xhc3NpZmljYXRpb24sIHBsb3RST0MgPSBUUlVFKQpgYGAKCiMgQ29tcGFyaW5nIGFsbCBtb2RlbHMKCkp1c3QgYXMgYSBzdW1tYXJ5LCBsZXQncyBjb21wYXJlIGFsbCBtb2RlbHMgdG9nZXRoZXIuCgojIyBDb21wYXJpbmcgdHJhaW5pbmcgbWV0cmljcwoKSXQgaXMgaW50ZXJlc3RpbmcgdG8gc2VlIHRoYXQgYWx0aG91Z2ggYFNWTWAgaXMgcmFua2VkIGFzIHRoZSBiZXN0IG1vZGVsLCB3ZSBjbGVhcmx5IHNlZSB0aGF0CnRoZSBgc3RhY2tgIGVuc2VtYmxlIGlzIG11Y2ggbW9yZSByb2J1c3QsIGhhdmluZyB0aGUgc2hvcnRlc3QgY29uZmlkZW5jZSBpbnRlcnZhbC4gSW4gZ2VuZXJhbCB3ZSBjYW4gYWxzbwpub3RpY2UgdGhhdCBlbnNlbWJsZSBtb2RlbHMgYXJlIGFsd2F5cyBiZXR0ZXIgdGhhbiBgcnBhcnRgIGFsb25lLgoKYGBge3IgYWxsX3JvY3N9CnJlc3VsdHNfYWxsIDwtIHJlc2FtcGxlcyhsaXN0KAogIHJwYXJ0ID0gZml0LnJwYXJ0LCBzdm0gPSBmaXQuc3ZtLCBuYiA9IGZpdC5uYiwKICBzdGFjayA9IGdsbV9lbnNlbWJsZSRlbnNfbW9kZWwsIGdibSA9IGZpdC5nYm0sIGM1MCA9IGZpdC5jNTAsIHRyZWViYWcgPSBmaXQudHJlZWJhZywgcmYgPSBmaXQucmYKKSkKCiMgQ29tcGFyZSBtb2RlbHMKZG90cGxvdChyZXN1bHRzX2FsbCkKYGBgCgojIyBDb21wYXJpbmcgcHJlZGljdGlvbnMKCkluIHRoZSAiZmluYWwiIHRlc3QsIHRoZSBwcmVkaWN0aW9ucyBpbiBhbiBpbmRlcGVuZGVudCBkYXRhc2V0LCBgcnBhcnRgIHNob3dzIGl0J3Mgd2Vha25lc3MgaW4KZ2VuZXJhbGl6aW5nIHRoZSBwcmVkaWN0aW9uLCB3aGlsZSBgcmZgIGRvIGEgZ29vZCBqb2IuCgpgYGB7ciBhbGxfcHJlZHN9CmFsbF9wcmVkcyA8LSBkYXRhLmZyYW1lKGNiaW5kKHJwYXJ0ID0gbW9kZWxfcHJlZHMkcnBhcnQsIHN2bSA9IG1vZGVsX3ByZWRzJHN2bUxpbmVhciwgbmIgPSBtb2RlbF9wcmVkcyRuYiwgc3RhY2sgPSBtb2RlbF9wcmVkcyRzdGFjaywgZ2JtID0gcHJlZF9ib29zdCRnYm0sIGM1MCA9IHByZWRfYm9vc3QkYzUwLCB0cmVlYmFnID0gcHJlZF9iYWckdHJlZWJhZywgcmYgPSBwcmVkX2JhZyRyZikpCmNhVG9vbHM6OmNvbEFVQyhhbGxfcHJlZHMsIHRlc3RpbmckQ2xhc3NpZmljYXRpb24pCmBgYAo=