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
)
library(tidyverse)
ames <- AmesHousing::make_ames()
Prerequisites
For this chapter we’ll use the following packages:
# Helper packages
library(dplyr) # for data wrangling
library(ggplot2) # for awesome graphics
library(rsample) # for creating validation splits
library(recipes) # for feature engineering
# Modeling packages
library(caret) # for fitting KNN models
To illustrate various concepts we’ll continue working with the ames_train
and ames_test
data sets. We’ll also illustrate the performance of KNNs on the employee attrition and MNIST data sets.
# create training (70%) set for the rsample::attrition data.
attrit <- attrition %>% mutate_if(is.ordered, factor, ordered = FALSE)
set.seed(123)
churn_split <- initial_split(attrit, prop = .7, strata = "Attrition")
churn_train <- training(churn_split)
# import MNIST training data
mnist <- dslabs::read_mnist()
names(mnist)
[1] "train" "test"
# stratified sampling with the rsample package
set.seed(123)
split <- rsample::initial_split(ames, prop = 0.7, strata = "Sale_Price")
ames_train <- rsample::training(split)
Measuring similarity
Figure 8.1
library(ggmap)
library(recipes)
df <- recipe(Sale_Price ~ ., data = ames_train) %>%
step_nzv(all_nominal()) %>%
step_integer(matches("Qual|Cond|QC|Qu")) %>%
step_center(all_numeric(), -all_outcomes()) %>%
step_scale(all_numeric(), -all_outcomes()) %>%
step_dummy(all_nominal(), -all_outcomes(), one_hot = TRUE) %>%
prep(training = ames_train, retain = TRUE) %>%
juice() %>%
select(-Sale_Price)
home <- 30
k = 10
index <- as.vector(FNN::knnx.index(df[-home, ], df[home, ], k = k))
knn_homes <- ames_train[c(home, index), ]
knn_homes %>%
select(Longitude, Latitude) %>%
mutate(desc = factor(c('House of interest', rep('Closest neighbors', k)),
levels = c('House of interest', 'Closest neighbors'))) %>%
qmplot(Longitude, Latitude, data = .,
maptype = "toner-background", darken = .7, color = desc, size = I(2.5)) +
theme(legend.position = "top",
legend.title = element_blank())
Distance measures
(two_houses <- ames_train[1:2, c("Gr_Liv_Area", "Year_Built")])
# Euclidean
dist(two_houses, method = "euclidean")
1
2 760.0007
# Manhattan
dist(two_houses, method = "manhattan")
1
2 761
Figure 8.2:
p1 <- ggplot(two_houses, aes(Gr_Liv_Area, Year_Built)) +
geom_point() +
geom_line(lty = "dashed") +
ggtitle("(A) Euclidean distance")
p2 <- ggplot(two_houses, aes(Gr_Liv_Area, Year_Built)) +
geom_point() +
geom_step(lty = "dashed") +
ggtitle("(B) Manhattan distance")
gridExtra::grid.arrange(p1, p2, nrow = 1)
Pre-processing
home1 <- ames %>%
mutate(id = row_number()) %>%
select(Bedroom_AbvGr, Year_Built, id) %>%
filter(Bedroom_AbvGr == 4 & Year_Built == 2008) %>%
slice(1) %>%
mutate(home = "home1") %>%
select(home, everything())
home2 <- ames %>%
mutate(id = row_number()) %>%
select(Bedroom_AbvGr, Year_Built, id) %>%
filter(Bedroom_AbvGr == 2 & Year_Built == 2008) %>%
slice(1) %>%
mutate(home = "home2") %>%
select(home, everything())
home3 <- ames %>%
mutate(id = row_number()) %>%
select(Bedroom_AbvGr, Year_Built, id) %>%
filter(Bedroom_AbvGr == 3 & Year_Built == 1998) %>%
slice(1) %>%
mutate(home = "home3") %>%
select(home, everything())
home1
home2
home3
features <- c("Bedroom_AbvGr", "Year_Built")
# distance between home 1 and 2
dist(rbind(home1[,features], home2[,features]))
1
2 2
# distance between home 1 and 3
dist(rbind(home1[,features], home3[,features]))
1
2 10.04988
scaled_ames <- recipe(Sale_Price ~ ., ames_train) %>%
step_center(all_numeric()) %>%
step_scale(all_numeric()) %>%
prep(training = ames, retain = TRUE) %>%
juice()
home1_std <- scaled_ames %>%
mutate(id = row_number()) %>%
filter(id == home1$id) %>%
select(Bedroom_AbvGr, Year_Built, id) %>%
mutate(home = "home1") %>%
select(home, everything())
home2_std <- scaled_ames %>%
mutate(id = row_number()) %>%
filter(id == home2$id) %>%
select(Bedroom_AbvGr, Year_Built, id) %>%
mutate(home = "home2") %>%
select(home, everything())
home3_std <- scaled_ames %>%
mutate(id = row_number()) %>%
filter(id == home3$id) %>%
select(Bedroom_AbvGr, Year_Built, id) %>%
mutate(home = "home3") %>%
select(home, everything())
home1_std
home2_std
home3_std
# distance between home 1 and 2
dist(rbind(home1_std[,features], home2_std[,features]))
1
2 2.416244
# distance between home 1 and 3
dist(rbind(home1_std[,features], home3_std[,features]))
1
2 1.252547
Choosing k
# Create blueprint
blueprint <- recipe(Attrition ~ ., data = churn_train) %>%
step_nzv(all_nominal()) %>%
step_integer(contains("Satisfaction")) %>%
step_integer(WorkLifeBalance) %>%
step_integer(JobInvolvement) %>%
step_dummy(all_nominal(), -all_outcomes(), one_hot = TRUE) %>%
step_center(all_numeric(), -all_outcomes()) %>%
step_scale(all_numeric(), -all_outcomes())
# Create a resampling method
cv <- trainControl(
method = "repeatedcv",
number = 10,
repeats = 5,
classProbs = TRUE,
summaryFunction = twoClassSummary
)
# Create a hyperparameter grid search
hyper_grid <- expand.grid(
k = floor(seq(1, nrow(churn_train)/3, length.out = 20))
)
# Fit knn model and perform grid search
knn_grid <- train(
blueprint,
data = churn_train,
method = "knn",
trControl = cv,
tuneGrid = hyper_grid,
metric = "ROC"
)
ggplot(knn_grid)
MNIST example
set.seed(123)
index <- sample(nrow(mnist$train$images), size = 10000)
mnist_x <- mnist$train$images[index, ]
mnist_y <- factor(mnist$train$labels[index])
mnist_x %>%
as.data.frame() %>%
map_df(sd) %>%
gather(feature, sd) %>%
ggplot(aes(sd)) +
geom_histogram(binwidth = 1)
Figure 8.5:
nzv <- nearZeroVar(mnist_x)
par(mfrow = c(1, 4))
i <- 2
image(1:28, 1:28, matrix(mnist$test$images[i,], nrow=28)[ , 28:1],
col = gray(seq(0, 1, 0.05)), xlab = "", ylab="",
xaxt="n", yaxt="n", main = "(A) Example image \nfor digit 2")
i <- 7
image(1:28, 1:28, matrix(mnist$test$images[i,], nrow=28)[ , 28:1],
col = gray(seq(0, 1, 0.05)), xlab = "", ylab="",
xaxt="n", yaxt="n", main = "(B) Example image \nfor digit 4")
i <- 9
image(1:28, 1:28, matrix(mnist$test$images[i,], nrow=28)[ , 28:1],
col = gray(seq(0, 1, 0.05)), xlab = "", ylab="",
xaxt="n", yaxt="n", main = "(C) Example image \nfor digit 5")
image(matrix(!(1:784 %in% nzv), 28, 28), col = gray(seq(0, 1, 0.05)),
xaxt="n", yaxt="n", main = "(D) Typical variability \nin images.")
# Rename features
colnames(mnist_x) <- paste0("V", 1:ncol(mnist_x))
# Remove near zero variance features manually
nzv <- nearZeroVar(mnist_x)
index <- setdiff(1:ncol(mnist_x), nzv)
mnist_x <- mnist_x[, index]
# Use train/validate resampling method
cv <- trainControl(
method = "LGOCV",
p = 0.7,
number = 1,
savePredictions = TRUE
)
# Create a hyperparameter grid search
hyper_grid <- expand.grid(k = seq(3, 25, by = 2))
# Execute grid search
knn_mnist <- train(
mnist_x,
mnist_y,
method = "knn",
tuneGrid = hyper_grid,
preProc = c("center", "scale"),
trControl = cv
)
ggplot(knn_mnist)
# Create confusion matrix
cm <- confusionMatrix(knn_mnist$pred$pred, knn_mnist$pred$obs)
cm$byClass[, c(1:2, 11)] # sensitivity, specificity, & accuracy
Sensitivity Specificity Balanced Accuracy
Class: 0 0.9641638 0.9962374 0.9802006
Class: 1 0.9916667 0.9841210 0.9878938
Class: 2 0.9155666 0.9955114 0.9555390
Class: 3 0.9163952 0.9920325 0.9542139
Class: 4 0.8698630 0.9960538 0.9329584
Class: 5 0.9151404 0.9914891 0.9533148
Class: 6 0.9795322 0.9888684 0.9842003
Class: 7 0.9326520 0.9896962 0.9611741
Class: 8 0.8224382 0.9978798 0.9101590
Class: 9 0.9329897 0.9852687 0.9591292
# Top 20 most important features
vi <- varImp(knn_mnist)
vi
ROC curve variable importance
variables are sorted by maximum importance across the classes
only 20 most important variables shown (out of 249)
# Get median value for feature importance
imp <- vi$importance %>%
rownames_to_column(var = "feature") %>%
gather(response, imp, -feature) %>%
group_by(feature) %>%
summarize(imp = median(imp))
# Create tibble for all edge pixels
edges <- tibble(
feature = paste0("V", nzv),
imp = 0
)
# Combine and plot
imp <- rbind(imp, edges) %>%
mutate(ID = as.numeric(str_extract(feature, "\\d+"))) %>%
arrange(ID)
image(matrix(imp$imp, 28, 28), col = gray(seq(0, 1, 0.05)),
xaxt="n", yaxt="n")
# Get a few accurate predictions
set.seed(9)
good <- knn_mnist$pred %>%
filter(pred == obs) %>%
sample_n(4)
# Get a few inaccurate predictions
set.seed(9)
bad <- knn_mnist$pred %>%
filter(pred != obs) %>%
sample_n(4)
combine <- bind_rows(good, bad)
# Get original feature set with all pixel features
set.seed(123)
index <- sample(nrow(mnist$train$images), 10000)
X <- mnist$train$images[index,]
# Plot results
par(mfrow = c(4, 2), mar=c(1, 1, 1, 1))
layout(matrix(seq_len(nrow(combine)), 4, 2, byrow = FALSE))
for(i in seq_len(nrow(combine))) {
image(matrix(X[combine$rowIndex[i],], 28, 28)[, 28:1],
col = gray(seq(0, 1, 0.05)),
main = paste("Actual:", combine$obs[i], " ",
"Predicted:", combine$pred[i]),
xaxt="n", yaxt="n")
}
LS0tCnRpdGxlOiAiQ2hhcHRlciA4OiBLLU5lYXJlc3QgTmVpZ2hib3JzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpfX05vdGVfXzogU29tZSByZXN1bHRzIG1heSBkaWZmZXIgZnJvbSB0aGUgaGFyZCBjb3B5IGJvb2sgZHVlIHRvIHRoZSBjaGFuZ2luZyBvZiBzYW1wbGluZyBwcm9jZWR1cmVzIGludHJvZHVjZWQgaW4gUiAzLjYuMC4gU2VlIGh0dHA6Ly9iaXQubHkvMzVEMVNXNyBmb3IgbW9yZSBkZXRhaWxzLiBBY2Nlc3MgYW5kIHJ1biB0aGUgc291cmNlIGNvZGUgZm9yIHRoaXMgbm90ZWJvb2sgW2hlcmVdKGh0dHBzOi8vcnN0dWRpby5jbG91ZC9wcm9qZWN0LzgwMTE4NSkuIAoKSGlkZGVuIGNoYXB0ZXIgcmVxdWlyZW1lbnRzIHVzZWQgaW4gdGhlIGJvb2sgdG8gc2V0IHRoZSBwbG90dGluZyB0aGVtZSBhbmQgbG9hZCBwYWNrYWdlcyB1c2VkIGluIGhpZGRlbiBjb2RlIGNodW5rczoKCmBgYHtyIHNldHVwfQojIFNldCBnbG9iYWwgUiBvcHRpb25zCm9wdGlvbnMoc2NpcGVuID0gOTk5KQoKIyBTZXQgdGhlIGdyYXBoaWNhbCB0aGVtZQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfbGlnaHQoKSkKCiMgU2V0IGdsb2JhbCBrbml0ciBjaHVuayBvcHRpb25zCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICB3YXJuaW5nID0gRkFMU0UsIAogIG1lc3NhZ2UgPSBGQUxTRQopCgpsaWJyYXJ5KHRpZHl2ZXJzZSkKYW1lcyA8LSBBbWVzSG91c2luZzo6bWFrZV9hbWVzKCkKYGBgCgojIyBQcmVyZXF1aXNpdGVzCgpGb3IgdGhpcyBjaGFwdGVyIHdlJ2xsIHVzZSB0aGUgZm9sbG93aW5nIHBhY2thZ2VzOgoKYGBge3Iga25uLXBrZ3MsIG1lc3NhZ2U9RkFMU0V9CiMgSGVscGVyIHBhY2thZ2VzCmxpYnJhcnkoZHBseXIpICAgICAgIyBmb3IgZGF0YSB3cmFuZ2xpbmcKbGlicmFyeShnZ3Bsb3QyKSAgICAjIGZvciBhd2Vzb21lIGdyYXBoaWNzCmxpYnJhcnkocnNhbXBsZSkgICAgIyBmb3IgY3JlYXRpbmcgdmFsaWRhdGlvbiBzcGxpdHMKbGlicmFyeShyZWNpcGVzKSAgICAjIGZvciBmZWF0dXJlIGVuZ2luZWVyaW5nCgojIE1vZGVsaW5nIHBhY2thZ2VzCmxpYnJhcnkoY2FyZXQpICAgICAgICMgZm9yIGZpdHRpbmcgS05OIG1vZGVscwpgYGAKClRvIGlsbHVzdHJhdGUgdmFyaW91cyBjb25jZXB0cyB3ZSdsbCBjb250aW51ZSB3b3JraW5nIHdpdGggdGhlIGBhbWVzX3RyYWluYCBhbmQgYGFtZXNfdGVzdGAgZGF0YSBzZXRzLiBXZSdsbCBhbHNvIGlsbHVzdHJhdGUgdGhlIHBlcmZvcm1hbmNlIG9mIEtOTnMgb24gdGhlIGVtcGxveWVlIGF0dHJpdGlvbiBhbmQgTU5JU1QgZGF0YSBzZXRzLgoKYGBge3Iga25uLWRhdGEtcHJlcmVxfQojIGNyZWF0ZSB0cmFpbmluZyAoNzAlKSBzZXQgZm9yIHRoZSByc2FtcGxlOjphdHRyaXRpb24gZGF0YS4KYXR0cml0IDwtIGF0dHJpdGlvbiAlPiUgbXV0YXRlX2lmKGlzLm9yZGVyZWQsIGZhY3Rvciwgb3JkZXJlZCA9IEZBTFNFKQpzZXQuc2VlZCgxMjMpCmNodXJuX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoYXR0cml0LCBwcm9wID0gLjcsIHN0cmF0YSA9ICJBdHRyaXRpb24iKQpjaHVybl90cmFpbiA8LSB0cmFpbmluZyhjaHVybl9zcGxpdCkKCiMgaW1wb3J0IE1OSVNUIHRyYWluaW5nIGRhdGEKbW5pc3QgPC0gZHNsYWJzOjpyZWFkX21uaXN0KCkKbmFtZXMobW5pc3QpCmBgYAoKYGBge3Iga25uLWFtZXMtdHJhaW4sIGVjaG89VFJVRX0KIyBzdHJhdGlmaWVkIHNhbXBsaW5nIHdpdGggdGhlIHJzYW1wbGUgcGFja2FnZQpzZXQuc2VlZCgxMjMpCnNwbGl0ICA8LSByc2FtcGxlOjppbml0aWFsX3NwbGl0KGFtZXMsIHByb3AgPSAwLjcsIHN0cmF0YSA9ICJTYWxlX1ByaWNlIikKYW1lc190cmFpbiAgPC0gcnNhbXBsZTo6dHJhaW5pbmcoc3BsaXQpCmBgYAoKIyMgTWVhc3VyaW5nIHNpbWlsYXJpdHkKCkZpZ3VyZSA4LjEKCmBgYHtyIG1hcC1ob21lcywgZmlnLmNhcD0iVGhlIDEwIG5lYXJlc3QgbmVpZ2hib3JzIChibHVlKSB3aG9zZSBob21lIGF0dHJpYnV0ZXMgbW9zdCBjbG9zZWx5IHJlc2VtYmxlIHRoZSBob3VzZSBvZiBpbnRlcmVzdCAocmVkKS4iLCBlY2hvPVRSVUV9CmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkocmVjaXBlcykKZGYgPC0gcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9uenYoYWxsX25vbWluYWwoKSkgJT4lCiAgc3RlcF9pbnRlZ2VyKG1hdGNoZXMoIlF1YWx8Q29uZHxRQ3xRdSIpKSAlPiUKICBzdGVwX2NlbnRlcihhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpICU+JQogIHN0ZXBfc2NhbGUoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSwgb25lX2hvdCA9IFRSVUUpICU+JQogIHByZXAodHJhaW5pbmcgPSBhbWVzX3RyYWluLCByZXRhaW4gPSBUUlVFKSAlPiUKICBqdWljZSgpICU+JQogIHNlbGVjdCgtU2FsZV9QcmljZSkKCmhvbWUgPC0gMzAKayA9IDEwCmluZGV4IDwtIGFzLnZlY3RvcihGTk46OmtubnguaW5kZXgoZGZbLWhvbWUsIF0sIGRmW2hvbWUsIF0sIGsgPSBrKSkKa25uX2hvbWVzIDwtIGFtZXNfdHJhaW5bYyhob21lLCBpbmRleCksIF0KCmtubl9ob21lcyAlPiUgCiAgc2VsZWN0KExvbmdpdHVkZSwgTGF0aXR1ZGUpICU+JQogIG11dGF0ZShkZXNjID0gZmFjdG9yKGMoJ0hvdXNlIG9mIGludGVyZXN0JywgcmVwKCdDbG9zZXN0IG5laWdoYm9ycycsIGspKSwgCiAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygnSG91c2Ugb2YgaW50ZXJlc3QnLCAnQ2xvc2VzdCBuZWlnaGJvcnMnKSkpICU+JQogIHFtcGxvdChMb25naXR1ZGUsIExhdGl0dWRlLCBkYXRhID0gLiwgCiAgICAgICAgIG1hcHR5cGUgPSAidG9uZXItYmFja2dyb3VuZCIsIGRhcmtlbiA9IC43LCBjb2xvciA9IGRlc2MsIHNpemUgPSBJKDIuNSkpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCiMjIyBEaXN0YW5jZSBtZWFzdXJlcwoKYGBge3IgZGlzdGFuY2UtYnR3bi10d28taG91c2VzfQoodHdvX2hvdXNlcyA8LSBhbWVzX3RyYWluWzE6MiwgYygiR3JfTGl2X0FyZWEiLCAiWWVhcl9CdWlsdCIpXSkKCiMgRXVjbGlkZWFuCmRpc3QodHdvX2hvdXNlcywgbWV0aG9kID0gImV1Y2xpZGVhbiIpCgojIE1hbmhhdHRhbgpkaXN0KHR3b19ob3VzZXMsIG1ldGhvZCA9ICJtYW5oYXR0YW4iKQpgYGAKCkZpZ3VyZSA4LjI6CgpgYGB7ciBkaWZmZXJlbmNlLWJ0d24tZGlzdGFuY2UtbWVhc3VyZXMsIGVjaG89VFJVRSwgZmlnLmhlaWdodD0zLCBmaWcuY2FwPSJFdWNsaWRlYW4gKEEpIHZlcnN1cyBNYW5oYXR0YW4gKEIpIGRpc3RhbmNlLiJ9CnAxIDwtIGdncGxvdCh0d29faG91c2VzLCBhZXMoR3JfTGl2X0FyZWEsIFllYXJfQnVpbHQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUobHR5ID0gImRhc2hlZCIpICsKICBnZ3RpdGxlKCIoQSkgRXVjbGlkZWFuIGRpc3RhbmNlIikKICAKCnAyIDwtIGdncGxvdCh0d29faG91c2VzLCBhZXMoR3JfTGl2X0FyZWEsIFllYXJfQnVpbHQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3N0ZXAobHR5ID0gImRhc2hlZCIpICsKICBnZ3RpdGxlKCIoQikgTWFuaGF0dGFuIGRpc3RhbmNlIikKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDEpCmBgYAoKIyMjIFByZS1wcm9jZXNzaW5nCgpgYGB7ciBzY2FsZS1pbXBhY3RzLWRpc3RhbmNlLWhpZGRlbiwgZWNobz1UUlVFfQpob21lMSA8LSBhbWVzICU+JQogIG11dGF0ZShpZCA9IHJvd19udW1iZXIoKSkgJT4lCiAgc2VsZWN0KEJlZHJvb21fQWJ2R3IsIFllYXJfQnVpbHQsIGlkKSAlPiUKICBmaWx0ZXIoQmVkcm9vbV9BYnZHciA9PSA0ICYgWWVhcl9CdWlsdCA9PSAyMDA4KSAlPiUKICBzbGljZSgxKSAlPiUKICBtdXRhdGUoaG9tZSA9ICJob21lMSIpICU+JQogIHNlbGVjdChob21lLCBldmVyeXRoaW5nKCkpCgpob21lMiA8LSBhbWVzICU+JQogIG11dGF0ZShpZCA9IHJvd19udW1iZXIoKSkgJT4lCiAgc2VsZWN0KEJlZHJvb21fQWJ2R3IsIFllYXJfQnVpbHQsIGlkKSAlPiUKICBmaWx0ZXIoQmVkcm9vbV9BYnZHciA9PSAyICYgWWVhcl9CdWlsdCA9PSAyMDA4KSAlPiUKICBzbGljZSgxKSAlPiUKICBtdXRhdGUoaG9tZSA9ICJob21lMiIpICU+JQogIHNlbGVjdChob21lLCBldmVyeXRoaW5nKCkpCgpob21lMyA8LSBhbWVzICU+JQogIG11dGF0ZShpZCA9IHJvd19udW1iZXIoKSkgJT4lCiAgc2VsZWN0KEJlZHJvb21fQWJ2R3IsIFllYXJfQnVpbHQsIGlkKSAlPiUKICBmaWx0ZXIoQmVkcm9vbV9BYnZHciA9PSAzICYgWWVhcl9CdWlsdCA9PSAxOTk4KSAlPiUKICBzbGljZSgxKSAlPiUKICBtdXRhdGUoaG9tZSA9ICJob21lMyIpICU+JQogIHNlbGVjdChob21lLCBldmVyeXRoaW5nKCkpCmBgYAoKYGBge3Igc2NhbGUtaW1wYWN0cy1kaXN0YW5jZX0KaG9tZTEKaG9tZTIKaG9tZTMKYGBgCgpgYGB7ciBzY2FsZS1pbXBhY3RzLWRpc3RhbmNlMn0KZmVhdHVyZXMgPC0gYygiQmVkcm9vbV9BYnZHciIsICJZZWFyX0J1aWx0IikKCiMgZGlzdGFuY2UgYmV0d2VlbiBob21lIDEgYW5kIDIKZGlzdChyYmluZChob21lMVssZmVhdHVyZXNdLCBob21lMlssZmVhdHVyZXNdKSkKCiMgZGlzdGFuY2UgYmV0d2VlbiBob21lIDEgYW5kIDMKZGlzdChyYmluZChob21lMVssZmVhdHVyZXNdLCBob21lM1ssZmVhdHVyZXNdKSkKYGBgCgpgYGB7ciBzY2FsaW5nLCBlY2hvPVRSVUV9CnNjYWxlZF9hbWVzIDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgYW1lc190cmFpbikgJT4lCiAgc3RlcF9jZW50ZXIoYWxsX251bWVyaWMoKSkgJT4lCiAgc3RlcF9zY2FsZShhbGxfbnVtZXJpYygpKSAlPiUKICBwcmVwKHRyYWluaW5nID0gYW1lcywgcmV0YWluID0gVFJVRSkgJT4lCiAganVpY2UoKQoKaG9tZTFfc3RkIDwtIHNjYWxlZF9hbWVzICU+JQogIG11dGF0ZShpZCA9IHJvd19udW1iZXIoKSkgJT4lCiAgZmlsdGVyKGlkID09IGhvbWUxJGlkKSAlPiUKICBzZWxlY3QoQmVkcm9vbV9BYnZHciwgWWVhcl9CdWlsdCwgaWQpICU+JQogIG11dGF0ZShob21lID0gImhvbWUxIikgJT4lCiAgc2VsZWN0KGhvbWUsIGV2ZXJ5dGhpbmcoKSkKCmhvbWUyX3N0ZCA8LSBzY2FsZWRfYW1lcyAlPiUKICBtdXRhdGUoaWQgPSByb3dfbnVtYmVyKCkpICU+JQogIGZpbHRlcihpZCA9PSBob21lMiRpZCkgJT4lCiAgc2VsZWN0KEJlZHJvb21fQWJ2R3IsIFllYXJfQnVpbHQsIGlkKSAlPiUKICBtdXRhdGUoaG9tZSA9ICJob21lMiIpICU+JQogIHNlbGVjdChob21lLCBldmVyeXRoaW5nKCkpCgpob21lM19zdGQgPC0gc2NhbGVkX2FtZXMgJT4lCiAgbXV0YXRlKGlkID0gcm93X251bWJlcigpKSAlPiUKICBmaWx0ZXIoaWQgPT0gaG9tZTMkaWQpICU+JQogIHNlbGVjdChCZWRyb29tX0FidkdyLCBZZWFyX0J1aWx0LCBpZCkgJT4lCiAgbXV0YXRlKGhvbWUgPSAiaG9tZTMiKSAlPiUKICBzZWxlY3QoaG9tZSwgZXZlcnl0aGluZygpKQpgYGAKCmBgYHtyIHNjYWxlLWltcGFjdHMtZGlzdGFuY2UzfQpob21lMV9zdGQKaG9tZTJfc3RkCmhvbWUzX3N0ZAoKIyBkaXN0YW5jZSBiZXR3ZWVuIGhvbWUgMSBhbmQgMgpkaXN0KHJiaW5kKGhvbWUxX3N0ZFssZmVhdHVyZXNdLCBob21lMl9zdGRbLGZlYXR1cmVzXSkpCgojIGRpc3RhbmNlIGJldHdlZW4gaG9tZSAxIGFuZCAzCmRpc3QocmJpbmQoaG9tZTFfc3RkWyxmZWF0dXJlc10sIGhvbWUzX3N0ZFssZmVhdHVyZXNdKSkKYGBgCgojIyBDaG9vc2luZyBfa18KCmBgYHtyIHJhbmdlLWstdmFsdWVzLCBmaWcuaGVpZ2h0PTMsIGZpZy5jYXA9IkNyb3NzIHZhbGlkYXRlZCBzZWFyY2ggZ3JpZCByZXN1bHRzIGZvciBBdHRyaXRpb24gdHJhaW5pbmcgZGF0YSB3aGVyZSAyMCB2YWx1ZXMgYmV0d2VlbiAxIGFuZCAzNDMgYXJlIGFzc2Vzc2VkIGZvciBrLiBXaGVuIGsgPSAxLCB0aGUgcHJlZGljdGVkIHZhbHVlIGlzIGJhc2VkIG9uIGEgc2luZ2xlIG9ic2VydmF0aW9uIHRoYXQgaXMgY2xvc2VzdCB0byB0aGUgdGFyZ2V0IHNhbXBsZSBhbmQgd2hlbiBrID0gMzQzLCB0aGUgcHJlZGljdGVkIHZhbHVlIGlzIGJhc2VkIG9uIHRoZSByZXNwb25zZSB3aXRoIHRoZSBsYXJnZXN0IHByb3BvcnRpb24gZm9yIDEvMyBvZiB0aGUgdHJhaW5pbmcgc2FtcGxlLiJ9CiMgQ3JlYXRlIGJsdWVwcmludApibHVlcHJpbnQgPC0gcmVjaXBlKEF0dHJpdGlvbiB+IC4sIGRhdGEgPSBjaHVybl90cmFpbikgJT4lCiAgc3RlcF9uenYoYWxsX25vbWluYWwoKSkgJT4lCiAgc3RlcF9pbnRlZ2VyKGNvbnRhaW5zKCJTYXRpc2ZhY3Rpb24iKSkgJT4lCiAgc3RlcF9pbnRlZ2VyKFdvcmtMaWZlQmFsYW5jZSkgJT4lCiAgc3RlcF9pbnRlZ2VyKEpvYkludm9sdmVtZW50KSAlPiUKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSwgb25lX2hvdCA9IFRSVUUpICU+JQogIHN0ZXBfY2VudGVyKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9zY2FsZShhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpCgojIENyZWF0ZSBhIHJlc2FtcGxpbmcgbWV0aG9kCmN2IDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsIAogIG51bWJlciA9IDEwLCAKICByZXBlYXRzID0gNSwKICBjbGFzc1Byb2JzID0gVFJVRSwgICAgICAgICAgICAgICAgIAogIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeQopCgojIENyZWF0ZSBhIGh5cGVycGFyYW1ldGVyIGdyaWQgc2VhcmNoCmh5cGVyX2dyaWQgPC0gZXhwYW5kLmdyaWQoCiAgayA9IGZsb29yKHNlcSgxLCBucm93KGNodXJuX3RyYWluKS8zLCBsZW5ndGgub3V0ID0gMjApKQopCgojIEZpdCBrbm4gbW9kZWwgYW5kIHBlcmZvcm0gZ3JpZCBzZWFyY2gKa25uX2dyaWQgPC0gdHJhaW4oCiAgYmx1ZXByaW50LCAKICBkYXRhID0gY2h1cm5fdHJhaW4sIAogIG1ldGhvZCA9ICJrbm4iLCAKICB0ckNvbnRyb2wgPSBjdiwgCiAgdHVuZUdyaWQgPSBoeXBlcl9ncmlkLAogIG1ldHJpYyA9ICJST0MiCikKCmdncGxvdChrbm5fZ3JpZCkKYGBgCgoKIyMgTU5JU1QgZXhhbXBsZSAKCmBgYHtyIG1uaXN0LXN1YnNhbXBsZX0Kc2V0LnNlZWQoMTIzKQppbmRleCA8LSBzYW1wbGUobnJvdyhtbmlzdCR0cmFpbiRpbWFnZXMpLCBzaXplID0gMTAwMDApCm1uaXN0X3ggPC0gbW5pc3QkdHJhaW4kaW1hZ2VzW2luZGV4LCBdCm1uaXN0X3kgPC0gZmFjdG9yKG1uaXN0JHRyYWluJGxhYmVsc1tpbmRleF0pCmBgYAoKYGBge3IgbW5pc3QtcGxvdC12YXJpYW5jZSwgZmlnLmhlaWdodD0zLCBmaWcuY2FwPSJEaXN0cmlidXRpb24gb2YgdmFyaWFiaWxpdHkgYWNyb3NzIHRoZSBNTklTVCBmZWF0dXJlcy4gIFdlIHNlZSBhIHNpZ25pZmljYW50IG51bWJlciBvZiB6ZXJvIHZhcmlhbmNlIGZlYXR1cmVzIHRoYXQgc2hvdWxkIGJlIHJlbW92ZWQuIn0KbW5pc3RfeCAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgbWFwX2RmKHNkKSAlPiUKICBnYXRoZXIoZmVhdHVyZSwgc2QpICU+JQogIGdncGxvdChhZXMoc2QpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKQpgYGAKCkZpZ3VyZSA4LjU6CgpgYGB7ciBtbmlzdC1wbG90LW56di1mZWF0dXJlLWltYWdlLCBlY2hvPVRSVUUsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTMuNSwgZmlnLmNhcD0iRXhhbXBsZSBpbWFnZXMgKEEpLShDKSBmcm9tIG91ciBkYXRhIHNldCBhbmQgKEQpIGhpZ2hsaWdodHMgbmVhci16ZXJvIHZhcmlhbmNlIGZlYXR1cmVzIGFyb3VuZCB0aGUgZWRnZXMgb2Ygb3VyIGltYWdlcy4ifQpuenYgPC0gbmVhclplcm9WYXIobW5pc3RfeCkKcGFyKG1mcm93ID0gYygxLCA0KSkKaSA8LSAyCmltYWdlKDE6MjgsIDE6MjgsIG1hdHJpeChtbmlzdCR0ZXN0JGltYWdlc1tpLF0sIG5yb3c9MjgpWyAsIDI4OjFdLCAKICAgICAgY29sID0gZ3JheShzZXEoMCwgMSwgMC4wNSkpLCB4bGFiID0gIiIsIHlsYWI9IiIsIAogICAgICB4YXh0PSJuIiwgeWF4dD0ibiIsIG1haW4gPSAiKEEpIEV4YW1wbGUgaW1hZ2UgXG5mb3IgZGlnaXQgMiIpCmkgPC0gNwppbWFnZSgxOjI4LCAxOjI4LCBtYXRyaXgobW5pc3QkdGVzdCRpbWFnZXNbaSxdLCBucm93PTI4KVsgLCAyODoxXSwgCiAgICAgIGNvbCA9IGdyYXkoc2VxKDAsIDEsIDAuMDUpKSwgeGxhYiA9ICIiLCB5bGFiPSIiLCAKICAgICAgeGF4dD0ibiIsIHlheHQ9Im4iLCBtYWluID0gIihCKSBFeGFtcGxlIGltYWdlIFxuZm9yIGRpZ2l0IDQiKQppIDwtIDkKaW1hZ2UoMToyOCwgMToyOCwgbWF0cml4KG1uaXN0JHRlc3QkaW1hZ2VzW2ksXSwgbnJvdz0yOClbICwgMjg6MV0sIAogICAgICBjb2wgPSBncmF5KHNlcSgwLCAxLCAwLjA1KSksIHhsYWIgPSAiIiwgeWxhYj0iIiwgCiAgICAgIHhheHQ9Im4iLCB5YXh0PSJuIiwgbWFpbiA9ICIoQykgRXhhbXBsZSBpbWFnZSBcbmZvciBkaWdpdCA1IikKaW1hZ2UobWF0cml4KCEoMTo3ODQgJWluJSBuenYpLCAyOCwgMjgpLCBjb2wgPSBncmF5KHNlcSgwLCAxLCAwLjA1KSksIAogICAgICB4YXh0PSJuIiwgeWF4dD0ibiIsIG1haW4gPSAiKEQpIFR5cGljYWwgdmFyaWFiaWxpdHkgXG5pbiBpbWFnZXMuIikKYGBgCgpgYGB7ciBwcmVwLW1uaXN0LWRhdGF9CiMgUmVuYW1lIGZlYXR1cmVzCmNvbG5hbWVzKG1uaXN0X3gpIDwtIHBhc3RlMCgiViIsIDE6bmNvbChtbmlzdF94KSkKCiMgUmVtb3ZlIG5lYXIgemVybyB2YXJpYW5jZSBmZWF0dXJlcyBtYW51YWxseQpuenYgPC0gbmVhclplcm9WYXIobW5pc3RfeCkKaW5kZXggPC0gc2V0ZGlmZigxOm5jb2wobW5pc3RfeCksIG56dikKbW5pc3RfeCA8LSBtbmlzdF94WywgaW5kZXhdCmBgYAoKYGBge3IgbW5pc3QtaW5pdGlhbC1tb2RlbCwgZmlnLmhlaWdodD0zLCBmaWcuY2FwPSJLTk4gc2VhcmNoIGdyaWQgcmVzdWx0cyBmb3IgdGhlIE1OSVNUIGRhdGEifQojIFVzZSB0cmFpbi92YWxpZGF0ZSByZXNhbXBsaW5nIG1ldGhvZApjdiA8LSB0cmFpbkNvbnRyb2woCiAgbWV0aG9kID0gIkxHT0NWIiwgCiAgcCA9IDAuNywKICBudW1iZXIgPSAxLAogIHNhdmVQcmVkaWN0aW9ucyA9IFRSVUUKKQoKIyBDcmVhdGUgYSBoeXBlcnBhcmFtZXRlciBncmlkIHNlYXJjaApoeXBlcl9ncmlkIDwtIGV4cGFuZC5ncmlkKGsgPSBzZXEoMywgMjUsIGJ5ID0gMikpCgojIEV4ZWN1dGUgZ3JpZCBzZWFyY2gKa25uX21uaXN0IDwtIHRyYWluKAogIG1uaXN0X3gsCiAgbW5pc3RfeSwKICBtZXRob2QgPSAia25uIiwKICB0dW5lR3JpZCA9IGh5cGVyX2dyaWQsCiAgcHJlUHJvYyA9IGMoImNlbnRlciIsICJzY2FsZSIpLAogIHRyQ29udHJvbCA9IGN2CikKCmdncGxvdChrbm5fbW5pc3QpCmBgYAoKYGBge3IgbW5pc3QtY2xhc3MtcmVzdWx0c30KIyBDcmVhdGUgY29uZnVzaW9uIG1hdHJpeApjbSA8LSBjb25mdXNpb25NYXRyaXgoa25uX21uaXN0JHByZWQkcHJlZCwga25uX21uaXN0JHByZWQkb2JzKQpjbSRieUNsYXNzWywgYygxOjIsIDExKV0gICMgc2Vuc2l0aXZpdHksIHNwZWNpZmljaXR5LCAmIGFjY3VyYWN5CmBgYAoKYGBge3IgbW5pc3Qtdml9CiMgVG9wIDIwIG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzCnZpIDwtIHZhckltcChrbm5fbW5pc3QpCnZpCmBgYAoKYGBge3IgcGxvdC1tbmlzdC12aSwgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9NCwgZmlnLmNhcD0iSW1hZ2UgaGVhdCBtYXAgc2hvd2luZyB3aGljaCBmZWF0dXJlcywgb24gYXZlcmFnZSwgYXJlIG1vc3QgaW5mbHVlbnRpYWwgYWNyb3NzIGFsbCByZXNwb25zZSBjbGFzc2VzIGZvciBvdXIgS05OIG1vZGVsLiJ9CiMgR2V0IG1lZGlhbiB2YWx1ZSBmb3IgZmVhdHVyZSBpbXBvcnRhbmNlCmltcCA8LSB2aSRpbXBvcnRhbmNlICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiZmVhdHVyZSIpICU+JQogIGdhdGhlcihyZXNwb25zZSwgaW1wLCAtZmVhdHVyZSkgJT4lCiAgZ3JvdXBfYnkoZmVhdHVyZSkgJT4lCiAgc3VtbWFyaXplKGltcCA9IG1lZGlhbihpbXApKQoKIyBDcmVhdGUgdGliYmxlIGZvciBhbGwgZWRnZSBwaXhlbHMKZWRnZXMgPC0gdGliYmxlKAogIGZlYXR1cmUgPSBwYXN0ZTAoIlYiLCBuenYpLAogIGltcCA9IDAKKQoKIyBDb21iaW5lIGFuZCBwbG90CmltcCA8LSByYmluZChpbXAsIGVkZ2VzKSAlPiUKICBtdXRhdGUoSUQgID0gYXMubnVtZXJpYyhzdHJfZXh0cmFjdChmZWF0dXJlLCAiXFxkKyIpKSkgJT4lCiAgYXJyYW5nZShJRCkKaW1hZ2UobWF0cml4KGltcCRpbXAsIDI4LCAyOCksIGNvbCA9IGdyYXkoc2VxKDAsIDEsIDAuMDUpKSwgCiAgICAgIHhheHQ9Im4iLCB5YXh0PSJuIikKYGBgCgpgYGB7ciBjb3JyZWN0LXZzLWluY29ycmVjdCwgZmlnLmhlaWdodD03LCBmaWcuY2FwPSJBY3R1YWwgaW1hZ2VzIGZyb20gdGhlIE1OSVNUIGRhdGEgc2V0IGFsb25nIHdpdGggb3VyIEtOTiBtb2RlbCdzIHByZWRpY3Rpb25zLiAgTGVmdCBjb2x1bW4gaWxsdXN0cmF0ZXMgYSBmZXcgYWNjdXJhdGUgcHJlZGljdGlvbnMgYW5kIHRoZSByaWdodCBjb2x1bW4gaWxsdXN0cmF0ZXMgYSBmZXcgaW5hY2N1cmF0ZSBwcmVkaWN0aW9ucy4ifQojIEdldCBhIGZldyBhY2N1cmF0ZSBwcmVkaWN0aW9ucwpzZXQuc2VlZCg5KQpnb29kIDwtIGtubl9tbmlzdCRwcmVkICU+JQogIGZpbHRlcihwcmVkID09IG9icykgJT4lCiAgc2FtcGxlX24oNCkKCiMgR2V0IGEgZmV3IGluYWNjdXJhdGUgcHJlZGljdGlvbnMKc2V0LnNlZWQoOSkKYmFkIDwtIGtubl9tbmlzdCRwcmVkICU+JQogIGZpbHRlcihwcmVkICE9IG9icykgJT4lCiAgc2FtcGxlX24oNCkKCmNvbWJpbmUgPC0gYmluZF9yb3dzKGdvb2QsIGJhZCkKCiMgR2V0IG9yaWdpbmFsIGZlYXR1cmUgc2V0IHdpdGggYWxsIHBpeGVsIGZlYXR1cmVzCnNldC5zZWVkKDEyMykKaW5kZXggPC0gc2FtcGxlKG5yb3cobW5pc3QkdHJhaW4kaW1hZ2VzKSwgMTAwMDApClggPC0gbW5pc3QkdHJhaW4kaW1hZ2VzW2luZGV4LF0KCiMgUGxvdCByZXN1bHRzCnBhcihtZnJvdyA9IGMoNCwgMiksIG1hcj1jKDEsIDEsIDEsIDEpKQpsYXlvdXQobWF0cml4KHNlcV9sZW4obnJvdyhjb21iaW5lKSksIDQsIDIsIGJ5cm93ID0gRkFMU0UpKQpmb3IoaSBpbiBzZXFfbGVuKG5yb3coY29tYmluZSkpKSB7CiAgaW1hZ2UobWF0cml4KFhbY29tYmluZSRyb3dJbmRleFtpXSxdLCAyOCwgMjgpWywgMjg6MV0sIAogICAgICAgIGNvbCA9IGdyYXkoc2VxKDAsIDEsIDAuMDUpKSwKICAgICAgICBtYWluID0gcGFzdGUoIkFjdHVhbDoiLCBjb21iaW5lJG9ic1tpXSwgIiAgIiwgCiAgICAgICAgICAgICAgICAgICAgICJQcmVkaWN0ZWQ6IiwgY29tYmluZSRwcmVkW2ldKSwKICAgICAgICB4YXh0PSJuIiwgeWF4dD0ibiIpIAp9CmBgYAo=