Introduction

Bộ số liệu German Credit đã được sử dụng trong nhiều reseach paper. Một số kết quả thực nghiệm với bộ số liệu này được nhắc lại trong nghiên cứu có tên Investigation and improvement of multi-layer perceptron neural networks for credit scoring như sau:

Với các thuật toán Machine Learning thì khối lượng tính toán là rất lớn và các thuật toán có thể cần hàng giờ đến hàng chục giờ để chạy. Con số này có thể lớn hơn rất nhiều nếu bộ số liệu có kích thước lớn và nhiều biến số đầu vào. Vì lí do này, lựa chọn các biến đầu vào phù hợp hoặc sử dụng các kĩ thuật cho Feature Engineering có thể hướng đến đồng thời cả hai mục đích: (1) giảm số lượng biến đầu vào cần thiết để xây dựng mô hình và dẫn đến giảm thời gian tính toán, và (2) đồng thời cải thiện chất lượng dự báo của mô hình hoặc đảm bảo rằng chất lượng dự báo của mô hình giảm không đáng kể trong khi số lượng biến sử dụng lại giảm đi rất nhiều.

Với bộ số liệu German Credit ở trên kết quả chỉ ra rằng:

  1. Mức độ chính xác trung bình cho 10 lần thử nghiệm bằng Cross - Validation là 79% (một kết quả thuộc loại khá nếu chúng ta so sánh với các kết quả ở các nghiên cứu khác) bằng cách sử dụng Random Forest (RF) không tinh chỉnh gì. Kết quả này có thể cải thiện hơn nữa nếu chúng ta tinh chỉnh các tham số cho RF.

  2. Autoencoder có thể được sử dụng hiệu quả như là một phương pháp cho Feature Engineering.

Default Random Forest

R codes dưới đây thực hiện huấn luyện RF không tinh chỉnh đồng thời khảo sát kết quả thu được trên 10 mẫu bằng kĩ thuật Cross-Validation:

##  Connection successful!
## 
## R is connected to the H2O cluster: 
##     H2O cluster uptime:         27 minutes 58 seconds 
##     H2O cluster timezone:       Asia/Bangkok 
##     H2O data parsing timezone:  UTC 
##     H2O cluster version:        3.20.0.2 
##     H2O cluster version age:    5 months and 26 days !!! 
##     H2O cluster name:           H2O_started_from_R_Zbook_waz726 
##     H2O cluster total nodes:    1 
##     H2O cluster total memory:   13.84 GB 
##     H2O cluster total cores:    8 
##     H2O cluster allowed cores:  8 
##     H2O cluster healthy:        TRUE 
##     H2O Connection ip:          localhost 
##     H2O Connection port:        54321 
##     H2O Connection proxy:       NA 
##     H2O Internal Security:      FALSE 
##     H2O API Extensions:         Algos, AutoML, Core V3, Core V4 
##     R Version:                  R version 3.5.1 (2018-07-02)
##     Accuracy           AUC           Precision       Specificity    
##  Min.   :0.6857   Min.   :0.7067   Min.   :0.6667   Min.   :0.1538  
##  1st Qu.:0.7605   1st Qu.:0.7664   1st Qu.:0.7794   1st Qu.:0.3750  
##  Median :0.7887   Median :0.7805   Median :0.8038   Median :0.4603  
##  Mean   :0.7896   Mean   :0.7963   Mean   :0.8008   Mean   :0.4546  
##  3rd Qu.:0.8194   3rd Qu.:0.8404   3rd Qu.:0.8468   3rd Qu.:0.5395  
##  Max.   :0.8659   Max.   :0.8645   Max.   :0.8750   Max.   :0.6857  
##      Recall          Logloss      
##  Min.   :0.7843   Min.   :0.4048  
##  1st Qu.:0.9216   1st Qu.:0.4304  
##  Median :0.9382   Median :0.5425  
##  Mean   :0.9324   Mean   :0.6132  
##  3rd Qu.:0.9673   3rd Qu.:0.7855  
##  Max.   :1.0000   Max.   :0.9896

Nếu chọn Accuracy làm tiêu chí đánh giá thì mô hình RF không tinh chỉnh như trên có trung bình Accuracy là xấp xỉ 79%.

Autoencoder as a Feature Engineering Technique

Sử dụng Autoencoder cho mục đích Feature Engineering với bộ số liệu German Credit cho kết quả như sau:

# Buil a autoencoder: 

autoencoder <- h2o.deeplearning(x = x,
                                training_frame = train, 
                                autoencoder = TRUE, 
                                seed = 29, 
                                hidden = c(10, 20, 61), 
                                epochs = 30, 
                                activation = "Tanh")

#============================================================
#  Use Autoencoder as Feature Engineering Method (Version 1)
#============================================================


train_autoen <- h2o.predict(autoencoder, train) %>% 
  as.data.frame() %>% 
  mutate(Class = df_train$Class) %>% 
  as.h2o()

test_autoen <- h2o.predict(autoencoder, test) %>% 
  as.data.frame() %>% 
  mutate(Class = df_test$Class) %>% 
  as.h2o()


nn_autoen_layers1 <- h2o.randomForest(x = setdiff(colnames(train_autoen), "Class"), 
                                      y = y, 
                                      training_frame = train_autoen,
                                      nfolds = 10, 
                                      stopping_rounds = 5, 
                                      stopping_metric = "AUC", 
                                      seed = 29)



#============================================================
#  Use Autoencoder as Feature Engineering Method (Version 2)
#============================================================

train_features_l2 <- h2o.deepfeatures(autoencoder, train, layer = 2) %>%
  as.data.frame() %>%
  mutate(Class = df_train$Class) %>% 
  as.h2o()


test_features_l2 <- h2o.deepfeatures(autoencoder, test, layer = 2) %>%
  as.data.frame() %>%
  mutate(Class = df_test$Class) %>% 
  as.h2o()   


nn_autoen_layers2 <- h2o.randomForest(x = setdiff(colnames(train_features_l2), "Class"), 
                                      y = y, 
                                      training_frame = train_features_l2,
                                      nfolds = 10, 
                                      stopping_rounds = 5, 
                                      stopping_metric = "AUC", 
                                      seed = 29)


#============================================================
#  Use Autoencoder as Feature Engineering Method (Version 3)
#============================================================

train_features_l3 <- h2o.deepfeatures(autoencoder, train, layer = 3) %>%
  as.data.frame() %>%
  mutate(Class = df_train$Class) %>% 
  as.h2o()


test_features_l3 <- h2o.deepfeatures(autoencoder, test, layer = 3) %>%
  as.data.frame() %>%
  mutate(Class = df_test$Class) %>% 
  as.h2o()


nn_autoen_layers3 <- h2o.randomForest(x = setdiff(colnames(train_features_l3), "Class"), 
                                      y = y, 
                                      training_frame = train_features_l3,
                                      nfolds = 10, 
                                      stopping_rounds = 5, 
                                      stopping_metric = "AUC", 
                                      seed = 29)

#==========================
#  Compare between models
#==========================

do.call("bind_rows", 
        lapply(list(pure_nn, 
                    nn_autoen_layers1, 
                    nn_autoen_layers2, 
                    nn_autoen_layers3), results_df)) -> df_com


df_com %<>% mutate(Model = rep(c("Original", "Layer1", "20Var", "61Var"), each = 10, time = 1))

theme_set(theme_minimal())
df_com %>% 
  gather(a, b, -Model) %>% 
  ggplot(aes(Model, b, fill = Model, color = Model)) + 
  geom_boxplot(alpha = 0.3) + 
  scale_y_continuous(labels = scales::percent) + 
  facet_wrap(~ a, scales = "free") + 
  labs(x = NULL, y = NULL, title = "Model Performance")

Model Accuracy AUC Logloss Precision Recall Specificity
Original 0.790 0.796 0.613 0.801 0.932 0.455
Layer1 0.756 0.737 0.879 0.758 0.964 0.278
61Var 0.743 0.732 0.621 0.751 0.950 0.236
20Var 0.726 0.696 0.837 0.724 0.987 0.124

Kết quả này chỉ ra rằng: Autoencoder có thể được sử dụng hiệu quả như là một kĩ thuật cho Feature Engineering.

Optimal Threshold

RF sử dụng toàn bộ 61 biến số nguyên bản là mô hình cho Accuracy cao nhất. Chúng ta có thể khảo sát sự biến đổi các tiêu chí đánh giá chất lượng mô hình phân loại khi ngưỡng được lựa chọn dán nhãn (Bad hay Good) thay đổi như sau:

##    user  system elapsed 
##   94.45    3.05  138.31

Chúng ta có thể thấy rõ sự đánh đổi ở đây: khẳ năng phân loại cho nhãn Bad càng cao (Sensitivity) càng cao thì khả năng phân loại cho nhãn Good (Specificity) càng thấp. Điều này dẫn đến Accuracy có dạng hình chữ U ngược với mức cực đại đạt được khi ngưỡng được chọn là 0.7.

Chúng ta có thể hình ảnh hóa khả năng phân loại của mô hình cho hai loại hồ sơ như sau:

LS0tDQp0aXRsZTogIlVzZSBBdXRvZW5jb2RlciBhcyBhIEZlYXR1cmUgRW5naW5lZXJpbmcgVGVjaG5pcXVlIiANCnN1YnRpdGxlOiAiRm9yIEtpbGxpbmcgVGltZSBhdCB0aGUgSG9zcGl0YWwiDQphdXRob3I6ICJOZ3V5ZW4gQ2hpIER1bmciDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICAjIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICAjIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6ICJmbGF0bHkiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24NCg0KQuG7mSBz4buRIGxp4buHdSAqKkdlcm1hbiBDcmVkaXQqKiDEkcOjIMSRxrDhu6NjIHPhu60gZOG7pW5nIHRyb25nIG5oaeG7gXUgcmVzZWFjaCBwYXBlci4gTeG7mXQgc+G7kSBr4bq/dCBxdeG6oyB0aOG7sWMgbmdoaeG7h20gduG7m2kgYuG7mSBz4buRIGxp4buHdSBuw6B5IMSRxrDhu6NjIG5o4bqvYyBs4bqhaSB0cm9uZyBuZ2hpw6puIGPhu6l1IGPDsyB0w6puIFtJbnZlc3RpZ2F0aW9uIGFuZCBpbXByb3ZlbWVudCBvZiBtdWx0aS1sYXllciBwZXJjZXB0cm9uIG5ldXJhbCBuZXR3b3JrcyBmb3IgY3JlZGl0IHNjb3JpbmddKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzA5NTc0MTc0MTQwMDc3MjYpIG5oxrAgc2F1OiANCg0KIVtdKEM6L1VzZXJzL1pib29rL0RvY3VtZW50cy9yZXN1bHRfZ2VybWFuLnBuZykNCg0KVuG7m2kgY8OhYyB0aHXhuq10IHRvw6FuIE1hY2hpbmUgTGVhcm5pbmcgdGjDrCBraOG7kWkgbMaw4bujbmcgdMOtbmggdG/DoW4gbMOgIHLhuqV0IGzhu5tuIHbDoCBjw6FjIHRodeG6rXQgdG/DoW4gY8OzIHRo4buDIGPhuqduIGjDoG5nIGdp4budIMSR4bq/biBow6BuZyBjaOG7pWMgZ2nhu50gxJHhu4MgY2jhuqF5LiBDb24gc+G7kSBuw6B5IGPDsyB0aOG7gyBs4bubbiBoxqFuIHLhuqV0IG5oaeG7gXUgbuG6v3UgYuG7mSBz4buRIGxp4buHdSBjw7Mga8OtY2ggdGjGsOG7m2MgbOG7m24gdsOgIG5oaeG7gXUgYmnhur9uIHPhu5EgxJHhuqd1IHbDoG8uIFbDrCBsw60gZG8gbsOgeSwgbOG7sWEgY2jhu41uIGPDoWMgYmnhur9uIMSR4bqndSB2w6BvIHBow7kgaOG7o3AgaG/hurdjIHPhu60gZOG7pW5nIGPDoWMga8SpIHRodeG6rXQgY2hvIEZlYXR1cmUgRW5naW5lZXJpbmcgY8OzIHRo4buDIGjGsOG7m25nIMSR4bq/biDEkeG7k25nIHRo4budaSBj4bqjIGhhaSBt4bulYyDEkcOtY2g6ICgxKSBnaeG6o20gc+G7kSBsxrDhu6NuZyBiaeG6v24gxJHhuqd1IHbDoG8gY+G6p24gdGhp4bq/dCDEkeG7gyB4w6J5IGThu7FuZyBtw7QgaMOsbmggdsOgIGThuqtuIMSR4bq/biBnaeG6o20gdGjhu51pIGdpYW4gdMOtbmggdG/DoW4sIHbDoCAoMikgxJHhu5NuZyB0aOG7nWkgY+G6o2kgdGhp4buHbiBjaOG6pXQgbMaw4bujbmcgZOG7sSBiw6FvIGPhu6dhIG3DtCBow6xuaCBob+G6t2MgxJHhuqNtIGLhuqNvIHLhurFuZyBjaOG6pXQgbMaw4bujbmcgZOG7sSBiw6FvIGPhu6dhIG3DtCBow6xuaCBnaeG6o20ga2jDtG5nIMSRw6FuZyBr4buDIHRyb25nIGtoaSBz4buRIGzGsOG7o25nIGJp4bq/biBz4butIGThu6VuZyBs4bqhaSBnaeG6o20gxJFpIHLhuqV0IG5oaeG7gXUuIA0KDQpW4bubaSBi4buZIHPhu5EgbGnhu4d1IEdlcm1hbiBDcmVkaXQg4bufIHRyw6puIGvhur90IHF14bqjIGNo4buJIHJhIHLhurFuZzogDQoNCjEuIE3hu6ljIMSR4buZIGNow61uaCB4w6FjIHRydW5nIGLDrG5oIGNobyAxMCBs4bqnbiB0aOG7rSBuZ2hp4buHbSBi4bqxbmcgQ3Jvc3MgLSBWYWxpZGF0aW9uIGzDoCA3OSUgKG3hu5l0IGvhur90IHF14bqjIHRodeG7mWMgbG/huqFpIGtow6EgbuG6v3UgY2jDum5nIHRhIHNvIHPDoW5oIHbhu5tpIGPDoWMga+G6v3QgcXXhuqMg4bufIGPDoWMgbmdoacOqbiBj4bupdSBraMOhYykgYuG6sW5nIGPDoWNoIHPhu60gZOG7pW5nIFJhbmRvbSBGb3Jlc3QgKFJGKSBraMO0bmcgdGluaCBjaOG7iW5oIGfDrC4gS+G6v3QgcXXhuqMgbsOgeSBjw7MgdGjhu4MgY+G6o2kgdGhp4buHbiBoxqFuIG7hu69hIG7hur91IGNow7puZyB0YSB0aW5oIGNo4buJbmggY8OhYyB0aGFtIHPhu5EgY2hvIFJGLiANCg0KDQoyLiBBdXRvZW5jb2RlciBjw7MgdGjhu4MgxJHGsOG7o2Mgc+G7rSBk4bulbmcgaGnhu4d1IHF14bqjIG5oxrAgbMOgIG3hu5l0IHBoxrDGoW5nIHBow6FwIGNobyBGZWF0dXJlIEVuZ2luZWVyaW5nLiANCg0KIyBEZWZhdWx0IFJhbmRvbSBGb3Jlc3QNCg0KUiBjb2RlcyBkxrDhu5tpIMSRw6J5IHRo4buxYyBoaeG7h24gaHXhuqVuIGx1eeG7h24gUkYga2jDtG5nIHRpbmggY2jhu4luaCDEkeG7k25nIHRo4budaSBraOG6o28gc8OhdCBr4bq/dCBxdeG6oyB0aHUgxJHGsOG7o2MgdHLDqm4gMTAgbeG6q3UgYuG6sW5nIGvEqSB0aHXhuq10IENyb3NzLVZhbGlkYXRpb246IA0KDQoNCmBgYHtyLCBmaWcuZnVsbHdpZHRoID0gVFJVRSwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTJ9DQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojICBTdGFnZSAxOiBEYXRhIFByZS1wcm9jZXNzaW5nDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCiMgQ2xlYXIgd29ya3NwYWNlOiANCnJtKGxpc3QgPSBscygpKQ0KDQojIExvYWQgcGFja2FnZXMgYW5kIGRhdGE6IA0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KbGlicmFyeShjYXJldCkNCmRhdGEoIkdlcm1hbkNyZWRpdCIpDQoNCiMgU3BsaXQgZGF0YTogDQpzZXQuc2VlZCgxKQ0KaWQgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5ID0gR2VybWFuQ3JlZGl0JENsYXNzLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQpkZl90cmFpbiA8LSBHZXJtYW5DcmVkaXRbaWQsIF0gIyBGb3IgdHJhaW5pbmcNCmRmX3Rlc3QgPC0gR2VybWFuQ3JlZGl0Wy1pZCwgXSAjIEZvciB0ZXN0aW5nDQoNCg0KIyBBY3RpdmF0ZSBoMm8gcGFja2FnZSBmb3IgZGVlcCBsZWFybmluZzogDQpsaWJyYXJ5KGgybykNCmgyby5pbml0KG50aHJlYWRzID0gLTEsIG1heF9tZW1fc2l6ZSA9ICIxNmciKQ0KaDJvLm5vX3Byb2dyZXNzKCkNCg0KIyBDb252ZXJ0IHRvIEgybyBGcmFtZToNCnRyYWluIDwtIGFzLmgybyhkZl90cmFpbikNCnRlc3QgPC0gYXMuaDJvKGRmX3Rlc3QpDQoNCiMgSWRlbnRpZnkgaW5wdXRwdXQgYW5kIG91dHB1dDogDQp5IDwtICJDbGFzcyINCnggPC0gc2V0ZGlmZihuYW1lcyh0cmFpbiksIHkpDQoNCiMgVHJhaW4gRGVmYXVsdCBSYW5kb20gRm9yZXN0OiANCnB1cmVfbm4gPC0gaDJvLnJhbmRvbUZvcmVzdCh4ID0geCwgeSA9IHksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmZvbGRzID0gMTAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX21ldHJpYyA9ICJBVUMiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMjkpDQoNCg0KIyBGdW5jdGlvbiBjb2xsZWN0cyByZXN1bHRzIGZyb20gY3Jvc3MtdmFsaWRhdGlvbjogDQoNCnJlc3VsdHNfZGYgPC0gZnVuY3Rpb24oaDJvX21vZGVsKSB7DQogIGgyb19tb2RlbEBtb2RlbCRjcm9zc192YWxpZGF0aW9uX21ldHJpY3Nfc3VtbWFyeSAlPiUgDQogICAgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgICBzZWxlY3QoLW1lYW4sIC1zZCkgJT4lIA0KICAgIHQoKSAlPiUgDQogICAgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgICBtdXRhdGVfYWxsKGFzLmNoYXJhY3RlcikgJT4lIA0KICAgIG11dGF0ZV9hbGwoYXMubnVtZXJpYykgJT4lIA0KICAgIHNlbGVjdChBY2N1cmFjeSA9IGFjY3VyYWN5LCANCiAgICAgICAgICAgQVVDID0gYXVjLCANCiAgICAgICAgICAgUHJlY2lzaW9uID0gcHJlY2lzaW9uLCANCiAgICAgICAgICAgU3BlY2lmaWNpdHkgPSBzcGVjaWZpY2l0eSwgDQogICAgICAgICAgIFJlY2FsbCA9IHJlY2FsbCwgDQogICAgICAgICAgIExvZ2xvc3MgPSBsb2dsb3NzKSAlPiUgDQogICAgcmV0dXJuKCkNCiAgfQ0KDQoNCiMgRnVuY3Rpb24gcHJlc2VudHMgcmVzdWx0cyBieSBncmFwaDogDQoNCnZpc19yZXN1bHRzIDwtIGZ1bmN0aW9uKGRmX3Jlc3VsdHMpIHsNCiAgZGZfcmVzdWx0cyAlPiUgDQogICAgZ2F0aGVyKE1ldHJpY3MsIFZhbHVlcykgJT4lIA0KICAgIGdncGxvdChhZXMoTWV0cmljcywgVmFsdWVzLCBmaWxsID0gTWV0cmljcywgY29sb3IgPSBNZXRyaWNzKSkgKw0KICAgIGdlb21fYm94cGxvdChhbHBoYSA9IDAuMywgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyANCiAgICBmYWNldF93cmFwKH4gTWV0cmljcywgc2NhbGVzID0gImZyZWUiKSArIA0KICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgDQogICAgdGhlbWVfbWluaW1hbCgpICsgDQogICAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwsIA0KICAgICAgICAgdGl0bGUgPSAiTW9kZWwgUGVyZm9ybWFuY2UgQmFzZWQgb24gQ3Jvc3MgVmFsaWRhdGlvbiIpDQp9DQoNCg0KcHVyZV9ubiAlPiUgcmVzdWx0c19kZigpICU+JSBzdW1tYXJ5KCkNCnB1cmVfbm4gJT4lIHJlc3VsdHNfZGYoKSAlPiUgdmlzX3Jlc3VsdHMoKQ0KDQpgYGANCg0KTuG6v3UgY2jhu41uIEFjY3VyYWN5IGzDoG0gdGnDqnUgY2jDrSDEkcOhbmggZ2nDoSB0aMOsIG3DtCBow6xuaCBSRiBraMO0bmcgdGluaCBjaOG7iW5oIG5oxrAgdHLDqm4gY8OzIHRydW5nIGLDrG5oIEFjY3VyYWN5IGzDoCB44bqlcCB44buJIDc5JS4gDQoNCg0KIyBBdXRvZW5jb2RlciBhcyBhIEZlYXR1cmUgRW5naW5lZXJpbmcgVGVjaG5pcXVlDQoNClPhu60gZOG7pW5nIEF1dG9lbmNvZGVyIGNobyBt4bulYyDEkcOtY2ggRmVhdHVyZSBFbmdpbmVlcmluZyB24bubaSBi4buZIHPhu5EgbGnhu4d1IEdlcm1hbiBDcmVkaXQgY2hvIGvhur90IHF14bqjIG5oxrAgc2F1OiANCg0KYGBge3IsIGZpZy5mdWxsd2lkdGggPSBUUlVFLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMn0NCg0KIyBCdWlsIGEgYXV0b2VuY29kZXI6IA0KDQphdXRvZW5jb2RlciA8LSBoMm8uZGVlcGxlYXJuaW5nKHggPSB4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXV0b2VuY29kZXIgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDI5LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuID0gYygxMCwgMjAsIDYxKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVwb2NocyA9IDMwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJUYW5oIikNCg0KIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyAgVXNlIEF1dG9lbmNvZGVyIGFzIEZlYXR1cmUgRW5naW5lZXJpbmcgTWV0aG9kIChWZXJzaW9uIDEpDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCg0KdHJhaW5fYXV0b2VuIDwtIGgyby5wcmVkaWN0KGF1dG9lbmNvZGVyLCB0cmFpbikgJT4lIA0KICBhcy5kYXRhLmZyYW1lKCkgJT4lIA0KICBtdXRhdGUoQ2xhc3MgPSBkZl90cmFpbiRDbGFzcykgJT4lIA0KICBhcy5oMm8oKQ0KDQp0ZXN0X2F1dG9lbiA8LSBoMm8ucHJlZGljdChhdXRvZW5jb2RlciwgdGVzdCkgJT4lIA0KICBhcy5kYXRhLmZyYW1lKCkgJT4lIA0KICBtdXRhdGUoQ2xhc3MgPSBkZl90ZXN0JENsYXNzKSAlPiUgDQogIGFzLmgybygpDQoNCg0Kbm5fYXV0b2VuX2xheWVyczEgPC0gaDJvLnJhbmRvbUZvcmVzdCh4ID0gc2V0ZGlmZihjb2xuYW1lcyh0cmFpbl9hdXRvZW4pLCAiQ2xhc3MiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSB5LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbl9hdXRvZW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IDEwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gNSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX21ldHJpYyA9ICJBVUMiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDI5KQ0KDQoNCg0KIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyAgVXNlIEF1dG9lbmNvZGVyIGFzIEZlYXR1cmUgRW5naW5lZXJpbmcgTWV0aG9kIChWZXJzaW9uIDIpDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCnRyYWluX2ZlYXR1cmVzX2wyIDwtIGgyby5kZWVwZmVhdHVyZXMoYXV0b2VuY29kZXIsIHRyYWluLCBsYXllciA9IDIpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIG11dGF0ZShDbGFzcyA9IGRmX3RyYWluJENsYXNzKSAlPiUgDQogIGFzLmgybygpDQoNCg0KdGVzdF9mZWF0dXJlc19sMiA8LSBoMm8uZGVlcGZlYXR1cmVzKGF1dG9lbmNvZGVyLCB0ZXN0LCBsYXllciA9IDIpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIG11dGF0ZShDbGFzcyA9IGRmX3Rlc3QkQ2xhc3MpICU+JSANCiAgYXMuaDJvKCkgICANCg0KDQpubl9hdXRvZW5fbGF5ZXJzMiA8LSBoMm8ucmFuZG9tRm9yZXN0KHggPSBzZXRkaWZmKGNvbG5hbWVzKHRyYWluX2ZlYXR1cmVzX2wyKSwgIkNsYXNzIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0geSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5fZmVhdHVyZXNfbDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IDEwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gNSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX21ldHJpYyA9ICJBVUMiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDI5KQ0KDQoNCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgIFVzZSBBdXRvZW5jb2RlciBhcyBGZWF0dXJlIEVuZ2luZWVyaW5nIE1ldGhvZCAoVmVyc2lvbiAzKQ0KIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQp0cmFpbl9mZWF0dXJlc19sMyA8LSBoMm8uZGVlcGZlYXR1cmVzKGF1dG9lbmNvZGVyLCB0cmFpbiwgbGF5ZXIgPSAzKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBtdXRhdGUoQ2xhc3MgPSBkZl90cmFpbiRDbGFzcykgJT4lIA0KICBhcy5oMm8oKQ0KDQoNCnRlc3RfZmVhdHVyZXNfbDMgPC0gaDJvLmRlZXBmZWF0dXJlcyhhdXRvZW5jb2RlciwgdGVzdCwgbGF5ZXIgPSAzKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBtdXRhdGUoQ2xhc3MgPSBkZl90ZXN0JENsYXNzKSAlPiUgDQogIGFzLmgybygpDQoNCg0Kbm5fYXV0b2VuX2xheWVyczMgPC0gaDJvLnJhbmRvbUZvcmVzdCh4ID0gc2V0ZGlmZihjb2xuYW1lcyh0cmFpbl9mZWF0dXJlc19sMyksICJDbGFzcyIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluX2ZlYXR1cmVzX2wzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZm9sZHMgPSAxMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19tZXRyaWMgPSAiQVVDIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAyOSkNCg0KIz09PT09PT09PT09PT09PT09PT09PT09PT09DQojICBDb21wYXJlIGJldHdlZW4gbW9kZWxzDQojPT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KZG8uY2FsbCgiYmluZF9yb3dzIiwgDQogICAgICAgIGxhcHBseShsaXN0KHB1cmVfbm4sIA0KICAgICAgICAgICAgICAgICAgICBubl9hdXRvZW5fbGF5ZXJzMSwgDQogICAgICAgICAgICAgICAgICAgIG5uX2F1dG9lbl9sYXllcnMyLCANCiAgICAgICAgICAgICAgICAgICAgbm5fYXV0b2VuX2xheWVyczMpLCByZXN1bHRzX2RmKSkgLT4gZGZfY29tDQoNCg0KZGZfY29tICU8PiUgbXV0YXRlKE1vZGVsID0gcmVwKGMoIk9yaWdpbmFsIiwgIkxheWVyMSIsICIyMFZhciIsICI2MVZhciIpLCBlYWNoID0gMTAsIHRpbWUgPSAxKSkNCg0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkNCmRmX2NvbSAlPiUgDQogIGdhdGhlcihhLCBiLCAtTW9kZWwpICU+JSANCiAgZ2dwbG90KGFlcyhNb2RlbCwgYiwgZmlsbCA9IE1vZGVsLCBjb2xvciA9IE1vZGVsKSkgKyANCiAgZ2VvbV9ib3hwbG90KGFscGhhID0gMC4zKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArIA0KICBmYWNldF93cmFwKH4gYSwgc2NhbGVzID0gImZyZWUiKSArIA0KICBsYWJzKHggPSBOVUxMLCB5ID0gTlVMTCwgdGl0bGUgPSAiTW9kZWwgUGVyZm9ybWFuY2UiKQ0KDQoNCmRmX2NvbSAlPiUgDQogIGdyb3VwX2J5KE1vZGVsKSAlPiUgDQogIHN1bW1hcmlzZV9lYWNoKGZ1bnMobWVhbiksIEFjY3VyYWN5LCBBVUMsIExvZ2xvc3MsIFByZWNpc2lvbiwgUmVjYWxsLCBTcGVjaWZpY2l0eSkgJT4lIA0KICBhcnJhbmdlKC1BY2N1cmFjeSkgJT4lIA0KICBtdXRhdGVfaWYoaXMubnVtZXJpYywgZnVuY3Rpb24oeCkge3JvdW5kKHgsIDMpfSkgJT4lIA0KICBrbml0cjo6a2FibGUoKQ0KDQpgYGANCg0KS+G6v3QgcXXhuqMgbsOgeSBjaOG7iSByYSBy4bqxbmc6IEF1dG9lbmNvZGVyIGPDsyB0aOG7gyDEkcaw4bujYyBz4butIGThu6VuZyBoaeG7h3UgcXXhuqMgbmjGsCBsw6AgbeG7mXQga8SpIHRodeG6rXQgY2hvIEZlYXR1cmUgRW5naW5lZXJpbmcuIA0KDQojIE9wdGltYWwgVGhyZXNob2xkDQoNClJGIHPhu60gZOG7pW5nIHRvw6BuIGLhu5kgNjEgYmnhur9uIHPhu5Egbmd1ecOqbiBi4bqjbiBsw6AgbcO0IGjDrG5oIGNobyBBY2N1cmFjeSBjYW8gbmjhuqV0LiBDaMO6bmcgdGEgY8OzIHRo4buDIGto4bqjbyBzw6F0IHPhu7EgYmnhur9uIMSR4buVaSBjw6FjIHRpw6p1IGNow60gxJHDoW5oIGdpw6EgY2jhuqV0IGzGsOG7o25nIG3DtCBow6xuaCBwaMOibiBsb+G6oWkga2hpIG5nxrDhu6FuZyDEkcaw4bujYyBs4buxYSBjaOG7jW4gZMOhbiBuaMOjbiAoQmFkIGhheSBHb29kKSB0aGF5IMSR4buVaSBuaMawIHNhdTogDQoNCmBgYHtyLCBmaWcuZnVsbHdpZHRoID0gVFJVRSwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTJ9DQpldmFsX2Z1biA8LSBmdW5jdGlvbih0aHJlKSB7DQogIGxhcHBseSgxOjEwLCBmdW5jdGlvbih4KSB7DQogICAgDQogICAgc2V0LnNlZWQoeCkNCiAgICBpZCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSBkZl90ZXN0JENsYXNzLCBwID0gMC41LCBsaXN0ID0gRkFMU0UpDQogICAgdGVzdF9kZiA8LSBkZl90ZXN0W2lkLCBdDQogIA0KICAgIGR1X2Jhb19wcm9iIDwtIGgyby5wcmVkaWN0KHB1cmVfbm4sIHRlc3RfZGYgJT4lIGFzLmgybygpKSAlPiUgDQogICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIA0KICAgICAgcHVsbChCYWQpDQogICAgDQogICAgZHVfYmFvIDwtIGNhc2Vfd2hlbihkdV9iYW9fcHJvYiA+PSB0aHJlIH4gIkJhZCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgZHVfYmFvX3Byb2IgPCB0aHJlIH4gIkdvb2QiKSAlPiUgYXMuZmFjdG9yKCkNCiAgICBjbSA8LSBjb25mdXNpb25NYXRyaXgoZHVfYmFvLCB0ZXN0X2RmJENsYXNzLCBwb3NpdGl2ZSA9ICJCYWQiKQ0KICAgIA0KICAgIGJnX2dnIDwtIGNtJHRhYmxlICU+JSANCiAgICAgIGFzLnZlY3RvcigpICU+JSANCiAgICAgIG1hdHJpeChuY29sID0gNCkgJT4lIA0KICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgICAgIHJlbmFtZShUUCA9IFYxLCBGTiA9IFYyLCBGUCA9IFYzLCBUTiA9IFY0KQ0KICANCiAgICANCiAgICBrcSA8LSBjKGNtJG92ZXJhbGwsIGNtJGJ5Q2xhc3MpIA0KICAgIHRlbiA8LSBrcSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByb3cubmFtZXMoKQ0KICAgIA0KICAgIGtxICU+JSANCiAgICAgIGFzLnZlY3RvcigpICU+JSANCiAgICAgIG1hdHJpeChuY29sID0gMTgpICU+JSANCiAgICAgIGFzLmRhdGEuZnJhbWUoKSAtPiBhbGxfZGYNCiAgICANCiAgICBuYW1lcyhhbGxfZGYpIDwtIHRlbg0KICAgIGFsbF9kZiA8LSBiaW5kX2NvbHMoYWxsX2RmLCBiZ19nZykNCiAgICByZXR1cm4oYWxsX2RmKQ0KICB9KQ0KfQ0KDQoNCg0KIyDEkMOhbmggZ2nDoSBz4buxIGJp4bq/biDEkeG7lWkgdGhlbyBt4buZdCBsb+G6oXQgbmfGsOG7oW5nOiANCg0Kc3lzdGVtLnRpbWUoc29fc2FuaF9saXN0IDwtIGxhcHBseShzZXEoMC4wNSwgMC44LCBieSA9IDAuMDUpLCBldmFsX2Z1bikpDQoNCnNvX3NhbmhfZGYgPC0gZG8uY2FsbCgiYmluZF9yb3dzIiwgc29fc2FuaF9saXN0KSANCg0Kc29fc2FuaF9kZiAlPD4lIA0KICBtdXRhdGUoVGhyZXNob2xkID0gbGFwcGx5KHNlcSgwLjA1LCAwLjgsIGJ5ID0gMC4wNSksIGZ1bmN0aW9uKHgpIHtyZXAoeCwgMTApfSkgJT4lIHVubGlzdCgpKQ0KDQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKQ0Kc29fc2FuaF9kZiAlPiUgDQogIGdyb3VwX2J5KFRocmVzaG9sZCkgJT4lIA0KICBzdW1tYXJpc2VfZWFjaChmdW5zKG1lZGlhbiksIEFjY3VyYWN5LCBLYXBwYSwgU2Vuc2l0aXZpdHksIFNwZWNpZmljaXR5KSAlPiUgDQogIGdhdGhlcihNZXRyaWMsIGIsIC1UaHJlc2hvbGQpICU+JSANCiAgZ2dwbG90KGFlcyhUaHJlc2hvbGQsIGIsIGNvbG9yID0gTWV0cmljKSkgKyANCiAgZ2VvbV9saW5lKCkgKyANCiAgZ2VvbV9wb2ludChzaXplID0gMykgKyANCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKyANCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLjA1LCAwLjgsIGJ5ID0gMC4wNSkpICsgDQogIGxhYnMoeSA9ICJBY2N1cmFjeSBSYXRlIiwgDQogICAgICAgdGl0bGUgPSAiVmFyaWF0aW9uIG9mIENsYXNzaWZpZXIncyBNZXRyaWNzIGJ5IFRocmVzaG9sZCIpDQpgYGANCg0KQ2jDum5nIHRhIGPDsyB0aOG7gyB0aOG6pXkgcsO1IHPhu7EgxJHDoW5oIMSR4buVaSDhu58gxJHDonk6IGto4bqzIG7Eg25nIHBow6JuIGxv4bqhaSBjaG8gbmjDo24gQmFkIGPDoG5nIGNhbyAoU2Vuc2l0aXZpdHkpIGPDoG5nIGNhbyB0aMOsIGto4bqjIG7Eg25nIHBow6JuIGxv4bqhaSBjaG8gbmjDo24gR29vZCAoU3BlY2lmaWNpdHkpIGPDoG5nIHRo4bqlcC4gxJBp4buBdSBuw6B5IGThuqtuIMSR4bq/biBBY2N1cmFjeSBjw7MgZOG6oW5nIGjDrG5oIGNo4buvIFUgbmfGsOG7o2MgduG7m2kgbeG7qWMgY+G7sWMgxJHhuqFpIMSR4bqhdCDEkcaw4bujYyBraGkgbmfGsOG7oW5nIMSRxrDhu6NjIGNo4buNbiBsw6AgMC43LiANCg0KDQpDaMO6bmcgdGEgY8OzIHRo4buDIGjDrG5oIOG6o25oIGjDs2Ega2jhuqMgbsSDbmcgcGjDom4gbG/huqFpIGPhu6dhIG3DtCBow6xuaCBjaG8gaGFpIGxv4bqhaSBo4buTIHPGoSBuaMawIHNhdTogDQoNCmBgYHtyLCBmaWcuZnVsbHdpZHRoID0gVFJVRSwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTJ9DQpteV9jbV9jb21fZGwgPC0gZnVuY3Rpb24odGhyZSkgew0KICBkdV9iYW9fcHJvYiA8LSBoMm8ucHJlZGljdChwdXJlX25uLCB0ZXN0KSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBwdWxsKEJhZCkNCiAgZHVfYmFvIDwtIGNhc2Vfd2hlbihkdV9iYW9fcHJvYiA+PSB0aHJlIH4gIkJhZCIsIA0KICAgICAgICAgICAgICAgICAgICAgIGR1X2Jhb19wcm9iIDwgdGhyZSB+ICJHb29kIikgJT4lIGFzLmZhY3RvcigpDQogIGNtIDwtIGNvbmZ1c2lvbk1hdHJpeChkdV9iYW8sIGRmX3Rlc3QkQ2xhc3MsIHBvc2l0aXZlID0gIkJhZCIpDQogIHJldHVybihjbSkNCiAgDQp9DQoNCm15X3RocmVzaG9sZCA8LSBjKDAuMTAsIDAuMjUsIDAuNSwgMC43KQ0KcmVzdWx0c19saXN0X2RsIDwtIGxhcHBseShteV90aHJlc2hvbGQsIG15X2NtX2NvbV9kbCkNCg0KDQp2aXNfZGV0ZWN0aW9uX3JhdGVfZGwgPC0gZnVuY3Rpb24oeCkgew0KICANCiAgcmVzdWx0c19saXN0X2RsW1t4XV0kdGFibGUgJT4lIGFzLmRhdGEuZnJhbWUoKSAtPiBtDQogIHJhdGUgPC0gcm91bmQoMTAwKm0kRnJlcVsxXSAvIHN1bShtJEZyZXFbYygxLCAyKV0pLCAyKQ0KICBhY2MgPC0gcm91bmQoMTAwKnN1bShtJEZyZXFbYygxLCA0KV0pIC8gc3VtKG0kRnJlcSksIDIpDQogIGFjYyA8LSBwYXN0ZTAoYWNjLCAiJSIpDQogIA0KICBtICU+JSANCiAgICBnZ3Bsb3QoYWVzKFJlZmVyZW5jZSwgRnJlcSwgZmlsbCA9IFByZWRpY3Rpb24pKSArDQogICAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZmlsbCIpICsgDQogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2U0MWExYyIsICIjMzc3ZWI4IiksIG5hbWUgPSAiIikgKyANCiAgICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnkgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArIA0KICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgDQogICAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwsIA0KICAgICAgICAgdGl0bGUgPSBwYXN0ZTAoIkRldGVjdGluZyBCYWQgQ2FzZXMgd2hlbiBUaHJlc2hvbGQgPSAiLCBteV90aHJlc2hvbGRbeF0pLCANCiAgICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKCJEZXRlY3RpbmcgUmF0ZSBmb3IgQmFkIENhc2VzOiAiLCByYXRlLCAiJSIsICIsICIsICJBY2N1cmFjeTogIiwgYWNjKSkNCiAgfQ0KDQoNCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHZpc19kZXRlY3Rpb25fcmF0ZV9kbCgxKSwgDQogICAgICAgICAgICAgICAgICAgICAgICB2aXNfZGV0ZWN0aW9uX3JhdGVfZGwoMiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgdmlzX2RldGVjdGlvbl9yYXRlX2RsKDMpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHZpc19kZXRlY3Rpb25fcmF0ZV9kbCg0KSkNCg0KYGBgDQoNCg0KDQoNCg0KDQo=