1 Regularisation cho mô hình Bayes

Mô hình tuyến tính tổng quát (GLM) là một công cụ phổ quát và linh hoạt để suy diễn thống kê trong nghiên cứu khoa học. Khi dựng một mô hình, chúng ta đang cố gắng đơn giản hay thu gọn một hệ thống phức tạp (đa chiều) trong thế giới tự nhiên rộng lớn thành một hệ thống đơn giản vừa đủ cho phép tiên lượng giá trị biến kết quả (outcome) dựa vào một số ít những biến số (predictor) tiêu biểu nhất.

Không gian dữ liệu đơn giản này thường được nhà nghiên cứu chủ động tạo ra bằng thiết kế thí nghiệm, nhất là nghiên cứu lâm sàng. Đôi khi, người ta không hài lòng với mô hình quá đơn giản và lại tìm cách mở rộng nó thành mô hình đa biến, hoặc mixed model. Theo thời gian, tiến bộ về công nghệ khiến dữ liệu ngày càng phong phú và dễ thu thập hơn, do đó số lượng biến trong dữ liệu trở nên to lớn, thậm chí lớn hơn cả kích thước mẫu khảo sát. Lúc này, bài toán chọn lọc biến số, giản lược nội dung mô hình trở nên quan trọng. Việc xác định một mô hình tối giản không chỉ cần thiết cho mục tiêu suy diễn mà còn có ảnh hưởng tích cực cho độ chính xác vì giảm nguy cơ mô hình bị “overfit”.

Tương tự những phương pháp hiệu chỉnh (Regularisation) giá trị tham số hồi quy như ridge, lasso, elasticnet bên phái « frequentist », khi làm hồi quy Bayes chúng ta cũng mong muốn rút gọn nội dung mô hình bằng cách đẩy những tham số có hiệu ứng cực thấp trong mô hình về zero, nhưng bảo toàn giá trị những tham số có vai trò đóng góp quan trọng cho kết quả tiên lượng.

2 Prior Piironen-Vehtari (2017)

Năm 2017, hai tác giả Piironen và Vehtari đã tìm ra giải pháp cho câu hỏi trên, họ đề xuất 1 prior có hành vi giống như trên. Ta gọi nó là prior « hình móng ngựa » (horseshoe prior) vì đồ thị hàm mật độ xác suất của nó có dạng chữ U, tập trung về gần 0 hoặc 1.

Như ta biết, tham số hồi quy beta trong các mô hình tuyến tính Bayes thường được áp dụng một prior yếu như Gaussian với µ=0 và sigma rộng (thí dụ sigma=100) :

\[\beta_j \sim{} N(0, \sigma^2)\]

Prior hình móng ngựa cổ điển được xác định bằng một phân bố Gaussian có µ cũng =0, nhưng sigma được phân tích thành 2 tham số bộ phận khác là Tau và Lambda như sau :

\[\beta_j \sim{} N(0, \tau^2\lambda_j^2)\]

Tham số Tau và Lambda đều cùng được mô tả bằng phân phối Cauchy(0,1),

\[\tau \sim{} Cauchy(0,1)\]

\[\lambda_j \sim{} Cauchy(0,1)\] Trong đó j nhận giá trị từ 1,2,3… đến nX là số lượng predictor trong design matrix của mô hình

Như vậy, vai trò của tham số Tau là xác định cho một tham số scale parameter tổng quát (khoảng nằm giữa của phân bố) có khuynh hướng đẩy giá trị beta về zero qua một trọng số. Trong khi đó, Lambda đặc trưng cho phần đuôi của phân bố, cho phép bảo tồn giá trị của một vài tham số beta.

Piironen và Vehtari đã cải tiến prior móng ngựa cơ bản này thành một dạng mới, trong đó Tau vẫn được xác định bằng phân bố Cauchy(0,1) , tuy nhiên tham số Lambda được xác định phụ thuộc vào Tau và một hằng số c (gọi là slab) :

\[\beta_j \sim{} N(0, \tau^2\tilde{\lambda_j}^2)\]

\[\tau \sim{} cauchy(0,1)\]

\[\tilde{\lambda_j}^2 =\frac{c^2\lambda_j^2}{c^2+\tau^2\lambda_j^2}\]

\[\lambda_j \sim{} Cauchy(0,1)\]

Với những biến số (predictor) có hiệu ứng mạnh, ta có :

\[\tau^2\lambda_j^2 \gg c^2\]

Khi đó, prior sẽ xấp xỉ :

\[N(0,c^2)\]

Khi đó beta sẽ không bị phạt nhiều, trái lại những biến số có hiệu ứng yếu (bao gồm cả trường hợp có quá ít dữ liệu) , ta có:

\[\tau^2\lambda_j^2 \ll c^2\] Lúc này gía trị beta sẽ bị đầy về gần zero theo quy luật chung của prior móng ngựa

3 Thí dụ minh họa

Trong thí dụ minh họa này, Nhi sử dụng bộ dữ liệu « Body Fat » của Roger W. Johnson và Carleton College, có thể tải từ:

https://ww2.amstat.org/publications/jse/v4n1/datasets.johnson.html

Mục tiêu của bài toán là xây dựng một mô hình hồi quy tuyến tính nhằm ước tính lượng mỡ trong cơ thể (biến Brozek Body Fat) dựa vào Tuổi, trọng lượng, chiều cao, và các chỉ số nhân trắc (bụng, ngực, cổ tay,…). Bài toán cũng có thể được đặt ra theo hướng diễn dịch với mục tiêu khảo sát mối tương quan bộ phận giữa mỗi chỉ số nhân trắc và giá trị Body_fat.

library(tidyverse)

df=read.table("https://ww2.amstat.org/publications/jse/datasets/fat.dat.txt")

df= df[,c(2, 5:7, 10:19)]

names(df)=c("brozek_fat","age","weight","height","neck","chest","abdomen","hip","thigh","knee","ankle","biceps","forearm","wrist")
df= df[-42,]

head(df)%>%knitr::kable()
brozek_fat age weight height neck chest abdomen hip thigh knee ankle biceps forearm wrist
12.6 23 154.25 67.75 36.2 93.1 85.2 94.5 59.0 37.3 21.9 32.0 27.4 17.1
6.9 22 173.25 72.25 38.5 93.6 83.0 98.7 58.7 37.3 23.4 30.5 28.9 18.2
24.6 22 154.00 66.25 34.0 95.8 87.9 99.2 59.6 38.9 24.0 28.8 25.2 16.6
10.9 26 184.75 72.25 37.4 101.8 86.4 101.2 60.1 37.3 22.8 32.4 29.4 18.2
27.8 24 184.25 71.25 34.4 97.3 100.0 101.9 63.2 42.2 24.0 32.2 27.7 17.7
20.6 24 210.25 74.75 39.0 104.5 94.4 107.8 66.0 42.0 25.6 35.7 30.6 18.8

Có đến 13 predictors trong dữ liệu.

library(corrplot)
library(RColorBrewer)

cor_mat=as.matrix(cor(method="pearson",as.matrix(df)))

cor_mat%>%corrplot(.,order="hclust",type="lower",method="color",tl.col="black", tl.srt=45,tl.cex=0.5,col=rev(brewer.pal(n=10, name="RdBu")))

Nhi sẽ dựng mô hình hồi quy tuyến tính có kèm hiệu chỉnh (regularisation) tham số hồi quy cho mỗi predictor để phân lập những biến có hiệu ứng mạnh nhất (quan trọng nhất ) và những biến có ảnh hưởng yếu hoặc không có vai trò đóng góp đáng kể.

8 phương pháp sẽ được so sánh: Bên phái frequentist, đầu tiên là một mô hình hồi quy tuyến tính đa biến không có hiệu chỉnh gì cả, được dựng bằng hàm GLM, sau đó Nhi sẽ lần lượt thử : hồi quy Ridge, hồi quy Lasso và Elastic net bằng package glmnet (Friedman, Trevor Hastie và Rob Tibshirani, 2013). Bên phái Bayes, Nhi sẽ áp dụng phương pháp BMA với package BMS, và prior hình móng ngựa cải tiến của Piironen (2017) được thực hiện bằng 3 cách khác nhau: code thủ công bằng STAN, cũng như thông qua giao thức rstanarm và brms.

4 Mô hình GLM không áp dụng regularisation

# GLM

fit_glm=glm(data=df,brozek_fat~.)

summary(fit_glm)
## 
## Call:
## glm(formula = brozek_fat ~ ., data = df)
## 
## Deviance Residuals: 
##      Min        1Q    Median        3Q       Max  
## -10.2537   -2.5759   -0.0991    2.8863    9.3718  
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -17.80071   20.60403  -0.864   0.3885    
## age           0.05684    0.03003   1.893   0.0596 .  
## weight       -0.08595    0.05747  -1.496   0.1361    
## height       -0.03734    0.16571  -0.225   0.8219    
## neck         -0.43027    0.21897  -1.965   0.0506 .  
## chest        -0.01844    0.09575  -0.193   0.8474    
## abdomen       0.89013    0.08378  10.624   <2e-16 ***
## hip          -0.19589    0.13605  -1.440   0.1512    
## thigh         0.23640    0.13596   1.739   0.0834 .  
## knee         -0.02129    0.22994  -0.093   0.9263    
## ankle         0.16723    0.20643   0.810   0.4187    
## biceps        0.15655    0.15999   0.978   0.3288    
## forearm       0.42942    0.18491   2.322   0.0211 *  
## wrist        -1.47440    0.49664  -2.969   0.0033 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for gaussian family taken to be 15.96847)
## 
##     Null deviance: 14915.5  on 250  degrees of freedom
## Residual deviance:  3784.5  on 237  degrees of freedom
## AIC: 1423.3
## 
## Number of Fisher Scoring iterations: 2

5 Bộ ba Lasso-Ridge-Elasticnet

Đầu tiên là mô hình LASSO

# Lasso

library(glmnet)

lasso = cv.glmnet(x=as.matrix(df[,-1]),y=df$brozek_fat,alpha=1,nfolds=5)

coef(lasso)
## 14 x 1 sparse Matrix of class "dgCMatrix"
##                       1
## (Intercept) -9.09239847
## age          0.02400386
## weight       .         
## height      -0.24340856
## neck         .         
## chest        .         
## abdomen      0.57021332
## hip          .         
## thigh        .         
## knee         .         
## ankle        .         
## biceps       .         
## forearm      .         
## wrist       -0.47897402

Sau đó là mô hình Ridge

ridge = cv.glmnet(x=as.matrix(df[,-1]),y=df$brozek_fat,alpha=0,nfolds=5)

coef(ridge)
## 14 x 1 sparse Matrix of class "dgCMatrix"
##                       1
## (Intercept) -6.15352703
## age          0.09357610
## weight       0.01731019
## height      -0.37028608
## neck        -0.12669803
## chest        0.14384149
## abdomen      0.27866578
## hip          0.08675680
## thigh        0.13744500
## knee         0.10353008
## ankle       -0.07357488
## biceps       0.07180562
## forearm      0.16700123
## wrist       -0.98190998

Cuối cùng là Elastic net

enet= cv.glmnet(x=as.matrix(df[,-1]),y=df$brozek_fat,nfolds=5)

coef(enet)
## 14 x 1 sparse Matrix of class "dgCMatrix"
##                       1
## (Intercept) -6.53342854
## age          0.02997906
## weight       .         
## height      -0.24850948
## neck         .         
## chest        .         
## abdomen      0.58853559
## hip          .         
## thigh        .         
## knee         .         
## ankle        .         
## biceps       .         
## forearm      .         
## wrist       -0.70732370

Như ta thấy, mô hình Lasso và elasticnet có khuynh hướng triệt tiêu hẳn những biến số được cho là yếu khỏi mô hình, trong khi ridge regression chỉ giảm gía trị tham số hồi quy cho những biến này xuống thấp nhất có thể.

6 Phương pháp BMA

Trong khi đó, phương pháp Bayesian Model averaging kiểm tra tất cả những tổ hợp có thể giữa 13 biến và xác định những mô hình tối ưu nhất.

library(BMS)

bmsmod= bms(df, burn = 1000, 
            iter = 10000, 
            g = "EBL",
            mprior = "uniform",
            mcmc="bdn", 
            user.int = F)

coef(bmsmod, std.coefs = T, order.by.pip = T, include.constant = T)
##                PIP    Post Mean    Post SD Cond.Pos.Sign Idx
## abdomen     1.0000  1.255317146 0.09604764    1.00000000   6
## weight      0.9146 -0.434024060 0.18300505    0.00000000   2
## wrist       0.7624 -0.118947512 0.08372544    0.00000000  13
## forearm     0.5851  0.066038463 0.06619393    1.00000000  12
## neck        0.3310 -0.041071144 0.07142256    0.00000000   4
## thigh       0.2303  0.026584495 0.06242852    0.99956578   8
## biceps      0.2165  0.021031372 0.04944985    1.00000000  11
## age         0.1940  0.012537607 0.03462482    0.97010309   1
## hip         0.1910 -0.034141666 0.09343202    0.00000000   7
## height      0.1115 -0.004677901 0.02397510    0.07533632   3
## knee        0.0861  0.003214869 0.02276074    0.94308943   9
## chest       0.0834 -0.001058647 0.03027382    0.51318945   5
## ankle       0.0737  0.002293030 0.01514326    0.96200814  10
## (Intercept) 1.0000 -3.696741238         NA            NA   0
image(bmsmod,col=c("#199ac1","#e30c37"))

summary(bmsmod)
## Mean no. regressors               Draws             Burnins 
##            "4.7796"             "10000"              "1000" 
##                Time  No. models visited      Modelspace 2^K 
##    "0.8600001 secs"              "3360"              "8192" 
##           % visited         % Topmodels            Corr PMP 
##                "41"                "98"            "0.9854" 
##            No. Obs.         Model Prior             g-Prior 
##               "251"     "uniform / 6.5"               "EBL" 
##     Shrinkage-Stats 
##         "Av=0.9929"

7 Code mô hình thủ công với STAN

Trong bài báo gốc, 2 tác giả đã cung cấp 2 bộ STAN code viết sẵn cho mô hình hồi quy tuyến tính Gaussian (như thí dụ này) và logistic sử dụng phân bố bernoulli , Nhi chỉ việc dùng lại code của họ.

# Stan code

stan_code="
// data block begins here
data {
int <lower=0> n;                // number  of  instances
int <lower=0> d;               // number  of  predictors
vector[n] y;                   //numeric outcome
matrix[n,d] x;                 //  Design matrix 
real <lower=0>  scale_icept;   // prior scale parameter for  the  intercept
real <lower=0>  scale_global; // scale  for  the half-t prior  for  tau
real <lower=1>  nu_global;     // degrees  of  freedom  for  the half -t priors  for  tau
real <lower=1> nu_local;      // degrees  of  freedom  for  the half -t priors  for  lambdas
real <lower=0>  slab_scale;    // slab  scale  for  the  regularized  horseshoe
real <lower=0> slab_df;       // slab  degrees  of  freedom  for the  regularized  horseshoe
}   // data block ends here

// Parameter blocks begins here

parameters {
real  logsigma;
real  beta0;
vector[d] z;
real <lower=0>  aux1_global;
real <lower=0>  aux2_global;
vector <lower =0>[d] aux1_local;
vector <lower =0>[d] aux2_local;
real <lower=0> caux;
}  

transformed  parameters {
real <lower=0> sigma;                 // noise  scale parameter
real <lower=0> tau;                   // global  shrinkage  parameter
vector <lower =0>[d] lambda;          // local  shrinkage  parameter
vector <lower =0>[d] lambda_tilde;   // ’truncated ’ local  shrinkage  parameter
real <lower=0> c;                      // slab  scale
vector[d] beta;                       // regression  coefficients
vector[n] f;                           // latent  function  values
sigma = exp(logsigma );
lambda = aux1_local  .* sqrt(aux2_local );
tau = aux1_global * sqrt(aux2_global) * scale_global*sigma;
c = slab_scale * sqrt(caux);
lambda_tilde = sqrt( c^2 * square(lambda) ./ (c^2 + tau^2* square(lambda )) );
beta = z .*  lambda_tilde*tau;
f = beta0 + x*beta;
}
// Parameter blocks ends here

model {
// half -t priors  for  lambdas  and tau , and  inverse -gamma  for c^2
z~normal(0, 1);

aux1_local∼normal(0, 1);

aux2_local∼inv_gamma (0.5* nu_local , 0.5* nu_local );

aux1_global∼normal(0, 1);

aux2_global∼inv_gamma (0.5* nu_global , 0.5* nu_global );

caux~inv_gamma (0.5* slab_df , 0.5* slab_df );

beta0~normal(0,scale_icept );

y~normal(f, sigma);

}"

Sau đó tạo dữ liệu cho mô hình STAN này, list dữ liệu đầu vào gồm có:

  1. Design matrix Xmat (bao gồm Intercept) và vector Response

  2. Khai báo các giá trị tham số: p0= số biến dự kiến có hiệu ứng mạnh, thí dụ 4; d= số biến trong design matrix = 14; n= cỡ mẫu; giá trị scale_global được ước tính bằng công thức ; các tham số cho prior khác như nu_global=1, nu_local=1, scale_icept=100 (cho beta intercept), slab_scale=2 (hằng số c), độ tự do cho slab =

Response=df$brozek_fat

Xmat <- model.matrix(~.,df[,-1])

p0 = 4
d = ncol(Xmat)
n = nrow(df)

scale_global = p0/(ncol(Xmat)-p0-1)/sqrt(n)

data_list <- list(y = Response, 
                  d = d,
                  n = n,
                  x=Xmat,
                  scale_icept = 100,
                  scale_global = scale_global,
                  nu_global = 1,
                  nu_local = 1, 
                  slab_scale = 2,
                  slab_df=4)

Sau đó ta compile code mô hình vào sampler qua hàm stan

library(rstan)

rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())

set.seed(123)

fit_stan= stan(data = data_list, 
          model_code = stan_code , 
          chains = 1,
          iter = 2500, 
          warmup = 500, 
          thin = 2,
          control=list(adapt_delta=0.95,max_treedepth=20)
)
## In file included from C:/Users/Admin/Documents/R/win-library/3.5/BH/include/boost/config.hpp:39:0,
##                  from C:/Users/Admin/Documents/R/win-library/3.5/BH/include/boost/math/tools/config.hpp:13,
##                  from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/stan/math/rev/core/var.hpp:7,
##                  from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/stan/math/rev/core/gevv_vvv_vari.hpp:5,
##                  from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/stan/math/rev/core.hpp:12,
##                  from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/stan/math/rev/mat.hpp:4,
##                  from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/stan/math.hpp:4,
##                  from C:/Users/Admin/Documents/R/win-library/3.5/StanHeaders/include/src/stan/model/model_header.hpp:4,
##                  from file2e20582d312a.cpp:8:
## C:/Users/Admin/Documents/R/win-library/3.5/BH/include/boost/config/compiler/gcc.hpp:186:0: warning: "BOOST_NO_CXX11_RVALUE_REFERENCES" redefined
##  #  define BOOST_NO_CXX11_RVALUE_REFERENCES
##  ^
## <command-line>:0:0: note: this is the location of the previous definition
## cc1plus.exe: warning: unrecognized command line option "-Wno-ignored-attributes"
## 
## SAMPLING FOR MODEL '8399c2e5d8b36da20e9c1268d0d2033c' NOW (CHAIN 1).
## 
## Gradient evaluation took 0 seconds
## 1000 transitions using 10 leapfrog steps per transition would take 0 seconds.
## Adjust your expectations accordingly!
## 
## 
## Iteration:    1 / 2500 [  0%]  (Warmup)
## Iteration:  250 / 2500 [ 10%]  (Warmup)
## Iteration:  500 / 2500 [ 20%]  (Warmup)
## Iteration:  501 / 2500 [ 20%]  (Sampling)
## Iteration:  750 / 2500 [ 30%]  (Sampling)
## Iteration: 1000 / 2500 [ 40%]  (Sampling)
## Iteration: 1250 / 2500 [ 50%]  (Sampling)
## Iteration: 1500 / 2500 [ 60%]  (Sampling)
## Iteration: 1750 / 2500 [ 70%]  (Sampling)
## Iteration: 2000 / 2500 [ 80%]  (Sampling)
## Iteration: 2250 / 2500 [ 90%]  (Sampling)
## Iteration: 2500 / 2500 [100%]  (Sampling)
## 
##  Elapsed Time: 59.462 seconds (Warm-up)
##                162.751 seconds (Sampling)
##                222.213 seconds (Total)
library(broom)

tidyMCMC(fit_stan, conf.int = TRUE, conf.method = "HPDinterval", pars = "beta")->beta_stan

beta_stan$term=colnames(Xmat)

beta_stan
##           term      estimate  std.error    conf.low   conf.high
## 1  (Intercept)  0.0162140658 0.50068044 -1.00990049  1.01002596
## 2          age  0.0210251496 0.02703985 -0.02665005  0.07517061
## 3       weight -0.1047821207 0.04352636 -0.18899519 -0.02011387
## 4       height -0.0356375656 0.09617571 -0.27757721  0.12485590
## 5         neck -0.1938621839 0.21749998 -0.63916929  0.14148045
## 6        chest  0.0009930989 0.05783711 -0.11754580  0.13076210
## 7      abdomen  0.8785487422 0.07256873  0.74642848  1.01479645
## 8          hip -0.0739597853 0.10788929 -0.31094738  0.10679842
## 9        thigh  0.0929407358 0.10748415 -0.06355845  0.32501330
## 10        knee  0.0113087731 0.11149836 -0.22134925  0.27797802
## 11       ankle  0.0285924087 0.11578093 -0.18632014  0.32528990
## 12      biceps  0.0812814654 0.11278566 -0.10052852  0.33205229
## 13     forearm  0.2006673886 0.18594765 -0.06872802  0.57754006
## 14       wrist -0.8128474580 0.60129260 -1.82980265  0.12618305

8 Package rstanarm

Ngoài cách viết code thủ công, package rstanarm là một giao thức tiện dụng hơn để dùng sampler STAN. rstanarm hỗ trợ prior Piironen thông qua hàm hs()

# rstanarm

library(rstanarm)

n = nrow(df)
nX = 13
p0 = 4
global_scale = p0/(nX - p0)/sqrt(n)

fit_stanarm = stan_glm(brozek_fat~ ., 
                                  data = df, 
                                  iter=2500,
                                  warmup=500, 
                                  chains = 1, 
                                  thin = 2, 
                                  refresh = 0,
                                  prior_intercept = normal(0,100), 
                                  prior = hs(df = 4, 
                                             global_df = 1, 
                                             global_scale = global_scale),
                                  prior_aux = cauchy(0,2),
                                  control=list(adapt_delta=0.95,max_treedepth=20)
                                  )
## 
## Gradient evaluation took 0.01 seconds
## 1000 transitions using 10 leapfrog steps per transition would take 100 seconds.
## Adjust your expectations accordingly!
## 
## 
## 
##  Elapsed Time: 7.25 seconds (Warm-up)
##                25.164 seconds (Sampling)
##                32.414 seconds (Total)
tidyMCMC(fit_stanarm$stanfit,conf.int = TRUE, 
         conf.method = "HPDinterval")->beta_stanarm

beta_stanarm
##             term      estimate   std.error      conf.low     conf.high
## 1    (Intercept) -2.837949e+01 15.47142563  -55.19909207    4.40828527
## 2            age  2.383024e-02  0.02673085   -0.02040000    0.08058551
## 3         weight -1.081275e-01  0.04660253   -0.19166770   -0.01360330
## 4         height -4.134557e-02  0.10966803   -0.30118243    0.15836417
## 5           neck -2.614049e-01  0.20917925   -0.65407528    0.07724954
## 6          chest  1.726801e-03  0.06538599   -0.12216647    0.15482328
## 7        abdomen  8.804351e-01  0.07658661    0.73167595    1.02762084
## 8            hip -9.061495e-02  0.11646706   -0.33143812    0.10055583
## 9          thigh  1.290939e-01  0.12043056   -0.05098560    0.38748269
## 10          knee  8.938019e-03  0.12439847   -0.25556659    0.27856085
## 11         ankle  2.388494e-02  0.13009575   -0.24625557    0.28512641
## 12        biceps  9.202022e-02  0.12984876   -0.09846373    0.39373817
## 13       forearm  2.183674e-01  0.17955693   -0.07128410    0.56673512
## 14         wrist -6.578419e-01  0.52219401   -1.66303209    0.10603066
## 15         sigma  4.053744e+00  0.19268328    3.67157192    4.42385861
## 16      mean_PPD  1.888053e+01  0.36285075   18.15397978   19.57771935
## 17 log-posterior -7.805212e+02  5.86496781 -791.74405554 -769.61170182

9 Package brms

Cuối cùng, brms là 1 giao thức khác của sampler STAN, brms mạnh hơn rstanarm vì hỗ trợ nhiều dạng mô hình hơn, tuy nhiên cú pháp khai báo prior Piironen trong brms phức tạp hơn rstanarm một chút. Cho mô hình Gaussian, brms sử dụng 3 class prior là Intercept, beta (b) và sigma

# brms

nX = 13
p0 = 4
par_ratio = p0/(nX - p0)

library(brms)

fit_brms= brm(brozek_fat~ ., 
              data = df,
              iter = 2500, 
              warmup = 500,
              chains = 1, 
              thin = 2,
              refresh = 0,
              prior = c(prior(normal(0, 100),class = "Intercept"), 
                        prior(horseshoe(df = 4,
                                        par_ratio = par_ratio),
                              class = "b"), 
                        prior(cauchy(0, 5),class = "sigma")
                        ),
              control=list(adapt_delta=0.95,max_treedepth=20)
              )
## 
## Gradient evaluation took 0 seconds
## 1000 transitions using 10 leapfrog steps per transition would take 0 seconds.
## Adjust your expectations accordingly!
## 
## 
## 
##  Elapsed Time: 4.166 seconds (Warm-up)
##                16.871 seconds (Sampling)
##                21.037 seconds (Total)
tidyMCMC(fit_brms$fit,conf.int = TRUE, 
         conf.method = "HPDinterval")->beta_brms

beta_brms
##           term      estimate   std.error     conf.low    conf.high
## 1  b_Intercept -2.831626e+01 15.93898643 -54.19379742  7.492338784
## 2        b_age  2.068595e-02  0.02619802  -0.02400919  0.073916499
## 3     b_weight -1.084322e-01  0.04729948  -0.19390625 -0.009047489
## 4     b_height -4.742829e-02  0.10774528  -0.28352938  0.156593600
## 5       b_neck -2.462822e-01  0.21273066  -0.67589728  0.054713620
## 6      b_chest  1.822540e-03  0.06475696  -0.12749344  0.143689846
## 7    b_abdomen  8.791064e-01  0.07475039   0.74674926  1.016718300
## 8        b_hip -8.854441e-02  0.10865496  -0.33725519  0.067052421
## 9      b_thigh  1.217147e-01  0.11135051  -0.06446909  0.338543806
## 10      b_knee  3.788469e-04  0.12919469  -0.26014343  0.291824528
## 11     b_ankle  2.604558e-02  0.12551830  -0.22191156  0.309632577
## 12    b_biceps  9.238613e-02  0.11849940  -0.09810031  0.346194821
## 13   b_forearm  2.010712e-01  0.17969870  -0.08467930  0.542849804
## 14     b_wrist -5.968681e-01  0.53614990  -1.67633265  0.103817062
## 15       sigma  4.046756e+00  0.18951528   3.70842548  4.454642806
## 16       hs_c2  1.958997e+00  2.82946496   0.18636004  5.326918895
beta_brms$term=c(colnames(Xmat),"sigma","mean_PPD")

10 So sánh giá trị tham số beta của các mô hình

Ta có thể trích xuất giá trị của 14 tham số hồi quy beta trong 8 mô hình đã dựng và so sánh chúng với nhau một cách trực quan bằng heatmap

# Coefficient

glm_dat=data_frame(Term=fit_glm$coefficients%>%names(),
                   Betas=fit_glm$coefficients%>%as.numeric(),
                   Model="GLM")

lasso_dat=data_frame(Term=coef(lasso)%>%as.matrix()%>%rownames(),
                   Betas=coef(lasso)%>%as.numeric(),
                   Model="LASSO")

enet_dat=data_frame(Term=coef(enet)%>%as.matrix()%>%rownames(),
                     Betas=coef(enet)%>%as.numeric(),
                     Model="ENET")

ridge_dat=data_frame(Term=coef(ridge)%>%as.matrix()%>%rownames(),
                    Betas=coef(ridge)%>%as.numeric(),
                    Model="RIDGE")

coef_bms=coef(bmsmod, std.coefs = T, order.by.pip = T, include.constant = T)%>%as.matrix()

bma_dat=data_frame(Term=coef_bms%>%rownames(),
                     Betas=coef_bms[,3]%>%as.numeric(),
                     Model="BMA")

brms_dat=data_frame(Term=beta_brms[1:14,]%>%.$term,
                   Betas=beta_brms[1:14,]%>%.$estimate,
                   Model="BRMS")

stanarm_dat=data_frame(Term=beta_stanarm[1:14,]%>%.$term,
                    Betas=beta_stanarm[1:14,]%>%.$estimate,
                    Model="STANARM")

stan_dat=data_frame(Term=beta_stan[1:14,]%>%.$term,
                       Betas=beta_stan[1:14,]%>%.$estimate,
                       Model="STAN")

comp_dat=rbind(glm_dat,lasso_dat,bma_dat,
               ridge_dat,enet_dat,
               brms_dat,stanarm_dat,stan_dat)

comp_dat%>%filter(Term!="(Intercept)")%>%
  ggplot(aes(x=reorder(Model,Betas),
             y=reorder(Term,Betas),fill=Betas))+
  geom_tile()+
  theme_bw()+
  scale_fill_gradient2(low="#199ac1",high="#e30c37",mid=NA,midpoint = 0)+
  scale_x_discrete()

Kết quả cho thấy cả 3 mô hình STAn thủ công, brms và rstanarm là tương đương, vì thực chất chúng đều dựa vào lõi là STAN sampler, và cùng code cho hàm likelihood, cùng prior. Có thể dùng 1 trong 3 mô hình để so sánh với những mô hình còn lại.

Prior móng ngựa của Piironen gây ra một sự điều chỉnh rất nhẹ cho tham số hồi quy, tương đương với Ridge regularisation và không khác biệt rõ so với mô hình GLM không có hiệu chỉnh. Nó không triệt tiêu biến số như Lasso hay enet. Có vẻ như các tham số của prior đã không được tối ưu ? Hoặc hiệu ứng này chỉ rõ ràng hơn khi áp dụng cho dataset với rất nhiều biến, thí dụ như dataset Leukemia chẳng hạn ?

Những biến có hiệu ứng mạnh thì kết quả đồng nhất ở các mô hình, thí dụ abdomen, wrist, …

11 So sánh trực quan kết quả của 5 mô hình với giá trị thực tế

pred_brms=data_frame(Pred=predict(fit_brms,newdata=df)%>%as.matrix()%>%.[,1],
                     Model="Piironen")

pred_enet=data_frame(Pred=predict(enet,newx=as.matrix(df[,-1]))%>%as.numeric(),
                     Model="ENET")

pred_lasso=data_frame(Pred=predict(lasso,newx=as.matrix(df[,-1]))%>%as.numeric(),
                      Model="LASSO")

pred_ridge=data_frame(Pred=predict(ridge,newx=as.matrix(df[,-1]))%>%as.numeric(),
                      Model="RIDGE")

truth=data_frame(Pred=df$brozek_fat,Model="Truth")

pred_glm=data_frame(Pred=fit_glm$fitted.values,Model="GLM")

comp_pred=rbind(pred_brms,pred_enet,pred_glm,pred_lasso,pred_ridge)

comp_pred%>%mutate(Truth=rep(df$brozek_fat,5))%>%
  ggplot()+
  geom_violin(alpha=0.7,aes(x=Model,y=Truth,group=Model),fill="white")+
  geom_violin(alpha=0.5,aes(x=Model,y=Pred,fill=Model,group=Model),linetype=2)+
  geom_jitter(alpha=0.2,aes(x=Model,y=Pred,group=Model),shape=21,col="black")+
  theme_bw()+scale_y_continuous("Value")+coord_flip()

Trên chính tập dữ liệu gốc, mô hình Bayes với prior Piironen cho ra kết quả tiên lượng có phân phối đồng dạng với giá trị thực tế,độ chính xác của nó tương đương với mô hình tuyến tính cơ bản bằng hàm glm.

12 Kết luận

Trong bài này Nhi đã giới thiệu với các bạn thêm 1 phương pháp chọn lọc biến số/mô hình nữa, và lần này là cho mô hình Bayes viết bằng STAN code. Một công cụ Regularisation cho mô hình Bayes có ý nghĩa quan trọng, vì trong hoàn cảnh dữ liệu hạn chế, nó sẽ giúp tránh nguy cơ diễn giải sai phân bố hậu nghiệm.

Các bạn có thể tải bài báo gốc của Piironen và Vehtari : https://arxiv.org/pdf/1707.01694.pdf, trong phần phụ lục, tác giả có hướng dẫn 2 cách code khác nhau cho mô hình Gaussian, và chỉ thay đổi 3 dòng code thì bạn đã có thể làm mô hình logistic.

Nếu chưa quen code thủ công, bạn cũng có thể chọn cách dùng giao thức qua stanarm và brms, dễ dàng và nhanh chóng hơn.

LS0tDQp0aXRsZTogIlJlZ3VsYXJpc2F0aW9uIG3DtCBow6xuaCBo4buTaSBxdXkgQmF5ZXMiDQphdXRob3I6ICJMw6ogTmfhu41jIEto4bqjIE5oaSINCmRhdGU6ICIwNCBUaMOhbmcgNiAyMDE4Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImRlZmF1bHQiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQohW10ocGlpcm9uZW5wcmlvcjEucG5nKQ0KDQojIFJlZ3VsYXJpc2F0aW9uIGNobyBtw7QgaMOsbmggQmF5ZXMNCg0KTcO0IGjDrG5oIHR1eeG6v24gdMOtbmggdOG7lW5nIHF1w6F0IChHTE0pIGzDoCBt4buZdCBjw7RuZyBj4bulIHBo4buVIHF1w6F0IHbDoCBsaW5oIGhv4bqhdCDEkeG7gyBzdXkgZGnhu4VuIHRo4buRbmcga8OqIHRyb25nIG5naGnDqm4gY+G7qXUga2hvYSBo4buNYy4gS2hpIGThu7FuZyBt4buZdCBtw7QgaMOsbmgsIGNow7puZyB0YSDEkWFuZyBj4buRIGfhuq9uZyDEkcahbiBnaeG6o24gaGF5IHRodSBn4buNbiBt4buZdCBo4buHIHRo4buRbmcgcGjhu6ljIHThuqFwICjEkWEgY2hp4buBdSkgdHJvbmcgdGjhur8gZ2nhu5tpIHThu7Egbmhpw6puIHLhu5luZyBs4bubbiB0aMOgbmggbeG7mXQgaOG7hyB0aOG7kW5nIMSRxqFuIGdp4bqjbiB24burYSDEkeG7pyBjaG8gcGjDqXAgdGnDqm4gbMaw4bujbmcgZ2nDoSB0cuG7iyBiaeG6v24ga+G6v3QgcXXhuqMgKG91dGNvbWUpIGThu7FhIHbDoG8gbeG7mXQgc+G7kSDDrXQgbmjhu69uZyBiaeG6v24gc+G7kSAocHJlZGljdG9yKSB0acOqdSBiaeG7g3UgbmjhuqV0LiAgDQoNCktow7RuZyBnaWFuIGThu68gbGnhu4d1IMSRxqFuIGdp4bqjbiBuw6B5IHRoxrDhu51uZyDEkcaw4bujYyBuaMOgIG5naGnDqm4gY+G7qXUgY2jhu6cgxJHhu5luZyB04bqhbyByYSBi4bqxbmcgdGhp4bq/dCBr4bq/IHRow60gbmdoaeG7h20sIG5o4bqldCBsw6AgbmdoacOqbiBj4bupdSBsw6JtIHPDoG5nLiDEkMO0aSBraGksIG5nxrDhu51pIHRhIGtow7RuZyBow6BpIGzDsm5nIHbhu5tpIG3DtCBow6xuaCBxdcOhIMSRxqFuIGdp4bqjbiB2w6AgbOG6oWkgdMOsbSBjw6FjaCBt4bufIHLhu5luZyBuw7MgdGjDoG5oIG3DtCBow6xuaCDEkWEgYmnhur9uLCBob+G6t2MgbWl4ZWQgbW9kZWwuIFRoZW8gdGjhu51pIGdpYW4sIHRp4bq/biBi4buZIHbhu4EgY8O0bmcgbmdo4buHIGtoaeG6v24gZOG7ryBsaeG7h3UgbmfDoHkgY8OgbmcgcGhvbmcgcGjDuiB2w6AgZOG7hSB0aHUgdGjhuq1wIGjGoW4sIGRvIMSRw7Mgc+G7kSBsxrDhu6NuZyBiaeG6v24gdHJvbmcgZOG7ryBsaeG7h3UgdHLhu58gbsOqbiB0byBs4bubbiwgdGjhuq1tIGNow60gbOG7m24gaMahbiBj4bqjIGvDrWNoIHRoxrDhu5tjIG3huqt1IGto4bqjbyBzw6F0LiBMw7pjIG7DoHksIGLDoGkgdG/DoW4gY2jhu41uIGzhu41jIGJp4bq/biBz4buRLCBnaeG6o24gbMaw4bujYyBu4buZaSBkdW5nIG3DtCBow6xuaCB0cuG7nyBuw6puIHF1YW4gdHLhu41uZy4gVmnhu4djIHjDoWMgxJHhu4tuaCBt4buZdCBtw7QgaMOsbmggdOG7kWkgZ2nhuqNuIGtow7RuZyBjaOG7iSBj4bqnbiB0aGnhur90IGNobyBt4bulYyB0acOqdSBzdXkgZGnhu4VuIG3DoCBjw7JuIGPDsyDhuqNuaCBoxrDhu59uZyB0w61jaCBj4buxYyBjaG8gxJHhu5kgY2jDrW5oIHjDoWMgdsOsIGdp4bqjbSBuZ3V5IGPGoSBtw7QgaMOsbmggYuG7iyAib3ZlcmZpdCIuDQoNClTGsMahbmcgdOG7sSBuaOG7r25nIHBoxrDGoW5nIHBow6FwIGhp4buHdSBjaOG7iW5oIChSZWd1bGFyaXNhdGlvbikgZ2nDoSB0cuG7iyB0aGFtIHPhu5EgaOG7k2kgcXV5IG5oxrAgcmlkZ2UsIGxhc3NvLCBlbGFzdGljbmV0IGLDqm4gcGjDoWkgwqsgZnJlcXVlbnRpc3QgwrssIGtoaSBsw6BtIGjhu5NpIHF1eSBCYXllcyBjaMO6bmcgdGEgY8WpbmcgbW9uZyBtdeG7kW4gcsO6dCBn4buNbiBu4buZaSBkdW5nIG3DtCBow6xuaCBi4bqxbmcgY8OhY2ggxJHhuql5IG5o4buvbmcgdGhhbSBz4buRIGPDsyBoaeG7h3Ug4bupbmcgY+G7sWMgdGjhuqVwIHRyb25nIG3DtCBow6xuaCB24buBIHplcm8sIG5oxrBuZyBi4bqjbyB0b8OgbiBnacOhIHRy4buLIG5o4buvbmcgdGhhbSBz4buRIGPDsyB2YWkgdHLDsiDEkcOzbmcgZ8OzcCBxdWFuIHRy4buNbmcgY2hvIGvhur90IHF14bqjIHRpw6puIGzGsOG7o25nLiANCg0KIyBQcmlvciBQaWlyb25lbi1WZWh0YXJpICgyMDE3KQ0KDQpOxINtIDIwMTcsIGhhaSB0w6FjIGdp4bqjIFBpaXJvbmVuIHbDoCBWZWh0YXJpIMSRw6MgdMOsbSByYSBnaeG6o2kgcGjDoXAgY2hvIGPDonUgaOG7j2kgdHLDqm4sIGjhu40gxJHhu4EgeHXhuqV0IDEgcHJpb3IgY8OzIGjDoG5oIHZpIGdp4buRbmcgbmjGsCB0csOqbi4gVGEgZ+G7jWkgbsOzIGzDoCBwcmlvciDCqyBow6xuaCBtw7NuZyBuZ+G7sWEgwrsgKGhvcnNlc2hvZSBwcmlvcikgdsOsIMSR4buTIHRo4buLIGjDoG0gbeG6rXQgxJHhu5kgeMOhYyBzdeG6pXQgY+G7p2EgbsOzIGPDsyBk4bqhbmcgY2jhu68gVSwgdOG6rXAgdHJ1bmcgduG7gSBn4bqnbiAwIGhv4bq3YyAxLg0KDQpOaMawIHRhIGJp4bq/dCwgdGhhbSBz4buRIGjhu5NpIHF1eSBiZXRhIHRyb25nIGPDoWMgbcO0IGjDrG5oIHR1eeG6v24gdMOtbmggQmF5ZXMgdGjGsOG7nW5nIMSRxrDhu6NjIMOhcCBk4bulbmcgbeG7mXQgcHJpb3IgeeG6v3UgbmjGsCBHYXVzc2lhbiB24bubaSDCtT0wIHbDoCBzaWdtYSBy4buZbmcgKHRow60gZOG7pSBzaWdtYT0xMDApIDoNCg0KJCRcYmV0YV9qIFxzaW17fSBOKDAsIFxzaWdtYV4yKSQkDQoNClByaW9yIGjDrG5oIG3Ds25nIG5n4buxYSBj4buVIMSRaeG7g24gxJHGsOG7o2MgeMOhYyDEkeG7i25oIGLhurFuZyBt4buZdCBwaMOibiBi4buRIEdhdXNzaWFuIGPDsyDCtSBjxaluZyA9MCwgbmjGsG5nIHNpZ21hIMSRxrDhu6NjIHBow6JuIHTDrWNoIHRow6BuaCAyIHRoYW0gc+G7kSBi4buZIHBo4bqtbiBraMOhYyBsw6AgVGF1IHbDoCBMYW1iZGEgbmjGsCBzYXUgOg0KDQoNCiQkXGJldGFfaiBcc2lte30gTigwLCBcdGF1XjJcbGFtYmRhX2peMikkJA0KDQpUaGFtIHPhu5EgVGF1IHbDoCBMYW1iZGEgxJHhu4F1IGPDuW5nIMSRxrDhu6NjIG3DtCB04bqjIGLhurFuZyBwaMOibiBwaOG7kWkgQ2F1Y2h5KDAsMSksIA0KDQoNCiQkXHRhdSBcc2lte30gQ2F1Y2h5KDAsMSkkJA0KDQokJFxsYW1iZGFfaiBcc2lte30gQ2F1Y2h5KDAsMSkkJA0KVHJvbmcgxJHDsyBqIG5o4bqtbiBnacOhIHRy4buLIHThu6sgMSwyLDPigKYgxJHhur9uIG5YIGzDoCBz4buRIGzGsOG7o25nIHByZWRpY3RvciB0cm9uZyBkZXNpZ24gbWF0cml4IGPhu6dhIG3DtCBow6xuaCANCg0KTmjGsCB24bqteSwgdmFpIHRyw7IgY+G7p2EgdGhhbSBz4buRIFRhdSBsw6AgeMOhYyDEkeG7i25oIGNobyBt4buZdCB0aGFtIHPhu5Egc2NhbGUgcGFyYW1ldGVyIHThu5VuZyBxdcOhdCAoa2hv4bqjbmcgbuG6sW0gZ2nhu69hIGPhu6dhIHBow6JuIGLhu5EpIGPDsyBraHV5bmggaMaw4bubbmcgxJHhuql5IGdpw6EgdHLhu4sgYmV0YSB24buBIHplcm8gcXVhIG3hu5l0IHRy4buNbmcgc+G7kS4gVHJvbmcga2hpIMSRw7MsIExhbWJkYSDEkeG6t2MgdHLGsG5nIGNobyBwaOG6p24gxJF1w7RpIGPhu6dhIHBow6JuIGLhu5EsIGNobyBwaMOpcCBi4bqjbyB04buTbiBnacOhIHRy4buLIGPhu6dhIG3hu5l0IHbDoGkgdGhhbSBz4buRIGJldGEuDQoNClBpaXJvbmVuIHbDoCBWZWh0YXJpIMSRw6MgY+G6o2kgdGnhur9uICBwcmlvciBtw7NuZyBuZ+G7sWEgY8ahIGLhuqNuIG7DoHkgdGjDoG5oIG3hu5l0IGThuqFuZyBt4bubaSwgdHJvbmcgxJHDsyBUYXUgduG6q24gxJHGsOG7o2MgeMOhYyDEkeG7i25oIGLhurFuZyBwaMOibiBi4buRIENhdWNoeSgwLDEpICwgdHV5IG5oacOqbiB0aGFtIHPhu5EgTGFtYmRhIMSRxrDhu6NjIHjDoWMgxJHhu4tuaCBwaOG7pSB0aHXhu5ljIHbDoG8gVGF1IHbDoCBt4buZdCBo4bqxbmcgc+G7kSBjIChn4buNaSBsw6Agc2xhYikgOg0KDQokJFxiZXRhX2ogXHNpbXt9IE4oMCwgXHRhdV4yXHRpbGRle1xsYW1iZGFfan1eMikkJA0KDQokJFx0YXUgXHNpbXt9IGNhdWNoeSgwLDEpJCQNCg0KDQokJFx0aWxkZXtcbGFtYmRhX2p9XjIgPVxmcmFje2NeMlxsYW1iZGFfal4yfXtjXjIrXHRhdV4yXGxhbWJkYV9qXjJ9JCQNCg0KJCRcbGFtYmRhX2ogXHNpbXt9IENhdWNoeSgwLDEpJCQNCg0KVuG7m2kgbmjhu69uZyBiaeG6v24gc+G7kSAocHJlZGljdG9yKSBjw7MgaGnhu4d1IOG7qW5nIG3huqFuaCwgdGEgY8OzIDoNCg0KJCRcdGF1XjJcbGFtYmRhX2peMiBcZ2cgY14yJCQNCg0KS2hpIMSRw7MsIHByaW9yIHPhur0geOG6pXAgeOG7iSA6DQoNCiQkTigwLGNeMikkJA0KDQpLaGkgxJHDsyBiZXRhIHPhur0ga2jDtG5nIGLhu4sgcGjhuqF0IG5oaeG7gXUsIHRyw6FpIGzhuqFpIG5o4buvbmcgYmnhur9uIHPhu5EgY8OzIGhp4buHdSDhu6luZyB54bq/dSAoYmFvIGfhu5NtIGPhuqMgdHLGsOG7nW5nIGjhu6NwIGPDsyBxdcOhIMOtdCBk4buvIGxp4buHdSkgLCB0YSBjw7M6DQoNCiQkXHRhdV4yXGxhbWJkYV9qXjIgXGxsIGNeMiQkDQpMw7pjIG7DoHkgIGfDrWEgdHLhu4sgYmV0YSBz4bq9IGLhu4sgxJHhuqd5IHbhu4EgZ+G6p24gemVybyB0aGVvIHF1eSBsdeG6rXQgY2h1bmcgY+G7p2EgcHJpb3IgbcOzbmcgbmfhu7FhIA0KDQojIFRow60gZOG7pSBtaW5oIGjhu41hIA0KDQpUcm9uZyB0aMOtIGThu6UgbWluaCBo4buNYSBuw6B5LCBOaGkgc+G7rSBk4bulbmcgYuG7mSBk4buvIGxp4buHdSDCqyBCb2R5IEZhdCDCuyBj4bunYSBSb2dlciBXLiBKb2huc29uIHbDoCBDYXJsZXRvbiBDb2xsZWdlLCBjw7MgdGjhu4MgdOG6o2kgdOG7qzoNCg0KPGh0dHBzOi8vd3cyLmFtc3RhdC5vcmcvcHVibGljYXRpb25zL2pzZS92NG4xL2RhdGFzZXRzLmpvaG5zb24uaHRtbD4NCg0KTeG7pWMgdGnDqnUgY+G7p2EgYsOgaSB0b8OhbiBsw6AgeMOieSBk4buxbmcgbeG7mXQgbcO0IGjDrG5oIGjhu5NpIHF1eSB0dXnhur9uIHTDrW5oIG5o4bqxbSDGsOG7m2MgdMOtbmggbMaw4bujbmcgbeG7oSB0cm9uZyBjxqEgdGjhu4MgKGJp4bq/biBCcm96ZWsgQm9keSBGYXQpIGThu7FhIHbDoG8gVHXhu5VpLCB0cuG7jW5nIGzGsOG7o25nLCBjaGnhu4F1IGNhbywgdsOgIGPDoWMgY2jhu4kgc+G7kSBuaMOibiB0cuG6r2MgKGLhu6VuZywgbmfhu7FjLCBj4buVIHRheSzigKYpLiBCw6BpIHRvw6FuIGPFqW5nIGPDsyB0aOG7gyDEkcaw4bujYyDEkeG6t3QgcmEgdGhlbyBoxrDhu5tuZyBkaeG7hW4gZOG7i2NoIHbhu5tpIG3hu6VjIHRpw6p1IGto4bqjbyBzw6F0IG3hu5FpIHTGsMahbmcgcXVhbiBi4buZIHBo4bqtbiBnaeG7r2EgbeG7l2kgY2jhu4kgc+G7kSBuaMOibiB0cuG6r2MgdsOgIGdpw6EgdHLhu4sgQm9keV9mYXQuDQoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KZGY9cmVhZC50YWJsZSgiaHR0cHM6Ly93dzIuYW1zdGF0Lm9yZy9wdWJsaWNhdGlvbnMvanNlL2RhdGFzZXRzL2ZhdC5kYXQudHh0IikNCg0KZGY9IGRmWyxjKDIsIDU6NywgMTA6MTkpXQ0KDQpuYW1lcyhkZik9YygiYnJvemVrX2ZhdCIsImFnZSIsIndlaWdodCIsImhlaWdodCIsIm5lY2siLCJjaGVzdCIsImFiZG9tZW4iLCJoaXAiLCJ0aGlnaCIsImtuZWUiLCJhbmtsZSIsImJpY2VwcyIsImZvcmVhcm0iLCJ3cmlzdCIpDQpkZj0gZGZbLTQyLF0NCg0KaGVhZChkZiklPiVrbml0cjo6a2FibGUoKQ0KYGBgDQoNCkPDsyDEkeG6v24gMTMgcHJlZGljdG9ycyB0cm9uZyBk4buvIGxp4buHdS4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQoNCmNvcl9tYXQ9YXMubWF0cml4KGNvcihtZXRob2Q9InBlYXJzb24iLGFzLm1hdHJpeChkZikpKQ0KDQpjb3JfbWF0JT4lY29ycnBsb3QoLixvcmRlcj0iaGNsdXN0Iix0eXBlPSJsb3dlciIsbWV0aG9kPSJjb2xvciIsdGwuY29sPSJibGFjayIsIHRsLnNydD00NSx0bC5jZXg9MC41LGNvbD1yZXYoYnJld2VyLnBhbChuPTEwLCBuYW1lPSJSZEJ1IikpKQ0KYGBgDQoNCk5oaSBz4bq9IGThu7FuZyBtw7QgaMOsbmggaOG7k2kgcXV5IHR1eeG6v24gdMOtbmggY8OzIGvDqG0gaGnhu4d1IGNo4buJbmggKHJlZ3VsYXJpc2F0aW9uKSB0aGFtIHPhu5EgaOG7k2kgcXV5IGNobyBt4buXaSBwcmVkaWN0b3IgxJHhu4MgcGjDom4gbOG6rXAgbmjhu69uZyBiaeG6v24gY8OzIGhp4buHdSDhu6luZyBt4bqhbmggbmjhuqV0IChxdWFuIHRy4buNbmcgbmjhuqV0ICkgdsOgICBuaOG7r25nIGJp4bq/biBjw7Mg4bqjbmggaMaw4bufbmcgeeG6v3UgaG/hurdjIGtow7RuZyBjw7MgdmFpIHRyw7IgxJHDs25nIGfDs3AgxJHDoW5nIGvhu4MuDQoNCjggcGjGsMahbmcgcGjDoXAgc+G6vSDEkcaw4bujYyBzbyBzw6FuaDogQsOqbiBwaMOhaSBmcmVxdWVudGlzdCwgxJHhuqd1IHRpw6puIGzDoCBt4buZdCBtw7QgaMOsbmggaOG7k2kgcXV5IHR1eeG6v24gdMOtbmggxJFhIGJp4bq/biBraMO0bmcgY8OzIGhp4buHdSBjaOG7iW5oIGfDrCBj4bqjLCDEkcaw4bujYyBk4buxbmcgYuG6sW5nIGjDoG0gR0xNLCBzYXUgxJHDsyBOaGkgc+G6vSBs4bqnbiBsxrDhu6N0IHRo4butIDogaOG7k2kgcXV5IFJpZGdlLCBo4buTaSBxdXkgTGFzc28gdsOgIEVsYXN0aWMgbmV0IGLhurFuZyBwYWNrYWdlIGdsbW5ldCAoRnJpZWRtYW4sIFRyZXZvciBIYXN0aWUgdsOgIFJvYiBUaWJzaGlyYW5pLCAyMDEzKS4gDQpCw6puIHBow6FpIEJheWVzLCBOaGkgc+G6vSDDoXAgZOG7pW5nIHBoxrDGoW5nIHBow6FwIEJNQSB24bubaSBwYWNrYWdlIEJNUywgdsOgIHByaW9yIGjDrG5oIG3Ds25nIG5n4buxYSBj4bqjaSB0aeG6v24gY+G7p2EgUGlpcm9uZW4gKDIwMTcpIMSRxrDhu6NjIHRo4buxYyBoaeG7h24gYuG6sW5nIDMgY8OhY2gga2jDoWMgbmhhdTogY29kZSB0aOG7pyBjw7RuZyBi4bqxbmcgU1RBTiwgY8WpbmcgbmjGsCB0aMO0bmcgcXVhIGdpYW8gdGjhu6ljIHJzdGFuYXJtIHbDoCBicm1zLg0KDQojIE3DtCBow6xuaCBHTE0ga2jDtG5nIMOhcCBk4bulbmcgcmVndWxhcmlzYXRpb24NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQojIEdMTQ0KDQpmaXRfZ2xtPWdsbShkYXRhPWRmLGJyb3pla19mYXR+LikNCg0Kc3VtbWFyeShmaXRfZ2xtKQ0KYGBgDQoNCiMgQuG7mSBiYSBMYXNzby1SaWRnZS1FbGFzdGljbmV0DQoNCsSQ4bqndSB0acOqbiBsw6AgbcO0IGjDrG5oIExBU1NPDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQojIExhc3NvDQoNCmxpYnJhcnkoZ2xtbmV0KQ0KDQpsYXNzbyA9IGN2LmdsbW5ldCh4PWFzLm1hdHJpeChkZlssLTFdKSx5PWRmJGJyb3pla19mYXQsYWxwaGE9MSxuZm9sZHM9NSkNCg0KY29lZihsYXNzbykNCmBgYA0KDQpTYXUgxJHDsyBsw6AgbcO0IGjDrG5oIFJpZGdlDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KcmlkZ2UgPSBjdi5nbG1uZXQoeD1hcy5tYXRyaXgoZGZbLC0xXSkseT1kZiRicm96ZWtfZmF0LGFscGhhPTAsbmZvbGRzPTUpDQoNCmNvZWYocmlkZ2UpDQoNCmBgYA0KDQpDdeG7kWkgY8O5bmcgbMOgIEVsYXN0aWMgbmV0DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZW5ldD0gY3YuZ2xtbmV0KHg9YXMubWF0cml4KGRmWywtMV0pLHk9ZGYkYnJvemVrX2ZhdCxuZm9sZHM9NSkNCg0KY29lZihlbmV0KQ0KDQpgYGANCg0KTmjGsCB0YSB0aOG6pXksIG3DtCBow6xuaCBMYXNzbyB2w6AgZWxhc3RpY25ldCBjw7Mga2h1eW5oIGjGsOG7m25nIHRyaeG7h3QgdGnDqnUgaOG6s24gbmjhu69uZyBiaeG6v24gc+G7kSDEkcaw4bujYyBjaG8gbMOgIHnhur91IGto4buPaSBtw7QgaMOsbmgsIHRyb25nIGtoaSByaWRnZSByZWdyZXNzaW9uIGNo4buJIGdp4bqjbSBnw61hIHRy4buLIHRoYW0gc+G7kSBo4buTaSBxdXkgY2hvIG5o4buvbmcgYmnhur9uIG7DoHkgeHXhu5FuZyB0aOG6pXAgbmjhuqV0IGPDsyB0aOG7gy4NCg0KIyBQaMawxqFuZyBwaMOhcCBCTUENCg0KVHJvbmcga2hpIMSRw7MsIHBoxrDGoW5nIHBow6FwIEJheWVzaWFuIE1vZGVsIGF2ZXJhZ2luZyBraeG7g20gdHJhIHThuqV0IGPhuqMgbmjhu69uZyB04buVIGjhu6NwIGPDsyB0aOG7gyBnaeG7r2EgMTMgYmnhur9uIHbDoCB4w6FjIMSR4buLbmggbmjhu69uZyBtw7QgaMOsbmggdOG7kWkgxrB1IG5o4bqldC4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCmxpYnJhcnkoQk1TKQ0KDQpibXNtb2Q9IGJtcyhkZiwgYnVybiA9IDEwMDAsIA0KICAgICAgICAgICAgaXRlciA9IDEwMDAwLCANCiAgICAgICAgICAgIGcgPSAiRUJMIiwNCiAgICAgICAgICAgIG1wcmlvciA9ICJ1bmlmb3JtIiwNCiAgICAgICAgICAgIG1jbWM9ImJkbiIsIA0KICAgICAgICAgICAgdXNlci5pbnQgPSBGKQ0KDQpjb2VmKGJtc21vZCwgc3RkLmNvZWZzID0gVCwgb3JkZXIuYnkucGlwID0gVCwgaW5jbHVkZS5jb25zdGFudCA9IFQpDQoNCmltYWdlKGJtc21vZCxjb2w9YygiIzE5OWFjMSIsIiNlMzBjMzciKSkNCg0Kc3VtbWFyeShibXNtb2QpDQoNCmBgYA0KDQojIENvZGUgbcO0IGjDrG5oIHRo4bunIGPDtG5nIHbhu5tpIFNUQU4NCg0KVHJvbmcgYsOgaSBiw6FvIGfhu5FjLCAyIHTDoWMgZ2nhuqMgxJHDoyBjdW5nIGPhuqVwIDIgYuG7mSBTVEFOIGNvZGUgdmnhur90IHPhurVuIGNobyBtw7QgaMOsbmggaOG7k2kgcXV5IHR1eeG6v24gdMOtbmggR2F1c3NpYW4gKG5oxrAgdGjDrSBk4bulIG7DoHkpIHbDoCBsb2dpc3RpYyBz4butIGThu6VuZyBwaMOibiBi4buRIGJlcm5vdWxsaSAsIE5oaSBjaOG7iSB2aeG7h2MgZMO5bmcgbOG6oWkgY29kZSBj4bunYSBo4buNLg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiMgU3RhbiBjb2RlDQoNCnN0YW5fY29kZT0iDQovLyBkYXRhIGJsb2NrIGJlZ2lucyBoZXJlDQpkYXRhIHsNCmludCA8bG93ZXI9MD4gbjsgICAgICAgICAgICAgICAgLy8gbnVtYmVyICBvZiAgaW5zdGFuY2VzDQppbnQgPGxvd2VyPTA+IGQ7ICAgICAgICAgICAgICAgLy8gbnVtYmVyICBvZiAgcHJlZGljdG9ycw0KdmVjdG9yW25dIHk7ICAgICAgICAgICAgICAgICAgIC8vbnVtZXJpYyBvdXRjb21lDQptYXRyaXhbbixkXSB4OyAgICAgICAgICAgICAgICAgLy8gIERlc2lnbiBtYXRyaXggDQpyZWFsIDxsb3dlcj0wPiAgc2NhbGVfaWNlcHQ7ICAgLy8gcHJpb3Igc2NhbGUgcGFyYW1ldGVyIGZvciAgdGhlICBpbnRlcmNlcHQNCnJlYWwgPGxvd2VyPTA+ICBzY2FsZV9nbG9iYWw7IC8vIHNjYWxlICBmb3IgIHRoZSBoYWxmLXQgcHJpb3IgIGZvciAgdGF1DQpyZWFsIDxsb3dlcj0xPiAgbnVfZ2xvYmFsOyAgICAgLy8gZGVncmVlcyAgb2YgIGZyZWVkb20gIGZvciAgdGhlIGhhbGYgLXQgcHJpb3JzICBmb3IgIHRhdQ0KcmVhbCA8bG93ZXI9MT4gbnVfbG9jYWw7ICAgICAgLy8gZGVncmVlcyAgb2YgIGZyZWVkb20gIGZvciAgdGhlIGhhbGYgLXQgcHJpb3JzICBmb3IgIGxhbWJkYXMNCnJlYWwgPGxvd2VyPTA+ICBzbGFiX3NjYWxlOyAgICAvLyBzbGFiICBzY2FsZSAgZm9yICB0aGUgIHJlZ3VsYXJpemVkICBob3JzZXNob2UNCnJlYWwgPGxvd2VyPTA+IHNsYWJfZGY7ICAgICAgIC8vIHNsYWIgIGRlZ3JlZXMgIG9mICBmcmVlZG9tICBmb3IgdGhlICByZWd1bGFyaXplZCAgaG9yc2VzaG9lDQp9ICAgLy8gZGF0YSBibG9jayBlbmRzIGhlcmUNCg0KLy8gUGFyYW1ldGVyIGJsb2NrcyBiZWdpbnMgaGVyZQ0KDQpwYXJhbWV0ZXJzIHsNCnJlYWwgIGxvZ3NpZ21hOw0KcmVhbCAgYmV0YTA7DQp2ZWN0b3JbZF0gejsNCnJlYWwgPGxvd2VyPTA+ICBhdXgxX2dsb2JhbDsNCnJlYWwgPGxvd2VyPTA+ICBhdXgyX2dsb2JhbDsNCnZlY3RvciA8bG93ZXIgPTA+W2RdIGF1eDFfbG9jYWw7DQp2ZWN0b3IgPGxvd2VyID0wPltkXSBhdXgyX2xvY2FsOw0KcmVhbCA8bG93ZXI9MD4gY2F1eDsNCn0gIA0KDQp0cmFuc2Zvcm1lZCAgcGFyYW1ldGVycyB7DQpyZWFsIDxsb3dlcj0wPiBzaWdtYTsgICAgICAgICAgICAgICAgIC8vIG5vaXNlICBzY2FsZSBwYXJhbWV0ZXINCnJlYWwgPGxvd2VyPTA+IHRhdTsgICAgICAgICAgICAgICAgICAgLy8gZ2xvYmFsICBzaHJpbmthZ2UgIHBhcmFtZXRlcg0KdmVjdG9yIDxsb3dlciA9MD5bZF0gbGFtYmRhOyAgICAgICAgICAvLyBsb2NhbCAgc2hyaW5rYWdlICBwYXJhbWV0ZXINCnZlY3RvciA8bG93ZXIgPTA+W2RdIGxhbWJkYV90aWxkZTsgICAvLyDigJl0cnVuY2F0ZWQg4oCZIGxvY2FsICBzaHJpbmthZ2UgIHBhcmFtZXRlcg0KcmVhbCA8bG93ZXI9MD4gYzsgICAgICAgICAgICAgICAgICAgICAgLy8gc2xhYiAgc2NhbGUNCnZlY3RvcltkXSBiZXRhOyAgICAgICAgICAgICAgICAgICAgICAgLy8gcmVncmVzc2lvbiAgY29lZmZpY2llbnRzDQp2ZWN0b3Jbbl0gZjsgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBsYXRlbnQgIGZ1bmN0aW9uICB2YWx1ZXMNCnNpZ21hID0gZXhwKGxvZ3NpZ21hICk7DQpsYW1iZGEgPSBhdXgxX2xvY2FsICAuKiBzcXJ0KGF1eDJfbG9jYWwgKTsNCnRhdSA9IGF1eDFfZ2xvYmFsICogc3FydChhdXgyX2dsb2JhbCkgKiBzY2FsZV9nbG9iYWwqc2lnbWE7DQpjID0gc2xhYl9zY2FsZSAqIHNxcnQoY2F1eCk7DQpsYW1iZGFfdGlsZGUgPSBzcXJ0KCBjXjIgKiBzcXVhcmUobGFtYmRhKSAuLyAoY14yICsgdGF1XjIqIHNxdWFyZShsYW1iZGEgKSkgKTsNCmJldGEgPSB6IC4qICBsYW1iZGFfdGlsZGUqdGF1Ow0KZiA9IGJldGEwICsgeCpiZXRhOw0KfQ0KLy8gUGFyYW1ldGVyIGJsb2NrcyBlbmRzIGhlcmUNCg0KbW9kZWwgew0KLy8gaGFsZiAtdCBwcmlvcnMgIGZvciAgbGFtYmRhcyAgYW5kIHRhdSAsIGFuZCAgaW52ZXJzZSAtZ2FtbWEgIGZvciBjXjINCnp+bm9ybWFsKDAsIDEpOw0KDQphdXgxX2xvY2Fs4oi8bm9ybWFsKDAsIDEpOw0KDQphdXgyX2xvY2Fs4oi8aW52X2dhbW1hICgwLjUqIG51X2xvY2FsICwgMC41KiBudV9sb2NhbCApOw0KDQphdXgxX2dsb2JhbOKIvG5vcm1hbCgwLCAxKTsNCg0KYXV4Ml9nbG9iYWziiLxpbnZfZ2FtbWEgKDAuNSogbnVfZ2xvYmFsICwgMC41KiBudV9nbG9iYWwgKTsNCg0KY2F1eH5pbnZfZ2FtbWEgKDAuNSogc2xhYl9kZiAsIDAuNSogc2xhYl9kZiApOw0KDQpiZXRhMH5ub3JtYWwoMCxzY2FsZV9pY2VwdCApOw0KDQp5fm5vcm1hbChmLCBzaWdtYSk7DQoNCn0iDQoNCmBgYA0KDQpTYXUgxJHDsyB04bqhbyBk4buvIGxp4buHdSBjaG8gbcO0IGjDrG5oIFNUQU4gbsOgeSwgbGlzdCBk4buvIGxp4buHdSDEkeG6p3UgdsOgbyBn4buTbSBjw7M6DQoNCjEpIERlc2lnbiBtYXRyaXggWG1hdCAoYmFvIGfhu5NtIEludGVyY2VwdCkgdsOgIHZlY3RvciBSZXNwb25zZQ0KDQoyKSBLaGFpIGLDoW8gY8OhYyBnacOhIHRy4buLIHRoYW0gc+G7kTogcDA9IHPhu5EgYmnhur9uIGThu7Ega2nhur9uIGPDsyBoaeG7h3Ug4bupbmcgbeG6oW5oLCB0aMOtIGThu6UgNDsNCmQ9IHPhu5EgYmnhur9uIHRyb25nIGRlc2lnbiBtYXRyaXggPSAxNDsNCm49IGPhu6EgbeG6q3U7DQpnacOhIHRy4buLIHNjYWxlX2dsb2JhbCDEkcaw4bujYyDGsOG7m2MgdMOtbmggYuG6sW5nIGPDtG5nIHRo4bupYyA7DQpjw6FjIHRoYW0gc+G7kSBjaG8gcHJpb3Iga2jDoWMgbmjGsCBudV9nbG9iYWw9MSwgbnVfbG9jYWw9MSwgc2NhbGVfaWNlcHQ9MTAwIChjaG8gYmV0YSBpbnRlcmNlcHQpLCBzbGFiX3NjYWxlPTIgKGjhurFuZyBz4buRIGMpLCDEkeG7mSB04buxIGRvIGNobyBzbGFiID0gDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KUmVzcG9uc2U9ZGYkYnJvemVrX2ZhdA0KDQpYbWF0IDwtIG1vZGVsLm1hdHJpeCh+LixkZlssLTFdKQ0KDQpwMCA9IDQNCmQgPSBuY29sKFhtYXQpDQpuID0gbnJvdyhkZikNCg0Kc2NhbGVfZ2xvYmFsID0gcDAvKG5jb2woWG1hdCktcDAtMSkvc3FydChuKQ0KDQpkYXRhX2xpc3QgPC0gbGlzdCh5ID0gUmVzcG9uc2UsIA0KICAgICAgICAgICAgICAgICAgZCA9IGQsDQogICAgICAgICAgICAgICAgICBuID0gbiwNCiAgICAgICAgICAgICAgICAgIHg9WG1hdCwNCiAgICAgICAgICAgICAgICAgIHNjYWxlX2ljZXB0ID0gMTAwLA0KICAgICAgICAgICAgICAgICAgc2NhbGVfZ2xvYmFsID0gc2NhbGVfZ2xvYmFsLA0KICAgICAgICAgICAgICAgICAgbnVfZ2xvYmFsID0gMSwNCiAgICAgICAgICAgICAgICAgIG51X2xvY2FsID0gMSwgDQogICAgICAgICAgICAgICAgICBzbGFiX3NjYWxlID0gMiwNCiAgICAgICAgICAgICAgICAgIHNsYWJfZGY9NCkNCmBgYA0KDQpTYXUgxJHDsyB0YSBjb21waWxlIGNvZGUgbcO0IGjDrG5oIHbDoG8gc2FtcGxlciBxdWEgaMOgbSBzdGFuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShyc3RhbikNCg0KcnN0YW5fb3B0aW9ucyhhdXRvX3dyaXRlID0gVFJVRSkNCm9wdGlvbnMobWMuY29yZXMgPSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSkNCg0Kc2V0LnNlZWQoMTIzKQ0KDQpmaXRfc3Rhbj0gc3RhbihkYXRhID0gZGF0YV9saXN0LCANCiAgICAgICAgICBtb2RlbF9jb2RlID0gc3Rhbl9jb2RlICwgDQogICAgICAgICAgY2hhaW5zID0gMSwNCiAgICAgICAgICBpdGVyID0gMjUwMCwgDQogICAgICAgICAgd2FybXVwID0gNTAwLCANCiAgICAgICAgICB0aGluID0gMiwNCiAgICAgICAgICBjb250cm9sPWxpc3QoYWRhcHRfZGVsdGE9MC45NSxtYXhfdHJlZWRlcHRoPTIwKQ0KKQ0KYGBgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShicm9vbSkNCg0KdGlkeU1DTUMoZml0X3N0YW4sIGNvbmYuaW50ID0gVFJVRSwgY29uZi5tZXRob2QgPSAiSFBEaW50ZXJ2YWwiLCBwYXJzID0gImJldGEiKS0+YmV0YV9zdGFuDQoNCmJldGFfc3RhbiR0ZXJtPWNvbG5hbWVzKFhtYXQpDQoNCmJldGFfc3Rhbg0KYGBgDQoNCiMgUGFja2FnZSByc3RhbmFybQ0KDQpOZ2/DoGkgY8OhY2ggdmnhur90IGNvZGUgdGjhu6cgY8O0bmcsIHBhY2thZ2UgcnN0YW5hcm0gbMOgIG3hu5l0IGdpYW8gdGjhu6ljIHRp4buHbiBk4bulbmcgaMahbiDEkeG7gyBkw7luZyBzYW1wbGVyIFNUQU4uIHJzdGFuYXJtIGjhu5cgdHLhu6MgcHJpb3IgUGlpcm9uZW4gdGjDtG5nIHF1YSBow6BtIGhzKCkgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KIyByc3RhbmFybQ0KDQpsaWJyYXJ5KHJzdGFuYXJtKQ0KDQpuID0gbnJvdyhkZikNCm5YID0gMTMNCnAwID0gNA0KZ2xvYmFsX3NjYWxlID0gcDAvKG5YIC0gcDApL3NxcnQobikNCg0KZml0X3N0YW5hcm0gPSBzdGFuX2dsbShicm96ZWtfZmF0fiAuLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGYsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZXI9MjUwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3YXJtdXA9NTAwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGFpbnMgPSAxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGluID0gMiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVmcmVzaCA9IDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpb3JfaW50ZXJjZXB0ID0gbm9ybWFsKDAsMTAwKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpb3IgPSBocyhkZiA9IDQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2xvYmFsX2RmID0gMSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnbG9iYWxfc2NhbGUgPSBnbG9iYWxfc2NhbGUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW9yX2F1eCA9IGNhdWNoeSgwLDIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2w9bGlzdChhZGFwdF9kZWx0YT0wLjk1LG1heF90cmVlZGVwdGg9MjApDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KYGBgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdGlkeU1DTUMoZml0X3N0YW5hcm0kc3RhbmZpdCxjb25mLmludCA9IFRSVUUsIA0KICAgICAgICAgY29uZi5tZXRob2QgPSAiSFBEaW50ZXJ2YWwiKS0+YmV0YV9zdGFuYXJtDQoNCmJldGFfc3RhbmFybQ0KYGBgDQoNCiMgUGFja2FnZSBicm1zDQoNCkN14buRaSBjw7luZywgYnJtcyBsw6AgMSBnaWFvIHRo4bupYyBraMOhYyBj4bunYSBzYW1wbGVyIFNUQU4sIGJybXMgbeG6oW5oIGjGoW4gcnN0YW5hcm0gdsOsIGjhu5cgdHLhu6Mgbmhp4buBdSBk4bqhbmcgbcO0IGjDrG5oIGjGoW4sIHR1eSBuaGnDqm4gY8O6IHBow6FwIGtoYWkgYsOhbyBwcmlvciBQaWlyb25lbiB0cm9uZyBicm1zIHBo4bupYyB04bqhcCBoxqFuIHJzdGFuYXJtICBt4buZdCBjaMO6dC4gQ2hvIG3DtCBow6xuaCBHYXVzc2lhbiwgYnJtcyBz4butIGThu6VuZyAzIGNsYXNzIHByaW9yIGzDoCBJbnRlcmNlcHQsIGJldGEgKGIpIHbDoCBzaWdtYQ0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiMgYnJtcw0KDQpuWCA9IDEzDQpwMCA9IDQNCnBhcl9yYXRpbyA9IHAwLyhuWCAtIHAwKQ0KDQpsaWJyYXJ5KGJybXMpDQoNCmZpdF9icm1zPSBicm0oYnJvemVrX2ZhdH4gLiwgDQogICAgICAgICAgICAgIGRhdGEgPSBkZiwNCiAgICAgICAgICAgICAgaXRlciA9IDI1MDAsIA0KICAgICAgICAgICAgICB3YXJtdXAgPSA1MDAsDQogICAgICAgICAgICAgIGNoYWlucyA9IDEsIA0KICAgICAgICAgICAgICB0aGluID0gMiwNCiAgICAgICAgICAgICAgcmVmcmVzaCA9IDAsDQogICAgICAgICAgICAgIHByaW9yID0gYyhwcmlvcihub3JtYWwoMCwgMTAwKSxjbGFzcyA9ICJJbnRlcmNlcHQiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICBwcmlvcihob3JzZXNob2UoZGYgPSA0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcl9yYXRpbyA9IHBhcl9yYXRpbyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcyA9ICJiIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgcHJpb3IoY2F1Y2h5KDAsIDUpLGNsYXNzID0gInNpZ21hIikNCiAgICAgICAgICAgICAgICAgICAgICAgICksDQogICAgICAgICAgICAgIGNvbnRyb2w9bGlzdChhZGFwdF9kZWx0YT0wLjk1LG1heF90cmVlZGVwdGg9MjApDQogICAgICAgICAgICAgICkNCmBgYA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnRpZHlNQ01DKGZpdF9icm1zJGZpdCxjb25mLmludCA9IFRSVUUsIA0KICAgICAgICAgY29uZi5tZXRob2QgPSAiSFBEaW50ZXJ2YWwiKS0+YmV0YV9icm1zDQoNCmJldGFfYnJtcw0KDQpiZXRhX2JybXMkdGVybT1jKGNvbG5hbWVzKFhtYXQpLCJzaWdtYSIsIm1lYW5fUFBEIikNCg0KYGBgDQoNCiMgU28gc8OhbmggZ2nDoSB0cuG7iyB0aGFtIHPhu5EgYmV0YSBj4bunYSBjw6FjIG3DtCBow6xuaA0KDQpUYSBjw7MgdGjhu4MgdHLDrWNoIHh14bqldCBnacOhIHRy4buLIGPhu6dhIDE0IHRoYW0gc+G7kSBo4buTaSBxdXkgYmV0YSB0cm9uZyA4IG3DtCBow6xuaCDEkcOjIGThu7FuZyB2w6Agc28gc8OhbmggY2jDum5nIHbhu5tpIG5oYXUgbeG7mXQgY8OhY2ggdHLhu7FjIHF1YW4gYuG6sW5nIGhlYXRtYXANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQojIENvZWZmaWNpZW50DQoNCmdsbV9kYXQ9ZGF0YV9mcmFtZShUZXJtPWZpdF9nbG0kY29lZmZpY2llbnRzJT4lbmFtZXMoKSwNCiAgICAgICAgICAgICAgICAgICBCZXRhcz1maXRfZ2xtJGNvZWZmaWNpZW50cyU+JWFzLm51bWVyaWMoKSwNCiAgICAgICAgICAgICAgICAgICBNb2RlbD0iR0xNIikNCg0KbGFzc29fZGF0PWRhdGFfZnJhbWUoVGVybT1jb2VmKGxhc3NvKSU+JWFzLm1hdHJpeCgpJT4lcm93bmFtZXMoKSwNCiAgICAgICAgICAgICAgICAgICBCZXRhcz1jb2VmKGxhc3NvKSU+JWFzLm51bWVyaWMoKSwNCiAgICAgICAgICAgICAgICAgICBNb2RlbD0iTEFTU08iKQ0KDQplbmV0X2RhdD1kYXRhX2ZyYW1lKFRlcm09Y29lZihlbmV0KSU+JWFzLm1hdHJpeCgpJT4lcm93bmFtZXMoKSwNCiAgICAgICAgICAgICAgICAgICAgIEJldGFzPWNvZWYoZW5ldCklPiVhcy5udW1lcmljKCksDQogICAgICAgICAgICAgICAgICAgICBNb2RlbD0iRU5FVCIpDQoNCnJpZGdlX2RhdD1kYXRhX2ZyYW1lKFRlcm09Y29lZihyaWRnZSklPiVhcy5tYXRyaXgoKSU+JXJvd25hbWVzKCksDQogICAgICAgICAgICAgICAgICAgIEJldGFzPWNvZWYocmlkZ2UpJT4lYXMubnVtZXJpYygpLA0KICAgICAgICAgICAgICAgICAgICBNb2RlbD0iUklER0UiKQ0KDQpjb2VmX2Jtcz1jb2VmKGJtc21vZCwgc3RkLmNvZWZzID0gVCwgb3JkZXIuYnkucGlwID0gVCwgaW5jbHVkZS5jb25zdGFudCA9IFQpJT4lYXMubWF0cml4KCkNCg0KYm1hX2RhdD1kYXRhX2ZyYW1lKFRlcm09Y29lZl9ibXMlPiVyb3duYW1lcygpLA0KICAgICAgICAgICAgICAgICAgICAgQmV0YXM9Y29lZl9ibXNbLDNdJT4lYXMubnVtZXJpYygpLA0KICAgICAgICAgICAgICAgICAgICAgTW9kZWw9IkJNQSIpDQoNCmJybXNfZGF0PWRhdGFfZnJhbWUoVGVybT1iZXRhX2JybXNbMToxNCxdJT4lLiR0ZXJtLA0KICAgICAgICAgICAgICAgICAgIEJldGFzPWJldGFfYnJtc1sxOjE0LF0lPiUuJGVzdGltYXRlLA0KICAgICAgICAgICAgICAgICAgIE1vZGVsPSJCUk1TIikNCg0Kc3RhbmFybV9kYXQ9ZGF0YV9mcmFtZShUZXJtPWJldGFfc3RhbmFybVsxOjE0LF0lPiUuJHRlcm0sDQogICAgICAgICAgICAgICAgICAgIEJldGFzPWJldGFfc3RhbmFybVsxOjE0LF0lPiUuJGVzdGltYXRlLA0KICAgICAgICAgICAgICAgICAgICBNb2RlbD0iU1RBTkFSTSIpDQoNCnN0YW5fZGF0PWRhdGFfZnJhbWUoVGVybT1iZXRhX3N0YW5bMToxNCxdJT4lLiR0ZXJtLA0KICAgICAgICAgICAgICAgICAgICAgICBCZXRhcz1iZXRhX3N0YW5bMToxNCxdJT4lLiRlc3RpbWF0ZSwNCiAgICAgICAgICAgICAgICAgICAgICAgTW9kZWw9IlNUQU4iKQ0KDQpjb21wX2RhdD1yYmluZChnbG1fZGF0LGxhc3NvX2RhdCxibWFfZGF0LA0KICAgICAgICAgICAgICAgcmlkZ2VfZGF0LGVuZXRfZGF0LA0KICAgICAgICAgICAgICAgYnJtc19kYXQsc3RhbmFybV9kYXQsc3Rhbl9kYXQpDQoNCmNvbXBfZGF0JT4lZmlsdGVyKFRlcm0hPSIoSW50ZXJjZXB0KSIpJT4lDQogIGdncGxvdChhZXMoeD1yZW9yZGVyKE1vZGVsLEJldGFzKSwNCiAgICAgICAgICAgICB5PXJlb3JkZXIoVGVybSxCZXRhcyksZmlsbD1CZXRhcykpKw0KICBnZW9tX3RpbGUoKSsNCiAgdGhlbWVfYncoKSsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93PSIjMTk5YWMxIixoaWdoPSIjZTMwYzM3IixtaWQ9TkEsbWlkcG9pbnQgPSAwKSsNCiAgc2NhbGVfeF9kaXNjcmV0ZSgpDQoNCmBgYA0KDQpL4bq/dCBxdeG6oyBjaG8gdGjhuqV5IGPhuqMgMyBtw7QgaMOsbmggU1RBbiB0aOG7pyBjw7RuZywgYnJtcyB2w6AgcnN0YW5hcm0gbMOgIHTGsMahbmcgxJHGsMahbmcsIHbDrCB0aOG7sWMgY2jhuqV0IGNow7puZyDEkeG7gXUgZOG7sWEgdsOgbyBsw7VpIGzDoCBTVEFOIHNhbXBsZXIsIHbDoCBjw7luZyBjb2RlIGNobyBow6BtIGxpa2VsaWhvb2QsIGPDuW5nIHByaW9yLiBDw7MgdGjhu4MgZMO5bmcgMSB0cm9uZyAzIG3DtCBow6xuaCDEkeG7gyBzbyBzw6FuaCB24bubaSBuaOG7r25nIG3DtCBow6xuaCBjw7JuIGzhuqFpLg0KDQpQcmlvciBtw7NuZyBuZ+G7sWEgY+G7p2EgUGlpcm9uZW4gZ8OieSByYSBt4buZdCBz4buxIMSRaeG7gXUgY2jhu4luaCBy4bqldCBuaOG6uSBjaG8gdGhhbSBz4buRIGjhu5NpIHF1eSwgdMawxqFuZyDEkcawxqFuZyB24bubaSBSaWRnZSByZWd1bGFyaXNhdGlvbiB2w6Aga2jDtG5nIGtow6FjIGJp4buHdCByw7Ugc28gduG7m2kgbcO0IGjDrG5oIEdMTSBraMO0bmcgY8OzIGhp4buHdSBjaOG7iW5oLiBOw7Mga2jDtG5nIHRyaeG7h3QgdGnDqnUgYmnhur9uIHPhu5EgbmjGsCBMYXNzbyBoYXkgZW5ldC4gQ8OzIHbhursgbmjGsCBjw6FjIHRoYW0gc+G7kSBj4bunYSBwcmlvciDEkcOjIGtow7RuZyDEkcaw4bujYyB04buRaSDGsHUgPyBIb+G6t2MgaGnhu4d1IOG7qW5nIG7DoHkgY2jhu4kgcsO1IHLDoG5nIGjGoW4ga2hpIMOhcCBk4bulbmcgY2hvIGRhdGFzZXQgduG7m2kgcuG6pXQgbmhp4buBdSBiaeG6v24sIHRow60gZOG7pSBuaMawIGRhdGFzZXQgTGV1a2VtaWEgY2jhurNuZyBo4bqhbiA/DQoNCk5o4buvbmcgYmnhur9uIGPDsyBoaeG7h3Ug4bupbmcgbeG6oW5oIHRow6wga+G6v3QgcXXhuqMgxJHhu5NuZyBuaOG6pXQg4bufIGPDoWMgbcO0IGjDrG5oLCB0aMOtIGThu6UgYWJkb21lbiwgd3Jpc3QsIC4uLiANCg0KIyBTbyBzw6FuaCB0cuG7sWMgcXVhbiBr4bq/dCBxdeG6oyBj4bunYSA1IG3DtCBow6xuaCB24bubaSBnacOhIHRy4buLIHRo4buxYyB04bq/DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KcHJlZF9icm1zPWRhdGFfZnJhbWUoUHJlZD1wcmVkaWN0KGZpdF9icm1zLG5ld2RhdGE9ZGYpJT4lYXMubWF0cml4KCklPiUuWywxXSwNCiAgICAgICAgICAgICAgICAgICAgIE1vZGVsPSJQaWlyb25lbiIpDQoNCnByZWRfZW5ldD1kYXRhX2ZyYW1lKFByZWQ9cHJlZGljdChlbmV0LG5ld3g9YXMubWF0cml4KGRmWywtMV0pKSU+JWFzLm51bWVyaWMoKSwNCiAgICAgICAgICAgICAgICAgICAgIE1vZGVsPSJFTkVUIikNCg0KcHJlZF9sYXNzbz1kYXRhX2ZyYW1lKFByZWQ9cHJlZGljdChsYXNzbyxuZXd4PWFzLm1hdHJpeChkZlssLTFdKSklPiVhcy5udW1lcmljKCksDQogICAgICAgICAgICAgICAgICAgICAgTW9kZWw9IkxBU1NPIikNCg0KcHJlZF9yaWRnZT1kYXRhX2ZyYW1lKFByZWQ9cHJlZGljdChyaWRnZSxuZXd4PWFzLm1hdHJpeChkZlssLTFdKSklPiVhcy5udW1lcmljKCksDQogICAgICAgICAgICAgICAgICAgICAgTW9kZWw9IlJJREdFIikNCg0KdHJ1dGg9ZGF0YV9mcmFtZShQcmVkPWRmJGJyb3pla19mYXQsTW9kZWw9IlRydXRoIikNCg0KcHJlZF9nbG09ZGF0YV9mcmFtZShQcmVkPWZpdF9nbG0kZml0dGVkLnZhbHVlcyxNb2RlbD0iR0xNIikNCg0KY29tcF9wcmVkPXJiaW5kKHByZWRfYnJtcyxwcmVkX2VuZXQscHJlZF9nbG0scHJlZF9sYXNzbyxwcmVkX3JpZGdlKQ0KDQpjb21wX3ByZWQlPiVtdXRhdGUoVHJ1dGg9cmVwKGRmJGJyb3pla19mYXQsNSkpJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX3Zpb2xpbihhbHBoYT0wLjcsYWVzKHg9TW9kZWwseT1UcnV0aCxncm91cD1Nb2RlbCksZmlsbD0id2hpdGUiKSsNCiAgZ2VvbV92aW9saW4oYWxwaGE9MC41LGFlcyh4PU1vZGVsLHk9UHJlZCxmaWxsPU1vZGVsLGdyb3VwPU1vZGVsKSxsaW5ldHlwZT0yKSsNCiAgZ2VvbV9qaXR0ZXIoYWxwaGE9MC4yLGFlcyh4PU1vZGVsLHk9UHJlZCxncm91cD1Nb2RlbCksc2hhcGU9MjEsY29sPSJibGFjayIpKw0KICB0aGVtZV9idygpK3NjYWxlX3lfY29udGludW91cygiVmFsdWUiKStjb29yZF9mbGlwKCkNCg0KYGBgDQoNClRyw6puIGNow61uaCB04bqtcCBk4buvIGxp4buHdSBn4buRYywgbcO0IGjDrG5oIEJheWVzIHbhu5tpIHByaW9yIFBpaXJvbmVuIGNobyByYSBr4bq/dCBxdeG6oyB0acOqbiBsxrDhu6NuZyBjw7MgcGjDom4gcGjhu5FpIMSR4buTbmcgZOG6oW5nIHbhu5tpIGdpw6EgdHLhu4sgdGjhu7FjIHThur8sxJHhu5kgY2jDrW5oIHjDoWMgY+G7p2EgbsOzIHTGsMahbmcgxJHGsMahbmcgduG7m2kgbcO0IGjDrG5oIHR1eeG6v24gdMOtbmggY8ahIGLhuqNuIGLhurFuZyBow6BtIGdsbS4gDQoNCiMgS+G6v3QgbHXhuq1uDQoNClRyb25nIGLDoGkgbsOgeSBOaGkgxJHDoyBnaeG7m2kgdGhp4buHdSB24bubaSBjw6FjIGLhuqFuIHRow6ptIDEgcGjGsMahbmcgcGjDoXAgY2jhu41uIGzhu41jIGJp4bq/biBz4buRL23DtCBow6xuaCBu4buvYSwgdsOgIGzhuqduIG7DoHkgbMOgIGNobyBtw7QgaMOsbmggQmF5ZXMgdmnhur90IGLhurFuZyBTVEFOIGNvZGUuIE3hu5l0IGPDtG5nIGPhu6UgUmVndWxhcmlzYXRpb24gY2hvIG3DtCBow6xuaCBCYXllcyBjw7Mgw70gbmdoxKlhIHF1YW4gdHLhu41uZywgdsOsIHRyb25nIGhvw6BuIGPhuqNuaCBk4buvIGxp4buHdSBo4bqhbiBjaOG6vywgbsOzIHPhur0gZ2nDunAgdHLDoW5oIG5ndXkgY8ahIGRp4buFbiBnaeG6o2kgc2FpIHBow6JuIGLhu5EgaOG6rXUgbmdoaeG7h20uDQoNCkPDoWMgYuG6oW4gY8OzIHRo4buDIHThuqNpIGLDoGkgYsOhbyBn4buRYyBj4bunYSBQaWlyb25lbiB2w6AgVmVodGFyaSA6IDxodHRwczovL2FyeGl2Lm9yZy9wZGYvMTcwNy4wMTY5NC5wZGY+LCB0cm9uZyBwaOG6p24gcGjhu6UgbOG7pWMsIHTDoWMgZ2nhuqMgY8OzIGjGsOG7m25nIGThuqtuIDIgY8OhY2ggY29kZSBraMOhYyBuaGF1IGNobyBtw7QgaMOsbmggR2F1c3NpYW4sIHbDoCBjaOG7iSB0aGF5IMSR4buVaSAzIGTDsm5nIGNvZGUgdGjDrCBi4bqhbiDEkcOjIGPDsyB0aOG7gyBsw6BtIG3DtCBow6xuaCBsb2dpc3RpYy4gDQoNCk7hur91IGNoxrBhIHF1ZW4gY29kZSB0aOG7pyBjw7RuZywgYuG6oW4gY8WpbmcgY8OzIHRo4buDIGNo4buNbiBjw6FjaCBkw7luZyBnaWFvIHRo4bupYyBxdWEgc3RhbmFybSB2w6AgYnJtcywgZOG7hSBkw6BuZyB2w6AgbmhhbmggY2jDs25nIGjGoW4u