Goals:
- Illustrate why naive implementations of model selection using Lasso
are problematic
Data generating process
We consider a DGP with correlated variables:
\(p\geq10\) covariates drawn
from a multivariate normal distribution: \(X
\sim N(0,\Sigma)\), where we set in \(\Sigma\) the diagonal entries (variance) to
one and the off-diagonal entries (covariance) to 0.5.
The outcome model is \(Y =
\underbrace{\beta_0 + \beta_1 X_1 + ... + \beta_{10} X_{10}}_{\text{CEF
}f(X)} + e\), where \(X_j\) is
the \(j\)-th column of \(X\) and \(e \sim
N(0,2)\)
We consider the following parameters \(\beta_0 = 0\), \(\beta_1 = 1\), \(\beta_2 = 0.9\), …, \(\beta_9 = 0.2\), \(\beta_{10} = 0.1\) such that the first 10
variables have a decreasing impact on the outcome and any further
variables are just irrelevant noise with no true predictive
power
We set \(p=99\) and work with sample
size \(N=100\).
# Load the packages required for later
library(tidyverse)
library(mvtnorm)
library(glmnet)
set.seed(1234) # For replicability
# Define the relevant parameters
n = 100
var = 1
cov = 0.5
p = 99
beta = c(0,seq(1,0.1,-0.1),rep(0,p-10))
sig = matrix(cov,p,p)
diag(sig) = var
A causal question and different advice
You are interested in the causal effect of variable \(X_1\) on outcome \(Y\). You know that you should control for
additional variables, but not really which specification to choose. You
organize a meeting with some colleagues and ask for advice. You receive
three different answers:
- Professor: Looks in a coffee cup and tells you that it is obvious
that you should control for \(X_2-X_{10}\) using OLS and leaves.
The remaining PhD students just finished a course in “Big Data,
Machine Learning and AI” and are very excited about variable selection
via Lasso because this is exactly what you are asking for:
PhD student 1: Tells you that you should just throw everything in
a Lasso regression and report the resulting coefficient of \(\beta_1\) as your causal effect.
PhD student 2: Is skeptical because the causal effect is shrunken
towards zero and proposes to leave the coefficient of \(\beta_1\) unpenalized and to only penalize
the other variables.
A simulation study to figure out what works
We run a simulation study that draws 1000 random samples based on our
DGP and stores the estimates resulting from the three proposed
procedures (runs several minutes).
repl = 1000
beta1_ols = beta1_lasso = beta1_lasso_unpen = rep(NA,repl)
for (i in 1:repl) {
x = cbind(rep(1,n),rmvnorm(n,sigma=sig))
y = x %*% beta + rnorm(n,0,2)
# OLS
beta1_ols[i] = lm(y~x[,2:11])$coefficients[2]
# Plain Lasso (specifying lambda.min.ratio speeds up)
cv_lasso = cv.glmnet(x[,-1],y,lambda.min.ratio=0.001)
lasso_temp = glmnet(x[,-1],y,lambda=cv_lasso$lambda.min)
beta1_lasso[i] = lasso_temp$beta[1]
# Lasso with unpenalized X_1
cv_lasso = cv.glmnet(x[,-1],y,penalty.factor=c(0,rep(1,p-1)),lambda.min.ratio=0.001)
lasso_temp = glmnet(x[,-1],y,lambda=cv_lasso$lambda.min,penalty.factor=c(0,rep(1,p-1)))
beta1_lasso_unpen[i] = lasso_temp$beta[1]
}
Let’s check the resulting estimator distributions of the proposed
strategies:
Professor OLS
Does it work?
Yes! The distribution of the estimator has a mean (dashed line) close
to the true value of one (solid line) \(\Rightarrow\) unbiased:
df = data.frame(x=beta1_ols)
ggplot(df,aes(x=x)) + geom_histogram(bins=30,aes(y =..density..)) +
geom_vline(xintercept = c(beta[2],mean(df$x)),linetype=c('solid','dashed'))
Why? (i) The model was chosen without looking at the data \(\Rightarrow\) no problems with
post-selection issues. (ii) The correct model was chosen (however the
Prof. managed to do this). (iii) OLS is used, which is the best unbiased
estimator for such a setting.
Student 1’s plain Lasso
Does it work?
No! The estimator is clearly downward biased.
df = data.frame(x=beta1_lasso)
ggplot(df,aes(x=x)) + geom_histogram(bins=30,aes(y =..density..)) +
geom_vline(xintercept = c(beta[2],mean(df$x)),linetype=c('solid','dashed'))
Why? Student 2 is right. The penalization shrinks the parameter of
interest towards zero. In several replications the variable of interest
is not even selected, which explains the spike at zero:
paste("Share of replications where variable of interest not selected:",round( sum(beta1_lasso == 0)) / repl *100,"%")
[1] "Share of replications where variable of interest not selected: 2.8 %"
Student 2’s Lasso with unpenalized parameter of interest
Does it work?
No! The estimator is upward biased:
df = data.frame(x=beta1_lasso_unpen)
ggplot(df,aes(x=x)) + geom_histogram(bins=30,aes(y =..density..)) +
geom_vline(xintercept = c(beta[2],mean(df$x)),linetype=c('solid','dashed'))
Why? Lasso is not aware of the causal problem at hand. Thus, it could
use the omitted variable bias (OVB) for prediction purposes. Correcting
OVB requires to build up regression coefficients of confounding
variables, but this is costly because of the penalization. Leveraging
the OVB in the unpenalized coefficient is “for free”. On the other hand,
this must not be the case. With a sufficiently low penalty term, it
could work. But plain Lasso does not care about an unbiased parameter
\(\beta_1\).
To understand this better, let’s zoom into the coefficient path of
the last replication:
cv_lasso_unpen = cv.glmnet(x[,-1],y,penalty.factor=c(0,rep(1,p-1)),lambda.min.ratio=0.002)
plot(cv_lasso_unpen)
lasso_unpen = glmnet(x[,-1],y,penalty.factor=c(0,rep(1,p-1)),lambda.min.ratio=0.002)
plot(lasso_unpen,xvar = "lambda",label=T)
The empty model consists now of the unpenalized variable and it
leverages the full omitted variable bias for prediction. The OVB is
reduced as the other variables receive at least parts of their
coefficients. In principle, there would be a sweet spot on the
trajectory of \(\beta_1\), but
cross-validation seems to systematically favor models where the OVB in
\(\beta_1\) is used for prediction.
Such things happen if the ML method is not “taught” that we are
interest in a causal effect in the correct way.
Take-away
If somebody tells us the correct model without looking at the
data, life is good.
In the likely case that not, we need to be careful how we
leverage the ML machinery for causal analysis.
There are a lot of interesting things to be learned about how to
teach ML that we are estimating causal effects.
Suggestions to play with the toy model
Feel free to play around with the code. This is useful to sharpen and
challenge your understanding of the methods. Think about the
consequences of a modifications before you run it and check whether the
results are in line with your expectation. Some suggestions:
Modify DGP (correlation of covariates, betas, noise term,
…)
Increase the number of observations
Implement other ad hoc ideas that one could come up with
LS0tDQp0aXRsZTogIkNhdXNhbCBNTDogV2h5IG5haXZlIG1vZGVsIHNlbGVjdGlvbiBmYWlscyINCnN1YnRpdGxlOiAiU2ltdWxhdGlvbiBub3RlYm9vayINCmF1dGhvcjogIk1pY2hhZWwgS25hdXMiDQpkYXRlOiAiMDIvMjMiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KLS0tDQoNCg0KR29hbHM6DQoNCi0gSWxsdXN0cmF0ZSB3aHkgbmFpdmUgaW1wbGVtZW50YXRpb25zIG9mIG1vZGVsIHNlbGVjdGlvbiB1c2luZyBMYXNzbyBhcmUgcHJvYmxlbWF0aWMNCg0KPGJyPg0KDQoNCiMgRGF0YSBnZW5lcmF0aW5nIHByb2Nlc3MNCg0KV2UgY29uc2lkZXIgYSBER1Agd2l0aCBjb3JyZWxhdGVkIHZhcmlhYmxlczoNCg0KLSAkcFxnZXExMCQgY292YXJpYXRlcyBkcmF3biBmcm9tIGEgbXVsdGl2YXJpYXRlIG5vcm1hbCBkaXN0cmlidXRpb246ICRYIFxzaW0gTigwLFxTaWdtYSkkLCB3aGVyZSB3ZSBzZXQgaW4gJFxTaWdtYSQgdGhlIGRpYWdvbmFsIGVudHJpZXMgKHZhcmlhbmNlKSB0byBvbmUgYW5kIHRoZSBvZmYtZGlhZ29uYWwgZW50cmllcyAoY292YXJpYW5jZSkgdG8gMC41Lg0KDQotIFRoZSBvdXRjb21lIG1vZGVsIGlzICRZID0gXHVuZGVyYnJhY2V7XGJldGFfMCArIFxiZXRhXzEgWF8xICsgLi4uICsgXGJldGFfezEwfSBYX3sxMH19X3tcdGV4dHtDRUYgfWYoWCl9ICsgZSQsIHdoZXJlICRYX2okIGlzIHRoZSAkaiQtdGggY29sdW1uIG9mICRYJCBhbmQgJGUgXHNpbSBOKDAsMikkDQoNCi0gV2UgY29uc2lkZXIgdGhlIGZvbGxvd2luZyBwYXJhbWV0ZXJzICRcYmV0YV8wID0gMCQsICRcYmV0YV8xID0gMSQsICRcYmV0YV8yID0gMC45JCwgLi4uLCAkXGJldGFfOSA9IDAuMiQsICRcYmV0YV97MTB9ID0gMC4xJCBzdWNoIHRoYXQgdGhlIGZpcnN0IDEwIHZhcmlhYmxlcyBoYXZlIGEgZGVjcmVhc2luZyBpbXBhY3Qgb24gdGhlIG91dGNvbWUgYW5kIGFueSBmdXJ0aGVyIHZhcmlhYmxlcyBhcmUganVzdCBpcnJlbGV2YW50IG5vaXNlIHdpdGggbm8gdHJ1ZSBwcmVkaWN0aXZlIHBvd2VyDQoNCldlIHNldCAkcD05OSQgYW5kIHdvcmsgd2l0aCBzYW1wbGUgc2l6ZSAkTj0xMDAkLg0KDQpgYGB7ciwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KIyBMb2FkIHRoZSBwYWNrYWdlcyByZXF1aXJlZCBmb3IgbGF0ZXINCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShtdnRub3JtKQ0KbGlicmFyeShnbG1uZXQpDQoNCnNldC5zZWVkKDEyMzQpICMgRm9yIHJlcGxpY2FiaWxpdHkNCg0KIyBEZWZpbmUgdGhlIHJlbGV2YW50IHBhcmFtZXRlcnMNCm4gPSAxMDANCnZhciA9IDENCmNvdiA9IDAuNQ0KcCA9IDk5DQoNCmJldGEgPSBjKDAsc2VxKDEsMC4xLC0wLjEpLHJlcCgwLHAtMTApKQ0Kc2lnID0gbWF0cml4KGNvdixwLHApDQpkaWFnKHNpZykgPSB2YXINCmBgYA0KDQo8YnI+DQoNCg0KIyBBIGNhdXNhbCBxdWVzdGlvbiBhbmQgZGlmZmVyZW50IGFkdmljZQ0KDQpZb3UgYXJlIGludGVyZXN0ZWQgaW4gdGhlIGNhdXNhbCBlZmZlY3Qgb2YgdmFyaWFibGUgJFhfMSQgb24gb3V0Y29tZSAkWSQuIFlvdSBrbm93IHRoYXQgeW91IHNob3VsZCBjb250cm9sIGZvciBhZGRpdGlvbmFsIHZhcmlhYmxlcywgYnV0IG5vdCByZWFsbHkgd2hpY2ggc3BlY2lmaWNhdGlvbiB0byBjaG9vc2UuIFlvdSBvcmdhbml6ZSBhIG1lZXRpbmcgd2l0aCBzb21lIGNvbGxlYWd1ZXMgYW5kIGFzayBmb3IgYWR2aWNlLiBZb3UgcmVjZWl2ZSB0aHJlZSBkaWZmZXJlbnQgYW5zd2VyczoNCg0KLSBQcm9mZXNzb3I6IExvb2tzIGluIGEgY29mZmVlIGN1cCBhbmQgdGVsbHMgeW91IHRoYXQgaXQgaXMgb2J2aW91cyB0aGF0IHlvdSBzaG91bGQgY29udHJvbCBmb3IgJFhfMi1YX3sxMH0kIHVzaW5nIE9MUyBhbmQgbGVhdmVzLg0KDQpUaGUgcmVtYWluaW5nIFBoRCBzdHVkZW50cyBqdXN0IGZpbmlzaGVkIGEgY291cnNlIGluICJCaWcgRGF0YSwgTWFjaGluZSBMZWFybmluZyBhbmQgQUkiIGFuZCBhcmUgdmVyeSBleGNpdGVkIGFib3V0IHZhcmlhYmxlIHNlbGVjdGlvbiB2aWEgTGFzc28gYmVjYXVzZSB0aGlzIGlzIGV4YWN0bHkgd2hhdCB5b3UgYXJlIGFza2luZyBmb3I6DQoNCi0gUGhEIHN0dWRlbnQgMTogVGVsbHMgeW91IHRoYXQgeW91IHNob3VsZCBqdXN0IHRocm93IGV2ZXJ5dGhpbmcgaW4gYSBMYXNzbyByZWdyZXNzaW9uIGFuZCByZXBvcnQgdGhlIHJlc3VsdGluZyBjb2VmZmljaWVudCBvZiAkXGJldGFfMSQgYXMgeW91ciBjYXVzYWwgZWZmZWN0Lg0KDQotIFBoRCBzdHVkZW50IDI6IElzIHNrZXB0aWNhbCBiZWNhdXNlIHRoZSBjYXVzYWwgZWZmZWN0IGlzIHNocnVua2VuIHRvd2FyZHMgemVybyBhbmQgcHJvcG9zZXMgdG8gbGVhdmUgdGhlIGNvZWZmaWNpZW50IG9mICRcYmV0YV8xJCB1bnBlbmFsaXplZCBhbmQgdG8gb25seSBwZW5hbGl6ZSB0aGUgb3RoZXIgdmFyaWFibGVzLg0KDQo8YnI+DQoNCiMgQSBzaW11bGF0aW9uIHN0dWR5IHRvIGZpZ3VyZSBvdXQgd2hhdCB3b3Jrcw0KDQpXZSBydW4gYSBzaW11bGF0aW9uIHN0dWR5IHRoYXQgZHJhd3MgMTAwMCByYW5kb20gc2FtcGxlcyBiYXNlZCBvbiBvdXIgREdQIGFuZCBzdG9yZXMgdGhlIGVzdGltYXRlcyByZXN1bHRpbmcgZnJvbSB0aGUgdGhyZWUgcHJvcG9zZWQgcHJvY2VkdXJlcyAocnVucyBzZXZlcmFsIG1pbnV0ZXMpLg0KDQpgYGB7cn0NCnJlcGwgPSAxMDAwDQoNCmJldGExX29scyA9IGJldGExX2xhc3NvID0gYmV0YTFfbGFzc29fdW5wZW4gPSByZXAoTkEscmVwbCkNCg0KZm9yIChpIGluIDE6cmVwbCkgew0KICB4ID0gY2JpbmQocmVwKDEsbikscm12bm9ybShuLHNpZ21hPXNpZykpDQogIHkgPSB4ICUqJSBiZXRhICsgcm5vcm0obiwwLDIpDQogIA0KICAjIE9MUw0KICBiZXRhMV9vbHNbaV0gPSBsbSh5fnhbLDI6MTFdKSRjb2VmZmljaWVudHNbMl0NCiAgDQogICMgUGxhaW4gTGFzc28gKHNwZWNpZnlpbmcgbGFtYmRhLm1pbi5yYXRpbyBzcGVlZHMgdXApDQogIGN2X2xhc3NvID0gY3YuZ2xtbmV0KHhbLC0xXSx5LGxhbWJkYS5taW4ucmF0aW89MC4wMDEpIA0KICBsYXNzb190ZW1wID0gZ2xtbmV0KHhbLC0xXSx5LGxhbWJkYT1jdl9sYXNzbyRsYW1iZGEubWluKQ0KICBiZXRhMV9sYXNzb1tpXSA9IGxhc3NvX3RlbXAkYmV0YVsxXQ0KICANCiAgIyBMYXNzbyB3aXRoIHVucGVuYWxpemVkIFhfMQ0KICBjdl9sYXNzbyA9IGN2LmdsbW5ldCh4WywtMV0seSxwZW5hbHR5LmZhY3Rvcj1jKDAscmVwKDEscC0xKSksbGFtYmRhLm1pbi5yYXRpbz0wLjAwMSkNCiAgbGFzc29fdGVtcCA9IGdsbW5ldCh4WywtMV0seSxsYW1iZGE9Y3ZfbGFzc28kbGFtYmRhLm1pbixwZW5hbHR5LmZhY3Rvcj1jKDAscmVwKDEscC0xKSkpDQogIGJldGExX2xhc3NvX3VucGVuW2ldID0gbGFzc29fdGVtcCRiZXRhWzFdDQp9DQpgYGANCg0KPGJyPg0KDQpMZXQncyBjaGVjayB0aGUgcmVzdWx0aW5nIGVzdGltYXRvciBkaXN0cmlidXRpb25zIG9mIHRoZSBwcm9wb3NlZCBzdHJhdGVnaWVzOg0KDQojIyBQcm9mZXNzb3IgT0xTDQoNCkRvZXMgaXQgd29yaz8gDQoNClllcyEgVGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgZXN0aW1hdG9yIGhhcyBhIG1lYW4gKGRhc2hlZCBsaW5lKSBjbG9zZSB0byB0aGUgdHJ1ZSB2YWx1ZSBvZiBvbmUgKHNvbGlkIGxpbmUpICRcUmlnaHRhcnJvdyQgdW5iaWFzZWQ6DQoNCg0KYGBge3J9DQpkZiA9IGRhdGEuZnJhbWUoeD1iZXRhMV9vbHMpDQpnZ3Bsb3QoZGYsYWVzKHg9eCkpICsgZ2VvbV9oaXN0b2dyYW0oYmlucz0zMCxhZXMoeSA9Li5kZW5zaXR5Li4pKSArIA0KICAgICAgICAgICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKGJldGFbMl0sbWVhbihkZiR4KSksbGluZXR5cGU9Yygnc29saWQnLCdkYXNoZWQnKSkNCmBgYA0KDQpXaHk/IChpKSBUaGUgbW9kZWwgd2FzIGNob3NlbiB3aXRob3V0IGxvb2tpbmcgYXQgdGhlIGRhdGEgJFxSaWdodGFycm93JCBubyBwcm9ibGVtcyB3aXRoIHBvc3Qtc2VsZWN0aW9uIGlzc3Vlcy4gKGlpKSBUaGUgY29ycmVjdCBtb2RlbCB3YXMgY2hvc2VuIChob3dldmVyIHRoZSBQcm9mLiBtYW5hZ2VkIHRvIGRvIHRoaXMpLiAoaWlpKSBPTFMgaXMgdXNlZCwgd2hpY2ggaXMgdGhlIGJlc3QgdW5iaWFzZWQgZXN0aW1hdG9yIGZvciBzdWNoIGEgc2V0dGluZy4NCg0KPGJyPg0KDQojIyBTdHVkZW50IDEncyBwbGFpbiBMYXNzbw0KDQpEb2VzIGl0IHdvcms/IA0KDQpObyEgVGhlIGVzdGltYXRvciBpcyBjbGVhcmx5IGRvd253YXJkIGJpYXNlZC4NCg0KYGBge3J9DQpkZiA9IGRhdGEuZnJhbWUoeD1iZXRhMV9sYXNzbykNCmdncGxvdChkZixhZXMoeD14KSkgKyBnZW9tX2hpc3RvZ3JhbShiaW5zPTMwLGFlcyh5ID0uLmRlbnNpdHkuLikpICsgDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoYmV0YVsyXSxtZWFuKGRmJHgpKSxsaW5ldHlwZT1jKCdzb2xpZCcsJ2Rhc2hlZCcpKQ0KYGBgDQpXaHk/IFN0dWRlbnQgMiBpcyByaWdodC4gVGhlIHBlbmFsaXphdGlvbiBzaHJpbmtzIHRoZSBwYXJhbWV0ZXIgb2YgaW50ZXJlc3QgdG93YXJkcyB6ZXJvLiBJbiBzZXZlcmFsIHJlcGxpY2F0aW9ucyB0aGUgdmFyaWFibGUgb2YgaW50ZXJlc3QgaXMgbm90IGV2ZW4gc2VsZWN0ZWQsIHdoaWNoIGV4cGxhaW5zIHRoZSBzcGlrZSBhdCB6ZXJvOg0KDQpgYGB7cn0NCnBhc3RlKCJTaGFyZSBvZiByZXBsaWNhdGlvbnMgd2hlcmUgdmFyaWFibGUgb2YgaW50ZXJlc3Qgbm90IHNlbGVjdGVkOiIscm91bmQoIHN1bShiZXRhMV9sYXNzbyA9PSAwKSkgLyByZXBsICoxMDAsIiUiKQ0KYGBgDQoNCjxicj4NCg0KIyMgU3R1ZGVudCAyJ3MgTGFzc28gd2l0aCB1bnBlbmFsaXplZCBwYXJhbWV0ZXIgb2YgaW50ZXJlc3QNCg0KRG9lcyBpdCB3b3JrPyANCg0KTm8hIFRoZSBlc3RpbWF0b3IgaXMgdXB3YXJkIGJpYXNlZDoNCg0KYGBge3J9DQpkZiA9IGRhdGEuZnJhbWUoeD1iZXRhMV9sYXNzb191bnBlbikNCmdncGxvdChkZixhZXMoeD14KSkgKyBnZW9tX2hpc3RvZ3JhbShiaW5zPTMwLGFlcyh5ID0uLmRlbnNpdHkuLikpICsgDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoYmV0YVsyXSxtZWFuKGRmJHgpKSxsaW5ldHlwZT1jKCdzb2xpZCcsJ2Rhc2hlZCcpKQ0KYGBgDQpXaHk/IExhc3NvIGlzIG5vdCBhd2FyZSBvZiB0aGUgY2F1c2FsIHByb2JsZW0gYXQgaGFuZC4gVGh1cywgaXQgY291bGQgdXNlIHRoZSBvbWl0dGVkIHZhcmlhYmxlIGJpYXMgKE9WQikgZm9yIHByZWRpY3Rpb24gcHVycG9zZXMuIENvcnJlY3RpbmcgT1ZCIHJlcXVpcmVzIHRvIGJ1aWxkIHVwIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIG9mIGNvbmZvdW5kaW5nIHZhcmlhYmxlcywgYnV0IHRoaXMgaXMgY29zdGx5IGJlY2F1c2Ugb2YgdGhlIHBlbmFsaXphdGlvbi4gTGV2ZXJhZ2luZyB0aGUgT1ZCIGluIHRoZSB1bnBlbmFsaXplZCBjb2VmZmljaWVudCBpcyAiZm9yIGZyZWUiLiBPbiB0aGUgb3RoZXIgaGFuZCwgdGhpcyBtdXN0IG5vdCBiZSB0aGUgY2FzZS4gV2l0aCBhIHN1ZmZpY2llbnRseSBsb3cgcGVuYWx0eSB0ZXJtLCBpdCBjb3VsZCB3b3JrLiBCdXQgcGxhaW4gTGFzc28gZG9lcyBub3QgY2FyZSBhYm91dCBhbiB1bmJpYXNlZCBwYXJhbWV0ZXIgJFxiZXRhXzEkLg0KDQpUbyB1bmRlcnN0YW5kIHRoaXMgYmV0dGVyLCBsZXQncyB6b29tIGludG8gdGhlIGNvZWZmaWNpZW50IHBhdGggb2YgdGhlIGxhc3QgcmVwbGljYXRpb246DQoNCmBgYHtyfQ0KY3ZfbGFzc29fdW5wZW4gPSBjdi5nbG1uZXQoeFssLTFdLHkscGVuYWx0eS5mYWN0b3I9YygwLHJlcCgxLHAtMSkpLGxhbWJkYS5taW4ucmF0aW89MC4wMDIpDQpwbG90KGN2X2xhc3NvX3VucGVuKQ0KDQpsYXNzb191bnBlbiA9IGdsbW5ldCh4WywtMV0seSxwZW5hbHR5LmZhY3Rvcj1jKDAscmVwKDEscC0xKSksbGFtYmRhLm1pbi5yYXRpbz0wLjAwMikNCnBsb3QobGFzc29fdW5wZW4seHZhciA9ICJsYW1iZGEiLGxhYmVsPVQpDQpgYGANCg0KVGhlIGVtcHR5IG1vZGVsIGNvbnNpc3RzIG5vdyBvZiB0aGUgdW5wZW5hbGl6ZWQgdmFyaWFibGUgYW5kIGl0IGxldmVyYWdlcyB0aGUgZnVsbCBvbWl0dGVkIHZhcmlhYmxlIGJpYXMgZm9yIHByZWRpY3Rpb24uIFRoZSBPVkIgaXMgcmVkdWNlZCBhcyB0aGUgb3RoZXIgdmFyaWFibGVzIHJlY2VpdmUgYXQgbGVhc3QgcGFydHMgb2YgdGhlaXIgY29lZmZpY2llbnRzLiBJbiBwcmluY2lwbGUsIHRoZXJlIHdvdWxkIGJlIGEgc3dlZXQgc3BvdCBvbiB0aGUgdHJhamVjdG9yeSBvZiAkXGJldGFfMSQsIGJ1dCBjcm9zcy12YWxpZGF0aW9uIHNlZW1zIHRvIHN5c3RlbWF0aWNhbGx5IGZhdm9yIG1vZGVscyB3aGVyZSB0aGUgT1ZCIGluICRcYmV0YV8xJCBpcyB1c2VkIGZvciBwcmVkaWN0aW9uLiANCg0KU3VjaCB0aGluZ3MgaGFwcGVuIGlmIHRoZSBNTCBtZXRob2QgaXMgbm90ICJ0YXVnaHQiIHRoYXQgd2UgYXJlIGludGVyZXN0IGluIGEgY2F1c2FsIGVmZmVjdCBpbiB0aGUgY29ycmVjdCB3YXkuDQoNCg0KPGJyPg0KPGJyPg0KDQojIyBUYWtlLWF3YXkNCiANCiAtIElmIHNvbWVib2R5IHRlbGxzIHVzIHRoZSBjb3JyZWN0IG1vZGVsIHdpdGhvdXQgbG9va2luZyBhdCB0aGUgZGF0YSwgbGlmZSBpcyBnb29kLg0KIA0KIC0gSW4gdGhlIGxpa2VseSBjYXNlIHRoYXQgbm90LCB3ZSBuZWVkIHRvIGJlIGNhcmVmdWwgaG93IHdlIGxldmVyYWdlIHRoZSBNTCBtYWNoaW5lcnkgZm9yIGNhdXNhbCBhbmFseXNpcy4NCiANCiAtIFRoZXJlIGFyZSBhIGxvdCBvZiBpbnRlcmVzdGluZyB0aGluZ3MgdG8gYmUgbGVhcm5lZCBhYm91dCBob3cgdG8gdGVhY2ggTUwgdGhhdCB3ZSBhcmUgZXN0aW1hdGluZyBjYXVzYWwgZWZmZWN0cy4NCiANCjxicj4NCjxicj4NCiANCiANCiMjIFN1Z2dlc3Rpb25zIHRvIHBsYXkgd2l0aCB0aGUgdG95IG1vZGVsDQoNCkZlZWwgZnJlZSB0byBwbGF5IGFyb3VuZCB3aXRoIHRoZSBjb2RlLiBUaGlzIGlzIHVzZWZ1bCB0byBzaGFycGVuIGFuZCBjaGFsbGVuZ2UgeW91ciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBtZXRob2RzLiBUaGluayBhYm91dCB0aGUgY29uc2VxdWVuY2VzIG9mIGEgbW9kaWZpY2F0aW9ucyBiZWZvcmUgeW91IHJ1biBpdCBhbmQgY2hlY2sgd2hldGhlciB0aGUgcmVzdWx0cyBhcmUgaW4gbGluZSB3aXRoIHlvdXIgZXhwZWN0YXRpb24uIFNvbWUgc3VnZ2VzdGlvbnM6DQogDQotIE1vZGlmeSBER1AgKGNvcnJlbGF0aW9uIG9mIGNvdmFyaWF0ZXMsIGJldGFzLCBub2lzZSB0ZXJtLCAuLi4pDQoNCi0gSW5jcmVhc2UgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMNCg0KLSBJbXBsZW1lbnQgb3RoZXIgYWQgaG9jIGlkZWFzIHRoYXQgb25lIGNvdWxkIGNvbWUgdXAgd2l0aA0KDQog