# load packages
library(glmnet)
library(randomForest)
library(dplyr)
library(doMC)
library(foreach)
library(ggplot2)
library(plsgenomics) # for PLS-LDA
# devtools::install_github("ad1729/akshat")
#library(akshat) # my own package with useful functions
Update: An older version of this script ran the nested for loops sequentially which had relatively long run times. The latest version uses the foreach package to run the resampling inner loops in parallel thereby leading to a reduction in computation time.
Introduction
This short project aims to build a classifier for accurately detecting whether patients with a particular gene expression profile have prostate cancer or not. Starting with a brief description of the dataset, statistical methods for building classifiers that are used in this project are mentioned along with the reasons for their selection. Next, R output from the modelling is provided. This is followed by a discussion of the output and some comments.
Data Description
The data are obtained from the study in Singh et al (2002) where the authors provided access to two datasets. The first dataset is taken as the training set with 102 observations, 12600 predictors, and 1 categorical response column indicating “Tumor” or “Normal”. 50 patients belonged to the category “Normal” and 52 patients had tumorous growth. A test dataset was also provided with 34 observations (9 Normal and 25 Tumor) and (12600 + 1) columns in the data matrix.
The code for naming the columns, reading in the training and test data is given in the chunk below.
# read in colnames from the names file
col_name = read.table("prostate/prostate_TumorVSNormal.names", skip = 4)
col_name = col_name[,1]
head(col_name)
[1] AFFX-MurIL2_at AFFX-MurIL10_at AFFX-MurIL4_at AFFX-MurFAS_at AFFX-BioB-5_at
[6] AFFX-BioB-M_at
12600 Levels: 1000_at 1001_at 1002_f_at 1003_s_at 1004_at 1005_at 1006_at 1007_s_at ... AFFX-YEL024w/RIP1_at
col_name = gsub("-", ".", col_name)
col_name = gsub("_", ".", col_name)
head(col_name)
[1] "AFFX.MurIL2.at" "AFFX.MurIL10.at" "AFFX.MurIL4.at" "AFFX.MurFAS.at" "AFFX.BioB.5.at"
[6] "AFFX.BioB.M.at"
# read training data
tr = read.table("prostate/prostate_TumorVSNormal_train.data", header = F, sep = ",",
blank.lines.skip = T, col.names = c(col_name, "response"),
colClasses = c(rep("numeric", length(col_name)), "factor"))
#head(tr[, 1:5]) # last col is the response
str(tr[,1:10])
'data.frame': 102 obs. of 10 variables:
$ AFFX.MurIL2.at : num -9 -2 -6 0 -1 0 -5 -3 -8 -12 ...
$ AFFX.MurIL10.at: num 1 1 17 9 0 17 5 1 -2 11 ...
$ AFFX.MurIL4.at : num 1 1 6 4 1 1 -1 1 -1 -3 ...
$ AFFX.MurFAS.at : num 15 4 29 19 5 20 9 5 -32 21 ...
$ AFFX.BioB.5.at : num -2 -2 4 -10 0 -20 -10 -2 -20 -10 ...
$ AFFX.BioB.M.at : num -3 -5 -11 -18 -4 -18 -17 -6 -41 -9 ...
$ AFFX.BioB.3.at : num 4 0 -8 -18 1 -2 -8 0 1 -9 ...
$ AFFX.BioC.5.at : num 8 8 10 5 6 15 13 0 30 12 ...
$ AFFX.BioC.3.at : num -12 -5 -24 -33 -4 -9 -15 -4 -23 -29 ...
$ AFFX.BioDn.5.at: num -12 -9 -32 -31 -9 -23 -26 -8 -36 -43 ...
#dim(tr)
# read test data
test = read.table("prostate/prostate_TumorVSNormal_test.data", header = F, sep = ",",
blank.lines.skip = T, col.names = c(col_name, "response"),
colClasses = c(rep("numeric", length(col_name)), "factor"))
#head(test[1:10, 1:10]) # last col is the response
#dim(test)
Y = tr[, "response"]
X = tr[, -ncol(tr)]
The following output shows a summary of the variability in the variances across the predictors.
summary(apply(tr[,-ncol(tr)], 2, var))
Min. 1st Qu. Median Mean 3rd Qu. Max.
7 88 259 5332 1024 8031000
This extreme difference in the variances between predictors indicates that standardizing our dataset will be necessary, to put variables on a similar footing, so to speak. We can also get an idea about the multicollinearity among the variables by running the following code chunk.
cor_mat = cor(tr[,-ncol(tr)])
cor_vec = cor_mat[upper.tri(cor_mat, diag = FALSE)]
foo = summary(cor_vec) # summary of pairwise correlations between predictors
rm(cor_mat, cor_vec) # remove the above two objects since they're consuming a lot of memory
foo
Min. 1st Qu. Median Mean 3rd Qu. Max.
-0.989800 -0.306100 0.006571 0.004139 0.313800 0.994900
It appears that most of the genes are uncorrelated with each other, as indicated by the mean/median but some genes are very strongly correlated with each other, as indicated by the min/max values.
Modelling
Since we find ourselves in a situation where \(p \gg n\), a lot of the classical methods will fail. Thus, relatively modern methods are required for the classification task. To this end, the following three methods were used: PLS-LDA (partial least squares - linear discriminant analysis), random forests, and penalized logistic regression.
The classifiers are assessed based on the following metrics: misclassification rate, precision and recall which are defined as follows:
misclassification rate: \(1 - \frac{TP + TN}{N}\)
precision (PPV): \(\frac{TP}{TP + FP}\)
recall (sensitivity): \(\frac{TP}{TP + FN}\)
where TP: true positive, TN: true negative, FP: false positive, FN: false negatives.
PLS-(L)DA
PLS-LDA first performs PLS on the data and then performs classification using LDA on the scores (lower-dimensional projections of the data) that are created by PLS. LDA cannot be used here without preprocessing the data since \(p\) is much larger than \(n\). PCA (principal component analysis) could be performed instead of PLS as an input to LDA, however, PCA creates projected variables that ignore the covariance between the predictors and the response as opposed to the PLS method which takes this into account.
Number of scores/components \((n_{comp})\) to retain from the PLS procedure are selected using cross-validation, by trying a sequence of values. For each \((n_{comp})\), the performance (misclassification rate, precision, recall) is assessed by running 120 repeated 60-40 (train-validation sets) splits of the data.
## PLSDA - train the model and assess performance using repeated CV
score = seq(1, 30, 3) # PLS scores - number of components to retain
mcr.df = c()
ppv.df = c()
rec.df = c()
system.time({
registerDoMC(cores = 8)
for (ctr in seq_along(score)) {
#ctr = 1
#print(sprintf("#comp = %s", score[ctr]))
ncomp = score[ctr]
foo <- foreach(i = 1:120, .combine = 'rbind', .packages = c("plsgenomics", "magrittr")) %dopar% {
# split data into 60-40
indx = unname(unlist(caret::createDataPartition(Y, p = 0.6)))
temp.tr = X[indx, ] %>% apply(., 2, scale)
temp.te = X[-indx, ] %>% apply(., 2, scale)
# fit the model
temp.fit = pls.lda(Xtrain = temp.tr, Ytrain = as.numeric(Y[indx]),
ncomp = ncomp, Xtest = temp.te)
# predict the validation set class
temp.pred = temp.fit$predclass
# convert predict number to label (1: normal, 2: tumor)
temp.class = ifelse(temp.pred == "1", "Normal", "Tumor")
# compute metrics
temp.conf = table(actual = Y[-indx], predicted = temp.class)
temp.mcr = (1 - (sum(base::diag(temp.conf)) / sum(temp.conf))) * 100
temp.ppv = (temp.conf[2,2] / (temp.conf[2,2] + temp.conf[1,2])) * 100
temp.rec = (temp.conf[2,2] / (temp.conf[2,2] + temp.conf[2,1])) * 100
c(ncomp, temp.mcr, temp.ppv, temp.rec)
}
# save the results
mcr.df = rbind(mcr.df, foo[, c(1,2)])
ppv.df = rbind(ppv.df, foo[, c(1,3)])
rec.df = rbind(rec.df, foo[, c(1,4)])
}
})
user system elapsed
10598.412 2264.540 1668.038
beepr::beep(3)
colnames(mcr.df) = c("ncomp", "misclass")
colnames(ppv.df) = c("ncomp", "PosPredVal")
colnames(rec.df) = c("ncomp", "recall")
mcr.df = as.data.frame(mcr.df)
ppv.df = as.data.frame(ppv.df)
rec.df = as.data.frame(rec.df)
The runtime was 28 minutes.
Next, we can visualize the results from the training process. The plots show boxplots for misclassification rate, precision, and recall across the \(n_{comp}\) values. We can pick \(n_{comp}\) based on the value that had the highest (or smallest) median value, or the value at which the boxplot is the narrowest.
mcr.df %>%
ggplot(aes(x = as.factor(ncomp), y = misclass)) +
geom_boxplot() + theme_bw() + xlab("# Components") +
ylab("% Misclassified (120 random splits of 60-40)") +
stat_summary(fun.y = "mean", geom = "point", size = 5, color = "red", shape = 16)

ppv.df %>%
ggplot(aes(x = as.factor(ncomp), y = PosPredVal)) +
geom_boxplot() + theme_bw() + xlab("# Components") +
ylab("Precision (%) (120 random splits of 60-40)") +
stat_summary(fun.y = "mean", geom = "point", size = 5, color = "red", shape = 16)

rec.df %>%
ggplot(aes(x = as.factor(ncomp), y = recall)) +
geom_boxplot() + theme_bw() + xlab("# Components") +
ylab("Sensitivity/Recall (%) (120 random splits of 60-40)") +
stat_summary(fun.y = "mean", geom = "point", size = 5, color = "red", shape = 16)

Based on the figures, we select \(n_{comp}\) as 13. The model will be assessed on the test set in one of the later sections.
Random Forest
Next, a random forest is used as a classifier on the standardized data with 1000 trees. The rest of the parameters are left at their default values (Eg: \(m_{try} \approx \sqrt p\), etc). The following output shows the result of training the random forest classifier on the standardized training data.
X_std = X %>% apply(., 2, scale)
rf_fit = randomForest(x = X_std, y = Y, ntree = 1000, importance = TRUE, proximity = TRUE)
Call:
randomForest(x = X_std, y = Y, ntree = 1000, importance = TRUE, proximity = TRUE)
Type of random forest: classification
Number of trees: 1000
No. of variables tried at each split: 112
OOB estimate of error rate: 8.82%
Confusion matrix:
Normal Tumor class.error
Normal 48 2 0.0400000
Tumor 7 45 0.1346154
Based on the results, we see that the classifier had an error rate of 9.8%. Cross-validation was not explicitly performed here since random forest assesses error rate using out-of-bag samples, i.e., all the cases that were not in the bootstrap sample used to train the model. As the authors of ESL point out in section 15.3.1, the OOB error rate is approximately similar to the LOO-CV rate.
The confusion matrix shows that the classifier has a higher false negative rate rather than the false positive rate. This indicates that the precision will be higher as compared to the recall in this case. Next, the OOB error rate vs the number of trees in the ensemble is plotted below. The plot indicates that the OOB error stabilizes around 200 trees.
#plot(1:1000, rf_fit$err.rate[,1], type = "l", xlab = "# Trees", ylab = "OOB Error Rate", main = "Random Forest Error Rate")
# old code
# data.frame(x = 1:1000, y = rf_fit$err.rate[,1]) %>%
# ggplot(aes(x, y)) +
# geom_line(color = "blue") + theme_bw() +
# xlab("# Trees") + ylab("OOB Error Rate")
# new code that uses the oob_error() function in my package
akshat::oob_error(rf_fit)

# plotting by class
akshat::oob_error(rf_fit, by_class = TRUE)

#plot(rf_fit, main = "")
#varImpPlot(rf_fit)
# proximity plot
#MDSplot(rf_fit, Y)
#title("MDS Proximity Plot For Random Forest")
# MCR, PPV and recall
rf.conf = rf_fit$confusion[,1:2]
rf.mcr = (1 - (sum(base::diag(rf.conf)) / sum(rf.conf))) * 100
rf.ppv = (rf.conf[2,2] / (rf.conf[2,2] + rf.conf[1,2])) * 100
rf.rec = (rf.conf[2,2] / (rf.conf[2,2] + rf.conf[2,1])) * 100
data.frame(misclass = round(rf.mcr, 2), precision = round(rf.ppv, 2), recall = round(rf.rec, 2))
Penalized Linear Regression
Finally, a penalized logistic regression (using the elastic-net penalty) is performed. This fits a penalized linear model which simultaneously performs parameter estimation and variable selection. It rests on the (informal) “bet on sparsity” principle, which states that only a small subset of the predictor have a non-zero effect on the response variable. The final model can easily be written down in a simple prediction equation and only has a small subset of genes which have non-zero coefficients. Model assessment is performed by computing performance criteria on 1000 random splits of the training data into training set (60%) and validation set (40%).
en.mcr = c()
en.ppv = c()
en.rec = c()
registerDoMC(cores = 8)
system.time({
res <- foreach(i = 1:1000, .combine = 'rbind', .packages = c("glmnet")) %dopar% {
# split data into 60-40
indx = unname(unlist(caret::createDataPartition(Y, p = 0.6)))
temp.tr = X[indx, ] #%>% apply(., 2, scale)
temp.te = X[-indx, ] #%>% apply(., 2, scale)
# fit model (standardizes data internally)
temp.las = cv.glmnet(x = data.matrix(temp.tr), y = Y[indx], family = "binomial",
type.measure = "class", nfolds = 5, alpha = 0.5)
# get pred
temp.class = predict(temp.las, data.matrix(temp.te), s = "lambda.1se", type = "class")
# compute metrics
temp.conf = table(actual = Y[-indx], predicted = temp.class)
mcr = (1 - (sum(base::diag(temp.conf)) / sum(temp.conf))) * 100
ppv = (temp.conf[2,2] / (temp.conf[2,2] + temp.conf[1,2])) * 100
rec = (temp.conf[2,2] / (temp.conf[2,2] + temp.conf[2,1])) * 100
c(mcr, ppv, rec)
}
})
user system elapsed
1384.528 20.496 699.936
# remove the rownames
rownames(res) = c()
en.mcr = res[, 1, drop = T]
en.ppv = res[, 2, drop = T]
en.rec = res[, 3, drop = T]
en.res = data.frame(
rbind(unname(summary(en.mcr)), unname(summary(en.ppv)), unname(summary(en.rec))), row.names = c("misclass", "precision", "recall"))
colnames(en.res) = names(summary(en.rec))
#en.res %>% xtable::xtable(digits = 2, align = rep("c", ncol(en.res) + 1), caption = "1000 repeats")
Training time was about 700/60 \(\approx\) 12 minutes.
The table above shows the summary of the misclassification rate, precision, and recall across the 1000 random splits. The median misclassification rate is 7.5%, precision: 95% and recall: 90%. Ideally, the recall should be 100% since it would be catastrophic if the classifier assigned the ‘non-tumor’ label to someone with cancer.
# predict using full data
lasso_fit = cv.glmnet(x = data.matrix(X), y = Y, family = "binomial", type.measure = "class", nfolds = 5, alpha = 0.4)
plot(lasso_fit)

#title("Elastic Net (alpha = 0.5)", line = 2.5)
Selecting \(\lambda\) based on the “1-standard error” rule results in a lambda value of 0.12 and that about 54 genes (out of 12600) have a non-zero coefficient at this lambda value, as seen from the figure above.
Test Set Performance
We can assess the performance of the three classifiers on the test set to get a measure of the generalization error of the classifier. The confusion matrices for the PLS-LDA, random forest, and lasso are given below.
### PLS-LDA
# fit the PLS model to the full training data with ncomp selected from the previous plots
# obtain confusion matrix and metrics from the final model on the test set
testY = test[, c("response")]
Xt = test[, -ncol(test)]
# PLS
fit.pls.lda = pls.lda(Xtrain = X_std, Ytrain = as.numeric(Y), ncomp = 13, Xtest = Xt)
test.class = ifelse(fit.pls.lda$predclass == "1", "Normal", "Tumor")
test.pls.conf = table(actual = testY, predicted = test.class)
pls_test = cbind(
(1 - (sum(base::diag(test.pls.conf)) / sum(test.pls.conf))) * 100, # mcr
(test.pls.conf[2,2] / (test.pls.conf[2,2] + test.pls.conf[1,2])) * 100, # ppv
(test.pls.conf[2,2] / (test.pls.conf[2,2] + test.pls.conf[2,1])) * 100 # rec
)
test.pls.conf
predicted
actual Normal Tumor
Normal 1 8
Tumor 0 25
### Random Forest
# get rf prediction
rf.test = predict(rf_fit, data.frame(apply(Xt, 2, scale)), "response")
# compute metrics
rf.conf.test = table(actual = testY, predicted = rf.test)
rf_test = cbind(
(1 - (base::sum(base::diag(rf.conf.test)) / sum(rf.conf.test))) * 100, #mcr
(rf.conf.test[2,2] / (rf.conf.test[2,2] + rf.conf.test[1,2])) * 100, # ppv/precision
(rf.conf.test[2,2] / (rf.conf.test[2,2] + rf.conf.test[2,1])) * 100 # recall
)
rf.conf.test
predicted
actual Normal Tumor
Normal 9 0
Tumor 2 23
### Lasso
lasso.test = predict(lasso_fit, data.matrix(Xt), s = "lambda.1se", type = "class")
# compute metrics
lasso.conf = table(actual = testY, predicted = lasso.test)
lasso_test = cbind(
(1 - (sum(base::diag(lasso.conf)) / sum(lasso.conf))) * 100, #mcr
(lasso.conf[2,2] / (lasso.conf[2,2] + lasso.conf[1,2])) * 100, # ppv/precision
(lasso.conf[2,2] / (lasso.conf[2,2] + lasso.conf[2,1])) * 100 # recall
)
lasso.conf
predicted
actual Normal Tumor
Normal 9 0
Tumor 1 24
The following table contrasts the various metrics for the three models on the test data.
test_df = data.frame(cbind(t(pls_test), t(rf_test), t(lasso_test)), row.names = c("Misclassification rate", "Precision", "Recall"))
colnames(test_df) = c("PLS-LDA", "RF", "Lasso")
round(test_df, 2)
There was also substantial variability observed in the test set performance of the lasso model across different reruns of the command for training the final lasso model using all the training data.
References
Singh, D., Febbo, P. G., Ross, K., Jackson, D. G., Manola, J., Ladd, C., … & Lander, E. S. (2002). Gene expression correlates of clinical prostate cancer behavior. Cancer cell, 1(2), 203-209. (Dataset Link)
Nguyen, D. V., & Rocke, D. M. (2002). Tumor classification by partial least squares using microarray gene expression data. Bioinformatics, 18(1), 39-50.
Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements of statistical learning. Springer New York.
LS0tCnRpdGxlOiAiRGV0ZWN0aW5nIFByb3N0YXRlIENhbmNlciBVc2luZyBHZW5lIEV4cHJlc3Npb24gUHJvZmlsZXMiCmF1dGhvcjogIkFrc2hhdCBEd2l2ZWRpIgpkYXRlOiAnYHIgU3lzLkRhdGUoKWAnCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgdGhlbWU6IGZsYXRseQogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgaHRtbF9kb2N1bWVudDoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoY2FjaGUgPSBUUlVFKQpgYGAKCgpgYGB7ciwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFfQojIGxvYWQgcGFja2FnZXMKbGlicmFyeShnbG1uZXQpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGRvTUMpCmxpYnJhcnkoZm9yZWFjaCkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHBsc2dlbm9taWNzKSAjIGZvciBQTFMtTERBCiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJhZDE3MjkvYWtzaGF0IikKI2xpYnJhcnkoYWtzaGF0KSAjIG15IG93biBwYWNrYWdlIHdpdGggdXNlZnVsIGZ1bmN0aW9ucwpgYGAKCioqVXBkYXRlOiBBbiBvbGRlciB2ZXJzaW9uIG9mIHRoaXMgc2NyaXB0IHJhbiB0aGUgbmVzdGVkIGZvciBsb29wcyBzZXF1ZW50aWFsbHkgd2hpY2ggaGFkIHJlbGF0aXZlbHkgbG9uZyBydW4gdGltZXMuIFRoZSBsYXRlc3QgdmVyc2lvbiB1c2VzIHRoZSBmb3JlYWNoIHBhY2thZ2UgdG8gcnVuIHRoZSByZXNhbXBsaW5nIGlubmVyIGxvb3BzIGluIHBhcmFsbGVsIHRoZXJlYnkgbGVhZGluZyB0byBhIHJlZHVjdGlvbiBpbiBjb21wdXRhdGlvbiB0aW1lLioqCgojIEludHJvZHVjdGlvbgoKVGhpcyBzaG9ydCBwcm9qZWN0IGFpbXMgdG8gYnVpbGQgYSBjbGFzc2lmaWVyIGZvciBhY2N1cmF0ZWx5IGRldGVjdGluZyB3aGV0aGVyIHBhdGllbnRzIHdpdGggYSBwYXJ0aWN1bGFyIGdlbmUgZXhwcmVzc2lvbiBwcm9maWxlIGhhdmUgcHJvc3RhdGUgY2FuY2VyIG9yIG5vdC4gU3RhcnRpbmcgd2l0aCBhIGJyaWVmIGRlc2NyaXB0aW9uIG9mIHRoZSBkYXRhc2V0LCBzdGF0aXN0aWNhbCBtZXRob2RzIGZvciBidWlsZGluZyBjbGFzc2lmaWVycyB0aGF0IGFyZSB1c2VkIGluIHRoaXMgcHJvamVjdCBhcmUgbWVudGlvbmVkIGFsb25nIHdpdGggdGhlIHJlYXNvbnMgZm9yIHRoZWlyIHNlbGVjdGlvbi4gTmV4dCwgUiBvdXRwdXQgZnJvbSB0aGUgbW9kZWxsaW5nIGlzIHByb3ZpZGVkLiBUaGlzIGlzIGZvbGxvd2VkIGJ5IGEgZGlzY3Vzc2lvbiBvZiB0aGUgb3V0cHV0IGFuZCBzb21lIGNvbW1lbnRzLgoKIyBEYXRhIERlc2NyaXB0aW9uCgpUaGUgZGF0YSBhcmUgb2J0YWluZWQgZnJvbSB0aGUgc3R1ZHkgaW4gU2luZ2ggZXQgYWwgKDIwMDIpIHdoZXJlIHRoZSBhdXRob3JzIHByb3ZpZGVkIGFjY2VzcyB0byB0d28gZGF0YXNldHMuIFRoZSBmaXJzdCBkYXRhc2V0IGlzIHRha2VuIGFzIHRoZSB0cmFpbmluZyBzZXQgd2l0aCAxMDIgb2JzZXJ2YXRpb25zLCAxMjYwMCBwcmVkaWN0b3JzLCBhbmQgMSBjYXRlZ29yaWNhbCByZXNwb25zZSBjb2x1bW4gaW5kaWNhdGluZyAiVHVtb3IiIG9yICJOb3JtYWwiLiA1MCBwYXRpZW50cyBiZWxvbmdlZCB0byB0aGUgY2F0ZWdvcnkgIk5vcm1hbCIgYW5kIDUyIHBhdGllbnRzIGhhZCB0dW1vcm91cyBncm93dGguIEEgdGVzdCBkYXRhc2V0IHdhcyBhbHNvIHByb3ZpZGVkIHdpdGggMzQgb2JzZXJ2YXRpb25zICg5IE5vcm1hbCBhbmQgMjUgVHVtb3IpIGFuZCAoMTI2MDAgKyAxKSBjb2x1bW5zIGluIHRoZSBkYXRhIG1hdHJpeC4KClRoZSBjb2RlIGZvciBuYW1pbmcgdGhlIGNvbHVtbnMsIHJlYWRpbmcgaW4gdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgaXMgZ2l2ZW4gaW4gdGhlIGNodW5rIGJlbG93LgoKYGBge3J9CiMgcmVhZCBpbiBjb2xuYW1lcyBmcm9tIHRoZSBuYW1lcyBmaWxlCmNvbF9uYW1lID0gcmVhZC50YWJsZSgicHJvc3RhdGUvcHJvc3RhdGVfVHVtb3JWU05vcm1hbC5uYW1lcyIsIHNraXAgPSA0KQpjb2xfbmFtZSA9IGNvbF9uYW1lWywxXQpoZWFkKGNvbF9uYW1lKQpjb2xfbmFtZSA9IGdzdWIoIi0iLCAiLiIsIGNvbF9uYW1lKQpjb2xfbmFtZSA9IGdzdWIoIl8iLCAiLiIsIGNvbF9uYW1lKQpoZWFkKGNvbF9uYW1lKQoKIyByZWFkIHRyYWluaW5nIGRhdGEKdHIgPSByZWFkLnRhYmxlKCJwcm9zdGF0ZS9wcm9zdGF0ZV9UdW1vclZTTm9ybWFsX3RyYWluLmRhdGEiLCBoZWFkZXIgPSBGLCBzZXAgPSAiLCIsIAogICAgICAgICAgICAgICAgYmxhbmsubGluZXMuc2tpcCA9IFQsIGNvbC5uYW1lcyA9IGMoY29sX25hbWUsICJyZXNwb25zZSIpLCAKICAgICAgICAgICAgICAgIGNvbENsYXNzZXMgPSBjKHJlcCgibnVtZXJpYyIsIGxlbmd0aChjb2xfbmFtZSkpLCAiZmFjdG9yIikpCiNoZWFkKHRyWywgMTo1XSkgIyBsYXN0IGNvbCBpcyB0aGUgcmVzcG9uc2UKc3RyKHRyWywxOjEwXSkKI2RpbSh0cikKCiMgcmVhZCB0ZXN0IGRhdGEKdGVzdCA9IHJlYWQudGFibGUoInByb3N0YXRlL3Byb3N0YXRlX1R1bW9yVlNOb3JtYWxfdGVzdC5kYXRhIiwgaGVhZGVyID0gRiwgc2VwID0gIiwiLCAKICAgICAgICAgICAgICAgICAgYmxhbmsubGluZXMuc2tpcCA9IFQsIGNvbC5uYW1lcyA9IGMoY29sX25hbWUsICJyZXNwb25zZSIpLCAKICAgICAgICAgICAgICAgICAgY29sQ2xhc3NlcyA9IGMocmVwKCJudW1lcmljIiwgbGVuZ3RoKGNvbF9uYW1lKSksICJmYWN0b3IiKSkKI2hlYWQodGVzdFsxOjEwLCAxOjEwXSkgIyBsYXN0IGNvbCBpcyB0aGUgcmVzcG9uc2UKI2RpbSh0ZXN0KQoKWSA9IHRyWywgInJlc3BvbnNlIl0KWCA9IHRyWywgLW5jb2wodHIpXQpgYGAKClRoZSBmb2xsb3dpbmcgb3V0cHV0IHNob3dzIGEgc3VtbWFyeSBvZiB0aGUgdmFyaWFiaWxpdHkgaW4gdGhlIHZhcmlhbmNlcyBhY3Jvc3MgdGhlIHByZWRpY3RvcnMuCgpgYGB7cn0Kc3VtbWFyeShhcHBseSh0clssLW5jb2wodHIpXSwgMiwgdmFyKSkKYGBgCgpUaGlzIGV4dHJlbWUgZGlmZmVyZW5jZSBpbiB0aGUgdmFyaWFuY2VzIGJldHdlZW4gcHJlZGljdG9ycyBpbmRpY2F0ZXMgdGhhdCBzdGFuZGFyZGl6aW5nIG91ciBkYXRhc2V0IHdpbGwgYmUgbmVjZXNzYXJ5LCB0byBwdXQgdmFyaWFibGVzIG9uIGEgc2ltaWxhciBmb290aW5nLCBzbyB0byBzcGVhay4gV2UgY2FuIGFsc28gZ2V0IGFuIGlkZWEgYWJvdXQgdGhlIG11bHRpY29sbGluZWFyaXR5IGFtb25nIHRoZSB2YXJpYWJsZXMgYnkgcnVubmluZyB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmsuCgpgYGB7cn0KY29yX21hdCA9IGNvcih0clssLW5jb2wodHIpXSkKY29yX3ZlYyA9IGNvcl9tYXRbdXBwZXIudHJpKGNvcl9tYXQsIGRpYWcgPSBGQUxTRSldCmZvbyA9IHN1bW1hcnkoY29yX3ZlYykgIyBzdW1tYXJ5IG9mIHBhaXJ3aXNlIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHByZWRpY3RvcnMKcm0oY29yX21hdCwgY29yX3ZlYykgIyByZW1vdmUgdGhlIGFib3ZlIHR3byBvYmplY3RzIHNpbmNlIHRoZXkncmUgY29uc3VtaW5nIGEgbG90IG9mIG1lbW9yeQpmb28KYGBgCgpJdCBhcHBlYXJzIHRoYXQgbW9zdCBvZiB0aGUgZ2VuZXMgYXJlIHVuY29ycmVsYXRlZCB3aXRoIGVhY2ggb3RoZXIsIGFzIGluZGljYXRlZCBieSB0aGUgbWVhbi9tZWRpYW4gYnV0IHNvbWUgZ2VuZXMgYXJlIHZlcnkgc3Ryb25nbHkgY29ycmVsYXRlZCB3aXRoIGVhY2ggb3RoZXIsIGFzIGluZGljYXRlZCBieSB0aGUgbWluL21heCB2YWx1ZXMuCgojIE1vZGVsbGluZwoKU2luY2Ugd2UgZmluZCBvdXJzZWx2ZXMgaW4gYSBzaXR1YXRpb24gd2hlcmUgJHAgXGdnIG4kLCBhIGxvdCBvZiB0aGUgY2xhc3NpY2FsIG1ldGhvZHMgd2lsbCBmYWlsLiBUaHVzLCByZWxhdGl2ZWx5IG1vZGVybiBtZXRob2RzIGFyZSByZXF1aXJlZCBmb3IgdGhlIGNsYXNzaWZpY2F0aW9uIHRhc2suIFRvIHRoaXMgZW5kLCB0aGUgZm9sbG93aW5nIHRocmVlIG1ldGhvZHMgd2VyZSB1c2VkOiBQTFMtTERBIChwYXJ0aWFsIGxlYXN0IHNxdWFyZXMgLSBsaW5lYXIgZGlzY3JpbWluYW50IGFuYWx5c2lzKSwgcmFuZG9tIGZvcmVzdHMsIGFuZCBwZW5hbGl6ZWQgbG9naXN0aWMgcmVncmVzc2lvbi4KClRoZSBjbGFzc2lmaWVycyBhcmUgYXNzZXNzZWQgYmFzZWQgb24gdGhlIGZvbGxvd2luZyBtZXRyaWNzOiBtaXNjbGFzc2lmaWNhdGlvbiByYXRlLCBwcmVjaXNpb24gYW5kIHJlY2FsbCB3aGljaCBhcmUgZGVmaW5lZCBhcyBmb2xsb3dzOgoKbWlzY2xhc3NpZmljYXRpb24gcmF0ZTogJDEgLSBcZnJhY3tUUCArIFROfXtOfSQKCnByZWNpc2lvbiAoUFBWKTogJFxmcmFje1RQfXtUUCArIEZQfSQKCnJlY2FsbCAoc2Vuc2l0aXZpdHkpOiAkXGZyYWN7VFB9e1RQICsgRk59JAoKd2hlcmUgVFA6IHRydWUgcG9zaXRpdmUsIFROOiB0cnVlIG5lZ2F0aXZlLCBGUDogZmFsc2UgcG9zaXRpdmUsIEZOOiBmYWxzZSBuZWdhdGl2ZXMuCgojIyBQTFMtKEwpREEKClBMUy1MREEgZmlyc3QgcGVyZm9ybXMgUExTIG9uIHRoZSBkYXRhIGFuZCB0aGVuIHBlcmZvcm1zIGNsYXNzaWZpY2F0aW9uIHVzaW5nIExEQSBvbiB0aGUgc2NvcmVzIChsb3dlci1kaW1lbnNpb25hbCBwcm9qZWN0aW9ucyBvZiB0aGUgZGF0YSkgdGhhdCBhcmUgY3JlYXRlZCBieSBQTFMuIExEQSBjYW5ub3QgYmUgdXNlZCBoZXJlIHdpdGhvdXQgcHJlcHJvY2Vzc2luZyB0aGUgZGF0YSBzaW5jZSAkcCQgaXMgbXVjaCBsYXJnZXIgdGhhbiAkbiQuIFBDQSAocHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcykgY291bGQgYmUgcGVyZm9ybWVkIGluc3RlYWQgb2YgUExTIGFzIGFuIGlucHV0IHRvIExEQSwgaG93ZXZlciwgUENBIGNyZWF0ZXMgcHJvamVjdGVkIHZhcmlhYmxlcyB0aGF0IGlnbm9yZSB0aGUgY292YXJpYW5jZSBiZXR3ZWVuIHRoZSBwcmVkaWN0b3JzIGFuZCB0aGUgcmVzcG9uc2UgYXMgb3Bwb3NlZCB0byB0aGUgUExTIG1ldGhvZCB3aGljaCB0YWtlcyB0aGlzIGludG8gYWNjb3VudC4gCgpOdW1iZXIgb2Ygc2NvcmVzL2NvbXBvbmVudHMgJChuX3tjb21wfSkkIHRvIHJldGFpbiBmcm9tIHRoZSBQTFMgcHJvY2VkdXJlIGFyZSBzZWxlY3RlZCB1c2luZyBjcm9zcy12YWxpZGF0aW9uLCBieSB0cnlpbmcgYSBzZXF1ZW5jZSBvZiB2YWx1ZXMuIEZvciBlYWNoICQobl97Y29tcH0pJCwgdGhlIHBlcmZvcm1hbmNlIChtaXNjbGFzc2lmaWNhdGlvbiByYXRlLCBwcmVjaXNpb24sIHJlY2FsbCkgaXMgYXNzZXNzZWQgYnkgcnVubmluZyAxMjAgcmVwZWF0ZWQgNjAtNDAgKHRyYWluLXZhbGlkYXRpb24gc2V0cykgc3BsaXRzIG9mIHRoZSBkYXRhLgoKYGBge3IgcGxzbGRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIyBQTFNEQSAtIHRyYWluIHRoZSBtb2RlbCBhbmQgYXNzZXNzIHBlcmZvcm1hbmNlIHVzaW5nIHJlcGVhdGVkIENWCnNjb3JlID0gc2VxKDEsIDMwLCAzKSAjIFBMUyBzY29yZXMgLSBudW1iZXIgb2YgY29tcG9uZW50cyB0byByZXRhaW4KCm1jci5kZiA9IGMoKQpwcHYuZGYgPSBjKCkKcmVjLmRmID0gYygpCgpzeXN0ZW0udGltZSh7CiAgcmVnaXN0ZXJEb01DKGNvcmVzID0gOCkKICBmb3IgKGN0ciBpbiBzZXFfYWxvbmcoc2NvcmUpKSB7CiAgICAKICAgICNjdHIgPSAxCiAgICAjcHJpbnQoc3ByaW50ZigiI2NvbXAgPSAlcyIsIHNjb3JlW2N0cl0pKQogICAgCiAgICBuY29tcCA9IHNjb3JlW2N0cl0KICAgIAogICAgZm9vIDwtIGZvcmVhY2goaSA9IDE6MTIwLCAuY29tYmluZSA9ICdyYmluZCcsIC5wYWNrYWdlcyA9IGMoInBsc2dlbm9taWNzIiwgIm1hZ3JpdHRyIikpICVkb3BhciUgewogICAgICAKICAgICAgIyBzcGxpdCBkYXRhIGludG8gNjAtNDAKICAgICAgaW5keCA9IHVubmFtZSh1bmxpc3QoY2FyZXQ6OmNyZWF0ZURhdGFQYXJ0aXRpb24oWSwgcCA9IDAuNikpKQogICAgICB0ZW1wLnRyID0gWFtpbmR4LCBdICU+JSBhcHBseSguLCAyLCBzY2FsZSkKICAgICAgdGVtcC50ZSA9IFhbLWluZHgsIF0gJT4lIGFwcGx5KC4sIDIsIHNjYWxlKQogICAgICAKICAgICAgIyBmaXQgdGhlIG1vZGVsCiAgICAgIHRlbXAuZml0ID0gcGxzLmxkYShYdHJhaW4gPSB0ZW1wLnRyLCBZdHJhaW4gPSBhcy5udW1lcmljKFlbaW5keF0pLCAKICAgICAgICAgICAgICAgICAgICAgICAgIG5jb21wID0gbmNvbXAsIFh0ZXN0ID0gdGVtcC50ZSkKICAgICAgCiAgICAgICMgcHJlZGljdCB0aGUgdmFsaWRhdGlvbiBzZXQgY2xhc3MKICAgICAgdGVtcC5wcmVkID0gdGVtcC5maXQkcHJlZGNsYXNzCiAgICAgIAogICAgICAjIGNvbnZlcnQgcHJlZGljdCBudW1iZXIgdG8gbGFiZWwgKDE6IG5vcm1hbCwgMjogdHVtb3IpCiAgICAgIHRlbXAuY2xhc3MgPSBpZmVsc2UodGVtcC5wcmVkID09ICIxIiwgIk5vcm1hbCIsICJUdW1vciIpCiAgICAgIAogICAgICAjIGNvbXB1dGUgbWV0cmljcwogICAgICB0ZW1wLmNvbmYgPSB0YWJsZShhY3R1YWwgPSBZWy1pbmR4XSwgcHJlZGljdGVkID0gdGVtcC5jbGFzcykKICAgICAgdGVtcC5tY3IgPSAoMSAtIChzdW0oYmFzZTo6ZGlhZyh0ZW1wLmNvbmYpKSAvIHN1bSh0ZW1wLmNvbmYpKSkgKiAxMDAKICAgICAgdGVtcC5wcHYgPSAodGVtcC5jb25mWzIsMl0gLyAodGVtcC5jb25mWzIsMl0gKyB0ZW1wLmNvbmZbMSwyXSkpICogMTAwCiAgICAgIHRlbXAucmVjID0gKHRlbXAuY29uZlsyLDJdIC8gKHRlbXAuY29uZlsyLDJdICsgdGVtcC5jb25mWzIsMV0pKSAqIDEwMAogICAgICAKICAgICAgYyhuY29tcCwgdGVtcC5tY3IsIHRlbXAucHB2LCB0ZW1wLnJlYykKICAgIH0KICAgIAogICAgIyBzYXZlIHRoZSByZXN1bHRzCiAgICBtY3IuZGYgPSByYmluZChtY3IuZGYsIGZvb1ssIGMoMSwyKV0pCiAgICBwcHYuZGYgPSByYmluZChwcHYuZGYsIGZvb1ssIGMoMSwzKV0pCiAgICByZWMuZGYgPSByYmluZChyZWMuZGYsIGZvb1ssIGMoMSw0KV0pCiAgICAKICB9Cn0pCiAgCmNvbG5hbWVzKG1jci5kZikgPSBjKCJuY29tcCIsICJtaXNjbGFzcyIpCmNvbG5hbWVzKHBwdi5kZikgPSBjKCJuY29tcCIsICJQb3NQcmVkVmFsIikKY29sbmFtZXMocmVjLmRmKSA9IGMoIm5jb21wIiwgInJlY2FsbCIpCgptY3IuZGYgPSBhcy5kYXRhLmZyYW1lKG1jci5kZikKcHB2LmRmID0gYXMuZGF0YS5mcmFtZShwcHYuZGYpCnJlYy5kZiA9IGFzLmRhdGEuZnJhbWUocmVjLmRmKQpgYGAKClRoZSBydW50aW1lIHdhcyBgciByb3VuZCgxNjY4LzYwKWAgbWludXRlcy4KCk5leHQsIHdlIGNhbiB2aXN1YWxpemUgdGhlIHJlc3VsdHMgZnJvbSB0aGUgdHJhaW5pbmcgcHJvY2Vzcy4gVGhlIHBsb3RzIHNob3cgYm94cGxvdHMgZm9yIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUsIHByZWNpc2lvbiwgYW5kIHJlY2FsbCBhY3Jvc3MgdGhlICRuX3tjb21wfSQgdmFsdWVzLiBXZSBjYW4gcGljayAkbl97Y29tcH0kIGJhc2VkIG9uIHRoZSB2YWx1ZSB0aGF0IGhhZCB0aGUgaGlnaGVzdCAob3Igc21hbGxlc3QpIG1lZGlhbiB2YWx1ZSwgb3IgdGhlIHZhbHVlIGF0IHdoaWNoIHRoZSBib3hwbG90IGlzIHRoZSBuYXJyb3dlc3QuCgpgYGB7cn0KbWNyLmRmICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IobmNvbXApLCB5ID0gbWlzY2xhc3MpKSArCiAgZ2VvbV9ib3hwbG90KCkgKyB0aGVtZV9idygpICsgeGxhYigiIyBDb21wb25lbnRzIikgKyAKICB5bGFiKCIlIE1pc2NsYXNzaWZpZWQgKDEyMCByYW5kb20gc3BsaXRzIG9mIDYwLTQwKSIpICsKICBzdGF0X3N1bW1hcnkoZnVuLnkgPSAibWVhbiIsIGdlb20gPSAicG9pbnQiLCBzaXplID0gNSwgY29sb3IgPSAicmVkIiwgc2hhcGUgPSAxNikKYGBgCgpgYGB7cn0KcHB2LmRmICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IobmNvbXApLCB5ID0gUG9zUHJlZFZhbCkpICsKICBnZW9tX2JveHBsb3QoKSArIHRoZW1lX2J3KCkgKyB4bGFiKCIjIENvbXBvbmVudHMiKSArIAogIHlsYWIoIlByZWNpc2lvbiAoJSkgKDEyMCByYW5kb20gc3BsaXRzIG9mIDYwLTQwKSIpICsKICBzdGF0X3N1bW1hcnkoZnVuLnkgPSAibWVhbiIsIGdlb20gPSAicG9pbnQiLCBzaXplID0gNSwgY29sb3IgPSAicmVkIiwgc2hhcGUgPSAxNikKYGBgCgpgYGB7cn0KcmVjLmRmICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IobmNvbXApLCB5ID0gcmVjYWxsKSkgKwogIGdlb21fYm94cGxvdCgpICsgdGhlbWVfYncoKSArIHhsYWIoIiMgQ29tcG9uZW50cyIpICsgCiAgeWxhYigiU2Vuc2l0aXZpdHkvUmVjYWxsICglKSAoMTIwIHJhbmRvbSBzcGxpdHMgb2YgNjAtNDApIikgKwogIHN0YXRfc3VtbWFyeShmdW4ueSA9ICJtZWFuIiwgZ2VvbSA9ICJwb2ludCIsIHNpemUgPSA1LCBjb2xvciA9ICJyZWQiLCBzaGFwZSA9IDE2KQpgYGAKCkJhc2VkIG9uIHRoZSBmaWd1cmVzLCB3ZSBzZWxlY3QgJG5fe2NvbXB9JCBhcyAxMy4gVGhlIG1vZGVsIHdpbGwgYmUgYXNzZXNzZWQgb24gdGhlIHRlc3Qgc2V0IGluIG9uZSBvZiB0aGUgbGF0ZXIgc2VjdGlvbnMuCgojIyBSYW5kb20gRm9yZXN0CgpOZXh0LCBhIHJhbmRvbSBmb3Jlc3QgaXMgdXNlZCBhcyBhIGNsYXNzaWZpZXIgb24gdGhlIHN0YW5kYXJkaXplZCBkYXRhIHdpdGggMTAwMCB0cmVlcy4gVGhlIHJlc3Qgb2YgdGhlIHBhcmFtZXRlcnMgYXJlIGxlZnQgYXQgdGhlaXIgZGVmYXVsdCB2YWx1ZXMgKEVnOiAkbV97dHJ5fSBcYXBwcm94IFxzcXJ0IHAkLCBldGMpLiBUaGUgZm9sbG93aW5nIG91dHB1dCBzaG93cyB0aGUgcmVzdWx0IG9mIHRyYWluaW5nIHRoZSByYW5kb20gZm9yZXN0IGNsYXNzaWZpZXIgb24gdGhlIHN0YW5kYXJkaXplZCB0cmFpbmluZyBkYXRhLgoKYGBge3J9Clhfc3RkID0gWCAlPiUgYXBwbHkoLiwgMiwgc2NhbGUpCgpyZl9maXQgPSByYW5kb21Gb3Jlc3QoeCA9IFhfc3RkLCB5ID0gWSwgbnRyZWUgPSAxMDAwLCBpbXBvcnRhbmNlID0gVFJVRSwgcHJveGltaXR5ID0gVFJVRSkKYGBgCgpgYGB7cn0KcmZfZml0CmBgYAoKQmFzZWQgb24gdGhlIHJlc3VsdHMsIHdlIHNlZSB0aGF0IHRoZSBjbGFzc2lmaWVyIGhhZCBhbiBlcnJvciByYXRlIG9mIDkuOCUuIENyb3NzLXZhbGlkYXRpb24gd2FzIG5vdCBleHBsaWNpdGx5IHBlcmZvcm1lZCBoZXJlIHNpbmNlIHJhbmRvbSBmb3Jlc3QgYXNzZXNzZXMgZXJyb3IgcmF0ZSB1c2luZyBvdXQtb2YtYmFnIHNhbXBsZXMsIGkuZS4sIGFsbCB0aGUgY2FzZXMgdGhhdCB3ZXJlIG5vdCBpbiB0aGUgYm9vdHN0cmFwIHNhbXBsZSB1c2VkIHRvIHRyYWluIHRoZSBtb2RlbC4gQXMgdGhlIGF1dGhvcnMgb2YgRVNMIHBvaW50IG91dCBpbiBzZWN0aW9uIDE1LjMuMSwgdGhlIE9PQiBlcnJvciByYXRlIGlzIGFwcHJveGltYXRlbHkgc2ltaWxhciB0byB0aGUgTE9PLUNWIHJhdGUuCgpUaGUgY29uZnVzaW9uIG1hdHJpeCBzaG93cyB0aGF0IHRoZSBjbGFzc2lmaWVyIGhhcyBhIGhpZ2hlciBmYWxzZSBuZWdhdGl2ZSByYXRlIHJhdGhlciB0aGFuIHRoZSBmYWxzZSBwb3NpdGl2ZSByYXRlLiBUaGlzIGluZGljYXRlcyB0aGF0IHRoZSBwcmVjaXNpb24gd2lsbCBiZSBoaWdoZXIgYXMgY29tcGFyZWQgdG8gdGhlIHJlY2FsbCBpbiB0aGlzIGNhc2UuIE5leHQsIHRoZSBPT0IgZXJyb3IgcmF0ZSB2cyB0aGUgbnVtYmVyIG9mIHRyZWVzIGluIHRoZSBlbnNlbWJsZSBpcyBwbG90dGVkIGJlbG93LiBUaGUgcGxvdCBpbmRpY2F0ZXMgdGhhdCB0aGUgT09CIGVycm9yIHN0YWJpbGl6ZXMgYXJvdW5kIDIwMCB0cmVlcy4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQojcGxvdCgxOjEwMDAsIHJmX2ZpdCRlcnIucmF0ZVssMV0sIHR5cGUgPSAibCIsIHhsYWIgPSAiIyBUcmVlcyIsIHlsYWIgPSAiT09CIEVycm9yIFJhdGUiLCBtYWluID0gIlJhbmRvbSBGb3Jlc3QgRXJyb3IgUmF0ZSIpCgojIG9sZCBjb2RlCiMgZGF0YS5mcmFtZSh4ID0gMToxMDAwLCB5ID0gcmZfZml0JGVyci5yYXRlWywxXSkgJT4lCiMgICBnZ3Bsb3QoYWVzKHgsIHkpKSArIAojICAgZ2VvbV9saW5lKGNvbG9yID0gImJsdWUiKSArIHRoZW1lX2J3KCkgKyAKIyAgIHhsYWIoIiMgVHJlZXMiKSArIHlsYWIoIk9PQiBFcnJvciBSYXRlIikKCiMgbmV3IGNvZGUgdGhhdCB1c2VzIHRoZSBvb2JfZXJyb3IoKSBmdW5jdGlvbiBpbiBteSBwYWNrYWdlCmFrc2hhdDo6b29iX2Vycm9yKHJmX2ZpdCkKCiMgcGxvdHRpbmcgYnkgY2xhc3MKYWtzaGF0Ojpvb2JfZXJyb3IocmZfZml0LCBieV9jbGFzcyA9IFRSVUUpCgojcGxvdChyZl9maXQsIG1haW4gPSAiIikKCiN2YXJJbXBQbG90KHJmX2ZpdCkKCiMgcHJveGltaXR5IHBsb3QKI01EU3Bsb3QocmZfZml0LCBZKQojdGl0bGUoIk1EUyBQcm94aW1pdHkgUGxvdCBGb3IgUmFuZG9tIEZvcmVzdCIpCgojIE1DUiwgUFBWIGFuZCByZWNhbGwKcmYuY29uZiA9IHJmX2ZpdCRjb25mdXNpb25bLDE6Ml0KcmYubWNyID0gKDEgLSAoc3VtKGJhc2U6OmRpYWcocmYuY29uZikpIC8gc3VtKHJmLmNvbmYpKSkgKiAxMDAKcmYucHB2ID0gKHJmLmNvbmZbMiwyXSAvIChyZi5jb25mWzIsMl0gKyByZi5jb25mWzEsMl0pKSAqIDEwMApyZi5yZWMgPSAocmYuY29uZlsyLDJdIC8gKHJmLmNvbmZbMiwyXSArIHJmLmNvbmZbMiwxXSkpICogMTAwCmRhdGEuZnJhbWUobWlzY2xhc3MgPSByb3VuZChyZi5tY3IsIDIpLCBwcmVjaXNpb24gPSByb3VuZChyZi5wcHYsIDIpLCByZWNhbGwgPSByb3VuZChyZi5yZWMsIDIpKQpgYGAKCiMjIFBlbmFsaXplZCBMaW5lYXIgUmVncmVzc2lvbgoKRmluYWxseSwgYSBwZW5hbGl6ZWQgbG9naXN0aWMgcmVncmVzc2lvbiAodXNpbmcgdGhlIGVsYXN0aWMtbmV0IHBlbmFsdHkpIGlzIHBlcmZvcm1lZC4gVGhpcyBmaXRzIGEgcGVuYWxpemVkIGxpbmVhciBtb2RlbCB3aGljaCBzaW11bHRhbmVvdXNseSBwZXJmb3JtcyBwYXJhbWV0ZXIgZXN0aW1hdGlvbiBhbmQgdmFyaWFibGUgc2VsZWN0aW9uLiBJdCByZXN0cyBvbiB0aGUgKGluZm9ybWFsKSAiYmV0IG9uIHNwYXJzaXR5IiBwcmluY2lwbGUsIHdoaWNoIHN0YXRlcyB0aGF0IG9ubHkgYSBzbWFsbCBzdWJzZXQgb2YgdGhlIHByZWRpY3RvciBoYXZlIGEgbm9uLXplcm8gZWZmZWN0IG9uIHRoZSByZXNwb25zZSB2YXJpYWJsZS4gVGhlIGZpbmFsIG1vZGVsIGNhbiBlYXNpbHkgYmUgd3JpdHRlbiBkb3duIGluIGEgc2ltcGxlIHByZWRpY3Rpb24gZXF1YXRpb24gYW5kIG9ubHkgaGFzIGEgc21hbGwgc3Vic2V0IG9mIGdlbmVzIHdoaWNoIGhhdmUgbm9uLXplcm8gY29lZmZpY2llbnRzLiBNb2RlbCBhc3Nlc3NtZW50IGlzIHBlcmZvcm1lZCBieSBjb21wdXRpbmcgcGVyZm9ybWFuY2UgY3JpdGVyaWEgb24gMTAwMCByYW5kb20gc3BsaXRzIG9mIHRoZSB0cmFpbmluZyBkYXRhIGludG8gdHJhaW5pbmcgc2V0ICg2MFwlKSBhbmQgdmFsaWRhdGlvbiBzZXQgKDQwXCUpLgoKYGBge3J9CmVuLm1jciA9IGMoKQplbi5wcHYgPSBjKCkKZW4ucmVjID0gYygpCgpyZWdpc3RlckRvTUMoY29yZXMgPSA4KQpzeXN0ZW0udGltZSh7CiAgcmVzIDwtIGZvcmVhY2goaSA9IDE6MTAwMCwgLmNvbWJpbmUgPSAncmJpbmQnLCAucGFja2FnZXMgPSBjKCJnbG1uZXQiKSkgJWRvcGFyJSB7CiAgICAKICAgICMgc3BsaXQgZGF0YSBpbnRvIDYwLTQwCiAgICBpbmR4ID0gdW5uYW1lKHVubGlzdChjYXJldDo6Y3JlYXRlRGF0YVBhcnRpdGlvbihZLCBwID0gMC42KSkpCiAgICB0ZW1wLnRyID0gWFtpbmR4LCBdICMlPiUgYXBwbHkoLiwgMiwgc2NhbGUpCiAgICB0ZW1wLnRlID0gWFstaW5keCwgXSAjJT4lIGFwcGx5KC4sIDIsIHNjYWxlKQogICAgCiAgICAjIGZpdCBtb2RlbCAoc3RhbmRhcmRpemVzIGRhdGEgaW50ZXJuYWxseSkKICAgIHRlbXAubGFzID0gY3YuZ2xtbmV0KHggPSBkYXRhLm1hdHJpeCh0ZW1wLnRyKSwgeSA9IFlbaW5keF0sIGZhbWlseSA9ICJiaW5vbWlhbCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgdHlwZS5tZWFzdXJlID0gImNsYXNzIiwgbmZvbGRzID0gNSwgYWxwaGEgPSAwLjUpCiAgICAKICAgICMgZ2V0IHByZWQKICAgIHRlbXAuY2xhc3MgPSBwcmVkaWN0KHRlbXAubGFzLCBkYXRhLm1hdHJpeCh0ZW1wLnRlKSwgcyA9ICJsYW1iZGEuMXNlIiwgdHlwZSA9ICJjbGFzcyIpCiAgICAKICAgICMgY29tcHV0ZSBtZXRyaWNzCiAgICB0ZW1wLmNvbmYgPSB0YWJsZShhY3R1YWwgPSBZWy1pbmR4XSwgcHJlZGljdGVkID0gdGVtcC5jbGFzcykKICAgIG1jciA9ICgxIC0gKHN1bShiYXNlOjpkaWFnKHRlbXAuY29uZikpIC8gc3VtKHRlbXAuY29uZikpKSAqIDEwMAogICAgcHB2ID0gKHRlbXAuY29uZlsyLDJdIC8gKHRlbXAuY29uZlsyLDJdICsgdGVtcC5jb25mWzEsMl0pKSAqIDEwMAogICAgcmVjID0gKHRlbXAuY29uZlsyLDJdIC8gKHRlbXAuY29uZlsyLDJdICsgdGVtcC5jb25mWzIsMV0pKSAqIDEwMAogICAgCiAgICBjKG1jciwgcHB2LCByZWMpCiAgfQp9KQoKIyByZW1vdmUgdGhlIHJvd25hbWVzCnJvd25hbWVzKHJlcykgPSBjKCkKCmVuLm1jciA9IHJlc1ssIDEsIGRyb3AgPSBUXQplbi5wcHYgPSByZXNbLCAyLCBkcm9wID0gVF0KZW4ucmVjID0gcmVzWywgMywgZHJvcCA9IFRdCgplbi5yZXMgPSBkYXRhLmZyYW1lKAogIHJiaW5kKHVubmFtZShzdW1tYXJ5KGVuLm1jcikpLCB1bm5hbWUoc3VtbWFyeShlbi5wcHYpKSwgdW5uYW1lKHN1bW1hcnkoZW4ucmVjKSkpLCByb3cubmFtZXMgPSBjKCJtaXNjbGFzcyIsICJwcmVjaXNpb24iLCAicmVjYWxsIikpCmNvbG5hbWVzKGVuLnJlcykgPSBuYW1lcyhzdW1tYXJ5KGVuLnJlYykpCiNlbi5yZXMgJT4lIHh0YWJsZTo6eHRhYmxlKGRpZ2l0cyA9IDIsIGFsaWduID0gcmVwKCJjIiwgbmNvbChlbi5yZXMpICsgMSksIGNhcHRpb24gPSAiMTAwMCByZXBlYXRzIikKYGBgCgpUcmFpbmluZyB0aW1lIHdhcyBhYm91dCA3MDAvNjAgJFxhcHByb3gkIGByIHJvdW5kKDcwMC82MClgIG1pbnV0ZXMuCgpgYGB7cn0KZW4ucmVzCmBgYAoKVGhlIHRhYmxlIGFib3ZlIHNob3dzIHRoZSBzdW1tYXJ5IG9mIHRoZSBtaXNjbGFzc2lmaWNhdGlvbiByYXRlLCBwcmVjaXNpb24sIGFuZCByZWNhbGwgYWNyb3NzIHRoZSAxMDAwIHJhbmRvbSBzcGxpdHMuICBUaGUgbWVkaWFuIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUgaXMgNy41JSwgcHJlY2lzaW9uOiA5NSUgYW5kIHJlY2FsbDogOTAlLiBJZGVhbGx5LCB0aGUgcmVjYWxsIHNob3VsZCBiZSAxMDAlIHNpbmNlIGl0IHdvdWxkIGJlIGNhdGFzdHJvcGhpYyBpZiB0aGUgY2xhc3NpZmllciBhc3NpZ25lZCB0aGUgJ25vbi10dW1vcicgbGFiZWwgdG8gc29tZW9uZSB3aXRoIGNhbmNlci4KCmBgYHtyfQojIHByZWRpY3QgdXNpbmcgZnVsbCBkYXRhCmxhc3NvX2ZpdCA9IGN2LmdsbW5ldCh4ID0gZGF0YS5tYXRyaXgoWCksIHkgPSBZLCBmYW1pbHkgPSAiYmlub21pYWwiLCB0eXBlLm1lYXN1cmUgPSAiY2xhc3MiLCBuZm9sZHMgPSA1LCBhbHBoYSA9IDAuNCkKCnBsb3QobGFzc29fZml0KQojdGl0bGUoIkVsYXN0aWMgTmV0IChhbHBoYSA9IDAuNSkiLCBsaW5lID0gMi41KQpgYGAKCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQphID0gY29lZihsYXNzb19maXQsIHMgPSAibGFtYmRhLjFzZSIpCmBgYAoKU2VsZWN0aW5nICRcbGFtYmRhJCBiYXNlZCBvbiB0aGUgIjEtc3RhbmRhcmQgZXJyb3IiIHJ1bGUgcmVzdWx0cyBpbiBhIGxhbWJkYSB2YWx1ZSBvZiBgciByb3VuZChsYXNzb19maXQkbGFtYmRhLjFzZSwgMilgIGFuZCB0aGF0IGFib3V0IGByIGxlbmd0aChhW3doaWNoKGEgIT0gMCldKSAtIDFgIGdlbmVzIChvdXQgb2YgMTI2MDApIGhhdmUgYSBub24temVybyBjb2VmZmljaWVudCBhdCB0aGlzIGxhbWJkYSB2YWx1ZSwgYXMgc2VlbiBmcm9tIHRoZSBmaWd1cmUgYWJvdmUuCgojIFRlc3QgU2V0IFBlcmZvcm1hbmNlCgpXZSBjYW4gYXNzZXNzIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgdGhyZWUgY2xhc3NpZmllcnMgb24gdGhlIHRlc3Qgc2V0IHRvIGdldCBhIG1lYXN1cmUgb2YgdGhlIGdlbmVyYWxpemF0aW9uIGVycm9yIG9mIHRoZSBjbGFzc2lmaWVyLiBUaGUgY29uZnVzaW9uIG1hdHJpY2VzIGZvciB0aGUgUExTLUxEQSwgcmFuZG9tIGZvcmVzdCwgYW5kIGxhc3NvIGFyZSBnaXZlbiBiZWxvdy4KCmBgYHtyfQojIyMgUExTLUxEQQojIGZpdCB0aGUgUExTIG1vZGVsIHRvIHRoZSBmdWxsIHRyYWluaW5nIGRhdGEgd2l0aCBuY29tcCBzZWxlY3RlZCBmcm9tIHRoZSBwcmV2aW91cyBwbG90cwojIG9idGFpbiBjb25mdXNpb24gbWF0cml4IGFuZCBtZXRyaWNzIGZyb20gdGhlIGZpbmFsIG1vZGVsIG9uIHRoZSB0ZXN0IHNldAp0ZXN0WSA9IHRlc3RbLCBjKCJyZXNwb25zZSIpXQpYdCA9IHRlc3RbLCAtbmNvbCh0ZXN0KV0KCiMgUExTCmZpdC5wbHMubGRhID0gcGxzLmxkYShYdHJhaW4gPSBYX3N0ZCwgWXRyYWluID0gYXMubnVtZXJpYyhZKSwgbmNvbXAgPSAxMywgWHRlc3QgPSBYdCkKdGVzdC5jbGFzcyA9IGlmZWxzZShmaXQucGxzLmxkYSRwcmVkY2xhc3MgPT0gIjEiLCAiTm9ybWFsIiwgIlR1bW9yIikKdGVzdC5wbHMuY29uZiA9IHRhYmxlKGFjdHVhbCA9IHRlc3RZLCBwcmVkaWN0ZWQgPSB0ZXN0LmNsYXNzKQpwbHNfdGVzdCA9IGNiaW5kKAogICgxIC0gKHN1bShiYXNlOjpkaWFnKHRlc3QucGxzLmNvbmYpKSAvIHN1bSh0ZXN0LnBscy5jb25mKSkpICogMTAwLCAjIG1jcgogICh0ZXN0LnBscy5jb25mWzIsMl0gLyAodGVzdC5wbHMuY29uZlsyLDJdICsgdGVzdC5wbHMuY29uZlsxLDJdKSkgKiAxMDAsICMgcHB2CiAgKHRlc3QucGxzLmNvbmZbMiwyXSAvICh0ZXN0LnBscy5jb25mWzIsMl0gKyB0ZXN0LnBscy5jb25mWzIsMV0pKSAqIDEwMCAjIHJlYwopCgp0ZXN0LnBscy5jb25mCgojIyMgUmFuZG9tIEZvcmVzdAojIGdldCByZiBwcmVkaWN0aW9uCnJmLnRlc3QgPSBwcmVkaWN0KHJmX2ZpdCwgZGF0YS5mcmFtZShhcHBseShYdCwgMiwgc2NhbGUpKSwgInJlc3BvbnNlIikKIyBjb21wdXRlIG1ldHJpY3MKcmYuY29uZi50ZXN0ID0gdGFibGUoYWN0dWFsID0gdGVzdFksIHByZWRpY3RlZCA9IHJmLnRlc3QpCnJmX3Rlc3QgPSBjYmluZCgKICAoMSAtIChiYXNlOjpzdW0oYmFzZTo6ZGlhZyhyZi5jb25mLnRlc3QpKSAvIHN1bShyZi5jb25mLnRlc3QpKSkgKiAxMDAsICNtY3IKICAocmYuY29uZi50ZXN0WzIsMl0gLyAocmYuY29uZi50ZXN0WzIsMl0gKyByZi5jb25mLnRlc3RbMSwyXSkpICogMTAwLCAjIHBwdi9wcmVjaXNpb24KICAocmYuY29uZi50ZXN0WzIsMl0gLyAocmYuY29uZi50ZXN0WzIsMl0gKyByZi5jb25mLnRlc3RbMiwxXSkpICogMTAwICMgcmVjYWxsCikKCnJmLmNvbmYudGVzdAoKIyMjIExhc3NvCmxhc3NvLnRlc3QgPSBwcmVkaWN0KGxhc3NvX2ZpdCwgZGF0YS5tYXRyaXgoWHQpLCBzID0gImxhbWJkYS4xc2UiLCB0eXBlID0gImNsYXNzIikKIyBjb21wdXRlIG1ldHJpY3MKbGFzc28uY29uZiA9IHRhYmxlKGFjdHVhbCA9IHRlc3RZLCBwcmVkaWN0ZWQgPSBsYXNzby50ZXN0KQpsYXNzb190ZXN0ID0gY2JpbmQoCiAgKDEgLSAoc3VtKGJhc2U6OmRpYWcobGFzc28uY29uZikpIC8gc3VtKGxhc3NvLmNvbmYpKSkgKiAxMDAsICNtY3IKICAobGFzc28uY29uZlsyLDJdIC8gKGxhc3NvLmNvbmZbMiwyXSArIGxhc3NvLmNvbmZbMSwyXSkpICogMTAwLCAjIHBwdi9wcmVjaXNpb24KICAobGFzc28uY29uZlsyLDJdIC8gKGxhc3NvLmNvbmZbMiwyXSArIGxhc3NvLmNvbmZbMiwxXSkpICogMTAwICMgcmVjYWxsCikKbGFzc28uY29uZgpgYGAKClRoZSBmb2xsb3dpbmcgdGFibGUgY29udHJhc3RzIHRoZSB2YXJpb3VzIG1ldHJpY3MgZm9yIHRoZSB0aHJlZSBtb2RlbHMgb24gdGhlIHRlc3QgZGF0YS4KCmBgYHtyfQp0ZXN0X2RmID0gZGF0YS5mcmFtZShjYmluZCh0KHBsc190ZXN0KSwgdChyZl90ZXN0KSwgdChsYXNzb190ZXN0KSksIHJvdy5uYW1lcyA9IGMoIk1pc2NsYXNzaWZpY2F0aW9uIHJhdGUiLCAiUHJlY2lzaW9uIiwgIlJlY2FsbCIpKQpjb2xuYW1lcyh0ZXN0X2RmKSA9IGMoIlBMUy1MREEiLCAiUkYiLCAiTGFzc28iKQpyb3VuZCh0ZXN0X2RmLCAyKQpgYGAKClRoZXJlIHdhcyBhbHNvIHN1YnN0YW50aWFsIHZhcmlhYmlsaXR5IG9ic2VydmVkIGluIHRoZSB0ZXN0IHNldCBwZXJmb3JtYW5jZSBvZiB0aGUgbGFzc28gbW9kZWwgYWNyb3NzIGRpZmZlcmVudCByZXJ1bnMgb2YgdGhlIGNvbW1hbmQgZm9yIHRyYWluaW5nIHRoZSBmaW5hbCBsYXNzbyBtb2RlbCB1c2luZyBhbGwgdGhlIHRyYWluaW5nIGRhdGEuCgojIENvbW1lbnRzCgpJbiB0aGUgbmV4dCBwYXJ0LCBJIHdpbGwgbW9kaWZ5IHRoZSBsb3NzIGZ1bmN0aW9uIHRvIHBlbmFsaXplIHRoZSBtaXNjbGFzc2lmaWNhdGlvbiBmcm9tICd0dW1vdXInIHRvICdub3JtYWwnIG1vcmUgZ3JlYXRseSB0aGFuIHRoZSBtaXNjbGFzc2ZpY2F0aW9uIGluIHRoZSBvdGhlciBkaXJlY3Rpb24uIAoKRnVydGhlcm1vcmUsIGluIHRoZSBQTFMtTERBIGNhc2UsIGNyb3NzLXZhbGlkYXRpb24gd2FzIHVzZWQgZm9yIG1vZGVsIHNlbGVjdGlvbiB3aGljaCBoYWQgdGhlIHByb2JsZW0gb2YgcmVkdWNlZCBzYW1wbGUgc2l6ZSBmb3IgdGhlIG1vZGVsIGJ1aWxkaW5nLiBPbmx5IDYwIG91dCBvZiAxMDAgZGF0YSBwb2ludHMgd2VyZSB1c2VkIGF0IGVhY2ggc3RlcCB0byB0cmFpbiB0aGUgbW9kZWwsIGFuZCBpbiB0aGlzIGNhc2Ugd2hlcmUgdGhlIHNhbXBsZSBzaXplIGlzIHZlcnkgc21hbGwsIGl0IG1heSBiZSBtb3JlIHVzZWZ1bCB0byB1c2UgdGhlIGJvb3RzdHJhcCBhcyB0aGUgcmVzYW1wbGluZyBtZXRob2QgZm9yIG1vZGVsIHNlbGVjdGlvbiwgd2hlcmUgdGhlIG91dC1vZi1iYWcgKE9PQikgZXJyb3IgY2FuIGJlIHVzZWQgdG8gb2J0YWluIGFuIGVzdGltYXRlIG9mIHRoZSBleHBlY3RlZCBwcmVkaWNpdGlvbiBlcnJvci4KCiMgUmVmZXJlbmNlcwoKU2luZ2gsIEQuLCBGZWJibywgUC4gRy4sIFJvc3MsIEsuLCBKYWNrc29uLCBELiBHLiwgTWFub2xhLCBKLiwgTGFkZCwgQy4sIC4uLiAmIExhbmRlciwgRS4gUy4gKDIwMDIpLiBHZW5lIGV4cHJlc3Npb24gY29ycmVsYXRlcyBvZiBjbGluaWNhbCBwcm9zdGF0ZSBjYW5jZXIgYmVoYXZpb3IuIENhbmNlciBjZWxsLCAxKDIpLCAyMDMtMjA5LiBbKERhdGFzZXQgTGluayldKGh0dHA6Ly9kYXRhbS5pMnIuYS1zdGFyLmVkdS5zZy9kYXRhc2V0cy9rcmJkL1Byb3N0YXRlQ2FuY2VyL1Byb3N0YXRlQ2FuY2VyLmh0bWwpCgpbTmd1eWVuLCBELiBWLiwgJiBSb2NrZSwgRC4gTS4gKDIwMDIpLiBUdW1vciBjbGFzc2lmaWNhdGlvbiBieSBwYXJ0aWFsIGxlYXN0IHNxdWFyZXMgdXNpbmcgbWljcm9hcnJheSBnZW5lIGV4cHJlc3Npb24gZGF0YS4gQmlvaW5mb3JtYXRpY3MsIDE4KDEpLCAzOS01MC5dKGh0dHBzOi8vYWNhZGVtaWMub3VwLmNvbS9iaW9pbmZvcm1hdGljcy9hcnRpY2xlLzE4LzEvMzkvMjQzNjE1L1R1bW9yLWNsYXNzaWZpY2F0aW9uLWJ5LXBhcnRpYWwtbGVhc3Qtc3F1YXJlcykKCkhhc3RpZSwgVC4sIFRpYnNoaXJhbmksIFIuLCAmIEZyaWVkbWFuLCBKLiAoMjAwOSkuIFRoZSBlbGVtZW50cyBvZiBzdGF0aXN0aWNhbCBsZWFybmluZy4gU3ByaW5nZXIgTmV3IFlvcmsuCg==
Comments
In the next part, I will modify the loss function to penalize the misclassification from ‘tumour’ to ‘normal’ more greatly than the misclassfication in the other direction.
Furthermore, in the PLS-LDA case, cross-validation was used for model selection which had the problem of reduced sample size for the model building. Only 60 out of 100 data points were used at each step to train the model, and in this case where the sample size is very small, it may be more useful to use the bootstrap as the resampling method for model selection, where the out-of-bag (OOB) error can be used to obtain an estimate of the expected predicition error.