Tên: Mai Huy

MSSV: 43.01.104.065

Số thứ tự: 08

a) Create a training set containing a random sample of 800 observations, and a test set containing the remaining observations.

# Load thư viện ISLR
library(ISLR)
# set.seed dùng để tái tạo những vector random giống nhau theo tương ứng với giá trị được đưa vào hàm seed
set.seed(1)
# Sử dụng 800 quan sát được random để sử dụng cho tập train 
train <- sample(1:nrow(OJ), 800)
# Chia tập train có 800 quan sát
OJ.train <- OJ[train, ]
# Tập Test bao gồm số lượng quan sát còn lại
OJ.test <- OJ[-train, ]

b) Fit a tree to the training data, with “Purchase” as the response and the other variables except for “Buy” as predictors.Use the “summary()” function to produce summary statistics about the tree, and describe the results obtained. What is the training error rate ? How many terminal nodes does the tree have ?

# Load thư viện dữ liệu tree
library(tree)
# Hàm tree dùng để tạo ra cây phân loại giúp dự đoán giá trị đầu ra là biến Purchase và sử dụng tất cả các biến còn lại làm giá trị đầu vào trong tập train
tree.oj <- tree(Purchase ~ ., data = OJ.train)
# Phân tích dữ liệu cây phân loại vừa được tạo
summary(tree.oj)

Classification tree:
tree(formula = Purchase ~ ., data = OJ.train)
Variables actually used in tree construction:
[1] "LoyalCH"       "PriceDiff"     "SpecialCH"     "ListPriceDiff" "PctDiscMM"    
Number of terminal nodes:  9 
Residual mean deviance:  0.7432 = 587.8 / 791 
Misclassification error rate: 0.1588 = 127 / 800 

Hàm summary liệt kê các biến internal nodes trong cây bao gồm các biến : “LoyalCH” “PriceDiff” “SpecialCH” “ListPriceDiff” “PctDiscMM” , cho biết số lượng terminal nodes = 9, độ lệch chuẩn trung bình = 0.7432, tỉ lệ sai số huấn luyện = 0.1588

c) Type in the name of the tree object in order to get a detailed text output. Pick one of the terminal nodes, and interpret the information displayed.

# Hiển thị kết quả output tương ứng với mỗi nhánh của cây
tree.oj
node), split, n, deviance, yval, (yprob)
      * denotes terminal node

 1) root 800 1073.00 CH ( 0.60625 0.39375 )  
   2) LoyalCH < 0.5036 365  441.60 MM ( 0.29315 0.70685 )  
     4) LoyalCH < 0.280875 177  140.50 MM ( 0.13559 0.86441 )  
       8) LoyalCH < 0.0356415 59   10.14 MM ( 0.01695 0.98305 ) *
       9) LoyalCH > 0.0356415 118  116.40 MM ( 0.19492 0.80508 ) *
     5) LoyalCH > 0.280875 188  258.00 MM ( 0.44149 0.55851 )  
      10) PriceDiff < 0.05 79   84.79 MM ( 0.22785 0.77215 )  
        20) SpecialCH < 0.5 64   51.98 MM ( 0.14062 0.85938 ) *
        21) SpecialCH > 0.5 15   20.19 CH ( 0.60000 0.40000 ) *
      11) PriceDiff > 0.05 109  147.00 CH ( 0.59633 0.40367 ) *
   3) LoyalCH > 0.5036 435  337.90 CH ( 0.86897 0.13103 )  
     6) LoyalCH < 0.764572 174  201.00 CH ( 0.73563 0.26437 )  
      12) ListPriceDiff < 0.235 72   99.81 MM ( 0.50000 0.50000 )  
        24) PctDiscMM < 0.196197 55   73.14 CH ( 0.61818 0.38182 ) *
        25) PctDiscMM > 0.196197 17   12.32 MM ( 0.11765 0.88235 ) *
      13) ListPriceDiff > 0.235 102   65.43 CH ( 0.90196 0.09804 ) *
     7) LoyalCH > 0.764572 261   91.20 CH ( 0.95785 0.04215 ) *

Ta chọn node thứ 8, là một terminal node do có đánh dấu ’*’. Node này có tiêu chuẩn chia nhánh là LoyalCH < 0.0356415, số lượng quan sát trong nhánh là 59, sai số cross-validation = 10.14, kết quả dự đoán của nhánh này là MM. Ngoài ra còn cho biết thêm là ít hơn 2% số quan sát có giá trị CH, còn lại là 98% mang giá trị MM.

d) Create a plot of the tree, and interpret the results.

# Hiển thị cấu trúc cây 
plot(tree.oj)
# Hiển thị nhãn tên các node của cây, biến pretty = 0 là để bao gồm tên loại cho bất cứ giá trị định tính nào so với việc chỉ hiển thị các kí tự chữ cái cho mỗi loại
text(tree.oj, pretty = 0)

Chúng ta thấy rằng biến quan trọng nhất góp phần vào việc dự đoán Purchase là biến LoyalCH khi mà ngay từ nhánh đầu tiên đòi hỏi sự phân biệt mức độ trung thành nhãn hiệu của khách hàng. Ngoài ra, top 3 nodes có chứa bien1 “LoyalCH”

e) Predict the response on the test data, and produce aconfusion matrix comparing the test labels to the predicted test labels. What is the test error rate?

# Tiến hành dự đoán trên tập test
tree.pred <- predict(tree.oj, OJ.test, type = "class")
# Dùng hàm table() để tạo ra một ma trận để quyết định xem có bao nhiêu quan sát được phân loại đúng, bao nhiêu bị phân loại sai
table(tree.pred, OJ.test$Purchase)
         
tree.pred  CH  MM
       CH 160  38
       MM   8  64

Tỉ lệ số lượng quan sát được phân loại đúng trong tập test là : (160+64)/270 = 0.8296

=> Tỉ lệ phân loại sai là khoảng 17%

f) Apply the cv.tree() function to the training set in order to determine the optimal tree size.

# Hàm cv.tree thực hiện cross-validation để quyết định độ phức tạp tối ưu cho cây.
cv.oj <- cv.tree(tree.oj, FUN = prune.misclass)
#Hàm cv.tree() cho biết số lượng terminal nodes của mỗi cây, cũng như là tỉ lệ phân loại lỗi và giá trị của tham số chi phí phức (k) được sử dụng
cv.oj
$size
[1] 9 8 7 4 2 1

$dev
[1] 150 150 149 158 172 315

$k
[1]       -Inf   0.000000   3.000000   4.333333  10.500000 151.000000

$method
[1] "misclass"

attr(,"class")
[1] "prune"         "tree.sequence"

Chúng ta thấy rằng với 7 terminal nodes thì sai số lỗi cross-validation ra được là thấp nhất = 149

g) Produce a plot with tree size on the x-axis and cross-validated classification error rate on the y-axis.

# Hiển thị đồ thị phân tán với x là số lượng terminal nodes và y là sai số lỗi cross validation
plot(cv.oj$size, cv.oj$dev, type = "b", xlab = "Tree size", ylab = "Deviance")

h) Which tree size corresponds to the lowest cross-validated classification error rate?

Chúng ta thấy rằng với 7 terminal nodes thì sai số lỗi cross-validation ra được là thấp nhất

Produce a pruned tree corresponding to the optimal tree size obtained using cross-validation. If cross-validation does not lead to selection of a pruned tree, then create a pruned tree with five terminal nodes.

# Thực hiện cắt tỉa cây xuống còn 7 nodes
prune.oj <- prune.misclass(tree.oj, best = 7)
# Hiển thị cấu trúc cây đã được tỉa còn 7 nodes
plot(prune.oj)
# Hiển thị nhãn tên các node của cây, biến pretty = 0 là để bao gồm tên loại cho bất cứ giá trị định tính nào so với việc chỉ hiển thị các kí tự chữ cái cho mỗi loại
text(prune.oj, pretty = 0)

j) Compare the training error rates between the pruned and unpruned trees. Which is higher?

# Phân tích dữ liệu cây phân loại gốc chưa cắt tỉa
summary(tree.oj)

Classification tree:
tree(formula = Purchase ~ ., data = OJ.train)
Variables actually used in tree construction:
[1] "LoyalCH"       "PriceDiff"     "SpecialCH"     "ListPriceDiff" "PctDiscMM"    
Number of terminal nodes:  9 
Residual mean deviance:  0.7432 = 587.8 / 791 
Misclassification error rate: 0.1588 = 127 / 800 
# Phân tích dữ liệu cây phân loại đã được cắt tỉa
summary(prune.oj)

Classification tree:
snip.tree(tree = tree.oj, nodes = c(4L, 10L))
Variables actually used in tree construction:
[1] "LoyalCH"       "PriceDiff"     "ListPriceDiff" "PctDiscMM"    
Number of terminal nodes:  7 
Residual mean deviance:  0.7748 = 614.4 / 793 
Misclassification error rate: 0.1625 = 130 / 800 

Tỉ lệ phân loại sai của cây được cắt tỉa là cao hơn (0.1625 so với 0.1588)

k) Compare the test error rates between the pruned and unpruned trees. Which is higher?

# Tiến hành dự đoán trên tập test dùng mô hình là cây đã được cắt tỉa
prune.pred <- predict(prune.oj, OJ.test, type = "class")
# Dùng hàm table() để tạo ra một ma trận để quyết định xem có bao nhiêu quan sát được phân loại đúng, bao nhiêu bị phân loại sai
table(prune.pred, OJ.test$Purchase)
          
prune.pred  CH  MM
        CH 160  36
        MM   8  66

Tỉ lệ số lượng quan sát được phân loại đúng trong tập test là : (160+66)/270 = 0.837

=> Tỉ lệ phân loại sai của mô hình cây được cắt tỉa là khoảng 16.3%, nhỏ hơn so với mô hình cây gốc chưa được cắt tỉa là 17%

LS0tDQp0aXRsZTogIkLDoGkgdOG6rXAgMl9UdeG6p24gNyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KIyMjIFTDqm46IE1haSBIdXkNCiMjIyBNU1NWOiA0My4wMS4xMDQuMDY1DQojIyMgU+G7kSB0aOG7qSB04buxOiAwOA0KDQojIyMgYSkgQ3JlYXRlIGEgdHJhaW5pbmcgc2V0IGNvbnRhaW5pbmcgYSByYW5kb20gc2FtcGxlIG9mIDgwMCBvYnNlcnZhdGlvbnMsIGFuZCBhIHRlc3Qgc2V0IGNvbnRhaW5pbmcgdGhlIHJlbWFpbmluZyBvYnNlcnZhdGlvbnMuDQoNCmBgYHtyfQ0KIyBMb2FkIHRoxrAgdmnhu4duIElTTFINCmxpYnJhcnkoSVNMUikNCmBgYA0KDQpgYGB7cn0NCiMgc2V0LnNlZWQgZMO5bmcgxJHhu4MgdMOhaSB04bqhbyBuaOG7r25nIHZlY3RvciByYW5kb20gZ2nhu5FuZyBuaGF1IHRoZW8gdMawxqFuZyDhu6luZyB24bubaSBnacOhIHRy4buLIMSRxrDhu6NjIMSRxrBhIHbDoG8gaMOgbSBzZWVkDQpzZXQuc2VlZCgxKQ0KIyBT4butIGThu6VuZyA4MDAgcXVhbiBzw6F0IMSRxrDhu6NjIHJhbmRvbSDEkeG7gyBz4butIGThu6VuZyBjaG8gdOG6rXAgdHJhaW4gDQp0cmFpbiA8LSBzYW1wbGUoMTpucm93KE9KKSwgODAwKQ0KIyBDaGlhIHThuq1wIHRyYWluIGPDsyA4MDAgcXVhbiBzw6F0DQpPSi50cmFpbiA8LSBPSlt0cmFpbiwgXQ0KIyBU4bqtcCBUZXN0IGJhbyBn4buTbSBz4buRIGzGsOG7o25nIHF1YW4gc8OhdCBjw7JuIGzhuqFpDQpPSi50ZXN0IDwtIE9KWy10cmFpbiwgXQ0KYGBgDQoNCiMjIyBiKSBGaXQgYSB0cmVlIHRvIHRoZSB0cmFpbmluZyBkYXRhLCB3aXRoIOKAnFB1cmNoYXNl4oCdIGFzIHRoZSByZXNwb25zZSBhbmQgdGhlIG90aGVyIHZhcmlhYmxlcyBleGNlcHQgZm9yIOKAnEJ1eeKAnSBhcyBwcmVkaWN0b3JzLlVzZSB0aGUg4oCcc3VtbWFyeSgp4oCdIGZ1bmN0aW9uIHRvIHByb2R1Y2Ugc3VtbWFyeSBzdGF0aXN0aWNzIGFib3V0IHRoZSB0cmVlLCBhbmQgZGVzY3JpYmUgdGhlIHJlc3VsdHMgb2J0YWluZWQuIFdoYXQgaXMgdGhlIHRyYWluaW5nIGVycm9yIHJhdGUgPyBIb3cgbWFueSB0ZXJtaW5hbCBub2RlcyBkb2VzIHRoZSB0cmVlIGhhdmUgPw0KDQpgYGB7cn0NCiMgTG9hZCB0aMawIHZp4buHbiBk4buvIGxp4buHdSB0cmVlDQpsaWJyYXJ5KHRyZWUpDQojIEjDoG0gdHJlZSBkw7luZyDEkeG7gyB04bqhbyByYSBjw6J5IHBow6JuIGxv4bqhaSBnacO6cCBk4buxIMSRb8OhbiBnacOhIHRy4buLIMSR4bqndSByYSBsw6AgYmnhur9uIFB1cmNoYXNlIHbDoCBz4butIGThu6VuZyB04bqldCBj4bqjIGPDoWMgYmnhur9uIGPDsm4gbOG6oWkgbMOgbSBnacOhIHRy4buLIMSR4bqndSB2w6BvIHRyb25nIHThuq1wIHRyYWluDQp0cmVlLm9qIDwtIHRyZWUoUHVyY2hhc2UgfiAuLCBkYXRhID0gT0oudHJhaW4pDQojIFBow6JuIHTDrWNoIGThu68gbGnhu4d1IGPDonkgcGjDom4gbG/huqFpIHbhu6thIMSRxrDhu6NjIHThuqFvDQpzdW1tYXJ5KHRyZWUub2opDQpgYGANCg0KSMOgbSBzdW1tYXJ5IGxp4buHdCBrw6ogY8OhYyBiaeG6v24gaW50ZXJuYWwgbm9kZXMgdHJvbmcgY8OieSBiYW8gZ+G7k20gY8OhYyBiaeG6v24gOiAiTG95YWxDSCIgICJQcmljZURpZmYiICJTcGVjaWFsQ0giICJMaXN0UHJpY2VEaWZmIiAiUGN0RGlzY01NIiAsIGNobyBiaeG6v3Qgc+G7kSBsxrDhu6NuZyB0ZXJtaW5hbCBub2RlcyA9IDksIMSR4buZIGzhu4djaCBjaHXhuqluIHRydW5nIGLDrG5oID0gMC43NDMyLCB04buJIGzhu4cgc2FpIHPhu5EgaHXhuqVuIGx1eeG7h24gPSAwLjE1ODgNCg0KIyMjIGMpIFR5cGUgaW4gdGhlIG5hbWUgb2YgdGhlIHRyZWUgb2JqZWN0IGluIG9yZGVyIHRvIGdldCBhIGRldGFpbGVkIHRleHQgb3V0cHV0LiBQaWNrIG9uZSBvZiB0aGUgdGVybWluYWwgbm9kZXMsIGFuZCBpbnRlcnByZXQgdGhlIGluZm9ybWF0aW9uIGRpc3BsYXllZC4NCg0KYGBge3J9DQojIEhp4buDbiB0aOG7iyBr4bq/dCBxdeG6oyBvdXRwdXQgdMawxqFuZyDhu6luZyB24bubaSBt4buXaSBuaMOhbmggY+G7p2EgY8OieQ0KdHJlZS5vag0KYGBgDQoNClRhIGNo4buNbiBub2RlIHRo4bupIDgsIGzDoCBt4buZdCB0ZXJtaW5hbCBub2RlIGRvIGPDsyDEkcOhbmggZOG6pXUgJyonLiBOb2RlIG7DoHkgY8OzIHRpw6p1IGNodeG6qW4gY2hpYSBuaMOhbmggbMOgIExveWFsQ0ggPCAwLjAzNTY0MTUsIHPhu5EgbMaw4bujbmcgcXVhbiBzw6F0IHRyb25nIG5ow6FuaCBsw6AgNTksIHNhaSBz4buRIGNyb3NzLXZhbGlkYXRpb24gPSAxMC4xNCwga+G6v3QgcXXhuqMgZOG7sSDEkW/DoW4gY+G7p2EgbmjDoW5oIG7DoHkgbMOgIE1NLiBOZ2/DoGkgcmEgY8OybiBjaG8gYmnhur90IHRow6ptIGzDoCDDrXQgaMahbiAyJSBz4buRIHF1YW4gc8OhdCBjw7MgZ2nDoSB0cuG7iyBDSCwgY8OybiBs4bqhaSBsw6AgOTglIG1hbmcgZ2nDoSB0cuG7iyBNTS4NCg0KIyMjIGQpIENyZWF0ZSBhIHBsb3Qgb2YgdGhlIHRyZWUsIGFuZCBpbnRlcnByZXQgdGhlIHJlc3VsdHMuDQoNCmBgYHtyfQ0KIyBIaeG7g24gdGjhu4sgY+G6pXUgdHLDumMgY8OieSANCnBsb3QodHJlZS5vaikNCiMgSGnhu4NuIHRo4buLIG5ow6NuIHTDqm4gY8OhYyBub2RlIGPhu6dhIGPDonksIGJp4bq/biBwcmV0dHkgPSAwIGzDoCDEkeG7gyBiYW8gZ+G7k20gdMOqbiBsb+G6oWkgY2hvIGLhuqV0IGPhu6kgZ2nDoSB0cuG7iyDEkeG7i25oIHTDrW5oIG7DoG8gc28gduG7m2kgdmnhu4djIGNo4buJIGhp4buDbiB0aOG7iyBjw6FjIGvDrSB04buxIGNo4buvIGPDoWkgY2hvIG3hu5dpIGxv4bqhaQ0KdGV4dCh0cmVlLm9qLCBwcmV0dHkgPSAwKQ0KYGBgDQoNCkNow7puZyB0YSB0aOG6pXkgcuG6sW5nIGJp4bq/biBxdWFuIHRy4buNbmcgbmjhuqV0IGfDs3AgcGjhuqduIHbDoG8gdmnhu4djIGThu7EgxJFvw6FuIFB1cmNoYXNlIGzDoCBiaeG6v24gTG95YWxDSCBraGkgbcOgIG5nYXkgdOG7qyBuaMOhbmggxJHhuqd1IHRpw6puIMSRw7JpIGjhu49pIHPhu7EgcGjDom4gYmnhu4d0IG3hu6ljIMSR4buZIHRydW5nIHRow6BuaCBuaMOjbiBoaeG7h3UgY+G7p2Ega2jDoWNoIGjDoG5nLiBOZ2/DoGkgcmEsIHRvcCAzIG5vZGVzIGPDsyBjaOG7qWEgYmllbjEgIkxveWFsQ0giDQoNCiMjIyBlKSBQcmVkaWN0IHRoZSByZXNwb25zZSBvbiB0aGUgdGVzdCBkYXRhLCBhbmQgcHJvZHVjZSBhY29uZnVzaW9uIG1hdHJpeCBjb21wYXJpbmcgdGhlIHRlc3QgbGFiZWxzIHRvIHRoZSBwcmVkaWN0ZWQgdGVzdCBsYWJlbHMuIFdoYXQgaXMgdGhlIHRlc3QgZXJyb3IgcmF0ZT8NCg0KYGBge3J9DQojIFRp4bq/biBow6BuaCBk4buxIMSRb8OhbiB0csOqbiB04bqtcCB0ZXN0DQp0cmVlLnByZWQgPC0gcHJlZGljdCh0cmVlLm9qLCBPSi50ZXN0LCB0eXBlID0gImNsYXNzIikNCiMgRMO5bmcgaMOgbSB0YWJsZSgpIMSR4buDIHThuqFvIHJhIG3hu5l0IG1hIHRy4bqtbiDEkeG7gyBxdXnhur90IMSR4buLbmggeGVtIGPDsyBiYW8gbmhpw6p1IHF1YW4gc8OhdCDEkcaw4bujYyBwaMOibiBsb+G6oWkgxJHDum5nLCBiYW8gbmhpw6p1IGLhu4sgcGjDom4gbG/huqFpIHNhaQ0KdGFibGUodHJlZS5wcmVkLCBPSi50ZXN0JFB1cmNoYXNlKQ0KYGBgDQoNClThu4kgbOG7hyBz4buRIGzGsOG7o25nIHF1YW4gc8OhdCDEkcaw4bujYyBwaMOibiBsb+G6oWkgxJHDum5nIHRyb25nIHThuq1wIHRlc3QgbMOgIDogKDE2MCs2NCkvMjcwID0gMC44Mjk2DQoNCj0+IFThu4kgbOG7hyBwaMOibiBsb+G6oWkgc2FpIGzDoCBraG/huqNuZyAxNyUNCg0KIyMjIGYpIEFwcGx5IHRoZSBjdi50cmVlKCkgZnVuY3Rpb24gdG8gdGhlIHRyYWluaW5nIHNldCBpbiBvcmRlciB0byBkZXRlcm1pbmUgdGhlIG9wdGltYWwgdHJlZSBzaXplLg0KDQpgYGB7cn0NCiMgSMOgbSBjdi50cmVlIHRo4buxYyBoaeG7h24gY3Jvc3MtdmFsaWRhdGlvbiDEkeG7gyBxdXnhur90IMSR4buLbmggxJHhu5kgcGjhu6ljIHThuqFwIHThu5FpIMawdSBjaG8gY8OieS4NCmN2Lm9qIDwtIGN2LnRyZWUodHJlZS5vaiwgRlVOID0gcHJ1bmUubWlzY2xhc3MpDQojSMOgbSBjdi50cmVlKCkgY2hvIGJp4bq/dCBz4buRIGzGsOG7o25nIHRlcm1pbmFsIG5vZGVzIGPhu6dhIG3hu5dpIGPDonksIGPFqW5nIG5oxrAgbMOgIHThu4kgbOG7hyBwaMOibiBsb+G6oWkgbOG7l2kgdsOgIGdpw6EgdHLhu4sgY+G7p2EgdGhhbSBz4buRIGNoaSBwaMOtIHBo4bupYyAoaykgxJHGsOG7o2Mgc+G7rSBk4bulbmcNCmN2Lm9qDQpgYGANCg0KQ2jDum5nIHRhIHRo4bqleSBy4bqxbmcgduG7m2kgNyB0ZXJtaW5hbCBub2RlcyB0aMOsIHNhaSBz4buRIGzhu5dpIGNyb3NzLXZhbGlkYXRpb24gcmEgxJHGsOG7o2MgbMOgIHRo4bqlcCBuaOG6pXQgPSAxNDkNCg0KIyMjIGcpIFByb2R1Y2UgYSBwbG90IHdpdGggdHJlZSBzaXplIG9uIHRoZSB4LWF4aXMgYW5kIGNyb3NzLXZhbGlkYXRlZCBjbGFzc2lmaWNhdGlvbiBlcnJvciByYXRlIG9uIHRoZSB5LWF4aXMuDQoNCmBgYHtyfQ0KIyBIaeG7g24gdGjhu4sgxJHhu5MgdGjhu4sgcGjDom4gdMOhbiB24bubaSB4IGzDoCBz4buRIGzGsOG7o25nIHRlcm1pbmFsIG5vZGVzIHbDoCB5IGzDoCBzYWkgc+G7kSBs4buXaSBjcm9zcyB2YWxpZGF0aW9uDQpwbG90KGN2Lm9qJHNpemUsIGN2Lm9qJGRldiwgdHlwZSA9ICJiIiwgeGxhYiA9ICJUcmVlIHNpemUiLCB5bGFiID0gIkRldmlhbmNlIikNCmBgYA0KDQojIyMgaCkgV2hpY2ggdHJlZSBzaXplIGNvcnJlc3BvbmRzIHRvIHRoZSBsb3dlc3QgY3Jvc3MtdmFsaWRhdGVkIGNsYXNzaWZpY2F0aW9uIGVycm9yIHJhdGU/DQoNCkNow7puZyB0YSB0aOG6pXkgcuG6sW5nIHbhu5tpIDcgdGVybWluYWwgbm9kZXMgdGjDrCBzYWkgc+G7kSBs4buXaSBjcm9zcy12YWxpZGF0aW9uIHJhIMSRxrDhu6NjIGzDoCB0aOG6pXAgbmjhuqV0DQoNCiMjIyBQcm9kdWNlIGEgcHJ1bmVkIHRyZWUgY29ycmVzcG9uZGluZyB0byB0aGUgb3B0aW1hbCB0cmVlIHNpemUgb2J0YWluZWQgdXNpbmcgY3Jvc3MtdmFsaWRhdGlvbi4gSWYgY3Jvc3MtdmFsaWRhdGlvbiBkb2VzIG5vdCBsZWFkIHRvIHNlbGVjdGlvbiBvZiBhIHBydW5lZCB0cmVlLCB0aGVuIGNyZWF0ZSBhIHBydW5lZCB0cmVlIHdpdGggZml2ZSB0ZXJtaW5hbCBub2Rlcy4NCg0KYGBge3J9DQojIFRo4buxYyBoaeG7h24gY+G6r3QgdOG7iWEgY8OieSB4deG7kW5nIGPDsm4gNyBub2Rlcw0KcHJ1bmUub2ogPC0gcHJ1bmUubWlzY2xhc3ModHJlZS5vaiwgYmVzdCA9IDcpDQpgYGANCg0KDQpgYGB7cn0NCiMgSGnhu4NuIHRo4buLIGPhuqV1IHRyw7pjIGPDonkgxJHDoyDEkcaw4bujYyB04buJYSBjw7JuIDcgbm9kZXMNCnBsb3QocHJ1bmUub2opDQojIEhp4buDbiB0aOG7iyBuaMOjbiB0w6puIGPDoWMgbm9kZSBj4bunYSBjw6J5LCBiaeG6v24gcHJldHR5ID0gMCBsw6AgxJHhu4MgYmFvIGfhu5NtIHTDqm4gbG/huqFpIGNobyBi4bqldCBj4bupIGdpw6EgdHLhu4sgxJHhu4tuaCB0w61uaCBuw6BvIHNvIHbhu5tpIHZp4buHYyBjaOG7iSBoaeG7g24gdGjhu4sgY8OhYyBrw60gdOG7sSBjaOG7ryBjw6FpIGNobyBt4buXaSBsb+G6oWkNCnRleHQocHJ1bmUub2osIHByZXR0eSA9IDApDQpgYGANCg0KIyMjIGopIENvbXBhcmUgdGhlIHRyYWluaW5nIGVycm9yIHJhdGVzIGJldHdlZW4gdGhlIHBydW5lZCBhbmQgdW5wcnVuZWQgdHJlZXMuIFdoaWNoIGlzIGhpZ2hlcj8NCg0KYGBge3J9DQojIFBow6JuIHTDrWNoIGThu68gbGnhu4d1IGPDonkgcGjDom4gbG/huqFpIGfhu5FjIGNoxrBhIGPhuq90IHThu4lhDQpzdW1tYXJ5KHRyZWUub2opDQojIFBow6JuIHTDrWNoIGThu68gbGnhu4d1IGPDonkgcGjDom4gbG/huqFpIMSRw6MgxJHGsOG7o2MgY+G6r3QgdOG7iWENCnN1bW1hcnkocHJ1bmUub2opDQpgYGANCg0KVOG7iSBs4buHIHBow6JuIGxv4bqhaSBzYWkgY+G7p2EgY8OieSDEkcaw4bujYyBj4bqvdCB04buJYSBsw6AgY2FvIGjGoW4gKDAuMTYyNSBzbyB24bubaSAwLjE1ODgpDQoNCiMjIyBrKSBDb21wYXJlIHRoZSB0ZXN0IGVycm9yIHJhdGVzIGJldHdlZW4gdGhlIHBydW5lZCBhbmQgdW5wcnVuZWQgdHJlZXMuIFdoaWNoIGlzIGhpZ2hlcj8NCg0KYGBge3J9DQojIFRp4bq/biBow6BuaCBk4buxIMSRb8OhbiB0csOqbiB04bqtcCB0ZXN0IGTDuW5nIG3DtCBow6xuaCBsw6AgY8OieSDEkcOjIMSRxrDhu6NjIGPhuq90IHThu4lhDQpwcnVuZS5wcmVkIDwtIHByZWRpY3QocHJ1bmUub2osIE9KLnRlc3QsIHR5cGUgPSAiY2xhc3MiKQ0KIyBEw7luZyBow6BtIHRhYmxlKCkgxJHhu4MgdOG6oW8gcmEgbeG7mXQgbWEgdHLhuq1uIMSR4buDIHF1eeG6v3QgxJHhu4tuaCB4ZW0gY8OzIGJhbyBuaGnDqnUgcXVhbiBzw6F0IMSRxrDhu6NjIHBow6JuIGxv4bqhaSDEkcO6bmcsIGJhbyBuaGnDqnUgYuG7iyBwaMOibiBsb+G6oWkgc2FpDQp0YWJsZShwcnVuZS5wcmVkLCBPSi50ZXN0JFB1cmNoYXNlKQ0KYGBgDQoNCg0KVOG7iSBs4buHIHPhu5EgbMaw4bujbmcgcXVhbiBzw6F0IMSRxrDhu6NjIHBow6JuIGxv4bqhaSDEkcO6bmcgdHJvbmcgdOG6rXAgdGVzdCBsw6AgOiAoMTYwKzY2KS8yNzAgPSAwLjgzNw0KDQo9PiBU4buJIGzhu4cgcGjDom4gbG/huqFpIHNhaSBj4bunYSBtw7QgaMOsbmggY8OieSDEkcaw4bujYyBj4bqvdCB04buJYSBsw6Aga2hv4bqjbmcgMTYuMyUsIG5o4buPIGjGoW4gc28gduG7m2kgbcO0IGjDrG5oIGPDonkgZ+G7kWMgY2jGsGEgxJHGsOG7o2MgY+G6r3QgdOG7iWEgbMOgIDE3JQ==