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=