1 Tại sao phải diễn giải mô hình trong y học ?

Trong y học, khả năng giải nghĩa được cơ chế, nội dung của một mô hình là một trong những phẩm chất quan trọng quyết định tính khả dụng của của mô hình đó.

Không phải ngẫu nhiên mà sau hàng chục năm phát triển của thống kê ứng dụng, những bác sĩ lâm sàng vẫn đang ưa chuộng sử dụng các thang điểm lâm sàng, thí dụ Apgar, Glasgow khi tiếp xúc với bệnh nhân, thay vì một mô hình logistic hoặc mô hình cây, cho dù mô hình cây vô cùng đơn giản, và bệnh viện không thiếu máy tính để chạy những mô hình phức tạp. Bởi vì, khi tính nhẩm thang điểm Glasgow, bác sĩ cấp cứu đang đồng thời quan sát triệu chứng và dùng tư duy để suy luận về mức độ nghiêm trọng của tình trạng bệnh nhân. Nếu người ta tạo ra 1 algorithm trong 1 chiếc camera, cho phép đánh giá tri giác bệnh nhân tự động, bác sĩ sẽ ngừng suy luận và lệ thuộc vào cái máy này.

Một mô hình chính xác dĩ nhiên là tốt, nhưng chưa đủ. Độ chính xác của mô hình là tiêu chí được nhắm tới bởi hầu hết data scientist, tuy nhiên, tính chính xác không đồng nghĩa với tính hiệu quả. Khi áp dụng vào y học, machine learning bị chi phối nhiều hơn bởi những quy tắc về Y đức và Lương tâm.

Ta có thể đưa ra 3 trường hợp giả định như sau để làm rõ sự quan trọng của sự giải nghĩa mô hình :

Một bệnh viện xây dựng mô hình tiên lượng tử vong cho bệnh nhân bị tai biến mạch máu não, sử dụng Machine learning. Họ tạo ra một mô hình Random Forest cực kì chính xác, nó dự báo đúng kết quả Tử vong/sống sót của bệnh nhân đến 98%, 100 trường hợp chỉ sai lầm có 2 mà thôi. Tuy nhiên, khi áp dụng mô hình trên lâm sàng, một số bác sĩ nội trú bắt đầu đặt ra những câu hỏi hoài nghi :

  1. Tại sao trước một bệnh nhân có nhiều tiền sử bệnh lý tim mạch, huyết áp, thậm chí viêm hô hấp mạn tính, thì mô hình lại tiên lượng là người đó sẽ sống sót, trong khi một bệnh nhân trẻ tuổi, hoàn toàn khỏe mạnh, lại nhận tiên lượng nguy cơ tử vong rất cao ?

  2. Tại sao người thành phố và cư ngụ tại các quận trung tâm lại có tiên lượng sống còn tốt hơn, còn người ở vùng nông thôn đa số đều nhận kết qủa tiên lượng tử vong ? Bắt đầu có lời đồn trong bệnh viện về sự phân biệt đối xử giữa bệnh nhân giàu và nghèo, người thành phố và nông thôn.

Sau khi ngồi lại thảo luận giữa các chuyên viên thống kê và các bác sĩ, người ta nhận ra những điều bất ngờ :

  1. Mô hình đã phản ánh hoàn toàn chính xác thực tế : Những bệnh nhân có nhiều tiền sử bệnh lý và yếu tố nguy cơ cao thường nhận được sự theo dõi, chăm sóc và điều trị tích cực hơn, nên kết quả là họ có cơ may sống sót cao hơn. Nhưng : nếu dựa vào tiên lượng sống còn của mô hình mà cán bộ y tế trở nên chủ quan, lơ là trong việc theo dõi và chăm sóc thì kết quả tiên lượng của mô hình hoàn toàn vô nghĩa, thậm chí CÓ HẠI.

  2. Trong training data, địa chỉ thường trú của bệnh nhân được mô hình sử dụng như 1 feature, nhưng cùng lúc, có một feature khác đó là thời gian chậm trễ nhập viện tính từ lúc bệnh nhân bị ngất và tai biến tại nhà. Một cách trùng hợp ngẫu nhiên, bệnh nhân người thành phố sống gần bệnh viện hơn và họ được cấp cứu kịp thời hơn. Chỉ có vậy. Nhưng hãy tưởng tượng nếu tin đồn « phân biệt đối xử » vô căn cứ kia đến tai báo chí, thì mô hình trở thành một bằng cớ chống lại chính chủ nhân của nó. Bây giờ chắc các bạn đã hiểu tại sao tính chính xác không phải là tất cả, nó có thể trở nên vô nghĩa thậm chí nguy hiểm, nếu nó không được giải nghĩa một cách logic.

Thời gian gần đây đã bắt đầu có những ý kiến hoài nghi, thậm chí cảnh báo về phong trào sử dụng Big data và Machine learning vào Y học. Một trong những luận điểm mà các bác sĩ lâm sàng đưa ra, đó là hầu hết những mô hình có nội dung không thể giải thích được. Khi họ không thể hiểu được cơ chế đằng sau kết quả, họ không thể tin cậy vào bản thân kết qủa đó. Kết quả đôi khi ảnh hưởng rất nghiêm trọng đến bệnh nhân : thí dụ chẩn đoán Ung thư, tiên lượng tử vong, lựa chọn phẫu thuật hay điều trị nội khoa ?

Khác với Việt Nam, bệnh nhân tại nước ngoài có quyền được biết lý do của quyết định điều trị. Bác sĩ không thể giải thích rằng : “Bà có 99.5% nguy cơ bi ung thư, vì một mô hình Neural network đã quyết định như vậy”.

Vào năm 2018, tại Âu Châu sẽ có một luật lệ bắt buộc mọi quyết định dựa vào algorithm phải có khả năng lý giải nguyên nhân cho công dân trực tiếp có liên quan. Nếu luật này thực thi, thì khách hàng có quyền yêu cầu ngân hàng giải thích tại sao hồ sơ xin vay vốn của họ bị xếp loại « Nguy cơ cao », tương tự, bệnh nhân có thể tra hỏi bác sĩ về kết quả CT Scan của mình đến cùng.

2 Giải thích mô hình không đơn giản chút nào

Trong Machine learning, có một lời nguyền tai ác, đó là : « Tính tường minh của mô hình tỉ lệ nghịch với tính chính xác”, mô hình càng chính xác thì càng bí hiểm. Sự phức tạp đến từ bản chất của algorithm, số lượng input features hoặc cả 2. Thời đại Big Data và IOT đã cưỡng ép chúng ta phải lựa chọn những algorithm phức tạp nhất, vì những mô hình tuyến tính không có cách nào xử lý được lượng data lớn cỡ đó.

Trong sơ đồ trên, những mô hình chính xác nhất lại chính là những mô hình bí hiểm nhất, bao gồm: Random Forest, GBM, Deep neural network, SVM… Chúng trở thành những hộp đen, hay mô hình “Bất khả Tri”.

Năm 2016, một phương pháp có tên là LIME được tạo ra, nhằm diễn giải nội dung của những black boxes này.

3 Giới thiệu về LIME

Đầu tiên, cần ghi chú rằng LIME không phải là trái chanh, nó là chữ viết tắt của phương pháp có tên là : Local Interpretable Model-Agnostic Explanations, tạm dịch : « Phép diễn giải cục bộ cho mô hình bất khả tri ».

Phương pháp LIME được giới thiệu lần đầu tiên năm 2016 trong một bài báo của 3 data scientists tại ĐH Washinton là Marco Tulio Ribeiro, Sameer Singh và Carlos Guestrin. https://arxiv.org/abs/1602.04938

Sau khi ứng dụng trong Python năm 2016, một phiên bản ứng dụng cho R do Thomas Lin Pedersen và Michaël Benesty đồng tác giả vừa mới được công bố trên CRAN tháng 9 năm 2017. https://cran.r-project.org/web/packages/lime/index.html https://github.com/thomasp85/lime

Một tutorial ngắn được giới thiệu kèm theo package https://cran.r-project.org/web/packages/lime/vignettes/Understanding_lime.html

Cơ chế hoạt động của LIME

Lime diễn giải bất cứ mô hình classifier nào, bao gồm các mô hình « bất khả tri » hay hộp đen (blackbox) theo quy trình như sau :

  1. Phương pháp LIME dựa trên một giả định cơ bản, đó là bất kể mô hình phức tạp đến đâu, thì tại một miền cục bộ trong không gian dữ liệu (không gian nhỏ xíu xung quanh một điểm), mô hình có thể được ước lượng xấp xỉ bằng quy luật tuyến tính. Bạn có thể hình dung trên một vài bệnh nhân có đặc tính input data xấp xỉ như nhau, thì kết quả output của model sẽ như nhau, đó là bằng chứng về sự tuyến tính cục bộ.

  2. Đầu tiên, LIME sẽ lấy thông tin về đặc tính phân phối của feature (dữ liệu đầu vào) dựa vào training dataset và nội dung (feature nào được dùng) trong model. Thông tin này được lưu trữ trong một object gọi là explainer.

  3. Việc diễn giải sẽ được áp dụng cho 1 trường hợp cá thể, mới (unseen case), thí dụ một bệnh nhân bất kì trích từ tập kiểm định (testing subset).

LIME sẽ mô phỏng một lượng lớn các trường hợp giả định (một đám mây nhiễu của features) nằm kề cận chung quanh trường hợp (điểm) đang được xét, dựa vào quy luật phân phối của features mà nó đã ghi nhận từ trước.

  1. Sau đó LIME áp dụng mô hình cho toàn bộ những điểm trong không gian nhiễu này, đồng thời tính khoảng cách giữa các điểm mô phỏng đến điểm trung tâm là trường hợp được xét. Khoảng cách này sẽ được chuyển thành thang điểm (score).

  2. Tiếp theo, LIME chọn một số lượng M features tiêu biểu nhất cho phép mô tả tốt nhất khoảng cách nói trên.

  3. Cuối cùng, LIME dựng một mô hình rất đơn giản cho các điểm mô phỏng, sử dụng M features được chọn làm predictor, để giải nghĩa cho outcome của model. Mô hình này có dạng Tuyến tính, hoặc mô hình Decision tree. Tham số hồi quy cho mỗi Features được điều chỉnh bằng một trong số (Weight) tỉ lệ với khoảng cách sai biệt với giá trị feature có thực của cá thể.

  4. Việc diễn giải tính hợp lý của kết quả được thực hiện dựa vào Weight coefficient và danh sách M features được chọn. Nếu Weight coefficient > 0, thì giá trị quan sát của feature Mi đang ủng hộ cho kết quả tiên lượng (outcome) P, ngược lại, Weight Coefficient <0 thì giá trị feature Mi chống lại kết quả P.

Ưu thế lớn nhất của phương pháp LIME, đó là tính đôc lập với Algorithm. Lime không phụ thuộc vào bản chất của algorithm, thậm chí nó không dùng đến (và không cần biết) cơ chế bên trong của mô hình. Do đó, LIME có thể áp dụng cho mọi algorithm, từ Naive Bayes, KNN cho đến Random Forest hay Neural network. Tính độc lập này cho phép LIME hoạt động tại mọi thời điểm, cho mọi version của mô hình.

Tuy nhiên, package LIME nguyên thủy chỉ mới hỗ trợ mô hình dựng bằng CARET, do đó Nhi có hứng thú viết 1 tutorial khác để áp dụng Lime cho h2o model. H2O là một giao thức machine learning đang phát triển rất mạnh trong R với tính linh hoạt vượt trội so với các công cụ có trước.

Tính tương thích giữa lime và machine learning framework tùy thuộc vào 2 yếu tố: 1) bản chất của model: kết quả dự báo là class hay probability , và tế nhị hơn, 2) output matrix của hàm predict phải chuẩn hóa theo một cấu trúc chuẩn mà LIME quy ước. Nếu bạn dùng caret, cả 2 yếu tố đều được đảm bảo, nhưng nếu bạn dùng 1 tool khác, thí dụ mlr, h2o hay những package chuyên biệt, bạn phải chuẩn hóa và thích nghi 2 yếu tố này, như điều mà Nhi sẽ làm trong bài, vì Nhi sẽ áp dụng LIME trên 1 mô hình Random Forest dựng bằng h2o.

4 Mô hình Random Forest H2O

Thí dụ minh họa trong bài được lấy lại từ một tutorial khác mà Nhi từng viết cho dự án MLM: Thoracic surgery Dataset này là một bài toán tiên lượng tử vong 1 năm cho bệnh nhân chịu phẫu thuật lồng ngực, dựa vào bệnh sử và xét nghiệm tiền phẫu của họ. Các bạn có thể xem qua phần thăm dò dữ liệu tại đây:

http://rpubs.com/ledongnhatnam/270873

Chúng ta sẽ đi thẳng đến thí nghiệm Machine learning, đầu tiên ta chia dữ liệu thành train và test subsets

library(tidyverse)

require(foreign)
df=read.arff("https://archive.ics.uci.edu/ml/machine-learning-databases/00277/ThoraricSurgery.arff")

names(df)=c("Diagnosis","FVC","FEV1","Zubrod","Pain","Haemoptysis","Dyspnoea","Cough","Weakness","T_grade","DBtype2","MI","PAD","Smoking","Asthma","Age","Survival")

df$Survival=df$Survival%>%recode_factor(.,`F` = "Survived", `T` = "Dead")

df$Tiffneau=df$FEV1/df$FVC

library(caret)
set.seed(123)

idTrain=caret::createDataPartition(y=df$Survival,p=410/470,list=FALSE)
trainset=df[idTrain,]
testset=df[-idTrain,]

Sau đó, ta khởi động package h2o, chuyển dạng dữ liệu, và viết code để dựng một mô hình Random Forest

# h2O

library(h2o)
h2o.init(nthreads = -1,max_mem_size ="4g")

wtrain=as.h2o(trainset)
wtest=as.h2o(testset)

response="Survival"
features=setdiff(colnames(wtrain),response)

rfmod=h2o.randomForest(x = features,
                        y = response,
                        training_frame = wtrain,nfolds=10,
                        fold_assignment = "Stratified",
                        ntrees = 200, max_depth = 50,sample_rate=0.5,mtries=4,
                        balance_classes = FALSE,
                        stopping_metric = "mean_per_class_error",
                        stopping_tolerance = 0.001,
                        stopping_rounds = 3,
                        keep_cross_validation_fold_assignment = F,
                        keep_cross_validation_predictions=F,
                        score_each_iteration = TRUE,
                        seed=12345)

Kiểm tra trên train subset, mô hình hoạt động rất tốt:

h2o.performance(rfmod,wtrain)
## H2OBinomialMetrics: drf
## 
## MSE:  0.05088624
## RMSE:  0.2255798
## LogLoss:  0.1777317
## Mean Per-Class Error:  0.09210648
## AUC:  0.983224
## Gini:  0.9664479
## 
## Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:
##          Dead Survived    Error     Rate
## Dead       52       10 0.161290   =10/62
## Survived    8      341 0.022923   =8/349
## Totals     60      351 0.043796  =18/411
## 
## Maximum Metrics: Maximum metrics at their respective thresholds
##                         metric threshold    value idx
## 1                       max f1  0.642857 0.974286  62
## 2                       max f2  0.542857 0.986395  67
## 3                 max f0point5  0.698075 0.975539  57
## 4                 max accuracy  0.642857 0.956204  62
## 5                max precision  1.000000 1.000000   0
## 6                   max recall  0.428571 1.000000  76
## 7              max specificity  1.000000 1.000000   0
## 8             max absolute_mcc  0.642857 0.826906  62
## 9   max min_per_class_accuracy  0.721429 0.908309  53
## 10 max mean_per_class_accuracy  0.698075 0.923491  57
## 
## Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`

Tuy nhiên: Kiểm tra mô hình RF trên testset cho ra kết quả không mấy khả quan:Mô hình phạm tiên lượng sống sót rất chính xác, tuy nhiên nó phạm nhiều sai lầm khi tiên lượng tử vong (7/8)

h2o.performance(rfmod,wtest)
## H2OBinomialMetrics: drf
## 
## MSE:  0.1105337
## RMSE:  0.3324662
## LogLoss:  0.9148379
## Mean Per-Class Error:  0.4375
## AUC:  0.6813725
## Gini:  0.3627451
## 
## Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:
##          Dead Survived    Error   Rate
## Dead        1        7 0.875000   =7/8
## Survived    0       51 0.000000  =0/51
## Totals      1       58 0.118644  =7/59
## 
## Maximum Metrics: Maximum metrics at their respective thresholds
##                         metric threshold    value idx
## 1                       max f1  0.428571 0.935780  21
## 2                       max f2  0.428571 0.973282  21
## 3                 max f0point5  0.571429 0.909091  20
## 4                 max accuracy  0.571429 0.881356  20
## 5                max precision  0.976646 0.954545   4
## 6                   max recall  0.428571 1.000000  21
## 7              max specificity  1.000000 0.875000   0
## 8             max absolute_mcc  0.571429 0.359040  20
## 9   max min_per_class_accuracy  0.857143 0.625000  10
## 10 max mean_per_class_accuracy  0.840816 0.685049  11
## 
## Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`

5 Tại sao vậy bác sĩ ?

Đầu tiên, chúng ta gọi package lime, sau đó viết 2 functions model_type.H2Omodel và predict_model.H2Omodel, để thích nghi định dạng output của mô hình h2O với cấu trúc chuẩn bên trong lime:

Sau đó ta có thể tạo object explainer chứa phân phối của features từ training set và mô hình:

library(lime)

# Adapting lime functions to h2O framework

model_type.H2OModel<- function(x, ...) "classification"

predict_model.H2OModel <- function(x, newdata, type, ...) {
  pred <- h2o.predict(x, as.h2o(newdata))
    return(as.data.frame(pred[,-1]))
}

set.seed(12345)
explainer <- lime(trainset[,-17], rfmod, bin_continuous = FALSE, n_bins = 10, n_permutations = 10000)

Sau đó ta tách riêng ra 2 phần testset: những case tử vong và những case sống sót

deaddf=subset(testset,Survival!="Survived")%>%.[,-17]

survdf=subset(testset,Survival=="Survived")%>%.[,-17]

Việc lựa chọn M features được thực hiện bằng nhiều cách, có thể tùy chọn, thí dụ : None : không lựa chọn ưu tiên, mà tính weights score cho toàn bộ feature trong model, cách này không được khuyến khích nếu M>10

Foward selection (hay ridge regression) : Mô hình tuyến tính được dựng theo ridge regression, có khuynh hướng giảm tham số hồi quy về gần 0 và loại bỏ những feature không quan trọng

Highest weights : Chọn m feature với giá trị weight tuyệt đối cao nhất, dựa vào ridge regression

Lasso : Dựng mô hình tuyến tính bằng phương pháp Lasso, nó giúp loại bỏ triệt để những feature không cần thiết bằng cách đẩy weight score về 0.

Tree: dựng mô hình decision tree được phân cấp dựa vào log2(M) từ giá trị M cao nhất.

Auto: tự động, lime dùng phương pháp forward nếu m<=6 hoặc highest weights nếu m>6.

Đầu tiên, chúng ta chọn ngẫu nhiên 1 bệnh nhân (số 49) trong nhóm Sống sót, và giải thích tại sao mô hình lại tiên lượng bệnh nhân này là “Survied” ?

caseS1<-explain(survdf[12,], explainer, labels="Survived", n_features =10,feature_select="auto")
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=================================================================| 100%
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=================================================================| 100%
plot_features(caseS1)

Kết quả cho thấy: Trong 10 features có ảnh hưởng cao nhất đến tiên lượng: 7 trong số đó ủng hộ cho kết quả sống sót: bệnh nhân không có triệu chứng khó thở, không ho ra máu, không mắc bệnh tiểu đường type 2, Không đau ngực, dung tích phổi cao hơn ngưỡng nguy cơ. Tuy nhiên, có 3 feature mâu thuẫn: bệnh nhân có triệu chứng yếu cơ, có hút thuốc, và điểm Zubrod

caseS1<-explain(survdf[30,], explainer, labels="Survived", n_features =10,feature_select="auto")
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=================================================================| 100%
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=================================================================| 100%
plot_features(caseS1)

Ở 1 bệnh nhân khác (số 273), 9/10 features ủng hộ cho kết quả tiên lượng tốt, bao gồm việc không mắt bệnh hen, chỉ có 1 chi tiết mâu thuẫn là bệnh nhân có ho ra máu

Chúng ta đã tạm giải thích được tại sao mô hình tiên lượng sống sót đúng, bây giờ ta thử giải thích tại sao nó tiên lượng tử vong SAI ? Chỉ có 8 case tử vong nên ta chia làm 2 nhóm

dead1<-explain(deaddf[c(1:4),], explainer, labels="Dead", n_features =10,feature_select="auto")
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=================================================================| 100%
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=================================================================| 100%
plot_features(dead1)

dead2<-explain(deaddf[c(5:8),], explainer, labels="Dead", n_features =10,feature_select="auto")
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=================================================================| 100%
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=================================================================| 100%
plot_features(dead2)

Chỉ có 2 case được tiên lượng dương tính thật, chính xác (bệnh nhân số 41, xác suất tử vong > 60%, bệnh nhân 351, xác suất tử vong 57%); Còn lại kết quả tiên lượng là âm tính giả (bệnh nhân tử vong nhưng mô hình không tiên lượng được). Xem xét những chứng cứ ủng hộ và mâu thuẫn trong cả 2 trường hợp đều có ích và cho phép suy luận cơ chế mà mô hình hoạt động:

Kết quả gỉai thích cho thấy :

Đầu tiên; đặc tính Có hút thuốc lá là một chứng cứ quan trọng, nó ủng hộ cho tiên lượng tử vong một cách có logic. Cả 2 bệnh nhân dương tính thật đều có hút thuốc. Tuy nhiên 1 mình feature smoking không đủ để kéo kết quả outcome theo hướng positive, vì nó bị các triệu chứng khác gây nhiễu. Có vẻ như sự vắng mặt các triệu chứng không đủ để loại trừ nguy cơ tử vong, trong 8 bệnh nhân tử vong hầu hết không biểu hiện triệu chứng khó thở, yếu cơ, hay ho ra máu, nhưng họ vẫn đã chết. Giá trị dung tích phổi cũng không phải là feature có giá trị.

Một cách giải thích khác, đó là mô hình RF đã không thành công trong việc phân tích những triệu chứng này.

Việc giải nghĩa cho cá nhân rất thú vị, tuy nhiên ta còn có thể tiếp cận theo hướng tập thể, tức là giải nghĩa cho toàn bộ testset

explanation <- explain(testset[,-17], explainer, labels="Dead", n_features = 10,feature_select="auto")
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=================================================================| 100%
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=================================================================| 100%
exdf=explanation%>%as_tibble()

exdf$Class=ifelse(exdf$label_prob>=0.5,"Dead","Survived")

exdf%>%
  ggplot(aes(x=reorder(case,feature_weight),y=feature_desc,fill=feature_weight,col=feature_weight))+
  geom_tile(show.legend=F)+
  scale_fill_gradient2(low="red4",mid="white",high="green4",midpoint = 0.0)+
  scale_color_gradient2(low="red",mid="grey",high="green",midpoint = 0.0)+
  theme_bw()+
  theme(axis.text.x=element_blank())+
  facet_wrap(~Class,shrink=T,scale="free")

6 Bàn luận

Lime là một phương pháp rất thú vị và hữu ích, nó cho phép chúng ta trả lời nhiều câu hỏi thực tế khi áp dụng một mô hình black box trong y học lâm sàng, thí dụ:

  1. Tại sao bệnh nhân A lại nhận kết quả như thế này ? (Hãy cho tôi biết mô hình của bạn đã hoạt động theo cơ chế nào trên bệnh nhân này ?)

  2. Tại sao mô hình của bạn phạm sai lầm trong trường hợp này ? Điều gì đã xảy ra bên trong nó dẫn tới sai lầm này ?

Câu trả lời cho câu hỏi thứ nhất có thể giải tỏa hoài nghi của bác sĩ và bệnh nhân, khiến họ tin tưởng vào mô hình Machine learning hơn. Câu hỏi thứ 2, mặt khác, có thể giúp chúng ta phát hiện nhược điểm của mô hình để cải thiện nó.

LS0tDQp0aXRsZTogIkRp4buFbiBnaeG6o2kgbcO0IGjDrG5oIGLhuqV0IGto4bqjIHRyaSINCnN1YnRpdGxlOiAiU+G7rSBk4bulbmcgcGjGsMahbmcgcGjDoXAgTElNRSINCmF1dGhvcjogIkzDqiBOZ+G7jWMgS2jhuqMgTmhpIChNRCxQaEQpIg0KZGF0ZTogIjAyIE9jdCwgMjAxNyINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGVycm9yID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkodmlyaWRpcykNCmxpYnJhcnkoaDJvKQ0KbGlicmFyeShsaW1lKQ0KDQpgYGANCg0KIVtdKExJTUVSLnBuZykNCg0KIyBU4bqhaSBzYW8gcGjhuqNpIGRp4buFbiBnaeG6o2kgbcO0IGjDrG5oIHRyb25nIHkgaOG7jWMgPw0KDQpUcm9uZyB5IGjhu41jLCBraOG6oyBuxINuZyBnaeG6o2kgbmdoxKlhIMSRxrDhu6NjIGPGoSBjaOG6vywgbuG7mWkgZHVuZyBj4bunYSBt4buZdCBtw7QgaMOsbmggbMOgIG3hu5l0IHRyb25nIG5o4buvbmcgcGjhuqltIGNo4bqldCBxdWFuIHRy4buNbmcgcXV54bq/dCDEkeG7i25oIHTDrW5oIGto4bqjIGThu6VuZyBj4bunYSBj4bunYSBtw7QgaMOsbmggxJHDsy4NCg0KS2jDtG5nIHBo4bqjaSBuZ+G6q3Ugbmhpw6puIG3DoCBzYXUgaMOgbmcgY2jhu6VjIG7Eg20gcGjDoXQgdHJp4buDbiBj4bunYSB0aOG7kW5nIGvDqiDhu6luZyBk4bulbmcsIG5o4buvbmcgYsOhYyBzxKkgbMOibSBzw6BuZyB24bqrbiDEkWFuZyDGsGEgY2h14buZbmcgc+G7rSBk4bulbmcgY8OhYyB0aGFuZyDEkWnhu4NtIGzDom0gc8OgbmcsIHRow60gZOG7pSBBcGdhciwgR2xhc2dvdyBraGkgdGnhur9wIHjDumMgduG7m2kgYuG7h25oIG5ow6JuLCB0aGF5IHbDrCBt4buZdCBtw7QgaMOsbmggbG9naXN0aWMgaG/hurdjIG3DtCBow6xuaCBjw6J5LCBjaG8gZMO5IG3DtCBow6xuaCBjw6J5IHbDtCBjw7luZyDEkcahbiBnaeG6o24sIHbDoCBi4buHbmggdmnhu4duIGtow7RuZyB0aGnhur91IG3DoXkgdMOtbmggxJHhu4MgY2jhuqF5IG5o4buvbmcgbcO0IGjDrG5oIHBo4bupYyB04bqhcC4gQuG7n2kgdsOsLCBraGkgdMOtbmggbmjhuqltIHRoYW5nIMSRaeG7g20gR2xhc2dvdywgYsOhYyBzxKkgY+G6pXAgY+G7qXUgxJFhbmcgxJHhu5NuZyB0aOG7nWkgcXVhbiBzw6F0IHRyaeG7h3UgY2jhu6luZyB2w6AgZMO5bmcgdMawIGR1eSDEkeG7gyBzdXkgbHXhuq1uIHbhu4EgbeG7qWMgxJHhu5kgbmdoacOqbSB0cuG7jW5nIGPhu6dhIHTDrG5oIHRy4bqhbmcgYuG7h25oIG5ow6JuLiBO4bq/dSBuZ8aw4budaSB0YSB04bqhbyByYSAxIGFsZ29yaXRobSB0cm9uZyAxIGNoaeG6v2MgY2FtZXJhLCBjaG8gcGjDqXAgxJHDoW5oIGdpw6EgdHJpIGdpw6FjIGLhu4duaCBuaMOibiB04buxIMSR4buZbmcsIGLDoWMgc8SpIHPhur0gbmfhu6tuZyBzdXkgbHXhuq1uIHbDoCBs4buHIHRodeG7mWMgdsOgbyBjw6FpIG3DoXkgbsOgeS4NCg0KTeG7mXQgbcO0IGjDrG5oIGNow61uaCB4w6FjIGTEqSBuaGnDqm4gbMOgIHThu5F0LCBuaMawbmcgY2jGsGEgxJHhu6cuIMSQ4buZIGNow61uaCB4w6FjIGPhu6dhIG3DtCBow6xuaCBsw6AgdGnDqnUgY2jDrSDEkcaw4bujYyBuaOG6r20gdOG7m2kgYuG7n2kgaOG6p3UgaOG6v3QgZGF0YSBzY2llbnRpc3QsIHR1eSBuaGnDqm4sIHTDrW5oIGNow61uaCB4w6FjIGtow7RuZyDEkeG7k25nIG5naMSpYSB24bubaSB0w61uaCBoaeG7h3UgcXXhuqMuIEtoaSDDoXAgZOG7pW5nIHbDoG8geSBo4buNYywgbWFjaGluZSBsZWFybmluZyBi4buLIGNoaSBwaOG7kWkgbmhp4buBdSBoxqFuIGLhu59pIG5o4buvbmcgcXV5IHThuq9jIHbhu4EgWSDEkeG7qWMgdsOgIEzGsMahbmcgdMOibS4NCg0KVGEgY8OzIHRo4buDIMSRxrBhIHJhIDMgdHLGsOG7nW5nIGjhu6NwIGdp4bqjIMSR4buLbmggbmjGsCBzYXUgxJHhu4MgbMOgbSByw7Ugc+G7sSBxdWFuIHRy4buNbmcgY+G7p2Egc+G7sSBnaeG6o2kgbmdoxKlhIG3DtCBow6xuaCA6DQoNCk3hu5l0IGLhu4duaCB2aeG7h24geMOieSBk4buxbmcgbcO0IGjDrG5oIHRpw6puIGzGsOG7o25nIHThu60gdm9uZyBjaG8gYuG7h25oIG5ow6JuIGLhu4sgdGFpIGJp4bq/biBt4bqhY2ggbcOhdSBuw6NvLCAgc+G7rSBk4bulbmcgTWFjaGluZSBsZWFybmluZy4gSOG7jSB04bqhbyByYSBt4buZdCBtw7QgaMOsbmggUmFuZG9tIEZvcmVzdCBj4buxYyBrw6wgY2jDrW5oIHjDoWMsIG7DsyBk4buxIGLDoW8gxJHDum5nIGvhur90IHF14bqjIFThu60gdm9uZy9z4buRbmcgc8OzdCBj4bunYSBi4buHbmggbmjDom4gxJHhur9uIDk4JSwgMTAwIHRyxrDhu51uZyBo4bujcCBjaOG7iSBzYWkgbOG6p20gY8OzIDIgbcOgIHRow7RpLg0KVHV5IG5oacOqbiwga2hpIMOhcCBk4bulbmcgbcO0IGjDrG5oIHRyw6puIGzDom0gc8OgbmcsIG3hu5l0IHPhu5EgYsOhYyBzxKkgbuG7mWkgdHLDuiBi4bqvdCDEkeG6p3UgxJHhurd0IHJhIG5o4buvbmcgY8OidSBo4buPaSBob8OgaSBuZ2hpIDoNCg0KMSkgVOG6oWkgc2FvIHRyxrDhu5tjIG3hu5l0IGLhu4duaCBuaMOibiBjw7Mgbmhp4buBdSB0aeG7gW4gc+G7rSBi4buHbmggbMO9IHRpbSBt4bqhY2gsIGh1eeG6v3Qgw6FwLCB0aOG6rW0gY2jDrSB2acOqbSBow7QgaOG6pXAgbeG6oW4gdMOtbmgsIHRow6wgbcO0IGjDrG5oIGzhuqFpIHRpw6puIGzGsOG7o25nIGzDoCBuZ8aw4budaSDEkcOzIHPhur0gc+G7kW5nIHPDs3QsIHRyb25nIGtoaSBt4buZdCBi4buHbmggbmjDom4gdHLhursgdHXhu5VpLCBob8OgbiB0b8OgbiBraOG7j2UgbeG6oW5oLCBs4bqhaSBuaOG6rW4gdGnDqm4gbMaw4bujbmcgbmd1eSBjxqEgdOG7rSB2b25nIHLhuqV0IGNhbyA/DQoNCjIpIFThuqFpIHNhbyBuZ8aw4budaSB0aMOgbmggcGjhu5EgdsOgIGPGsCBuZ+G7pSB04bqhaSBjw6FjIHF14bqtbiB0cnVuZyB0w6JtIGzhuqFpIGPDsyB0acOqbiBsxrDhu6NuZyBz4buRbmcgY8OybiB04buRdCBoxqFuLCBjw7JuIG5nxrDhu51pIOG7nyB2w7luZyBuw7RuZyB0aMO0biDEkWEgc+G7kSDEkeG7gXUgbmjhuq1uIGvhur90IHHhu6dhIHRpw6puIGzGsOG7o25nIHThu60gdm9uZyA/IELhuq90IMSR4bqndSBjw7MgbOG7nWkgxJHhu5NuIHRyb25nIGLhu4duaCB2aeG7h24gduG7gSBz4buxIHBow6JuIGJp4buHdCDEkeG7kWkgeOG7rSBnaeG7r2EgYuG7h25oIG5ow6JuIGdpw6B1IHbDoCBuZ2jDqG8sIG5nxrDhu51pIHRow6BuaCBwaOG7kSB2w6AgbsO0bmcgdGjDtG4uDQoNClNhdSBraGkgbmfhu5NpIGzhuqFpIHRo4bqjbyBsdeG6rW4gZ2nhu69hIGPDoWMgY2h1ecOqbiB2acOqbiB0aOG7kW5nIGvDqiB2w6AgY8OhYyBiw6FjIHPEqSwgbmfGsOG7nWkgdGEgbmjhuq1uIHJhIG5o4buvbmcgxJFp4buBdSBi4bqldCBuZ+G7nSA6DQoNCjEpCU3DtCBow6xuaCDEkcOjIHBo4bqjbiDDoW5oIGhvw6BuIHRvw6BuIGNow61uaCB4w6FjIHRo4buxYyB04bq/IDogTmjhu69uZyBi4buHbmggbmjDom4gY8OzIG5oaeG7gXUgdGnhu4FuIHPhu60gYuG7h25oIGzDvSB2w6AgeeG6v3UgdOG7kSBuZ3V5IGPGoSBjYW8gdGjGsOG7nW5nIG5o4bqtbiDEkcaw4bujYyBz4buxIHRoZW8gZMO1aSwgY2jEg20gc8OzYyB2w6AgxJFp4buBdSB0cuG7iyB0w61jaCBj4buxYyBoxqFuLCBuw6puIGvhur90IHF14bqjIGzDoCBo4buNIGPDsyBjxqEgbWF5IHPhu5FuZyBzw7N0IGNhbyBoxqFuLiBOaMawbmcgOiBu4bq/dSBk4buxYSB2w6BvIHRpw6puIGzGsOG7o25nIHPhu5FuZyBjw7JuIGPhu6dhIG3DtCBow6xuaCBtw6AgY8OhbiBi4buZIHkgdOG6vyB0cuG7nyBuw6puIGNo4bunIHF1YW4sIGzGoSBsw6AgdHJvbmcgdmnhu4djIHRoZW8gZMO1aSB2w6AgY2jEg20gc8OzYyB0aMOsIGvhur90IHF14bqjIHRpw6puIGzGsOG7o25nIGPhu6dhIG3DtCBow6xuaCBob8OgbiB0b8OgbiB2w7QgbmdoxKlhLCB0aOG6rW0gY2jDrSBDw5MgSOG6oEkuDQoNCjIpCVRyb25nIHRyYWluaW5nIGRhdGEsIMSR4buLYSBjaOG7iSB0aMaw4budbmcgdHLDuiBj4bunYSBi4buHbmggbmjDom4gxJHGsOG7o2MgbcO0IGjDrG5oIHPhu60gZOG7pW5nIG5oxrAgMSBmZWF0dXJlLCBuaMawbmcgY8O5bmcgbMO6YywgY8OzIG3hu5l0IGZlYXR1cmUga2jDoWMgxJHDsyBsw6AgdGjhu51pIGdpYW4gY2jhuq1tIHRy4buFIG5o4bqtcCB2aeG7h24gdMOtbmggdOG7qyBsw7pjIGLhu4duaCBuaMOibiBi4buLIG5n4bqldCB2w6AgdGFpIGJp4bq/biB04bqhaSBuaMOgLiBN4buZdCBjw6FjaCB0csO5bmcgaOG7o3Agbmfhuqt1IG5oacOqbiwgYuG7h25oIG5ow6JuIG5nxrDhu51pIHRow6BuaCBwaOG7kSBz4buRbmcgZ+G6p24gYuG7h25oIHZp4buHbiBoxqFuIHbDoCBo4buNIMSRxrDhu6NjIGPhuqVwIGPhu6l1IGvhu4twIHRo4budaSBoxqFuLiBDaOG7iSBjw7MgduG6rXkuIE5oxrBuZyBow6N5IHTGsOG7n25nIHTGsOG7o25nIG7hur91IHRpbiDEkeG7k24gwqsgcGjDom4gYmnhu4d0IMSR4buRaSB44butIMK7IHbDtCBjxINuIGPhu6kga2lhIMSR4bq/biB0YWkgYsOhbyBjaMOtLCB0aMOsIG3DtCBow6xuaCB0cuG7nyB0aMOgbmggbeG7mXQgYuG6sW5nIGPhu5sgY2jhu5FuZyBs4bqhaSBjaMOtbmggY2jhu6cgbmjDom4gY+G7p2EgbsOzLg0KQsOieSBnaeG7nSBjaOG6r2MgY8OhYyBi4bqhbiDEkcOjIGhp4buDdSB04bqhaSBzYW8gdMOtbmggY2jDrW5oIHjDoWMga2jDtG5nIHBo4bqjaSBsw6AgdOG6pXQgY+G6oywgbsOzIGPDsyB0aOG7gyB0cuG7nyBuw6puIHbDtCBuZ2jEqWEgdGjhuq1tIGNow60gbmd1eSBoaeG7g20sIG7hur91IG7DsyBraMO0bmcgxJHGsOG7o2MgZ2nhuqNpIG5naMSpYSBt4buZdCBjw6FjaCBsb2dpYy4NCg0KVGjhu51pIGdpYW4gZ+G6p24gxJHDonkgxJHDoyBi4bqvdCDEkeG6p3UgY8OzIG5o4buvbmcgw70ga2nhur9uIGhvw6BpIG5naGksIHRo4bqtbSBjaMOtIGPhuqNuaCBiw6FvIHbhu4EgcGhvbmcgdHLDoG8gc+G7rSBk4bulbmcgQmlnIGRhdGEgdsOgIE1hY2hpbmUgbGVhcm5pbmcgdsOgbyBZIGjhu41jLiBN4buZdCB0cm9uZyBuaOG7r25nIGx14bqtbiDEkWnhu4NtIG3DoCBjw6FjIGLDoWMgc8SpIGzDom0gc8OgbmcgxJHGsGEgcmEsIMSRw7MgbMOgIGjhuqd1IGjhur90IG5o4buvbmcgbcO0IGjDrG5oIGPDsyBu4buZaSBkdW5nIGtow7RuZyB0aOG7gyBnaeG6o2kgdGjDrWNoIMSRxrDhu6NjLiBLaGkgaOG7jSBraMO0bmcgdGjhu4MgaGnhu4N1IMSRxrDhu6NjIGPGoSBjaOG6vyDEkeG6sW5nIHNhdSBr4bq/dCBxdeG6oywgaOG7jSBraMO0bmcgdGjhu4MgdGluIGPhuq15IHbDoG8gYuG6o24gdGjDom4ga+G6v3QgceG7p2EgxJHDsy4gS+G6v3QgcXXhuqMgxJHDtGkga2hpIOG6o25oIGjGsOG7n25nIHLhuqV0IG5naGnDqm0gdHLhu41uZyDEkeG6v24gYuG7h25oIG5ow6JuIDogdGjDrSBk4bulIGNo4bqpbiDEkW/DoW4gVW5nIHRoxrAsIHRpw6puIGzGsOG7o25nIHThu60gdm9uZywgbOG7sWEgY2jhu41uIHBo4bqrdSB0aHXhuq10IGhheSDEkWnhu4F1IHRy4buLIG7hu5lpIGtob2EgPw0KDQpLaMOhYyB24bubaSBWaeG7h3QgTmFtLCBi4buHbmggbmjDom4gdOG6oWkgbsaw4bubYyBuZ2/DoGkgY8OzIHF1eeG7gW4gxJHGsOG7o2MgYmnhur90IGzDvSBkbyBj4bunYSBxdXnhur90IMSR4buLbmggxJFp4buBdSB0cuG7iy4gQsOhYyBzxKkga2jDtG5nIHRo4buDIGdp4bqjaSB0aMOtY2ggcuG6sW5nIDogIkLDoCBjw7MgOTkuNSUgbmd1eSBjxqEgYmkgdW5nIHRoxrAsIHbDrCBt4buZdCBtw7QgaMOsbmggTmV1cmFsIG5ldHdvcmsgxJHDoyBxdXnhur90IMSR4buLbmggbmjGsCB24bqteSIuDQoNClbDoG8gbsSDbSAyMDE4LCB04bqhaSDDgnUgQ2jDonUgc+G6vSBjw7MgbeG7mXQgbHXhuq10IGzhu4cgYuG6r3QgYnXhu5ljIG3hu41pIHF1eeG6v3QgxJHhu4tuaCBk4buxYSB2w6BvIGFsZ29yaXRobSBwaOG6o2kgY8OzIGto4bqjIG7Eg25nIGzDvSBnaeG6o2kgbmd1ecOqbiBuaMOibiBjaG8gY8O0bmcgZMOibiB0cuG7sWMgdGnhur9wIGPDsyBsacOqbiBxdWFuLiBO4bq/dSBsdeG6rXQgbsOgeSB0aOG7sWMgdGhpLCB0aMOsIGtow6FjaCBow6BuZyBjw7MgcXV54buBbiB5w6p1IGPhuqd1IG5nw6JuIGjDoG5nIGdp4bqjaSB0aMOtY2ggdOG6oWkgc2FvIGjhu5Mgc8ahIHhpbiB2YXkgduG7kW4gY+G7p2EgaOG7jSBi4buLIHjhur9wIGxv4bqhaSDCqyBOZ3V5IGPGoSBjYW8gwrssIHTGsMahbmcgdOG7sSwgYuG7h25oIG5ow6JuIGPDsyB0aOG7gyB0cmEgaOG7j2kgYsOhYyBzxKkgduG7gSBr4bq/dCBxdeG6oyBDVCBTY2FuIGPhu6dhIG3DrG5oIMSR4bq/biBjw7luZy4NCg0KIyBHaeG6o2kgdGjDrWNoIG3DtCBow6xuaCBraMO0bmcgxJHGoW4gZ2nhuqNuIGNow7p0IG7DoG8NCg0KIVtdKGludGVycHJldGFiaWxpdHkyLnBuZykNCg0KVHJvbmcgTWFjaGluZSBsZWFybmluZywgY8OzIG3hu5l0IGzhu51pIG5ndXnhu4FuIHRhaSDDoWMsIMSRw7MgbMOgIDogwqsgVMOtbmggdMaw4budbmcgbWluaCBj4bunYSBtw7QgaMOsbmggdOG7iSBs4buHIG5naOG7i2NoIHbhu5tpIHTDrW5oIGNow61uaCB4w6Fj4oCdLCBtw7QgaMOsbmggY8OgbmcgY2jDrW5oIHjDoWMgdGjDrCBjw6BuZyBiw60gaGnhu4NtLiBT4buxIHBo4bupYyB04bqhcCDEkeG6v24gdOG7qyBi4bqjbiBjaOG6pXQgY+G7p2EgYWxnb3JpdGhtLCBz4buRIGzGsOG7o25nIGlucHV0IGZlYXR1cmVzIGhv4bq3YyBj4bqjIDIuIFRo4budaSDEkeG6oWkgQmlnIERhdGEgdsOgIElPVCDEkcOjIGPGsOG7oW5nIMOpcCBjaMO6bmcgdGEgcGjhuqNpIGzhu7FhIGNo4buNbiBuaOG7r25nIGFsZ29yaXRobSBwaOG7qWMgdOG6oXAgbmjhuqV0LCB2w6wgbmjhu69uZyBtw7QgaMOsbmggdHV54bq/biB0w61uaCBraMO0bmcgY8OzIGPDoWNoIG7DoG8geOG7rSBsw70gxJHGsOG7o2MgbMaw4bujbmcgZGF0YSBs4bubbiBj4buhIMSRw7MuDQoNClRyb25nIHPGoSDEkeG7kyB0csOqbiwgbmjhu69uZyBtw7QgaMOsbmggY2jDrW5oIHjDoWMgbmjhuqV0IGzhuqFpIGNow61uaCBsw6Agbmjhu69uZyBtw7QgaMOsbmggYsOtIGhp4buDbSBuaOG6pXQsIGJhbyBn4buTbTogUmFuZG9tIEZvcmVzdCwgR0JNLCBEZWVwIG5ldXJhbCBuZXR3b3JrLCBTVk3igKYgQ2jDum5nIHRy4bufIHRow6BuaCBuaOG7r25nIGjhu5lwIMSRZW4sIGhheSBtw7QgaMOsbmgg4oCcQuG6pXQga2jhuqMgVHJp4oCdLiANCg0KTsSDbSAyMDE2LCBt4buZdCBwaMawxqFuZyBwaMOhcCBjw7MgdMOqbiBsw6AgTElNRSDEkcaw4bujYyB04bqhbyByYSwgbmjhurFtIGRp4buFbiBnaeG6o2kgbuG7mWkgZHVuZyBj4bunYSBuaOG7r25nIGJsYWNrIGJveGVzIG7DoHkuIA0KDQojIEdp4bubaSB0aGnhu4d1IHbhu4EgTElNRQ0KDQrEkOG6p3UgdGnDqm4sIGPhuqduIGdoaSBjaMO6IHLhurFuZyBMSU1FIGtow7RuZyBwaOG6o2kgbMOgIHRyw6FpIGNoYW5oLCBuw7MgbMOgIGNo4buvIHZp4bq/dCB04bqvdCBj4bunYSBwaMawxqFuZyBwaMOhcCBjw7MgdMOqbiBsw6AgOiBMb2NhbCBJbnRlcnByZXRhYmxlIE1vZGVsLUFnbm9zdGljIEV4cGxhbmF0aW9ucywgdOG6oW0gZOG7i2NoIDogwqsgUGjDqXAgZGnhu4VuIGdp4bqjaSBj4bulYyBi4buZIGNobyBtw7QgaMOsbmggYuG6pXQga2jhuqMgdHJpIMK7LiANCg0KUGjGsMahbmcgcGjDoXAgTElNRSDEkcaw4bujYyBnaeG7m2kgdGhp4buHdSBs4bqnbiDEkeG6p3UgdGnDqm4gbsSDbSAyMDE2IHRyb25nIG3hu5l0IGLDoGkgYsOhbyBj4bunYSAzIGRhdGEgc2NpZW50aXN0cyB04bqhaSDEkEggV2FzaGludG9uIGzDoCBNYXJjbyBUdWxpbyBSaWJlaXJvLCBTYW1lZXIgU2luZ2ggdsOgIENhcmxvcyBHdWVzdHJpbi4gPGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8xNjAyLjA0OTM4Pg0KDQpTYXUga2hpIOG7qW5nIGThu6VuZyB0cm9uZyBQeXRob24gbsSDbSAyMDE2LCBt4buZdCBwaGnDqm4gYuG6o24g4bupbmcgZOG7pW5nIGNobyBSIGRvIFRob21hcyBMaW4gUGVkZXJzZW4gdsOgIE1pY2hhw6tsIEJlbmVzdHkgxJHhu5NuZyB0w6FjIGdp4bqjIHbhu6thIG3hu5tpIMSRxrDhu6NjIGPDtG5nIGLhu5EgdHLDqm4gQ1JBTiB0aMOhbmcgOSBuxINtIDIwMTcuDQo8aHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2xpbWUvaW5kZXguaHRtbD4NCjxodHRwczovL2dpdGh1Yi5jb20vdGhvbWFzcDg1L2xpbWU+DQoNCk3hu5l0IHR1dG9yaWFsIG5n4bqvbiDEkcaw4bujYyBnaeG7m2kgdGhp4buHdSBrw6htIHRoZW8gcGFja2FnZQ0KPGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9saW1lL3ZpZ25ldHRlcy9VbmRlcnN0YW5kaW5nX2xpbWUuaHRtbD4NCg0KKkPGoSBjaOG6vyBob+G6oXQgxJHhu5luZyBj4bunYSBMSU1FKg0KDQpMaW1lIGRp4buFbiBnaeG6o2kgYuG6pXQgY+G7qSBtw7QgaMOsbmggY2xhc3NpZmllciBuw6BvLCBiYW8gZ+G7k20gY8OhYyBtw7QgaMOsbmggwqsgYuG6pXQga2jhuqMgdHJpIMK7IGhheSBo4buZcCDEkWVuIChibGFja2JveCkgdGhlbyBxdXkgdHLDrG5oIG5oxrAgc2F1IDoNCg0KMCkgUGjGsMahbmcgcGjDoXAgTElNRSBk4buxYSB0csOqbiBt4buZdCBnaeG6oyDEkeG7i25oIGPGoSBi4bqjbiwgxJHDsyBsw6AgYuG6pXQga+G7gyBtw7QgaMOsbmggcGjhu6ljIHThuqFwIMSR4bq/biDEkcOidSwgdGjDrCB04bqhaSBt4buZdCBtaeG7gW4gY+G7pWMgYuG7mSB0cm9uZyBraMO0bmcgZ2lhbiBk4buvIGxp4buHdSAoa2jDtG5nIGdpYW4gbmjhu48geMOtdSB4dW5nIHF1YW5oIG3hu5l0IMSRaeG7g20pLCBtw7QgaMOsbmggY8OzIHRo4buDIMSRxrDhu6NjIMaw4bubYyBsxrDhu6NuZyB44bqlcCB44buJIGLhurFuZyBxdXkgbHXhuq10IHR1eeG6v24gdMOtbmguIELhuqFuIGPDsyB0aOG7gyBow6xuaCBkdW5nIHRyw6puIG3hu5l0IHbDoGkgYuG7h25oIG5ow6JuIGPDsyDEkeG6t2MgdMOtbmggaW5wdXQgZGF0YSB44bqlcCB44buJIG5oxrAgbmhhdSwgdGjDrCBr4bq/dCBxdeG6oyBvdXRwdXQgY+G7p2EgbW9kZWwgc+G6vSBuaMawIG5oYXUsIMSRw7MgbMOgIGLhurFuZyBjaOG7qW5nIHbhu4Egc+G7sSB0dXnhur9uIHTDrW5oIGPhu6VjIGLhu5kuDQoNCjEpIMSQ4bqndSB0acOqbiwgTElNRSBz4bq9IGzhuqV5IHRow7RuZyB0aW4gduG7gSDEkeG6t2MgdMOtbmggcGjDom4gcGjhu5FpIGPhu6dhIGZlYXR1cmUgKGThu68gbGnhu4d1IMSR4bqndSB2w6BvKSBk4buxYSB2w6BvIHRyYWluaW5nIGRhdGFzZXQgdsOgIG7hu5lpIGR1bmcgKGZlYXR1cmUgbsOgbyDEkcaw4bujYyBkw7luZykgdHJvbmcgbW9kZWwuIFRow7RuZyB0aW4gbsOgeSDEkcaw4bujYyBsxrB1IHRy4buvIHRyb25nIG3hu5l0IG9iamVjdCBn4buNaSBsw6AgZXhwbGFpbmVyLg0KDQoyKSBWaeG7h2MgZGnhu4VuIGdp4bqjaSBz4bq9IMSRxrDhu6NjIMOhcCBk4bulbmcgY2hvIDEgdHLGsOG7nW5nIGjhu6NwIGPDoSB0aOG7gywgbeG7m2kgKHVuc2VlbiBjYXNlKSwgdGjDrSBk4bulIG3hu5l0IGLhu4duaCBuaMOibiBi4bqldCBrw6wgdHLDrWNoIHThu6sgdOG6rXAga2nhu4NtIMSR4buLbmggKHRlc3Rpbmcgc3Vic2V0KS4gDQoNCkxJTUUgc+G6vSBtw7QgcGjhu49uZyBt4buZdCBsxrDhu6NuZyBs4bubbiBjw6FjIHRyxrDhu51uZyBo4bujcCBnaeG6oyDEkeG7i25oICht4buZdCDEkcOhbSBtw6J5IG5oaeG7hXUgY+G7p2EgZmVhdHVyZXMpIG7hurFtIGvhu4EgY+G6rW4gY2h1bmcgcXVhbmggdHLGsOG7nW5nIGjhu6NwICjEkWnhu4NtKSDEkWFuZyDEkcaw4bujYyB4w6l0LCBk4buxYSB2w6BvIHF1eSBsdeG6rXQgcGjDom4gcGjhu5FpIGPhu6dhIGZlYXR1cmVzIG3DoCBuw7MgxJHDoyBnaGkgbmjhuq1uIHThu6sgdHLGsOG7m2MuDQoNCjMpIFNhdSDEkcOzIExJTUUgw6FwIGThu6VuZyBtw7QgaMOsbmggY2hvIHRvw6BuIGLhu5kgbmjhu69uZyDEkWnhu4NtIHRyb25nIGtow7RuZyBnaWFuIG5oaeG7hXUgbsOgeSwgxJHhu5NuZyB0aOG7nWkgdMOtbmgga2hv4bqjbmcgY8OhY2ggZ2nhu69hIGPDoWMgxJFp4buDbSBtw7QgcGjhu49uZyDEkeG6v24gxJFp4buDbSB0cnVuZyB0w6JtIGzDoCB0csaw4budbmcgaOG7o3AgxJHGsOG7o2MgeMOpdC4gS2hv4bqjbmcgY8OhY2ggbsOgeSBz4bq9IMSRxrDhu6NjIGNodXnhu4NuIHRow6BuaCB0aGFuZyDEkWnhu4NtIChzY29yZSkuIA0KDQo0KSBUaeG6v3AgdGhlbywgTElNRSBjaOG7jW4gbeG7mXQgc+G7kSBsxrDhu6NuZyBNIGZlYXR1cmVzIHRpw6p1IGJp4buDdSBuaOG6pXQgY2hvIHBow6lwIG3DtCB04bqjIHThu5F0IG5o4bqldCBraG/huqNuZyBjw6FjaCBuw7NpIHRyw6puLg0KDQo1KSBDdeG7kWkgY8O5bmcsIExJTUUgZOG7sW5nIG3hu5l0IG3DtCBow6xuaCBy4bqldCDEkcahbiBnaeG6o24gY2hvIGPDoWMgxJFp4buDbSBtw7QgcGjhu49uZywgc+G7rSBk4bulbmcgTSBmZWF0dXJlcyDEkcaw4bujYyBjaOG7jW4gbMOgbSBwcmVkaWN0b3IsIMSR4buDIGdp4bqjaSBuZ2jEqWEgY2hvIG91dGNvbWUgY+G7p2EgbW9kZWwuIE3DtCBow6xuaCBuw6B5IGPDsyBk4bqhbmcgVHV54bq/biB0w61uaCwgaG/hurdjIG3DtCBow6xuaCBEZWNpc2lvbiB0cmVlLiBUaGFtIHPhu5EgaOG7k2kgcXV5IGNobyBt4buXaSBGZWF0dXJlcyDEkcaw4bujYyDEkWnhu4F1IGNo4buJbmggYuG6sW5nIG3hu5l0IHRyb25nIHPhu5EgKFdlaWdodCkgdOG7iSBs4buHIHbhu5tpIGtob+G6o25nIGPDoWNoIHNhaSBiaeG7h3QgduG7m2kgZ2nDoSB0cuG7iyBmZWF0dXJlIGPDsyB0aOG7sWMgY+G7p2EgY8OhIHRo4buDLg0KDQo2KSBWaeG7h2MgZGnhu4VuIGdp4bqjaSB0w61uaCBo4bujcCBsw70gY+G7p2Ega+G6v3QgcXXhuqMgxJHGsOG7o2MgdGjhu7FjIGhp4buHbiBk4buxYSB2w6BvIFdlaWdodCBjb2VmZmljaWVudCB2w6AgZGFuaCBzw6FjaCBNIGZlYXR1cmVzIMSRxrDhu6NjIGNo4buNbi4gTuG6v3UgV2VpZ2h0IGNvZWZmaWNpZW50ID4gMCwgdGjDrCBnacOhIHRy4buLIHF1YW4gc8OhdCBj4bunYSBmZWF0dXJlIE1pIMSRYW5nIOG7p25nIGjhu5kgY2hvIGvhur90IHF14bqjIHRpw6puIGzGsOG7o25nIChvdXRjb21lKSBQLCBuZ8aw4bujYyBs4bqhaSwgV2VpZ2h0IENvZWZmaWNpZW50IDwwIHRow6wgZ2nDoSB0cuG7iyBmZWF0dXJlIE1pIGNo4buRbmcgbOG6oWkga+G6v3QgcXXhuqMgUC4NCg0Kxq91IHRo4bq/IGzhu5tuIG5o4bqldCBj4bunYSBwaMawxqFuZyBwaMOhcCBMSU1FLCDEkcOzIGzDoCB0w61uaCDEkcO0YyBs4bqtcCB24bubaSBBbGdvcml0aG0uIExpbWUga2jDtG5nIHBo4bulIHRodeG7mWMgdsOgbyBi4bqjbiBjaOG6pXQgY+G7p2EgYWxnb3JpdGhtLCB0aOG6rW0gY2jDrSBuw7Mga2jDtG5nIGTDuW5nIMSR4bq/biAodsOgIGtow7RuZyBj4bqnbiBiaeG6v3QpIGPGoSBjaOG6vyBiw6puIHRyb25nIGPhu6dhIG3DtCBow6xuaC4gRG8gxJHDsywgTElNRSBjw7MgdGjhu4Mgw6FwIGThu6VuZyBjaG8gbeG7jWkgYWxnb3JpdGhtLCB04burIE5haXZlIEJheWVzLCBLTk4gY2hvIMSR4bq/biBSYW5kb20gRm9yZXN0IGhheSBOZXVyYWwgbmV0d29yay4gVMOtbmggxJHhu5ljIGzhuq1wIG7DoHkgY2hvIHBow6lwIExJTUUgaG/huqF0IMSR4buZbmcgdOG6oWkgbeG7jWkgdGjhu51pIMSRaeG7g20sIGNobyBt4buNaSB2ZXJzaW9uIGPhu6dhIG3DtCBow6xuaC4gDQoNClR1eSBuaGnDqm4sIHBhY2thZ2UgTElNRSBuZ3V5w6puIHRo4buneSBjaOG7iSBt4bubaSBo4buXIHRy4bujIG3DtCBow6xuaCBk4buxbmcgYuG6sW5nIENBUkVULCBkbyDEkcOzIE5oaSBjw7MgaOG7qW5nIHRow7ogdmnhur90IDEgdHV0b3JpYWwga2jDoWMgxJHhu4Mgw6FwIGThu6VuZyBMaW1lIGNobyBoMm8gbW9kZWwuIEgyTyBsw6AgbeG7mXQgZ2lhbyB0aOG7qWMgbWFjaGluZSBsZWFybmluZyDEkWFuZyBwaMOhdCB0cmnhu4NuIHLhuqV0IG3huqFuaCB0cm9uZyBSIHbhu5tpIHTDrW5oIGxpbmggaG/huqF0IHbGsOG7o3QgdHLhu5lpIHNvIHbhu5tpIGPDoWMgY8O0bmcgY+G7pSBjw7MgdHLGsOG7m2MuDQoNClTDrW5oIHTGsMahbmcgdGjDrWNoIGdp4buvYSBsaW1lIHbDoCBtYWNoaW5lIGxlYXJuaW5nIGZyYW1ld29yayB0w7l5IHRodeG7mWMgdsOgbyAyIHnhur91IHThu5E6IDEpIGLhuqNuIGNo4bqldCBj4bunYSBtb2RlbDoga+G6v3QgcXXhuqMgZOG7sSBiw6FvIGzDoCBjbGFzcyBoYXkgcHJvYmFiaWxpdHkgLCB2w6AgdOG6vyBuaOG7iyBoxqFuLCAyKSBvdXRwdXQgbWF0cml4IGPhu6dhIGjDoG0gcHJlZGljdCBwaOG6o2kgY2h14bqpbiBow7NhIHRoZW8gbeG7mXQgY+G6pXUgdHLDumMgY2h14bqpbiBtw6AgTElNRSBxdXkgxrDhu5tjLiBO4bq/dSBi4bqhbiBkw7luZyBjYXJldCwgY+G6oyAyIHnhur91IHThu5EgxJHhu4F1IMSRxrDhu6NjIMSR4bqjbSBi4bqjbywgbmjGsG5nIG7hur91IGLhuqFuIGTDuW5nIDEgdG9vbCBraMOhYywgdGjDrSBk4bulIG1sciwgaDJvIGhheSBuaOG7r25nIHBhY2thZ2UgY2h1ecOqbiBiaeG7h3QsIGLhuqFuIHBo4bqjaSBjaHXhuqluIGjDs2EgdsOgIHRow61jaCBuZ2hpIDIgeeG6v3UgdOG7kSBuw6B5LCBuaMawIMSRaeG7gXUgbcOgIE5oaSBz4bq9IGzDoG0gdHJvbmcgYsOgaSwgdsOsIE5oaSBz4bq9IMOhcCBk4bulbmcgTElNRSB0csOqbiAxIG3DtCBow6xuaCBSYW5kb20gRm9yZXN0IGThu7FuZyAgYuG6sW5nIGgyby4NCg0KIyBNw7QgaMOsbmggUmFuZG9tIEZvcmVzdCBIMk8NCg0KVGjDrSBk4bulIG1pbmggaOG7jWEgdHJvbmcgYsOgaSDEkcaw4bujYyBs4bqleSBs4bqhaSB04burIG3hu5l0IHR1dG9yaWFsIGtow6FjIG3DoCBOaGkgdOG7q25nIHZp4bq/dCBjaG8gZOG7sSDDoW4gTUxNOiBUaG9yYWNpYyBzdXJnZXJ5IERhdGFzZXQgbsOgeSBsw6AgbeG7mXQgYsOgaSB0b8OhbiB0acOqbiBsxrDhu6NuZyB04butIHZvbmcgMSBuxINtIGNobyBi4buHbmggbmjDom4gY2jhu4t1IHBo4bqrdSB0aHXhuq10IGzhu5NuZyBuZ+G7sWMsIGThu7FhIHbDoG8gYuG7h25oIHPhu60gdsOgIHjDqXQgbmdoaeG7h20gdGnhu4FuIHBo4bqrdSBj4bunYSBo4buNLiBDw6FjIGLhuqFuIGPDsyB0aOG7gyB4ZW0gcXVhIHBo4bqnbiB0aMSDbSBkw7IgZOG7ryBsaeG7h3UgdOG6oWkgxJHDonk6DQoNCmh0dHA6Ly9ycHVicy5jb20vbGVkb25nbmhhdG5hbS8yNzA4NzMNCg0KQ2jDum5nIHRhIHPhur0gxJFpIHRo4bqzbmcgxJHhur9uIHRow60gbmdoaeG7h20gTWFjaGluZSBsZWFybmluZywgxJHhuqd1IHRpw6puIHRhIGNoaWEgZOG7ryBsaeG7h3UgdGjDoG5oIHRyYWluIHbDoCB0ZXN0IHN1YnNldHMNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KcmVxdWlyZShmb3JlaWduKQ0KZGY9cmVhZC5hcmZmKCJodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvMDAyNzcvVGhvcmFyaWNTdXJnZXJ5LmFyZmYiKQ0KDQpuYW1lcyhkZik9YygiRGlhZ25vc2lzIiwiRlZDIiwiRkVWMSIsIlp1YnJvZCIsIlBhaW4iLCJIYWVtb3B0eXNpcyIsIkR5c3Bub2VhIiwiQ291Z2giLCJXZWFrbmVzcyIsIlRfZ3JhZGUiLCJEQnR5cGUyIiwiTUkiLCJQQUQiLCJTbW9raW5nIiwiQXN0aG1hIiwiQWdlIiwiU3Vydml2YWwiKQ0KDQpkZiRTdXJ2aXZhbD1kZiRTdXJ2aXZhbCU+JXJlY29kZV9mYWN0b3IoLixgRmAgPSAiU3Vydml2ZWQiLCBgVGAgPSAiRGVhZCIpDQoNCmRmJFRpZmZuZWF1PWRmJEZFVjEvZGYkRlZDDQoNCmxpYnJhcnkoY2FyZXQpDQpzZXQuc2VlZCgxMjMpDQoNCmlkVHJhaW49Y2FyZXQ6OmNyZWF0ZURhdGFQYXJ0aXRpb24oeT1kZiRTdXJ2aXZhbCxwPTQxMC80NzAsbGlzdD1GQUxTRSkNCnRyYWluc2V0PWRmW2lkVHJhaW4sXQ0KdGVzdHNldD1kZlstaWRUcmFpbixdDQoNCmBgYA0KDQpTYXUgxJHDsywgdGEga2jhu59pIMSR4buZbmcgcGFja2FnZSBoMm8sIGNodXnhu4NuIGThuqFuZyBk4buvIGxp4buHdSwgdsOgIHZp4bq/dCBjb2RlIMSR4buDIGThu7FuZyBt4buZdCBtw7QgaMOsbmggUmFuZG9tIEZvcmVzdA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0UscmVzdWx0cz0iaGlkZSJ9DQojIGgyTw0KDQpsaWJyYXJ5KGgybykNCmgyby5pbml0KG50aHJlYWRzID0gLTEsbWF4X21lbV9zaXplID0iNGciKQ0KDQp3dHJhaW49YXMuaDJvKHRyYWluc2V0KQ0Kd3Rlc3Q9YXMuaDJvKHRlc3RzZXQpDQoNCnJlc3BvbnNlPSJTdXJ2aXZhbCINCmZlYXR1cmVzPXNldGRpZmYoY29sbmFtZXMod3RyYWluKSxyZXNwb25zZSkNCg0KcmZtb2Q9aDJvLnJhbmRvbUZvcmVzdCh4ID0gZmVhdHVyZXMsDQogICAgICAgICAgICAgICAgICAgICAgICB5ID0gcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHd0cmFpbixuZm9sZHM9MTAsDQogICAgICAgICAgICAgICAgICAgICAgICBmb2xkX2Fzc2lnbm1lbnQgPSAiU3RyYXRpZmllZCIsDQogICAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAyMDAsIG1heF9kZXB0aCA9IDUwLHNhbXBsZV9yYXRlPTAuNSxtdHJpZXM9NCwNCiAgICAgICAgICAgICAgICAgICAgICAgIGJhbGFuY2VfY2xhc3NlcyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfbWV0cmljID0gIm1lYW5fcGVyX2NsYXNzX2Vycm9yIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3RvbGVyYW5jZSA9IDAuMDAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgIGtlZXBfY3Jvc3NfdmFsaWRhdGlvbl9mb2xkX2Fzc2lnbm1lbnQgPSBGLA0KICAgICAgICAgICAgICAgICAgICAgICAga2VlcF9jcm9zc192YWxpZGF0aW9uX3ByZWRpY3Rpb25zPUYsDQogICAgICAgICAgICAgICAgICAgICAgICBzY29yZV9lYWNoX2l0ZXJhdGlvbiA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICBzZWVkPTEyMzQ1KQ0KDQpgYGANCg0KS2nhu4NtIHRyYSB0csOqbiB0cmFpbiBzdWJzZXQsIG3DtCBow6xuaCBob+G6oXQgxJHhu5luZyBy4bqldCB04buRdDoNCg0KYGBge3J9DQoNCmgyby5wZXJmb3JtYW5jZShyZm1vZCx3dHJhaW4pDQoNCmBgYA0KDQpUdXkgbmhpw6puOiBLaeG7g20gdHJhIG3DtCBow6xuaCBSRiB0csOqbiB0ZXN0c2V0IGNobyByYSBr4bq/dCBxdeG6oyBraMO0bmcgbeG6pXkga2jhuqMgcXVhbjpNw7QgaMOsbmggcGjhuqFtIHRpw6puIGzGsOG7o25nIHPhu5FuZyBzw7N0IHLhuqV0IGNow61uaCB4w6FjLCB0dXkgbmhpw6puIG7DsyBwaOG6oW0gbmhp4buBdSBzYWkgbOG6p20ga2hpIHRpw6puIGzGsOG7o25nIHThu60gdm9uZyAoNy84KQ0KDQpgYGB7cn0NCmgyby5wZXJmb3JtYW5jZShyZm1vZCx3dGVzdCkNCmBgYA0KDQojIFThuqFpIHNhbyB24bqteSBiw6FjIHPEqSA/DQoNCsSQ4bqndSB0acOqbiwgY2jDum5nIHRhIGfhu41pIHBhY2thZ2UgbGltZSwgc2F1IMSRw7Mgdmnhur90IDIgZnVuY3Rpb25zIG1vZGVsX3R5cGUuSDJPbW9kZWwgdsOgIHByZWRpY3RfbW9kZWwuSDJPbW9kZWwsIMSR4buDIHRow61jaCBuZ2hpIMSR4buLbmggZOG6oW5nIG91dHB1dCBj4bunYSBtw7QgaMOsbmggaDJPIHbhu5tpIGPhuqV1IHRyw7pjIGNodeG6qW4gYsOqbiB0cm9uZyBsaW1lOg0KDQpTYXUgxJHDsyB0YSBjw7MgdGjhu4MgdOG6oW8gb2JqZWN0IGV4cGxhaW5lciBjaOG7qWEgcGjDom4gcGjhu5FpIGPhu6dhIGZlYXR1cmVzIHThu6sgdHJhaW5pbmcgc2V0IHbDoCBtw7QgaMOsbmg6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSxyZXN1bHRzPSJoaWRlIn0NCg0KbGlicmFyeShsaW1lKQ0KDQojIEFkYXB0aW5nIGxpbWUgZnVuY3Rpb25zIHRvIGgyTyBmcmFtZXdvcmsNCg0KbW9kZWxfdHlwZS5IMk9Nb2RlbDwtIGZ1bmN0aW9uKHgsIC4uLikgImNsYXNzaWZpY2F0aW9uIg0KDQpwcmVkaWN0X21vZGVsLkgyT01vZGVsIDwtIGZ1bmN0aW9uKHgsIG5ld2RhdGEsIHR5cGUsIC4uLikgew0KICBwcmVkIDwtIGgyby5wcmVkaWN0KHgsIGFzLmgybyhuZXdkYXRhKSkNCiAgICByZXR1cm4oYXMuZGF0YS5mcmFtZShwcmVkWywtMV0pKQ0KfQ0KDQpzZXQuc2VlZCgxMjM0NSkNCmV4cGxhaW5lciA8LSBsaW1lKHRyYWluc2V0WywtMTddLCByZm1vZCwgYmluX2NvbnRpbnVvdXMgPSBGQUxTRSwgbl9iaW5zID0gMTAsIG5fcGVybXV0YXRpb25zID0gMTAwMDApDQoNCmBgYA0KDQpTYXUgxJHDsyB0YSB0w6FjaCByacOqbmcgcmEgMiBwaOG6p24gdGVzdHNldDogbmjhu69uZyBjYXNlIHThu60gdm9uZyB2w6Agbmjhu69uZyBjYXNlIHPhu5FuZyBzw7N0DQoNCmBgYHtyfQ0KZGVhZGRmPXN1YnNldCh0ZXN0c2V0LFN1cnZpdmFsIT0iU3Vydml2ZWQiKSU+JS5bLC0xN10NCg0Kc3VydmRmPXN1YnNldCh0ZXN0c2V0LFN1cnZpdmFsPT0iU3Vydml2ZWQiKSU+JS5bLC0xN10NCg0KYGBgDQoNClZp4buHYyBs4buxYSBjaOG7jW4gTSBmZWF0dXJlcyDEkcaw4bujYyB0aOG7sWMgaGnhu4duIGLhurFuZyBuaGnhu4F1IGPDoWNoLCBjw7MgdGjhu4MgIHTDuXkgY2jhu41uLCB0aMOtIGThu6UgDQo6DQpOb25lIDoga2jDtG5nIGzhu7FhIGNo4buNbiDGsHUgdGnDqm4sIG3DoCB0w61uaCB3ZWlnaHRzIHNjb3JlIGNobyB0b8OgbiBi4buZIGZlYXR1cmUgdHJvbmcgbW9kZWwsIGPDoWNoIG7DoHkga2jDtG5nIMSRxrDhu6NjIGtodXnhur9uIGtow61jaCBu4bq/dSBNPjEwDQoNCkZvd2FyZCBzZWxlY3Rpb24gKGhheSByaWRnZSByZWdyZXNzaW9uKSA6IE3DtCBow6xuaCB0dXnhur9uIHTDrW5oIMSRxrDhu6NjIGThu7FuZyB0aGVvIHJpZGdlIHJlZ3Jlc3Npb24sIGPDsyBraHV5bmggaMaw4bubbmcgZ2nhuqNtIHRoYW0gc+G7kSBo4buTaSBxdXkgduG7gSBn4bqnbiAwIHbDoCBsb+G6oWkgYuG7jyBuaOG7r25nIGZlYXR1cmUga2jDtG5nIHF1YW4gdHLhu41uZw0KDQpIaWdoZXN0IHdlaWdodHMgOiBDaOG7jW4gbSBmZWF0dXJlIHbhu5tpIGdpw6EgdHLhu4sgd2VpZ2h0IHR1eeG7h3QgxJHhu5FpIGNhbyBuaOG6pXQsIGThu7FhIHbDoG8gcmlkZ2UgcmVncmVzc2lvbg0KDQpMYXNzbyA6IEThu7FuZyBtw7QgaMOsbmggdHV54bq/biB0w61uaCBi4bqxbmcgcGjGsMahbmcgcGjDoXAgTGFzc28sIG7DsyBnacO6cCBsb+G6oWkgYuG7jyB0cmnhu4d0IMSR4buDIG5o4buvbmcgZmVhdHVyZSBraMO0bmcgY+G6p24gdGhp4bq/dCBi4bqxbmcgY8OhY2ggxJHhuql5IHdlaWdodCBzY29yZSB24buBIDAuDQoNClRyZWU6IGThu7FuZyBtw7QgaMOsbmggZGVjaXNpb24gdHJlZSDEkcaw4bujYyBwaMOibiBj4bqlcCBk4buxYSB2w6BvIGxvZzIoTSkgdOG7qyBnacOhIHRy4buLIE0gY2FvIG5o4bqldC4NCg0KQXV0bzogdOG7sSDEkeG7mW5nLCBsaW1lIGTDuW5nIHBoxrDGoW5nIHBow6FwIGZvcndhcmQgbuG6v3UgbTw9NiBob+G6t2MgaGlnaGVzdCB3ZWlnaHRzIG7hur91IG0+Ni4NCg0KxJDhuqd1IHRpw6puLCBjaMO6bmcgdGEgY2jhu41uIG5n4bqrdSBuaGnDqm4gMSBi4buHbmggbmjDom4gKHPhu5EgNDkpIHRyb25nIG5ow7NtIFPhu5FuZyBzw7N0LCB2w6AgZ2nhuqNpIHRow61jaCB04bqhaSBzYW8gbcO0IGjDrG5oIGzhuqFpIHRpw6puIGzGsOG7o25nIGLhu4duaCBuaMOibiBuw6B5IGzDoCDigJxTdXJ2aWVk4oCdID8NCg0KYGBge3J9DQpjYXNlUzE8LWV4cGxhaW4oc3VydmRmWzEyLF0sIGV4cGxhaW5lciwgbGFiZWxzPSJTdXJ2aXZlZCIsIG5fZmVhdHVyZXMgPTEwLGZlYXR1cmVfc2VsZWN0PSJhdXRvIikNCg0KcGxvdF9mZWF0dXJlcyhjYXNlUzEpDQoNCmBgYA0KDQpL4bq/dCBxdeG6oyBjaG8gdGjhuqV5OiBUcm9uZyAxMCBmZWF0dXJlcyBjw7Mg4bqjbmggaMaw4bufbmcgY2FvIG5o4bqldCDEkeG6v24gdGnDqm4gbMaw4bujbmc6IDcgdHJvbmcgc+G7kSDEkcOzIOG7p25nIGjhu5kgY2hvIGvhur90IHF14bqjIHPhu5FuZyBzw7N0OiBi4buHbmggbmjDom4ga2jDtG5nIGPDsyB0cmnhu4d1IGNo4bupbmcga2jDsyB0aOG7nywga2jDtG5nIGhvIHJhIG3DoXUsIGtow7RuZyBt4bqvYyBi4buHbmggdGnhu4N1IMSRxrDhu51uZyB0eXBlIDIsIEtow7RuZyDEkWF1IG5n4buxYywgZHVuZyB0w61jaCBwaOG7lWkgY2FvIGjGoW4gbmfGsOG7oW5nIG5ndXkgY8ahLiBUdXkgbmhpw6puLCBjw7MgMyBmZWF0dXJlIG3DonUgdGh14bqrbjogYuG7h25oIG5ow6JuIGPDsyB0cmnhu4d1IGNo4bupbmcgeeG6v3UgY8ahLCBjw7MgaMO6dCB0aHXhu5FjLCB2w6AgxJFp4buDbSBadWJyb2QNCg0KYGBge3J9DQpjYXNlUzE8LWV4cGxhaW4oc3VydmRmWzMwLF0sIGV4cGxhaW5lciwgbGFiZWxzPSJTdXJ2aXZlZCIsIG5fZmVhdHVyZXMgPTEwLGZlYXR1cmVfc2VsZWN0PSJhdXRvIikNCg0KcGxvdF9mZWF0dXJlcyhjYXNlUzEpDQoNCmBgYA0KDQrhu54gMSBi4buHbmggbmjDom4ga2jDoWMgKHPhu5EgMjczKSwgOS8xMCBmZWF0dXJlcyDhu6duZyBo4buZIGNobyBr4bq/dCBxdeG6oyB0acOqbiBsxrDhu6NuZyB04buRdCwgYmFvIGfhu5NtIHZp4buHYyBraMO0bmcgbeG6r3QgYuG7h25oIGhlbiwgY2jhu4kgY8OzIDEgY2hpIHRp4bq/dCBtw6J1IHRodeG6q24gbMOgICBi4buHbmggbmjDom4gY8OzIGhvIHJhIG3DoXUNCg0KQ2jDum5nIHRhIMSRw6MgdOG6oW0gZ2nhuqNpIHRow61jaCDEkcaw4bujYyB04bqhaSBzYW8gbcO0IGjDrG5oIHRpw6puIGzGsOG7o25nIHPhu5FuZyBzw7N0IMSRw7puZywgYsOieSBnaeG7nSB0YSB0aOG7rSBnaeG6o2kgdGjDrWNoIHThuqFpIHNhbyBuw7MgdGnDqm4gbMaw4bujbmcgdOG7rSB2b25nIFNBSSA/IENo4buJIGPDsyA4IGNhc2UgdOG7rSB2b25nIG7Dqm4gdGEgY2hpYSBsw6BtIDIgbmjDs20NCg0KYGBge3J9DQpkZWFkMTwtZXhwbGFpbihkZWFkZGZbYygxOjQpLF0sIGV4cGxhaW5lciwgbGFiZWxzPSJEZWFkIiwgbl9mZWF0dXJlcyA9MTAsZmVhdHVyZV9zZWxlY3Q9ImF1dG8iKQ0KDQpwbG90X2ZlYXR1cmVzKGRlYWQxKQ0KDQpgYGANCg0KYGBge3J9DQpkZWFkMjwtZXhwbGFpbihkZWFkZGZbYyg1OjgpLF0sIGV4cGxhaW5lciwgbGFiZWxzPSJEZWFkIiwgbl9mZWF0dXJlcyA9MTAsZmVhdHVyZV9zZWxlY3Q9ImF1dG8iKQ0KDQpwbG90X2ZlYXR1cmVzKGRlYWQyKQ0KDQpgYGANCg0KQ2jhu4kgY8OzIDIgY2FzZSDEkcaw4bujYyB0acOqbiBsxrDhu6NuZyBkxrDGoW5nIHTDrW5oIHRo4bqtdCwgY2jDrW5oIHjDoWMgKGLhu4duaCBuaMOibiBz4buRIDQxLCB4w6FjIHN14bqldCB04butIHZvbmcgPiA2MCUsIGLhu4duaCBuaMOibiAzNTEsIHjDoWMgc3XhuqV0IHThu60gdm9uZyA1NyUpOyBDw7JuIGzhuqFpIGvhur90IHF14bqjIHRpw6puIGzGsOG7o25nIGzDoCDDom0gdMOtbmggZ2nhuqMgKGLhu4duaCBuaMOibiB04butIHZvbmcgbmjGsG5nIG3DtCBow6xuaCBraMO0bmcgdGnDqm4gbMaw4bujbmcgxJHGsOG7o2MpLiBYZW0geMOpdCBuaOG7r25nIGNo4bupbmcgY+G7qSDhu6duZyBo4buZIHbDoCBtw6J1IHRodeG6q24gdHJvbmcgY+G6oyAyIHRyxrDhu51uZyBo4bujcCDEkeG7gXUgY8OzIMOtY2ggdsOgIGNobyBwaMOpcCBzdXkgbHXhuq1uIGPGoSBjaOG6vyBtw6AgbcO0IGjDrG5oIGhv4bqhdCDEkeG7mW5nOg0KDQpL4bq/dCBxdeG6oyBn4buJYWkgdGjDrWNoIGNobyB0aOG6pXkgOg0KDQrEkOG6p3UgdGnDqm47IMSR4bq3YyB0w61uaCBDw7MgaMO6dCB0aHXhu5FjIGzDoSBsw6AgbeG7mXQgY2jhu6luZyBj4bupIHF1YW4gdHLhu41uZywgbsOzIOG7p25nIGjhu5kgY2hvIHRpw6puIGzGsOG7o25nIHThu60gdm9uZyBt4buZdCBjw6FjaCBjw7MgbG9naWMuIEPhuqMgMiBi4buHbmggbmjDom4gZMawxqFuZyB0w61uaCB0aOG6rXQgxJHhu4F1IGPDsyBow7p0IHRodeG7kWMuIFR1eSBuaGnDqm4gMSBtw6xuaCBmZWF0dXJlIHNtb2tpbmcga2jDtG5nIMSR4bunIMSR4buDIGvDqW8ga+G6v3QgcXXhuqMgb3V0Y29tZSB0aGVvIGjGsOG7m25nIHBvc2l0aXZlLCB2w6wgbsOzIGLhu4sgY8OhYyB0cmnhu4d1IGNo4bupbmcga2jDoWMgZ8OieSBuaGnhu4V1Lg0KQ8OzIHbhursgbmjGsCBz4buxIHbhuq9uZyBt4bq3dCBjw6FjIHRyaeG7h3UgY2jhu6luZyBraMO0bmcgxJHhu6cgxJHhu4MgbG/huqFpIHRy4burIG5ndXkgY8ahIHThu60gdm9uZywgdHJvbmcgOCBi4buHbmggbmjDom4gdOG7rSB2b25nIGjhuqd1IGjhur90IGtow7RuZyBiaeG7g3UgaGnhu4duIHRyaeG7h3UgY2jhu6luZyBraMOzIHRo4bufLCB54bq/dSBjxqEsIGhheSBobyByYSBtw6F1LCBuaMawbmcgaOG7jSB24bqrbiDEkcOjIGNo4bq/dC4gR2nDoSB0cuG7iyBkdW5nIHTDrWNoIHBo4buVaSBjxaluZyBraMO0bmcgcGjhuqNpIGzDoCBmZWF0dXJlIGPDsyBnacOhIHRy4buLLiANCg0KTeG7mXQgY8OhY2ggZ2nhuqNpIHRow61jaCBraMOhYywgxJHDsyBsw6AgbcO0IGjDrG5oIFJGIMSRw6Mga2jDtG5nIHRow6BuaCBjw7RuZyB0cm9uZyB2aeG7h2MgcGjDom4gdMOtY2ggbmjhu69uZyB0cmnhu4d1IGNo4bupbmcgbsOgeS4NCg0KVmnhu4djIGdp4bqjaSBuZ2jEqWEgY2hvIGPDoSBuaMOibiBy4bqldCB0aMO6IHbhu4ssIHR1eSBuaGnDqm4gdGEgY8OybiBjw7MgdGjhu4MgdGnhur9wIGPhuq1uIHRoZW8gIGjGsOG7m25nIHThuq1wIHRo4buDLCB04bupYyBsw6AgZ2nhuqNpIG5naMSpYSBjaG8gdG/DoG4gYuG7mSB0ZXN0c2V0DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZXhwbGFuYXRpb24gPC0gZXhwbGFpbih0ZXN0c2V0WywtMTddLCBleHBsYWluZXIsIGxhYmVscz0iRGVhZCIsIG5fZmVhdHVyZXMgPSAxMCxmZWF0dXJlX3NlbGVjdD0iYXV0byIpDQoNCmBgYA0KDQpgYGB7cn0NCmV4ZGY9ZXhwbGFuYXRpb24lPiVhc190aWJibGUoKQ0KDQpleGRmJENsYXNzPWlmZWxzZShleGRmJGxhYmVsX3Byb2I+PTAuNSwiRGVhZCIsIlN1cnZpdmVkIikNCg0KZXhkZiU+JQ0KICBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihjYXNlLGZlYXR1cmVfd2VpZ2h0KSx5PWZlYXR1cmVfZGVzYyxmaWxsPWZlYXR1cmVfd2VpZ2h0LGNvbD1mZWF0dXJlX3dlaWdodCkpKw0KICBnZW9tX3RpbGUoc2hvdy5sZWdlbmQ9RikrDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdz0icmVkNCIsbWlkPSJ3aGl0ZSIsaGlnaD0iZ3JlZW40IixtaWRwb2ludCA9IDAuMCkrDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50Mihsb3c9InJlZCIsbWlkPSJncmV5IixoaWdoPSJncmVlbiIsbWlkcG9pbnQgPSAwLjApKw0KICB0aGVtZV9idygpKw0KICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCkpKw0KICBmYWNldF93cmFwKH5DbGFzcyxzaHJpbms9VCxzY2FsZT0iZnJlZSIpDQoNCmBgYA0KDQojIELDoG4gbHXhuq1uDQoNCkxpbWUgbMOgIG3hu5l0IHBoxrDGoW5nIHBow6FwIHLhuqV0IHRow7ogduG7iyB2w6AgaOG7r3Ugw61jaCwgbsOzIGNobyBwaMOpcCBjaMO6bmcgdGEgdHLhuqMgbOG7nWkgbmhp4buBdSBjw6J1IGjhu49pIHRo4buxYyB04bq/IGtoaSDDoXAgZOG7pW5nIG3hu5l0IG3DtCBow6xuaCBibGFjayBib3ggdHJvbmcgeSBo4buNYyBsw6JtIHPDoG5nLCB0aMOtIGThu6U6DQoNCjEpIFThuqFpIHNhbyBi4buHbmggbmjDom4gQSBs4bqhaSBuaOG6rW4ga+G6v3QgcXXhuqMgbmjGsCB0aOG6vyBuw6B5ID8gKEjDo3kgY2hvIHTDtGkgYmnhur90IG3DtCBow6xuaCBj4bunYSBi4bqhbiDEkcOjIGhv4bqhdCDEkeG7mW5nIHRoZW8gY8ahIGNo4bq/IG7DoG8gdHLDqm4gYuG7h25oIG5ow6JuIG7DoHkgPykNCg0KMikgVOG6oWkgc2FvIG3DtCBow6xuaCBj4bunYSBi4bqhbiBwaOG6oW0gc2FpIGzhuqdtIHRyb25nIHRyxrDhu51uZyBo4bujcCBuw6B5ID8gxJBp4buBdSBnw6wgxJHDoyB44bqjeSByYSBiw6puIHRyb25nIG7DsyBk4bqrbiB04bubaSBzYWkgbOG6p20gbsOgeSA/DQoNCkPDonUgdHLhuqMgbOG7nWkgY2hvIGPDonUgaOG7j2kgdGjhu6kgbmjhuqV0IGPDsyB0aOG7gyBnaeG6o2kgdOG7j2EgaG/DoGkgbmdoaSBj4bunYSBiw6FjIHPEqSB2w6AgYuG7h25oIG5ow6JuLCBraGnhur9uIGjhu40gdGluIHTGsOG7n25nIHbDoG8gbcO0IGjDrG5oIE1hY2hpbmUgbGVhcm5pbmcgaMahbi4gQ8OidSBo4buPaSB0aOG7qSAyLCBt4bq3dCBraMOhYywgY8OzIHRo4buDIGdpw7pwIGNow7puZyB0YSBwaMOhdCBoaeG7h24gbmjGsOG7o2MgxJFp4buDbSBj4bunYSBtw7QgaMOsbmggxJHhu4MgY+G6o2kgdGhp4buHbiBuw7MuDQoNCg0K