Giới thiệu: BAYES
Hiện nay, trường phái Bayes ngày càng được phổ biến như một giải pháp thay thế cho những phương pháp thống kê cổ điển dựa theo trường phái frequentist. Thí dụ mô hình hồi quy Bayes thay thế cho phương pháp ước lượng Maximum likelihood. Trường phái Bayes có một vài ưu điểm như:
Cho phép hòa hợp giữa yếu tố tiền định (thí dụ giả thuyết, giả định, thông tin trong y văn) và bằng chứng từ dữ liệu quan sát được trong nghiên cứu, thí nghiệm
Diễn giải kết quả linh hoạt hơn rất nhiều so với Null hypothesis test và p value, không áp đặt nhưng đưa ra chứng cứ, khả năng tin cậy, và cho phép cập nhật kết quả khi có thêm chứng cứ, dữ liệu mới.
- Giản dị và đồng nhất : Những phân tích được diễn tả bằng mô hình, bằng xác suất điều kiện, chứ không còn bị phân tán thành từng mảnh nhỏ là các loại kiểm định .
Vì phương pháp Bayes yêu cầu người học phải giải phẫu mô hình hết sức chi tiết, và hiểu lý thuyết xác suất, để có thể viết code, nên các bạn thực hành Bayes thường có kiến thức vững vàng về thống kê học hơn so với các bạn chỉ dùng phần mềm thống kê theo trường phái Frequentist.
Tiếc rằng, việc ứng dụng rộng rãi phương pháp Bayes vẫn còn gặp nhiều khó khăn, nhất là tại Việt Nam. Chủ yếu là khó khăn về kỹ thuật, vì người thực hành phải có khả năng lập trình, viết code cho các Sampler để đạt được phân phối hậu nghiệm. Các ngôn ngữ và Sampler như BUGs, JAGS hay STAN do đó không mấy phổ biến.
Tuy nhiên, rất may mắn là có một số giao thức đã được phát triển trên nền tảng của JAGs và STAN trong R. Một trong những giao thức đó là rstanarm, Nhi sẽ thực hiện một số tutorial hướng dẫn cách dùng giao thức này để thực hiện các bài toán hồi quy phổ biến. Bài đầu tiên sẽ là Hồi quy Logistic.
STAN và rstanarm package
STAN là một ngôn ngữ lập trình thống kê xác suất,và đã được phát triển cho R, Python và Matlab. STAN cho phép người dùng dựng những mô hình thông qua xác suất có điều kiện (xác định phân phối hậu định của tham số hồi quy theta khi có dữ liệu, hàm likelihood và giả thuyết tiền định), hay còn gọi là trường phái Bayes.
Khi bạn dựng một mô hình trong STAN, nó phải được giải phẫu đến từng chi tiết nhỏ nhất (trường phái Bayes bắt buộc người học phải hiểu chi tiết algorithm, bản chất biến số, các hàm mật độ xác suất). Khi bạn đã có mô hình, thì có thể giải quyết hầu hết những bài toán thống kê, như so sánh, tương quan, hồi quy, tiên lượng… Từ chuỗi MCMC (phân phối hậu định), bạn có thể suy diễn thống kê theo nhiều cách: dùng Bayes factor, ROPE, null hypothesis testing. Mô hình Bayes có thể áp dụng để diễn giải hay tiên lượng đều được, thậm chí nó cho phép ướng lượng phân phối hậu định cho giá trị dự báo, hồi quy phân vị… một cách tự nhiên.
Khi bạn dùng STAN, đầu tiên 1 đoạn code của mô hình sẽ được phiên mã thành một chương trình (executable program) ngôn ngữ C++ (compilation), sau đó nó mới được thi hành. Cơ chế tạo ra chuỗi Markov Monte Carlo của STAN hoàn toàn khác biệt với JAGs và BUGs là 2 sampler có trước. STAN sử dụng phương pháp Hamiltonian Monte Carlo sampling dựa vào lý thuyết của Metropolis-Hastings (1953, 1970), mô phỏng chuyển động bất định của một động tử.
Bạn có thể tiếp cận STAN tại đây:
https://github.com/stan-dev
Trong R, STAN được ứng dụng dựa vào 1 số package chính, trong đó phần lõi là Rcpp, rstan. Dựa trên phần lõi này, ta có những package giao thức như bayesplot, rstanarm, brms… cho phép người dùng có thể dựng được mô hình, làm được suy diễn Bayes mà không cần biết ngôn ngữ STAN. Các giao thức này sử dụng cú pháp quy ước trong R (glm, lm, aov, gam, nlme, survival…) để khai báo mô hình, sau đó dịch sang STAN code, compile vào C++ rồi thi hành. Kết quả xuất ra có thể tương thích với những method quen thuộc, thí dụ ggplot. Đó là lợi thế của các giao thức.
Hồi quy logistic theo BAYES
Một cách tổng quát, suy diễn Bayes dựa trên định lý Bayes như sau :
\[ p(\theta |y,x)\propto p(y|\theta ,x) p(\theta ,x) \] theo đó, phân phối hậu định của một tham số Theta (khi ta có trong tay dữ liệu biến kết quả Y và hằng số X là một matrix các predictors trong mô hình) tỉ lệ với tích của hàm likelihood (xác suất có điều kiện của y khi có theta và x) và phân phối tiền định (prior= giả thuyết về phân phối của theta).
Bài toán dán nhãn phân loại một bệnh nhân với hai khả năng (0 và 1) tương đương với việc ước tính xác suất có y=1 trong n lượt rút thăm ngẫu nhiên từ n phần tử. Xác suất này được mô tả bằng 1 hàm mật độ xác suất (PMF) của phân phối binomial:
\[\binom{n}{y} \pi^{y} (1 - \pi)^{n - y}\]
Với pi là xác suất tìm thấy y=1.
Cho 1 link function g ta có :
\[\pi = g^{-1}(\eta)\]
Cho mô hình logistic, g là 1 hàm logit, hay còn gọi là log-odds
\[g(x) = \ln{\left(\frac{x}{1-x}\right)}\]
Từ đây ta có mô hình hồi quy logistic :
\[\binom{n}{y}\left(\text{logit}^{-1}(\eta)\right)^y
\left(1 - \text{logit}^{-1}(\eta)\right)^{n-y} =
\binom{n}{y} \left(\frac{e^{\eta}}{1 + e^{\eta}}\right)^{y}
\left(\frac{1}{1 + e^{\eta}}\right)^{n - y}\]
Với eta là kết quả ước lượng từ một phương trình tuyến tính có dạng:
\[\eta = \alpha + \mathbf{x}^\top \boldsymbol{\beta}\]
Trong đó alpha là intercept, X là design matrix của mô hình, beta là tham số hồi quy.
Trong trường hợp hồi quy logistic với một dataset có N quan sát, K biến số, và kết quả Y là một biến nhị phân (0,1); phân phối hậu định trong Bayes có thể được viết như sau:
\[f\left(\alpha,\boldsymbol{\beta} | \mathbf{y},\mathbf{X}\right) \propto
f\left(\alpha\right) \times \prod_{k=1}^K f\left(\beta_k\right) \times
\prod_{i=1}^N {
g^{-1}\left(\eta_i\right)^{y_i}
\left(1 - g^{-1}\left(\eta_i\right)\right)^{n_i-y_i}}\]
Với alpha và beta(k) lần lượt là intercept và các tham số hồi quy cho K biến số trong mô hình.
Để xác định phân phối hậu định cho alpha và beta, ta phảcung cấp cho Sampler một khái niệm (giả định) về phân phối của chúng (priors) là f(alpha) và f(beta). Prior này có thể là normal hoặc Student-t.
Dữ liệu minh họa
Để minh họa, Nhi chọn một bài toán Hồi quy Logistic với dataset Biopsy trong package MASS. Dataset này bao gồm 9 chỉ số về đặc tính giải phẫu bệnh lý của mô sinh thiết ở bệnh nhân có khối u Vú, và 1 biến định tính nhị phân là phân loại Lành tính hay ác tính của khối u.
Nếu đặt phân loại Lành/Ác tính như biến kết quả Y được mã hóa bằng 2 con số 0 (lành tính) và 1 (ác tính), việc dựng một mô hình hồi quy cho phép trả lời cả 2 câu hỏi :
Tôi muốn biết vai trò của mỗi đặc tính tế bào học góp phần định nghĩa tính chất U lành hay ác tính ? (bài toán thăm dò /diễn dịch)
Tôi muốn phân loại một mẫu sinh thiết bất kì dựa vào các đặc tính tế bào học (mục tiêu phân loại)
Dataset này đã được thăm dò rất chi tiết trong 1 tutorial trước đây (Gamlss bài 3):
http://rpubs.com/lengockhanhi/294782
Chúng ta chia dữ liệu ra 2 phần: trainset và testset như thường lệ:
library(tidyverse)
df=read.csv("https://raw.github.com/vincentarelbundock/Rdatasets/master/csv/MASS/biopsy.csv")
df=read.csv("http://vincentarelbundock.github.io/Rdatasets/csv/MASS/biopsy.csv")%>%as_tibble()%>%.[,c(3:12)]%>%na.omit()
names(df)=c("clumpthickness",
"SizeUniformity",
"ShapeUniformity",
"Margin_adhesion",
"EpiCellSize",
"Barenuclei",
"BlandChromatin",
"NormalNucleoli",
"Mitoses",
"Class"
)
df2=df%>%mutate(.,Class=as.integer(.$Class)-1L)
library(caret)
set.seed(123)
id=createDataPartition(y=df$Class, p=499/683,list=FALSE)
trainset=df2[id,]
testset=df2[-id,]
Mô hình logistic dùng GLM
Đầu tiên, Nhi dựng một mô hình logistic theo phương pháp quen thuộc trong R là hàm glm:
fml="Class~clumpthickness+
ShapeUniformity+
Margin_adhesion+
Barenuclei+
BlandChromatin"
fitglm=glm(
fml,
data = trainset,
family = binomial(link = "logit")
)
summary(fitglm)
##
## Call:
## glm(formula = fml, family = binomial(link = "logit"), data = trainset)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -3.04325 -0.14356 -0.06977 0.03671 2.34910
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -9.07134 1.06807 -8.493 < 2e-16 ***
## clumpthickness 0.58294 0.14131 4.125 3.7e-05 ***
## ShapeUniformity 0.48237 0.15835 3.046 0.002317 **
## Margin_adhesion 0.31521 0.11347 2.778 0.005469 **
## Barenuclei 0.34662 0.09697 3.574 0.000351 ***
## BlandChromatin 0.44241 0.17664 2.505 0.012259 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 647.45 on 499 degrees of freedom
## Residual deviance: 97.51 on 494 degrees of freedom
## AIC: 109.51
##
## Number of Fisher Scoring iterations: 7
Dựng một mô hình logistic thủ công dùng STAN
Quy trình sau đây tương đối phức tạp, nên nếu các bạn không có hứng thú có thể bỏ qua. Nhi muốn minh họa cách code 1 mô hình trong STAN. Đầu tiên chúng ta phải khai báo về data, bao gồm tên biến số. Sau đó, ta phải khai báo về biến số thật chi tiết, kiểu biến số (integer), các khoảng chặn, danh sách tham số hồi quy, chuyển dạng thành xác suất, cuối cùng là nội dung mô hình. Sau đó ta cho STAN thi hành việc lấy mẫu cho chuỗi MCMC với 2000 lượt bao gồm 500 lượt warm-u.
Kết quả cuối cùng là phân phối hậu định của 6 tham số hồi quy beta:
library(rstan)
## Create Stan data
data=list(N= nrow(trainset),
p = 6,
Class = trainset$Class,
clmptkns=trainset$clumpthickness,
sunif=trainset$ShapeUniformity,
mad=trainset$Margin_adhesion,
bn=trainset$Barenuclei,
bchro=trainset$BlandChromatin
)
stancode="data {
// Khai bao bien so
// Khai bao co mau (so nguyen)
int<lower=0> N;
// Khai bao so luong bien so
int<lower=0> p;
// Khai bao ten bien so va ban chat (so nguyen)
int <lower=0,upper=1> Class[N];
int<lower=0> clmptkns[N];
int<lower=0> sunif[N];
int<lower=0> mad[N];
int<lower=0> bn[N];
int<lower=0> bchro[N];
}
parameters {
// Khai bao tham so hoi quy beta
real beta[p];
}
transformed parameters {
// Hoan chuyen sang xac suat
real<lower=0> odds[N];
real<lower=0, upper=1> prob[N];
for (i in 1:N) {
odds[i] = exp(beta[1] + beta[2]*clmptkns[i] + beta[3]*sunif[i] + beta[4]*mad[i] + beta[5]*bn[i] + beta[6]*bchro[i]);
prob[i] = odds[i] / (odds[i] + 1);
}
}
model {
// Prior cua mo hinh (flat)
// Thanh phan Likelihood cua suy dien Bayesian
Class ~ bernoulli(prob);
}"
mod=stan(model_code = stancode,
data = data,
chains = 2, iter = 2000, warmup = 500,
cores=parallel::detectCores())
## In file included from C:/Users/Admin/Documents/R/win-library/3.4/BH/include/boost/config.hpp:39:0,
## from C:/Users/Admin/Documents/R/win-library/3.4/BH/include/boost/math/tools/config.hpp:13,
## from C:/Users/Admin/Documents/R/win-library/3.4/StanHeaders/include/stan/math/rev/core/var.hpp:7,
## from C:/Users/Admin/Documents/R/win-library/3.4/StanHeaders/include/stan/math/rev/core/gevv_vvv_vari.hpp:5,
## from C:/Users/Admin/Documents/R/win-library/3.4/StanHeaders/include/stan/math/rev/core.hpp:12,
## from C:/Users/Admin/Documents/R/win-library/3.4/StanHeaders/include/stan/math/rev/mat.hpp:4,
## from C:/Users/Admin/Documents/R/win-library/3.4/StanHeaders/include/stan/math.hpp:4,
## from C:/Users/Admin/Documents/R/win-library/3.4/StanHeaders/include/src/stan/model/model_header.hpp:4,
## from file2ac052fc7dd9.cpp:8:
## C:/Users/Admin/Documents/R/win-library/3.4/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"
print(mod,pars = c("beta"))
## Inference for Stan model: 09413ad80138a7d73f1d3cf363aaa40d.
## 2 chains, each with iter=2000; warmup=500; thin=1;
## post-warmup draws per chain=1500, total post-warmup draws=3000.
##
## mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat
## beta[1] -9.70 0.03 1.18 -12.14 -10.49 -9.65 -8.87 -7.56 1529 1
## beta[2] 0.63 0.00 0.15 0.35 0.52 0.62 0.73 0.94 1646 1
## beta[3] 0.52 0.00 0.16 0.22 0.41 0.52 0.63 0.86 2442 1
## beta[4] 0.34 0.00 0.12 0.11 0.26 0.34 0.42 0.59 3000 1
## beta[5] 0.37 0.00 0.10 0.17 0.30 0.37 0.44 0.57 2447 1
## beta[6] 0.47 0.00 0.18 0.14 0.35 0.47 0.59 0.83 2311 1
##
## Samples were drawn using NUTS(diag_e) at Wed Sep 06 09:22:13 2017.
## For each parameter, n_eff is a crude measure of effective sample size,
## and Rhat is the potential scale reduction factor on split chains (at
## convergence, Rhat=1).
Mô hình logistic sử dụng rstanarm package
Bây giờ, chúng ta sẽ đi theo con đường đơn giản hơn rất nhiều đó là dùng giao thức rstanarm.
Cú pháp của hàm stan_glm hoàn toàn giống như hàm glm(), chỉ khác là ta có thêm 1 số arguments về: giả thuyết tiền định prior cho intercept và các tham số hồi quy, quy trình lấy mẫu cho chuỗi MCMC (ở đây Nhi dùng 2 chuỗi, mỗi chuỗi 1500 lượt, bao gồm 500 lượt warming-up, chạy song song trên 4 core). Bạn nhớ xác định giá trị seed để tái lập kết quả.
Ta có thể thí dụ: giả thuyết tiền định (piror) là Student-t với df=5, trung bình = 0 và scale=2.5;
Mô hình sẽ được rstan compile thành C++ program và chạy với tốc độ converge rất nhanh chỉ trong vài phút:
library(rstanarm)
t_prior=student_t(df = 5, location = 0, scale = 2.5)
fml="Class~clumpthickness+
ShapeUniformity+
Margin_adhesion+
Barenuclei+
BlandChromatin"
fittprior=stan_glm(
fml,
data = trainset,
family = binomial(link = "logit"),
prior = t_prior,
prior_intercept = t_prior,
algorithm="sampling",
chains = 2, iter = 1500,warmup = 500,
cores=parallel::detectCores(),
seed=123
)
Khai thác mô hình logistic Bayes
Đầu tiên ta dùng hàm summary: Nội dung mô hình rất gần với kết quả của phương pháp REML
Sử dụng hàm exp(), ta tính được OR
summary(fittprior,digits=5,probs=c(0.025,0.975))
##
## Model Info:
##
## function: stan_glm
## family: binomial [logit]
## formula: "Class~clumpthickness+\n ShapeUniformity+\nMargin_adhesion+\nBarenuclei+\nBlandChromatin"
## algorithm: sampling
## priors: see help('prior_summary')
## sample: 2000 (posterior sample size)
## num obs: 500
##
## Estimates:
## mean sd 2.5% 97.5%
## (Intercept) -9.35274 1.07046 -11.58034 -7.39699
## clumpthickness 0.59474 0.13949 0.33598 0.88405
## ShapeUniformity 0.51011 0.16115 0.21630 0.85396
## Margin_adhesion 0.32270 0.11846 0.09652 0.56287
## Barenuclei 0.36470 0.09297 0.18309 0.55348
## BlandChromatin 0.46691 0.16797 0.15067 0.81076
## mean_PPD 0.35065 0.00999 0.33000 0.37000
## log-posterior -58.89030 1.63128 -62.69783 -56.54771
##
## Diagnostics:
## mcse Rhat n_eff
## (Intercept) 0.02697 0.99942 1575
## clumpthickness 0.00364 1.00030 1472
## ShapeUniformity 0.00392 0.99948 1693
## Margin_adhesion 0.00293 0.99995 1633
## Barenuclei 0.00227 1.00025 1674
## BlandChromatin 0.00406 0.99968 1713
## mean_PPD 0.00022 1.00023 2000
## log-posterior 0.05397 1.00064 914
##
## For each parameter, mcse is Monte Carlo standard error, n_eff is a crude measure of effective sample size, and Rhat is the potential scale reduction factor on split chains (at convergence Rhat=1).
posterior_interval(fittprior)%>%exp()
## 5% 95%
## (Intercept) 1.396778e-05 0.0004701105
## clumpthickness 1.448342e+00 2.3156126545
## ShapeUniformity 1.295290e+00 2.1984733924
## Margin_adhesion 1.141195e+00 1.6927596223
## Barenuclei 1.230699e+00 1.6758497047
## BlandChromatin 1.215031e+00 2.1168243484
Ta vẽ được 1 biểu đồ marginalized effect cho OR:
ordf=exp(cbind(OR = coef(fittprior), posterior_interval(fittprior)))%>%
as_tibble()%>%
mutate(.,Predictor=fittprior$covmat%>%rownames)
names(ordf)=c("OR","LL","UL","Predictor")
ordf%>%ggplot(aes(x=reorder(Predictor,OR),color=reorder(Predictor,OR)))+
geom_errorbar(aes(ymin=LL, ymax=UL),width=0.5,size=1,show.legend = F)+
geom_point(aes(y=OR),shape=21,size=5,fill="white",stroke=1.5,show.legend = F)+
theme_bw()+
coord_flip()+
geom_hline(yintercept = 1,size=1,color="blue",linetype=2)+
scale_x_discrete("Predictors")+
scale_y_continuous("Odds-Ratio")

Hàm as.data.frame cho phép trích xuất trực tiếp 6 chuỗi MCMC từ mô hình Bayes:
Bây giờ ta có thể vẽ được phân phối hậu định của odds-ratio cho 6 tham số hồi quy
posterior=as.data.frame(fittprior)
posterior%>%head()%>%knitr::kable()
-7.975938 |
0.4612697 |
0.5978315 |
0.0613562 |
0.4350187 |
0.3733282 |
-7.582937 |
0.3877390 |
0.5682261 |
0.0235291 |
0.4186240 |
0.4031363 |
-9.319668 |
0.5947666 |
0.5949226 |
0.2925004 |
0.4407699 |
0.3757688 |
-9.526398 |
0.6752297 |
0.2945156 |
0.3742914 |
0.3731516 |
0.4677688 |
-8.085083 |
0.4918975 |
0.5138523 |
0.2328746 |
0.3753085 |
0.3650310 |
-11.037517 |
0.7963245 |
0.5443347 |
0.4349765 |
0.4546275 |
0.3139365 |
posterior%>%mutate(.,Iteration=as.numeric(rownames(.)))%>%
gather(`(Intercept)`:BlandChromatin,
key="Predictors",
value="Coefficients")%>%
ggplot(aes(x=Iteration,
y=exp(Coefficients),
color=Predictors))+
geom_line(alpha=0.7,show.legend = F)+
geom_hline(yintercept=1,size=1,linetype=2,color="red4")+
scale_y_continuous("Odds-ratio")+
facet_wrap(~Predictors,scales = "free",ncol=2)+
theme_bw()

library(ggjoy)
## Warning: package 'ggjoy' was built under R version 3.4.1
posterior[,-1]%>%gather(clumpthickness:BlandChromatin,
key="Predictors",
value="Coefficients")%>%
ggplot(aes(x=exp(Coefficients),
y=reorder(Predictors,Coefficients),
fill=reorder(Predictors,Coefficients)))+
geom_joy(alpha=0.6,show.legend = F,scale=3)+
scale_y_discrete("Predictors")+
scale_x_continuous("Odds-ratio")+
geom_vline(xintercept=c(1,2),size=1,linetype=2,color=c("red4","blue"))+
theme_bw()
## Picking joint bandwidth of 0.0425

Ta có thể kiểm định kết quả của mô hình BAYES trên testset với package caret:Mô hình này hoạt động rất tốt, không thua kém mô hình dựng bằng GLM hay Gamlss
testpred=predict(fittprior,newdata=testset,type="link")%>%exp()
testset$Truth=ifelse(testset$Class==1,"Malignant","Benign")
testset$Pred=testpred
testset$Pred=ifelse(testset$Pred>=0.5,"Malignant","Benign")
caret::confusionMatrix(testset$Pred,reference=testset$Truth,mode="everything",positive="Malignant")
## Confusion Matrix and Statistics
##
## Reference
## Prediction Benign Malignant
## Benign 119 1
## Malignant 0 63
##
## Accuracy : 0.9945
## 95% CI : (0.9699, 0.9999)
## No Information Rate : 0.6503
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.9879
## Mcnemar's Test P-Value : 1
##
## Sensitivity : 0.9844
## Specificity : 1.0000
## Pos Pred Value : 1.0000
## Neg Pred Value : 0.9917
## Precision : 1.0000
## Recall : 0.9844
## F1 : 0.9921
## Prevalence : 0.3497
## Detection Rate : 0.3443
## Detection Prevalence : 0.3443
## Balanced Accuracy : 0.9922
##
## 'Positive' Class : Malignant
##
Bonus: Cơ chế hoạt động của mô hình, từ góc nhìn 2 chiều giữa 2 biến số bất kì trong mô hình:
pr=function(x, y, ests) plogis(ests[1] +
ests[2] * x +
ests[3]*3.164+
ests[4]*3.23+
ests[6] *y +
ests[5]*3.564)
grid=expand.grid(clumpthickness = seq(0, 10, length.out = 50),
BlandChromatin= seq(0, 10, length.out = 50))
grid$prob=with(grid, pr(clumpthickness, BlandChromatin, coef(fittprior)))
ggplot(grid, aes(x = clumpthickness,
y = BlandChromatin)) +
geom_tile(aes(fill = prob),alpha=0.6) +
geom_point(data = testset,
aes(color = factor(Class)),
size = 3, alpha = 0.8) +
scale_fill_gradient(low="#7ee1ea",high="#ed283b")+
scale_color_manual("Class",
values = c("blue3", "red3"),
labels = c("Neg", "Pos")
)+
scale_x_continuous(breaks=c(0,1,2,3,4,5,6,7,8,9,10))+
scale_y_continuous(breaks=c(0,1,2,3,4,5,6,7,8,9,10))+
theme_bw()

Kết luận
Với những giao thức hồi quy Bayes trong R, chúng ta hoàn toàn có thể thay thế các phương pháp REML cổ điển bằng trường phái BAYES mà không cần phải biết về ngôn ngữ lập trình cho sampler như STAN.
Bản thân ngôn ngữ STAN là một phát triển rất hữu ích, nó cho phép dựng những mô hình phức tạp mà trước đó người dùng gặp trở ngại với JAGs hoặc BUGs. Có 2 giao thứccho STAN trong R đó là rstanarm và brms. Giao thức brms đã được giới thiệu vào năm 2016 trong dự án Bayes for Vietnam, và vẫn đang được phát triển.
Giao thức rstanarm đơn giản và dễ sử dụng hơn brms, nó phù hợp hơn cho những nghiên cứu thông thường, và hỗ trợ hầu hết các mô hình GLM và MGLM, bao gồm hồi quy với random effect, beta, gamma, và hồi quy cho count data (negative binomial).
Trong thời gian tới Nhi sẽ giới thiệu với các bạn thêm một vài mô hình Bayes khác.
Nếu các bạn có hứng thú tham gia reboot cho dự án Bayes for Vietnam, xin liên lạc với nhóm chúng tôi.
Xin cảm ơn và hẹn gặp lại
LS0tDQp0aXRsZTogIkjhu5NpIHF1eSBsb2dpc3RpYyBCYXllcyINCnN1YnRpdGxlOiAiU+G7rSBk4bulbmcgZ2lhbyB0aOG7qWMgU1RBTiINCmF1dGhvcjogIkzDqiBOZ+G7jWMgS2jhuqMgTmhpIg0KZGF0ZTogIjA1IFRow6FuZyA5IDIwMTciDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiAiZGVmYXVsdCINCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShyc3RhbmFybSkNCg0KDQpgYGANCg0KIVtdKGJheWVzbG9naXN0aWMucG5nKQ0KDQojIEdp4bubaSB0aGnhu4d1OiBCQVlFUw0KDQpIaeG7h24gbmF5LCB0csaw4budbmcgcGjDoWkgQmF5ZXMgbmfDoHkgY8OgbmcgxJHGsOG7o2MgcGjhu5UgYmnhur9uIG5oxrAgbeG7mXQgZ2nhuqNpIHBow6FwIHRoYXkgdGjhur8gY2hvIG5o4buvbmcgcGjGsMahbmcgcGjDoXAgdGjhu5FuZyBrw6ogY+G7lSDEkWnhu4NuIGThu7FhIHRoZW8gdHLGsOG7nW5nIHBow6FpIGZyZXF1ZW50aXN0LiBUaMOtIGThu6UgbcO0IGjDrG5oIGjhu5NpIHF1eSBCYXllcyB0aGF5IHRo4bq/IGNobyBwaMawxqFuZyBwaMOhcCDGsOG7m2MgbMaw4bujbmcgTWF4aW11bSBsaWtlbGlob29kLiBUcsaw4budbmcgcGjDoWkgQmF5ZXMgY8OzIG3hu5l0IHbDoGkgxrB1IMSRaeG7g20gbmjGsDoNCg0KMSkJQ2hvIHBow6lwIGjDsmEgaOG7o3AgZ2nhu69hIHnhur91IHThu5EgdGnhu4FuIMSR4buLbmggKHRow60gZOG7pSBnaeG6oyB0aHV54bq/dCwgZ2nhuqMgxJHhu4tuaCwgdGjDtG5nIHRpbiB0cm9uZyB5IHbEg24pIHbDoCBi4bqxbmcgY2jhu6luZyB04burIGThu68gbGnhu4d1IHF1YW4gc8OhdCDEkcaw4bujYyB0cm9uZyBuZ2hpw6puIGPhu6l1LCB0aMOtIG5naGnhu4dtDQoNCjIpCURp4buFbiBnaeG6o2kga+G6v3QgcXXhuqMgbGluaCBob+G6oXQgaMahbiBy4bqldCBuaGnhu4F1IHNvIHbhu5tpIE51bGwgaHlwb3RoZXNpcyB0ZXN0IHbDoCBwIHZhbHVlLCBraMO0bmcgw6FwIMSR4bq3dCBuaMawbmcgxJHGsGEgcmEgY2jhu6luZyBj4bupLCBraOG6oyBuxINuZyB0aW4gY+G6rXksIHbDoCBjaG8gcGjDqXAgY+G6rXAgbmjhuq10IGvhur90IHF14bqjIGtoaSBjw7MgdGjDqm0gY2jhu6luZyBj4bupLCBk4buvIGxp4buHdSBt4bubaS4NCg0KMykJR2nhuqNuIGThu4sgdsOgIMSR4buTbmcgbmjhuqV0IDogTmjhu69uZyBwaMOibiB0w61jaCDEkcaw4bujYyBkaeG7hW4gdOG6oyBi4bqxbmcgbcO0IGjDrG5oLCBi4bqxbmcgeMOhYyBzdeG6pXQgxJFp4buBdSBraeG7h24sIGNo4bupIGtow7RuZyBjw7JuIGLhu4sgcGjDom4gdMOhbiB0aMOgbmggdOG7q25nIG3huqNuaCBuaOG7jyBsw6AgY8OhYyBsb+G6oWkga2nhu4NtIMSR4buLbmgNCi4gDQo0KQlWw6wgcGjGsMahbmcgcGjDoXAgQmF5ZXMgecOqdSBj4bqndSBuZ8aw4budaSBo4buNYyBwaOG6o2kgZ2nhuqNpIHBo4bqrdSBtw7QgaMOsbmggaOG6v3Qgc+G7qWMgY2hpIHRp4bq/dCwgdsOgIGhp4buDdSBsw70gdGh1eeG6v3QgeMOhYyBzdeG6pXQsIMSR4buDIGPDsyB0aOG7gyB2aeG6v3QgY29kZSwgbsOqbiBjw6FjIGLhuqFuIHRo4buxYyBow6BuaCBCYXllcyB0aMaw4budbmcgY8OzIGtp4bq/biB0aOG7qWMgduG7r25nIHbDoG5nIHbhu4EgdGjhu5FuZyBrw6ogaOG7jWMgaMahbiBzbyB24bubaSBjw6FjIGLhuqFuIGNo4buJIGTDuW5nIHBo4bqnbiBt4buBbSB0aOG7kW5nIGvDqiB0aGVvIHRyxrDhu51uZyBwaMOhaSBGcmVxdWVudGlzdC4NCg0KVGnhur9jIHLhurFuZywgdmnhu4djIOG7qW5nIGThu6VuZyBy4buZbmcgcsOjaSBwaMawxqFuZyBwaMOhcCBCYXllcyB24bqrbiBjw7JuIGfhurdwIG5oaeG7gXUga2jDsyBraMSDbiwgbmjhuqV0IGzDoCB04bqhaSBWaeG7h3QgTmFtLiBDaOG7pyB54bq/dSBsw6Aga2jDsyBraMSDbiB24buBIGvhu7kgdGh14bqtdCwgdsOsIG5nxrDhu51pIHRo4buxYyBow6BuaCBwaOG6o2kgY8OzIGto4bqjIG7Eg25nIGzhuq1wIHRyw6xuaCwgdmnhur90IGNvZGUgY2hvIGPDoWMgU2FtcGxlciDEkeG7gyDEkeG6oXQgxJHGsOG7o2MgcGjDom4gcGjhu5FpIGjhuq11IG5naGnhu4dtLiBDw6FjIG5nw7RuIG5n4buvIHbDoCBTYW1wbGVyIG5oxrAgQlVHcywgSkFHUyBoYXkgU1RBTiBkbyDEkcOzIGtow7RuZyBt4bqleSBwaOG7lSBiaeG6v24uIA0KDQpUdXkgbmhpw6puLCBy4bqldCBtYXkgbeG6r24gbMOgIGPDsyBt4buZdCBz4buRIGdpYW8gdGjhu6ljIMSRw6MgxJHGsOG7o2MgcGjDoXQgdHJp4buDbiB0csOqbiBu4buBbiB04bqjbmcgY+G7p2EgSkFHcyB2w6AgU1RBTiB0cm9uZyBSLiBN4buZdCB0cm9uZyBuaOG7r25nIGdpYW8gdGjhu6ljIMSRw7MgbMOgIHJzdGFuYXJtLCBOaGkgc+G6vSB0aOG7sWMgaGnhu4duIG3hu5l0IHPhu5EgdHV0b3JpYWwgaMaw4bubbmcgZOG6q24gY8OhY2ggZMO5bmcgZ2lhbyB0aOG7qWMgbsOgeSDEkeG7gyB0aOG7sWMgaGnhu4duIGPDoWMgYsOgaSB0b8OhbiBo4buTaSBxdXkgcGjhu5UgYmnhur9uLiBCw6BpIMSR4bqndSB0acOqbiBz4bq9IGzDoCBI4buTaSBxdXkgTG9naXN0aWMuDQoNCiMgU1RBTiB2w6AgcnN0YW5hcm0gcGFja2FnZQ0KDQpTVEFOIGzDoCBt4buZdCBuZ8O0biBuZ+G7ryBs4bqtcCB0csOsbmggdGjhu5FuZyBrw6ogeMOhYyBzdeG6pXQsdsOgIMSRw6MgxJHGsOG7o2MgcGjDoXQgdHJp4buDbiBjaG8gUiwgUHl0aG9uIHbDoCBNYXRsYWIuIFNUQU4gY2hvIHBow6lwIG5nxrDhu51pIGTDuW5nIGThu7FuZyBuaOG7r25nIG3DtCBow6xuaCB0aMO0bmcgcXVhIHjDoWMgc3XhuqV0IGPDsyDEkWnhu4F1IGtp4buHbiAoeMOhYyDEkeG7i25oIHBow6JuIHBo4buRaSBo4bqtdSDEkeG7i25oIGPhu6dhIHRoYW0gc+G7kSBo4buTaSBxdXkgdGhldGEga2hpIGPDsyBk4buvIGxp4buHdSwgaMOgbSBsaWtlbGlob29kIHbDoCBnaeG6oyB0aHV54bq/dCB0aeG7gW4gxJHhu4tuaCksIGhheSBjw7JuIGfhu41pIGzDoCB0csaw4budbmcgcGjDoWkgQmF5ZXMuIA0KDQpLaGkgYuG6oW4gZOG7sW5nIG3hu5l0IG3DtCBow6xuaCB0cm9uZyBTVEFOLCBuw7MgcGjhuqNpIMSRxrDhu6NjIGdp4bqjaSBwaOG6q3UgxJHhur9uIHThu6tuZyBjaGkgdGnhur90IG5o4buPIG5o4bqldCAodHLGsOG7nW5nIHBow6FpIEJheWVzIGLhuq90IGJ14buZYyBuZ8aw4budaSBo4buNYyBwaOG6o2kgaGnhu4N1IGNoaSB0aeG6v3QgYWxnb3JpdGhtLCBi4bqjbiBjaOG6pXQgYmnhur9uIHPhu5EsIGPDoWMgaMOgbSBt4bqtdCDEkeG7mSB4w6FjIHN14bqldCkuIEtoaSBi4bqhbiDEkcOjIGPDsyBtw7QgaMOsbmgsIHRow6wgY8OzIHRo4buDIGdp4bqjaSBxdXnhur90IGjhuqd1IGjhur90IG5o4buvbmcgYsOgaSB0b8OhbiB0aOG7kW5nIGvDqiwgbmjGsCBzbyBzw6FuaCwgdMawxqFuZyBxdWFuLCBo4buTaSBxdXksIHRpw6puIGzGsOG7o25nLi4uIFThu6sgY2h14buXaSBNQ01DIChwaMOibiBwaOG7kWkgaOG6rXUgxJHhu4tuaCksIGLhuqFuIGPDsyB0aOG7gyBzdXkgZGnhu4VuIHRo4buRbmcga8OqIHRoZW8gbmhp4buBdSBjw6FjaDogZMO5bmcgQmF5ZXMgZmFjdG9yLCBST1BFLCBudWxsIGh5cG90aGVzaXMgdGVzdGluZy4gTcO0IGjDrG5oIEJheWVzIGPDsyB0aOG7gyDDoXAgZOG7pW5nIMSR4buDIGRp4buFbiBnaeG6o2kgaGF5IHRpw6puIGzGsOG7o25nIMSR4buBdSDEkcaw4bujYywgdGjhuq1tIGNow60gbsOzIGNobyBwaMOpcCDGsOG7m25nIGzGsOG7o25nIHBow6JuIHBo4buRaSBo4bqtdSDEkeG7i25oIGNobyBnacOhIHRy4buLIGThu7EgYsOhbywgaOG7k2kgcXV5IHBow6JuIHbhu4suLi4gbeG7mXQgY8OhY2ggdOG7sSBuaGnDqm4uDQoNCktoaSBi4bqhbiBkw7luZyBTVEFOLCDEkeG6p3UgdGnDqm4gMSDEkW/huqFuIGNvZGUgY+G7p2EgbcO0IGjDrG5oIHPhur0gxJHGsOG7o2MgcGhpw6puIG3DoyB0aMOgbmggbeG7mXQgY2jGsMahbmcgdHLDrG5oIChleGVjdXRhYmxlIHByb2dyYW0pIG5nw7RuIG5n4buvIEMrKyAoY29tcGlsYXRpb24pLCBzYXUgxJHDsyBuw7MgbeG7m2kgxJHGsOG7o2MgdGhpIGjDoG5oLiBDxqEgY2jhur8gdOG6oW8gcmEgY2h14buXaSBNYXJrb3YgTW9udGUgQ2FybG8gY+G7p2EgU1RBTiBob8OgbiB0b8OgbiBraMOhYyBiaeG7h3QgduG7m2kgSkFHcyB2w6AgQlVHcyBsw6AgMiBzYW1wbGVyIGPDsyB0csaw4bubYy4gU1RBTiBz4butIGThu6VuZyBwaMawxqFuZyBwaMOhcCBIYW1pbHRvbmlhbiBNb250ZSBDYXJsbyBzYW1wbGluZyBk4buxYSB2w6BvIGzDvSB0aHV54bq/dCBj4bunYSBNZXRyb3BvbGlzLUhhc3RpbmdzICgxOTUzLCAxOTcwKSwgbcO0IHBo4buPbmcgY2h1eeG7g24gxJHhu5luZyBi4bqldCDEkeG7i25oIGPhu6dhIG3hu5l0IMSR4buZbmcgdOG7rS4gICAgDQoNCkLhuqFuIGPDsyB0aOG7gyB0aeG6v3AgY+G6rW4gU1RBTiB04bqhaSDEkcOieToNCg0KaHR0cHM6Ly9naXRodWIuY29tL3N0YW4tZGV2DQoNClRyb25nIFIsIFNUQU4gxJHGsOG7o2Mg4bupbmcgZOG7pW5nIGThu7FhIHbDoG8gMSBz4buRIHBhY2thZ2UgY2jDrW5oLCB0cm9uZyDEkcOzIHBo4bqnbiBsw7VpIGzDoCBSY3BwLCByc3Rhbi4gROG7sWEgdHLDqm4gcGjhuqduIGzDtWkgbsOgeSwgdGEgY8OzIG5o4buvbmcgcGFja2FnZSBnaWFvIHRo4bupYyBuaMawIGJheWVzcGxvdCwgcnN0YW5hcm0sIGJybXMuLi4gY2hvIHBow6lwIG5nxrDhu51pIGTDuW5nIGPDsyB0aOG7gyBk4buxbmcgxJHGsOG7o2MgbcO0IGjDrG5oLCBsw6BtIMSRxrDhu6NjIHN1eSBkaeG7hW4gQmF5ZXMgbcOgIGtow7RuZyBj4bqnbiBiaeG6v3QgbmfDtG4gbmfhu68gU1RBTi4gQ8OhYyBnaWFvIHRo4bupYyBuw6B5IHPhu60gZOG7pW5nIGPDuiBwaMOhcCBxdXkgxrDhu5tjIHRyb25nIFIgKGdsbSwgbG0sIGFvdiwgZ2FtLCBubG1lLCBzdXJ2aXZhbC4uLikgxJHhu4Mga2hhaSBiw6FvIG3DtCBow6xuaCwgc2F1IMSRw7MgZOG7i2NoIHNhbmcgU1RBTiBjb2RlLCBjb21waWxlIHbDoG8gQysrIHLhu5NpIHRoaSBow6BuaC4gS+G6v3QgcXXhuqMgeHXhuqV0IHJhIGPDsyB0aOG7gyB0xrDGoW5nIHRow61jaCB24bubaSBuaOG7r25nIG1ldGhvZCBxdWVuIHRodeG7mWMsIHRow60gZOG7pSBnZ3Bsb3QuIMSQw7MgbMOgIGzhu6NpIHRo4bq/IGPhu6dhIGPDoWMgZ2lhbyB0aOG7qWMuDQoNCiMgSOG7k2kgcXV5IGxvZ2lzdGljIHRoZW8gQkFZRVMNCg0KTeG7mXQgY8OhY2ggdOG7lW5nIHF1w6F0LCBzdXkgZGnhu4VuIEJheWVzIGThu7FhIHRyw6puIMSR4buLbmggbMO9IEJheWVzIG5oxrAgc2F1IDoNCg0KJCQgcChcdGhldGEgfHkseClccHJvcHRvIHAoeXxcdGhldGEgLHgpIHAoXHRoZXRhICx4KSAkJA0KdGhlbyDEkcOzLCBwaMOibiBwaOG7kWkgaOG6rXUgxJHhu4tuaCBj4bunYSBt4buZdCB0aGFtIHPhu5EgVGhldGEgKGtoaSB0YSBjw7MgdHJvbmcgdGF5IGThu68gbGnhu4d1IGJp4bq/biBr4bq/dCBxdeG6oyBZIHbDoCBo4bqxbmcgc+G7kSBYIGzDoCBt4buZdCBtYXRyaXggY8OhYyBwcmVkaWN0b3JzIHRyb25nIG3DtCBow6xuaCkgdOG7iSBs4buHIHbhu5tpIHTDrWNoIGPhu6dhIGjDoG0gbGlrZWxpaG9vZCAoeMOhYyBzdeG6pXQgY8OzIMSRaeG7gXUga2nhu4duIGPhu6dhIHkga2hpIGPDsyB0aGV0YSB2w6AgeCkgdsOgIHBow6JuIHBo4buRaSB0aeG7gW4gxJHhu4tuaCAocHJpb3I9IGdp4bqjIHRodXnhur90IHbhu4EgcGjDom4gcGjhu5FpIGPhu6dhIHRoZXRhKS4NCg0KQsOgaSB0b8OhbiBkw6FuIG5ow6NuIHBow6JuIGxv4bqhaSBt4buZdCBi4buHbmggbmjDom4gduG7m2kgaGFpIGto4bqjIG7Eg25nICgwIHbDoCAxKSB0xrDGoW5nIMSRxrDGoW5nIHbhu5tpIHZp4buHYyDGsOG7m2MgdMOtbmggeMOhYyBzdeG6pXQgY8OzIHk9MSB0cm9uZyBuIGzGsOG7o3QgcsO6dCB0aMSDbSBuZ+G6q3Ugbmhpw6puIHThu6sgbiBwaOG6p24gdOG7rS4gWMOhYyBzdeG6pXQgbsOgeSDEkcaw4bujYyBtw7QgdOG6oyBi4bqxbmcgMSBow6BtIG3huq10IMSR4buZIHjDoWMgc3XhuqV0IChQTUYpIGPhu6dhIHBow6JuIHBo4buRaSBiaW5vbWlhbDoNCg0KJCRcYmlub217bn17eX0gXHBpXnt5fSAoMSAtIFxwaSlee24gLSB5fSQkDQoNClbhu5tpIHBpIGzDoCB4w6FjIHN14bqldCB0w6xtIHRo4bqleSB5PTEuDQoNCkNobyAxIGxpbmsgZnVuY3Rpb24gZyB0YSBjw7MgOg0KDQokJFxwaSA9IGdeey0xfShcZXRhKSQkDQoNCkNobyBtw7QgaMOsbmggbG9naXN0aWMsIGcgbMOgIDEgaMOgbSBsb2dpdCwgaGF5IGPDsm4gZ+G7jWkgbMOgIGxvZy1vZGRzDQoNCiQkZyh4KSA9IFxsbntcbGVmdChcZnJhY3t4fXsxLXh9XHJpZ2h0KX0kJA0KDQpU4burIMSRw6J5IHRhIGPDsyBtw7QgaMOsbmggaOG7k2kgcXV5IGxvZ2lzdGljIDoNCg0KJCRcYmlub217bn17eX1cbGVmdChcdGV4dHtsb2dpdH1eey0xfShcZXRhKVxyaWdodCleeSANClxsZWZ0KDEgLSBcdGV4dHtsb2dpdH1eey0xfShcZXRhKVxyaWdodClee24teX0gPSANClxiaW5vbXtufXt5fSBcbGVmdChcZnJhY3tlXntcZXRhfX17MSArIGVee1xldGF9fVxyaWdodClee3l9DQpcbGVmdChcZnJhY3sxfXsxICsgZV57XGV0YX19XHJpZ2h0KV57biAtIHl9JCQNCg0KVuG7m2kgZXRhIGzDoCBr4bq/dCBxdeG6oyDGsOG7m2MgbMaw4bujbmcgdOG7qyBt4buZdCBwaMawxqFuZyB0csOsbmggdHV54bq/biB0w61uaCBjw7MgZOG6oW5nOg0KDQokJFxldGEgPSBcYWxwaGEgKyBcbWF0aGJme3h9Xlx0b3AgXGJvbGRzeW1ib2x7XGJldGF9JCQNCg0KVHJvbmcgxJHDsyBhbHBoYSBsw6AgaW50ZXJjZXB0LCBYIGzDoCBkZXNpZ24gbWF0cml4IGPhu6dhIG3DtCBow6xuaCwgYmV0YSBsw6AgdGhhbSBz4buRIGjhu5NpIHF1eS4NCg0KVHJvbmcgdHLGsOG7nW5nIGjhu6NwIGjhu5NpIHF1eSBsb2dpc3RpYyB24bubaSBt4buZdCBkYXRhc2V0IGPDsyBOIHF1YW4gc8OhdCwgSyBiaeG6v24gc+G7kSwgdsOgIGvhur90IHF14bqjIFkgbMOgIG3hu5l0IGJp4bq/biBuaOG7iyBwaMOibiAoMCwxKTsgcGjDom4gcGjhu5FpIGjhuq11IMSR4buLbmggdHJvbmcgQmF5ZXMgY8OzIHRo4buDIMSRxrDhu6NjIHZp4bq/dCBuaMawIHNhdToNCg0KJCRmXGxlZnQoXGFscGhhLFxib2xkc3ltYm9se1xiZXRhfSB8IFxtYXRoYmZ7eX0sXG1hdGhiZntYfVxyaWdodCkgXHByb3B0bw0KICBmXGxlZnQoXGFscGhhXHJpZ2h0KSBcdGltZXMgXHByb2Rfe2s9MX1eSyBmXGxlZnQoXGJldGFfa1xyaWdodCkgXHRpbWVzDQogIFxwcm9kX3tpPTF9Xk4gew0KICBnXnstMX1cbGVmdChcZXRhX2lccmlnaHQpXnt5X2l9IA0KICBcbGVmdCgxIC0gZ157LTF9XGxlZnQoXGV0YV9pXHJpZ2h0KVxyaWdodClee25faS15X2l9fSQkDQoNClbhu5tpIGFscGhhIHbDoCBiZXRhKGspIGzhuqduIGzGsOG7o3QgbMOgIGludGVyY2VwdCB2w6AgY8OhYyB0aGFtIHPhu5EgaOG7k2kgcXV5IGNobyBLIGJp4bq/biBz4buRIHRyb25nIG3DtCBow6xuaC4gDQoNCsSQ4buDIHjDoWMgxJHhu4tuaCBwaMOibiBwaOG7kWkgaOG6rXUgxJHhu4tuaCBjaG8gYWxwaGEgdsOgIGJldGEsIHRhIHBo4bqjY3VuZyBj4bqlcCBjaG8gU2FtcGxlciBt4buZdCBraMOhaSBuaeG7h20gKGdp4bqjIMSR4buLbmgpIHbhu4EgcGjDom4gcGjhu5FpIGPhu6dhIGNow7puZyAocHJpb3JzKSBsw6AgZihhbHBoYSkgdsOgIGYoYmV0YSkuIFByaW9yIG7DoHkgY8OzIHRo4buDIGzDoCBub3JtYWwgaG/hurdjIFN0dWRlbnQtdC4NCg0KIyBE4buvIGxp4buHdSBtaW5oIGjhu41hDQoNCsSQ4buDIG1pbmggaOG7jWEsIE5oaSBjaOG7jW4gbeG7mXQgYsOgaSB0b8OhbiBI4buTaSBxdXkgTG9naXN0aWMgduG7m2kgZGF0YXNldCBCaW9wc3kgdHJvbmcgcGFja2FnZSBNQVNTLiBEYXRhc2V0IG7DoHkgYmFvIGfhu5NtIDkgY2jhu4kgc+G7kSB24buBIMSR4bq3YyB0w61uaCBnaeG6o2kgcGjhuqt1IGLhu4duaCBsw70gY+G7p2EgbcO0IHNpbmggdGhp4bq/dCDhu58gYuG7h25oIG5ow6JuIGPDsyBraOG7kWkgdSBWw7osIHbDoCAxIGJp4bq/biDEkeG7i25oIHTDrW5oIG5o4buLIHBow6JuIGzDoCBwaMOibiBsb+G6oWkgTMOgbmggdMOtbmggaGF5IMOhYyB0w61uaCBj4bunYSBraOG7kWkgdS4gDQoNCk7hur91IMSR4bq3dCBwaMOibiBsb+G6oWkgTMOgbmgvw4FjIHTDrW5oIG5oxrAgYmnhur9uIGvhur90IHF14bqjIFkgxJHGsOG7o2MgbcOjIGjDs2EgYuG6sW5nIDIgY29uIHPhu5EgMCAobMOgbmggdMOtbmgpIHbDoCAxICjDoWMgdMOtbmgpLCB2aeG7h2MgZOG7sW5nIG3hu5l0IG3DtCBow6xuaCBo4buTaSBxdXkgY2hvIHBow6lwIHRy4bqjIGzhu51pIGPhuqMgMiBjw6J1IGjhu49pIDoNCg0KKlTDtGkgbXXhu5FuIGJp4bq/dCB2YWkgdHLDsiBj4bunYSBt4buXaSDEkeG6t2MgdMOtbmggdOG6vyBiw6BvIGjhu41jIGfDs3AgcGjhuqduIMSR4buLbmggbmdoxKlhIHTDrW5oIGNo4bqldCBVIGzDoG5oIGhheSDDoWMgdMOtbmggPyAoYsOgaSB0b8OhbiB0aMSDbSBkw7IgL2Rp4buFbiBk4buLY2gqKQ0KDQoqVMO0aSBtdeG7kW4gcGjDom4gbG/huqFpIG3hu5l0IG3huqt1IHNpbmggdGhp4bq/dCBi4bqldCBrw6wgZOG7sWEgdsOgbyBjw6FjIMSR4bq3YyB0w61uaCB04bq/IGLDoG8gaOG7jWMgKG3hu6VjIHRpw6p1IHBow6JuIGxv4bqhaSkqDQoNCkRhdGFzZXQgbsOgeSDEkcOjIMSRxrDhu6NjIHRoxINtIGTDsiBy4bqldCBjaGkgdGnhur90IHRyb25nIDEgdHV0b3JpYWwgdHLGsOG7m2MgxJHDonkgKEdhbWxzcyBiw6BpIDMpOg0KDQpodHRwOi8vcnB1YnMuY29tL2xlbmdvY2toYW5oaS8yOTQ3ODINCg0KQ2jDum5nIHRhIGNoaWEgZOG7ryBsaeG7h3UgcmEgMiBwaOG6p246IHRyYWluc2V0IHbDoCB0ZXN0c2V0IG5oxrAgdGjGsOG7nW5nIGzhu4c6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCmRmPXJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWIuY29tL3ZpbmNlbnRhcmVsYnVuZG9jay9SZGF0YXNldHMvbWFzdGVyL2Nzdi9NQVNTL2Jpb3BzeS5jc3YiKQ0KDQpkZj1yZWFkLmNzdigiaHR0cDovL3ZpbmNlbnRhcmVsYnVuZG9jay5naXRodWIuaW8vUmRhdGFzZXRzL2Nzdi9NQVNTL2Jpb3BzeS5jc3YiKSU+JWFzX3RpYmJsZSgpJT4lLlssYygzOjEyKV0lPiVuYS5vbWl0KCkNCg0KbmFtZXMoZGYpPWMoImNsdW1wdGhpY2tuZXNzIiwNCiAgICAgICAgICAgICJTaXplVW5pZm9ybWl0eSIsDQogICAgICAgICAgICAiU2hhcGVVbmlmb3JtaXR5IiwNCiAgICAgICAgICAgICJNYXJnaW5fYWRoZXNpb24iLA0KICAgICAgICAgICAgIkVwaUNlbGxTaXplIiwNCiAgICAgICAgICAgICJCYXJlbnVjbGVpIiwNCiAgICAgICAgICAgICJCbGFuZENocm9tYXRpbiIsDQogICAgICAgICAgICAiTm9ybWFsTnVjbGVvbGkiLA0KICAgICAgICAgICAgIk1pdG9zZXMiLA0KICAgICAgICAgICAgIkNsYXNzIg0KKQ0KDQpkZjI9ZGYlPiVtdXRhdGUoLixDbGFzcz1hcy5pbnRlZ2VyKC4kQ2xhc3MpLTFMKQ0KDQpsaWJyYXJ5KGNhcmV0KQ0KDQpzZXQuc2VlZCgxMjMpDQppZD1jcmVhdGVEYXRhUGFydGl0aW9uKHk9ZGYkQ2xhc3MsIHA9NDk5LzY4MyxsaXN0PUZBTFNFKQ0KdHJhaW5zZXQ9ZGYyW2lkLF0NCnRlc3RzZXQ9ZGYyWy1pZCxdDQpgYGANCg0KIyBNw7QgaMOsbmggbG9naXN0aWMgZMO5bmcgR0xNDQoNCsSQ4bqndSB0acOqbiwgTmhpIGThu7FuZyBt4buZdCBtw7QgaMOsbmggbG9naXN0aWMgdGhlbyBwaMawxqFuZyBwaMOhcCBxdWVuIHRodeG7mWMgdHJvbmcgUiBsw6AgaMOgbSBnbG06DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZm1sPSJDbGFzc35jbHVtcHRoaWNrbmVzcysNCiAgU2hhcGVVbmlmb3JtaXR5Kw0KICBNYXJnaW5fYWRoZXNpb24rDQogIEJhcmVudWNsZWkrDQogIEJsYW5kQ2hyb21hdGluIg0KDQoNCmZpdGdsbT1nbG0oDQogIGZtbCwNCiAgZGF0YSA9IHRyYWluc2V0LCANCiAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpDQopDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KGZpdGdsbSkNCmBgYA0KDQojIEThu7FuZyBt4buZdCBtw7QgaMOsbmggbG9naXN0aWMgdGjhu6cgY8O0bmcgZMO5bmcgU1RBTg0KDQpRdXkgdHLDrG5oIHNhdSDEkcOieSB0xrDGoW5nIMSR4buRaSBwaOG7qWMgdOG6oXAsIG7Dqm4gbuG6v3UgY8OhYyBi4bqhbiBraMO0bmcgY8OzIGjhu6luZyB0aMO6IGPDsyB0aOG7gyBi4buPIHF1YS4gTmhpIG114buRbiBtaW5oIGjhu41hIGPDoWNoIGNvZGUgMSBtw7QgaMOsbmggdHJvbmcgU1RBTi4gxJDhuqd1IHRpw6puIGNow7puZyB0YSBwaOG6o2kga2hhaSBiw6FvIHbhu4EgZGF0YSwgYmFvIGfhu5NtIHTDqm4gYmnhur9uIHPhu5EuIFNhdSDEkcOzLCB0YSBwaOG6o2kga2hhaSBiw6FvIHbhu4EgYmnhur9uIHPhu5EgdGjhuq10IGNoaSB0aeG6v3QsIGtp4buDdSBiaeG6v24gc+G7kSAoaW50ZWdlciksIGPDoWMga2hv4bqjbmcgY2jhurduLCBkYW5oIHPDoWNoIHRoYW0gc+G7kSBo4buTaSBxdXksIGNodXnhu4NuIGThuqFuZyB0aMOgbmggeMOhYyBzdeG6pXQsIGN14buRaSBjw7luZyBsw6AgbuG7mWkgZHVuZyBtw7QgaMOsbmguIFNhdSDEkcOzIHRhIGNobyBTVEFOIHRoaSBow6BuaCB2aeG7h2MgbOG6pXkgbeG6q3UgY2hvIGNodeG7l2kgTUNNQyB24bubaSAyMDAwIGzGsOG7o3QgYmFvIGfhu5NtIDUwMCBsxrDhu6N0IHdhcm0tdS4NCg0KS+G6v3QgcXXhuqMgY3Xhu5FpIGPDuW5nIGzDoCBwaMOibiBwaOG7kWkgaOG6rXUgxJHhu4tuaCBj4bunYSA2IHRoYW0gc+G7kSBo4buTaSBxdXkgYmV0YToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCmxpYnJhcnkocnN0YW4pDQoNCiMjIENyZWF0ZSBTdGFuIGRhdGENCmRhdGE9bGlzdChOPSBucm93KHRyYWluc2V0KSwNCiAgICAgICAgICAgIHAgICAgICAgID0gNiwNCiAgICAgICAgICAgIENsYXNzICAgID0gdHJhaW5zZXQkQ2xhc3MsDQogICAgICAgICAgICBjbG1wdGtucz10cmFpbnNldCRjbHVtcHRoaWNrbmVzcywNCiAgICAgICAgICAgIHN1bmlmPXRyYWluc2V0JFNoYXBlVW5pZm9ybWl0eSwNCiAgICAgICAgICAgIG1hZD10cmFpbnNldCRNYXJnaW5fYWRoZXNpb24sDQogICAgICAgICAgICBibj10cmFpbnNldCRCYXJlbnVjbGVpLA0KICAgICAgICAgICAgYmNocm89dHJhaW5zZXQkQmxhbmRDaHJvbWF0aW4NCiAgICAgICAgICApDQoNCnN0YW5jb2RlPSJkYXRhIHsNCiAvLyBLaGFpIGJhbyBiaWVuIHNvIA0KDQovLyBLaGFpIGJhbyBjbyBtYXUgKHNvIG5ndXllbikNCmludDxsb3dlcj0wPiBOOw0KDQovLyBLaGFpIGJhbyBzbyBsdW9uZyBiaWVuIHNvDQoNCmludDxsb3dlcj0wPiBwOw0KDQovLyBLaGFpIGJhbyB0ZW4gYmllbiBzbyB2YSBiYW4gY2hhdCAoc28gbmd1eWVuKQ0KDQppbnQgPGxvd2VyPTAsdXBwZXI9MT4gQ2xhc3NbTl07DQppbnQ8bG93ZXI9MD4gIGNsbXB0a25zW05dOw0KaW50PGxvd2VyPTA+ICBzdW5pZltOXTsNCmludDxsb3dlcj0wPiBtYWRbTl07DQppbnQ8bG93ZXI9MD4gIGJuW05dOw0KaW50PGxvd2VyPTA+IGJjaHJvW05dOw0KfQ0KDQpwYXJhbWV0ZXJzIHsNCi8vIEtoYWkgYmFvIHRoYW0gc28gaG9pIHF1eSBiZXRhDQpyZWFsIGJldGFbcF07DQp9DQoNCnRyYW5zZm9ybWVkIHBhcmFtZXRlcnMgIHsNCiAgIC8vIEhvYW4gY2h1eWVuIHNhbmcgeGFjIHN1YXQNCnJlYWw8bG93ZXI9MD4gb2Rkc1tOXTsNCnJlYWw8bG93ZXI9MCwgdXBwZXI9MT4gcHJvYltOXTsNCg0KZm9yIChpIGluIDE6Tikgew0Kb2Rkc1tpXSA9IGV4cChiZXRhWzFdICsgYmV0YVsyXSpjbG1wdGtuc1tpXSArIGJldGFbM10qc3VuaWZbaV0gKyBiZXRhWzRdKm1hZFtpXSArIGJldGFbNV0qYm5baV0gKyBiZXRhWzZdKmJjaHJvW2ldKTsNCnByb2JbaV0gPSBvZGRzW2ldIC8gKG9kZHNbaV0gKyAxKTsNCn0NCn0NCg0KbW9kZWwgew0KLy8gUHJpb3IgY3VhIG1vIGhpbmggKGZsYXQpDQoNCi8vIFRoYW5oIHBoYW4gTGlrZWxpaG9vZCBjdWEgc3V5IGRpZW4gQmF5ZXNpYW4NCkNsYXNzIH4gYmVybm91bGxpKHByb2IpOw0KfSINCg0KbW9kPXN0YW4obW9kZWxfY29kZSA9IHN0YW5jb2RlLCANCiAgICAgICAgIGRhdGEgPSBkYXRhLA0KICAgICAgICBjaGFpbnMgPSAyLCBpdGVyID0gMjAwMCwgd2FybXVwID0gNTAwLA0KICAgICAgICBjb3Jlcz1wYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSkNCg0KcHJpbnQobW9kLHBhcnMgPSBjKCJiZXRhIikpDQpgYGANCg0KIyBNw7QgaMOsbmggbG9naXN0aWMgc+G7rSBk4bulbmcgcnN0YW5hcm0gcGFja2FnZQ0KDQpCw6J5IGdp4budLCBjaMO6bmcgdGEgc+G6vSDEkWkgdGhlbyBjb24gxJHGsOG7nW5nIMSRxqFuIGdp4bqjbiBoxqFuIHLhuqV0IG5oaeG7gXUgxJHDsyBsw6AgZMO5bmcgZ2lhbyB0aOG7qWMgcnN0YW5hcm0uDQoNCkPDuiBwaMOhcCBj4bunYSBow6BtIHN0YW5fZ2xtIGhvw6BuIHRvw6BuIGdp4buRbmcgbmjGsCBow6BtIGdsbSgpLCBjaOG7iSBraMOhYyBsw6AgdGEgY8OzIHRow6ptIDEgc+G7kSBhcmd1bWVudHMgduG7gTogZ2nhuqMgdGh1eeG6v3QgdGnhu4FuIMSR4buLbmggcHJpb3IgY2hvIGludGVyY2VwdCB2w6AgY8OhYyB0aGFtIHPhu5EgaOG7k2kgcXV5LCBxdXkgdHLDrG5oIGzhuqV5IG3huqt1IGNobyBjaHXhu5dpIE1DTUMgKOG7nyDEkcOieSBOaGkgZMO5bmcgMiBjaHXhu5dpLCBt4buXaSBjaHXhu5dpIDE1MDAgbMaw4bujdCwgYmFvIGfhu5NtIDUwMCBsxrDhu6N0IHdhcm1pbmctdXAsIGNo4bqheSBzb25nIHNvbmcgdHLDqm4gNCBjb3JlKS4gQuG6oW4gbmjhu5sgeMOhYyDEkeG7i25oIGdpw6EgdHLhu4sgc2VlZCDEkeG7gyB0w6FpIGzhuq1wIGvhur90IHF14bqjLg0KDQpUYSBjw7MgdGjhu4MgdGjDrSBk4bulOiBnaeG6oyB0aHV54bq/dCB0aeG7gW4gxJHhu4tuaCAocGlyb3IpIGzDoCBTdHVkZW50LXQgduG7m2kgZGY9NSwgdHJ1bmcgYsOsbmggPSAwIHbDoCBzY2FsZT0yLjU7DQoNCk3DtCBow6xuaCBz4bq9IMSRxrDhu6NjIHJzdGFuIGNvbXBpbGUgdGjDoG5oIEMrKyBwcm9ncmFtIHbDoCBjaOG6oXkgduG7m2kgdOG7kWMgxJHhu5kgY29udmVyZ2UgcuG6pXQgbmhhbmggY2jhu4kgdHJvbmcgdsOgaSBwaMO6dDoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCmxpYnJhcnkocnN0YW5hcm0pDQoNCnRfcHJpb3I9c3R1ZGVudF90KGRmID0gNSwgbG9jYXRpb24gPSAwLCBzY2FsZSA9IDIuNSkNCg0KZm1sPSJDbGFzc35jbHVtcHRoaWNrbmVzcysNCiAgU2hhcGVVbmlmb3JtaXR5Kw0KTWFyZ2luX2FkaGVzaW9uKw0KQmFyZW51Y2xlaSsNCkJsYW5kQ2hyb21hdGluIg0KDQpmaXR0cHJpb3I9c3Rhbl9nbG0oDQogIGZtbCwNCiAgZGF0YSA9IHRyYWluc2V0LCANCiAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpLCANCiAgcHJpb3IgPSB0X3ByaW9yLA0KICBwcmlvcl9pbnRlcmNlcHQgPSB0X3ByaW9yLA0KICBhbGdvcml0aG09InNhbXBsaW5nIiwNCiAgY2hhaW5zID0gMiwgaXRlciA9IDE1MDAsd2FybXVwID0gNTAwLA0KICBjb3Jlcz1wYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSwNCiAgc2VlZD0xMjMNCikNCg0KYGBgDQoNCiMgS2hhaSB0aMOhYyBtw7QgaMOsbmggbG9naXN0aWMgQmF5ZXMNCg0KxJDhuqd1IHRpw6puIHRhIGTDuW5nIGjDoG0gc3VtbWFyeTogTuG7mWkgZHVuZyBtw7QgaMOsbmggcuG6pXQgZ+G6p24gduG7m2kga+G6v3QgcXXhuqMgY+G7p2EgcGjGsMahbmcgcGjDoXAgUkVNTA0KDQpT4butIGThu6VuZyBow6BtIGV4cCgpLCB0YSB0w61uaCDEkcaw4bujYyBPUg0KDQpgYGB7cn0NCg0Kc3VtbWFyeShmaXR0cHJpb3IsZGlnaXRzPTUscHJvYnM9YygwLjAyNSwwLjk3NSkpDQoNCnBvc3Rlcmlvcl9pbnRlcnZhbChmaXR0cHJpb3IpJT4lZXhwKCkNCg0KYGBgDQoNClRhIHbhur0gxJHGsOG7o2MgMSBiaeG7g3UgxJHhu5MgbWFyZ2luYWxpemVkIGVmZmVjdCBjaG8gT1I6DQoNCmBgYHtyfQ0Kb3JkZj1leHAoY2JpbmQoT1IgPSBjb2VmKGZpdHRwcmlvciksIHBvc3Rlcmlvcl9pbnRlcnZhbChmaXR0cHJpb3IpKSklPiUNCiAgYXNfdGliYmxlKCklPiUNCiAgbXV0YXRlKC4sUHJlZGljdG9yPWZpdHRwcmlvciRjb3ZtYXQlPiVyb3duYW1lcykNCg0KbmFtZXMob3JkZik9YygiT1IiLCJMTCIsIlVMIiwiUHJlZGljdG9yIikNCg0Kb3JkZiU+JWdncGxvdChhZXMoeD1yZW9yZGVyKFByZWRpY3RvcixPUiksY29sb3I9cmVvcmRlcihQcmVkaWN0b3IsT1IpKSkrDQogIGdlb21fZXJyb3JiYXIoYWVzKHltaW49TEwsIHltYXg9VUwpLHdpZHRoPTAuNSxzaXplPTEsc2hvdy5sZWdlbmQgPSBGKSsNCiAgZ2VvbV9wb2ludChhZXMoeT1PUiksc2hhcGU9MjEsc2l6ZT01LGZpbGw9IndoaXRlIixzdHJva2U9MS41LHNob3cubGVnZW5kID0gRikrDQogIHRoZW1lX2J3KCkrDQogIGNvb3JkX2ZsaXAoKSsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSxzaXplPTEsY29sb3I9ImJsdWUiLGxpbmV0eXBlPTIpKw0KICBzY2FsZV94X2Rpc2NyZXRlKCJQcmVkaWN0b3JzIikrDQogIHNjYWxlX3lfY29udGludW91cygiT2Rkcy1SYXRpbyIpDQpgYGANCg0KSMOgbSBhcy5kYXRhLmZyYW1lIGNobyBwaMOpcCB0csOtY2ggeHXhuqV0IHRy4buxYyB0aeG6v3AgNiBjaHXhu5dpIE1DTUMgdOG7qyBtw7QgaMOsbmggQmF5ZXM6IA0KDQpCw6J5IGdp4budIHRhIGPDsyB0aOG7gyB24bq9IMSRxrDhu6NjIHBow6JuIHBo4buRaSBo4bqtdSDEkeG7i25oIGPhu6dhIG9kZHMtcmF0aW8gIGNobyA2IHRoYW0gc+G7kSBo4buTaSBxdXkNCg0KYGBge3J9DQoNCnBvc3Rlcmlvcj1hcy5kYXRhLmZyYW1lKGZpdHRwcmlvcikNCg0KcG9zdGVyaW9yJT4laGVhZCgpJT4la25pdHI6OmthYmxlKCkNCg0KDQpwb3N0ZXJpb3IlPiVtdXRhdGUoLixJdGVyYXRpb249YXMubnVtZXJpYyhyb3duYW1lcyguKSkpJT4lDQogIGdhdGhlcihgKEludGVyY2VwdClgOkJsYW5kQ2hyb21hdGluLA0KICAgICAgICAgICAgICAgICAgICAgICAga2V5PSJQcmVkaWN0b3JzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlPSJDb2VmZmljaWVudHMiKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9SXRlcmF0aW9uLA0KICAgICAgICAgICAgIHk9ZXhwKENvZWZmaWNpZW50cyksDQogICAgICAgICAgICAgY29sb3I9UHJlZGljdG9ycykpKw0KICBnZW9tX2xpbmUoYWxwaGE9MC43LHNob3cubGVnZW5kID0gRikrDQogIGdlb21faGxpbmUoeWludGVyY2VwdD0xLHNpemU9MSxsaW5ldHlwZT0yLGNvbG9yPSJyZWQ0IikrDQogIHNjYWxlX3lfY29udGludW91cygiT2Rkcy1yYXRpbyIpKw0KICBmYWNldF93cmFwKH5QcmVkaWN0b3JzLHNjYWxlcyA9ICJmcmVlIixuY29sPTIpKw0KICB0aGVtZV9idygpDQoNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dqb3kpDQoNCnBvc3RlcmlvclssLTFdJT4lZ2F0aGVyKGNsdW1wdGhpY2tuZXNzOkJsYW5kQ2hyb21hdGluLA0KICAgICAgICAgICAgICAgICAgICAgICAga2V5PSJQcmVkaWN0b3JzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlPSJDb2VmZmljaWVudHMiKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9ZXhwKENvZWZmaWNpZW50cyksDQogICAgICAgICAgICAgeT1yZW9yZGVyKFByZWRpY3RvcnMsQ29lZmZpY2llbnRzKSwNCiAgICAgICAgICAgICBmaWxsPXJlb3JkZXIoUHJlZGljdG9ycyxDb2VmZmljaWVudHMpKSkrDQogIGdlb21fam95KGFscGhhPTAuNixzaG93LmxlZ2VuZCA9IEYsc2NhbGU9MykrDQogIHNjYWxlX3lfZGlzY3JldGUoIlByZWRpY3RvcnMiKSsNCiAgc2NhbGVfeF9jb250aW51b3VzKCJPZGRzLXJhdGlvIikrDQogIGdlb21fdmxpbmUoeGludGVyY2VwdD1jKDEsMiksc2l6ZT0xLGxpbmV0eXBlPTIsY29sb3I9YygicmVkNCIsImJsdWUiKSkrDQogIHRoZW1lX2J3KCkNCg0KYGBgDQoNClRhIGPDsyB0aOG7gyBraeG7g20gxJHhu4tuaCBr4bq/dCBxdeG6oyBj4bunYSBtw7QgaMOsbmggQkFZRVMgdHLDqm4gdGVzdHNldCB24bubaSBwYWNrYWdlIGNhcmV0Ok3DtCBow6xuaCBuw6B5IGhv4bqhdCDEkeG7mW5nIHLhuqV0IHThu5F0LCBraMO0bmcgdGh1YSBrw6ltIG3DtCBow6xuaCBk4buxbmcgYuG6sW5nIEdMTSBoYXkgR2FtbHNzDQoNCg0KYGBge3J9DQp0ZXN0cHJlZD1wcmVkaWN0KGZpdHRwcmlvcixuZXdkYXRhPXRlc3RzZXQsdHlwZT0ibGluayIpJT4lZXhwKCkNCg0KdGVzdHNldCRUcnV0aD1pZmVsc2UodGVzdHNldCRDbGFzcz09MSwiTWFsaWduYW50IiwiQmVuaWduIikNCnRlc3RzZXQkUHJlZD10ZXN0cHJlZA0KdGVzdHNldCRQcmVkPWlmZWxzZSh0ZXN0c2V0JFByZWQ+PTAuNSwiTWFsaWduYW50IiwiQmVuaWduIikNCg0KY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeCh0ZXN0c2V0JFByZWQscmVmZXJlbmNlPXRlc3RzZXQkVHJ1dGgsbW9kZT0iZXZlcnl0aGluZyIscG9zaXRpdmU9Ik1hbGlnbmFudCIpDQoNCmBgYA0KDQpCb251czogQ8ahIGNo4bq/IGhv4bqhdCDEkeG7mW5nIGPhu6dhIG3DtCBow6xuaCwgdOG7qyBnw7NjIG5ow6xuIDIgY2hp4buBdSBnaeG7r2EgMiBiaeG6v24gc+G7kSBi4bqldCBrw6wgdHJvbmcgbcO0IGjDrG5oOiANCg0KYGBge3J9DQpwcj1mdW5jdGlvbih4LCB5LCBlc3RzKSBwbG9naXMoZXN0c1sxXSArIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXN0c1syXSAqIHggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXN0c1szXSozLjE2NCsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVzdHNbNF0qMy4yMysNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVzdHNbNl0gKnkgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXN0c1s1XSozLjU2NCkNCg0KZ3JpZD1leHBhbmQuZ3JpZChjbHVtcHRoaWNrbmVzcyA9IHNlcSgwLCAxMCwgbGVuZ3RoLm91dCA9IDUwKSwgDQogICAgICAgICAgICAgICAgIEJsYW5kQ2hyb21hdGluPSBzZXEoMCwgMTAsIGxlbmd0aC5vdXQgPSA1MCkpDQoNCmdyaWQkcHJvYj13aXRoKGdyaWQsIHByKGNsdW1wdGhpY2tuZXNzLCBCbGFuZENocm9tYXRpbiwgY29lZihmaXR0cHJpb3IpKSkNCg0KZ2dwbG90KGdyaWQsIGFlcyh4ID0gY2x1bXB0aGlja25lc3MsIA0KICAgICAgICAgICAgICAgICB5ID0gQmxhbmRDaHJvbWF0aW4pKSArIA0KICBnZW9tX3RpbGUoYWVzKGZpbGwgPSBwcm9iKSxhbHBoYT0wLjYpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IHRlc3RzZXQsDQogICAgICAgICAgICAgYWVzKGNvbG9yID0gZmFjdG9yKENsYXNzKSksDQogICAgICAgICAgICAgc2l6ZSA9IDMsIGFscGhhID0gMC44KSArIA0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0iIzdlZTFlYSIsaGlnaD0iI2VkMjgzYiIpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwoIkNsYXNzIiwgDQogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBjKCJibHVlMyIsICJyZWQzIiksIA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiTmVnIiwgIlBvcyIpDQogICAgICAgICAgICAgICAgICAgICApKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPWMoMCwxLDIsMyw0LDUsNiw3LDgsOSwxMCkpKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzPWMoMCwxLDIsMyw0LDUsNiw3LDgsOSwxMCkpKw0KICB0aGVtZV9idygpDQpgYGANCg0KIyBL4bq/dCBsdeG6rW4NCg0KVuG7m2kgbmjhu69uZyBnaWFvIHRo4bupYyBo4buTaSBxdXkgQmF5ZXMgdHJvbmcgUiwgY2jDum5nIHRhIGhvw6BuIHRvw6BuIGPDsyB0aOG7gyB0aGF5IHRo4bq/IGPDoWMgcGjGsMahbmcgcGjDoXAgUkVNTCBj4buVIMSRaeG7g24gYuG6sW5nIHRyxrDhu51uZyBwaMOhaSBCQVlFUyBtw6Aga2jDtG5nIGPhuqduIHBo4bqjaSBiaeG6v3QgduG7gSBuZ8O0biBuZ+G7ryBs4bqtcCB0csOsbmggY2hvIHNhbXBsZXIgbmjGsCBTVEFOLiANCg0KQuG6o24gdGjDom4gbmfDtG4gbmfhu68gU1RBTiBsw6AgbeG7mXQgcGjDoXQgdHJp4buDbiBy4bqldCBo4buvdSDDrWNoLCBuw7MgY2hvIHBow6lwIGThu7FuZyBuaOG7r25nIG3DtCBow6xuaCBwaOG7qWMgdOG6oXAgbcOgIHRyxrDhu5tjIMSRw7MgbmfGsOG7nWkgZMO5bmcgZ+G6t3AgdHLhu58gbmfhuqFpIHbhu5tpIEpBR3MgaG/hurdjIEJVR3MuIEPDsyAyIGdpYW8gdGjhu6ljY2hvICBTVEFOIHRyb25nIFIgxJHDsyBsw6AgcnN0YW5hcm0gdsOgIGJybXMuIEdpYW8gdGjhu6ljIGJybXMgxJHDoyDEkcaw4bujYyBnaeG7m2kgdGhp4buHdSB2w6BvIG7Eg20gMjAxNiB0cm9uZyBk4buxIMOhbiBCYXllcyBmb3IgVmlldG5hbSwgdsOgIHbhuqtuIMSRYW5nIMSRxrDhu6NjIHBow6F0IHRyaeG7g24uIA0KDQpHaWFvIHRo4bupYyByc3RhbmFybSDEkcahbiBnaeG6o24gdsOgIGThu4Ugc+G7rSBk4bulbmcgaMahbiBicm1zLCBuw7MgcGjDuSBo4bujcCBoxqFuIGNobyBuaOG7r25nIG5naGnDqm4gY+G7qXUgdGjDtG5nIHRoxrDhu51uZywgdsOgIGjhu5cgdHLhu6MgaOG6p3UgaOG6v3QgY8OhYyBtw7QgaMOsbmggR0xNIHbDoCBNR0xNLCBiYW8gZ+G7k20gaOG7k2kgcXV5IHbhu5tpIHJhbmRvbSBlZmZlY3QsIGJldGEsIGdhbW1hLCB2w6AgaOG7k2kgcXV5IGNobyBjb3VudCBkYXRhIChuZWdhdGl2ZSBiaW5vbWlhbCkuIA0KDQpUcm9uZyB0aOG7nWkgZ2lhbiB04bubaSBOaGkgc+G6vSBnaeG7m2kgdGhp4buHdSB24bubaSBjw6FjIGLhuqFuIHRow6ptIG3hu5l0IHbDoGkgbcO0IGjDrG5oIEJheWVzIGtow6FjLg0KDQpO4bq/dSBjw6FjIGLhuqFuIGPDsyBo4bupbmcgdGjDuiB0aGFtIGdpYSByZWJvb3QgY2hvIGThu7Egw6FuIEJheWVzIGZvciBWaWV0bmFtLCB4aW4gbGnDqm4gbOG6oWMgduG7m2kgbmjDs20gY2jDum5nIHTDtGkuIA0KDQoqWGluIGPhuqNtIMahbiB2w6AgaOG6uW4gZ+G6t3AgbOG6oWkq