Motivation

Dự báo phá sản của các doanh nghiệp (Corporate Bankruptancy) bao gồm cả các tổ chức tài chính đã có nhiều tác giả nghiên cứu. Tại Việt Nam thì có luận văn Tiến Sĩ của tác giả Đặng Huy Ngân với đề tài Xây dựng mô hình cảnh báo nguy cơ vỡ nợ đối với các ngân hàng thương mại cổ phần Việt Nam (bạn đọc quan tâm có thể download toàn văn nghiên cứu này tại đây). Nghiên cứu này so sánh khả năng dự báo phá sản cho các ngân hàng từ ba mô hình: Logistic - một mô hình thống kê điển hình và hai mô hình phi tham số là mạng noron (Neural Network) và cây quyết định (Decision Trees). Tác giả sử dụng bộ dữ liệu train (có 114 quan sát) để huấn luyện ba mô hình hình sau đó sử dụng lại chính bộ dữ liệu này để kiểm tra và đánh giá chất lượng phân loại - dự báo và so sánh ba mô hình.

Đây là cách tiếp cận chưa phù hợp. Mặc dù tác giả cũng có nói đến sử dụng dữ liệu của năm 2015 để “…kiểm tra hiệu suất ngoài mẫu của mô hình” - tức là test data. Việc so sánh hiệu quả của các mô hình khác nhau sẽ được đánh giá trên test data chứ không phải là train data và được mô tả kĩ ở đây.

Ngoài ra ở luận văn này việc so sánh khả năng dự báo - phân loại của ba mô hình tác giả dựa vào ma trận nhầm lẫn (Confusion Matrix). Việc dán nhãn cho Y là 0 hay 1 hoàn toàn phụ thuộc vào: (1) xác xuất dự báo từ mô hình, và (2) ngưỡng được chọn để dán nhãn - phân loại. Nhưng ở đây tác giả mặc định sử dụng ngưỡng 0.5 (xem trang 105 của luận văn) là một thiếu sót. Kết quả của ma trận nhầm lẫn là phụ thuộc vào ngưỡng được chọn và điều này được giải thích chi tiết ở đây. Với một mô hình đã chọn hoàn toàn có thể tìm được một ngưỡng tối ưu tương ứng để, ví dụ, cực đại hóa Accuracy. Do vậy hợp lí hơn sẽ là khảo sát sự biến đổi của Accuracy theo ngưỡng phân loại để đánh giá đúng, chính xác và toàn diện hơn khả năng phân loại của ba mô hình chứ không phải là đánh giá chỉ tại một ngưỡng. Chưa kể việc sử dụng Accuracy làm tiêu chuẩn so sánh - đánh giá và lựa chọn mô hình trong trường hợp này là không phù hợp vì cái giá phải trả của Type I Error khác Type II Error (bạn đọc quan tâm có thể tìm hiểu thêm tại đây).

Mặt khác bộ dữ liệu sử dụng cho luận văn này có số lượng quan sát quá ít. Do vậy trong post này sẽ thay thế bằng bộ dữ liệu đã được sử dụng cho nghiên cứu có tên Financial Ratios and Corporate Governance Indicators in Bankruptcy Prediction: A Comprehensive Study đăng trên European Journal of Operational Research. Bộ dữ liệu này có 6819 quan sát và 3.2% trong số đó được xác định là phá sản. Bộ dữ liệu này được sử dụng cho cuộc thi Corporate Bankruptcy Prediction 2021 bởi Phòng nghiên cứu khai phá dữ liệu thuộc Chubu University. Bộ dữ liệu này có thể download tại đây. Tiêu chuẩn được lựa chọn để đánh giá khả năng dự báo và phân loại của các mô hình là AUC trong đó 70% dữ liệu ban đầu sử dụng làm test data và phần còn lại 30% được sử dụng để đánh giá mô hình và do vậy là được sử dụng để đánh giá thứ hại của các đội tham gia dự thi. Dưới đây là Private Score (AUC) và thứ hạng của một số đội có thứ hạng cao nhất:

Trước hết load bộ dữ liệu này và thực hiện một số bước xử lí dữ liệu đơn giản:

# Clear our R environment: 
rm(list = ls())

# Load tidyverse package: 
library(tidyverse)

# Load data: 
read_csv("F:/data.csv/data.csv") -> data

# Rename for all columns: 

old_names <- names(data)

old_names %>% str_replace_all("[^a-z|^A-Z]", "") -> new_names

names(data) <- new_names

# Remove NetIncomeFlag and relabel for Bankrupt column: 
data %>% 
  select(-NetIncomeFlag) %>% 
  mutate(Bankrupt = case_when(Bankrupt == 1 ~ "Bankrupt", TRUE ~ "NonBankrupt")) %>% 
  mutate(Bankrupt = as.factor(Bankrupt)) -> df

# Set response and predictors: 

response <- "Bankrupt"

predictors <- names(df %>% select(-response))

Do thời hạn cuộc thi đã hết nên chúng ta có thể phân chia dữ liệu theo tỉ lệ 70-30 như đã mô tả và sử dụng như là proxy để so sánh kết quả của những mô hình phân loại mà chúng ta xây dựng với kết quả của những đội đã tham gia cuộc thi:

# Split our data: 

library(caret)

set.seed(1)

id <- createDataPartition(y = df %>% pull(response), p = 0.7, list = FALSE)

# 70% data for training models: 
train <- df[id, ] 

# 30% data will be used for comparision and evaluating model performance: 
test <- df[-id, ] 

AUC for Feature Selection

Logistic là một cách tiếp cận của thống kê truyền thống nhưng tương đối hiệu quả. Vấn đề là có đến 94 chỉ số tài chính (Financial Ratios) và cũng là 94 features và không phải mọi feature đều tốt và có hiệu quả như nhau khi sử dụng cho mô hình Logistic. Trong post này tôi không sử dụng cách thức lựa chọn biến số như các tác giả của nghiên cứu Financial Ratios and Corporate Governance Indicators in Bankruptcy Prediction: A Comprehensive Study mà sử dụng AUC như là tiêu chuẩn để lựa chọn biến số.

Trước hết huấn luyện 94 mô hình Logistic đơn biến (tương ứng với 94 biến) và tính AUC tương ứng trên test data (Stage 1). Sau đó chọn ra mộ tổ hợp các biến số dựa trên ngưỡng AUC sao cho AUC trên Test Data là lớn nhất (Stage 2). Dưới đây là R codes:

#-----------------------
#        Stage 1
#-----------------------

# Function extracts ROC/AUC for a predictor selected: 

library(pROC) # For calculating AUC. 

actual_labels <- test$Bankrupt

returnROC_AUC <- function(predictor_selected) {
  
  f <- as.formula(paste0(response, " ~ ", predictor_selected))
  logit <- glm(f, family = "binomial", data = train)
  prob_pred <- predict(logit, test, type = "response")
  my_auc <- roc(actual_labels, prob_pred)$auc %>% as.numeric()
  return(tibble(predictor = predictor_selected, auc = my_auc))
  
}


# ROC/AUC by a given predictor: 

do.call("bind_rows", lapply(predictors, returnROC_AUC)) %>% 
  arrange(-auc) -> df_auc

# Some Results:

head(df_auc)
## # A tibble: 6 x 2
##   predictor                                   auc
##   <chr>                                     <dbl>
## 1 PersistentEPSintheLastFourSeasons         0.881
## 2 NetIncometoTotalAssets                    0.880
## 3 NetprofitbeforetaxPaidincapital           0.872
## 4 PerShareNetprofitbeforetaxYuan            0.872
## 5 ROABbeforeinterestanddepreciationaftertax 0.866
## 6 TotalincomeTotalexpense                   0.865

Như vậy nếu chỉ sử dụng biến số NetIncometoTotalAssets thì AUC trên Test Data là 0.880 - một con số khá cao. Thông tin này được sử dụng để lựa chọn ra các biến cho mô hình Logistic. Chẳng hạn tiêu chuẩn đặt ra có thể là chỉ chọn các biến nào mà mô hình Logistic đơn biến tương ứng lớn hơn 0.7.

Để hỗ trợ cho Stage 2 chúng ta viết hàm có tên returnROC_AUCTestData tính toán AUC trên Test Data khi biết trước các biến được lựa chọn cho mô hình Logistic:

# Function extracts ROC/AUC on test data: 

returnROC_AUCTestData <- function(predictor_selected) {
  f <- as.formula(paste0(response, " ~ ", paste(predictor_selected, collapse = " + ")))
  logit <- glm(f, family = "binomial", data = train)
  prob_pred <- predict(logit, test, type = "response")
  my_auc <- roc(actual_labels, prob_pred)$auc %>% as.numeric()
  return(my_auc)
  
}

Sử dụng hàm này để tính AUC trên Test Data tương ứng cho các mô hình Logistic khi biết ngưỡng AUC để lựa chọn biến cho mô hình:

# Set a sequence of thresholds: 

auc_thresholds <- seq(min(df_auc$auc), max(df_auc$auc), 0.01)

auc_space <- NULL

# AUC by threshold: 

for (j in auc_thresholds) {
  
  df_auc %>% 
    filter(auc >= j) %>% 
    pull(predictor) -> predictors_for_modelling
  
  returnROC_AUCTestData(predictors_for_modelling) -> my_auc
  
  auc_space <- c(auc_space, my_auc)
  
}


tibble(auc_thresholds = auc_thresholds, auc = auc_space) -> df_auc_threshold

# Features create max AUC on test data: 

auc_max <- df_auc_threshold %>% slice(which.max(auc))

auc_max
## # A tibble: 1 x 2
##   auc_thresholds   auc
##            <dbl> <dbl>
## 1          0.828 0.903

Kết quả này cho thấy khi lựa chọn các biến có ngưỡng AUC = 0.828 thì sẽ đặt được AUC tối ưu trên Test Data và AUC đạt được trên Test Data sẽ là 0.903. Kết quả khiêm tốn này cao hơn vị trí thứ 7 với AUC = 0.8754 (Team NguyenHuu BaoLong).

Dưới đây là 18 biến số được lựa chọn cho mô hình Logistic:

# Features >= 0.828: 

df_auc %>% 
  filter(auc >= auc_max$auc_thresholds) %>% 
  pull(predictor) -> var_auc_0903

# 18 features selected for Logistic Model: 
var_auc_0903
##  [1] "PersistentEPSintheLastFourSeasons"              
##  [2] "NetIncometoTotalAssets"                         
##  [3] "NetprofitbeforetaxPaidincapital"                
##  [4] "PerShareNetprofitbeforetaxYuan"                 
##  [5] "ROABbeforeinterestanddepreciationaftertax"      
##  [6] "TotalincomeTotalexpense"                        
##  [7] "RetainedEarningstoTotalAssets"                  
##  [8] "ROACbeforeinterestanddepreciationbeforeinterest"
##  [9] "ROAAbeforeinterestandaftertax"                  
## [10] "AftertaxnetInterestRate"                        
## [11] "Continuousinterestrateaftertax"                 
## [12] "PretaxnetInterestRate"                          
## [13] "Nonindustryincomeandexpenditurerevenue"         
## [14] "Debtratio"                                      
## [15] "NetworthAssets"                                 
## [16] "EquitytoLiability"                              
## [17] "NetValueGrowthRate"                             
## [18] "TotaldebtTotalnetworth"

Chúng ta có thể khảo sát AUC trên Test Data (Figure 1) với điểm màu đỏ là max AUC:

df_auc_threshold %>% 
  ggplot(aes(auc_thresholds, auc)) + 
  geom_line(size = 1, color = "blue") + 
  geom_point(data = auc_max, aes(auc_thresholds, auc), color = "red", size = 2) + 
    labs(x = "AUC Threshold", 
         y = "AUC on Test Data", 
         title = "Figure 1: AU on Test Data by AUC Threshold for Feature Selection")

Chúng ta có thể khảo sát sâu và kĩ hơn nữa chất lượng phân loại cũng như các đặc điểm của hai cách tiếp cận như sau:

# Logistic with 0903-features: 

f_0903 <- as.formula(paste0(response, " ~ ", paste(var_auc_0903, collapse = " + ")))

# Logistic with all features: 

f_all <- as.formula(paste0(response, " ~ ", paste(predictors, collapse = " + ")))

# New metric and sampling technique for searching optimal parameters:  

sampling_new <- trainControl(method = "repeatedcv", 
                             classProbs = TRUE,
                             summaryFunction = twoClassSummary, 
                             number = 3, 
                             repeats = 3)

# Train and turn RF which ROC-AUC used for searching optimal parameters: 

set.seed(29)

train(f_0903, 
      data = train, 
      method = "glm", 
      trControl = sampling_new) -> logit_0903

set.seed(29)

train(f_all, 
      data = train, 
      method = "glm", 
      trControl = sampling_new) -> logit_all

pd_0903 <- predict(logit_0903, test, type = "prob") %>% pull(response) 

pd_all <- predict(logit_all, test, type = "prob") %>% pull(response) 

roc(actual_labels, pd_0903) -> roc_0903

roc(actual_labels, pd_all) -> roc_all

sen_spec_0903 <- tibble(TPR = roc_0903$sensitivities, FPR = 1 - roc_0903$specificities)

sen_spec_all <- tibble(TPR = roc_all$sensitivities, FPR = 1 - roc_all$specificities)

df_roc <- bind_rows(sen_spec_0903 %>% mutate(Model = "903"), sen_spec_all %>% mutate(Model = "All"))

df_roc %>% 
  ggplot(aes(x = FPR, ymin = 0, ymax = TPR, color = Model, fill = Model))+
  geom_polygon(aes(y = TPR), alpha = 0.2)+
  geom_path(aes(y = TPR), size = 1.2) +
  geom_abline(intercept = 0, slope = 1, color = "gray37", size = 1, linetype = "dashed") + 
  theme_bw() +
  coord_equal() + 
  labs(x = "FPR (1 - Specificity)", 
       y = "TPR (Sensitivity)", 
       title = "Figure 2: Model Comparision by AUC-ROC on Test Data", 
       subtitle = "052-Predictor Logistic = 0.903, All-Predictor Logistic = 0.436")

Figure 2 cho thấy nếu sử dụng tất cả 94 features thì AUC trên Test Data chỉ là 0.436 - một kết quả rất thấp.

Automated Machine Learning

Với cố gắng đạt được kết quả cao hơn nữa một giải pháp đơn giản có thể áp dụng là sử dụng cách tiếp cận Automated Machine Learning (có thể đọc thêm tại đây). Dưới đây là R codes thực hiện Automated Machine Learning với chú ý rằng ở đây sử dụng tất cả 94 features (do có nhiều mô hình ML có khả năng tự động lựa chọn biến tối ưu):

# Load h2o package: 

library(h2o)
h2o.init(nthreads = 2, max_mem_size = "8g")
## 
## H2O is not running yet, starting it now...
## 
## Note:  In case of errors look at the following log files:
##     C:\Users\Admin\AppData\Local\Temp\RtmpoFMqqN\file2848213c6f7c/h2o_Admin_started_from_r.out
##     C:\Users\Admin\AppData\Local\Temp\RtmpoFMqqN\file284835dd2172/h2o_Admin_started_from_r.err
## 
## 
## Starting H2O JVM and connecting:  Connection successful!
## 
## R is connected to the H2O cluster: 
##     H2O cluster uptime:         2 seconds 745 milliseconds 
##     H2O cluster timezone:       Asia/Bangkok 
##     H2O data parsing timezone:  UTC 
##     H2O cluster version:        3.36.0.2 
##     H2O cluster version age:    22 days  
##     H2O cluster name:           H2O_started_from_R_Admin_apa530 
##     H2O cluster total nodes:    1 
##     H2O cluster total memory:   7.10 GB 
##     H2O cluster total cores:    4 
##     H2O cluster allowed cores:  2 
##     H2O cluster healthy:        TRUE 
##     H2O Connection ip:          localhost 
##     H2O Connection port:        54321 
##     H2O Connection proxy:       NA 
##     H2O Internal Security:      FALSE 
##     H2O API Extensions:         Amazon S3, Algos, Infogram, AutoML, Core V3, TargetEncoder, Core V4 
##     R Version:                  R version 4.1.2 (2021-11-01)
h2o.no_progress()

# Prepare data: 

as.h2o(train) -> h2o_frame

splits <- h2o.splitFrame(h2o_frame, ratios = nrow(test) / nrow(train), seed = 29)

train_h2o <- splits[[2]] # Train data. 

valid_h2o <- splits[[1]] # Validation data. 

test_h2o <- as.h2o(test) # Convert test data to h2o frame. 

#===================================
#  Training Auto Machine Learning
#===================================

# Train Auto Machine Learning: 

autoML <- h2o.automl(x = predictors, 
                     y = response, 
                     training_frame = train_h2o, 
                     leaderboard_frame = valid_h2o, 
                     stopping_metric = "AUC", 
                     stopping_rounds = 10, 
                     stopping_tolerance = 0.025, 
                     max_models = 15, 
                     max_runtime_secs = 60*60, 
                     seed = 1, 
                     sort_metric = "AUC")
## 
## 16:14:24.250: Project: AutoML_1_20220217_161424
## 16:14:24.251: 5-fold cross-validation will be used.
## 16:14:24.254: Stopping tolerance set by the user: 0.025
## 16:14:24.254: Build control seed: 1
## 16:14:24.254: training frame: Frame key: AutoML_1_20220217_161424_training_RTMP_sid_9f5c_5    cols: 95    rows: 2726  chunks: 2    size: 2035771  checksum: 490792892629137952
## 16:14:24.254: validation frame: NULL
## 16:14:24.260: leaderboard frame: Frame key: RTMP_sid_9f5c_7    cols: 95    rows: 2048  chunks: 2    size: 1529451  checksum: -8244253284601612160
## 16:14:24.260: blending frame: NULL
## 16:14:24.260: response column: Bankrupt
## 16:14:24.260: fold column: null
## 16:14:24.260: weights column: null
## 16:14:24.266: AutoML: XGBoost is not available; skipping it.
## 16:14:24.267: Loading execution steps: [{XGBoost : [def_2 (1g, 10w), def_1 (2g, 10w), def_3 (3g, 10w), grid_1 (4g, 90w), lr_search (6g, 30w)]}, {GLM : [def_1 (1g, 10w)]}, {DRF : [def_1 (2g, 10w), XRT (3g, 10w)]}, {GBM : [def_5 (1g, 10w), def_2 (2g, 10w), def_3 (2g, 10w), def_4 (2g, 10w), def_1 (3g, 10w), grid_1 (4g, 60w), lr_annealing (6g, 10w)]}, {DeepLearning : [def_1 (3g, 10w), grid_1 (4g, 30w), grid_2 (5g, 30w), grid_3 (5g, 30w)]}, {completion : [resume_best_grids (10g, 60w)]}, {StackedEnsemble : [best_of_family_1 (1g, 5w), best_of_family_2 (2g, 5w), best_of_family_3 (3g, 5w), best_of_family_4 (4g, 5w), best_of_family_5 (5g, 5w), all_2 (2g, 10w), all_3 (3g, 10w), all_4 (4g, 10w), all_5 (5g, 10w), monotonic (6g, 10w), best_of_family_xgboost (6g, 10w), best_of_family_gbm (6g, 10w), all_xgboost (7g, 10w), all_gbm (7g, 10w), best_of_family_xglm (8g, 10w), all_xglm (8g, 10w), best_of_family (10g, 10w), best_N (10g, 10w)]}]
## 16:14:24.285: Step 'best_of_family_xgboost' not defined in provider 'StackedEnsemble': skipping it.
## 16:14:24.285: Step 'all_xgboost' not defined in provider 'StackedEnsemble': skipping it.
## 16:14:24.286: Disabling Algo: XGBoost as requested by the user.
## 16:14:24.286: Defined work allocations: [Work{def_1, GLM, ModelBuild, group=1, weight=10}, Work{def_5, GBM, ModelBuild, group=1, weight=10}, Work{best_of_family_1, StackedEnsemble, ModelBuild, group=1, weight=5}, Work{def_1, DRF, ModelBuild, group=2, weight=10}, Work{def_2, GBM, ModelBuild, group=2, weight=10}, Work{def_3, GBM, ModelBuild, group=2, weight=10}, Work{def_4, GBM, ModelBuild, group=2, weight=10}, Work{best_of_family_2, StackedEnsemble, ModelBuild, group=2, weight=5}, Work{all_2, StackedEnsemble, ModelBuild, group=2, weight=10}, Work{XRT, DRF, ModelBuild, group=3, weight=10}, Work{def_1, GBM, ModelBuild, group=3, weight=10}, Work{def_1, DeepLearning, ModelBuild, group=3, weight=10}, Work{best_of_family_3, StackedEnsemble, ModelBuild, group=3, weight=5}, Work{all_3, StackedEnsemble, ModelBuild, group=3, weight=10}, Work{grid_1, GBM, HyperparamSearch, group=4, weight=60}, Work{grid_1, DeepLearning, HyperparamSearch, group=4, weight=30}, Work{best_of_family_4, StackedEnsemble, ModelBuild, group=4, weight=5}, Work{all_4, StackedEnsemble, ModelBuild, group=4, weight=10}, Work{grid_2, DeepLearning, HyperparamSearch, group=5, weight=30}, Work{grid_3, DeepLearning, HyperparamSearch, group=5, weight=30}, Work{best_of_family_5, StackedEnsemble, ModelBuild, group=5, weight=5}, Work{all_5, StackedEnsemble, ModelBuild, group=5, weight=10}, Work{lr_annealing, GBM, Selection, group=6, weight=10}, Work{monotonic, StackedEnsemble, ModelBuild, group=6, weight=10}, Work{best_of_family_gbm, StackedEnsemble, ModelBuild, group=6, weight=10}, Work{all_gbm, StackedEnsemble, ModelBuild, group=7, weight=10}, Work{best_of_family_xglm, StackedEnsemble, ModelBuild, group=8, weight=10}, Work{all_xglm, StackedEnsemble, ModelBuild, group=8, weight=10}, Work{resume_best_grids, virtual, Dynamic, group=10, weight=60}, Work{best_of_family, StackedEnsemble, ModelBuild, group=10, weight=10}, Work{best_N, StackedEnsemble, ModelBuild, group=10, weight=10}]
## 16:14:24.286: Actual work allocations: [Work{def_1, GLM, ModelBuild, group=1, weight=10}, Work{def_5, GBM, ModelBuild, group=1, weight=10}, Work{best_of_family_1, StackedEnsemble, ModelBuild, group=1, weight=5}, Work{def_1, DRF, ModelBuild, group=2, weight=10}, Work{def_2, GBM, ModelBuild, group=2, weight=10}, Work{def_3, GBM, ModelBuild, group=2, weight=10}, Work{def_4, GBM, ModelBuild, group=2, weight=10}, Work{best_of_family_2, StackedEnsemble, ModelBuild, group=2, weight=5}, Work{all_2, StackedEnsemble, ModelBuild, group=2, weight=10}, Work{XRT, DRF, ModelBuild, group=3, weight=10}, Work{def_1, GBM, ModelBuild, group=3, weight=10}, Work{def_1, DeepLearning, ModelBuild, group=3, weight=10}, Work{best_of_family_3, StackedEnsemble, ModelBuild, group=3, weight=5}, Work{all_3, StackedEnsemble, ModelBuild, group=3, weight=10}, Work{grid_1, GBM, HyperparamSearch, group=4, weight=60}, Work{grid_1, DeepLearning, HyperparamSearch, group=4, weight=30}, Work{best_of_family_4, StackedEnsemble, ModelBuild, group=4, weight=5}, Work{all_4, StackedEnsemble, ModelBuild, group=4, weight=10}, Work{grid_2, DeepLearning, HyperparamSearch, group=5, weight=30}, Work{grid_3, DeepLearning, HyperparamSearch, group=5, weight=30}, Work{best_of_family_5, StackedEnsemble, ModelBuild, group=5, weight=5}, Work{all_5, StackedEnsemble, ModelBuild, group=5, weight=10}, Work{lr_annealing, GBM, Selection, group=6, weight=10}, Work{monotonic, StackedEnsemble, ModelBuild, group=6, weight=10}, Work{best_of_family_gbm, StackedEnsemble, ModelBuild, group=6, weight=10}, Work{all_gbm, StackedEnsemble, ModelBuild, group=7, weight=10}, Work{best_of_family_xglm, StackedEnsemble, ModelBuild, group=8, weight=10}, Work{all_xglm, StackedEnsemble, ModelBuild, group=8, weight=10}, Work{resume_best_grids, virtual, Dynamic, group=10, weight=60}, Work{best_of_family, StackedEnsemble, ModelBuild, group=10, weight=10}, Work{best_N, StackedEnsemble, ModelBuild, group=10, weight=10}]
## 16:14:24.287: AutoML job created: 2022.02.17 16:14:24.231
## 16:14:24.288: AutoML build started: 2022.02.17 16:14:24.287
## 16:14:24.291: Time assigned for GLM_1_AutoML_1_20220217_161424: 1439.998375s
## 16:14:24.294: AutoML: starting GLM_1_AutoML_1_20220217_161424 model training
## 16:14:24.311: GLM_1_AutoML_1_20220217_161424 [GLM def_1] started
## 16:14:28.397: GLM_1_AutoML_1_20220217_161424 [GLM def_1] complete
## 16:14:28.397: Adding model GLM_1_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=3s
## 16:14:28.424: New leader: GLM_1_AutoML_1_20220217_161424, auc: 0.8896306402884708
## 16:14:28.424: Time assigned for GBM_1_AutoML_1_20220217_161424: 2397.242s
## 16:14:28.427: AutoML: starting GBM_1_AutoML_1_20220217_161424 model training
## 16:14:28.430: GBM_1_AutoML_1_20220217_161424 [GBM def_5] started
## 16:14:37.564: GBM_1_AutoML_1_20220217_161424 [GBM def_5] complete
## 16:14:37.564: Adding model GBM_1_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=1s, total=8s
## 16:14:37.590: New leader: GBM_1_AutoML_1_20220217_161424, auc: 0.9253849527336517
## 16:14:37.594: Time assigned for StackedEnsemble_BestOfFamily_1_AutoML_1_20220217_161424: 3586.693s
## 16:14:37.595: AutoML: starting StackedEnsemble_BestOfFamily_1_AutoML_1_20220217_161424 model training
## 16:14:37.597: StackedEnsemble_BestOfFamily_1_AutoML_1_20220217_161424 [StackedEnsemble best_of_family_1 (built with AUTO metalearner, using top model from each algorithm type)] started
## 16:14:38.606: StackedEnsemble_BestOfFamily_1_AutoML_1_20220217_161424 [StackedEnsemble best_of_family_1 (built with AUTO metalearner, using top model from each algorithm type)] complete
## 16:14:38.606: Adding model StackedEnsemble_BestOfFamily_1_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=0s
## 16:14:38.636: New leader: StackedEnsemble_BestOfFamily_1_AutoML_1_20220217_161424, auc: 0.9261564824740928
## 16:14:38.636: Time assigned for DRF_1_AutoML_1_20220217_161424: 651.9365625s
## 16:14:38.637: AutoML: starting DRF_1_AutoML_1_20220217_161424 model training
## 16:14:38.638: DRF_1_AutoML_1_20220217_161424 [DRF def_1] started
## 16:14:41.663: DRF_1_AutoML_1_20220217_161424 [DRF def_1] complete
## 16:14:41.663: Adding model DRF_1_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=2s
## 16:14:41.678: Time assigned for GBM_2_AutoML_1_20220217_161424: 796.1353125s
## 16:14:41.678: AutoML: starting GBM_2_AutoML_1_20220217_161424 model training
## 16:14:41.679: GBM_2_AutoML_1_20220217_161424 [GBM def_2] started
## 16:14:47.705: GBM_2_AutoML_1_20220217_161424 [GBM def_2] complete
## 16:14:47.705: Adding model GBM_2_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=5s
## 16:14:47.733: New leader: GBM_2_AutoML_1_20220217_161424, auc: 0.9278497872202189
## 16:14:47.734: Time assigned for GBM_3_AutoML_1_20220217_161424: 1021.872625s
## 16:14:47.734: AutoML: starting GBM_3_AutoML_1_20220217_161424 model training
## 16:14:47.734: GBM_3_AutoML_1_20220217_161424 [GBM def_3] started
## 16:14:53.772: GBM_3_AutoML_1_20220217_161424 [GBM def_3] complete
## 16:14:53.772: Adding model GBM_3_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=5s
## 16:14:53.799: Time assigned for GBM_4_AutoML_1_20220217_161424: 1428.19525s
## 16:14:53.799: AutoML: starting GBM_4_AutoML_1_20220217_161424 model training
## 16:14:53.799: GBM_4_AutoML_1_20220217_161424 [GBM def_4] started
## 16:15:00.817: GBM_4_AutoML_1_20220217_161424 [GBM def_4] complete
## 16:15:00.817: Adding model GBM_4_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=6s
## 16:15:00.842: Time assigned for StackedEnsemble_BestOfFamily_2_AutoML_1_20220217_161424: 1187.815s
## 16:15:00.842: AutoML: starting StackedEnsemble_BestOfFamily_2_AutoML_1_20220217_161424 model training
## 16:15:00.842: StackedEnsemble_BestOfFamily_2_AutoML_1_20220217_161424 [StackedEnsemble best_of_family_2 (built with AUTO metalearner, using top model from each algorithm type)] started
## 16:15:01.855: StackedEnsemble_BestOfFamily_2_AutoML_1_20220217_161424 [StackedEnsemble best_of_family_2 (built with AUTO metalearner, using top model from each algorithm type)] complete
## 16:15:01.855: Adding model StackedEnsemble_BestOfFamily_2_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=0s
## 16:15:01.913: Time assigned for StackedEnsemble_AllModels_1_AutoML_1_20220217_161424: 3562.374s
## 16:15:01.913: AutoML: starting StackedEnsemble_AllModels_1_AutoML_1_20220217_161424 model training
## 16:15:01.914: StackedEnsemble_AllModels_1_AutoML_1_20220217_161424 [StackedEnsemble all_2 (built with AUTO metalearner, using all AutoML models)] started
## 16:15:02.925: StackedEnsemble_AllModels_1_AutoML_1_20220217_161424 [StackedEnsemble all_2 (built with AUTO metalearner, using all AutoML models)] complete
## 16:15:02.925: Adding model StackedEnsemble_AllModels_1_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=0s
## 16:15:02.993: Time assigned for XRT_1_AutoML_1_20220217_161424: 791.3986875s
## 16:15:02.993: AutoML: starting XRT_1_AutoML_1_20220217_161424 model training
## 16:15:02.993: XRT_1_AutoML_1_20220217_161424 [DRF XRT (Extremely Randomized Trees)] started
## 16:15:07.13: XRT_1_AutoML_1_20220217_161424 [DRF XRT (Extremely Randomized Trees)] complete
## 16:15:07.13: Adding model XRT_1_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=3s
## 16:15:07.31: Time assigned for GBM_5_AutoML_1_20220217_161424: 1016.358875s
## 16:15:07.31: AutoML: starting GBM_5_AutoML_1_20220217_161424 model training
## 16:15:07.32: GBM_5_AutoML_1_20220217_161424 [GBM def_1] started
## 16:15:13.89: GBM_5_AutoML_1_20220217_161424 [GBM def_1] complete
## 16:15:13.89: Adding model GBM_5_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=5s
## 16:15:13.112: Time assigned for DeepLearning_1_AutoML_1_20220217_161424: 1420.47s
## 16:15:13.115: AutoML: starting DeepLearning_1_AutoML_1_20220217_161424 model training
## 16:15:13.116: DeepLearning_1_AutoML_1_20220217_161424 [DeepLearning def_1] started
## 16:15:16.129: DeepLearning_1_AutoML_1_20220217_161424 [DeepLearning def_1] complete
## 16:15:16.129: Adding model DeepLearning_1_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=2s
## 16:15:16.147: Time assigned for StackedEnsemble_BestOfFamily_3_AutoML_1_20220217_161424: 1182.713375s
## 16:15:16.147: AutoML: starting StackedEnsemble_BestOfFamily_3_AutoML_1_20220217_161424 model training
## 16:15:16.148: StackedEnsemble_BestOfFamily_3_AutoML_1_20220217_161424 [StackedEnsemble best_of_family_3 (built with AUTO metalearner, using top model from each algorithm type)] started
## 16:15:17.153: StackedEnsemble_BestOfFamily_3_AutoML_1_20220217_161424 [StackedEnsemble best_of_family_3 (built with AUTO metalearner, using top model from each algorithm type)] complete
## 16:15:17.153: Adding model StackedEnsemble_BestOfFamily_3_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=0s
## 16:15:17.205: Time assigned for StackedEnsemble_AllModels_2_AutoML_1_20220217_161424: 3547.082s
## 16:15:17.205: AutoML: starting StackedEnsemble_AllModels_2_AutoML_1_20220217_161424 model training
## 16:15:17.206: StackedEnsemble_AllModels_2_AutoML_1_20220217_161424 [StackedEnsemble all_3 (built with AUTO metalearner, using all AutoML models)] started
## 16:15:18.210: StackedEnsemble_AllModels_2_AutoML_1_20220217_161424 [StackedEnsemble all_3 (built with AUTO metalearner, using all AutoML models)] complete
## 16:15:18.210: Adding model StackedEnsemble_AllModels_2_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=0s
## 16:15:18.302: Time assigned for GBM_grid_1_AutoML_1_20220217_161424: 2026.27775s
## 16:15:18.302: AutoML: starting GBM_grid_1_AutoML_1_20220217_161424 hyperparameter search
## 16:15:18.312: GBM_grid_1_AutoML_1_20220217_161424 [GBM Grid Search] started
## 16:15:24.345: Built: 1 models for HyperparamSearch : GBM_grid_1_AutoML_1_20220217_161424 [GBM Grid Search]
## 16:15:24.345: Adding model GBM_grid_1_AutoML_1_20220217_161424_model_1 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=5s
## 16:15:28.396: Built: 2 models for HyperparamSearch : GBM_grid_1_AutoML_1_20220217_161424 [GBM Grid Search]
## 16:15:28.396: Adding model GBM_grid_1_AutoML_1_20220217_161424_model_2 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=4s
## 16:15:31.455: GBM_grid_1_AutoML_1_20220217_161424 [GBM Grid Search] complete
## 16:15:31.455: Built: 3 models for HyperparamSearch : GBM_grid_1_AutoML_1_20220217_161424 [GBM Grid Search]
## 16:15:31.455: Adding model GBM_grid_1_AutoML_1_20220217_161424_model_3 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=2s
## 16:15:31.476: Time assigned for DeepLearning_grid_1_AutoML_1_20220217_161424: 2355.2075s
## 16:15:31.477: AutoML: starting DeepLearning_grid_1_AutoML_1_20220217_161424 hyperparameter search
## 16:15:31.477: DeepLearning_grid_1_AutoML_1_20220217_161424 [DeepLearning Grid Search] started
## 16:26:10.647: DeepLearning_grid_1_AutoML_1_20220217_161424 [DeepLearning Grid Search] complete
## 16:26:10.647: Built: 1 models for HyperparamSearch : DeepLearning_grid_1_AutoML_1_20220217_161424 [DeepLearning Grid Search]
## 16:26:10.647: Adding model DeepLearning_grid_1_AutoML_1_20220217_161424_model_1 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=127s, total=639s
## 16:26:10.684: Time assigned for StackedEnsemble_AllModels_3_AutoML_1_20220217_161424: 2893.603s
## 16:26:10.684: AutoML: starting StackedEnsemble_AllModels_3_AutoML_1_20220217_161424 model training
## 16:26:10.685: StackedEnsemble_AllModels_3_AutoML_1_20220217_161424 [StackedEnsemble all_4 (built with AUTO metalearner, using all AutoML models)] started
## 16:26:11.688: StackedEnsemble_AllModels_3_AutoML_1_20220217_161424 [StackedEnsemble all_4 (built with AUTO metalearner, using all AutoML models)] complete
## 16:26:11.688: Adding model StackedEnsemble_AllModels_3_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=0s
## 16:26:11.788: Time assigned for DeepLearning_grid_2_AutoML_1_20220217_161424: 1156.999625s
## 16:26:11.788: AutoML: starting DeepLearning_grid_2_AutoML_1_20220217_161424 hyperparameter search
## 16:26:11.789: DeepLearning_grid_2_AutoML_1_20220217_161424 [DeepLearning Grid Search] started
## 16:37:26.377: DeepLearning_grid_2_AutoML_1_20220217_161424 [DeepLearning Grid Search] complete
## 16:37:26.379: Built: 1 models for HyperparamSearch : DeepLearning_grid_2_AutoML_1_20220217_161424 [DeepLearning Grid Search]
## 16:37:26.379: Adding model DeepLearning_grid_2_AutoML_1_20220217_161424_model_1 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=132s, total=673s
## 16:37:26.422: Time assigned for DeepLearning_grid_3_AutoML_1_20220217_161424: 1478.57675s
## 16:37:26.422: AutoML: starting DeepLearning_grid_3_AutoML_1_20220217_161424 hyperparameter search
## 16:37:26.422: DeepLearning_grid_3_AutoML_1_20220217_161424 [DeepLearning Grid Search] started
## 16:49:23.692: DeepLearning_grid_3_AutoML_1_20220217_161424 [DeepLearning Grid Search] complete
## 16:49:23.692: Built: 1 models for HyperparamSearch : DeepLearning_grid_3_AutoML_1_20220217_161424 [DeepLearning Grid Search]
## 16:49:23.692: Adding model DeepLearning_grid_3_AutoML_1_20220217_161424_model_1 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=141s, total=716s
## 16:49:23.749: Time assigned for StackedEnsemble_AllModels_4_AutoML_1_20220217_161424: 1500.538s
## 16:49:23.749: AutoML: starting StackedEnsemble_AllModels_4_AutoML_1_20220217_161424 model training
## 16:49:23.751: StackedEnsemble_AllModels_4_AutoML_1_20220217_161424 [StackedEnsemble all_5 (built with AUTO metalearner, using all AutoML models)] started
## 16:49:24.766: StackedEnsemble_AllModels_4_AutoML_1_20220217_161424 [StackedEnsemble all_5 (built with AUTO metalearner, using all AutoML models)] complete
## 16:49:24.766: Adding model StackedEnsemble_AllModels_4_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=0s, total=0s
## 16:49:24.915: AutoML: hit the max_models limit; skipping GBM lr_annealing
## 16:49:24.916: No base models, due to timeouts or the exclude_algos option. Skipping StackedEnsemble 'monotonic'.
## 16:49:24.916: Time assigned for StackedEnsemble_BestOfFamily_4_AutoML_1_20220217_161424: 749.6855s
## 16:49:24.916: AutoML: starting StackedEnsemble_BestOfFamily_4_AutoML_1_20220217_161424 model training
## 16:49:24.917: StackedEnsemble_BestOfFamily_4_AutoML_1_20220217_161424 [StackedEnsemble best_of_family_gbm (built with gbm metalearner, using top model from each algorithm type)] started
## 16:49:26.930: StackedEnsemble_BestOfFamily_4_AutoML_1_20220217_161424 [StackedEnsemble best_of_family_gbm (built with gbm metalearner, using top model from each algorithm type)] complete
## 16:49:26.930: Adding model StackedEnsemble_BestOfFamily_4_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=1s, total=1s
## 16:49:26.990: Time assigned for StackedEnsemble_AllModels_5_AutoML_1_20220217_161424: 1497.297s
## 16:49:26.991: AutoML: starting StackedEnsemble_AllModels_5_AutoML_1_20220217_161424 model training
## 16:49:26.991: StackedEnsemble_AllModels_5_AutoML_1_20220217_161424 [StackedEnsemble all_gbm (built with gbm metalearner, using all AutoML models)] started
## 16:49:30.19: StackedEnsemble_AllModels_5_AutoML_1_20220217_161424 [StackedEnsemble all_gbm (built with gbm metalearner, using all AutoML models)] complete
## 16:49:30.19: Adding model StackedEnsemble_AllModels_5_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=2s, total=2s
## 16:49:30.238: Time assigned for StackedEnsemble_BestOfFamily_5_AutoML_1_20220217_161424: 747.0245s
## 16:49:30.238: AutoML: starting StackedEnsemble_BestOfFamily_5_AutoML_1_20220217_161424 model training
## 16:49:30.239: StackedEnsemble_BestOfFamily_5_AutoML_1_20220217_161424 [StackedEnsemble best_of_family_xglm (built with AUTO metalearner, using top model from each algorithm type)] started
## 16:49:32.256: StackedEnsemble_BestOfFamily_5_AutoML_1_20220217_161424 [StackedEnsemble best_of_family_xglm (built with AUTO metalearner, using top model from each algorithm type)] complete
## 16:49:32.256: Adding model StackedEnsemble_BestOfFamily_5_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=1s, total=1s
## 16:49:32.338: Time assigned for StackedEnsemble_AllModels_6_AutoML_1_20220217_161424: 1491.949s
## 16:49:32.339: AutoML: starting StackedEnsemble_AllModels_6_AutoML_1_20220217_161424 model training
## 16:49:32.339: StackedEnsemble_AllModels_6_AutoML_1_20220217_161424 [StackedEnsemble all_xglm (built with AUTO metalearner, using all AutoML models)] started
## 16:49:35.365: StackedEnsemble_AllModels_6_AutoML_1_20220217_161424 [StackedEnsemble all_xglm (built with AUTO metalearner, using all AutoML models)] complete
## 16:49:35.365: Adding model StackedEnsemble_AllModels_6_AutoML_1_20220217_161424 to leaderboard Leaderboard_AutoML_1_20220217_161424@@Bankrupt. Training time: model=2s, total=2s
## 16:49:35.558: AutoML: hit the max_models limit; skipping completion resume_best_grids
## 16:49:35.561: Actual modeling steps: [{GLM : [def_1 (1g, 10w)]}, {GBM : [def_5 (1g, 10w)]}, {StackedEnsemble : [best_of_family_1 (1g, 5w)]}, {DRF : [def_1 (2g, 10w)]}, {GBM : [def_2 (2g, 10w), def_3 (2g, 10w), def_4 (2g, 10w)]}, {StackedEnsemble : [best_of_family_2 (2g, 5w), all_2 (2g, 10w)]}, {DRF : [XRT (3g, 10w)]}, {GBM : [def_1 (3g, 10w)]}, {DeepLearning : [def_1 (3g, 10w)]}, {StackedEnsemble : [best_of_family_3 (3g, 5w), all_3 (3g, 10w)]}, {GBM : [grid_1 (4g, 60w)]}, {DeepLearning : [grid_1 (4g, 30w)]}, {StackedEnsemble : [all_4 (4g, 10w)]}, {DeepLearning : [grid_2 (5g, 30w), grid_3 (5g, 30w)]}, {StackedEnsemble : [all_5 (5g, 10w), best_of_family_gbm (6g, 10w), all_gbm (7g, 10w), best_of_family_xglm (8g, 10w), all_xglm (8g, 10w)]}]
## 16:49:35.561: AutoML build stopped: 2022.02.17 16:49:35.561
## 16:49:35.561: AutoML build done: built 15 models
## 16:49:35.561: AutoML duration: 35 min 11.274 sec
## 16:49:35.568: Verifying training frame immutability. . .
## 16:49:35.568: Training frame was not mutated (as expected).

Như vậy AUC trên Validation Data cho mô hình tốt nhất là 0.9278 trên validation data như chúng ta có thể thấy ở Table 1:

autoML@leaderboard %>% 
  as.data.frame() %>% 
  select(model_id, auc) %>% 
  mutate(Rank = 1:nrow(.), auc = round(auc, 4)) %>% 
  rename(AUC_Val = auc) -> df_results

df_results %>% 
  knitr::kable(caption = "Table 1: AUC on Validation Data")
Table 1: AUC on Validation Data
model_id AUC_Val Rank
GBM_2_AutoML_1_20220217_161424 0.9278 1
StackedEnsemble_BestOfFamily_1_AutoML_1_20220217_161424 0.9262 2
GBM_1_AutoML_1_20220217_161424 0.9254 3
StackedEnsemble_AllModels_2_AutoML_1_20220217_161424 0.9248 4
StackedEnsemble_BestOfFamily_3_AutoML_1_20220217_161424 0.9240 5
StackedEnsemble_AllModels_3_AutoML_1_20220217_161424 0.9234 6
StackedEnsemble_AllModels_4_AutoML_1_20220217_161424 0.9227 7
StackedEnsemble_AllModels_1_AutoML_1_20220217_161424 0.9220 8
StackedEnsemble_BestOfFamily_5_AutoML_1_20220217_161424 0.9220 9
StackedEnsemble_BestOfFamily_2_AutoML_1_20220217_161424 0.9195 10
StackedEnsemble_AllModels_6_AutoML_1_20220217_161424 0.9193 11
GBM_grid_1_AutoML_1_20220217_161424_model_1 0.9141 12
StackedEnsemble_BestOfFamily_4_AutoML_1_20220217_161424 0.9137 13
GBM_5_AutoML_1_20220217_161424 0.9110 14
GBM_grid_1_AutoML_1_20220217_161424_model_2 0.9100 15
GBM_3_AutoML_1_20220217_161424 0.9091 16
GBM_4_AutoML_1_20220217_161424 0.9059 17
XRT_1_AutoML_1_20220217_161424 0.9030 18
StackedEnsemble_AllModels_5_AutoML_1_20220217_161424 0.8973 19
GBM_grid_1_AutoML_1_20220217_161424_model_3 0.8908 20
GLM_1_AutoML_1_20220217_161424 0.8896 21
DeepLearning_1_AutoML_1_20220217_161424 0.8830 22
DRF_1_AutoML_1_20220217_161424 0.8746 23
DeepLearning_grid_3_AutoML_1_20220217_161424_model_1 0.8266 24
DeepLearning_grid_2_AutoML_1_20220217_161424_model_1 0.8133 25
DeepLearning_grid_1_AutoML_1_20220217_161424_model_1 0.8045 26

Chúng ta có thể tính luôn AUC tương ứng với tất cả các mô hình ML trên Test Data (Table 2):

# AUC on test data by i-th model: 

getAUC_onTestData <- function(i) {
  
  # Extract i-th model: 
  h2o.getModel(autoML@leaderboard[i, 1]) -> best_ith
  
  # Model performance by ith model by AUC on Test data:  
  h2o.performance(model = best_ith, newdata = test_h2o) -> metrics_ith
  
  # Return output: 
  return(data.frame(AUC_Test = metrics_ith@metrics$AUC, model_id = best_ith@model_id))
  
}

# Calculate AUC for all models: 

lapply(1:nrow(df_results), getAUC_onTestData) -> auc_on_testData
do.call("bind_rows", auc_on_testData) -> auc_on_testData

# AUC by all models on test data: 
auc_on_testData %>% 
  select(model_id, AUC_Test) %>% 
  arrange(-AUC_Test) %>% 
  mutate(AUC_Test = round(AUC_Test, 4), Rank = 1:nrow(.)) %>% 
  knitr::kable(caption = "Table 2: AUC on Test Data")
Table 2: AUC on Test Data
model_id AUC_Test Rank
GBM_2_AutoML_1_20220217_161424 0.9181 1
StackedEnsemble_BestOfFamily_2_AutoML_1_20220217_161424 0.9175 2
StackedEnsemble_AllModels_2_AutoML_1_20220217_161424 0.9173 3
StackedEnsemble_AllModels_1_AutoML_1_20220217_161424 0.9168 4
StackedEnsemble_AllModels_3_AutoML_1_20220217_161424 0.9168 5
GBM_1_AutoML_1_20220217_161424 0.9162 6
StackedEnsemble_BestOfFamily_1_AutoML_1_20220217_161424 0.9160 7
StackedEnsemble_BestOfFamily_3_AutoML_1_20220217_161424 0.9147 8
GBM_3_AutoML_1_20220217_161424 0.9146 9
StackedEnsemble_AllModels_4_AutoML_1_20220217_161424 0.9142 10
StackedEnsemble_BestOfFamily_5_AutoML_1_20220217_161424 0.9133 11
GBM_grid_1_AutoML_1_20220217_161424_model_1 0.9118 12
StackedEnsemble_AllModels_6_AutoML_1_20220217_161424 0.9093 13
StackedEnsemble_AllModels_5_AutoML_1_20220217_161424 0.9023 14
GBM_grid_1_AutoML_1_20220217_161424_model_3 0.9011 15
StackedEnsemble_BestOfFamily_4_AutoML_1_20220217_161424 0.8999 16
GBM_4_AutoML_1_20220217_161424 0.8947 17
XRT_1_AutoML_1_20220217_161424 0.8945 18
DRF_1_AutoML_1_20220217_161424 0.8921 19
GBM_grid_1_AutoML_1_20220217_161424_model_2 0.8905 20
GBM_5_AutoML_1_20220217_161424 0.8889 21
GLM_1_AutoML_1_20220217_161424 0.8734 22
DeepLearning_1_AutoML_1_20220217_161424 0.8466 23
DeepLearning_grid_2_AutoML_1_20220217_161424_model_1 0.7950 24
DeepLearning_grid_1_AutoML_1_20220217_161424_model_1 0.7948 25
DeepLearning_grid_3_AutoML_1_20220217_161424_model_1 0.7554 26

Với kết quả AUC trên Test Data là 0.9181 (khi sử dụng mô hình ML tốt nhất) thì kết quả này cao hơn team ở vị trí thứ 5 (Ochiai) với AUC = 0.9165.

Key Notes

  1. Sử dụng AUC như là một tiêu chuẩn cho lựa chọn biến số cho mô hình Logistic và sử dụng Test Data để đánh giá lại mô hình thì AUC đạt 0.903. Đây là một kết quả không quá tệ và cách thức lựa chọn biến số này dễ thực hiện và không quá mất thời gian. Việc sử dụng AUC như là một tiêu chuẩn lựa chọn biến số cũng có nhiều paper đề cập bạn đọc có thể tự tìm hiểu thêm.

  2. Cách tiếp cận Automated Machine Learning có thể được sử dụng để nâng cao hơn nữa khả năng dự báo và phân loại phá sản của doanh nghiệp. Kết quả thực nghiệm chỉ ra rằng nếu sử dụng cách tiếp cận này thì AUC tăng (so với mô hình Logistic tốt nhất) và đạt 0.9181 trên Test Data.

  3. Chúng ta có thể đi xa hơn bằng cách sử dụng Automated Machine Learning với danh sách 18 biến số đã được sử dụng cho mô hình Logistic tối ưu như sau:

autoML0903 <- h2o.automl(x = var_auc_0903, 
                         y = response, 
                         training_frame = train_h2o, 
                         leaderboard_frame = valid_h2o, 
                         stopping_metric = "AUC", 
                         stopping_rounds = 10, 
                         stopping_tolerance = 0.025, 
                         max_models = 15, 
                         max_runtime_secs = 60*60, 
                         seed = 1, 
                         sort_metric = "AUC")
  1. Đây là dữ liệu bất cân bằng rất cao (chỉ có 3.2% các quan sát là Bankrupt) nên có thể cần xem xét đến khả năng sử dụng các giải pháp resampling dữ liệu như SMOTE, upsampling - downsampling.

R Environment and OS

sessionInfo()
## R version 4.1.2 (2021-11-01)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 19043)
## 
## Matrix products: default
## 
## locale:
## [1] LC_COLLATE=English_United States.1252 
## [2] LC_CTYPE=English_United States.1252   
## [3] LC_MONETARY=English_United States.1252
## [4] LC_NUMERIC=C                          
## [5] LC_TIME=English_United States.1252    
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] h2o_3.36.0.2    pROC_1.18.0     caret_6.0-90    lattice_0.20-45
##  [5] forcats_0.5.1   stringr_1.4.0   dplyr_1.0.7     purrr_0.3.4    
##  [9] readr_2.1.0     tidyr_1.1.4     tibble_3.1.6    ggplot2_3.3.5  
## [13] tidyverse_1.3.1
## 
## loaded via a namespace (and not attached):
##  [1] nlme_3.1-153         bitops_1.0-7         fs_1.5.0            
##  [4] lubridate_1.8.0      bit64_4.0.5          httr_1.4.2          
##  [7] tools_4.1.2          backports_1.3.0      bslib_0.3.1         
## [10] utf8_1.2.2           R6_2.5.1             rpart_4.1-15        
## [13] DBI_1.1.1            colorspace_2.0-2     nnet_7.3-16         
## [16] withr_2.4.3          tidyselect_1.1.1     bit_4.0.4           
## [19] compiler_4.1.2       cli_3.1.0            rvest_1.0.2         
## [22] xml2_1.3.2           labeling_0.4.2       sass_0.4.0          
## [25] scales_1.1.1         digest_0.6.28        rmarkdown_2.11      
## [28] pkgconfig_2.0.3      htmltools_0.5.2      parallelly_1.30.0   
## [31] highr_0.9            dbplyr_2.1.1         fastmap_1.1.0       
## [34] rlang_0.4.12         readxl_1.3.1         rstudioapi_0.13     
## [37] farver_2.1.0         jquerylib_0.1.4      generics_0.1.1      
## [40] jsonlite_1.7.3       vroom_1.5.6          ModelMetrics_1.2.2.2
## [43] RCurl_1.98-1.6       magrittr_2.0.1       Matrix_1.3-4        
## [46] Rcpp_1.0.7           munsell_0.5.0        fansi_0.5.0         
## [49] lifecycle_1.0.1      stringi_1.7.6        yaml_2.2.1          
## [52] MASS_7.3-54          plyr_1.8.6           recipes_0.1.17      
## [55] grid_4.1.2           parallel_4.1.2       listenv_0.8.0       
## [58] crayon_1.4.2         haven_2.4.3          splines_4.1.2       
## [61] hms_1.1.1            knitr_1.36           pillar_1.6.4        
## [64] stats4_4.1.2         future.apply_1.8.1   reshape2_1.4.4      
## [67] codetools_0.2-18     reprex_2.0.1         glue_1.5.0          
## [70] evaluate_0.14        data.table_1.14.2    modelr_0.1.8        
## [73] vctrs_0.3.8          tzdb_0.2.0           foreach_1.5.1       
## [76] cellranger_1.1.0     gtable_0.3.0         future_1.23.0       
## [79] assertthat_0.2.1     xfun_0.28            gower_0.2.2         
## [82] prodlim_2019.11.13   broom_0.7.10         class_7.3-19        
## [85] survival_3.2-13      timeDate_3043.102    iterators_1.0.13    
## [88] lava_1.6.10          globals_0.14.0       ellipsis_0.3.2      
## [91] ipred_0.9-12
LS0tDQp0aXRsZTogIkNvcnBvcmF0ZSBCYW5rcnVwdGN5IFByZWRpY3Rpb24gQ29tcGV0aXRpb24gMjAyMSINCmF1dGhvcjogJ0F1dGhvcjogTmd1eWVuIENoaSBEdW5nJw0Kc3VidGl0bGU6ICJSIE1hY2hpbmUgTGVhcm5pbmcgU2VyaWVzIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgIyBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBoaWdobGlnaHQ6IHplbmJ1cm4NCiAgICAjIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6ICJmbGF0bHkiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSkNCg0KYGBgDQoNCiFbXShDOlxVc2Vyc1xcQWRtaW5cXERvY3VtZW50c1xcYmFua3J1cC5qcGcpDQoNCiMgTW90aXZhdGlvbiANCg0KROG7sSBiw6FvIHBow6Egc+G6o24gY+G7p2EgY8OhYyBkb2FuaCBuZ2hp4buHcCAoQ29ycG9yYXRlIEJhbmtydXB0YW5jeSkgYmFvIGfhu5NtIGPhuqMgY8OhYyB04buVIGNo4bupYyB0w6BpIGNow61uaCDEkcOjIGPDsyBuaGnhu4F1IHTDoWMgZ2nhuqMgbmdoacOqbiBj4bupdS4gVOG6oWkgVmnhu4d0IE5hbSB0aMOsIGPDsyBsdeG6rW4gdsSDbiBUaeG6v24gU8SpIGPhu6dhIHTDoWMgZ2nhuqMgW8SQ4bq3bmcgSHV5IE5nw6JuXShodHRwczovL21mZS5lZHUudm4vZGFuZ2h1eW5nYW4vKSB24bubaSDEkeG7gSB0w6BpICpYw6J5IGThu7FuZyBtw7QgaMOsbmggY+G6o25oIGLDoW8gbmd1eSBjxqEgduG7oSBu4bujIMSR4buRaSB24bubaSBjw6FjIG5nw6JuIGjDoG5nIHRoxrDGoW5nIG3huqFpIGPhu5UgcGjhuqduIFZp4buHdCBOYW0qIChi4bqhbiDEkeG7jWMgcXVhbiB0w6JtIGPDsyB0aOG7gyBkb3dubG9hZCB0b8OgbiB2xINuIG5naGnDqm4gY+G7qXUgbsOgeSBbdOG6oWkgxJHDonldKGh0dHBzOi8vc2RoLm5ldS5lZHUudm4veGVtLXRhaS1saWV1L05naGllbi1jdXUtc2luaC1EYW5nLUh1eS1OZ2FuLWJhby12ZS1sdWFuLWFuLXRpZW4tc2lfXzE5MTM2Lmh0bWwpKS4gTmdoacOqbiBj4bupdSBuw6B5IHNvIHPDoW5oIGto4bqjIG7Eg25nIGThu7EgYsOhbyBwaMOhIHPhuqNuIGNobyBjw6FjIG5nw6JuIGjDoG5nIHThu6sgYmEgbcO0IGjDrG5oOiBMb2dpc3RpYyAtIG3hu5l0IG3DtCBow6xuaCB0aOG7kW5nIGvDqiDEkWnhu4NuIGjDrG5oIHbDoCBoYWkgbcO0IGjDrG5oIHBoaSB0aGFtIHPhu5EgbMOgIG3huqFuZyBub3JvbiAoTmV1cmFsIE5ldHdvcmspIHbDoCBjw6J5IHF1eeG6v3QgxJHhu4tuaCAoRGVjaXNpb24gVHJlZXMpLiBUw6FjIGdp4bqjIHPhu60gZOG7pW5nIGLhu5kgZOG7ryBsaeG7h3UgdHJhaW4gKGPDsyAxMTQgcXVhbiBzw6F0KSDEkeG7gyBodeG6pW4gbHV54buHbiBiYSBtw7QgaMOsbmggaMOsbmggc2F1IMSRw7MgKipz4butIGThu6VuZyBs4bqhaSoqIGNow61uaCBi4buZIGThu68gbGnhu4d1IG7DoHkgxJHhu4Mga2nhu4NtIHRyYSB2w6AgxJHDoW5oIGdpw6EgY2jhuqV0IGzGsOG7o25nIHBow6JuIGxv4bqhaSAtIGThu7EgYsOhbyB2w6Agc28gc8OhbmggYmEgbcO0IGjDrG5oLiANCg0KxJDDonkgbMOgIGPDoWNoIHRp4bq/cCBj4bqtbiBjaMawYSBwaMO5IGjhu6NwLiBN4bq3YyBkw7kgdMOhYyBnaeG6oyBjxaluZyBjw7MgbsOzaSDEkeG6v24gc+G7rSBk4bulbmcgZOG7ryBsaeG7h3UgY+G7p2EgbsSDbSAyMDE1IMSR4buDICIuLi5raeG7g20gdHJhIGhp4buHdSBzdeG6pXQgbmdvw6BpIG3huqt1IGPhu6dhIG3DtCBow6xuaCIgLSB04bupYyBsw6AgdGVzdCBkYXRhLiBWaeG7h2Mgc28gc8OhbmggaGnhu4d1IHF14bqjIGPhu6dhIGPDoWMgbcO0IGjDrG5oIGtow6FjIG5oYXUgc+G6vSDEkcaw4bujYyDEkcOhbmggZ2nDoSB0csOqbiB0ZXN0IGRhdGEgY2jhu6kga2jDtG5nIHBo4bqjaSBsw6AgdHJhaW4gZGF0YSB2w6AgxJHGsOG7o2MgbcO0IHThuqMga8SpIFvhu58gxJHDonldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1RyYWluaW5nLF92YWxpZGF0aW9uLF9hbmRfdGVzdF9zZXRzKS4gDQoNCk5nb8OgaSByYSDhu58gbHXhuq1uIHbEg24gbsOgeSB2aeG7h2Mgc28gc8Ohbmgga2jhuqMgbsSDbmcgZOG7sSBiw6FvIC0gcGjDom4gbG/huqFpIGPhu6dhIGJhIG3DtCBow6xuaCB0w6FjIGdp4bqjIGThu7FhIHbDoG8gbWEgdHLhuq1uIG5o4bqnbSBs4bqrbiAoQ29uZnVzaW9uIE1hdHJpeCkuIFZp4buHYyBkw6FuIG5ow6NuIGNobyBZIGzDoCAwIGhheSAxIGhvw6BuIHRvw6BuIHBo4bulIHRodeG7mWMgdsOgbzogKDEpIHjDoWMgeHXhuqV0IGThu7EgYsOhbyB04burIG3DtCBow6xuaCwgdsOgICgyKSBuZ8aw4buhbmcgxJHGsOG7o2MgY2jhu41uIMSR4buDIGTDoW4gbmjDo24gLSBwaMOibiBsb+G6oWkuIE5oxrBuZyDhu58gxJHDonkgdMOhYyBnaeG6oyBt4bq3YyDEkeG7i25oIHPhu60gZOG7pW5nIG5nxrDhu6FuZyAwLjUgKHhlbSB0cmFuZyAxMDUgY+G7p2EgbHXhuq1uIHbEg24pIGzDoCBt4buZdCB0aGnhur91IHPDs3QuIEvhur90IHF14bqjIGPhu6dhIG1hIHRy4bqtbiBuaOG6p20gbOG6q24gbMOgIHBo4bulIHRodeG7mWMgdsOgbyBuZ8aw4buhbmcgxJHGsOG7o2MgY2jhu41uIHbDoCDEkWnhu4F1IG7DoHkgxJHGsOG7o2MgZ2nhuqNpIHRow61jaCBjaGkgdGnhur90IFvhu58gxJHDonldKGh0dHBzOi8vcnB1YnMuY29tL2NoaWR1bmdrdC80NDc5ODkpLiBW4bubaSBt4buZdCBtw7QgaMOsbmggxJHDoyBjaOG7jW4gaG/DoG4gdG/DoG4gY8OzIHRo4buDIHTDrG0gxJHGsOG7o2MgbeG7mXQgbmfGsOG7oW5nIHThu5FpIMawdSB0xrDGoW5nIOG7qW5nIMSR4buDLCB2w60gZOG7pSwgY+G7sWMgxJHhuqFpIGjDs2EgQWNjdXJhY3kuIERvIHbhuq15IGjhu6NwIGzDrSBoxqFuIHPhur0gbMOgIGto4bqjbyBzw6F0IHPhu7EgYmnhur9uIMSR4buVaSBj4bunYSBBY2N1cmFjeSB0aGVvIG5nxrDhu6FuZyBwaMOibiBsb+G6oWkgxJHhu4MgxJHDoW5oIGdpw6EgxJHDum5nLCBjaMOtbmggeMOhYyB2w6AgdG/DoG4gZGnhu4duIGjGoW4ga2jhuqMgbsSDbmcgcGjDom4gbG/huqFpIGPhu6dhIGJhIG3DtCBow6xuaCBjaOG7qSBraMO0bmcgcGjhuqNpIGzDoCDEkcOhbmggZ2nDoSAqY2jhu4kgdOG6oWkgbeG7mXQgbmfGsOG7oW5nKi4gQ2jGsGEga+G7gyB2aeG7h2Mgc+G7rSBk4bulbmcgQWNjdXJhY3kgbMOgbSB0acOqdSBjaHXhuqluIHNvIHPDoW5oIC0gxJHDoW5oIGdpw6EgdsOgIGzhu7FhIGNo4buNbiBtw7QgaMOsbmggdHJvbmcgdHLGsOG7nW5nIGjhu6NwIG7DoHkgbMOgIGtow7RuZyBwaMO5IGjhu6NwIHbDrCBjw6FpIGdpw6EgcGjhuqNpIHRy4bqjIGPhu6dhIFR5cGUgSSBFcnJvciBraMOhYyBUeXBlIElJIEVycm9yIChi4bqhbiDEkeG7jWMgcXVhbiB0w6JtIGPDsyB0aOG7gyB0w6xtIGhp4buDdSB0aMOqbSBbdOG6oWkgxJHDonldKGh0dHBzOi8vbWxyLm1sci1vcmcuY29tL2FydGljbGVzL3R1dG9yaWFsL2Nvc3Rfc2Vuc2l0aXZlX2NsYXNzaWYuaHRtbCkpLiANCg0KTeG6t3Qga2jDoWMgYuG7mSBk4buvIGxp4buHdSBz4butIGThu6VuZyBjaG8gbHXhuq1uIHbEg24gbsOgeSBjw7Mgc+G7kSBsxrDhu6NuZyBxdWFuIHPDoXQgcXXDoSDDrXQuIERvIHbhuq15IHRyb25nIHBvc3QgbsOgeSBz4bq9IHRoYXkgdGjhur8gYuG6sW5nIGLhu5kgZOG7ryBsaeG7h3UgxJHDoyDEkcaw4bujYyBz4butIGThu6VuZyBjaG8gbmdoacOqbiBj4bupdSBjw7MgdMOqbiBbRmluYW5jaWFsIFJhdGlvcyBhbmQgQ29ycG9yYXRlIEdvdmVybmFuY2UgSW5kaWNhdG9ycyBpbiBCYW5rcnVwdGN5IFByZWRpY3Rpb246IEEgQ29tcHJlaGVuc2l2ZSBTdHVkeV0oaHR0cHM6Ly93d3cuc2NpZW5jZWRpcmVjdC5jb20vc2NpZW5jZS9hcnRpY2xlL2Ficy9waWkvUzAzNzcyMjE3MTYwMDA0MTI/dmlhJTNEaWh1YikgxJHEg25nIHRyw6puIEV1cm9wZWFuIEpvdXJuYWwgb2YgT3BlcmF0aW9uYWwgUmVzZWFyY2guIELhu5kgZOG7ryBsaeG7h3UgbsOgeSBjw7MgNjgxOSBxdWFuIHPDoXQgdsOgIDMuMiUgdHJvbmcgc+G7kSDEkcOzIMSRxrDhu6NjIHjDoWMgxJHhu4tuaCBsw6AgcGjDoSBz4bqjbi4gQuG7mSBk4buvIGxp4buHdSBuw6B5IMSRxrDhu6NjIHPhu60gZOG7pW5nIGNobyBjdeG7mWMgdGhpIFtDb3Jwb3JhdGUgQmFua3J1cHRjeSBQcmVkaWN0aW9uIDIwMjFdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vYy8xMDU2bGFiLWNvcnBvcmF0ZS1iYW5rcnVwdGN5LXByZWRpY3Rpb24tMjAyMS9vdmVydmlldykgYuG7n2kgUGjDsm5nIG5naGnDqm4gY+G7qXUga2hhaSBwaMOhIGThu68gbGnhu4d1IHRodeG7mWMgQ2h1YnUgVW5pdmVyc2l0eS4gQuG7mSBk4buvIGxp4buHdSBuw6B5IGPDsyB0aOG7gyBkb3dubG9hZCBbdOG6oWkgxJHDonldKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZmVkZXNvcmlhbm8vY29tcGFueS1iYW5rcnVwdGN5LXByZWRpY3Rpb24pLiBUacOqdSBjaHXhuqluIMSRxrDhu6NjIGzhu7FhIGNo4buNbiDEkeG7gyDEkcOhbmggZ2nDoSBraOG6oyBuxINuZyBk4buxIGLDoW8gdsOgIHBow6JuIGxv4bqhaSBj4bunYSBjw6FjIG3DtCBow6xuaCBsw6AgQVVDIHRyb25nIMSRw7MgNzAlIGThu68gbGnhu4d1IGJhbiDEkeG6p3Ugc+G7rSBk4bulbmcgbMOgbSB0ZXN0IGRhdGEgdsOgIHBo4bqnbiBjw7JuIGzhuqFpIDMwJSDEkcaw4bujYyBz4butIGThu6VuZyDEkeG7gyDEkcOhbmggZ2nDoSBtw7QgaMOsbmggdsOgIGRvIHbhuq15IGzDoCDEkcaw4bujYyBz4butIGThu6VuZyDEkeG7gyDEkcOhbmggZ2nDoSB0aOG7qSBo4bqhaSBj4bunYSBjw6FjIMSR4buZaSB0aGFtIGdpYSBk4buxIHRoaS4gRMaw4bubaSDEkcOieSBsw6AgUHJpdmF0ZSBTY29yZSAoQVVDKSB2w6AgdGjhu6kgaOG6oW5nIGPhu6dhIG3hu5l0IHPhu5EgxJHhu5lpIGPDsyB0aOG7qSBo4bqhbmcgY2FvIG5o4bqldDogDQoNCiFbXShDOlxVc2Vyc1xcQWRtaW5cXERvY3VtZW50c1xcYmFua3J1cF9yZXN1bHQuanBnKQ0KVHLGsOG7m2MgaOG6v3QgbG9hZCBi4buZIGThu68gbGnhu4d1IG7DoHkgdsOgIHRo4buxYyBoaeG7h24gbeG7mXQgc+G7kSBixrDhu5tjIHjhu60gbMOtIGThu68gbGnhu4d1IMSRxqFuIGdp4bqjbjogDQoNCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFfQ0KIyBDbGVhciBvdXIgUiBlbnZpcm9ubWVudDogDQpybShsaXN0ID0gbHMoKSkNCg0KIyBMb2FkIHRpZHl2ZXJzZSBwYWNrYWdlOiANCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQojIExvYWQgZGF0YTogDQpyZWFkX2NzdigiRjovZGF0YS5jc3YvZGF0YS5jc3YiKSAtPiBkYXRhDQoNCiMgUmVuYW1lIGZvciBhbGwgY29sdW1uczogDQoNCm9sZF9uYW1lcyA8LSBuYW1lcyhkYXRhKQ0KDQpvbGRfbmFtZXMgJT4lIHN0cl9yZXBsYWNlX2FsbCgiW15hLXp8XkEtWl0iLCAiIikgLT4gbmV3X25hbWVzDQoNCm5hbWVzKGRhdGEpIDwtIG5ld19uYW1lcw0KDQojIFJlbW92ZSBOZXRJbmNvbWVGbGFnIGFuZCByZWxhYmVsIGZvciBCYW5rcnVwdCBjb2x1bW46IA0KZGF0YSAlPiUgDQogIHNlbGVjdCgtTmV0SW5jb21lRmxhZykgJT4lIA0KICBtdXRhdGUoQmFua3J1cHQgPSBjYXNlX3doZW4oQmFua3J1cHQgPT0gMSB+ICJCYW5rcnVwdCIsIFRSVUUgfiAiTm9uQmFua3J1cHQiKSkgJT4lIA0KICBtdXRhdGUoQmFua3J1cHQgPSBhcy5mYWN0b3IoQmFua3J1cHQpKSAtPiBkZg0KDQojIFNldCByZXNwb25zZSBhbmQgcHJlZGljdG9yczogDQoNCnJlc3BvbnNlIDwtICJCYW5rcnVwdCINCg0KcHJlZGljdG9ycyA8LSBuYW1lcyhkZiAlPiUgc2VsZWN0KC1yZXNwb25zZSkpDQpgYGANCg0KRG8gdGjhu51pIGjhuqFuIGN14buZYyB0aGkgxJHDoyBo4bq/dCBuw6puIGNow7puZyB0YSBjw7MgdGjhu4MgcGjDom4gY2hpYSBk4buvIGxp4buHdSB0aGVvIHThu4kgbOG7hyA3MC0zMCBuaMawIMSRw6MgbcO0IHThuqMgdsOgIHPhu60gZOG7pW5nIG5oxrAgbMOgIHByb3h5IMSR4buDIHNvIHPDoW5oIGvhur90IHF14bqjIGPhu6dhIG5o4buvbmcgbcO0IGjDrG5oIHBow6JuIGxv4bqhaSBtw6AgY2jDum5nIHRhIHjDonkgZOG7sW5nIHbhu5tpIGvhur90IHF14bqjIGPhu6dhIG5o4buvbmcgxJHhu5lpIMSRw6MgdGhhbSBnaWEgY3Xhu5ljIHRoaTogDQoNCmBgYHtyfQ0KIyBTcGxpdCBvdXIgZGF0YTogDQoNCmxpYnJhcnkoY2FyZXQpDQoNCnNldC5zZWVkKDEpDQoNCmlkIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IGRmICU+JSBwdWxsKHJlc3BvbnNlKSwgcCA9IDAuNywgbGlzdCA9IEZBTFNFKQ0KDQojIDcwJSBkYXRhIGZvciB0cmFpbmluZyBtb2RlbHM6IA0KdHJhaW4gPC0gZGZbaWQsIF0gDQoNCiMgMzAlIGRhdGEgd2lsbCBiZSB1c2VkIGZvciBjb21wYXJpc2lvbiBhbmQgZXZhbHVhdGluZyBtb2RlbCBwZXJmb3JtYW5jZTogDQp0ZXN0IDwtIGRmWy1pZCwgXSANCmBgYA0KDQoNCiMgQVVDIGZvciBGZWF0dXJlIFNlbGVjdGlvbiANCg0KTG9naXN0aWMgbMOgIG3hu5l0IGPDoWNoIHRp4bq/cCBj4bqtbiBj4bunYSB0aOG7kW5nIGvDqiB0cnV54buBbiB0aOG7kW5nIG5oxrBuZyB0xrDGoW5nIMSR4buRaSBoaeG7h3UgcXXhuqMuIFbhuqVuIMSR4buBIGzDoCBjw7MgxJHhur9uIDk0IGNo4buJIHPhu5EgdMOgaSBjaMOtbmggKEZpbmFuY2lhbCBSYXRpb3MpIHbDoCBjxaluZyBsw6AgOTQgZmVhdHVyZXMgdsOgIGtow7RuZyBwaOG6o2kgbeG7jWkgZmVhdHVyZSDEkeG7gXUgdOG7kXQgdsOgIGPDsyBoaeG7h3UgcXXhuqMgbmjGsCBuaGF1IGtoaSBz4butIGThu6VuZyBjaG8gbcO0IGjDrG5oIExvZ2lzdGljLiBUcm9uZyBwb3N0IG7DoHkgdMO0aSBraMO0bmcgc+G7rSBk4bulbmcgY8OhY2ggdGjhu6ljIGzhu7FhIGNo4buNbiBiaeG6v24gc+G7kSBuaMawIGPDoWMgdMOhYyBnaeG6oyBj4bunYSBuZ2hpw6puIGPhu6l1ICpGaW5hbmNpYWwgUmF0aW9zIGFuZCBDb3Jwb3JhdGUgR292ZXJuYW5jZSBJbmRpY2F0b3JzIGluIEJhbmtydXB0Y3kgUHJlZGljdGlvbjogQSBDb21wcmVoZW5zaXZlIFN0dWR5KiBtw6Agc+G7rSBk4bulbmcgQVVDIG5oxrAgbMOgIHRpw6p1IGNodeG6qW4gxJHhu4MgbOG7sWEgY2jhu41uIGJp4bq/biBz4buRLiANCg0KVHLGsOG7m2MgaOG6v3QgaHXhuqVuIGx1eeG7h24gOTQgbcO0IGjDrG5oIExvZ2lzdGljIMSRxqFuIGJp4bq/biAodMawxqFuZyDhu6luZyB24bubaSA5NCBiaeG6v24pIHbDoCB0w61uaCBBVUMgdMawxqFuZyDhu6luZyB0csOqbiB0ZXN0IGRhdGEgKFN0YWdlIDEpLiBTYXUgxJHDsyBjaOG7jW4gcmEgbeG7mSB04buVIGjhu6NwIGPDoWMgYmnhur9uIHPhu5EgZOG7sWEgdHLDqm4gbmfGsOG7oW5nIEFVQyBzYW8gY2hvIEFVQyB0csOqbiBUZXN0IERhdGEgbMOgIGzhu5tuIG5o4bqldCAoU3RhZ2UgMikuIETGsOG7m2kgxJHDonkgbMOgIFIgY29kZXM6ICAgDQoNCg0KYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZSA9IFRSVUV9DQojLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgICAgICAgIFN0YWdlIDENCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIEZ1bmN0aW9uIGV4dHJhY3RzIFJPQy9BVUMgZm9yIGEgcHJlZGljdG9yIHNlbGVjdGVkOiANCg0KbGlicmFyeShwUk9DKSAjIEZvciBjYWxjdWxhdGluZyBBVUMuIA0KDQphY3R1YWxfbGFiZWxzIDwtIHRlc3QkQmFua3J1cHQNCg0KcmV0dXJuUk9DX0FVQyA8LSBmdW5jdGlvbihwcmVkaWN0b3Jfc2VsZWN0ZWQpIHsNCiAgDQogIGYgPC0gYXMuZm9ybXVsYShwYXN0ZTAocmVzcG9uc2UsICIgfiAiLCBwcmVkaWN0b3Jfc2VsZWN0ZWQpKQ0KICBsb2dpdCA8LSBnbG0oZiwgZmFtaWx5ID0gImJpbm9taWFsIiwgZGF0YSA9IHRyYWluKQ0KICBwcm9iX3ByZWQgPC0gcHJlZGljdChsb2dpdCwgdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQogIG15X2F1YyA8LSByb2MoYWN0dWFsX2xhYmVscywgcHJvYl9wcmVkKSRhdWMgJT4lIGFzLm51bWVyaWMoKQ0KICByZXR1cm4odGliYmxlKHByZWRpY3RvciA9IHByZWRpY3Rvcl9zZWxlY3RlZCwgYXVjID0gbXlfYXVjKSkNCiAgDQp9DQoNCg0KIyBST0MvQVVDIGJ5IGEgZ2l2ZW4gcHJlZGljdG9yOiANCg0KZG8uY2FsbCgiYmluZF9yb3dzIiwgbGFwcGx5KHByZWRpY3RvcnMsIHJldHVyblJPQ19BVUMpKSAlPiUgDQogIGFycmFuZ2UoLWF1YykgLT4gZGZfYXVjDQoNCiMgU29tZSBSZXN1bHRzOg0KDQpoZWFkKGRmX2F1YykNCmBgYA0KDQpOaMawIHbhuq15IG7hur91IGNo4buJIHPhu60gZOG7pW5nIGJp4bq/biBz4buRIE5ldEluY29tZXRvVG90YWxBc3NldHMgdGjDrCBBVUMgdHLDqm4gVGVzdCBEYXRhIGzDoCAwLjg4MCAtIG3hu5l0IGNvbiBz4buRIGtow6EgY2FvLiBUaMO0bmcgdGluIG7DoHkgxJHGsOG7o2Mgc+G7rSBk4bulbmcgxJHhu4MgbOG7sWEgY2jhu41uIHJhIGPDoWMgYmnhur9uIGNobyBtw7QgaMOsbmggTG9naXN0aWMuIENo4bqzbmcgaOG6oW4gdGnDqnUgY2h14bqpbiDEkeG6t3QgcmEgY8OzIHRo4buDIGzDoCAqY2jhu4kgY2jhu41uIGPDoWMgYmnhur9uIG7DoG8gbcOgIG3DtCBow6xuaCBMb2dpc3RpYyDEkcahbiBiaeG6v24gdMawxqFuZyDhu6luZyBs4bubbiBoxqFuIDAuNyouIA0KDQrEkOG7gyBo4buXIHRy4bujIGNobyBTdGFnZSAyIGNow7puZyB0YSB2aeG6v3QgaMOgbSBjw7MgdMOqbiAqKnJldHVyblJPQ19BVUNUZXN0RGF0YSoqIHTDrW5oIHRvw6FuIEFVQyB0csOqbiBUZXN0IERhdGEga2hpIGJp4bq/dCB0csaw4bubYyBjw6FjIGJp4bq/biDEkcaw4bujYyBs4buxYSBjaOG7jW4gY2hvIG3DtCBow6xuaCBMb2dpc3RpYzogDQoNCmBgYHtyfQ0KIyBGdW5jdGlvbiBleHRyYWN0cyBST0MvQVVDIG9uIHRlc3QgZGF0YTogDQoNCnJldHVyblJPQ19BVUNUZXN0RGF0YSA8LSBmdW5jdGlvbihwcmVkaWN0b3Jfc2VsZWN0ZWQpIHsNCiAgZiA8LSBhcy5mb3JtdWxhKHBhc3RlMChyZXNwb25zZSwgIiB+ICIsIHBhc3RlKHByZWRpY3Rvcl9zZWxlY3RlZCwgY29sbGFwc2UgPSAiICsgIikpKQ0KICBsb2dpdCA8LSBnbG0oZiwgZmFtaWx5ID0gImJpbm9taWFsIiwgZGF0YSA9IHRyYWluKQ0KICBwcm9iX3ByZWQgPC0gcHJlZGljdChsb2dpdCwgdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQogIG15X2F1YyA8LSByb2MoYWN0dWFsX2xhYmVscywgcHJvYl9wcmVkKSRhdWMgJT4lIGFzLm51bWVyaWMoKQ0KICByZXR1cm4obXlfYXVjKQ0KICANCn0NCmBgYA0KDQpT4butIGThu6VuZyBow6BtIG7DoHkgxJHhu4MgdMOtbmggQVVDIHRyw6puIFRlc3QgRGF0YSB0xrDGoW5nIOG7qW5nIGNobyBjw6FjIG3DtCBow6xuaCBMb2dpc3RpYyBraGkgYmnhur90IG5nxrDhu6FuZyBBVUMgxJHhu4MgbOG7sWEgY2jhu41uIGJp4bq/biBjaG8gbcO0IGjDrG5oOiANCg0KYGBge3J9DQojIFNldCBhIHNlcXVlbmNlIG9mIHRocmVzaG9sZHM6IA0KDQphdWNfdGhyZXNob2xkcyA8LSBzZXEobWluKGRmX2F1YyRhdWMpLCBtYXgoZGZfYXVjJGF1YyksIDAuMDEpDQoNCmF1Y19zcGFjZSA8LSBOVUxMDQoNCiMgQVVDIGJ5IHRocmVzaG9sZDogDQoNCmZvciAoaiBpbiBhdWNfdGhyZXNob2xkcykgew0KICANCiAgZGZfYXVjICU+JSANCiAgICBmaWx0ZXIoYXVjID49IGopICU+JSANCiAgICBwdWxsKHByZWRpY3RvcikgLT4gcHJlZGljdG9yc19mb3JfbW9kZWxsaW5nDQogIA0KICByZXR1cm5ST0NfQVVDVGVzdERhdGEocHJlZGljdG9yc19mb3JfbW9kZWxsaW5nKSAtPiBteV9hdWMNCiAgDQogIGF1Y19zcGFjZSA8LSBjKGF1Y19zcGFjZSwgbXlfYXVjKQ0KICANCn0NCg0KDQp0aWJibGUoYXVjX3RocmVzaG9sZHMgPSBhdWNfdGhyZXNob2xkcywgYXVjID0gYXVjX3NwYWNlKSAtPiBkZl9hdWNfdGhyZXNob2xkDQoNCiMgRmVhdHVyZXMgY3JlYXRlIG1heCBBVUMgb24gdGVzdCBkYXRhOiANCg0KYXVjX21heCA8LSBkZl9hdWNfdGhyZXNob2xkICU+JSBzbGljZSh3aGljaC5tYXgoYXVjKSkNCg0KYXVjX21heA0KYGBgDQoNCkvhur90IHF14bqjIG7DoHkgY2hvIHRo4bqleSBraGkgbOG7sWEgY2jhu41uIGPDoWMgYmnhur9uIGPDsyBuZ8aw4buhbmcgQVVDID0gMC44MjggdGjDrCBz4bq9IMSR4bq3dCDEkcaw4bujYyBBVUMgdOG7kWkgxrB1IHRyw6puIFRlc3QgRGF0YSB2w6AgQVVDIMSR4bqhdCDEkcaw4bujYyB0csOqbiBUZXN0IERhdGEgc+G6vSBsw6AgMC45MDMuIEvhur90IHF14bqjIGtoacOqbSB04buRbiBuw6B5IGNhbyBoxqFuIHbhu4sgdHLDrSB0aOG7qSA3IHbhu5tpIEFVQyA9IDAuODc1NCAoVGVhbSBOZ3V5ZW5IdXUgQmFvTG9uZykuIA0KDQpExrDhu5tpIMSRw6J5IGzDoCAxOCBiaeG6v24gc+G7kSDEkcaw4bujYyBs4buxYSBjaOG7jW4gY2hvIG3DtCBow6xuaCBMb2dpc3RpYzogDQoNCmBgYHtyfQ0KIyBGZWF0dXJlcyA+PSAwLjgyODogDQoNCmRmX2F1YyAlPiUgDQogIGZpbHRlcihhdWMgPj0gYXVjX21heCRhdWNfdGhyZXNob2xkcykgJT4lIA0KICBwdWxsKHByZWRpY3RvcikgLT4gdmFyX2F1Y18wOTAzDQoNCiMgMTggZmVhdHVyZXMgc2VsZWN0ZWQgZm9yIExvZ2lzdGljIE1vZGVsOiANCnZhcl9hdWNfMDkwMw0KYGBgDQoNCkNow7puZyB0YSBjw7MgdGjhu4Mga2jhuqNvIHPDoXQgQVVDIHRyw6puIFRlc3QgRGF0YSAoRmlndXJlIDEpIHbhu5tpIMSRaeG7g20gbcOgdSDEkeG7jyBsw6AgbWF4IEFVQzogDQoNCmBgYHtyfQ0KZGZfYXVjX3RocmVzaG9sZCAlPiUgDQogIGdncGxvdChhZXMoYXVjX3RocmVzaG9sZHMsIGF1YykpICsgDQogIGdlb21fbGluZShzaXplID0gMSwgY29sb3IgPSAiYmx1ZSIpICsgDQogIGdlb21fcG9pbnQoZGF0YSA9IGF1Y19tYXgsIGFlcyhhdWNfdGhyZXNob2xkcywgYXVjKSwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDIpICsgDQogICAgbGFicyh4ID0gIkFVQyBUaHJlc2hvbGQiLCANCiAgICAgICAgIHkgPSAiQVVDIG9uIFRlc3QgRGF0YSIsIA0KICAgICAgICAgdGl0bGUgPSAiRmlndXJlIDE6IEFVIG9uIFRlc3QgRGF0YSBieSBBVUMgVGhyZXNob2xkIGZvciBGZWF0dXJlIFNlbGVjdGlvbiIpDQpgYGANCg0KDQpDaMO6bmcgdGEgY8OzIHRo4buDIGto4bqjbyBzw6F0IHPDonUgdsOgIGvEqSBoxqFuIG7hu69hIGNo4bqldCBsxrDhu6NuZyBwaMOibiBsb+G6oWkgY8WpbmcgbmjGsCBjw6FjIMSR4bq3YyDEkWnhu4NtIGPhu6dhIGhhaSBjw6FjaCB0aeG6v3AgY+G6rW4gbmjGsCBzYXU6IA0KDQpgYGB7cn0NCiMgTG9naXN0aWMgd2l0aCAwOTAzLWZlYXR1cmVzOiANCg0KZl8wOTAzIDwtIGFzLmZvcm11bGEocGFzdGUwKHJlc3BvbnNlLCAiIH4gIiwgcGFzdGUodmFyX2F1Y18wOTAzLCBjb2xsYXBzZSA9ICIgKyAiKSkpDQoNCiMgTG9naXN0aWMgd2l0aCBhbGwgZmVhdHVyZXM6IA0KDQpmX2FsbCA8LSBhcy5mb3JtdWxhKHBhc3RlMChyZXNwb25zZSwgIiB+ICIsIHBhc3RlKHByZWRpY3RvcnMsIGNvbGxhcHNlID0gIiArICIpKSkNCg0KIyBOZXcgbWV0cmljIGFuZCBzYW1wbGluZyB0ZWNobmlxdWUgZm9yIHNlYXJjaGluZyBvcHRpbWFsIHBhcmFtZXRlcnM6ICANCg0Kc2FtcGxpbmdfbmV3IDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzc1Byb2JzID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtYmVyID0gMywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGVhdHMgPSAzKQ0KDQojIFRyYWluIGFuZCB0dXJuIFJGIHdoaWNoIFJPQy1BVUMgdXNlZCBmb3Igc2VhcmNoaW5nIG9wdGltYWwgcGFyYW1ldGVyczogDQoNCnNldC5zZWVkKDI5KQ0KDQp0cmFpbihmXzA5MDMsIA0KICAgICAgZGF0YSA9IHRyYWluLCANCiAgICAgIG1ldGhvZCA9ICJnbG0iLCANCiAgICAgIHRyQ29udHJvbCA9IHNhbXBsaW5nX25ldykgLT4gbG9naXRfMDkwMw0KDQpzZXQuc2VlZCgyOSkNCg0KdHJhaW4oZl9hbGwsIA0KICAgICAgZGF0YSA9IHRyYWluLCANCiAgICAgIG1ldGhvZCA9ICJnbG0iLCANCiAgICAgIHRyQ29udHJvbCA9IHNhbXBsaW5nX25ldykgLT4gbG9naXRfYWxsDQoNCnBkXzA5MDMgPC0gcHJlZGljdChsb2dpdF8wOTAzLCB0ZXN0LCB0eXBlID0gInByb2IiKSAlPiUgcHVsbChyZXNwb25zZSkgDQoNCnBkX2FsbCA8LSBwcmVkaWN0KGxvZ2l0X2FsbCwgdGVzdCwgdHlwZSA9ICJwcm9iIikgJT4lIHB1bGwocmVzcG9uc2UpIA0KDQpyb2MoYWN0dWFsX2xhYmVscywgcGRfMDkwMykgLT4gcm9jXzA5MDMNCg0Kcm9jKGFjdHVhbF9sYWJlbHMsIHBkX2FsbCkgLT4gcm9jX2FsbA0KDQpzZW5fc3BlY18wOTAzIDwtIHRpYmJsZShUUFIgPSByb2NfMDkwMyRzZW5zaXRpdml0aWVzLCBGUFIgPSAxIC0gcm9jXzA5MDMkc3BlY2lmaWNpdGllcykNCg0Kc2VuX3NwZWNfYWxsIDwtIHRpYmJsZShUUFIgPSByb2NfYWxsJHNlbnNpdGl2aXRpZXMsIEZQUiA9IDEgLSByb2NfYWxsJHNwZWNpZmljaXRpZXMpDQoNCmRmX3JvYyA8LSBiaW5kX3Jvd3Moc2VuX3NwZWNfMDkwMyAlPiUgbXV0YXRlKE1vZGVsID0gIjkwMyIpLCBzZW5fc3BlY19hbGwgJT4lIG11dGF0ZShNb2RlbCA9ICJBbGwiKSkNCg0KZGZfcm9jICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gRlBSLCB5bWluID0gMCwgeW1heCA9IFRQUiwgY29sb3IgPSBNb2RlbCwgZmlsbCA9IE1vZGVsKSkrDQogIGdlb21fcG9seWdvbihhZXMoeSA9IFRQUiksIGFscGhhID0gMC4yKSsNCiAgZ2VvbV9wYXRoKGFlcyh5ID0gVFBSKSwgc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gImdyYXkzNyIsIHNpemUgPSAxLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArIA0KICB0aGVtZV9idygpICsNCiAgY29vcmRfZXF1YWwoKSArIA0KICBsYWJzKHggPSAiRlBSICgxIC0gU3BlY2lmaWNpdHkpIiwgDQogICAgICAgeSA9ICJUUFIgKFNlbnNpdGl2aXR5KSIsIA0KICAgICAgIHRpdGxlID0gIkZpZ3VyZSAyOiBNb2RlbCBDb21wYXJpc2lvbiBieSBBVUMtUk9DIG9uIFRlc3QgRGF0YSIsIA0KICAgICAgIHN1YnRpdGxlID0gIjA1Mi1QcmVkaWN0b3IgTG9naXN0aWMgPSAwLjkwMywgQWxsLVByZWRpY3RvciBMb2dpc3RpYyA9IDAuNDM2IikNCmBgYA0KDQpGaWd1cmUgMiBjaG8gdGjhuqV5IG7hur91IHPhu60gZOG7pW5nIHThuqV0IGPhuqMgOTQgZmVhdHVyZXMgdGjDrCBBVUMgdHLDqm4gVGVzdCBEYXRhIGNo4buJIGzDoCAwLjQzNiAtIG3hu5l0IGvhur90IHF14bqjIHLhuqV0IHRo4bqlcC4gDQoNCg0KIyBBdXRvbWF0ZWQgTWFjaGluZSBMZWFybmluZw0KDQpW4bubaSBj4buRIGfhuq9uZyDEkeG6oXQgxJHGsOG7o2Mga+G6v3QgcXXhuqMgY2FvIGjGoW4gbuG7r2EgbeG7mXQgZ2nhuqNpIHBow6FwIMSRxqFuIGdp4bqjbiBjw7MgdGjhu4Mgw6FwIGThu6VuZyBsw6Agc+G7rSBk4bulbmcgY8OhY2ggdGnhur9wIGPhuq1uIEF1dG9tYXRlZCBNYWNoaW5lIExlYXJuaW5nIChjw7MgdGjhu4MgxJHhu41jIHRow6ptIFt04bqhaSDEkcOieV0oaHR0cHM6Ly9ycHVicy5jb20vY2hpZHVuZ2t0LzU4MDAwNSkpLiBExrDhu5tpIMSRw6J5IGzDoCBSIGNvZGVzIHRo4buxYyBoaeG7h24gQXV0b21hdGVkIE1hY2hpbmUgTGVhcm5pbmcgduG7m2kgY2jDuiDDvSBy4bqxbmcg4bufIMSRw6J5IHPhu60gZOG7pW5nIHThuqV0IGPhuqMgOTQgZmVhdHVyZXMgKGRvIGPDsyBuaGnhu4F1IG3DtCBow6xuaCBNTCBjw7Mga2jhuqMgbsSDbmcgdOG7sSDEkeG7mW5nIGzhu7FhIGNo4buNbiBiaeG6v24gdOG7kWkgxrB1KTogDQoNCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFfQ0KIyBMb2FkIGgybyBwYWNrYWdlOiANCg0KbGlicmFyeShoMm8pDQpoMm8uaW5pdChudGhyZWFkcyA9IDIsIG1heF9tZW1fc2l6ZSA9ICI4ZyIpDQpoMm8ubm9fcHJvZ3Jlc3MoKQ0KDQojIFByZXBhcmUgZGF0YTogDQoNCmFzLmgybyh0cmFpbikgLT4gaDJvX2ZyYW1lDQoNCnNwbGl0cyA8LSBoMm8uc3BsaXRGcmFtZShoMm9fZnJhbWUsIHJhdGlvcyA9IG5yb3codGVzdCkgLyBucm93KHRyYWluKSwgc2VlZCA9IDI5KQ0KDQp0cmFpbl9oMm8gPC0gc3BsaXRzW1syXV0gIyBUcmFpbiBkYXRhLiANCg0KdmFsaWRfaDJvIDwtIHNwbGl0c1tbMV1dICMgVmFsaWRhdGlvbiBkYXRhLiANCg0KdGVzdF9oMm8gPC0gYXMuaDJvKHRlc3QpICMgQ29udmVydCB0ZXN0IGRhdGEgdG8gaDJvIGZyYW1lLiANCg0KIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojICBUcmFpbmluZyBBdXRvIE1hY2hpbmUgTGVhcm5pbmcNCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQojIFRyYWluIEF1dG8gTWFjaGluZSBMZWFybmluZzogDQoNCmF1dG9NTCA8LSBoMm8uYXV0b21sKHggPSBwcmVkaWN0b3JzLCANCiAgICAgICAgICAgICAgICAgICAgIHkgPSByZXNwb25zZSwgDQogICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluX2gybywgDQogICAgICAgICAgICAgICAgICAgICBsZWFkZXJib2FyZF9mcmFtZSA9IHZhbGlkX2gybywgDQogICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19tZXRyaWMgPSAiQVVDIiwgDQogICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19yb3VuZHMgPSAxMCwgDQogICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ190b2xlcmFuY2UgPSAwLjAyNSwgDQogICAgICAgICAgICAgICAgICAgICBtYXhfbW9kZWxzID0gMTUsIA0KICAgICAgICAgICAgICAgICAgICAgbWF4X3J1bnRpbWVfc2VjcyA9IDYwKjYwLCANCiAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAxLCANCiAgICAgICAgICAgICAgICAgICAgIHNvcnRfbWV0cmljID0gIkFVQyIpDQpgYGANCg0KTmjGsCB24bqteSBBVUMgdHLDqm4gVmFsaWRhdGlvbiBEYXRhIGNobyBtw7QgaMOsbmggdOG7kXQgbmjhuqV0IGzDoCAwLjkyNzggdHLDqm4gdmFsaWRhdGlvbiBkYXRhIG5oxrAgY2jDum5nIHRhIGPDsyB0aOG7gyB0aOG6pXkg4bufIFRhYmxlIDE6IA0KDQpgYGB7cn0NCmF1dG9NTEBsZWFkZXJib2FyZCAlPiUgDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogIHNlbGVjdChtb2RlbF9pZCwgYXVjKSAlPiUgDQogIG11dGF0ZShSYW5rID0gMTpucm93KC4pLCBhdWMgPSByb3VuZChhdWMsIDQpKSAlPiUgDQogIHJlbmFtZShBVUNfVmFsID0gYXVjKSAtPiBkZl9yZXN1bHRzDQoNCmRmX3Jlc3VsdHMgJT4lIA0KICBrbml0cjo6a2FibGUoY2FwdGlvbiA9ICJUYWJsZSAxOiBBVUMgb24gVmFsaWRhdGlvbiBEYXRhIikNCmBgYA0KDQpDaMO6bmcgdGEgY8OzIHRo4buDIHTDrW5oIGx1w7RuIEFVQyB0xrDGoW5nIOG7qW5nIHbhu5tpIHThuqV0IGPhuqMgY8OhYyBtw7QgaMOsbmggTUwgdHLDqm4gVGVzdCBEYXRhIChUYWJsZSAyKTogDQoNCmBgYHtyfQ0KIyBBVUMgb24gdGVzdCBkYXRhIGJ5IGktdGggbW9kZWw6IA0KDQpnZXRBVUNfb25UZXN0RGF0YSA8LSBmdW5jdGlvbihpKSB7DQogIA0KICAjIEV4dHJhY3QgaS10aCBtb2RlbDogDQogIGgyby5nZXRNb2RlbChhdXRvTUxAbGVhZGVyYm9hcmRbaSwgMV0pIC0+IGJlc3RfaXRoDQogIA0KICAjIE1vZGVsIHBlcmZvcm1hbmNlIGJ5IGl0aCBtb2RlbCBieSBBVUMgb24gVGVzdCBkYXRhOiAgDQogIGgyby5wZXJmb3JtYW5jZShtb2RlbCA9IGJlc3RfaXRoLCBuZXdkYXRhID0gdGVzdF9oMm8pIC0+IG1ldHJpY3NfaXRoDQogIA0KICAjIFJldHVybiBvdXRwdXQ6IA0KICByZXR1cm4oZGF0YS5mcmFtZShBVUNfVGVzdCA9IG1ldHJpY3NfaXRoQG1ldHJpY3MkQVVDLCBtb2RlbF9pZCA9IGJlc3RfaXRoQG1vZGVsX2lkKSkNCiAgDQp9DQoNCiMgQ2FsY3VsYXRlIEFVQyBmb3IgYWxsIG1vZGVsczogDQoNCmxhcHBseSgxOm5yb3coZGZfcmVzdWx0cyksIGdldEFVQ19vblRlc3REYXRhKSAtPiBhdWNfb25fdGVzdERhdGENCmRvLmNhbGwoImJpbmRfcm93cyIsIGF1Y19vbl90ZXN0RGF0YSkgLT4gYXVjX29uX3Rlc3REYXRhDQoNCiMgQVVDIGJ5IGFsbCBtb2RlbHMgb24gdGVzdCBkYXRhOiANCmF1Y19vbl90ZXN0RGF0YSAlPiUgDQogIHNlbGVjdChtb2RlbF9pZCwgQVVDX1Rlc3QpICU+JSANCiAgYXJyYW5nZSgtQVVDX1Rlc3QpICU+JSANCiAgbXV0YXRlKEFVQ19UZXN0ID0gcm91bmQoQVVDX1Rlc3QsIDQpLCBSYW5rID0gMTpucm93KC4pKSAlPiUgDQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gIlRhYmxlIDI6IEFVQyBvbiBUZXN0IERhdGEiKQ0KDQpgYGANCg0KVuG7m2kga+G6v3QgcXXhuqMgQVVDIHRyw6puIFRlc3QgRGF0YSBsw6AgIDAuOTE4MSAoa2hpIHPhu60gZOG7pW5nIG3DtCBow6xuaCBNTCB04buRdCBuaOG6pXQpIHRow6wga+G6v3QgcXXhuqMgbsOgeSBjYW8gaMahbiB0ZWFtIOG7nyB24buLIHRyw60gdGjhu6kgNSAoT2NoaWFpKSB24bubaSBBVUMgPSAwLjkxNjUuIA0KDQojIEtleSBOb3Rlcw0KDQoxLiBT4butIGThu6VuZyBBVUMgbmjGsCBsw6AgbeG7mXQgdGnDqnUgY2h14bqpbiBjaG8gbOG7sWEgY2jhu41uIGJp4bq/biBz4buRIGNobyBtw7QgaMOsbmggTG9naXN0aWMgdsOgIHPhu60gZOG7pW5nIFRlc3QgRGF0YSDEkeG7gyDEkcOhbmggZ2nDoSBs4bqhaSBtw7QgaMOsbmggdGjDrCBBVUMgxJHhuqF0IDAuOTAzLiDEkMOieSBsw6AgbeG7mXQga+G6v3QgcXXhuqMga2jDtG5nIHF1w6EgdOG7hyB2w6AgY8OhY2ggdGjhu6ljIGzhu7FhIGNo4buNbiBiaeG6v24gc+G7kSBuw6B5IGThu4UgdGjhu7FjIGhp4buHbiB2w6Aga2jDtG5nIHF1w6EgbeG6pXQgdGjhu51pIGdpYW4uIFZp4buHYyBz4butIGThu6VuZyBBVUMgbmjGsCBsw6AgbeG7mXQgdGnDqnUgY2h14bqpbiBs4buxYSBjaOG7jW4gYmnhur9uIHPhu5EgY8WpbmcgY8OzIG5oaeG7gXUgcGFwZXIgxJHhu4EgY+G6rXAgYuG6oW4gxJHhu41jIGPDsyB0aOG7gyB04buxIHTDrG0gaGnhu4N1IHRow6ptLiANCg0KMi4gQ8OhY2ggdGnhur9wIGPhuq1uIEF1dG9tYXRlZCBNYWNoaW5lIExlYXJuaW5nIGPDsyB0aOG7gyDEkcaw4bujYyBz4butIGThu6VuZyDEkeG7gyBuw6JuZyBjYW8gaMahbiBu4buvYSBraOG6oyBuxINuZyBk4buxIGLDoW8gdsOgIHBow6JuIGxv4bqhaSBwaMOhIHPhuqNuIGPhu6dhIGRvYW5oIG5naGnhu4dwLiBL4bq/dCBxdeG6oyB0aOG7sWMgbmdoaeG7h20gY2jhu4kgcmEgcuG6sW5nIG7hur91IHPhu60gZOG7pW5nIGPDoWNoIHRp4bq/cCBj4bqtbiBuw6B5IHRow6wgQVVDIHTEg25nIChzbyB24bubaSBtw7QgaMOsbmggTG9naXN0aWMgdOG7kXQgbmjhuqV0KSB2w6AgxJHhuqF0IDAuOTE4MSB0csOqbiBUZXN0IERhdGEuIA0KDQozLiBDaMO6bmcgdGEgY8OzIHRo4buDIMSRaSB4YSBoxqFuIGLhurFuZyBjw6FjaCBz4butIGThu6VuZyBBdXRvbWF0ZWQgTWFjaGluZSBMZWFybmluZyB24bubaSBkYW5oIHPDoWNoIDE4IGJp4bq/biBz4buRIMSRw6MgxJHGsOG7o2Mgc+G7rSBk4bulbmcgY2hvIG3DtCBow6xuaCBMb2dpc3RpYyB04buRaSDGsHUgbmjGsCBzYXU6IA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCg0KYXV0b01MMDkwMyA8LSBoMm8uYXV0b21sKHggPSB2YXJfYXVjXzA5MDMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSByZXNwb25zZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbl9oMm8sIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGxlYWRlcmJvYXJkX2ZyYW1lID0gdmFsaWRfaDJvLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19tZXRyaWMgPSAiQVVDIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gMTAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3RvbGVyYW5jZSA9IDAuMDI1LCANCiAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfbW9kZWxzID0gMTUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9ydW50aW1lX3NlY3MgPSA2MCo2MCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNvcnRfbWV0cmljID0gIkFVQyIpDQoNCg0KYGBgDQoNCjQuIMSQw6J5IGzDoCBk4buvIGxp4buHdSBi4bqldCBjw6JuIGLhurFuZyBy4bqldCBjYW8gKGNo4buJIGPDsyAzLjIlIGPDoWMgcXVhbiBzw6F0IGzDoCBCYW5rcnVwdCkgbsOqbiBjw7MgdGjhu4MgY+G6p24geGVtIHjDqXQgxJHhur9uIGto4bqjIG7Eg25nIHPhu60gZOG7pW5nIGPDoWMgZ2nhuqNpIHBow6FwIHJlc2FtcGxpbmcgZOG7ryBsaeG7h3UgbmjGsCBTTU9URSwgdXBzYW1wbGluZyAtIGRvd25zYW1wbGluZy4gDQoNCiMgUmVmZXJlbmNlcw0KDQoxLiBbQ29ycG9yYXRlIEJhbmtydXB0Y3kgUHJlZGljdGlvbjogSW50ZXJuYXRpb25hbCBUcmVuZHMgYW5kIExvY2FsIEV4cGVyaWVuY2VdKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vdXJsP3NhPWkmdXJsPWh0dHBzJTNBJTJGJTJGd3d3Lm1kcGkuY29tJTJGYm9va3MlMkZwZGZkb3dubG9hZCUyRmJvb2slMkYyMzczJnBzaWc9QU92VmF3MlRLdU1EQnFpc0hHOG91aVR0bDVvcCZ1c3Q9MTY0NTE1MzYxMjI0MjAwMCZzb3VyY2U9aW1hZ2VzJmNkPXZmZSZ2ZWQ9MmFoVUtFd2piLU1UMjRJWDJBaFVUSTZZS0haUU9ENTRRamh4NkJBZ0FFQW8pLiANCg0KMi4gW0FkdmFuY2VzIGluIENyZWRpdCBSaXNrIE1vZGVsbGluZyBhbmQgQ29ycG9yYXRlIEJhbmtydXB0Y3kgUHJlZGljdGlvbl0oaHR0cHM6Ly93d3cuY2FtYnJpZGdlLm9yZy9jb3JlL2Jvb2tzL2FkdmFuY2VzLWluLWNyZWRpdC1yaXNrLW1vZGVsbGluZy1hbmQtY29ycG9yYXRlLWJhbmtydXB0Y3ktcHJlZGljdGlvbi9BRTdERUE5ODg0MjM4Nzk1RjQyQzc0NEIwQzZBQzM2OCkuIA0KDQozLiBbTGlhbmcsIEQuLCBMdSwgQy4tQy4sIFRzYWksIEMuLUYuLCBhbmQgU2hpaCwgRy4tQS4gKDIwMTYpIEZpbmFuY2lhbCBSYXRpb3MgYW5kIENvcnBvcmF0ZSBHb3Zlcm5hbmNlIEluZGljYXRvcnMgaW4gQmFua3J1cHRjeSBQcmVkaWN0aW9uOiBBIENvbXByZWhlbnNpdmUgU3R1ZHkuIEV1cm9wZWFuIEpvdXJuYWwgb2YgT3BlcmF0aW9uYWwgUmVzZWFyY2gsIHZvbC4gMjUyLCBuby4gMiwgcHAuIDU2MS01NzIuXShodHRwczovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvYWJzL3BpaS9TMDM3NzIyMTcxNjAwMDQxMj92aWElM0RpaHViKS4NCg0KNC4gW1ppZWJhLCBNLiwgVG9tY3phaywgUy4gSy4sICYgVG9tY3phaywgSi4gTS4gKDIwMTYpLiBFbnNlbWJsZSBCb29zdGVkIFRyZWVzIHdpdGggU3ludGhldGljIEZlYXR1cmVzIEdlbmVyYXRpb24gaW4gQXBwbGljYXRpb24gdG8gQmFua3J1cHRjeSBQcmVkaWN0aW9uLiBFeHBlcnQgU3lzdGVtcyB3aXRoIEFwcGxpY2F0aW9ucywgNTg6OTPigJMxMDEuXShodHRwczovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvYWJzL3BpaS9TMDk1NzQxNzQxNjMwMTU5Mj92aWElM0RpaHViKQ0KDQojIFIgRW52aXJvbm1lbnQgYW5kIE9TDQoNCg0KYGBge3J9DQpzZXNzaW9uSW5mbygpDQpgYGANCg0KDQoNCg0KDQoNCg==