Rancangan model Klasifikasi untuk memprediksi apakah kanker payudara termasuk tumor ganas atau tumor jinak menggunakan dataset default yang bisa dimuat dari R yaitu “BreastCancer”. Metode klasifikasi yang digunakan adalah Naive Bayes Classifier, Random Forest Classifier dan Decision Tree Classifier. Melalui rancangan ini diharapkan memberikan hasil dari 3 metode klasifikasi tersebut sehingga menghasilkan metode klasifikasi terbaik yang dapat diimplementasikan.

Memuat Data

Masukkan Paket yang berisi dataset BreastCancer

library(mlbench)

Memuat Dataset dari BeastCancer

data("BreastCancer")

Mengeksplor Dataset

Kita akan melihat struktur dari dataset BreastCanser, Terdapat 10 Variabel dengan jumlah total observasinya 699.

str(BreastCancer)
'data.frame':   699 obs. of  11 variables:
 $ Id             : chr  "1000025" "1002945" "1015425" "1016277" ...
 $ Cl.thickness   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 5 5 3 6 4 8 1 2 2 4 ...
 $ Cell.size      : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 4 1 8 1 10 1 1 1 2 ...
 $ Cell.shape     : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 4 1 8 1 10 1 2 1 1 ...
 $ Marg.adhesion  : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 5 1 1 3 8 1 1 1 1 ...
 $ Epith.c.size   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 2 7 2 3 2 7 2 2 2 2 ...
 $ Bare.nuclei    : Factor w/ 10 levels "1","2","3","4",..: 1 10 2 4 1 10 10 1 1 1 ...
 $ Bl.cromatin    : Factor w/ 10 levels "1","2","3","4",..: 3 3 3 3 3 9 3 3 1 2 ...
 $ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 1 2 1 7 1 7 1 1 1 1 ...
 $ Mitoses        : Factor w/ 9 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 5 1 ...
 $ Class          : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 2 1 1 1 1 ...

Setelah itu kita akan melihat level dari variabel Class untuk mengetahui berapa jenis tumor yang ada di dataset.

levels(BreastCancer$Class)
[1] "benign"    "malignant"

untuk melihat lebih jelas dan lebih detail mengenai dataset BreastCanser, bisa menggunakan fungsi summary()

summary(BreastCancer)
      Id             Cl.thickness   Cell.size     Cell.shape  Marg.adhesion  Epith.c.size  Bare.nuclei   Bl.cromatin 
 Length:699         1      :145   1      :384   1      :353   1      :407   2      :386   1      :402   2      :166  
 Class :character   5      :130   10     : 67   2      : 59   2      : 58   3      : 72   10     :132   3      :165  
 Mode  :character   3      :108   3      : 52   10     : 58   3      : 58   4      : 48   2      : 30   1      :152  
                    4      : 80   2      : 45   3      : 56   10     : 55   1      : 47   5      : 30   7      : 73  
                    10     : 69   4      : 40   4      : 44   4      : 33   6      : 41   3      : 28   4      : 40  
                    2      : 50   5      : 30   5      : 34   8      : 25   5      : 39   (Other): 61   5      : 34  
                    (Other):117   (Other): 81   (Other): 95   (Other): 63   (Other): 66   NA's   : 16   (Other): 69  
 Normal.nucleoli    Mitoses          Class    
 1      :443     1      :579   benign   :458  
 10     : 61     2      : 35   malignant:241  
 3      : 44     3      : 33                  
 2      : 36     10     : 14                  
 8      : 24     4      : 12                  
 6      : 22     7      :  9                  
 (Other): 69     (Other): 17                  

Dari hasil summary kita bisa melihat jumlah NA atau bisa disebut nilai yang hilang yakni sejumlah 16

Pembersihan Data

Nilai yang hilang adalah masalah yang sering terjadi dari kumpulan data. Untuk kasus ini nilai yang hilang bisa diatasi dengan menggunakan library mice untuk mengatasi 16 nilai yang hilang dengan memasukkan nilai yang hilang dengan nilai yang paling sesuai dengan mempertimbangkan kesembilan kolom lainnya dalam dataset.

Kolom Id disaring karena tidak diperlukan untuk mendesain pengklasifikasian.

library(mice) #library untuk mengatasi nilai yang hilang
library(caret) #library untuk training dan ploting model
dataset_impute <- mice(BreastCancer[,2:10],  print = FALSE) #Menghapus nilai yang hilang dan ID dari dataset 
BreastCancer <- cbind(BreastCancer[,11, drop = FALSE], mice::complete(dataset_impute, 1)) # Menambahkan kelas target ke dataset yang diperhitungkan tanpa nilai yang hilang

Kemudian cek kembali dataset yang sudah diperbaiki

summary(BreastCancer)
       Class      Cl.thickness   Cell.size     Cell.shape  Marg.adhesion  Epith.c.size  Bare.nuclei   Bl.cromatin 
 benign   :458   1      :145   1      :384   1      :353   1      :407   2      :386   1      :412   2      :166  
 malignant:241   5      :130   10     : 67   2      : 59   2      : 58   3      : 72   10     :133   3      :165  
                 3      :108   3      : 52   10     : 58   3      : 58   4      : 48   2      : 31   1      :152  
                 4      : 80   2      : 45   3      : 56   10     : 55   1      : 47   5      : 30   7      : 73  
                 10     : 69   4      : 40   4      : 44   4      : 33   6      : 41   3      : 29   4      : 40  
                 2      : 50   5      : 30   5      : 34   8      : 25   5      : 39   8      : 21   5      : 34  
                 (Other):117   (Other): 81   (Other): 95   (Other): 63   (Other): 66   (Other): 43   (Other): 69  
 Normal.nucleoli    Mitoses   
 1      :443     1      :579  
 10     : 61     2      : 35  
 3      : 44     3      : 33  
 2      : 36     10     : 14  
 8      : 24     4      : 12  
 6      : 22     7      :  9  
 (Other): 69     (Other): 17  

Membagi Dataset kedalam bentuk data training, data testing, dan data untuk prediksi

Install terlebih dahulu library caTools agar bisa digunakan

library(caTools) #library untuk pembagian dataset
set.seed(150)    
split=sample.split(BreastCancer, SplitRatio = 0.7)  # Membagi Dataset menjadi data training dan data testing
training_set=subset(BreastCancer,split==TRUE)       # Dataset Training
test_set=subset(BreastCancer,split==FALSE)          # Datset Testing

Kemudian kita akan melihat dimensi dari Data Training yang sudah dibuat

dim(training_set)                                   # Dimensi data training
[1] 490  10

Untuk melihat dimensi dari Data Testing yang sudah dibuat, seperti berikut

dim(test_set)  
[1] 209  10

Untuk melihat data yang digunakan untuk prediksi adalah dengan menghapus variabel kedua dari dataset, yakni variabel Class

topredict_set<-test_set[2:10]                       # Menghapus Target Class
dim(topredict_set)
[1] 209   9

Setelah semua dataset sudah disiapkan, maka langkah selanjutnya adalah mendesain model klasifikasi menggunakan algoritma yang berbeda untuk membandingkan keakuratan dari model algoritma dengan dataset BreastCancer.

Naive Bayes Classifier

Sekarang kita masuk pada Naive Bayes. Naive Bayes classifier merupakan salah satu metoda Machine Learning yang memanfaatkan perhitungan probabilitas dan statistik, yaitu memprediksi probabilitas pada masa depan berdasarkan pengalaman pada masa sebelumnya.

Untuk menggunakan Naive Bayes di Rstudo terlebih dahulu install Paket e1071, kemudian muat librarynya.

library(e1071)
model_naive <- naiveBayes(Class ~ ., data = training_set)           #Implementasi Naive Bayes
preds_naive <- predict(model_naive, newdata = topredict_set)        #Memprediksi Target Class untuk validasi 

(conf_matrix_naive <- table(preds_naive, test_set$Class))   
           
preds_naive benign malignant
  benign       129         2
  malignant      6        72

Dari hasil Confusion Matrix menunjukkan bahwa pengklasifikasian menggunakan Naive Bayes memprediksi 129 kasus benign / jinak dengan benar dan dua prediksi salah. Demikian pula, Naive Bayes memprediksi 72 kasus malignant / ganas dengan benar dan 6 prediksi salah.

Berikut adalah script untuk mengetahui akurasi dari Metode Klasifikasi Naive Bayes

confusionMatrix(conf_matrix_naive)  
Confusion Matrix and Statistics

           
preds_naive benign malignant
  benign       129         2
  malignant      6        72
                                         
               Accuracy : 0.9617         
                 95% CI : (0.926, 0.9833)
    No Information Rate : 0.6459         
    P-Value [Acc > NIR] : <2e-16         
                                         
                  Kappa : 0.9173         
                                         
 Mcnemar's Test P-Value : 0.2888         
                                         
            Sensitivity : 0.9556         
            Specificity : 0.9730         
         Pos Pred Value : 0.9847         
         Neg Pred Value : 0.9231         
             Prevalence : 0.6459         
         Detection Rate : 0.6172         
   Detection Prevalence : 0.6268         
      Balanced Accuracy : 0.9643         
                                         
       'Positive' Class : benign         
                                         

Hasil menunjukkan untuk keakuratan motode Naive Bayes untuk memprediksi Kanker Payudara sebesar 96.17%

Random Forest Classifier

Berikutnya kita akan menggunakan metode Random Forest. Random forest adalah suatu algoritma yang digunakan pada klasifikasi data dalam jumlah yang besar. Klasifikasi random forest dilakukan melalui penggabungan pohon dengan melakukan training pada sampel data yang dimiliki.

Untuk menggunakan fungsi randomForest install terlabih dahulu paket randomForest kemudian muat librarynya.

library(randomForest)
model_rf <- randomForest(Class ~ ., data = training_set, importance=TRUE, ntree = 5) # Implementasi Random Forest

preds_rf <- predict(model_rf, topredict_set)              

(conf_matrix_forest <- table(preds_rf, test_set$Class))
           
preds_rf    benign malignant
  benign       125         2
  malignant     10        72

Dari hasil Confusion Matrix menunjukkan bahwa pengklasifikasian menggunakan Random Forest memprediksi 125 kasus benign / jinak dengan benar dan dua prediksi salah. Demikian pula, Random Forest memprediksi 72 kasus malignant / ganas dengan benar dan 10 prediksi salah.

Berikut adalah script untuk mengetahui akurasi dari Metode Klasifikasi Random Forest.

confusionMatrix(conf_matrix_forest)  
Confusion Matrix and Statistics

           
preds_rf    benign malignant
  benign       125         2
  malignant     10        72
                                        
               Accuracy : 0.9426        
                 95% CI : (0.9019, 0.97)
    No Information Rate : 0.6459        
    P-Value [Acc > NIR] : < 2e-16       
                                        
                  Kappa : 0.8775        
                                        
 Mcnemar's Test P-Value : 0.04331       
                                        
            Sensitivity : 0.9259        
            Specificity : 0.9730        
         Pos Pred Value : 0.9843        
         Neg Pred Value : 0.8780        
             Prevalence : 0.6459        
         Detection Rate : 0.5981        
   Detection Prevalence : 0.6077        
      Balanced Accuracy : 0.9494        
                                        
       'Positive' Class : benign        
                                        

Hasil menunjukkan untuk keakuratan motode Random Forest untuk memprediksi Kanker Payudara sebesar 94.26%

DecisionTree Classifier

Metode selanjutnya yang akan kita gunakan adalah metode DecisionTree. Decision Tree atau bisa disebut dengan pohon keputusan merupakan adalah salah satu pendekatan pemodelan prediktif yang digunakan dalam statistik, penambangan data, dan pembelajaran mesin. Penggunaan pohon keputusan untuk beralih dari pengamatan tentang suatu item ke kesimpulan tentang nilai target item tersebut.

Fungsi rpart digunakan untuk menghitung Decision Tree. install terlabih dahulu paket rpart kemudian muat librarynya.

library(rpart)
model_dtree<- rpart(Class ~ ., data=training_set)       #Implementing Decision Tree
preds_dtree <- predict(model_dtree,newdata=topredict_set, type = "class")
#plot(preds_dtree, main="Decision tree created using rpart")
(conf_matrix_dtree <- table(preds_dtree, test_set$Class))
           
preds_dtree benign malignant
  benign       127         5
  malignant      8        69

Dari hasil DesicionTree menunjukkan bahwa pengklasifikasian menggunakan DecisionTree memprediksi 127 kasus benign / jinak dengan benar dan 5 prediksi salah. Demikian pula, DecisionTree memprediksi 69 kasus malignant / ganas dengan benar dan 8 prediksi salah.

Mari kita lihat keakuratan metode DecisionTree

confusionMatrix(conf_matrix_dtree)   
Confusion Matrix and Statistics

           
preds_dtree benign malignant
  benign       127         5
  malignant      8        69
                                         
               Accuracy : 0.9378         
                 95% CI : (0.896, 0.9665)
    No Information Rate : 0.6459         
    P-Value [Acc > NIR] : <2e-16         
                                         
                  Kappa : 0.8652         
                                         
 Mcnemar's Test P-Value : 0.5791         
                                         
            Sensitivity : 0.9407         
            Specificity : 0.9324         
         Pos Pred Value : 0.9621         
         Neg Pred Value : 0.8961         
             Prevalence : 0.6459         
         Detection Rate : 0.6077         
   Detection Prevalence : 0.6316         
      Balanced Accuracy : 0.9366         
                                         
       'Positive' Class : benign         
                                         

Hasil menunjukkan untuk keakuratan motode DecisionTree untuk memprediksi Kanker Payudara sebesar 93.78%

Kesimpulan yang bisa diambil dari hasil perbandingan 3 Metode Klasifikasi dalam memprediksi Kanker Payudara berdasarkan dataset default dari BreastCancer adalah metode Naive Bayes lebih baik dalam memprediksi Kanker Payudara dengan akurasi perhitungannya sebesar 96.17%.

LS0tDQp0aXRsZSA6ICJNZXRvZGUgS2xhc2lmaWthc2kgTmFpdmUgQmF5ZXMsIFJhbmRvbSBGb3Jlc3QgZGFuIERlY2ljaW9uIFRyZWUgdW50dWsgTWVtcHJlZGlrc2kgS2Fua2VyIFBheXVkYXJhIE1lbmdndW5ha2FuIFJzdHVkaW8iDQphdXRob3I6ICJQcm9mLiBEciBTdWhhcnRvbm8gTS5Lb20gJiBVJ3VuIFNldGlhd2F0aSwgUy5Lb20iDQpkYXRlICA6ICIxMi8xLzIwMjEiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIHRoZW1lOiBzcGFjZWxhYg0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMg0KICAgIHRvY19mbG9hdDogdHJ1ZQ0Kc3VidGl0bGU6IE1hZ2lzdGVyIEluZm9ybWF0aWthIFVJTiBNYXVsYW5hIE1hbGlrIElicmFoaW0gTWFsYW5nIA0KLS0tDQoNCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCmJvZHl7IC8qIE5vcm1hbCAgKi8NCiAgICAgIGZvbnQtc2l6ZTogMTRweDsNCiAgfQ0KdGQgeyAgLyogVGFibGUgICovDQogIGZvbnQtc2l6ZTogMTJweDsNCn0NCmgxLnRpdGxlIHsNCiAgZm9udC1zaXplOiAzOHB4Ow0KICBjb2xvcjogbGlnaHRibHVlOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgxIHsgLyogSGVhZGVyIDEgKi8NCiAgZm9udC1zaXplOiAyNHB4Ow0KICBjb2xvcjogRGFya0JsdWU7DQp9DQpoMiB7IC8qIEhlYWRlciAyICovDQogIGZvbnQtc2l6ZTogMjBweDsNCiAgY29sb3I6IERhcmtCbHVlOw0KfQ0KaDMgeyAvKiBIZWFkZXIgMyAqLw0KICBmb250LXNpemU6IDE2cHg7DQojICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgY29sb3I6IERhcmtCbHVlOw0KfQ0KaDQgeyAvKiBIZWFkZXIgNCAqLw0KICBmb250LXNpemU6IDE0cHg7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCn0NCmNvZGUucnsgLyogQ29kZSBibG9jayAqLw0KICAgIGZvbnQtc2l6ZTogMTJweDsNCn0NCnByZSB7IC8qIENvZGUgYmxvY2sgLSBkZXRlcm1pbmVzIGNvZGUgc3BhY2luZyBiZXR3ZWVuIGxpbmVzICovDQogICAgZm9udC1zaXplOiAxMnB4Ow0KfQ0KPC9zdHlsZT4NCi0tLQ0KDQpSYW5jYW5nYW4gbW9kZWwgS2xhc2lmaWthc2kgdW50dWsgbWVtcHJlZGlrc2kgYXBha2FoIGthbmtlciBwYXl1ZGFyYSB0ZXJtYXN1ayAgdHVtb3IgZ2FuYXMgYXRhdSB0dW1vciBqaW5hayBtZW5nZ3VuYWthbiBkYXRhc2V0IGRlZmF1bHQgeWFuZyBiaXNhIGRpbXVhdCBkYXJpIFIgeWFpdHUg4oCcQnJlYXN0Q2FuY2Vy4oCdLiBNZXRvZGUga2xhc2lmaWthc2kgeWFuZyBkaWd1bmFrYW4gYWRhbGFoIE5haXZlIEJheWVzIENsYXNzaWZpZXIsIFJhbmRvbSBGb3Jlc3QgQ2xhc3NpZmllciBkYW4gRGVjaXNpb24gVHJlZSBDbGFzc2lmaWVyLiBNZWxhbHVpIHJhbmNhbmdhbiBpbmkgZGloYXJhcGthbiBtZW1iZXJpa2FuIGhhc2lsIGRhcmkgMyBtZXRvZGUga2xhc2lmaWthc2kgdGVyc2VidXQgc2VoaW5nZ2EgbWVuZ2hhc2lsa2FuIG1ldG9kZSBrbGFzaWZpa2FzaSB0ZXJiYWlrIHlhbmcgZGFwYXQgZGlpbXBsZW1lbnRhc2lrYW4uDQoNCiMgTWVtdWF0IERhdGENCg0KTWFzdWtrYW4gUGFrZXQgeWFuZyBiZXJpc2kgZGF0YXNldCBgQnJlYXN0Q2FuY2VyYA0KDQpgYGB7cn0NCmxpYnJhcnkobWxiZW5jaCkNCmBgYA0KDQpNZW11YXQgRGF0YXNldCBkYXJpIGBCZWFzdENhbmNlcmANCg0KYGBge3J9DQpkYXRhKCJCcmVhc3RDYW5jZXIiKQ0KYGBgDQoNCiMgTWVuZ2Vrc3Bsb3IgRGF0YXNldA0KDQpLaXRhIGFrYW4gbWVsaWhhdCBzdHJ1a3R1ciBkYXJpIGRhdGFzZXQgYEJyZWFzdENhbnNlcmAsIFRlcmRhcGF0IDEwIFZhcmlhYmVsIGRlbmdhbiBqdW1sYWggdG90YWwgb2JzZXJ2YXNpbnlhIDY5OS4NCmBgYHtyfQ0Kc3RyKEJyZWFzdENhbmNlcikNCmBgYA0KU2V0ZWxhaCBpdHUga2l0YSBha2FuIG1lbGloYXQgbGV2ZWwgZGFyaSB2YXJpYWJlbCBDbGFzcyB1bnR1ayBtZW5nZXRhaHVpIGJlcmFwYSBqZW5pcyB0dW1vciB5YW5nIGFkYSBkaSBkYXRhc2V0Lg0KYGBge3J9DQpsZXZlbHMoQnJlYXN0Q2FuY2VyJENsYXNzKQ0KYGBgDQp1bnR1ayBtZWxpaGF0IGxlYmloIGplbGFzIGRhbiBsZWJpaCBkZXRhaWwgbWVuZ2VuYWkgZGF0YXNldCBCcmVhc3RDYW5zZXIsIGJpc2EgbWVuZ2d1bmFrYW4gZnVuZ3NpIGBzdW1tYXJ5KClgDQoNCmBgYHtyfQ0Kc3VtbWFyeShCcmVhc3RDYW5jZXIpDQpgYGANCkRhcmkgaGFzaWwgc3VtbWFyeSBraXRhIGJpc2EgbWVsaWhhdCBqdW1sYWggYE5BYCBhdGF1IGJpc2EgZGlzZWJ1dCBuaWxhaSB5YW5nIGhpbGFuZyB5YWtuaSBzZWp1bWxhaCAxNiANCg0KIyBQZW1iZXJzaWhhbiBEYXRhDQoNCk5pbGFpIHlhbmcgaGlsYW5nIGFkYWxhaCBtYXNhbGFoIHlhbmcgc2VyaW5nIHRlcmphZGkgZGFyaSBrdW1wdWxhbiBkYXRhLiBVbnR1ayBrYXN1cyBpbmkgbmlsYWkgeWFuZyBoaWxhbmcgYmlzYSBkaWF0YXNpIGRlbmdhbiBtZW5nZ3VuYWthbiBsaWJyYXJ5IGBtaWNlYCB1bnR1ayBtZW5nYXRhc2kgMTYgbmlsYWkgeWFuZyBoaWxhbmcgZGVuZ2FuIG1lbWFzdWtrYW4gbmlsYWkgeWFuZyBoaWxhbmcgZGVuZ2FuIG5pbGFpIHlhbmcgcGFsaW5nIHNlc3VhaSBkZW5nYW4gbWVtcGVydGltYmFuZ2thbiBrZXNlbWJpbGFuIGtvbG9tIGxhaW5ueWEgZGFsYW0gZGF0YXNldC4NCg0KS29sb20gSWQgZGlzYXJpbmcga2FyZW5hIHRpZGFrIGRpcGVybHVrYW4gdW50dWsgbWVuZGVzYWluIHBlbmdrbGFzaWZpa2FzaWFuLg0KYGBge3J9DQpsaWJyYXJ5KG1pY2UpICNsaWJyYXJ5IHVudHVrIG1lbmdhdGFzaSBuaWxhaSB5YW5nIGhpbGFuZw0KbGlicmFyeShjYXJldCkgI2xpYnJhcnkgdW50dWsgdHJhaW5pbmcgZGFuIHBsb3RpbmcgbW9kZWwNCmRhdGFzZXRfaW1wdXRlIDwtIG1pY2UoQnJlYXN0Q2FuY2VyWywyOjEwXSwgIHByaW50ID0gRkFMU0UpICNNZW5naGFwdXMgbmlsYWkgeWFuZyBoaWxhbmcgZGFuIElEIGRhcmkgZGF0YXNldCANCkJyZWFzdENhbmNlciA8LSBjYmluZChCcmVhc3RDYW5jZXJbLDExLCBkcm9wID0gRkFMU0VdLCBtaWNlOjpjb21wbGV0ZShkYXRhc2V0X2ltcHV0ZSwgMSkpICMgTWVuYW1iYWhrYW4ga2VsYXMgdGFyZ2V0IGtlIGRhdGFzZXQgeWFuZyBkaXBlcmhpdHVuZ2thbiB0YW5wYSBuaWxhaSB5YW5nIGhpbGFuZw0KYGBgDQoNCktlbXVkaWFuIGNlayBrZW1iYWxpIGRhdGFzZXQgeWFuZyBzdWRhaCBkaXBlcmJhaWtpDQoNCmBgYHtyfQ0Kc3VtbWFyeShCcmVhc3RDYW5jZXIpDQpgYGANCg0KIyBNZW1iYWdpIERhdGFzZXQga2VkYWxhbSBiZW50dWsgZGF0YSB0cmFpbmluZywgZGF0YSB0ZXN0aW5nLCBkYW4gZGF0YSB1bnR1ayBwcmVkaWtzaSANCg0KSW5zdGFsbCB0ZXJsZWJpaCBkYWh1bHUgbGlicmFyeSBgY2FUb29sc2AgYWdhciBiaXNhIGRpZ3VuYWthbg0KYGBge3J9DQpsaWJyYXJ5KGNhVG9vbHMpICNsaWJyYXJ5IHVudHVrIHBlbWJhZ2lhbiBkYXRhc2V0DQpzZXQuc2VlZCgxNTApICAgIA0Kc3BsaXQ9c2FtcGxlLnNwbGl0KEJyZWFzdENhbmNlciwgU3BsaXRSYXRpbyA9IDAuNykgICMgTWVtYmFnaSBEYXRhc2V0IG1lbmphZGkgZGF0YSB0cmFpbmluZyBkYW4gZGF0YSB0ZXN0aW5nDQp0cmFpbmluZ19zZXQ9c3Vic2V0KEJyZWFzdENhbmNlcixzcGxpdD09VFJVRSkgICAgICAgIyBEYXRhc2V0IFRyYWluaW5nDQp0ZXN0X3NldD1zdWJzZXQoQnJlYXN0Q2FuY2VyLHNwbGl0PT1GQUxTRSkgICAgICAgICAgIyBEYXRzZXQgVGVzdGluZw0KYGBgDQoNCktlbXVkaWFuIGtpdGEgYWthbiBtZWxpaGF0IGRpbWVuc2kgZGFyaSBgRGF0YSBUcmFpbmluZ2AgeWFuZyBzdWRhaCBkaWJ1YXQNCg0KYGBge3J9DQpkaW0odHJhaW5pbmdfc2V0KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEaW1lbnNpIGRhdGEgdHJhaW5pbmcNCmBgYA0KVW50dWsgbWVsaWhhdCBkaW1lbnNpIGRhcmkgYERhdGEgVGVzdGluZ2AgeWFuZyBzdWRhaCBkaWJ1YXQsIHNlcGVydGkgYmVyaWt1dA0KDQpgYGB7cn0NCmRpbSh0ZXN0X3NldCkgIA0KYGBgDQpVbnR1ayBtZWxpaGF0IGRhdGEgeWFuZyBkaWd1bmFrYW4gdW50dWsgcHJlZGlrc2kgYWRhbGFoIGRlbmdhbiBtZW5naGFwdXMgdmFyaWFiZWwga2VkdWEgZGFyaSBkYXRhc2V0LCB5YWtuaSB2YXJpYWJlbCBDbGFzcw0KYGBge3J9DQp0b3ByZWRpY3Rfc2V0PC10ZXN0X3NldFsyOjEwXSAgICAgICAgICAgICAgICAgICAgICAgIyBNZW5naGFwdXMgVGFyZ2V0IENsYXNzDQpkaW0odG9wcmVkaWN0X3NldCkNCmBgYA0KU2V0ZWxhaCBzZW11YSBkYXRhc2V0IHN1ZGFoIGRpc2lhcGthbiwgbWFrYSBsYW5na2FoIHNlbGFuanV0bnlhIGFkYWxhaCBtZW5kZXNhaW4gbW9kZWwga2xhc2lmaWthc2kgbWVuZ2d1bmFrYW4gYWxnb3JpdG1hIHlhbmcgYmVyYmVkYSB1bnR1ayBtZW1iYW5kaW5na2FuIGtlYWt1cmF0YW4gZGFyaSBtb2RlbCBhbGdvcml0bWEgZGVuZ2FuIGRhdGFzZXQgYEJyZWFzdENhbmNlcmAuDQoNCiMgTmFpdmUgQmF5ZXMgQ2xhc3NpZmllcg0KDQpTZWthcmFuZyBraXRhIG1hc3VrIHBhZGEgTmFpdmUgQmF5ZXMuIE5haXZlIEJheWVzIGNsYXNzaWZpZXIgbWVydXBha2FuIHNhbGFoIHNhdHUgbWV0b2RhIE1hY2hpbmUgTGVhcm5pbmcgeWFuZyBtZW1hbmZhYXRrYW4gcGVyaGl0dW5nYW4gcHJvYmFiaWxpdGFzIGRhbiBzdGF0aXN0aWssIHlhaXR1IG1lbXByZWRpa3NpIHByb2JhYmlsaXRhcyBwYWRhIG1hc2EgZGVwYW4gYmVyZGFzYXJrYW4gcGVuZ2FsYW1hbiBwYWRhIG1hc2Egc2ViZWx1bW55YS4NCg0KVW50dWsgbWVuZ2d1bmFrYW4gTmFpdmUgQmF5ZXMgZGkgUnN0dWRvIHRlcmxlYmloIGRhaHVsdSBpbnN0YWxsIFBha2V0IGBlMTA3MWAsIGtlbXVkaWFuIG11YXQgbGlicmFyeW55YS4NCg0KYGBge3J9DQpsaWJyYXJ5KGUxMDcxKQ0KbW9kZWxfbmFpdmUgPC0gbmFpdmVCYXllcyhDbGFzcyB+IC4sIGRhdGEgPSB0cmFpbmluZ19zZXQpICAgICAgICAgICAjSW1wbGVtZW50YXNpIE5haXZlIEJheWVzDQpwcmVkc19uYWl2ZSA8LSBwcmVkaWN0KG1vZGVsX25haXZlLCBuZXdkYXRhID0gdG9wcmVkaWN0X3NldCkgICAgICAgICNNZW1wcmVkaWtzaSBUYXJnZXQgQ2xhc3MgdW50dWsgdmFsaWRhc2kgDQoNCihjb25mX21hdHJpeF9uYWl2ZSA8LSB0YWJsZShwcmVkc19uYWl2ZSwgdGVzdF9zZXQkQ2xhc3MpKSAgIA0KYGBgDQpEYXJpIGhhc2lsIENvbmZ1c2lvbiBNYXRyaXggbWVudW5qdWtrYW4gYmFod2EgcGVuZ2tsYXNpZmlrYXNpYW4gbWVuZ2d1bmFrYW4gTmFpdmUgQmF5ZXMgbWVtcHJlZGlrc2kgMTI5IGthc3VzIGJlbmlnbiAvIGppbmFrIGRlbmdhbiBiZW5hciBkYW4gZHVhIHByZWRpa3NpIHNhbGFoLiBEZW1pa2lhbiBwdWxhLCBOYWl2ZSBCYXllcyBtZW1wcmVkaWtzaSA3MiBrYXN1cyBtYWxpZ25hbnQgLyBnYW5hcyBkZW5nYW4gYmVuYXIgZGFuIDYgcHJlZGlrc2kgc2FsYWguDQoNCkJlcmlrdXQgYWRhbGFoIHNjcmlwdCB1bnR1ayBtZW5nZXRhaHVpIGFrdXJhc2kgZGFyaSBNZXRvZGUgS2xhc2lmaWthc2kgTmFpdmUgQmF5ZXMNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgoY29uZl9tYXRyaXhfbmFpdmUpICANCmBgYA0KSGFzaWwgbWVudW5qdWtrYW4gdW50dWsga2Vha3VyYXRhbiBtb3RvZGUgTmFpdmUgQmF5ZXMgdW50dWsgbWVtcHJlZGlrc2kgS2Fua2VyIFBheXVkYXJhIHNlYmVzYXIgOTYuMTclDQoNCiMgUmFuZG9tIEZvcmVzdCBDbGFzc2lmaWVyDQoNCkJlcmlrdXRueWEga2l0YSBha2FuIG1lbmdndW5ha2FuIG1ldG9kZSBSYW5kb20gRm9yZXN0LiBSYW5kb20gZm9yZXN0IGFkYWxhaCBzdWF0dSBhbGdvcml0bWEgeWFuZyBkaWd1bmFrYW4gcGFkYSBrbGFzaWZpa2FzaSBkYXRhIGRhbGFtIGp1bWxhaCB5YW5nIGJlc2FyLiBLbGFzaWZpa2FzaSByYW5kb20gZm9yZXN0IGRpbGFrdWthbiBtZWxhbHVpIHBlbmdnYWJ1bmdhbiBwb2hvbiBkZW5nYW4gbWVsYWt1a2FuIHRyYWluaW5nIHBhZGEgc2FtcGVsIGRhdGEgeWFuZyBkaW1pbGlraS4NCg0KVW50dWsgbWVuZ2d1bmFrYW4gZnVuZ3NpIGByYW5kb21Gb3Jlc3RgIGluc3RhbGwgdGVybGFiaWggZGFodWx1IHBha2V0IHJhbmRvbUZvcmVzdCBrZW11ZGlhbiBtdWF0IGxpYnJhcnlueWEuDQoNCmBgYHtyfQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQptb2RlbF9yZiA8LSByYW5kb21Gb3Jlc3QoQ2xhc3MgfiAuLCBkYXRhID0gdHJhaW5pbmdfc2V0LCBpbXBvcnRhbmNlPVRSVUUsIG50cmVlID0gNSkgIyBJbXBsZW1lbnRhc2kgUmFuZG9tIEZvcmVzdA0KDQpwcmVkc19yZiA8LSBwcmVkaWN0KG1vZGVsX3JmLCB0b3ByZWRpY3Rfc2V0KSAgICAgICAgICAgICAgDQoNCihjb25mX21hdHJpeF9mb3Jlc3QgPC0gdGFibGUocHJlZHNfcmYsIHRlc3Rfc2V0JENsYXNzKSkNCmBgYA0KDQpEYXJpIGhhc2lsIENvbmZ1c2lvbiBNYXRyaXggbWVudW5qdWtrYW4gYmFod2EgcGVuZ2tsYXNpZmlrYXNpYW4gbWVuZ2d1bmFrYW4gUmFuZG9tIEZvcmVzdCBtZW1wcmVkaWtzaSAxMjUga2FzdXMgYmVuaWduIC8gamluYWsgZGVuZ2FuIGJlbmFyIGRhbiBkdWEgcHJlZGlrc2kgc2FsYWguIERlbWlraWFuIHB1bGEsIFJhbmRvbSBGb3Jlc3QgbWVtcHJlZGlrc2kgNzIga2FzdXMgbWFsaWduYW50IC8gZ2FuYXMgZGVuZ2FuIGJlbmFyIGRhbiAxMCBwcmVkaWtzaSBzYWxhaC4NCg0KQmVyaWt1dCBhZGFsYWggc2NyaXB0IHVudHVrIG1lbmdldGFodWkgYWt1cmFzaSBkYXJpIE1ldG9kZSBLbGFzaWZpa2FzaSBSYW5kb20gRm9yZXN0Lg0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChjb25mX21hdHJpeF9mb3Jlc3QpICANCmBgYA0KSGFzaWwgbWVudW5qdWtrYW4gdW50dWsga2Vha3VyYXRhbiBtb3RvZGUgUmFuZG9tIEZvcmVzdCB1bnR1ayBtZW1wcmVkaWtzaSBLYW5rZXIgUGF5dWRhcmEgc2ViZXNhciA5NC4yNiUNCg0KIyBEZWNpc2lvblRyZWUgQ2xhc3NpZmllcg0KDQpNZXRvZGUgc2VsYW5qdXRueWEgeWFuZyBha2FuIGtpdGEgZ3VuYWthbiBhZGFsYWggbWV0b2RlIERlY2lzaW9uVHJlZS4gRGVjaXNpb24gVHJlZSBhdGF1IGJpc2EgZGlzZWJ1dCBkZW5nYW4gcG9ob24ga2VwdXR1c2FuIG1lcnVwYWthbiBhZGFsYWggc2FsYWggc2F0dSBwZW5kZWthdGFuIHBlbW9kZWxhbiBwcmVkaWt0aWYgeWFuZyBkaWd1bmFrYW4gZGFsYW0gc3RhdGlzdGlrLCBwZW5hbWJhbmdhbiBkYXRhLCBkYW4gcGVtYmVsYWphcmFuIG1lc2luLiBQZW5nZ3VuYWFuIHBvaG9uIGtlcHV0dXNhbiB1bnR1ayBiZXJhbGloIGRhcmkgcGVuZ2FtYXRhbiB0ZW50YW5nIHN1YXR1IGl0ZW0ga2Uga2VzaW1wdWxhbiB0ZW50YW5nIG5pbGFpIHRhcmdldCBpdGVtIHRlcnNlYnV0Lg0KDQpGdW5nc2kgYHJwYXJ0YCBkaWd1bmFrYW4gdW50dWsgbWVuZ2hpdHVuZyBEZWNpc2lvbiBUcmVlLiBpbnN0YWxsIHRlcmxhYmloIGRhaHVsdSBwYWtldCBycGFydCBrZW11ZGlhbiBtdWF0IGxpYnJhcnlueWEuDQpgYGB7cn0NCmxpYnJhcnkocnBhcnQpDQptb2RlbF9kdHJlZTwtIHJwYXJ0KENsYXNzIH4gLiwgZGF0YT10cmFpbmluZ19zZXQpICAgICAgICNJbXBsZW1lbnRpbmcgRGVjaXNpb24gVHJlZQ0KcHJlZHNfZHRyZWUgPC0gcHJlZGljdChtb2RlbF9kdHJlZSxuZXdkYXRhPXRvcHJlZGljdF9zZXQsIHR5cGUgPSAiY2xhc3MiKQ0KYGBgDQoNCmBgYHtyfQ0KI3Bsb3QocHJlZHNfZHRyZWUsIG1haW49IkRlY2lzaW9uIHRyZWUgY3JlYXRlZCB1c2luZyBycGFydCIpDQooY29uZl9tYXRyaXhfZHRyZWUgPC0gdGFibGUocHJlZHNfZHRyZWUsIHRlc3Rfc2V0JENsYXNzKSkNCmBgYA0KRGFyaSBoYXNpbCBEZXNpY2lvblRyZWUgbWVudW5qdWtrYW4gYmFod2EgcGVuZ2tsYXNpZmlrYXNpYW4gbWVuZ2d1bmFrYW4gRGVjaXNpb25UcmVlIG1lbXByZWRpa3NpIDEyNyBrYXN1cyBiZW5pZ24gLyBqaW5hayBkZW5nYW4gYmVuYXIgZGFuIDUgcHJlZGlrc2kgc2FsYWguIERlbWlraWFuIHB1bGEsIERlY2lzaW9uVHJlZSBtZW1wcmVkaWtzaSA2OSBrYXN1cyBtYWxpZ25hbnQgLyBnYW5hcyBkZW5nYW4gYmVuYXIgZGFuIDggcHJlZGlrc2kgc2FsYWguDQoNCk1hcmkga2l0YSBsaWhhdCBrZWFrdXJhdGFuIG1ldG9kZSBEZWNpc2lvblRyZWUNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgoY29uZl9tYXRyaXhfZHRyZWUpICAgDQpgYGANCkhhc2lsIG1lbnVuanVra2FuIHVudHVrIGtlYWt1cmF0YW4gbW90b2RlIERlY2lzaW9uVHJlZSB1bnR1ayBtZW1wcmVkaWtzaSBLYW5rZXIgUGF5dWRhcmEgc2ViZXNhciA5My43OCUNCg0KS2VzaW1wdWxhbiB5YW5nIGJpc2EgZGlhbWJpbCBkYXJpIGhhc2lsIHBlcmJhbmRpbmdhbiAzIE1ldG9kZSBLbGFzaWZpa2FzaSBkYWxhbSBtZW1wcmVkaWtzaSBLYW5rZXIgUGF5dWRhcmEgYmVyZGFzYXJrYW4gZGF0YXNldCBkZWZhdWx0IGRhcmkgYEJyZWFzdENhbmNlcmAgYWRhbGFoIG1ldG9kZSBOYWl2ZSBCYXllcyBsZWJpaCBiYWlrIGRhbGFtIG1lbXByZWRpa3NpIEthbmtlciBQYXl1ZGFyYSBkZW5nYW4gYWt1cmFzaSBwZXJoaXR1bmdhbm55YSBzZWJlc2FyIDk2LjE3JS4NCg0KIyBEYWZ0YXIgUHVzdGFrYQ0KPGEgaHJlZiA9ICJodHRwczovL3NoaXJpbmcuZ2l0aHViLmlvL21hY2hpbmVfbGVhcm5pbmcvMjAxNy8wMS8xNS9yZmVfZ2FfcG9zdCI+aHR0cHM6Ly9zaGlyaW5nLmdpdGh1Yi5pby9tYWNoaW5lX2xlYXJuaW5nLzIwMTcvMDEvMTUvcmZlX2dhX3Bvc3Q8L2E+DQoNCjxhIGhyZWYgPSAiaHR0cHM6Ly93d3cucmVzZWFyY2hnYXRlLm5ldC9wdWJsaWNhdGlvbi8zMTE5NTA3OTlfQW5hbHlzaXNfb2ZfdGhlX1dpc2NvbnNpbl9CcmVhc3RfQ2FuY2VyX0RhdGFzZXRfYW5kX01hY2hpbmVfTGVhcm5pbmdfZm9yX0JyZWFzdF9DYW5jZXJfRGV0ZWN0aW9uIj5odHRwczovL3d3dy5yZXNlYXJjaGdhdGUubmV0L3B1YmxpY2F0aW9uLzMxMTk1MDc5OV9BbmFseXNpc19vZl90aGVfV2lzY29uc2luX0JyZWFzdF9DYW5jZXJfRGF0YXNldF9hbmRfTWFjaGluZV9MZWFybmluZ19mb3JfQnJlYXN0X0NhbmNlcl9EZXRlY3Rpb248L2E+