Data
Load Data
The Prostate data can be found [here]. The
data is to examine the correlation between the level of
prostate-specific antigen and a number of clinical measures in men who
were about to receive a radical prostatectomy.
lcavol
: log cancer volume
lweight
: log prostate weight
age
: in years
lbph
: log of the amount of benign prostatic
hyperplasia
svi
: seminal vesicle invasion
lcp
: log of capsular penetration
gleason
: a numeric vector
pgg45
: percent of Gleason score 4 or 5
lpsa
: response
The first 8 columns are predictors; column 9 is the outcome/response.
We do not use column 10, which indicates which 67 observations were used
as the “training set” and which 30 as the test set, as described on page
48 in the ESL book. For convenience, we rename the response column with
a generic name Y.
myData = read.table(file = "https://hastie.su.domains/ElemStatLearn/datasets/prostate.data", header = TRUE)
myData = myData[, -10]
names(myData)[9] = 'Y'
names(myData)
[1] "lcavol" "lweight" "age" "lbph" "svi" "lcp" "gleason" "pgg45" "Y"
n = dim(myData)[1] # sample size
p = dim(myData)[2] - 1 # number of non-intercept predictors
X = as.matrix(myData[, names(myData) != "Y"]); # some algorithms need the matrix/vector
Y = myData$Y;
Train/Test Split
Split the data into two parts: 80% for training and 20% for testing.
Throughout, we will use the the training data to select variables and
estimate coefficients and report the average MSE on the test data.
ntest = round(n*0.2)
ntrain = n - ntest;
test.id = sample(1:n, ntest);
Ytest = Y[test.id];
Full Model
Fit an ordinary linear regression model using all variables.
full.model = lm( Y ~ ., data = myData[-test.id, ]);
Ytest.pred = predict(full.model, newdata= myData[test.id, ]);
sum((Ytest - Ytest.pred)^2)/ntest # averaged MSE on the test set
[1] 0.2199186
Ridge
Fit a set of ridge regression models using glmnet
with
the default \(\lambda\) sequence.
myridge = glmnet(X[-test.id, ], Y[-test.id], alpha = 0)
plot(myridge, label = TRUE, xvar = "lambda")
Output
Check the output from glmnet
for ridge regression.
summary(myridge)
Retrieve the lambda values
length(myridge$lambda)
Retrieve coefficients using two different approaches.
dim(myridge$beta) # coefficients for non-intercept predictors
length(myridge$a0) # intercept
# Retrieve coefficients (including intercept) using
# coef(myridge)
dim(coef(myridge))
The two coefficient matrices should be the same
sum((coef(myridge) - rbind(myridge$a0, myridge$beta))^2)
[1] 0
Note: Ridge regression coefficients may change signs
along the path.
round(myridge$beta['age', ], dig = 4)
How are the intercepts computed?
k = 2;
my.mean = apply(X[-test.id, ], 2, mean) # 13x1 mean vector for training X
mean(Y[-test.id]) - sum(my.mean * myridge$beta[, k])
myridge$a0[k] # intercept for lambda = myridge$lambda[k]
Check whether our intercept formula is true for all intercepts
sum((mean(Y[-test.id]) - my.mean %*% myridge$beta - myridge$a0)^2)
CV & Path Plot
The CV results are stored in
cv.out$cvm
: mean CV errors
cv.out$cvsd
: standard errors of cvm
cv.out = cv.glmnet(X[-test.id, ], Y[-test.id], alpha = 0)
plot(cv.out)
The CV performance continues to drop when \(\lambda\) gets smaller. So, we provide R
with a \(\lambda\) sequence dense in
[-6, 2].
lam.seq = exp(seq(-6, 2, length=100))
cv.out = cv.glmnet(X[-test.id,], Y[-test.id], alpha=0, lambda=lam.seq)
plot(cv.out)
Two choices for lambda
lambda.min
: the value of lambda that achieves the
minimum cvm (the left dashed vertical line)
lambda.1se
: the largest value of lambda (i.e., the
largest regularization, the smallest df) whose cvm is within
one-standard-error of the cvm of lambda.min. (the left dashed vertical
line)
Note: by definition lambda.1se
(right
dashed vertical line) is always bigger than or equal to
lambda.min
(left dashed vertical line).
Check how lambda.min
and lambda.1se
are
computed.
names(cv.out)
[1] "lambda" "cvm" "cvsd" "cvup" "cvlo" "nzero"
[7] "call" "name" "glmnet.fit" "lambda.min" "lambda.1se" "index"
# Find lambda.min
cv.out$lambda.min
[1] 0.1656332
cv.out$lambda[which.min(cv.out$cvm)]
[1] 0.1656332
# Find lambda.1se
cv.out$lambda.1se
[1] 1.353955
tmp.id = which.min(cv.out$cvm)
max(cv.out$lambda[cv.out$cvm < cv.out$cvm[tmp.id] + cv.out$cvsd[tmp.id]])
[1] 1.353955
Prediction
Next we apply the two ridge regression models—one with
lambda.min
and one with lambda.1se
—on a new
test data set and report the corresponding mean squared prediction
errors.
myridge = glmnet(X[-test.id,], Y[-test.id], alpha=0, lambda = lam.seq)
Ytest.pred = predict(myridge, s = cv.out$lambda.1se, newx=X[test.id,])
mean((Ytest.pred - Y[test.id])^2)
[1] 0.3779362
Ytest.pred=predict(myridge, s = cv.out$lambda.min, newx=X[test.id,])
mean((Ytest.pred - Y[test.id])^2)
[1] 0.235441
Lasso
Output
Check the output from glmnet
for Lasso.
mylasso = glmnet(X[-test.id,], Y[-test.id], alpha = 1)
summary(mylasso)
CV & Path Plot
par(mfrow = c(1, 2))
plot(mylasso, label=TRUE, xvar = "norm")
plot(mylasso, label=TRUE, xvar = "lambda")
par(mfrow=c(1,1))
mylasso$df
[1] 0 1 1 1 1 1 1 1 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 6 6 6 6 6 6 7 7 7 7 7 7 8
[45] 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
cv.out = cv.glmnet(X[-test.id, ], Y[-test.id], alpha = 1)
plot(cv.out)
Try our own lambda sequences.
# lam.seq = exp(seq(-4, 2, length=100))
# lam.seq = exp(seq(-6, -1, length=100))
# cv.out = cv.glmnet(X[-test.id,], Y[-test.id], alpha = 1, lambda = l am.seq)
# plot(cv.out)
Check how lambda.min
and lambda.1se
are
computed.
cv.out$lambda.min
tmp.id=which.min(cv.out$cvm)
cv.out$lambda[tmp.id]
cv.out$lambda.1se
max(cv.out$lambda[cv.out$cvm < cv.out$cvm[tmp.id] + cv.out$cvsd[tmp.id]])
Coefficients
How to retrieve Lasso coefficients?
mylasso.coef.min = predict(mylasso, s=cv.out$lambda.min, type="coefficients")
mylasso.coef.1se = predict(mylasso, s=cv.out$lambda.1se, type="coefficients")
cbind(mylasso.coef.min, mylasso.coef.1se)
9 x 2 sparse Matrix of class "dgCMatrix"
s1 s1
(Intercept) 0.22065356 1.4530552
lcavol 0.51079974 0.4347692
lweight 0.39003496 0.1021676
age . .
lbph 0.05266784 .
svi 0.56214447 0.2256772
lcp . .
gleason . .
pgg45 . .
# number of variables selected (including the intercept)
sum(mylasso.coef.1se != 0)
[1] 4
# names of selected non-intercept variables
row.names(mylasso.coef.1se)[which(mylasso.coef.1se != 0)[-1]]
[1] "lcavol" "lweight" "svi"
Prediction
Apply the two fitted models for prediction on test data.
mylasso = glmnet(X[-test.id, ], Y[-test.id], alpha = 1)
Ytest.pred = predict(mylasso, s = cv.out$lambda.min, newx = X[test.id,])
mean((Ytest.pred - Y[test.id])^2)
[1] 0.2833383
Ytest.pred = predict(mylasso, s = cv.out$lambda.1se, newx = X[test.id,])
mean((Ytest.pred - Y[test.id])^2)
[1] 0.4411785
Refit to Reduce Bias
We refit an ordinary linear regression model using the variables
selected by lambda.1se
and then use it for prediction.
mylasso.coef.1se = predict(mylasso, s = cv.out$lambda.1se, type="coefficients")
var.sel = row.names(mylasso.coef.1se)[which(mylasso.coef.1se != 0)[-1]]
var.sel;
[1] "lcavol" "lweight" "svi"
tmp.X = X[, colnames(X) %in% var.sel]
mylasso.refit = coef(lm(Y[-test.id] ~ tmp.X[-test.id, ]))
Ytest.pred = mylasso.refit[1] + tmp.X[test.id,] %*% mylasso.refit[-1]
mean((Ytest.pred - Y[test.id])^2)
[1] 0.2601463
LS0tCnRpdGxlOiAiU3RhdDU0MjogUmlkZ2UgYW5kIExhc3NvIgpkYXRlOiAiRmFsbCAyMDIyIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiByZWFkYWJsZQogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKLS0tCgoKYGdsbW5ldGA6IGxpYnJhcnkgZm9yIGJvdGggbGFzc28gKGFscGhhPTEpIGFuZCByaWRnZSAoYWxwaGE9MCkuIENoZWNrIG1vcmUgZ2xtbmV0IGV4YW1wbGVzIGF0IApodHRwczovL3dlYi5zdGFuZm9yZC5lZHUvfmhhc3RpZS9nbG1uZXQvZ2xtbmV0X2JldGEuaHRtbAoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygiZ2xtbmV0IikKbGlicmFyeShnbG1uZXQpCmBgYAoKIyMgRGF0YQoKIyMjIExvYWQgRGF0YQoKVGhlICoqUHJvc3RhdGUgZGF0YSoqIGNhbiBiZSBmb3VuZCBbW2hlcmVdKGh0dHBzOi8vaGFzdGllLnN1LmRvbWFpbnMvRWxlbVN0YXRMZWFybi9kYXRhLmh0bWwpXS4gVGhlIGRhdGEgaXMgdG8gZXhhbWluZSB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgbGV2ZWwgb2YgcHJvc3RhdGUtc3BlY2lmaWMgYW50aWdlbiBhbmQgYSBudW1iZXIgb2YgY2xpbmljYWwgbWVhc3VyZXMgaW4gbWVuIHdobyB3ZXJlIGFib3V0IHRvIHJlY2VpdmUgYSByYWRpY2FsIHByb3N0YXRlY3RvbXkuCgotIGBsY2F2b2xgOiAgbG9nIGNhbmNlciB2b2x1bWUKLSBgbHdlaWdodGA6IGxvZyBwcm9zdGF0ZSB3ZWlnaHQKLSBgYWdlYDogaW4geWVhcnMKLSBgbGJwaGA6IGxvZyBvZiB0aGUgYW1vdW50IG9mIGJlbmlnbiBwcm9zdGF0aWMgaHlwZXJwbGFzaWEKLSBgc3ZpYDogc2VtaW5hbCB2ZXNpY2xlIGludmFzaW9uCi0gYGxjcGA6IGxvZyBvZiBjYXBzdWxhciBwZW5ldHJhdGlvbgotIGBnbGVhc29uYDogYSBudW1lcmljIHZlY3RvcgotIGBwZ2c0NWA6IHBlcmNlbnQgb2YgR2xlYXNvbiBzY29yZSA0IG9yIDUKLSBgbHBzYWA6IHJlc3BvbnNlCgpUaGUgZmlyc3QgOCBjb2x1bW5zIGFyZSBwcmVkaWN0b3JzOyBjb2x1bW4gOSBpcyB0aGUgb3V0Y29tZS9yZXNwb25zZS4gV2UgZG8gbm90IHVzZSBjb2x1bW4gMTAsIHdoaWNoIGluZGljYXRlcyB3aGljaCA2NyBvYnNlcnZhdGlvbnMgd2VyZSB1c2VkIGFzIHRoZSAidHJhaW5pbmcgc2V0IiBhbmQgd2hpY2ggMzAgYXMgdGhlIHRlc3Qgc2V0LCBhcyBkZXNjcmliZWQgb24gcGFnZSA0OCBpbiB0aGUgRVNMIGJvb2suIEZvciBjb252ZW5pZW5jZSwgd2UgcmVuYW1lIHRoZSByZXNwb25zZSBjb2x1bW4gd2l0aCBhIGdlbmVyaWMgbmFtZSBZLgoKYGBge3J9Cm15RGF0YSA9IHJlYWQudGFibGUoZmlsZSA9ICJodHRwczovL2hhc3RpZS5zdS5kb21haW5zL0VsZW1TdGF0TGVhcm4vZGF0YXNldHMvcHJvc3RhdGUuZGF0YSIsIGhlYWRlciA9IFRSVUUpCm15RGF0YSA9IG15RGF0YVssIC0xMF0KbmFtZXMobXlEYXRhKVs5XSA9ICdZJwpuYW1lcyhteURhdGEpCmBgYAoKYGBge3J9Cm4gPSBkaW0obXlEYXRhKVsxXSAgICAgICAgICMgc2FtcGxlIHNpemUKcCA9IGRpbShteURhdGEpWzJdIC0gMSAgICAgIyBudW1iZXIgb2Ygbm9uLWludGVyY2VwdCBwcmVkaWN0b3JzClggPSBhcy5tYXRyaXgobXlEYXRhWywgbmFtZXMobXlEYXRhKSAhPSAiWSJdKTsgICMgc29tZSBhbGdvcml0aG1zIG5lZWQgdGhlIG1hdHJpeC92ZWN0b3IgClkgPSBteURhdGEkWTsgICAgICAgCmBgYAoKIyMjIFRyYWluL1Rlc3QgU3BsaXQKClNwbGl0IHRoZSBkYXRhIGludG8gdHdvIHBhcnRzOiA4MCUgZm9yIHRyYWluaW5nIGFuZCAyMCUgZm9yIHRlc3RpbmcuIFRocm91Z2hvdXQsIHdlIHdpbGwgdXNlIHRoZSB0aGUgdHJhaW5pbmcgZGF0YSB0byBzZWxlY3QgdmFyaWFibGVzIGFuZCBlc3RpbWF0ZSBjb2VmZmljaWVudHMgYW5kIHJlcG9ydCB0aGUgYXZlcmFnZSBNU0Ugb24gdGhlIHRlc3QgZGF0YS4KCmBgYHtyfQpudGVzdCA9IHJvdW5kKG4qMC4yKQpudHJhaW4gPSBuIC0gbnRlc3Q7CnRlc3QuaWQgPSBzYW1wbGUoMTpuLCBudGVzdCk7Cll0ZXN0ID0gWVt0ZXN0LmlkXTsKYGBgCgojIyBGdWxsIE1vZGVsCgpGaXQgYW4gb3JkaW5hcnkgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdXNpbmcgYWxsIHZhcmlhYmxlcy4KCmBgYHtyfQpmdWxsLm1vZGVsID0gbG0oIFkgfiAuLCBkYXRhID0gbXlEYXRhWy10ZXN0LmlkLCBdKTsgIApZdGVzdC5wcmVkID0gcHJlZGljdChmdWxsLm1vZGVsLCBuZXdkYXRhPSBteURhdGFbdGVzdC5pZCwgXSk7CnN1bSgoWXRlc3QgLSBZdGVzdC5wcmVkKV4yKS9udGVzdCAjIGF2ZXJhZ2VkIE1TRSBvbiB0aGUgdGVzdCBzZXQKYGBgCgojIyBSaWRnZSAKCkZpdCBhIHNldCBvZiByaWRnZSByZWdyZXNzaW9uIG1vZGVscyB1c2luZyBgZ2xtbmV0YCB3aXRoIHRoZSBkZWZhdWx0ICRcbGFtYmRhJCBzZXF1ZW5jZS4gCgpgYGB7cn0KbXlyaWRnZSA9IGdsbW5ldChYWy10ZXN0LmlkLCBdLCBZWy10ZXN0LmlkXSwgYWxwaGEgPSAwKQpwbG90KG15cmlkZ2UsIGxhYmVsID0gVFJVRSwgeHZhciA9ICJsYW1iZGEiKQpgYGAKCiMjIyBPdXRwdXQKQ2hlY2sgdGhlIG91dHB1dCBmcm9tIGBnbG1uZXRgIGZvciByaWRnZSByZWdyZXNzaW9uLgoKYGBge3J9CnN1bW1hcnkobXlyaWRnZSkKYGBgCgpSZXRyaWV2ZSB0aGUgbGFtYmRhIHZhbHVlcwpgYGB7cn0KbGVuZ3RoKG15cmlkZ2UkbGFtYmRhKQpgYGAKClJldHJpZXZlIGNvZWZmaWNpZW50cyB1c2luZyB0d28gZGlmZmVyZW50IGFwcHJvYWNoZXMuIApgYGB7cn0KZGltKG15cmlkZ2UkYmV0YSkgICAgICAgIyBjb2VmZmljaWVudHMgZm9yICBub24taW50ZXJjZXB0IHByZWRpY3RvcnMKbGVuZ3RoKG15cmlkZ2UkYTApICAgICAgIyBpbnRlcmNlcHQKCgojIFJldHJpZXZlIGNvZWZmaWNpZW50cyAoaW5jbHVkaW5nIGludGVyY2VwdCkgdXNpbmcKIyBjb2VmKG15cmlkZ2UpCmRpbShjb2VmKG15cmlkZ2UpKQpgYGAKClRoZSB0d28gY29lZmZpY2llbnQgbWF0cmljZXMgc2hvdWxkIGJlIHRoZSBzYW1lCmBgYHtyfQpzdW0oKGNvZWYobXlyaWRnZSkgLSByYmluZChteXJpZGdlJGEwLCBteXJpZGdlJGJldGEpKV4yKQpgYGAKCioqTm90ZSoqOiBSaWRnZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyBtYXkgY2hhbmdlIHNpZ25zIGFsb25nIHRoZSBwYXRoLgoKYGBge3J9CnJvdW5kKG15cmlkZ2UkYmV0YVsnYWdlJywgXSwgZGlnID0gNCkKYGBgCgpIb3cgYXJlIHRoZSBpbnRlcmNlcHRzIGNvbXB1dGVkPwoKYGBge3J9CmsgPSAyOyAKbXkubWVhbiA9IGFwcGx5KFhbLXRlc3QuaWQsIF0sIDIsIG1lYW4pICAjIDEzeDEgbWVhbiB2ZWN0b3IgZm9yIHRyYWluaW5nIFgKbWVhbihZWy10ZXN0LmlkXSkgLSBzdW0obXkubWVhbiAqIG15cmlkZ2UkYmV0YVssIGtdKQpteXJpZGdlJGEwW2tdICAjIGludGVyY2VwdCBmb3IgbGFtYmRhID0gbXlyaWRnZSRsYW1iZGFba10KYGBgCgpDaGVjayB3aGV0aGVyIG91ciBpbnRlcmNlcHQgZm9ybXVsYSBpcyB0cnVlIGZvciBhbGwgaW50ZXJjZXB0cyAKYGBge3J9CnN1bSgobWVhbihZWy10ZXN0LmlkXSkgLSBteS5tZWFuICUqJSBteXJpZGdlJGJldGEgIC0gbXlyaWRnZSRhMCleMikKYGBgCgojIyMgQ1YgJiBQYXRoIFBsb3QKClRoZSBDViByZXN1bHRzIGFyZSBzdG9yZWQgaW4gCgotIGBjdi5vdXQkY3ZtYDogbWVhbiBDViBlcnJvcnMKLSBgY3Yub3V0JGN2c2RgOiBzdGFuZGFyZCBlcnJvcnMgb2YgY3ZtCgoKYGBge3J9CmN2Lm91dCA9IGN2LmdsbW5ldChYWy10ZXN0LmlkLCBdLCBZWy10ZXN0LmlkXSwgYWxwaGEgPSAwKSAKcGxvdChjdi5vdXQpCmBgYAoKVGhlIENWIHBlcmZvcm1hbmNlIGNvbnRpbnVlcyB0byBkcm9wIHdoZW4gJFxsYW1iZGEkIGdldHMgc21hbGxlci4gU28sIHdlIHByb3ZpZGUgUiB3aXRoIGEgICRcbGFtYmRhJCBzZXF1ZW5jZSBkZW5zZSBpbiBbLTYsIDJdLgoKYGBge3J9CmxhbS5zZXEgPSBleHAoc2VxKC02LCAyLCBsZW5ndGg9MTAwKSkKY3Yub3V0ID0gY3YuZ2xtbmV0KFhbLXRlc3QuaWQsXSwgWVstdGVzdC5pZF0sIGFscGhhPTAsIGxhbWJkYT1sYW0uc2VxKSAgCnBsb3QoY3Yub3V0KQpgYGAKClR3byBjaG9pY2VzIGZvciBsYW1iZGEKCi0gYGxhbWJkYS5taW5gOiB0aGUgdmFsdWUgb2YgbGFtYmRhIHRoYXQgYWNoaWV2ZXMgdGhlIG1pbmltdW0gY3ZtICh0aGUgbGVmdCBkYXNoZWQgdmVydGljYWwgbGluZSkKLSBgbGFtYmRhLjFzZWA6IHRoZSBsYXJnZXN0IHZhbHVlIG9mIGxhbWJkYSAoaS5lLiwgdGhlIGxhcmdlc3QgcmVndWxhcml6YXRpb24sIHRoZSBzbWFsbGVzdCBkZikgd2hvc2UgY3ZtIGlzIHdpdGhpbiBvbmUtc3RhbmRhcmQtZXJyb3Igb2YgdGhlIGN2bSBvZiBsYW1iZGEubWluLiAgKHRoZSBsZWZ0IGRhc2hlZCB2ZXJ0aWNhbCBsaW5lKQoKKipOb3RlKio6IGJ5IGRlZmluaXRpb24gYGxhbWJkYS4xc2VgIChyaWdodCBkYXNoZWQgdmVydGljYWwgbGluZSkgaXMgYWx3YXlzIGJpZ2dlciB0aGFuIG9yIGVxdWFsIHRvIGBsYW1iZGEubWluYCAobGVmdCBkYXNoZWQgdmVydGljYWwgbGluZSkuIAoKCkNoZWNrIGhvdyBgbGFtYmRhLm1pbmAgYW5kIGBsYW1iZGEuMXNlYCBhcmUgY29tcHV0ZWQuCgpgYGB7cn0KbmFtZXMoY3Yub3V0KQoKIyBGaW5kIGxhbWJkYS5taW4KY3Yub3V0JGxhbWJkYS5taW4KY3Yub3V0JGxhbWJkYVt3aGljaC5taW4oY3Yub3V0JGN2bSldCgojIEZpbmQgbGFtYmRhLjFzZQpjdi5vdXQkbGFtYmRhLjFzZQp0bXAuaWQgPSB3aGljaC5taW4oY3Yub3V0JGN2bSkKbWF4KGN2Lm91dCRsYW1iZGFbY3Yub3V0JGN2bSA8IGN2Lm91dCRjdm1bdG1wLmlkXSArIGN2Lm91dCRjdnNkW3RtcC5pZF1dKQpgYGAKCiMjIyBQcmVkaWN0aW9uCgpOZXh0IHdlIGFwcGx5IHRoZSB0d28gcmlkZ2UgcmVncmVzc2lvbiBtb2RlbHMtLS1vbmUgd2l0aCBgbGFtYmRhLm1pbmAgYW5kIG9uZSB3aXRoIGBsYW1iZGEuMXNlYC0tLW9uIGEgbmV3IHRlc3QgZGF0YSBzZXQgYW5kIHJlcG9ydCB0aGUgY29ycmVzcG9uZGluZyBtZWFuIHNxdWFyZWQgcHJlZGljdGlvbiBlcnJvcnMuIAoKYGBge3J9Cm15cmlkZ2UgPSBnbG1uZXQoWFstdGVzdC5pZCxdLCBZWy10ZXN0LmlkXSwgYWxwaGE9MCwgbGFtYmRhID0gbGFtLnNlcSkKWXRlc3QucHJlZCA9IHByZWRpY3QobXlyaWRnZSwgcyA9IGN2Lm91dCRsYW1iZGEuMXNlLCBuZXd4PVhbdGVzdC5pZCxdKQptZWFuKChZdGVzdC5wcmVkIC0gWVt0ZXN0LmlkXSleMikKWXRlc3QucHJlZD1wcmVkaWN0KG15cmlkZ2UsIHMgPSBjdi5vdXQkbGFtYmRhLm1pbiwgbmV3eD1YW3Rlc3QuaWQsXSkKbWVhbigoWXRlc3QucHJlZCAtIFlbdGVzdC5pZF0pXjIpCmBgYAoKCiMjIExhc3NvCgoKIyMjIE91dHB1dApDaGVjayB0aGUgb3V0cHV0IGZyb20gYGdsbW5ldGAgZm9yIExhc3NvLiAKCmBgYHtyfQpteWxhc3NvID0gZ2xtbmV0KFhbLXRlc3QuaWQsXSwgWVstdGVzdC5pZF0sIGFscGhhID0gMSkKc3VtbWFyeShteWxhc3NvKQpgYGAKCgojIyMgQ1YgJiBQYXRoIFBsb3QKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDEsIDIpKQpwbG90KG15bGFzc28sIGxhYmVsPVRSVUUsIHh2YXIgPSAibm9ybSIpCnBsb3QobXlsYXNzbywgbGFiZWw9VFJVRSwgeHZhciA9ICJsYW1iZGEiKQpwYXIobWZyb3c9YygxLDEpKQpgYGAKCmBgYHtyfQpteWxhc3NvJGRmCmN2Lm91dCA9IGN2LmdsbW5ldChYWy10ZXN0LmlkLCBdLCBZWy10ZXN0LmlkXSwgYWxwaGEgPSAxKQpwbG90KGN2Lm91dCkKYGBgCgpUcnkgb3VyIG93biBsYW1iZGEgc2VxdWVuY2VzLgpgYGB7cn0KIyBsYW0uc2VxID0gIGV4cChzZXEoLTQsIDIsIGxlbmd0aD0xMDApKQojIGxhbS5zZXEgPSAgZXhwKHNlcSgtNiwgLTEsIGxlbmd0aD0xMDApKQojIGN2Lm91dCA9IGN2LmdsbW5ldChYWy10ZXN0LmlkLF0sIFlbLXRlc3QuaWRdLCBhbHBoYSA9IDEsIGxhbWJkYSA9IGwgYW0uc2VxKQojIHBsb3QoY3Yub3V0KQpgYGAKCkNoZWNrIGhvdyBgbGFtYmRhLm1pbmAgYW5kIGBsYW1iZGEuMXNlYCBhcmUgY29tcHV0ZWQuCmBgYHtyfQpjdi5vdXQkbGFtYmRhLm1pbgp0bXAuaWQ9d2hpY2gubWluKGN2Lm91dCRjdm0pCmN2Lm91dCRsYW1iZGFbdG1wLmlkXQoKY3Yub3V0JGxhbWJkYS4xc2UKbWF4KGN2Lm91dCRsYW1iZGFbY3Yub3V0JGN2bSA8IGN2Lm91dCRjdm1bdG1wLmlkXSArIGN2Lm91dCRjdnNkW3RtcC5pZF1dKQpgYGAKCiMjIyBDb2VmZmljaWVudHMKCkhvdyB0byByZXRyaWV2ZSBMYXNzbyBjb2VmZmljaWVudHM/CgpgYGB7cn0KbXlsYXNzby5jb2VmLm1pbiA9IHByZWRpY3QobXlsYXNzbywgcz1jdi5vdXQkbGFtYmRhLm1pbiwgdHlwZT0iY29lZmZpY2llbnRzIikKbXlsYXNzby5jb2VmLjFzZSA9IHByZWRpY3QobXlsYXNzbywgcz1jdi5vdXQkbGFtYmRhLjFzZSwgdHlwZT0iY29lZmZpY2llbnRzIikKY2JpbmQobXlsYXNzby5jb2VmLm1pbiwgbXlsYXNzby5jb2VmLjFzZSkKCiMgbnVtYmVyIG9mIHZhcmlhYmxlcyBzZWxlY3RlZCAoaW5jbHVkaW5nIHRoZSBpbnRlcmNlcHQpCnN1bShteWxhc3NvLmNvZWYuMXNlICE9IDApCiMgbmFtZXMgb2Ygc2VsZWN0ZWQgbm9uLWludGVyY2VwdCB2YXJpYWJsZXMKcm93Lm5hbWVzKG15bGFzc28uY29lZi4xc2UpW3doaWNoKG15bGFzc28uY29lZi4xc2UgIT0gMClbLTFdXQpgYGAKCiMjIyBQcmVkaWN0aW9uCgpBcHBseSB0aGUgdHdvIGZpdHRlZCBtb2RlbHMgZm9yIHByZWRpY3Rpb24gb24gdGVzdCBkYXRhLgoKYGBge3J9Cm15bGFzc28gPSBnbG1uZXQoWFstdGVzdC5pZCwgXSwgWVstdGVzdC5pZF0sIGFscGhhID0gMSkKWXRlc3QucHJlZCA9IHByZWRpY3QobXlsYXNzbywgcyA9IGN2Lm91dCRsYW1iZGEubWluLCBuZXd4ID0gWFt0ZXN0LmlkLF0pCm1lYW4oKFl0ZXN0LnByZWQgLSBZW3Rlc3QuaWRdKV4yKQoKWXRlc3QucHJlZCA9IHByZWRpY3QobXlsYXNzbywgcyA9IGN2Lm91dCRsYW1iZGEuMXNlLCBuZXd4ID0gWFt0ZXN0LmlkLF0pCm1lYW4oKFl0ZXN0LnByZWQgLSBZW3Rlc3QuaWRdKV4yKQpgYGAKCiMjIyBSZWZpdCB0byBSZWR1Y2UgQmlhcwoKV2UgcmVmaXQgYW4gb3JkaW5hcnkgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdXNpbmcgdGhlIHZhcmlhYmxlcyBzZWxlY3RlZCBieSBgbGFtYmRhLjFzZWAgYW5kIHRoZW4gdXNlIGl0IGZvciBwcmVkaWN0aW9uLgoKYGBge3J9Cm15bGFzc28uY29lZi4xc2UgPSBwcmVkaWN0KG15bGFzc28sIHMgPSBjdi5vdXQkbGFtYmRhLjFzZSwgdHlwZT0iY29lZmZpY2llbnRzIikKdmFyLnNlbCA9IHJvdy5uYW1lcyhteWxhc3NvLmNvZWYuMXNlKVt3aGljaChteWxhc3NvLmNvZWYuMXNlICE9IDApWy0xXV0KdmFyLnNlbDsgCnRtcC5YID0gWFssIGNvbG5hbWVzKFgpICVpbiUgdmFyLnNlbF0KbXlsYXNzby5yZWZpdCA9IGNvZWYobG0oWVstdGVzdC5pZF0gfiB0bXAuWFstdGVzdC5pZCwgXSkpCll0ZXN0LnByZWQgPSBteWxhc3NvLnJlZml0WzFdICsgdG1wLlhbdGVzdC5pZCxdICUqJSBteWxhc3NvLnJlZml0Wy0xXQptZWFuKChZdGVzdC5wcmVkIC0gWVt0ZXN0LmlkXSleMikKYGBgCgoKCg==