Data generating process
Consider a model with only one covariate: \(Y = \underbrace{2X - X^3}_{CEF} +
\epsilon\), where \(X \sim
uniform(0,1)\) and \(\epsilon \sim
N(0,1/3)\).
Plot a random draw with \(N=500\)
and the true CEF.
# Load the packages required for later
if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("rpart")) install.packages("rpart", dependencies = TRUE); library(rpart)
if (!require("partykit")) install.packages("partykit", dependencies = TRUE); library(partykit)
if (!require("grf")) install.packages("grf", dependencies = TRUE); library(grf)
if (!require("rpart.plot")) install.packages("rpart.plot", dependencies = TRUE); library(rpart.plot)
set.seed(1234) # For replicability
cef = function(x){-x^3 + 2*x}
n = 500
x = runif(n)
y = cef(x) + rnorm(n,0,1/3)
df = data.frame(x=x,y=y)
ggplot(df) + stat_function(fun=cef,size=1) +
geom_point(aes(x=x,y=y),color="black",alpha = 0.4)
Different ways to fit the data
OLS
Plain OLS without any polynomials fits a straight line through the
data cloud missing the non-linear shape but providing the best linear
approximation:
df$y_hat_ols = predict(lm(y~x))
ggplot(df,aes(x=x,y=y)) + stat_function(fun=cef,size=1) +
geom_point(color="black",alpha = 0.4) +
geom_point(aes(x=x,y=y_hat_ols),shape="square",color="blue") +
geom_smooth(formula="y~x", method='lm')
Regression tree
The regression tree provides a step function approximation of the
non-linear shape:
tree = rpart(y~x,data = df)
rpart.plot(tree)
df$y_hat_tree = predict(tree)
ggplot(df) + stat_function(fun=cef,size=1) +
geom_point(aes(x=x,y=y),color="black",alpha = 0.4) +
geom_point(aes(x=x,y=y_hat_tree),shape="square",color="blue")
Notably the leaves are more narrow in the left part region where the
curve is steeper, while more observation are pooled on the right where
the functions is relatively flat. This makes intuitive sense.
Random Forest
Random Forest combines many of such stepwise approximation and
provides a relatively good and smooth approximation of non-linear
CEF:
rf = regression_forest(as.matrix(x),y,tune.parameters = "all",num.trees = 2000)
df$y_hat_rf = predict(rf)$predictions
ggplot(df) + stat_function(fun=cef,size=1) +
geom_point(aes(x=x,y=y),color="black",alpha = 0.4) +
geom_point(aes(x=x,y=y_hat_rf),shape="square",color="blue")
Global vs. local predictors
To understand the difference between global and local methods,
consider that we want to predict the outcome value at \(x_0 = 0.2\). Each of the three estimators
uses just a weighted average of observed outcomes to form this
prediction.
test_point = 0.2
OLS
Recall that in matrix notation the parameter estimates are \(\beta = (X'X)^{-1}X'Y\), where
\(X\) contains a constant. Now the
fitted value at the test point is \(\hat{y}_0
= [1~x_0] \beta = \underbrace{[1~x_0] (X'X)^{-1}X'}_{w_{ols}}
Y\), where \(w_{ols}\) contains
the weights that each outcome receives when forming the fitted
value.
We graph the scatterplot of the observed data with larger points
indicating a larger absolute outcome weight, and the color indicating
whether the outcome weight is positive or negative:
X = cbind(rep(1,n),x)
predict_ols = as.numeric(c(1,test_point) %*% solve(t(X) %*% X) %*% t(X) %*% y)
df$w_ols = t(c(1,test_point) %*% solve(t(X) %*% X) %*% t(X))
w_sign_ols = rep("negative",n)
w_sign_ols[df$w_ols>0] = "positive"
w_sign_ols = factor(w_sign_ols,level=c("negative","positive"))
ggplot(df) + stat_function(fun=cef,size=1) +
geom_point(aes(x=x,y=y,size=abs(w_ols),color=w_sign_ols),alpha = 0.2) +
geom_point(x=test_point,y=predict_ols,shape="cross",size = 3, stroke = 2,color="yellow") +
scale_color_manual(values=c("red","blue"))
A global method like OLS uses all available data points to form the
prediction and outcomes that are far away of the point of interest
receive negative weights.
Regression trees
The predictions of regression trees are formed as an average within
the leaf of the point of interest. This means that all outcomes in that
leaf receive the same weight and all other outcomes a weight of
zero:
predict_tree = predict(tree,newdata=data.frame(x=test_point))
tree2 = as.party(tree)
nodes = predict(tree2,df, type = "node")
node_test = predict(tree2, newdata=data.frame(x=test_point), type = "node")
df$w_tree = (nodes==node_test) / sum(nodes==node_test)
w_sign_tree = rep("zero",n)
w_sign_tree[df$w_tree>0] = "positive"
w_sign_tree = factor(w_sign_tree,level=c("zero","positive"))
ggplot(df) + stat_function(fun=cef,size=1) +
geom_point(aes(x=x,y=y,size=w_tree,color=w_sign_tree),alpha = 0.2) +
geom_point(x=test_point,y=predict_tree,shape="cross",size = 3, stroke = 2,color="yellow") +
scale_color_manual(values=c("darkgray","blue"))
The problem is that predictions at the boundary discard outcomes that
are very close.
Random Forest
The Random Forest creates a smoother weighting function and gives
larger weights to closer outcomes and ignores outcomes that are further
away.
predict_rf = predict(rf,newdata=as.matrix(test_point))$predictions
df$w_rf = t( as.matrix( get_forest_weights(rf,newdata=as.matrix(test_point)) ) )
w_sign_rf = rep("zero",n)
w_sign_rf[df$w_rf>0] = "positive"
w_sign_rf = factor(w_sign_rf,level=c("zero","positive"))
ggplot(df) + stat_function(fun=cef,size=1) +
geom_point(aes(x=x,y=y,size=w_rf,color=w_sign_rf),alpha = 0.2) +
geom_point(x=test_point,y=predict_rf,shape="cross",size = 3, stroke = 2,color="yellow") +
scale_color_manual(values=c("darkgray","blue"))
Take-away:
- Tree based methods use only outcomes close to the prediction point,
while global methods like OLS use a (at least for me) less intuitive
weighting with negative weights
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 (functional form of CEF, noise term, …)
Decrease and increase the number of observations
Decrease and increase the number of trees for the random
forest
Add squared or cubic covariates to OLS specification
LS0tDQp0aXRsZTogIlN1cGVydmlzZWQgTUw6IFRyZWUtYmFzZWQgbWV0aG9kcyINCmF1dGhvcjogIk1pY2hhZWwgS25hdXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclbS8leScpYCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQotLS0NCg0KPGJyPg0KDQpHb2FsczoNCg0KLSBJbGx1c3RyYXRlIHRyZWUtYmFzZWQgbWV0aG9kcw0KDQotIGFuZCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGdsb2JhbCBhbmQgbG9jYWwgbWV0aG9kcw0KDQo8YnI+DQoNCg0KIyMgRGF0YSBnZW5lcmF0aW5nIHByb2Nlc3MNCg0KQ29uc2lkZXIgYSBtb2RlbCB3aXRoIG9ubHkgb25lIGNvdmFyaWF0ZTogJFkgPSBcdW5kZXJicmFjZXsyWCAtIFheM31fe0NFRn0gKyBcZXBzaWxvbiQsIHdoZXJlICRYIFxzaW0gdW5pZm9ybSgwLDEpJCBhbmQgJFxlcHNpbG9uIFxzaW0gTigwLDEvMykkLg0KDQpQbG90IGEgcmFuZG9tIGRyYXcgd2l0aCAkTj01MDAkIGFuZCB0aGUgdHJ1ZSBDRUYuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQojIExvYWQgdGhlIHBhY2thZ2VzIHJlcXVpcmVkIGZvciBsYXRlcg0KaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkodGlkeXZlcnNlKQ0KaWYgKCFyZXF1aXJlKCJycGFydCIpKSBpbnN0YWxsLnBhY2thZ2VzKCJycGFydCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHJwYXJ0KQ0KaWYgKCFyZXF1aXJlKCJwYXJ0eWtpdCIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYXJ0eWtpdCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHBhcnR5a2l0KQ0KaWYgKCFyZXF1aXJlKCJncmYiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ3JmIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoZ3JmKQ0KaWYgKCFyZXF1aXJlKCJycGFydC5wbG90IikpIGluc3RhbGwucGFja2FnZXMoInJwYXJ0LnBsb3QiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShycGFydC5wbG90KQ0KDQpzZXQuc2VlZCgxMjM0KSAjIEZvciByZXBsaWNhYmlsaXR5DQoNCmNlZiA9IGZ1bmN0aW9uKHgpey14XjMgKyAyKnh9DQoNCm4gPSA1MDANCg0KeCA9IHJ1bmlmKG4pDQp5ID0gY2VmKHgpICsgcm5vcm0obiwwLDEvMykNCg0KZGYgPSBkYXRhLmZyYW1lKHg9eCx5PXkpDQpnZ3Bsb3QoZGYpICsgc3RhdF9mdW5jdGlvbihmdW49Y2VmLHNpemU9MSkgKyANCiAgICAgICAgICAgIGdlb21fcG9pbnQoYWVzKHg9eCx5PXkpLGNvbG9yPSJibGFjayIsYWxwaGEgPSAwLjQpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQojIyBEaWZmZXJlbnQgd2F5cyB0byBmaXQgdGhlIGRhdGENCg0KIyMjIE9MUw0KDQpQbGFpbiBPTFMgd2l0aG91dCBhbnkgcG9seW5vbWlhbHMgZml0cyBhIHN0cmFpZ2h0IGxpbmUgdGhyb3VnaCB0aGUgZGF0YSBjbG91ZCBtaXNzaW5nIHRoZSBub24tbGluZWFyIHNoYXBlIGJ1dCBwcm92aWRpbmcgdGhlIGJlc3QgbGluZWFyIGFwcHJveGltYXRpb246DQoNCmBgYHtyfQ0KZGYkeV9oYXRfb2xzID0gcHJlZGljdChsbSh5fngpKQ0KZ2dwbG90KGRmLGFlcyh4PXgseT15KSkgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1jZWYsc2l6ZT0xKSArIA0KICBnZW9tX3BvaW50KGNvbG9yPSJibGFjayIsYWxwaGEgPSAwLjQpICsgDQogIGdlb21fcG9pbnQoYWVzKHg9eCx5PXlfaGF0X29scyksc2hhcGU9InNxdWFyZSIsY29sb3I9ImJsdWUiKSArIA0KICBnZW9tX3Ntb290aChmb3JtdWxhPSJ5fngiLCBtZXRob2Q9J2xtJykgDQpgYGANCg0KPGJyPg0KDQojIyMgUmVncmVzc2lvbiB0cmVlDQoNClRoZSByZWdyZXNzaW9uIHRyZWUgcHJvdmlkZXMgYSBzdGVwIGZ1bmN0aW9uIGFwcHJveGltYXRpb24gb2YgdGhlIG5vbi1saW5lYXIgc2hhcGU6DQoNCmBgYHtyfQ0KdHJlZSA9IHJwYXJ0KHl+eCxkYXRhID0gZGYpDQpycGFydC5wbG90KHRyZWUpDQpkZiR5X2hhdF90cmVlID0gcHJlZGljdCh0cmVlKQ0KDQpnZ3Bsb3QoZGYpICsgc3RhdF9mdW5jdGlvbihmdW49Y2VmLHNpemU9MSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeD14LHk9eSksY29sb3I9ImJsYWNrIixhbHBoYSA9IDAuNCkgKw0KICBnZW9tX3BvaW50KGFlcyh4PXgseT15X2hhdF90cmVlKSxzaGFwZT0ic3F1YXJlIixjb2xvcj0iYmx1ZSIpIA0KYGBgDQoNCk5vdGFibHkgdGhlIGxlYXZlcyBhcmUgbW9yZSBuYXJyb3cgaW4gdGhlIGxlZnQgcGFydCByZWdpb24gd2hlcmUgdGhlIGN1cnZlIGlzIHN0ZWVwZXIsIHdoaWxlIG1vcmUgb2JzZXJ2YXRpb24gYXJlIHBvb2xlZCBvbiB0aGUgcmlnaHQgd2hlcmUgdGhlIGZ1bmN0aW9ucyBpcyByZWxhdGl2ZWx5IGZsYXQuIFRoaXMgbWFrZXMgaW50dWl0aXZlIHNlbnNlLg0KDQo8YnI+DQoNCiMjIyBSYW5kb20gRm9yZXN0DQoNClJhbmRvbSBGb3Jlc3QgY29tYmluZXMgbWFueSBvZiBzdWNoIHN0ZXB3aXNlIGFwcHJveGltYXRpb24gYW5kIHByb3ZpZGVzIGEgcmVsYXRpdmVseSBnb29kIGFuZCBzbW9vdGggYXBwcm94aW1hdGlvbiBvZiBub24tbGluZWFyIENFRjoNCg0KYGBge3J9DQpyZiA9IHJlZ3Jlc3Npb25fZm9yZXN0KGFzLm1hdHJpeCh4KSx5LHR1bmUucGFyYW1ldGVycyA9ICJhbGwiLG51bS50cmVlcyA9IDIwMDApDQpkZiR5X2hhdF9yZiA9IHByZWRpY3QocmYpJHByZWRpY3Rpb25zDQoNCmdncGxvdChkZikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1jZWYsc2l6ZT0xKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PXgseT15KSxjb2xvcj0iYmxhY2siLGFscGhhID0gMC40KSArDQogIGdlb21fcG9pbnQoYWVzKHg9eCx5PXlfaGF0X3JmKSxzaGFwZT0ic3F1YXJlIixjb2xvcj0iYmx1ZSIpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQojIyBHbG9iYWwgdnMuIGxvY2FsIHByZWRpY3RvcnMNCg0KVG8gdW5kZXJzdGFuZCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGdsb2JhbCBhbmQgbG9jYWwgbWV0aG9kcywgY29uc2lkZXIgdGhhdCB3ZSB3YW50IHRvIHByZWRpY3QgdGhlIG91dGNvbWUgdmFsdWUgYXQgJHhfMCA9IDAuMiQuIEVhY2ggb2YgdGhlIHRocmVlIGVzdGltYXRvcnMgdXNlcyBqdXN0IGEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiBvYnNlcnZlZCBvdXRjb21lcyB0byBmb3JtIHRoaXMgcHJlZGljdGlvbi4NCg0KYGBge3J9DQp0ZXN0X3BvaW50ID0gMC4yDQpgYGANCg0KIyMjIE9MUw0KDQpSZWNhbGwgdGhhdCBpbiBtYXRyaXggbm90YXRpb24gdGhlIHBhcmFtZXRlciBlc3RpbWF0ZXMgYXJlICRcYmV0YSA9IChYJ1gpXnstMX1YJ1kkLCB3aGVyZSAkWCQgY29udGFpbnMgYSBjb25zdGFudC4gTm93IHRoZSBmaXR0ZWQgdmFsdWUgYXQgdGhlIHRlc3QgcG9pbnQgaXMgJFxoYXR7eX1fMCA9IFsxfnhfMF0gXGJldGEgPSBcdW5kZXJicmFjZXtbMX54XzBdIChYJ1gpXnstMX1YJ31fe3dfe29sc319IFkkLCB3aGVyZSAkd197b2xzfSQgY29udGFpbnMgdGhlIHdlaWdodHMgdGhhdCBlYWNoIG91dGNvbWUgcmVjZWl2ZXMgd2hlbiBmb3JtaW5nIHRoZSBmaXR0ZWQgdmFsdWUuDQoNCldlIGdyYXBoIHRoZSBzY2F0dGVycGxvdCBvZiB0aGUgb2JzZXJ2ZWQgZGF0YSB3aXRoIGxhcmdlciBwb2ludHMgaW5kaWNhdGluZyBhIGxhcmdlciBhYnNvbHV0ZSBvdXRjb21lIHdlaWdodCwgYW5kIHRoZSBjb2xvciBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIG91dGNvbWUgd2VpZ2h0IGlzIHBvc2l0aXZlIG9yIG5lZ2F0aXZlOg0KDQpgYGB7cn0NClggPSBjYmluZChyZXAoMSxuKSx4KQ0KcHJlZGljdF9vbHMgPSBhcy5udW1lcmljKGMoMSx0ZXN0X3BvaW50KSAlKiUgc29sdmUodChYKSAlKiUgWCkgJSolIHQoWCkgJSolIHkpDQpkZiR3X29scyA9IHQoYygxLHRlc3RfcG9pbnQpICUqJSBzb2x2ZSh0KFgpICUqJSBYKSAlKiUgdChYKSkNCg0Kd19zaWduX29scyA9IHJlcCgibmVnYXRpdmUiLG4pDQp3X3NpZ25fb2xzW2RmJHdfb2xzPjBdID0gInBvc2l0aXZlIg0Kd19zaWduX29scyA9IGZhY3Rvcih3X3NpZ25fb2xzLGxldmVsPWMoIm5lZ2F0aXZlIiwicG9zaXRpdmUiKSkNCg0KZ2dwbG90KGRmKSArIHN0YXRfZnVuY3Rpb24oZnVuPWNlZixzaXplPTEpICsgDQogIGdlb21fcG9pbnQoYWVzKHg9eCx5PXksc2l6ZT1hYnMod19vbHMpLGNvbG9yPXdfc2lnbl9vbHMpLGFscGhhID0gMC4yKSArDQogIGdlb21fcG9pbnQoeD10ZXN0X3BvaW50LHk9cHJlZGljdF9vbHMsc2hhcGU9ImNyb3NzIixzaXplID0gMywgc3Ryb2tlID0gMixjb2xvcj0ieWVsbG93IikgKyANCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJyZWQiLCJibHVlIikpDQpgYGANCg0KQSBnbG9iYWwgbWV0aG9kIGxpa2UgT0xTIHVzZXMgYWxsIGF2YWlsYWJsZSBkYXRhIHBvaW50cyB0byBmb3JtIHRoZSBwcmVkaWN0aW9uIGFuZCBvdXRjb21lcyB0aGF0IGFyZSBmYXIgYXdheSBvZiB0aGUgcG9pbnQgb2YgaW50ZXJlc3QgcmVjZWl2ZSBuZWdhdGl2ZSB3ZWlnaHRzLg0KDQo8YnI+DQoNCiMjIyBSZWdyZXNzaW9uIHRyZWVzDQoNClRoZSBwcmVkaWN0aW9ucyBvZiByZWdyZXNzaW9uIHRyZWVzIGFyZSBmb3JtZWQgYXMgYW4gYXZlcmFnZSB3aXRoaW4gdGhlIGxlYWYgb2YgdGhlIHBvaW50IG9mIGludGVyZXN0LiBUaGlzIG1lYW5zIHRoYXQgYWxsIG91dGNvbWVzIGluIHRoYXQgbGVhZiByZWNlaXZlIHRoZSBzYW1lIHdlaWdodCBhbmQgYWxsIG90aGVyIG91dGNvbWVzIGEgd2VpZ2h0IG9mIHplcm86DQoNCmBgYHtyfQ0KcHJlZGljdF90cmVlID0gcHJlZGljdCh0cmVlLG5ld2RhdGE9ZGF0YS5mcmFtZSh4PXRlc3RfcG9pbnQpKQ0KDQp0cmVlMiA9IGFzLnBhcnR5KHRyZWUpDQpub2RlcyA9IHByZWRpY3QodHJlZTIsZGYsIHR5cGUgPSAibm9kZSIpDQpub2RlX3Rlc3QgPSBwcmVkaWN0KHRyZWUyLCBuZXdkYXRhPWRhdGEuZnJhbWUoeD10ZXN0X3BvaW50KSwgdHlwZSA9ICJub2RlIikNCg0KZGYkd190cmVlID0gKG5vZGVzPT1ub2RlX3Rlc3QpICAvIHN1bShub2Rlcz09bm9kZV90ZXN0KQ0KDQp3X3NpZ25fdHJlZSA9IHJlcCgiemVybyIsbikNCndfc2lnbl90cmVlW2RmJHdfdHJlZT4wXSA9ICJwb3NpdGl2ZSINCndfc2lnbl90cmVlID0gZmFjdG9yKHdfc2lnbl90cmVlLGxldmVsPWMoInplcm8iLCJwb3NpdGl2ZSIpKQ0KDQpnZ3Bsb3QoZGYpICsgc3RhdF9mdW5jdGlvbihmdW49Y2VmLHNpemU9MSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeD14LHk9eSxzaXplPXdfdHJlZSxjb2xvcj13X3NpZ25fdHJlZSksYWxwaGEgPSAwLjIpICsNCiAgZ2VvbV9wb2ludCh4PXRlc3RfcG9pbnQseT1wcmVkaWN0X3RyZWUsc2hhcGU9ImNyb3NzIixzaXplID0gMywgc3Ryb2tlID0gMixjb2xvcj0ieWVsbG93IikgKyANCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJkYXJrZ3JheSIsImJsdWUiKSkNCg0KYGBgDQoNClRoZSBwcm9ibGVtIGlzIHRoYXQgcHJlZGljdGlvbnMgYXQgdGhlIGJvdW5kYXJ5IGRpc2NhcmQgb3V0Y29tZXMgdGhhdCBhcmUgdmVyeSBjbG9zZS4NCg0KPGJyPg0KDQoNCiMjIyBSYW5kb20gRm9yZXN0DQoNClRoZSBSYW5kb20gRm9yZXN0IGNyZWF0ZXMgYSBzbW9vdGhlciB3ZWlnaHRpbmcgZnVuY3Rpb24gYW5kIGdpdmVzIGxhcmdlciB3ZWlnaHRzIHRvIGNsb3NlciBvdXRjb21lcyBhbmQgaWdub3JlcyBvdXRjb21lcyB0aGF0IGFyZSBmdXJ0aGVyIGF3YXkuDQoNCmBgYHtyfQ0KcHJlZGljdF9yZiA9IHByZWRpY3QocmYsbmV3ZGF0YT1hcy5tYXRyaXgodGVzdF9wb2ludCkpJHByZWRpY3Rpb25zDQpkZiR3X3JmID0gdCggYXMubWF0cml4KCBnZXRfZm9yZXN0X3dlaWdodHMocmYsbmV3ZGF0YT1hcy5tYXRyaXgodGVzdF9wb2ludCkpICkgKQ0KDQp3X3NpZ25fcmYgPSByZXAoInplcm8iLG4pDQp3X3NpZ25fcmZbZGYkd19yZj4wXSA9ICJwb3NpdGl2ZSINCndfc2lnbl9yZiA9IGZhY3Rvcih3X3NpZ25fcmYsbGV2ZWw9YygiemVybyIsInBvc2l0aXZlIikpDQoNCmdncGxvdChkZikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1jZWYsc2l6ZT0xKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PXgseT15LHNpemU9d19yZixjb2xvcj13X3NpZ25fcmYpLGFscGhhID0gMC4yKSArDQogIGdlb21fcG9pbnQoeD10ZXN0X3BvaW50LHk9cHJlZGljdF9yZixzaGFwZT0iY3Jvc3MiLHNpemUgPSAzLCBzdHJva2UgPSAyLGNvbG9yPSJ5ZWxsb3ciKSArIA0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImRhcmtncmF5IiwiYmx1ZSIpKQ0KYGBgDQoNCjxicj4NCjxicj4NCg0KKlRha2UtYXdheSo6DQogDQogLSBUcmVlIGJhc2VkIG1ldGhvZHMgdXNlIG9ubHkgb3V0Y29tZXMgY2xvc2UgdG8gdGhlIHByZWRpY3Rpb24gcG9pbnQsIHdoaWxlIGdsb2JhbCBtZXRob2RzIGxpa2UgT0xTIHVzZSBhIChhdCBsZWFzdCBmb3IgbWUpIGxlc3MgaW50dWl0aXZlIHdlaWdodGluZyB3aXRoIG5lZ2F0aXZlIHdlaWdodHMNCiANCjxicj4NCjxicj4NCiANCiANCipTdWdnZXN0aW9ucyB0byBwbGF5IHdpdGggdGhlIHRveSBtb2RlbCo6DQoNCkZlZWwgZnJlZSB0byBwbGF5IGFyb3VuZCB3aXRoIHRoZSBjb2RlLiBUaGlzIGlzIHVzZWZ1bCB0byBzaGFycGVuIGFuZCBjaGFsbGVuZ2UgeW91ciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBtZXRob2RzLiBUaGluayBhYm91dCB0aGUgY29uc2VxdWVuY2VzIG9mIGEgbW9kaWZpY2F0aW9ucyBiZWZvcmUgeW91IHJ1biBpdCBhbmQgY2hlY2sgd2hldGhlciB0aGUgcmVzdWx0cyBhcmUgaW4gbGluZSB3aXRoIHlvdXIgZXhwZWN0YXRpb24uIFNvbWUgc3VnZ2VzdGlvbnM6DQogDQotIE1vZGlmeSBER1AgKGZ1bmN0aW9uYWwgZm9ybSBvZiBDRUYsIG5vaXNlIHRlcm0sIC4uLikNCg0KLSBEZWNyZWFzZSBhbmQgaW5jcmVhc2UgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMNCg0KLSBEZWNyZWFzZSBhbmQgaW5jcmVhc2UgdGhlIG51bWJlciBvZiB0cmVlcyBmb3IgdGhlIHJhbmRvbSBmb3Jlc3QNCg0KLSBBZGQgc3F1YXJlZCBvciBjdWJpYyBjb3ZhcmlhdGVzIHRvIE9MUyBzcGVjaWZpY2F0aW9uDQog