1 Giới thiệu

Trong năm 2017, ta có thể chứng kiến một phong trào sôi nổi liên quan đến đề tài “Diễn giải nội dung của các mô hình Statistical learning” trong cộng đồng R. Hàng loạt package mới được phát triển nhằm diễn giải cho từng algorithm chuyên biệt như hồi quy tuyến tính (GLM), Random Forest (RF) và Extreme Gradient boosting (XGB). Do phương pháp Statistical learning (tức Machine learning) ngày càng phổ biến trong nghiên cứu y học, nhu cầu diễn giải các mô hình Machine learning trở thành nhu cầu thiết yếu. Do đó, Nhi sẽ lần lượt chuyển đến các bạ hướng dẫn sử dụng những packages mới này. Bài đầu tiên này sẽ là package « randomForestExplainer » , chuyên dụng cho mô hình Random Forest.

Đây là một package vừa được công bố vào cuối tháng 7 năm 2017 bởi tác giả Aleksandra Paluszyńska. Công dụng của package này cho phép khảo sát nội dung bên trong một mô hình Random Forest.

Như chúng ta biết, Random Forest là một tập hợp mô hình (ensemble). Mô hình Random Forest rất hiệu quả cho các bài toán phân loại vì nó huy động cùng lúc hàng trăm mô hình nhỏ hơn bên trong với quy luật khác nhau để đưa ra quyết định cuối cùng. Mỗi mô hình con có thể mạnh yếu khác nhau, nhưng theo nguyên tắc « wisdom of the crowd », ta sẽ có cơ hội phân loại chính xác hơn so với khi sử dụng bất kì một mô hình đơn lẻ nào.

Như tên gọi của nó, Random Forest (RF) dựa trên cơ sở :

  1. Random = Tính ngẫu nhiên ;

  2. Forest = nhiều cây quyết định (decision tree).

Đơn vị của RF là thuật toán cây quyết định, với số lượng hàng trăm. Mỗi cây quyết định được tạo ra một cách ngẫu nhiên từ việc : Tái chọn mẫu (bootstrap, random sampling) và chỉ dùng một phần nhỏ tập biến ngẫu nhiên (random features) từ toàn bộ các biến trong dữ liệu. Ở trạng thái sau cùng, mô hình RF thường hoạt động rất chính xác, nhưng đổi lại, ta không thể nào hiểu được cơ chế hoạt động bên trong mô hình vì cấu trúc quá phức tạp. RF do đó là một trong số những mô hình hộp đen (black box).

Trong quá khứ, chúng ta thường chấp nhận đánh đổi tính tường minh để đạt được tính chính xác. Từ mô hình Random Forest, chúng ta chỉ có thể làm một số khảo sát hạn chế, bao gồm vai trò tương đối của các biến (features) và vẽ các biểu đồ 2 chiều thể hiện ranh giới các vùng phân loại. Tuy nhiên, như Nhi đã giải thích trong bài về package Lime, nghiên cứu y học có yêu cầu cao hơn về khả năng diễn giải nội dung và cấu trúc mô hình.

2 Thí dụ minh họa:

Trong bài, Nhi sẽ minh họa bằng một mô hình RF với mục tiêu phân loại khối U lành tính/ác tính dựa vào 9 biến là các chỉ số tế bào học (dataset Biopsy).Dataset này đã được sử dụng nhiều lần trong các bài thực hành trước kia của project Machine learning applied to Medicine.

Đầu tiên, ta chuẩn bị một số theme ggplot2 để vẽ biểu đồ và tải data vào R, sau đó Nhi chia ngẫu nhiên dữ liệu gốc thành 2 tập: Trainset dùng để xây dựng model RF (638 cases) và Testset (100 cases) dùng kiểm định model.

library(tidyverse)

my_theme <- function(base_size =5, base_family = "sans"){
  theme_bw(base_size = base_size, base_family = base_family) +
    theme(
      panel.grid.major = element_line(color = "gray"),
      panel.grid.minor = element_blank(),
      panel.background = element_rect(fill = NA),
      strip.background = element_rect(fill = "#001d60", color = "#00113a", size =0.5),
      strip.text = element_text(face = "bold", size = 5, color = "white"),
      legend.position = "bottom",
      legend.justification = "center",
      legend.background = element_blank()
    )
}


theme_set(my_theme())


theme_bare <- function(base_size=8,base_family="sans"){theme_bw(base_size = base_size, base_family = base_family)+
    theme(
      axis.line = element_blank(), 
      axis.text.x = element_blank(), 
      axis.text.y = element_blank(),
      axis.ticks = element_blank(), 
      axis.title.x = element_blank(), 
      axis.title.y = element_blank(), 
      legend.position = "bottom", 
      panel.background = element_rect(fill = NA), 
      panel.border = element_blank(), 
      panel.grid.major = element_blank(), 
      panel.grid.minor = element_blank(), 
      plot.margin = unit(c(0,0,0,0), "lines")
    )
}
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"
)

library(caret)

set.seed(1234)
idtest=caret::createDataPartition(y=df$Class, p=99/683,list=FALSE)

trainset=df[-idtest,]
testset=df[idtest,]

3 Dựng model Random Forest

Ta sẽ dựng model bằng package randomForerst, với đặc tính mô hình như sau: Tổng số cây = 500, mỗi cây sẽ dùng ngẫu nhiên 3/9 biến, chế độ tái chọn mẫu có replace. Lưu ý: tùy chỉnh localImp phải được chuyển sang “TRUE” để có thể thực hiện các phép tính liên quan đến variable importance sau này.

Nguyên nhân của việc tạo ra một rừng cây lớn như vậy, vì ta muốn phân biệt những nhóm biến số quan trọng và tương tác có thể giữa chúng.

Mô hình được tạo ra khá nhanh chóng: Khi kiểm định mô hình trên chính tập Trainset: độ chính xác của nó rất cao, với tỉ lệ sai lầm chỉ có 2.6% cho U lành tính và 3.9% cho U ác tính.

Khi kiểm tra lại trên dữ liệu độc lập (Testset), mô hình đạt độ chính xác rất cao với BAC=95.6%, Fscore=94.3%.

library(randomForest)

rfmod=randomForest(Class~.,
                   data=trainset,
                   ntree=500,
                   mtry=sqrt(9),
                   replace=TRUE,
                   localImp=TRUE
                   )

rfmod
## 
## Call:
##  randomForest(formula = Class ~ ., data = trainset, ntree = 500,      mtry = sqrt(9), replace = TRUE, localImp = TRUE) 
##                Type of random forest: classification
##                      Number of trees: 500
## No. of variables tried at each split: 3
## 
##         OOB estimate of  error rate: 3.09%
## Confusion matrix:
##           benign malignant class.error
## benign       370         9  0.02374670
## malignant      9       195  0.04411765
testpred=predict(rfmod,testset)

confusionMatrix(as.vector(testpred),
                reference=testset$Class,
                positive ="malignant",
                mode="everything")
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign        63         2
##   malignant      2        33
##                                          
##                Accuracy : 0.96           
##                  95% CI : (0.9007, 0.989)
##     No Information Rate : 0.65           
##     P-Value [Acc > NIR] : 6.97e-14       
##                                          
##                   Kappa : 0.9121         
##  Mcnemar's Test P-Value : 1              
##                                          
##             Sensitivity : 0.9429         
##             Specificity : 0.9692         
##          Pos Pred Value : 0.9429         
##          Neg Pred Value : 0.9692         
##               Precision : 0.9429         
##                  Recall : 0.9429         
##                      F1 : 0.9429         
##              Prevalence : 0.3500         
##          Detection Rate : 0.3300         
##    Detection Prevalence : 0.3500         
##       Balanced Accuracy : 0.9560         
##                                          
##        'Positive' Class : malignant      
## 

Sau đây là sự dao động của độ chính xác mô hình khi tăng kích thước từ 0 đến 500 cây. Mô hình bắt đầu ổn định kể từ 300 cây trở lên:

# Learning curve

plot(rfmod, 
     main = "RF Learning curve")
legend("topright", 
       c("error for 'malignant class'", 
         "misclassification error", 
         "error for 'benign class'"), 
       lty = c(1,1,1), 
       col = c("green", "black", "red"))

Package caret cho phép khảo sát vai trò của từng biến trong mô hình (variable Importance), như sau:

varImpPlot(rfmod)

varImp(rfmod)%>%as.data.frame()%>%
  mutate(Feature=rownames(.))%>%
  gather(benign,malignant,key="Label",value="Importance")%>%
  mutate(Importance=100*Importance/max(.$Importance))%>%
  ggplot(aes(x=reorder(Feature,Importance),
             y=Importance,
             fill=Importance,
             color=Importance))+
  geom_segment(aes(x=reorder(Feature,Importance),
               xend=Feature,
               y=0,
               yend=Importance),
               size=1,
               show.legend = F)+
  geom_point(size=2,show.legend = F)+
  scale_x_discrete("Features")+
  coord_flip()+
  scale_fill_gradient(low="blue",high="red")+
  scale_color_gradient(low="blue",high="red")+
  geom_text(aes(label = round(Importance,1)),
            vjust=-0.5,size=3)+
  facet_wrap(~Label,ncol=2,scales="free")+
  theme_bw(base_family = "mono",8)

Một biến số tốt (có vai trò quan trọng, đóng góp đáng kể vào việc phân loại) sẽ có tần suất hiện diện cao hơn trong mô hình (xuất hiện trong những cây tốt nhất, và tương ứng với nhiều nodes).Biểu đồ trên cho thấy: Biến Barenuclei là quan trọng nhất, còn biến Mitoses ít quan trọng nhất. Tuy nhiên đây là việc duy nhất ta có thể làm trước kia.

4 Package randomForestExplainer

4.1 Vai trò của biến trong mô hình

Package randomForestExplainer cho phép khảo sát sâu hơn về vai trò của từng biến trong mô hình RF. Đầu tiên là kết quả định danh, ta có thể lọc ra danh sách K biến quan trọng nhất đã tham gia vào mô hình:

library(randomForestExplainer)

# 5 most Imp var

rfmod%>%
  important_variables(k=5,ties_action = "draw")
## [1] "Barenuclei"      "ShapeUniformity" "SizeUniformity"  "clumpthickness" 
## [5] "NormalNucleoli"

Thí dụ, k=5 tương ứng 5 biến quan trọng nhất, theo thứ tự từ Trái sang phải. Kết quả này có thể rất quan trọng, nếu bạn có trong tay đến hàng ngàn, thậm chí hàng trăm ngàn biến chứ không phải chỉ có 9. Như vậy, Mô hình Random Forest cũng là một giải pháp nhằm chọn lọc biến số cho bài toán phân loại, bất kể algorithm mà bạn muốn áp dụng sau đó là gì. Với tốc độ nhanh thế này, bạn hoàn toàn có thể dùng RF để thăm dò dữ liệu và chọn lọc biến số.

Hơn thế nữa, package randomForestExplainer còn cung cấp cho chúng ta đến 6 chỉ số thống kê để đo lường vai trò quan trọng của từng biến, như sau:

  1. Minimal depth.

Chiều sâu của cây quyết định là kích thước của con đường dài nhất từ rễ cây (biến số đầu tiên) đến lá cây (một kết quả phân loại, sau nhiều lần phân nhánh). Tree depth thể hiện mức độ phức tạp của cấu trúc mô hình.

Mỗi biến Xj nếu hiện diện trong một cây, sẽ có một giá trị minimal depth xác định, là một số nguyên dương (thí dụ từ 0 đến 7). Nếu Xj không hiện diện, thì giá trị này được tính là missing value (NA). Như vậy trong mô hình RF với 500 cây, phân bố của giá trị Min_depth cho mỗi biến số có thể cung cấp thông tin về : tần suất hiện diện của biến số đó, sự phức tạp của quy luật mà biến đó tham gia (min_depth cao). Đây là phân bố của một số nguyên dương, và ta có thể tính giá trị trung bình (mean of min_depth) cho từng biến.

Lưu ý : Khi chọn chế độ lấy mẫu là « relevant trees », giá trị trung bình chỉ tính dựa trên số lượng cây mà biến đó có tham gia, không kể các missing value.

  1. Accuracy decrease của một biến Xj đo lường sự sụt giảm độ chính xác của mô hình cây khi Xj bị hoán chuyển. Một cách gián tiếp, chỉ số này cho thấy mức độ quan trọng của biến Xj trong mô hình.

  2. Gini decrease của biến Xj đo lường sự suy giảm trung bình của Gini impurity tại mỗi node tương ứng với những lần phân chia dựa vào biến Xj này. Có thể hình dung là Gini decrease cho thấy ảnh hưởng quan trọng của Xj đối với quy luật phân nhóm dữ liệu, làm giảm xác suất phạm sai lầm khi dán nhãn cho các trường hợp.

  3. No_of_trees : đơn giản là tổng số cây có hiện diện biến Xj. Sự hiện diện của Xj trong nhiều cây cho thấy Xj là một tiêu chí quan trọng.

  4. No of nodes : tổng số node (nút phân nhánh) có liên quan đến Xj trong mô hình RF, ý nghĩa của chỉ số này tương tự như no_of_trees. Time_a_root : tổng số cây mà trong đó biến Xj được sử dụng ngay từ rễ (lần phân nhánh đầu tiên trong mô hình cây dựa vào Xj).

P_value : trị số p (1 bên) của kiểm định binomial dựa trên phân phối Binomial (tổng số nodes, P(số nodes do phân chia Xj)). Giả thuyết H0 của kiểm định này cho rằng việc Xj tham gia phân nhánh chỉ là do ngẫu nhiên. Đặt ngưỡng ý nghĩa cho p=0.05, ta có thể bác bỏ giả thuyết H0 này và tin rằng Xj thực sự có vai trò quan trọng.

Từ dataframe chứa những chỉ số kể trên, ta có thể trình bày kết quả dưới nhiều hình thức biểu đồ. Với mục tiêu mỹ thuật, Nhi quyết định vẽ lại nhiều biểu đồ hoàn toàn thủ công trong ggplot2, tuy nhiên bạn cũng có thể dùng những function plots mặc định của package:

Đầu tiên là bargraph cho giá trị trung bình:

rfmod%>%measure_importance(mean_sample = "relevant_trees")%>%
  as_tibble()->vimpdf

vimpdf%>%head()
## # A tibble: 6 x 8
##          variable mean_min_depth no_of_nodes accuracy_decrease
##            <fctr>          <dbl>       <dbl>             <dbl>
## 1      Barenuclei       1.516064        1899        0.11340121
## 2  BlandChromatin       2.372141        1168        0.01656099
## 3  clumpthickness       2.491736        1497        0.03474579
## 4     EpiCellSize       2.808602        1226        0.01801668
## 5 Margin_adhesion       2.903846        1231        0.01667767
## 6         Mitoses       3.737342         448        0.00373172
## # ... with 4 more variables: gini_decrease <dbl>, no_of_trees <dbl>,
## #   times_a_root <dbl>, p_value <dbl>
vimpdf%>%gather(mean_min_depth:p_value,
                key="Metric",
                value="Value")%>%
  ggplot(aes(x=reorder(variable,Value),
             y=Value,
             fill=Metric,
             color=Metric))+
  geom_segment(aes(x=reorder(variable,Value),
                   xend=variable,
                   y=0,
                   yend=Value),
               size=1,
               show.legend = F)+
  geom_point(size=2,show.legend = F)+
  scale_x_discrete("Features")+
  coord_flip()+
  facet_wrap(~Metric,ncol=4,scales="free")+
  theme_bw(base_family = "mono",8)

Tương quan phi tham số (thứ hạng):

Biểu đồ này cung cấp cả 3 thông tin: Đặc tính phân bố của mỗi chỉ số sau khi được xếp hạng (trừ p value), hệ số tương quan Spearman bắt cặp giữa chúng, và scatter dot plot của thứ hạng.

plot_importance_rankings(vimpdf)

Tương quan tuyến tính giữa các chỉ số:

Biểu đồ này cung cấp cả 3 thông tin: Đặc tính phân bố của mỗi chỉ số (trừ p value), hệ số tương quan Pearson bắt cặp giữa chúng, và scatter dot plot. Mỗi điểm trong biểu đồ tương ứng với 1 biến.

plot_importance_ggpairs(vimpdf)

Đây là biểu đồ tương tự do Nhi vẽ thủ công dựa vào dataset:

#Correlation matrix

library(GGally)

plotfuncLow <- function(data,mapping){
  p <- ggplot(data = data,mapping=mapping)+
    geom_point(aes(fill=rownames(data),
                   color=rownames(data)),
               shape=21,size=2,
               alpha=0.8,
               show.legend = F)+
    geom_line(aes(color=rownames(data),
                  group=1))+
    theme_bw(base_family = "mono",6)
  p
}

plotfuncmid <- function(data,mapping){
  p <- ggplot(data = data,mapping=mapping)+
    geom_histogram(aes(fill=rownames(data),
                   color=rownames(data)),
                   alpha=0.5)+
    theme_bw(base_family = "mono",6)
  p
}

ggpairs(vimpdf,columns=2:8,
        lower=list(continuous=plotfuncLow),
        diag=list(continuous=plotfuncmid))

Riêng chỉ số Minimal depth distribution có thể được trình bày bằng hình vẽ sau:

#Minimal depth distribution

mindepthdf=rfmod%>%min_depth_distribution()%>%as_tibble()

mindepthdf%>%ggplot(aes(x=minimal_depth,
                        fill=variable,
                        col=variable))+
  geom_histogram(alpha=0.5)+
  facet_wrap(~variable,ncol=3,scales="free")+
  my_theme()

rfmod%>%min_depth_distribution()%>%
  plot_min_depth_distribution(mean_sample="all_trees")+
  scale_fill_brewer(palette="Spectral",direction = -1)

5 Tương tác giữa 2 biến trong mô hình RF

Không chỉ đo lường vai trò của từng biến riêng lẻ, package này còn cho phép chúng ta khảo sát về tương tác giữa 2 biến. Sự tương tác xảy ra khi 2 biến cùng hiện diện trong một mô hình cây và tham gia vào sự phân nhánh ở các nodes.

Biểu đồ sau đây trình bày mean minimal depth tính cho 30 cặp tương tác nổi bật nhất:

# Interactions

mindpint=min_depth_interactions(rfmod,mean_sample = "relevant_trees", 
                       uncond_mean_sample = "relevant_trees")

library(viridis)

mindpint%>%
  plot_min_depth_interactions()+
  scale_fill_viridis("A")

Khi quan sát nội dung của dataframe kết quả phân tích tương tác biến, Nhi nhận thấy nó có dạng 1 network, tức là ta còn có thể trình bày mạng lưới tương tác giữa các biến bằng một biểu đồ mạng (network graph). Để làm việc này, Nhi kết hợp package igraph và package ggraph với nhau.

Đầu tiên ta lọc bỏ những cặp tương tác nào có tần số xuất hiện < 180 lần sau đó ta chuyển toàn bộ gdp dataframe thành 1 network, sau đó dùng ggraph để mã hóa màu sắc cho các liên kết theo tần suất hiện diện, màu xanh kém hơn màu đỏ.

Kết quả cuối cùng là một network như trong hình:

mindpint%>%head()
##     variable   root_variable mean_min_depth occurrences
## 1 Barenuclei      Barenuclei      1.7570423         284
## 2 Barenuclei  BlandChromatin      1.1019608         255
## 3 Barenuclei  clumpthickness      0.9392713         247
## 4 Barenuclei     EpiCellSize      0.8316832         202
## 5 Barenuclei Margin_adhesion      1.0909091         209
## 6 Barenuclei         Mitoses      0.9252336         107
##                  interaction uncond_mean_min_depth
## 1      Barenuclei:Barenuclei              1.516064
## 2  BlandChromatin:Barenuclei              1.516064
## 3  clumpthickness:Barenuclei              1.516064
## 4     EpiCellSize:Barenuclei              1.516064
## 5 Margin_adhesion:Barenuclei              1.516064
## 6         Mitoses:Barenuclei              1.516064
library(igraph)
library(ggraph)

gdf=filter(mindpint,
           occurrences>=180)

graph<-graph_from_data_frame(gdf[,c(1:3,4,6)])


ggraph(graph,
       layout = 'kk',
       circular=F)+
  geom_edge_link(aes(colour = occurrences),
                show.legend = T,width=1,alpha=0.7)+
  geom_node_label(aes(label = name))+
  coord_fixed()+
  scale_edge_color_gradient(high="#ff003f",low="#0094ff")+
  scale_edge_width_continuous()+
  theme_bare()

Network này cho chúng ta biết 3 thông tin: Mức độ tương tác của các biến trong mô hình : Những biến có tương tác chặt chẽ sẽ gom lại gần nhau thành một cụm và liên kết có màu đỏ, Biến Barenuclei nằm ở trung tâm được xem là quan trọng nhất, biến Mitose chỉ có tương tác với Size Uniformity và ít quan trọng nhất. Nhắc lại: Một trong những lợi thế của mô hình cây quyết định đó là nó cho phép mô hình hóa những quan hệ tương tác phức tạp giữa nhiều biến, thậm chí vượt ra ngoài tầm hiểu biết của người dựng mô hình; tiếp theo - đó là khả năng lọc bỏ tự động những biến không có vai trò quan trọng đối với mục tiêu mà ta cần nhắm tới. Do đó, khi 2 hay 3 biến đồng thời xuất hiện lặp đi lặp lại trong nhiều mô hình cây là một dấu hiệu cho thấy: 1) Chúng quan trọng, 2) Chúng có liên hệ với nhau.

Như vậy, chúng ta có thể áp dụng hình thức này như một giải pháp thay thế cho correlation matrix trong một mô hình tiên lượng hồi quy và phân loại, biểu đồ network trên đây đưa ra bức tranh toàn cảnh về liên hệ giữa các biến số quan trọng nhất cùng hướng tới một mục tiêu xác định.

6 Multiway importance plot

Ở trên, ta đã thử trình bày tương quan bắt cặp giữa 2 chỉ số đo lường độ quan trọng của các biến, dạng biểu đồ sau đây cho phép trình bày nhiều thông tin cùng lúc trên một biểu đồ 2 chiều: tương quan giữa 2 chỉ số bất kì, phân phối của chúng, và p_value của binomial test.

# Multiway Importance

plot_multi_way_importance(rfmod,
                          x_measure = "mean_min_depth",
                            y_measure = "times_a_root",
                           size_measure = "no_of_nodes",
                          min_no_of_trees = 20
                          )

plot_multi_way_importance(vimpdf, 
                          x_measure = "no_of_nodes", 
                          y_measure = "no_of_nodes", 
                          size_measure = "p_value",
                          min_no_of_trees = 20)

plot_multi_way_importance(vimpdf, 
                          x_measure = "accuracy_decrease", 
                          y_measure = "gini_decrease", 
                          size_measure = "p_value",
                          min_no_of_trees = 20)

plot_multi_way_importance(vimpdf, 
                          x_measure = "mean_min_depth", 
                          y_measure = "gini_decrease", 
                          size_measure = "p_value",
                          min_no_of_trees = 20)

7 Prediction interaction plot

Cuối cùng, package này cho phép vẽ một biểu đồ dạng heatmap, thể hiện phân bố kết quả của mô hình Random Forest tương ứng với sự tổ hợp của 2 giá trị bất kì trên thang đo của 2 biến số tùy chọn.

rfmod%>%plot_predict_interaction(testset, 
                         "Barenuclei", 
                         "ShapeUniformity",30)+scale_fill_viridis(option="A")+coord_equal()

rfmod%>%plot_predict_interaction(testset, 
                                 "clumpthickness", 
                                 "SizeUniformity",30)+scale_fill_viridis(option="A")+coord_equal()

Lưu ý: Màu sắc trên biểu đồ tương ứng với xác suất phân loại U ác tính (0 đến 1), và kết quả này là của mô hình gồm 9 biến chứ không chỉ giới hạn ở 2 biến mà ta đang xét.

8 Tổng kết

Bài thực hành đến đây là kết thúc. Trong bài này Nhi đã giới thiệu với các bạn tất cả những tính năng mà package randomForestExplainer cung cấp. Những tính năng này đã mở rộng đáng kể khả năng khảo sát cấu trúc của một mô hình Random Forest. Các phương pháp mà package này sử dụng bám sát vào các nguyên lý của mô hình cây (decision tree) và cung cấp thông tin phong phú về: vai trò của mỗi biến số và sự tương tác giữa chúng. Những chỉ số định lượng trong phân tích này có giá trị tương đương với hệ số hồi quy và p_value của kiểm định t trong một mô hình tuyến tính tổng quát.Ngoài công dụng diễn giải cấu trúc mô hình, những phương pháp này còn có thể được sử dụng cho việc thăm dò dữ liệu, lựa chọn biến số,ngay cả khi Random forest không phải là algorithm mà bạn muốn áp dụng cho bài toán của mình.

Trong bài tiếp theo, một thành viên khác của nhóm sẽ giới thiệu với các bạn cách diễn giải một black box khác là Extreme gradient boosting (XGBM).

Tạm biệt các bạn và hẹn gặp lại.

LS0tDQp0aXRsZTogIkRp4buFbiBnaeG6o2kgYmxhY2tib3ggbW9kZWwiIA0Kc3VidGl0bGU6ICJQaOG6p24gMTogUmFuZG9tIEZvcmVzdCINCmF1dGhvcjogIkzDqiBOZ+G7jWMgS2jhuqMgTmhpIg0KZGF0ZTogIjE1IFRow6FuZyAxMiAyMDE3Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImRlZmF1bHQiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCiFbXShSRmV4cGxhaW5lci5wbmcpDQoNCiMgR2nhu5tpIHRoaeG7h3UNCg0KVHJvbmcgbsSDbSAyMDE3LCB0YSBjw7MgdGjhu4MgY2jhu6luZyBraeG6v24gbeG7mXQgcGhvbmcgdHLDoG8gc8O0aSBu4buVaSBsacOqbiBxdWFuIMSR4bq/biDEkeG7gSB0w6BpICJEaeG7hW4gZ2nhuqNpIG7hu5lpIGR1bmcgY+G7p2EgY8OhYyBtw7QgaMOsbmggU3RhdGlzdGljYWwgbGVhcm5pbmciIHRyb25nIGPhu5luZyDEkeG7k25nIFIuIEjDoG5nIGxv4bqhdCBwYWNrYWdlIG3hu5tpIMSRxrDhu6NjIHBow6F0IHRyaeG7g24gbmjhurFtIGRp4buFbiBnaeG6o2kgY2hvIHThu6tuZyBhbGdvcml0aG0gY2h1ecOqbiBiaeG7h3QgbmjGsCBo4buTaSBxdXkgdHV54bq/biB0w61uaCAoR0xNKSwgUmFuZG9tIEZvcmVzdCAoUkYpIHbDoCBFeHRyZW1lIEdyYWRpZW50IGJvb3N0aW5nIChYR0IpLiBEbyBwaMawxqFuZyBwaMOhcCBTdGF0aXN0aWNhbCBsZWFybmluZyAodOG7qWMgTWFjaGluZSBsZWFybmluZykgbmfDoHkgY8OgbmcgcGjhu5UgYmnhur9uIHRyb25nIG5naGnDqm4gY+G7qXUgeSBo4buNYywgbmh1IGPhuqd1IGRp4buFbiBnaeG6o2kgY8OhYyBtw7QgaMOsbmggTWFjaGluZSBsZWFybmluZyB0cuG7nyB0aMOgbmggbmh1IGPhuqd1IHRoaeG6v3QgeeG6v3UuIERvIMSRw7MsIE5oaSBz4bq9IGzhuqduIGzGsOG7o3QgY2h1eeG7g24gxJHhur9uIGPDoWMgYuG6oSBoxrDhu5tuZyBk4bqrbiBz4butIGThu6VuZyBuaOG7r25nIHBhY2thZ2VzIG3hu5tpIG7DoHkuIELDoGkgxJHhuqd1IHRpw6puIG7DoHkgc+G6vSBsw6AgcGFja2FnZSDCqyByYW5kb21Gb3Jlc3RFeHBsYWluZXIgwrsgLCBjaHV5w6puIGThu6VuZyBjaG8gbcO0IGjDrG5oIFJhbmRvbSBGb3Jlc3QuDQoNCsSQw6J5IGzDoCBt4buZdCBwYWNrYWdlIHbhu6thIMSRxrDhu6NjIGPDtG5nIGLhu5EgdsOgbyBjdeG7kWkgdGjDoW5nIDcgbsSDbSAyMDE3IGLhu59pIHTDoWMgZ2nhuqMgQWxla3NhbmRyYSBQYWx1c3p5xYRza2EuIEPDtG5nIGThu6VuZyBj4bunYSBwYWNrYWdlIG7DoHkgY2hvIHBow6lwIGto4bqjbyBzw6F0IG7hu5lpIGR1bmcgYsOqbiB0cm9uZyBt4buZdCBtw7QgaMOsbmggUmFuZG9tIEZvcmVzdC4NCg0KTmjGsCBjaMO6bmcgdGEgYmnhur90LCBSYW5kb20gRm9yZXN0IGzDoCBt4buZdCB04bqtcCBo4bujcCBtw7QgaMOsbmggKGVuc2VtYmxlKS4gTcO0IGjDrG5oIFJhbmRvbSBGb3Jlc3QgcuG6pXQgaGnhu4d1IHF14bqjIGNobyBjw6FjIGLDoGkgdG/DoW4gcGjDom4gbG/huqFpIHbDrCBuw7MgaHV5IMSR4buZbmcgY8O5bmcgbMO6YyBow6BuZyB0csSDbSBtw7QgaMOsbmggbmjhu48gaMahbiBiw6puIHRyb25nIHbhu5tpIHF1eSBsdeG6rXQga2jDoWMgbmhhdSDEkeG7gyDEkcawYSByYSBxdXnhur90IMSR4buLbmggY3Xhu5FpIGPDuW5nLiBN4buXaSBtw7QgaMOsbmggY29uIGPDsyB0aOG7gyBt4bqhbmggeeG6v3Uga2jDoWMgbmhhdSwgbmjGsG5nIHRoZW8gbmd1ecOqbiB04bqvYyDCqyB3aXNkb20gb2YgdGhlIGNyb3dkIMK7LCB0YSBz4bq9IGPDsyBjxqEgaOG7mWkgcGjDom4gbG/huqFpIGNow61uaCB4w6FjIGjGoW4gc28gduG7m2kga2hpIHPhu60gZOG7pW5nIGLhuqV0IGvDrCBt4buZdCBtw7QgaMOsbmggxJHGoW4gbOG6uyBuw6BvLiANCg0KTmjGsCB0w6puIGfhu41pIGPhu6dhIG7DsywgUmFuZG9tIEZvcmVzdCAoUkYpIGThu7FhIHRyw6puIGPGoSBz4bufIDoNCg0KMSkgUmFuZG9tID0gVMOtbmggbmfhuqt1IG5oacOqbiA7DQoNCjIpIEZvcmVzdCA9IG5oaeG7gXUgY8OieSBxdXnhur90IMSR4buLbmggKGRlY2lzaW9uIHRyZWUpLg0KDQrEkMahbiB24buLIGPhu6dhIFJGIGzDoCB0aHXhuq10IHRvw6FuIGPDonkgcXV54bq/dCDEkeG7i25oLCB24bubaSBz4buRIGzGsOG7o25nIGjDoG5nIHRyxINtLiBN4buXaSBjw6J5IHF1eeG6v3QgxJHhu4tuaCDEkcaw4bujYyB04bqhbyByYSBt4buZdCBjw6FjaCBuZ+G6q3Ugbmhpw6puIHThu6sgdmnhu4djIDogVMOhaSBjaOG7jW4gbeG6q3UgKGJvb3RzdHJhcCwgcmFuZG9tIHNhbXBsaW5nKSB2w6AgY2jhu4kgZMO5bmcgbeG7mXQgcGjhuqduIG5o4buPIHThuq1wIGJp4bq/biBuZ+G6q3Ugbmhpw6puIChyYW5kb20gZmVhdHVyZXMpIHThu6sgdG/DoG4gYuG7mSBjw6FjIGJp4bq/biB0cm9uZyBk4buvIGxp4buHdS4g4bueIHRy4bqhbmcgdGjDoWkgc2F1IGPDuW5nLCBtw7QgaMOsbmggUkYgdGjGsOG7nW5nIGhv4bqhdCDEkeG7mW5nIHLhuqV0IGNow61uaCB4w6FjLCBuaMawbmcgxJHhu5VpIGzhuqFpLCB0YSBraMO0bmcgdGjhu4MgbsOgbyBoaeG7g3UgxJHGsOG7o2MgY8ahIGNo4bq/IGhv4bqhdCDEkeG7mW5nIGLDqm4gdHJvbmcgbcO0IGjDrG5oIHbDrCBj4bqldSB0csO6YyBxdcOhIHBo4bupYyB04bqhcC4gUkYgZG8gxJHDsyBsw6AgbeG7mXQgdHJvbmcgc+G7kSBuaOG7r25nIG3DtCBow6xuaCBo4buZcCDEkWVuIChibGFjayBib3gpLg0KDQohW10ocmFuZC1mb3Jlc3QtMS5qcGcpDQoNClRyb25nIHF1w6Ega2jhu6ksIGNow7puZyB0YSB0aMaw4budbmcgY2jhuqVwIG5o4bqtbiDEkcOhbmggxJHhu5VpIHTDrW5oIHTGsOG7nW5nIG1pbmggxJHhu4MgxJHhuqF0IMSRxrDhu6NjIHTDrW5oIGNow61uaCB4w6FjLiBU4burIG3DtCBow6xuaCBSYW5kb20gRm9yZXN0LCBjaMO6bmcgdGEgY2jhu4kgY8OzIHRo4buDIGzDoG0gbeG7mXQgc+G7kSBraOG6o28gc8OhdCBo4bqhbiBjaOG6vywgYmFvIGfhu5NtIHZhaSB0csOyIHTGsMahbmcgxJHhu5FpIGPhu6dhIGPDoWMgYmnhur9uIChmZWF0dXJlcykgdsOgIHbhur0gY8OhYyBiaeG7g3UgxJHhu5MgMiBjaGnhu4F1IHRo4buDIGhp4buHbiByYW5oIGdp4bubaSBjw6FjIHbDuW5nIHBow6JuIGxv4bqhaS4gVHV5IG5oacOqbiwgbmjGsCBOaGkgxJHDoyBnaeG6o2kgdGjDrWNoIHRyb25nIGLDoGkgduG7gSBwYWNrYWdlIExpbWUsIG5naGnDqm4gY+G7qXUgeSBo4buNYyBjw7MgecOqdSBj4bqndSBjYW8gaMahbiB24buBIGto4bqjIG7Eg25nIGRp4buFbiBnaeG6o2kgbuG7mWkgZHVuZyB2w6AgY+G6pXUgdHLDumMgbcO0IGjDrG5oLiANCg0KIyBUaMOtIGThu6UgbWluaCBo4buNYTogDQoNClRyb25nIGLDoGksIE5oaSBz4bq9IG1pbmggaOG7jWEgYuG6sW5nIG3hu5l0IG3DtCBow6xuaCBSRiB24bubaSBt4bulYyB0acOqdSBwaMOibiBsb+G6oWkga2jhu5FpIFUgbMOgbmggdMOtbmgvw6FjIHTDrW5oIGThu7FhIHbDoG8gOSBiaeG6v24gbMOgIGPDoWMgY2jhu4kgc+G7kSB04bq/IGLDoG8gaOG7jWMgKGRhdGFzZXQgQmlvcHN5KS5EYXRhc2V0IG7DoHkgxJHDoyDEkcaw4bujYyBz4butIGThu6VuZyBuaGnhu4F1IGzhuqduIHRyb25nIGPDoWMgYsOgaSB0aOG7sWMgaMOgbmggdHLGsOG7m2Mga2lhIGPhu6dhIHByb2plY3QgTWFjaGluZSBsZWFybmluZyBhcHBsaWVkIHRvIE1lZGljaW5lLg0KDQrEkOG6p3UgdGnDqm4sIHRhIGNodeG6qW4gYuG7iyBt4buZdCBz4buRIHRoZW1lIGdncGxvdDIgxJHhu4MgduG6vSBiaeG7g3UgxJHhu5MgdsOgIHThuqNpIGRhdGEgdsOgbyBSLCBzYXUgxJHDsyBOaGkgY2hpYSBuZ+G6q3Ugbmhpw6puIGThu68gbGnhu4d1IGfhu5FjIHRow6BuaCAyIHThuq1wOiBUcmFpbnNldCBkw7luZyDEkeG7gyB4w6J5IGThu7FuZyBtb2RlbCBSRiAoNjM4IGNhc2VzKSB2w6AgVGVzdHNldCAoMTAwIGNhc2VzKSBkw7luZyBraeG7g20gxJHhu4tuaCBtb2RlbC4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KbXlfdGhlbWUgPC0gZnVuY3Rpb24oYmFzZV9zaXplID01LCBiYXNlX2ZhbWlseSA9ICJzYW5zIil7DQogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IGJhc2Vfc2l6ZSwgYmFzZV9mYW1pbHkgPSBiYXNlX2ZhbWlseSkgKw0KICAgIHRoZW1lKA0KICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmF5IiksDQogICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLA0KICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIiMwMDFkNjAiLCBjb2xvciA9ICIjMDAxMTNhIiwgc2l6ZSA9MC41KSwNCiAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDUsIGNvbG9yID0gIndoaXRlIiksDQogICAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICAgIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gImNlbnRlciIsDQogICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKQ0KICAgICkNCn0NCg0KDQp0aGVtZV9zZXQobXlfdGhlbWUoKSkNCg0KDQp0aGVtZV9iYXJlIDwtIGZ1bmN0aW9uKGJhc2Vfc2l6ZT04LGJhc2VfZmFtaWx5PSJzYW5zIil7dGhlbWVfYncoYmFzZV9zaXplID0gYmFzZV9zaXplLCBiYXNlX2ZhbWlseSA9IGJhc2VfZmFtaWx5KSsNCiAgICB0aGVtZSgNCiAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksIA0KICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgDQogICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSksIA0KICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIA0KICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLDAsMCwwKSwgImxpbmVzIikNCiAgICApDQp9DQoNCmBgYA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmRmPXJlYWQuY3N2KCJodHRwOi8vdmluY2VudGFyZWxidW5kb2NrLmdpdGh1Yi5pby9SZGF0YXNldHMvY3N2L01BU1MvYmlvcHN5LmNzdiIpJT4lYXNfdGliYmxlKCklPiUuWyxjKDM6MTIpXSU+JW5hLm9taXQoKQ0KDQpuYW1lcyhkZik9YygiY2x1bXB0aGlja25lc3MiLA0KICAgICAgICAgICAgIlNpemVVbmlmb3JtaXR5IiwNCiAgICAgICAgICAgICJTaGFwZVVuaWZvcm1pdHkiLA0KICAgICAgICAgICAgIk1hcmdpbl9hZGhlc2lvbiIsDQogICAgICAgICAgICAiRXBpQ2VsbFNpemUiLA0KICAgICAgICAgICAgIkJhcmVudWNsZWkiLA0KICAgICAgICAgICAgIkJsYW5kQ2hyb21hdGluIiwNCiAgICAgICAgICAgICJOb3JtYWxOdWNsZW9saSIsDQogICAgICAgICAgICAiTWl0b3NlcyIsDQogICAgICAgICAgICAiQ2xhc3MiDQopDQoNCmxpYnJhcnkoY2FyZXQpDQoNCnNldC5zZWVkKDEyMzQpDQppZHRlc3Q9Y2FyZXQ6OmNyZWF0ZURhdGFQYXJ0aXRpb24oeT1kZiRDbGFzcywgcD05OS82ODMsbGlzdD1GQUxTRSkNCg0KdHJhaW5zZXQ9ZGZbLWlkdGVzdCxdDQp0ZXN0c2V0PWRmW2lkdGVzdCxdDQpgYGANCg0KIyBE4buxbmcgbW9kZWwgUmFuZG9tIEZvcmVzdA0KDQpUYSBz4bq9IGThu7FuZyBtb2RlbCBi4bqxbmcgcGFja2FnZSByYW5kb21Gb3JlcnN0LCB24bubaSDEkeG6t2MgdMOtbmggbcO0IGjDrG5oIG5oxrAgc2F1OiBU4buVbmcgc+G7kSBjw6J5ID0gNTAwLCBt4buXaSBjw6J5IHPhur0gZMO5bmcgbmfhuqt1IG5oacOqbiAzLzkgYmnhur9uLCBjaOG6vyDEkeG7mSB0w6FpIGNo4buNbiBt4bqrdSBjw7MgcmVwbGFjZS4gTMawdSDDvTogdMO5eSBjaOG7iW5oIGxvY2FsSW1wIHBo4bqjaSDEkcaw4bujYyBjaHV54buDbiBzYW5nICJUUlVFIiDEkeG7gyBjw7MgdGjhu4MgdGjhu7FjIGhp4buHbiBjw6FjIHBow6lwIHTDrW5oIGxpw6puIHF1YW4gxJHhur9uIHZhcmlhYmxlIGltcG9ydGFuY2Ugc2F1IG7DoHkuDQoNCk5ndXnDqm4gbmjDom4gY+G7p2Egdmnhu4djIHThuqFvIHJhIG3hu5l0IHLhu6tuZyBjw6J5IGzhu5tuIG5oxrAgduG6rXksIHbDrCB0YSBtdeG7kW4gcGjDom4gYmnhu4d0IG5o4buvbmcgbmjDs20gYmnhur9uIHPhu5EgcXVhbiB0cuG7jW5nIHbDoCB0xrDGoW5nIHTDoWMgY8OzIHRo4buDIGdp4buvYSBjaMO6bmcuIA0KDQpNw7QgaMOsbmggxJHGsOG7o2MgdOG6oW8gcmEga2jDoSBuaGFuaCBjaMOzbmc6IEtoaSBraeG7g20gxJHhu4tuaCBtw7QgaMOsbmggdHLDqm4gY2jDrW5oIHThuq1wIFRyYWluc2V0OiDEkeG7mSBjaMOtbmggeMOhYyBj4bunYSBuw7MgcuG6pXQgY2FvLCB24bubaSB04buJIGzhu4cgc2FpIGzhuqdtIGNo4buJIGPDsyAyLjYlIGNobyBVIGzDoG5oIHTDrW5oIHbDoCAzLjklIGNobyBVIMOhYyB0w61uaC4NCg0KS2hpIGtp4buDbSB0cmEgbOG6oWkgdHLDqm4gZOG7ryBsaeG7h3UgxJHhu5ljIGzhuq1wIChUZXN0c2V0KSwgbcO0IGjDrG5oIMSR4bqhdCDEkeG7mSBjaMOtbmggeMOhYyBy4bqldCBjYW8gduG7m2kgQkFDPTk1LjYlLCBGc2NvcmU9OTQuMyUuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQoNCnJmbW9kPXJhbmRvbUZvcmVzdChDbGFzc34uLA0KICAgICAgICAgICAgICAgICAgIGRhdGE9dHJhaW5zZXQsDQogICAgICAgICAgICAgICAgICAgbnRyZWU9NTAwLA0KICAgICAgICAgICAgICAgICAgIG10cnk9c3FydCg5KSwNCiAgICAgICAgICAgICAgICAgICByZXBsYWNlPVRSVUUsDQogICAgICAgICAgICAgICAgICAgbG9jYWxJbXA9VFJVRQ0KICAgICAgICAgICAgICAgICAgICkNCg0KcmZtb2QNCg0KdGVzdHByZWQ9cHJlZGljdChyZm1vZCx0ZXN0c2V0KQ0KDQpjb25mdXNpb25NYXRyaXgoYXMudmVjdG9yKHRlc3RwcmVkKSwNCiAgICAgICAgICAgICAgICByZWZlcmVuY2U9dGVzdHNldCRDbGFzcywNCiAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9Im1hbGlnbmFudCIsDQogICAgICAgICAgICAgICAgbW9kZT0iZXZlcnl0aGluZyIpDQpgYGANCg0KU2F1IMSRw6J5IGzDoCBz4buxIGRhbyDEkeG7mW5nIGPhu6dhIMSR4buZIGNow61uaCB4w6FjIG3DtCBow6xuaCBraGkgdMSDbmcga8OtY2ggdGjGsOG7m2MgdOG7qyAwIMSR4bq/biA1MDAgY8OieS4gTcO0IGjDrG5oIGLhuq90IMSR4bqndSDhu5VuIMSR4buLbmgga+G7gyB04burIDMwMCBjw6J5IHRy4bufIGzDqm46DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KIyBMZWFybmluZyBjdXJ2ZQ0KDQpwbG90KHJmbW9kLCANCiAgICAgbWFpbiA9ICJSRiBMZWFybmluZyBjdXJ2ZSIpDQpsZWdlbmQoInRvcHJpZ2h0IiwgDQogICAgICAgYygiZXJyb3IgZm9yICdtYWxpZ25hbnQgY2xhc3MnIiwgDQogICAgICAgICAibWlzY2xhc3NpZmljYXRpb24gZXJyb3IiLCANCiAgICAgICAgICJlcnJvciBmb3IgJ2JlbmlnbiBjbGFzcyciKSwgDQogICAgICAgbHR5ID0gYygxLDEsMSksIA0KICAgICAgIGNvbCA9IGMoImdyZWVuIiwgImJsYWNrIiwgInJlZCIpKQ0KYGBgDQoNClBhY2thZ2UgY2FyZXQgY2hvIHBow6lwIGto4bqjbyBzw6F0IHZhaSB0csOyIGPhu6dhIHThu6tuZyBiaeG6v24gdHJvbmcgbcO0IGjDrG5oICh2YXJpYWJsZSBJbXBvcnRhbmNlKSwgbmjGsCBzYXU6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdmFySW1wUGxvdChyZm1vZCkNCg0KdmFySW1wKHJmbW9kKSU+JWFzLmRhdGEuZnJhbWUoKSU+JQ0KICBtdXRhdGUoRmVhdHVyZT1yb3duYW1lcyguKSklPiUNCiAgZ2F0aGVyKGJlbmlnbixtYWxpZ25hbnQsa2V5PSJMYWJlbCIsdmFsdWU9IkltcG9ydGFuY2UiKSU+JQ0KICBtdXRhdGUoSW1wb3J0YW5jZT0xMDAqSW1wb3J0YW5jZS9tYXgoLiRJbXBvcnRhbmNlKSklPiUNCiAgZ2dwbG90KGFlcyh4PXJlb3JkZXIoRmVhdHVyZSxJbXBvcnRhbmNlKSwNCiAgICAgICAgICAgICB5PUltcG9ydGFuY2UsDQogICAgICAgICAgICAgZmlsbD1JbXBvcnRhbmNlLA0KICAgICAgICAgICAgIGNvbG9yPUltcG9ydGFuY2UpKSsNCiAgZ2VvbV9zZWdtZW50KGFlcyh4PXJlb3JkZXIoRmVhdHVyZSxJbXBvcnRhbmNlKSwNCiAgICAgICAgICAgICAgIHhlbmQ9RmVhdHVyZSwNCiAgICAgICAgICAgICAgIHk9MCwNCiAgICAgICAgICAgICAgIHllbmQ9SW1wb3J0YW5jZSksDQogICAgICAgICAgICAgICBzaXplPTEsDQogICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpKw0KICBnZW9tX3BvaW50KHNpemU9MixzaG93LmxlZ2VuZCA9IEYpKw0KICBzY2FsZV94X2Rpc2NyZXRlKCJGZWF0dXJlcyIpKw0KICBjb29yZF9mbGlwKCkrDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSJibHVlIixoaWdoPSJyZWQiKSsNCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93PSJibHVlIixoaWdoPSJyZWQiKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKEltcG9ydGFuY2UsMSkpLA0KICAgICAgICAgICAgdmp1c3Q9LTAuNSxzaXplPTMpKw0KICBmYWNldF93cmFwKH5MYWJlbCxuY29sPTIsc2NhbGVzPSJmcmVlIikrDQogIHRoZW1lX2J3KGJhc2VfZmFtaWx5ID0gIm1vbm8iLDgpDQpgYGANCg0KTeG7mXQgYmnhur9uIHPhu5EgdOG7kXQgKGPDsyB2YWkgdHLDsiBxdWFuIHRy4buNbmcsIMSRw7NuZyBnw7NwIMSRw6FuZyBr4buDIHbDoG8gdmnhu4djIHBow6JuIGxv4bqhaSkgc+G6vSBjw7MgdOG6p24gc3XhuqV0IGhp4buHbiBkaeG7h24gY2FvIGjGoW4gdHJvbmcgbcO0IGjDrG5oICh4deG6pXQgaGnhu4duIHRyb25nIG5o4buvbmcgY8OieSB04buRdCBuaOG6pXQsIHbDoCB0xrDGoW5nIOG7qW5nIHbhu5tpIG5oaeG7gXUgbm9kZXMpLkJp4buDdSDEkeG7kyB0csOqbiBjaG8gdGjhuqV5OiBCaeG6v24gQmFyZW51Y2xlaSBsw6AgcXVhbiB0cuG7jW5nIG5o4bqldCwgY8OybiBiaeG6v24gTWl0b3NlcyDDrXQgcXVhbiB0cuG7jW5nIG5o4bqldC4gVHV5IG5oacOqbiDEkcOieSBsw6Agdmnhu4djIGR1eSBuaOG6pXQgdGEgY8OzIHRo4buDIGzDoG0gdHLGsOG7m2Mga2lhLg0KDQojIFBhY2thZ2UgcmFuZG9tRm9yZXN0RXhwbGFpbmVyDQoNCiMjIFZhaSB0csOyIGPhu6dhIGJp4bq/biB0cm9uZyBtw7QgaMOsbmgNCg0KUGFja2FnZSByYW5kb21Gb3Jlc3RFeHBsYWluZXIgY2hvIHBow6lwIGto4bqjbyBzw6F0IHPDonUgaMahbiB24buBIHZhaSB0csOyIGPhu6dhIHThu6tuZyBiaeG6v24gdHJvbmcgbcO0IGjDrG5oIFJGLiDEkOG6p3UgdGnDqm4gbMOgIGvhur90IHF14bqjIMSR4buLbmggZGFuaCwgdGEgY8OzIHRo4buDIGzhu41jIHJhIGRhbmggc8OhY2ggSyBiaeG6v24gcXVhbiB0cuG7jW5nIG5o4bqldCDEkcOjIHRoYW0gZ2lhIHbDoG8gbcO0IGjDrG5oOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkocmFuZG9tRm9yZXN0RXhwbGFpbmVyKQ0KDQojIDUgbW9zdCBJbXAgdmFyDQoNCnJmbW9kJT4lDQogIGltcG9ydGFudF92YXJpYWJsZXMoaz01LHRpZXNfYWN0aW9uID0gImRyYXciKQ0KYGBgDQoNClRow60gZOG7pSwgaz01IHTGsMahbmcg4bupbmcgNSBiaeG6v24gcXVhbiB0cuG7jW5nIG5o4bqldCwgdGhlbyB0aOG7qSB04buxIHThu6sgVHLDoWkgc2FuZyBwaOG6o2kuIEvhur90IHF14bqjIG7DoHkgY8OzIHRo4buDIHLhuqV0IHF1YW4gdHLhu41uZywgbuG6v3UgYuG6oW4gY8OzIHRyb25nIHRheSDEkeG6v24gaMOgbmcgbmfDoG4sIHRo4bqtbSBjaMOtIGjDoG5nIHRyxINtIG5nw6BuIGJp4bq/biBjaOG7qSBraMO0bmcgcGjhuqNpIGNo4buJIGPDsyA5LiBOaMawIHbhuq15LCBNw7QgaMOsbmggUmFuZG9tIEZvcmVzdCBjxaluZyBsw6AgbeG7mXQgZ2nhuqNpIHBow6FwIG5o4bqxbSBjaOG7jW4gbOG7jWMgYmnhur9uIHPhu5EgY2hvIGLDoGkgdG/DoW4gcGjDom4gbG/huqFpLCBi4bqldCBr4buDIGFsZ29yaXRobSBtw6AgYuG6oW4gbXXhu5FuIMOhcCBk4bulbmcgc2F1IMSRw7MgbMOgIGfDrC4gVuG7m2kgdOG7kWMgxJHhu5kgbmhhbmggdGjhur8gbsOgeSwgYuG6oW4gaG/DoG4gdG/DoG4gY8OzIHRo4buDIGTDuW5nIFJGIMSR4buDIHRoxINtIGTDsiBk4buvIGxp4buHdSB2w6AgY2jhu41uIGzhu41jIGJp4bq/biBz4buRLg0KDQpIxqFuIHRo4bq/IG7hu69hLCBwYWNrYWdlIHJhbmRvbUZvcmVzdEV4cGxhaW5lciBjw7JuIGN1bmcgY+G6pXAgY2hvIGNow7puZyB0YSDEkeG6v24gNiBjaOG7iSBz4buRIHRo4buRbmcga8OqIMSR4buDIMSRbyBsxrDhu51uZyB2YWkgdHLDsiBxdWFuIHRy4buNbmcgY+G7p2EgdOG7q25nIGJp4bq/biwgbmjGsCBzYXU6DQoNCjEpIE1pbmltYWwgZGVwdGguIA0KDQpDaGnhu4F1IHPDonUgY+G7p2EgY8OieSBxdXnhur90IMSR4buLbmggbMOgIGvDrWNoIHRoxrDhu5tjIGPhu6dhIGNvbiDEkcaw4budbmcgZMOgaSBuaOG6pXQgdOG7qyBy4buFIGPDonkgKGJp4bq/biBz4buRIMSR4bqndSB0acOqbikgxJHhur9uIGzDoSBjw6J5ICht4buZdCBr4bq/dCBxdeG6oyBwaMOibiBsb+G6oWksIHNhdSBuaGnhu4F1IGzhuqduIHBow6JuIG5ow6FuaCkuIFRyZWUgZGVwdGggdGjhu4MgaGnhu4duIG3hu6ljIMSR4buZIHBo4bupYyB04bqhcCBj4bunYSBj4bqldSB0csO6YyBtw7QgaMOsbmguIA0KDQpN4buXaSBiaeG6v24gWGogbuG6v3UgaGnhu4duIGRp4buHbiB0cm9uZyBt4buZdCBjw6J5LCBz4bq9IGPDsyBt4buZdCBnacOhIHRy4buLIG1pbmltYWwgZGVwdGggeMOhYyDEkeG7i25oLCBsw6AgbeG7mXQgc+G7kSBuZ3V5w6puIGTGsMahbmcgKHRow60gZOG7pSB04burIDAgxJHhur9uIDcpLiBO4bq/dSBYaiBraMO0bmcgIGhp4buHbiBkaeG7h24sIHRow6wgZ2nDoSB0cuG7iyBuw6B5IMSRxrDhu6NjIHTDrW5oIGzDoCBtaXNzaW5nIHZhbHVlIChOQSkuIE5oxrAgduG6rXkgdHJvbmcgbcO0IGjDrG5oIFJGIHbhu5tpIDUwMCBjw6J5LCBwaMOibiBi4buRIGPhu6dhIGdpw6EgdHLhu4sgTWluX2RlcHRoIGNobyBt4buXaSBiaeG6v24gc+G7kSBjw7MgdGjhu4MgY3VuZyBj4bqlcCB0aMO0bmcgdGluIHbhu4EgOiB04bqnbiBzdeG6pXQgaGnhu4duIGRp4buHbiBj4bunYSBiaeG6v24gc+G7kSDEkcOzLCBz4buxIHBo4bupYyB04bqhcCBj4bunYSBxdXkgbHXhuq10IG3DoCBiaeG6v24gxJHDsyB0aGFtIGdpYSAobWluX2RlcHRoIGNhbykuIMSQw6J5IGzDoCBwaMOibiBi4buRIGPhu6dhIG3hu5l0IHPhu5Egbmd1ecOqbiBkxrDGoW5nLCB2w6AgdGEgY8OzIHRo4buDIHTDrW5oIGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmggKG1lYW4gb2YgbWluX2RlcHRoKSBjaG8gdOG7q25nIGJp4bq/bi4NCg0KTMawdSDDvSA6IEtoaSBjaOG7jW4gY2jhur8gxJHhu5kgbOG6pXkgbeG6q3UgbMOgIMKrIHJlbGV2YW50IHRyZWVzIMK7LCBnacOhIHRy4buLIHRydW5nIGLDrG5oIGNo4buJIHTDrW5oIGThu7FhIHRyw6puIHPhu5EgbMaw4bujbmcgY8OieSBtw6AgYmnhur9uIMSRw7MgY8OzIHRoYW0gZ2lhLCBraMO0bmcga+G7gyBjw6FjIG1pc3NpbmcgdmFsdWUuDQoNCjIpIEFjY3VyYWN5IGRlY3JlYXNlIGPhu6dhIG3hu5l0IGJp4bq/biBYaiDEkW8gbMaw4budbmcgc+G7sSBz4buldCBnaeG6o20gxJHhu5kgY2jDrW5oIHjDoWMgY+G7p2EgbcO0IGjDrG5oIGPDonkga2hpIFhqIGLhu4sgaG/DoW4gY2h1eeG7g24uIE3hu5l0IGPDoWNoIGdpw6FuIHRp4bq/cCwgY2jhu4kgc+G7kSBuw6B5IGNobyB0aOG6pXkgbeG7qWMgxJHhu5kgcXVhbiB0cuG7jW5nIGPhu6dhIGJp4bq/biBYaiB0cm9uZyBtw7QgaMOsbmguDQoNCjMpIEdpbmkgZGVjcmVhc2UgY+G7p2EgYmnhur9uIFhqIMSRbyBsxrDhu51uZyBz4buxIHN1eSBnaeG6o20gdHJ1bmcgYsOsbmggY+G7p2EgR2luaSBpbXB1cml0eSB04bqhaSBt4buXaSBub2RlIHTGsMahbmcg4bupbmcgduG7m2kgbmjhu69uZyBs4bqnbiBwaMOibiBjaGlhIGThu7FhIHbDoG8gYmnhur9uIFhqIG7DoHkuIEPDsyB0aOG7gyBow6xuaCBkdW5nIGzDoCBHaW5pIGRlY3JlYXNlIGNobyB0aOG6pXkg4bqjbmggaMaw4bufbmcgcXVhbiB0cuG7jW5nIGPhu6dhIFhqIMSR4buRaSB24bubaSBxdXkgbHXhuq10IHBow6JuIG5ow7NtIGThu68gbGnhu4d1LCBsw6BtIGdp4bqjbSB4w6FjIHN14bqldCBwaOG6oW0gc2FpIGzhuqdtIGtoaSBkw6FuIG5ow6NuIGNobyBjw6FjIHRyxrDhu51uZyBo4bujcC4gDQoNCjQpIE5vX29mX3RyZWVzIDogxJHGoW4gZ2nhuqNuIGzDoCB04buVbmcgc+G7kSBjw6J5IGPDsyBoaeG7h24gZGnhu4duIGJp4bq/biBYai4gU+G7sSBoaeG7h24gZGnhu4duIGPhu6dhIFhqIHRyb25nIG5oaeG7gXUgY8OieSBjaG8gdGjhuqV5IFhqIGzDoCBt4buZdCB0acOqdSBjaMOtIHF1YW4gdHLhu41uZy4NCg0KNSkgTm8gb2Ygbm9kZXMgOiB04buVbmcgc+G7kSBub2RlIChuw7p0IHBow6JuIG5ow6FuaCkgY8OzIGxpw6puIHF1YW4gxJHhur9uIFhqIHRyb25nIG3DtCBow6xuaCBSRiwgw70gbmdoxKlhIGPhu6dhIGNo4buJIHPhu5EgbsOgeSB0xrDGoW5nIHThu7EgbmjGsCBub19vZl90cmVlcy4NClRpbWVfYV9yb290IDogdOG7lW5nIHPhu5EgY8OieSBtw6AgdHJvbmcgxJHDsyBiaeG6v24gWGogxJHGsOG7o2Mgc+G7rSBk4bulbmcgbmdheSB04burIHLhu4UgKGzhuqduIHBow6JuIG5ow6FuaCDEkeG6p3UgdGnDqm4gdHJvbmcgbcO0IGjDrG5oIGPDonkgZOG7sWEgdsOgbyBYaikuDQoNClBfdmFsdWUgOiB0cuG7iyBz4buRIHAgKDEgYsOqbikgY+G7p2Ega2nhu4NtIMSR4buLbmggYmlub21pYWwgZOG7sWEgdHLDqm4gcGjDom4gcGjhu5FpIEJpbm9taWFsICh04buVbmcgc+G7kSBub2RlcywgUChz4buRIG5vZGVzIGRvIHBow6JuIGNoaWEgWGopKS4gR2nhuqMgdGh1eeG6v3QgSDAgY+G7p2Ega2nhu4NtIMSR4buLbmggbsOgeSBjaG8gcuG6sW5nIHZp4buHYyBYaiB0aGFtIGdpYSBwaMOibiBuaMOhbmggY2jhu4kgbMOgIGRvIG5n4bqrdSBuaGnDqm4uIMSQ4bq3dCBuZ8aw4buhbmcgw70gbmdoxKlhIGNobyBwPTAuMDUsIHRhIGPDsyB0aOG7gyBiw6FjIGLhu48gZ2nhuqMgdGh1eeG6v3QgSDAgbsOgeSB2w6AgdGluIHLhurFuZyBYaiB0aOG7sWMgc+G7sSBjw7MgdmFpIHRyw7IgcXVhbiB0cuG7jW5nLg0KDQpU4burIGRhdGFmcmFtZSBjaOG7qWEgbmjhu69uZyBjaOG7iSBz4buRIGvhu4MgdHLDqm4sIHRhIGPDsyB0aOG7gyB0csOsbmggYsOgeSBr4bq/dCBxdeG6oyBkxrDhu5tpIG5oaeG7gXUgaMOsbmggdGjhu6ljIGJp4buDdSDEkeG7ky4gVuG7m2kgbeG7pWMgdGnDqnUgbeG7uSB0aHXhuq10LCBOaGkgcXV54bq/dCDEkeG7i25oIHbhur0gbOG6oWkgbmhp4buBdSBiaeG7g3UgxJHhu5MgaG/DoG4gdG/DoG4gdGjhu6cgY8O0bmcgdHJvbmcgZ2dwbG90MiwgdHV5IG5oacOqbiBi4bqhbiBjxaluZyBjw7MgdGjhu4MgZMO5bmcgbmjhu69uZyBmdW5jdGlvbiBwbG90cyBt4bq3YyDEkeG7i25oIGPhu6dhIHBhY2thZ2U6DQoNCsSQ4bqndSB0acOqbiBsw6AgYmFyZ3JhcGggY2hvIGdpw6EgdHLhu4sgdHJ1bmcgYsOsbmg6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KcmZtb2QlPiVtZWFzdXJlX2ltcG9ydGFuY2UobWVhbl9zYW1wbGUgPSAicmVsZXZhbnRfdHJlZXMiKSU+JQ0KICBhc190aWJibGUoKS0+dmltcGRmDQoNCnZpbXBkZiU+JWhlYWQoKQ0KDQp2aW1wZGYlPiVnYXRoZXIobWVhbl9taW5fZGVwdGg6cF92YWx1ZSwNCiAgICAgICAgICAgICAgICBrZXk9Ik1ldHJpYyIsDQogICAgICAgICAgICAgICAgdmFsdWU9IlZhbHVlIiklPiUNCiAgZ2dwbG90KGFlcyh4PXJlb3JkZXIodmFyaWFibGUsVmFsdWUpLA0KICAgICAgICAgICAgIHk9VmFsdWUsDQogICAgICAgICAgICAgZmlsbD1NZXRyaWMsDQogICAgICAgICAgICAgY29sb3I9TWV0cmljKSkrDQogIGdlb21fc2VnbWVudChhZXMoeD1yZW9yZGVyKHZhcmlhYmxlLFZhbHVlKSwNCiAgICAgICAgICAgICAgICAgICB4ZW5kPXZhcmlhYmxlLA0KICAgICAgICAgICAgICAgICAgIHk9MCwNCiAgICAgICAgICAgICAgICAgICB5ZW5kPVZhbHVlKSwNCiAgICAgICAgICAgICAgIHNpemU9MSwNCiAgICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRikrDQogIGdlb21fcG9pbnQoc2l6ZT0yLHNob3cubGVnZW5kID0gRikrDQogIHNjYWxlX3hfZGlzY3JldGUoIkZlYXR1cmVzIikrDQogIGNvb3JkX2ZsaXAoKSsNCiAgZmFjZXRfd3JhcCh+TWV0cmljLG5jb2w9NCxzY2FsZXM9ImZyZWUiKSsNCiAgdGhlbWVfYncoYmFzZV9mYW1pbHkgPSAibW9ubyIsOCkNCiAgDQpgYGANCg0KVMawxqFuZyBxdWFuIHBoaSB0aGFtIHPhu5EgKHRo4bupIGjhuqFuZyk6DQoNCkJp4buDdSDEkeG7kyBuw6B5IGN1bmcgY+G6pXAgY+G6oyAzIHRow7RuZyB0aW46IMSQ4bq3YyB0w61uaCBwaMOibiBi4buRIGPhu6dhIG3hu5dpIGNo4buJIHPhu5Egc2F1IGtoaSDEkcaw4bujYyB44bq/cCBo4bqhbmcgKHRy4burIHAgdmFsdWUpLCBo4buHIHPhu5EgdMawxqFuZyBxdWFuIFNwZWFybWFuIGLhuq90IGPhurdwIGdp4buvYSBjaMO6bmcsIHbDoCBzY2F0dGVyIGRvdCBwbG90IGPhu6dhIHRo4bupIGjhuqFuZy4gDQoNCmBgYHtyfQ0KcGxvdF9pbXBvcnRhbmNlX3JhbmtpbmdzKHZpbXBkZikNCg0KYGBgDQoNClTGsMahbmcgcXVhbiB0dXnhur9uIHTDrW5oIGdp4buvYSBjw6FjIGNo4buJIHPhu5E6DQoNCkJp4buDdSDEkeG7kyBuw6B5IGN1bmcgY+G6pXAgY+G6oyAzIHRow7RuZyB0aW46IMSQ4bq3YyB0w61uaCBwaMOibiBi4buRIGPhu6dhIG3hu5dpIGNo4buJIHPhu5EgKHRy4burIHAgdmFsdWUpLCBo4buHIHPhu5EgdMawxqFuZyBxdWFuIFBlYXJzb24gYuG6r3QgY+G6t3AgZ2nhu69hIGNow7puZywgdsOgIHNjYXR0ZXIgZG90IHBsb3QuIE3hu5dpIMSRaeG7g20gdHJvbmcgYmnhu4N1IMSR4buTIHTGsMahbmcg4bupbmcgduG7m2kgMSBiaeG6v24uDQoNCmBgYHtyfQ0KcGxvdF9pbXBvcnRhbmNlX2dncGFpcnModmltcGRmKQ0KDQpgYGANCg0KxJDDonkgbMOgIGJp4buDdSDEkeG7kyB0xrDGoW5nIHThu7EgZG8gTmhpIHbhur0gdGjhu6cgY8O0bmcgZOG7sWEgdsOgbyBkYXRhc2V0Og0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiNDb3JyZWxhdGlvbiBtYXRyaXgNCg0KbGlicmFyeShHR2FsbHkpDQoNCnBsb3RmdW5jTG93IDwtIGZ1bmN0aW9uKGRhdGEsbWFwcGluZyl7DQogIHAgPC0gZ2dwbG90KGRhdGEgPSBkYXRhLG1hcHBpbmc9bWFwcGluZykrDQogICAgZ2VvbV9wb2ludChhZXMoZmlsbD1yb3duYW1lcyhkYXRhKSwNCiAgICAgICAgICAgICAgICAgICBjb2xvcj1yb3duYW1lcyhkYXRhKSksDQogICAgICAgICAgICAgICBzaGFwZT0yMSxzaXplPTIsDQogICAgICAgICAgICAgICBhbHBoYT0wLjgsDQogICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpKw0KICAgIGdlb21fbGluZShhZXMoY29sb3I9cm93bmFtZXMoZGF0YSksDQogICAgICAgICAgICAgICAgICBncm91cD0xKSkrDQogICAgdGhlbWVfYncoYmFzZV9mYW1pbHkgPSAibW9ubyIsNikNCiAgcA0KfQ0KDQpwbG90ZnVuY21pZCA8LSBmdW5jdGlvbihkYXRhLG1hcHBpbmcpew0KICBwIDwtIGdncGxvdChkYXRhID0gZGF0YSxtYXBwaW5nPW1hcHBpbmcpKw0KICAgIGdlb21faGlzdG9ncmFtKGFlcyhmaWxsPXJvd25hbWVzKGRhdGEpLA0KICAgICAgICAgICAgICAgICAgIGNvbG9yPXJvd25hbWVzKGRhdGEpKSwNCiAgICAgICAgICAgICAgICAgICBhbHBoYT0wLjUpKw0KICAgIHRoZW1lX2J3KGJhc2VfZmFtaWx5ID0gIm1vbm8iLDYpDQogIHANCn0NCg0KZ2dwYWlycyh2aW1wZGYsY29sdW1ucz0yOjgsDQogICAgICAgIGxvd2VyPWxpc3QoY29udGludW91cz1wbG90ZnVuY0xvdyksDQogICAgICAgIGRpYWc9bGlzdChjb250aW51b3VzPXBsb3RmdW5jbWlkKSkNCmBgYA0KDQpSacOqbmcgY2jhu4kgc+G7kSBNaW5pbWFsIGRlcHRoIGRpc3RyaWJ1dGlvbiBjw7MgdGjhu4MgxJHGsOG7o2MgdHLDrG5oIGLDoHkgYuG6sW5nIGjDrG5oIHbhur0gc2F1Og0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiNNaW5pbWFsIGRlcHRoIGRpc3RyaWJ1dGlvbg0KDQptaW5kZXB0aGRmPXJmbW9kJT4lbWluX2RlcHRoX2Rpc3RyaWJ1dGlvbigpJT4lYXNfdGliYmxlKCkNCg0KbWluZGVwdGhkZiU+JWdncGxvdChhZXMoeD1taW5pbWFsX2RlcHRoLA0KICAgICAgICAgICAgICAgICAgICAgICAgZmlsbD12YXJpYWJsZSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbD12YXJpYWJsZSkpKw0KICBnZW9tX2hpc3RvZ3JhbShhbHBoYT0wLjUpKw0KICBmYWNldF93cmFwKH52YXJpYWJsZSxuY29sPTMsc2NhbGVzPSJmcmVlIikrDQogIG15X3RoZW1lKCkNCg0KcmZtb2QlPiVtaW5fZGVwdGhfZGlzdHJpYnV0aW9uKCklPiUNCiAgcGxvdF9taW5fZGVwdGhfZGlzdHJpYnV0aW9uKG1lYW5fc2FtcGxlPSJhbGxfdHJlZXMiKSsNCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iU3BlY3RyYWwiLGRpcmVjdGlvbiA9IC0xKQ0KDQpgYGANCg0KIyBUxrDGoW5nIHTDoWMgZ2nhu69hIDIgYmnhur9uIHRyb25nIG3DtCBow6xuaCBSRg0KDQpLaMO0bmcgY2jhu4kgxJFvIGzGsOG7nW5nIHZhaSB0csOyIGPhu6dhIHThu6tuZyBiaeG6v24gcmnDqm5nIGzhurssIHBhY2thZ2UgbsOgeSBjw7JuIGNobyBwaMOpcCBjaMO6bmcgdGEga2jhuqNvIHPDoXQgduG7gSB0xrDGoW5nIHTDoWMgZ2nhu69hIDIgYmnhur9uLiBT4buxIHTGsMahbmcgdMOhYyB44bqjeSByYSBraGkgMiBiaeG6v24gY8O5bmcgaGnhu4duIGRp4buHbiB0cm9uZyBt4buZdCBtw7QgaMOsbmggY8OieSB2w6AgdGhhbSBnaWEgdsOgbyBz4buxIHBow6JuIG5ow6FuaCDhu58gY8OhYyBub2Rlcy4gDQoNCkJp4buDdSDEkeG7kyBzYXUgxJHDonkgdHLDrG5oIGLDoHkgbWVhbiBtaW5pbWFsIGRlcHRoIHTDrW5oIGNobyAzMCBj4bq3cCB0xrDGoW5nIHTDoWMgbuG7lWkgYuG6rXQgbmjhuqV0Og0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiMgSW50ZXJhY3Rpb25zDQoNCm1pbmRwaW50PW1pbl9kZXB0aF9pbnRlcmFjdGlvbnMocmZtb2QsbWVhbl9zYW1wbGUgPSAicmVsZXZhbnRfdHJlZXMiLCANCiAgICAgICAgICAgICAgICAgICAgICAgdW5jb25kX21lYW5fc2FtcGxlID0gInJlbGV2YW50X3RyZWVzIikNCg0KbGlicmFyeSh2aXJpZGlzKQ0KDQptaW5kcGludCU+JQ0KICBwbG90X21pbl9kZXB0aF9pbnRlcmFjdGlvbnMoKSsNCiAgc2NhbGVfZmlsbF92aXJpZGlzKCJBIikNCg0KYGBgDQoNCktoaSBxdWFuIHPDoXQgbuG7mWkgZHVuZyBj4bunYSBkYXRhZnJhbWUga+G6v3QgcXXhuqMgcGjDom4gdMOtY2ggdMawxqFuZyB0w6FjIGJp4bq/biwgTmhpIG5o4bqtbiB0aOG6pXkgbsOzIGPDsyBk4bqhbmcgMSBuZXR3b3JrLCB04bupYyBsw6AgdGEgY8OybiBjw7MgdGjhu4MgdHLDrG5oIGLDoHkgbeG6oW5nIGzGsOG7m2kgdMawxqFuZyB0w6FjIGdp4buvYSBjw6FjIGJp4bq/biBi4bqxbmcgbeG7mXQgYmnhu4N1IMSR4buTIG3huqFuZyAobmV0d29yayBncmFwaCkuIMSQ4buDIGzDoG0gdmnhu4djIG7DoHksIE5oaSBr4bq/dCBo4bujcCBwYWNrYWdlIGlncmFwaCB2w6AgcGFja2FnZSBnZ3JhcGggduG7m2kgbmhhdS4NCg0KxJDhuqd1IHRpw6puIHRhIGzhu41jIGLhu48gbmjhu69uZyBj4bq3cCB0xrDGoW5nIHTDoWMgbsOgbyBjw7MgdOG6p24gc+G7kSB4deG6pXQgaGnhu4duIDwgMTgwIGzhuqduIHNhdSDEkcOzIHRhIGNodXnhu4NuIHRvw6BuIGLhu5kgZ2RwIGRhdGFmcmFtZSB0aMOgbmggMSBuZXR3b3JrLCBzYXUgxJHDsyBkw7luZyBnZ3JhcGggxJHhu4MgbcOjIGjDs2EgbcOgdSBz4bqvYyBjaG8gY8OhYyBsacOqbiBr4bq/dCB0aGVvIHThuqduIHN14bqldCBoaeG7h24gZGnhu4duLCBtw6B1IHhhbmgga8OpbSBoxqFuIG3DoHUgxJHhu48uIA0KDQpL4bq/dCBxdeG6oyBjdeG7kWkgY8O5bmcgbMOgIG3hu5l0IG5ldHdvcmsgbmjGsCB0cm9uZyBow6xuaDoNCg0KYGBge3J9DQptaW5kcGludCU+JWhlYWQoKQ0KYGBgDQoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGlncmFwaCkNCmxpYnJhcnkoZ2dyYXBoKQ0KDQpnZGY9ZmlsdGVyKG1pbmRwaW50LA0KICAgICAgICAgICBvY2N1cnJlbmNlcz49MTgwKQ0KDQpncmFwaDwtZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGdkZlssYygxOjMsNCw2KV0pDQoNCg0KZ2dyYXBoKGdyYXBoLA0KICAgICAgIGxheW91dCA9ICdraycsDQogICAgICAgY2lyY3VsYXI9RikrDQogIGdlb21fZWRnZV9saW5rKGFlcyhjb2xvdXIgPSBvY2N1cnJlbmNlcyksDQogICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBULHdpZHRoPTEsYWxwaGE9MC43KSsNCiAgZ2VvbV9ub2RlX2xhYmVsKGFlcyhsYWJlbCA9IG5hbWUpKSsNCiAgY29vcmRfZml4ZWQoKSsNCiAgc2NhbGVfZWRnZV9jb2xvcl9ncmFkaWVudChoaWdoPSIjZmYwMDNmIixsb3c9IiMwMDk0ZmYiKSsNCiAgc2NhbGVfZWRnZV93aWR0aF9jb250aW51b3VzKCkrDQogIHRoZW1lX2JhcmUoKQ0KDQpgYGANCg0KTmV0d29yayBuw6B5IGNobyBjaMO6bmcgdGEgYmnhur90IDMgdGjDtG5nIHRpbjogTeG7qWMgxJHhu5kgdMawxqFuZyB0w6FjIGPhu6dhIGPDoWMgYmnhur9uIHRyb25nIG3DtCBow6xuaCA6IE5o4buvbmcgYmnhur9uIGPDsyB0xrDGoW5nIHTDoWMgY2jhurd0IGNo4bq9IHPhur0gZ29tIGzhuqFpIGfhuqduIG5oYXUgdGjDoG5oIG3hu5l0IGPhu6VtIHbDoCBsacOqbiBr4bq/dCBjw7MgbcOgdSDEkeG7jywgQmnhur9uIEJhcmVudWNsZWkgbuG6sW0g4bufIHRydW5nIHTDom0gxJHGsOG7o2MgeGVtIGzDoCBxdWFuIHRy4buNbmcgbmjhuqV0LCBiaeG6v24gTWl0b3NlIGNo4buJIGPDsyB0xrDGoW5nIHTDoWMgduG7m2kgU2l6ZSBVbmlmb3JtaXR5IHbDoCDDrXQgcXVhbiB0cuG7jW5nIG5o4bqldC4gTmjhuq9jIGzhuqFpOiBN4buZdCB0cm9uZyBuaOG7r25nIGzhu6NpIHRo4bq/IGPhu6dhIG3DtCBow6xuaCBjw6J5IHF1eeG6v3QgxJHhu4tuaCDEkcOzIGzDoCBuw7MgY2hvIHBow6lwIG3DtCBow6xuaCBow7NhIG5o4buvbmcgcXVhbiBo4buHIHTGsMahbmcgdMOhYyBwaOG7qWMgdOG6oXAgZ2nhu69hIG5oaeG7gXUgYmnhur9uLCB0aOG6rW0gY2jDrSB2xrDhu6N0IHJhIG5nb8OgaSB04bqnbSBoaeG7g3UgYmnhur90IGPhu6dhIG5nxrDhu51pIGThu7FuZyBtw7QgaMOsbmg7IHRp4bq/cCB0aGVvIC0gxJHDsyBsw6Aga2jhuqMgbsSDbmcgbOG7jWMgYuG7jyB04buxIMSR4buZbmcgbmjhu69uZyBiaeG6v24ga2jDtG5nIGPDsyB2YWkgdHLDsiBxdWFuIHRy4buNbmcgxJHhu5FpIHbhu5tpIG3hu6VjIHRpw6p1IG3DoCB0YSBj4bqnbiBuaOG6r20gdOG7m2kuIERvIMSRw7MsIGtoaSAyIGhheSAzIGJp4bq/biDEkeG7k25nIHRo4budaSB4deG6pXQgaGnhu4duIGzhurdwIMSRaSBs4bq3cCBs4bqhaSB0cm9uZyBuaGnhu4F1IG3DtCBow6xuaCBjw6J5IGzDoCBt4buZdCBk4bqldSBoaeG7h3UgY2hvIHRo4bqleTogMSkgQ2jDum5nIHF1YW4gdHLhu41uZywgMikgQ2jDum5nIGPDsyBsacOqbiBo4buHIHbhu5tpIG5oYXUuIA0KDQpOaMawIHbhuq15LCBjaMO6bmcgdGEgY8OzIHRo4buDIMOhcCBk4bulbmcgaMOsbmggdGjhu6ljIG7DoHkgbmjGsCBt4buZdCBnaeG6o2kgcGjDoXAgdGhheSB0aOG6vyBjaG8gY29ycmVsYXRpb24gbWF0cml4IHRyb25nIG3hu5l0IG3DtCBow6xuaCB0acOqbiBsxrDhu6NuZyBo4buTaSBxdXkgdsOgIHBow6JuIGxv4bqhaSwgYmnhu4N1IMSR4buTIG5ldHdvcmsgdHLDqm4gxJHDonkgxJHGsGEgcmEgYuG7qWMgdHJhbmggdG/DoG4gY+G6o25oIHbhu4EgbGnDqm4gaOG7hyBnaeG7r2EgY8OhYyBiaeG6v24gc+G7kSBxdWFuIHRy4buNbmcgbmjhuqV0IGPDuW5nIGjGsOG7m25nIHThu5tpIG3hu5l0IG3hu6VjIHRpw6p1IHjDoWMgxJHhu4tuaC4NCg0KIyBNdWx0aXdheSBpbXBvcnRhbmNlIHBsb3QNCg0K4bueIHRyw6puLCB0YSDEkcOjIHRo4butIHRyw6xuaCBiw6B5IHTGsMahbmcgcXVhbiBi4bqvdCBj4bq3cCBnaeG7r2EgMiBjaOG7iSBz4buRICDEkW8gbMaw4budbmcgxJHhu5kgcXVhbiB0cuG7jW5nIGPhu6dhIGPDoWMgYmnhur9uLCBk4bqhbmcgYmnhu4N1IMSR4buTIHNhdSDEkcOieSBjaG8gcGjDqXAgdHLDrG5oIGLDoHkgbmhp4buBdSB0aMO0bmcgdGluIGPDuW5nIGzDumMgdHLDqm4gbeG7mXQgYmnhu4N1IMSR4buTIDIgY2hp4buBdTogdMawxqFuZyBxdWFuIGdp4buvYSAyIGNo4buJIHPhu5EgYuG6pXQga8OsLCBwaMOibiBwaOG7kWkgY+G7p2EgY2jDum5nLCB2w6AgcF92YWx1ZSBj4bunYSBiaW5vbWlhbCB0ZXN0Lg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCiMgTXVsdGl3YXkgSW1wb3J0YW5jZQ0KDQpwbG90X211bHRpX3dheV9pbXBvcnRhbmNlKHJmbW9kLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB4X21lYXN1cmUgPSAibWVhbl9taW5fZGVwdGgiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlfbWVhc3VyZSA9ICJ0aW1lc19hX3Jvb3QiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZV9tZWFzdXJlID0gIm5vX29mX25vZGVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX25vX29mX3RyZWVzID0gMjANCiAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQpwbG90X211bHRpX3dheV9pbXBvcnRhbmNlKHZpbXBkZiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHhfbWVhc3VyZSA9ICJub19vZl9ub2RlcyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICB5X21lYXN1cmUgPSAibm9fb2Zfbm9kZXMiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZV9tZWFzdXJlID0gInBfdmFsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fbm9fb2ZfdHJlZXMgPSAyMCkNCg0KcGxvdF9tdWx0aV93YXlfaW1wb3J0YW5jZSh2aW1wZGYsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICB4X21lYXN1cmUgPSAiYWNjdXJhY3lfZGVjcmVhc2UiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgeV9tZWFzdXJlID0gImdpbmlfZGVjcmVhc2UiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZV9tZWFzdXJlID0gInBfdmFsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fbm9fb2ZfdHJlZXMgPSAyMCkNCg0KcGxvdF9tdWx0aV93YXlfaW1wb3J0YW5jZSh2aW1wZGYsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICB4X21lYXN1cmUgPSAibWVhbl9taW5fZGVwdGgiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgeV9tZWFzdXJlID0gImdpbmlfZGVjcmVhc2UiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZV9tZWFzdXJlID0gInBfdmFsdWUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fbm9fb2ZfdHJlZXMgPSAyMCkNCg0KYGBgDQoNCiMgUHJlZGljdGlvbiBpbnRlcmFjdGlvbiBwbG90DQoNCkN14buRaSBjw7luZywgcGFja2FnZSBuw6B5IGNobyBwaMOpcCAgduG6vSBt4buZdCBiaeG7g3UgxJHhu5MgZOG6oW5nIGhlYXRtYXAsIHRo4buDIGhp4buHbiBwaMOibiBi4buRIGvhur90IHF14bqjIGPhu6dhIG3DtCBow6xuaCBSYW5kb20gRm9yZXN0IHTGsMahbmcg4bupbmcgduG7m2kgc+G7sSB04buVIGjhu6NwIGPhu6dhIDIgZ2nDoSB0cuG7iyBi4bqldCBrw6wgdHLDqm4gdGhhbmcgxJFvIGPhu6dhIDIgYmnhur9uIHPhu5EgdMO5eSBjaOG7jW4uICANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpyZm1vZCU+JXBsb3RfcHJlZGljdF9pbnRlcmFjdGlvbih0ZXN0c2V0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAiQmFyZW51Y2xlaSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICJTaGFwZVVuaWZvcm1pdHkiLDMwKStzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uPSJBIikrY29vcmRfZXF1YWwoKQ0KDQpyZm1vZCU+JXBsb3RfcHJlZGljdF9pbnRlcmFjdGlvbih0ZXN0c2V0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjbHVtcHRoaWNrbmVzcyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNpemVVbmlmb3JtaXR5IiwzMCkrc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbj0iQSIpK2Nvb3JkX2VxdWFsKCkNCmBgYA0KDQpMxrB1IMO9OiBNw6B1IHPhuq9jIHRyw6puIGJp4buDdSDEkeG7kyB0xrDGoW5nIOG7qW5nIHbhu5tpIHjDoWMgc3XhuqV0IHBow6JuIGxv4bqhaSBVIMOhYyB0w61uaCAoMCDEkeG6v24gMSksIHbDoCBr4bq/dCBxdeG6oyBuw6B5IGzDoCBj4bunYSBtw7QgaMOsbmggZ+G7k20gOSBiaeG6v24gY2jhu6kga2jDtG5nIGNo4buJIGdp4bubaSBo4bqhbiDhu58gMiBiaeG6v24gbcOgIHRhIMSRYW5nIHjDqXQuDQoNCiMgVOG7lW5nIGvhur90DQoNCkLDoGkgdGjhu7FjIGjDoG5oIMSR4bq/biDEkcOieSBsw6Aga+G6v3QgdGjDumMuIFRyb25nIGLDoGkgbsOgeSBOaGkgxJHDoyBnaeG7m2kgdGhp4buHdSB24bubaSBjw6FjIGLhuqFuIHThuqV0IGPhuqMgbmjhu69uZyB0w61uaCBuxINuZyBtw6AgcGFja2FnZSByYW5kb21Gb3Jlc3RFeHBsYWluZXIgY3VuZyBj4bqlcC4gTmjhu69uZyB0w61uaCBuxINuZyBuw6B5IMSRw6MgbeG7nyBy4buZbmcgxJHDoW5nIGvhu4Mga2jhuqMgbsSDbmcga2jhuqNvIHPDoXQgY+G6pXUgdHLDumMgY+G7p2EgbeG7mXQgbcO0IGjDrG5oIFJhbmRvbSBGb3Jlc3QuIEPDoWMgcGjGsMahbmcgcGjDoXAgbcOgIHBhY2thZ2UgbsOgeSBz4butIGThu6VuZyBiw6FtIHPDoXQgdsOgbyBjw6FjIG5ndXnDqm4gbMO9IGPhu6dhIG3DtCBow6xuaCBjw6J5IChkZWNpc2lvbiB0cmVlKSB2w6AgY3VuZyBj4bqlcCB0aMO0bmcgdGluIHBob25nIHBow7ogduG7gTogdmFpIHRyw7IgY+G7p2EgbeG7l2kgYmnhur9uIHPhu5EgdsOgIHPhu7EgdMawxqFuZyB0w6FjIGdp4buvYSBjaMO6bmcuIE5o4buvbmcgY2jhu4kgc+G7kSDEkeG7i25oIGzGsOG7o25nIHRyb25nIHBow6JuIHTDrWNoIG7DoHkgY8OzIGdpw6EgdHLhu4sgdMawxqFuZyDEkcawxqFuZyB24bubaSBo4buHIHPhu5EgaOG7k2kgcXV5IHbDoCBwX3ZhbHVlIGPhu6dhIGtp4buDbSDEkeG7i25oIHQgdHJvbmcgbeG7mXQgbcO0IGjDrG5oIHR1eeG6v24gdMOtbmggdOG7lW5nIHF1w6F0Lk5nb8OgaSBjw7RuZyBk4bulbmcgZGnhu4VuIGdp4bqjaSBj4bqldSB0csO6YyBtw7QgaMOsbmgsIG5o4buvbmcgcGjGsMahbmcgcGjDoXAgbsOgeSBjw7JuIGPDsyB0aOG7gyDEkcaw4bujYyBz4butIGThu6VuZyBjaG8gdmnhu4djIHRoxINtIGTDsiBk4buvIGxp4buHdSwgbOG7sWEgY2jhu41uIGJp4bq/biBz4buRLG5nYXkgY+G6oyBraGkgUmFuZG9tIGZvcmVzdCBraMO0bmcgcGjhuqNpIGzDoCBhbGdvcml0aG0gbcOgIGLhuqFuIG114buRbiDDoXAgZOG7pW5nIGNobyBiw6BpIHRvw6FuIGPhu6dhIG3DrG5oLg0KDQpUcm9uZyBiw6BpIHRp4bq/cCB0aGVvLCBt4buZdCB0aMOgbmggdmnDqm4ga2jDoWMgY+G7p2EgbmjDs20gc+G6vSBnaeG7m2kgdGhp4buHdSB24bubaSBjw6FjIGLhuqFuIGPDoWNoIGRp4buFbiBnaeG6o2kgbeG7mXQgYmxhY2sgYm94IGtow6FjIGzDoCBFeHRyZW1lIGdyYWRpZW50IGJvb3N0aW5nIChYR0JNKS4NCg0KVOG6oW0gYmnhu4d0IGPDoWMgYuG6oW4gdsOgIGjhurluIGfhurdwIGzhuqFpLg0K