Tên: Mai Huy

MSSV: 43.01.104.065

Số thứ tự: 08

Câu a) Remove the observations for whom the salary information is unknown, and then log-transform the salaries.

# Load thư viện Mass
library(MASS)
# Load thư viện ISLR
library(ISLR)
# Bỏ những quan sát mà thông tin bị trống và cập nhập lại tập dữ liệu Hitlers mới
Hitters <- na.omit(Hitters)
# Trả về cột salary sau khi được log
Hitters$Salary <- log(Hitters$Salary)

Câu b) Create a training set consisting of the first 200 observations, and a test set consisting of the remaining observations.

# Tập train gồm 200 quan sát đầu
train <- 1:200
# Lấy 200 quan sát đầu trong tập dữ liệu Hitters làm tập train
Hitters.train <- Hitters[train, ]
# Lấy các quan sát còn lại ngoài tập train làm tập test
Hitters.test <- Hitters[-train, ]

câu c) Perform boosting on the training set with 1,000 trees for a range of values of the shrinkage parameter A. Produce a plot with different shrinkage values on the x-axis and the corresponding training set MSE on the y-axis.

# Load thư viện gbm
library(gbm)
# 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)
# Tạo một dãy số với số đầu là -10, số cuối là -0.2, giá trị mỗi bước nhảy là 0.1 
pows <- seq(-10, -0.2, by = 0.1)
# lambdas là một vector chứa giá trị 10^x với x là các giá trị trong vector pows
lambdas <- 10^pows
# Tạo ra một mảng trống với độ dài bằng vector lambdas
train.err <- rep(NA, length(lambdas))
for (i in 1:length(lambdas)) {
    boost.hitters <- gbm(Salary ~ ., data = Hitters.train, distribution = "gaussian", n.trees = 1000, shrinkage = lambdas[i])
    pred.train <- predict(boost.hitters, Hitters.train, n.trees = 1000)
    train.err[i] <- mean((pred.train - Hitters.train$Salary)^2)
}
plot(lambdas, train.err, type = "b", xlab = "Shrinkage values", ylab = "Training MSE")

Với mỗi giá trị trong vector lambdas, ta gán cho mô hình một giá trị learning rate khác nhau, sau đó tiến hành dự đoán và tính MSE trên tập huấn luyện, lưu lại từng giá trị MSE ứng với mỗi learning rate đó trong mảng trống train.err

Sau đó ta biểu diễn trên biểu đồ với x là giá trị learning của mô hình, y là giá trị MSE trên tập huấn luyện, ta thấy với learning rate khoảng gần 0.65 thì mô hình cho kết quả MSE trên tập train là nhỏ nhất

Câu d) Produce a plot with different shrinkage values on the x-axis and the corresponding test set MSE on the y-axis.

# 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)
# Tạo ra một mảng trống với độ dài bằng vector lambdas để lưu các giá trị MSE tương ứng mỗi learning rate khác nhau
test.err <- rep(NA, length(lambdas))
for (i in 1:length(lambdas)) {
    boost.hitters <- gbm(Salary ~ ., data = Hitters.train, distribution = "gaussian", n.trees = 1000, shrinkage = lambdas[i])
    yhat <- predict(boost.hitters, Hitters.test, n.trees = 1000)
    test.err[i] <- mean((yhat - Hitters.test$Salary)^2)
}
plot(lambdas, test.err, type = "b", xlab = "Shrinkage values", ylab = "Test MSE")

Với mỗi giá trị trong vector lambdas, ta gán cho mô hình một giá trị learning rate khác nhau, sau đó tiến hành dự đoán và tính MSE trên tập test, lưu lại từng giá trị MSE ứng với mỗi learning rate đó trong mảng trống test.err

Sau đó ta biểu diễn trên biểu đồ với x là giá trị learning của mô hình, y là giá trị MSE trên tập test

# Giá trị MSE nhỏ nhất trên tập test
min(test.err)
[1] 0.2540265
# Giá trị learning khiến cho MSE trên tập test là nhỏ nhất 
lambdas[which.min(test.err)]
[1] 0.07943282

Câu e) Compare the test MSE of boosting to the test MSE that results from applying two of the regression approaches seen in Chapters 3 and 6.

# Load thư viện glmnet
library(glmnet)
# Tạo mô hình hồi quy bội với Salary là biến đầu ra và các biến lại là biến đầu vào, tập huấn luyện là Hitters.train
fit1 <- lm(Salary ~ ., data = Hitters.train)
# Tiến hành dự đoán trên tập test
pred1 <- predict(fit1, Hitters.test)
# Tính Mean Squared Error (MSE) giữa giá trị dự đoán và giá trị thật
mean((pred1 - Hitters.test$Salary)^2)
[1] 0.4917959

MSE khi dùng hồi quy bội là 0.4917959

# Tạo 1 một ma trận mô hình với biến đầu ra là Salary, biến đầu vào là các biến loại, tập dữ liệu là tập Hitters.train
x <- model.matrix(Salary ~ ., data = Hitters.train)
# Tạo 1 một ma trận mô hình với biến đầu ra là Salary, biến đầu vào là các biến loại, tập dữ liệu là tập Hitters.test
x.test <- model.matrix(Salary ~ ., data = Hitters.test)
# Lấy biến Salary trên tập Hitters.train
y <- Hitters.train$Salary
# Tạo mô hình ridge regression với x là biến đầu vào, đầu ra là y, alpha =0 nhằm sử dụng ridge penalty
fit2 <- glmnet(x, y, alpha = 0)
# Tiến hành dự đoán trên tập x.test với s=0.01 là giá trị áp dụng cho ridge penalty
pred2 <- predict(fit2, s = 0.01, newx = x.test)
# Tính Mean Squared Error (MSE) giữa giá trị dự đoán và giá trị thật\
mean((pred2 - Hitters.test$Salary)^2)
[1] 0.4570283

MSE khi dùng ridge regression là 0.4570283

Ta thấy là MSE khi dùng mô hình boosting thấp hơn cả linear regression và ridge regression

Câu f)Which variables appear to be the most important predictors in the boosted model?

# Xây dụng mô hình boosting với biến đầu ra là Salary và các biến còn lại là biến đầu vào, mô hình dùng phân phối gaussian, số lượng cây = 1000, learning rate bằng giá trị mà cho ra MSE nhỏ nhất trên tập test
boost.hitters <- gbm(Salary ~ ., data = Hitters.train, distribution = "gaussian", n.trees = 1000, shrinkage = lambdas[which.min(test.err)])
# Hiển thị biểu dô mức quan trọng các biến
summary(boost.hitters)

Ta thấy biến CAtBat là biến quan trọng nhất là mức độ ảnh hưởng là 22.9336528

Câu g) Now apply bagging to the training set. What is the test set MSE for this approach?

# 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)
# Tạo mô hình randomforest để dự đoan giá trị Salary là biến đầu ra và lấy tất cả các biến còn lại làm biến đầu vào trong tập Hitters.train
bag.hitters <- randomForest(Salary ~ ., data = Hitters.train, mtry = 19, ntree = 500)
# Đưa ra dự đoán trên tập test ,biến yhat.bag trả về các giá trị dự đoán trên tập test
yhat.bag <- predict(bag.hitters, newdata = Hitters.test)
# Tính Mean Squared Error (MSE) giữa giá trị dự đoán và giá trị thật
mean((yhat.bag - Hitters.test$Salary)^2)
[1] 0.2299324

Ta thấy MSE của mô hình bagging là 0.2299324, nhỏ hơn so với mô hình boosting(0.2540265)

LS0tDQp0aXRsZTogIkLDoGkgdOG6rXAgMV8gdHXhuqduIDEwIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMjIFTDqm46IE1haSBIdXkNCiMjIyBNU1NWOiA0My4wMS4xMDQuMDY1DQojIyMgU+G7kSB0aOG7qSB04buxOiAwOA0KDQojIyMgQ8OidSBhKSBSZW1vdmUgdGhlIG9ic2VydmF0aW9ucyBmb3Igd2hvbSB0aGUgc2FsYXJ5IGluZm9ybWF0aW9uIGlzIHVua25vd24sIGFuZCB0aGVuIGxvZy10cmFuc2Zvcm0gdGhlIHNhbGFyaWVzLg0KDQpgYGB7cn0NCiMgTG9hZCB0aMawIHZp4buHbiBNYXNzDQpsaWJyYXJ5KE1BU1MpDQojIExvYWQgdGjGsCB2aeG7h24gSVNMUg0KbGlicmFyeShJU0xSKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCiMgQuG7jyBuaOG7r25nIHF1YW4gc8OhdCBtw6AgdGjDtG5nIHRpbiBi4buLIHRy4buRbmcgdsOgIGPhuq1wIG5o4bqtcCBs4bqhaSB04bqtcCBk4buvIGxp4buHdSBIaXRsZXJzIG3hu5tpDQpIaXR0ZXJzIDwtIG5hLm9taXQoSGl0dGVycykNCiMgVHLhuqMgduG7gSBj4buZdCBzYWxhcnkgc2F1IGtoaSDEkcaw4bujYyBsb2cNCkhpdHRlcnMkU2FsYXJ5IDwtIGxvZyhIaXR0ZXJzJFNhbGFyeSkNCmBgYA0KDQojIyMgQ8OidSBiKSBDcmVhdGUgYSB0cmFpbmluZyBzZXQgY29uc2lzdGluZyBvZiB0aGUgZmlyc3QgMjAwIG9ic2VydmF0aW9ucywgYW5kIGEgdGVzdCBzZXQgY29uc2lzdGluZyBvZiB0aGUgcmVtYWluaW5nIG9ic2VydmF0aW9ucy4NCg0KDQpgYGB7cn0NCiMgVOG6rXAgdHJhaW4gZ+G7k20gMjAwIHF1YW4gc8OhdCDEkeG6p3UNCnRyYWluIDwtIDE6MjAwDQojIEzhuqV5IDIwMCBxdWFuIHPDoXQgxJHhuqd1IHRyb25nIHThuq1wIGThu68gbGnhu4d1IEhpdHRlcnMgbMOgbSB04bqtcCB0cmFpbg0KSGl0dGVycy50cmFpbiA8LSBIaXR0ZXJzW3RyYWluLCBdDQojIEzhuqV5IGPDoWMgcXVhbiBzw6F0IGPDsm4gbOG6oWkgbmdvw6BpIHThuq1wIHRyYWluIGzDoG0gdOG6rXAgdGVzdA0KSGl0dGVycy50ZXN0IDwtIEhpdHRlcnNbLXRyYWluLCBdDQpgYGANCg0KIyMjIGPDonUgYykgUGVyZm9ybSBib29zdGluZyBvbiB0aGUgdHJhaW5pbmcgc2V0IHdpdGggMSwwMDAgdHJlZXMgZm9yIGEgcmFuZ2Ugb2YgdmFsdWVzIG9mIHRoZSBzaHJpbmthZ2UgcGFyYW1ldGVyIEEuIFByb2R1Y2UgYSBwbG90IHdpdGggZGlmZmVyZW50IHNocmlua2FnZSB2YWx1ZXMgb24gdGhlIHgtYXhpcyBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgdHJhaW5pbmcgc2V0IE1TRSBvbiB0aGUgeS1heGlzLg0KDQpgYGB7cn0NCmBgYA0KDQoNCg0KDQpgYGB7cn0NCiMgTG9hZCB0aMawIHZp4buHbiBnYm0NCmxpYnJhcnkoZ2JtKQ0KIyBzZXQuc2VlZCBkw7luZyDEkeG7gyB0w6FpIHThuqFvIG5o4buvbmcgdmVjdG9yIHJhbmRvbSBnaeG7kW5nIG5oYXUgdGhlbyB0xrDGoW5nIOG7qW5nIHbhu5tpIGdpw6EgdHLhu4sgxJHGsOG7o2MgxJHGsGEgdsOgbyBow6BtIHNlZWQNCnNldC5zZWVkKDEpDQojIFThuqFvIG3hu5l0IGTDo3kgc+G7kSB24bubaSBz4buRIMSR4bqndSBsw6AgLTEwLCBz4buRIGN14buRaSBsw6AgLTAuMiwgZ2nDoSB0cuG7iyBt4buXaSBixrDhu5tjIG5o4bqjeSBsw6AgMC4xIA0KcG93cyA8LSBzZXEoLTEwLCAtMC4yLCBieSA9IDAuMSkNCiMgbGFtYmRhcyBsw6AgbeG7mXQgdmVjdG9yIGNo4bupYSBnacOhIHRy4buLIDEwXnggduG7m2kgeCBsw6AgY8OhYyBnacOhIHRy4buLIHRyb25nIHZlY3RvciBwb3dzDQpsYW1iZGFzIDwtIDEwXnBvd3MNCiMgVOG6oW8gcmEgbeG7mXQgbeG6o25nIHRy4buRbmcgduG7m2kgxJHhu5kgZMOgaSBi4bqxbmcgdmVjdG9yIGxhbWJkYXMNCnRyYWluLmVyciA8LSByZXAoTkEsIGxlbmd0aChsYW1iZGFzKSkNCmBgYA0KDQpgYGB7cn0NCmZvciAoaSBpbiAxOmxlbmd0aChsYW1iZGFzKSkgew0KICAgIGJvb3N0LmhpdHRlcnMgPC0gZ2JtKFNhbGFyeSB+IC4sIGRhdGEgPSBIaXR0ZXJzLnRyYWluLCBkaXN0cmlidXRpb24gPSAiZ2F1c3NpYW4iLCBuLnRyZWVzID0gMTAwMCwgc2hyaW5rYWdlID0gbGFtYmRhc1tpXSkNCiAgICBwcmVkLnRyYWluIDwtIHByZWRpY3QoYm9vc3QuaGl0dGVycywgSGl0dGVycy50cmFpbiwgbi50cmVlcyA9IDEwMDApDQogICAgdHJhaW4uZXJyW2ldIDwtIG1lYW4oKHByZWQudHJhaW4gLSBIaXR0ZXJzLnRyYWluJFNhbGFyeSleMikNCn0NCnBsb3QobGFtYmRhcywgdHJhaW4uZXJyLCB0eXBlID0gImIiLCB4bGFiID0gIlNocmlua2FnZSB2YWx1ZXMiLCB5bGFiID0gIlRyYWluaW5nIE1TRSIpDQpgYGANCg0KVuG7m2kgbeG7l2kgZ2nDoSB0cuG7iyB0cm9uZyB2ZWN0b3IgbGFtYmRhcywgdGEgZ8OhbiBjaG8gbcO0IGjDrG5oIG3hu5l0IGdpw6EgdHLhu4sgbGVhcm5pbmcgcmF0ZSBraMOhYyBuaGF1LCBzYXUgxJHDsyB0aeG6v24gaMOgbmggZOG7sSDEkW/DoW4gdsOgIHTDrW5oIE1TRSB0csOqbiB04bqtcCBodeG6pW4gbHV54buHbiwgbMawdSBs4bqhaSB04burbmcgZ2nDoSB0cuG7iyBNU0Ug4bupbmcgduG7m2kgbeG7l2kgbGVhcm5pbmcgcmF0ZSDEkcOzIHRyb25nIG3huqNuZyB0cuG7kW5nIHRyYWluLmVycg0KDQpTYXUgxJHDsyB0YSBiaeG7g3UgZGnhu4VuIHRyw6puIGJp4buDdSDEkeG7kyB24bubaSB4IGzDoCBnacOhIHRy4buLIGxlYXJuaW5nIGPhu6dhIG3DtCBow6xuaCwgeSBsw6AgZ2nDoSB0cuG7iyBNU0UgdHLDqm4gdOG6rXAgaHXhuqVuIGx1eeG7h24sIHRhIHRo4bqleSB24bubaSBsZWFybmluZyByYXRlIGtob+G6o25nIGfhuqduIDAuNjUgdGjDrCBtw7QgaMOsbmggY2hvIGvhur90IHF14bqjIE1TRSB0csOqbiB04bqtcCB0cmFpbiBsw6Agbmjhu48gbmjhuqV0DQoNCiMjIyBDw6J1IGQpIFByb2R1Y2UgYSBwbG90IHdpdGggZGlmZmVyZW50IHNocmlua2FnZSB2YWx1ZXMgb24gdGhlIHgtYXhpcyBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgdGVzdCBzZXQgTVNFIG9uIHRoZSB5LWF4aXMuDQoNCg0KYGBge3J9DQojIHNldC5zZWVkIGTDuW5nIMSR4buDIHTDoWkgdOG6oW8gbmjhu69uZyB2ZWN0b3IgcmFuZG9tIGdp4buRbmcgbmhhdSB0aGVvIHTGsMahbmcg4bupbmcgduG7m2kgZ2nDoSB0cuG7iyDEkcaw4bujYyDEkcawYSB2w6BvIGjDoG0gc2VlZA0Kc2V0LnNlZWQoMSkNCiMgVOG6oW8gcmEgbeG7mXQgbeG6o25nIHRy4buRbmcgduG7m2kgxJHhu5kgZMOgaSBi4bqxbmcgdmVjdG9yIGxhbWJkYXMgxJHhu4MgbMawdSBjw6FjIGdpw6EgdHLhu4sgTVNFIHTGsMahbmcg4bupbmcgbeG7l2kgbGVhcm5pbmcgcmF0ZSBraMOhYyBuaGF1DQp0ZXN0LmVyciA8LSByZXAoTkEsIGxlbmd0aChsYW1iZGFzKSkNCmZvciAoaSBpbiAxOmxlbmd0aChsYW1iZGFzKSkgew0KICAgIGJvb3N0LmhpdHRlcnMgPC0gZ2JtKFNhbGFyeSB+IC4sIGRhdGEgPSBIaXR0ZXJzLnRyYWluLCBkaXN0cmlidXRpb24gPSAiZ2F1c3NpYW4iLCBuLnRyZWVzID0gMTAwMCwgc2hyaW5rYWdlID0gbGFtYmRhc1tpXSkNCiAgICB5aGF0IDwtIHByZWRpY3QoYm9vc3QuaGl0dGVycywgSGl0dGVycy50ZXN0LCBuLnRyZWVzID0gMTAwMCkNCiAgICB0ZXN0LmVycltpXSA8LSBtZWFuKCh5aGF0IC0gSGl0dGVycy50ZXN0JFNhbGFyeSleMikNCn0NCnBsb3QobGFtYmRhcywgdGVzdC5lcnIsIHR5cGUgPSAiYiIsIHhsYWIgPSAiU2hyaW5rYWdlIHZhbHVlcyIsIHlsYWIgPSAiVGVzdCBNU0UiKQ0KYGBgDQoNClbhu5tpIG3hu5dpIGdpw6EgdHLhu4sgdHJvbmcgdmVjdG9yIGxhbWJkYXMsIHRhIGfDoW4gY2hvIG3DtCBow6xuaCBt4buZdCBnacOhIHRy4buLIGxlYXJuaW5nIHJhdGUga2jDoWMgbmhhdSwgc2F1IMSRw7MgdGnhur9uIGjDoG5oIGThu7EgxJFvw6FuIHbDoCB0w61uaCBNU0UgdHLDqm4gdOG6rXAgdGVzdCwgbMawdSBs4bqhaSB04burbmcgZ2nDoSB0cuG7iyBNU0Ug4bupbmcgduG7m2kgbeG7l2kgbGVhcm5pbmcgcmF0ZSDEkcOzIHRyb25nIG3huqNuZyB0cuG7kW5nIHRlc3QuZXJyDQoNClNhdSDEkcOzIHRhIGJp4buDdSBkaeG7hW4gdHLDqm4gYmnhu4N1IMSR4buTIHbhu5tpIHggbMOgIGdpw6EgdHLhu4sgbGVhcm5pbmcgY+G7p2EgbcO0IGjDrG5oLCB5IGzDoCBnacOhIHRy4buLIE1TRSB0csOqbiB04bqtcCB0ZXN0DQoNCmBgYHtyfQ0KIyBHacOhIHRy4buLIE1TRSBuaOG7jyBuaOG6pXQgdHLDqm4gdOG6rXAgdGVzdA0KbWluKHRlc3QuZXJyKQ0KYGBgDQoNCmBgYHtyfQ0KIyBHacOhIHRy4buLIGxlYXJuaW5nIGtoaeG6v24gY2hvIE1TRSB0csOqbiB04bqtcCB0ZXN0IGzDoCBuaOG7jyBuaOG6pXQgDQpsYW1iZGFzW3doaWNoLm1pbih0ZXN0LmVycildDQpgYGANCg0KIyMjIEPDonUgZSkgQ29tcGFyZSB0aGUgdGVzdCBNU0Ugb2YgYm9vc3RpbmcgdG8gdGhlIHRlc3QgTVNFIHRoYXQgcmVzdWx0cyBmcm9tIGFwcGx5aW5nIHR3byBvZiB0aGUgcmVncmVzc2lvbiBhcHByb2FjaGVzIHNlZW4gaW4gQ2hhcHRlcnMgMyBhbmQgNi4NCg0KDQoNCmBgYHtyfQ0KIyBMb2FkIHRoxrAgdmnhu4duIGdsbW5ldA0KbGlicmFyeShnbG1uZXQpDQpgYGANCg0KYGBge3J9DQojIFThuqFvIG3DtCBow6xuaCBo4buTaSBxdXkgYuG7mWkgduG7m2kgU2FsYXJ5IGzDoCBiaeG6v24gxJHhuqd1IHJhIHbDoCBjw6FjIGJp4bq/biBs4bqhaSBsw6AgYmnhur9uIMSR4bqndSB2w6BvLCB04bqtcCBodeG6pW4gbHV54buHbiBsw6AgSGl0dGVycy50cmFpbg0KZml0MSA8LSBsbShTYWxhcnkgfiAuLCBkYXRhID0gSGl0dGVycy50cmFpbikNCiMgVGnhur9uIGjDoG5oIGThu7EgxJFvw6FuIHRyw6puIHThuq1wIHRlc3QNCnByZWQxIDwtIHByZWRpY3QoZml0MSwgSGl0dGVycy50ZXN0KQ0KIyBUw61uaCBNZWFuIFNxdWFyZWQgRXJyb3IgKE1TRSkgZ2nhu69hIGdpw6EgdHLhu4sgZOG7sSDEkW/DoW4gdsOgIGdpw6EgdHLhu4sgdGjhuq10DQptZWFuKChwcmVkMSAtIEhpdHRlcnMudGVzdCRTYWxhcnkpXjIpDQpgYGANCg0KTVNFIGtoaSBkw7luZyBo4buTaSBxdXkgYuG7mWkgbMOgICAwLjQ5MTc5NTkNCg0KYGBge3J9DQojIFThuqFvIDEgbeG7mXQgbWEgdHLhuq1uIG3DtCBow6xuaCB24bubaSBiaeG6v24gxJHhuqd1IHJhIGzDoCBTYWxhcnksIGJp4bq/biDEkeG6p3UgdsOgbyBsw6AgY8OhYyBiaeG6v24gbG/huqFpLCB04bqtcCBk4buvIGxp4buHdSBsw6AgdOG6rXAgSGl0dGVycy50cmFpbg0KeCA8LSBtb2RlbC5tYXRyaXgoU2FsYXJ5IH4gLiwgZGF0YSA9IEhpdHRlcnMudHJhaW4pDQojIFThuqFvIDEgbeG7mXQgbWEgdHLhuq1uIG3DtCBow6xuaCB24bubaSBiaeG6v24gxJHhuqd1IHJhIGzDoCBTYWxhcnksIGJp4bq/biDEkeG6p3UgdsOgbyBsw6AgY8OhYyBiaeG6v24gbG/huqFpLCB04bqtcCBk4buvIGxp4buHdSBsw6AgdOG6rXAgSGl0dGVycy50ZXN0DQp4LnRlc3QgPC0gbW9kZWwubWF0cml4KFNhbGFyeSB+IC4sIGRhdGEgPSBIaXR0ZXJzLnRlc3QpDQojIEzhuqV5IGJp4bq/biBTYWxhcnkgdHLDqm4gdOG6rXAgSGl0dGVycy50cmFpbg0KeSA8LSBIaXR0ZXJzLnRyYWluJFNhbGFyeQ0KIyBU4bqhbyBtw7QgaMOsbmggcmlkZ2UgcmVncmVzc2lvbiB24bubaSB4IGzDoCBiaeG6v24gxJHhuqd1IHbDoG8sIMSR4bqndSByYSBsw6AgeSwgYWxwaGEgPTAgbmjhurFtIHPhu60gZOG7pW5nIHJpZGdlIHBlbmFsdHkNCmZpdDIgPC0gZ2xtbmV0KHgsIHksIGFscGhhID0gMCkNCiMgVGnhur9uIGjDoG5oIGThu7EgxJFvw6FuIHRyw6puIHThuq1wIHgudGVzdCB24bubaSBzPTAuMDEgbMOgIGdpw6EgdHLhu4sgw6FwIGThu6VuZyBjaG8gcmlkZ2UgcGVuYWx0eQ0KcHJlZDIgPC0gcHJlZGljdChmaXQyLCBzID0gMC4wMSwgbmV3eCA9IHgudGVzdCkNCiMgVMOtbmggTWVhbiBTcXVhcmVkIEVycm9yIChNU0UpIGdp4buvYSBnacOhIHRy4buLIGThu7EgxJFvw6FuIHbDoCBnacOhIHRy4buLIHRo4bqtdFwNCm1lYW4oKHByZWQyIC0gSGl0dGVycy50ZXN0JFNhbGFyeSleMikNCmBgYA0KDQpNU0Uga2hpIGTDuW5nIHJpZGdlIHJlZ3Jlc3Npb24gbMOgICAwLjQ1NzAyODMNCg0KVGEgdGjhuqV5IGzDoCBNU0Uga2hpIGTDuW5nIG3DtCBow6xuaCBib29zdGluZyB0aOG6pXAgaMahbiBj4bqjIGxpbmVhciByZWdyZXNzaW9uIHZhzIAgcmlkZ2UgcmVncmVzc2lvbg0KDQojIyMgQ8OidSBmKVdoaWNoIHZhcmlhYmxlcyBhcHBlYXIgdG8gYmUgdGhlIG1vc3QgaW1wb3J0YW50IHByZWRpY3RvcnMgaW4gdGhlIGJvb3N0ZWQgbW9kZWw/DQoNCmBgYHtyfQ0KIyBYw6J5IGThu6VuZyBtw7QgaMOsbmggYm9vc3RpbmcgduG7m2kgYmnhur9uIMSR4bqndSByYSBsw6AgU2FsYXJ5IHbDoCBjw6FjIGJp4bq/biBjw7JuIGzhuqFpIGzDoCBiaeG6v24gxJHhuqd1IHbDoG8sIG3DtCBow6xuaCBkw7luZyBwaMOibiBwaOG7kWkgZ2F1c3NpYW4sIHPhu5EgbMaw4bujbmcgY8OieSA9IDEwMDAsIGxlYXJuaW5nIHJhdGUgYuG6sW5nIGdpw6EgdHLhu4sgbcOgIGNobyByYSBNU0Ugbmjhu48gbmjhuqV0IHRyw6puIHThuq1wIHRlc3QNCmJvb3N0LmhpdHRlcnMgPC0gZ2JtKFNhbGFyeSB+IC4sIGRhdGEgPSBIaXR0ZXJzLnRyYWluLCBkaXN0cmlidXRpb24gPSAiZ2F1c3NpYW4iLCBuLnRyZWVzID0gMTAwMCwgc2hyaW5rYWdlID0gbGFtYmRhc1t3aGljaC5taW4odGVzdC5lcnIpXSkNCiMgSGnhu4NuIHRo4buLIGJp4buDdSBkw7QgbeG7qWMgcXVhbiB0cuG7jW5nIGPDoWMgYmnhur9uDQpzdW1tYXJ5KGJvb3N0LmhpdHRlcnMpDQpgYGANCg0KVGEgdGjhuqV5IGJp4bq/biBDQXRCYXQgbMOgIGJp4bq/biBxdWFuIHRy4buNbmcgbmjhuqV0IGzDoCBt4bupYyDEkeG7mSDhuqNuaCBoxrDhu59uZyBsw6AgMjIuOTMzNjUyOAkNCg0KIyMjIEPDonUgZykgTm93IGFwcGx5IGJhZ2dpbmcgdG8gdGhlIHRyYWluaW5nIHNldC4gV2hhdCBpcyB0aGUgdGVzdCBzZXQgTVNFIGZvciB0aGlzIGFwcHJvYWNoPw0KDQpgYGB7cn0NCiMgc2V0LnNlZWQgZMO5bmcgxJHhu4MgdMOhaSB04bqhbyBuaOG7r25nIHZlY3RvciByYW5kb20gZ2nhu5FuZyBuaGF1IHRoZW8gdMawxqFuZyDhu6luZyB24bubaSBnacOhIHRy4buLIMSRxrDhu6NjIMSRxrBhIHbDoG8gaMOgbSBzZWVkDQpzZXQuc2VlZCgxKQ0KIyBU4bqhbyBtw7QgaMOsbmggcmFuZG9tZm9yZXN0IMSR4buDIGThu7EgxJFvYW4gZ2nDoSB0cuG7iyBTYWxhcnkgbMOgIGJp4bq/biDEkeG6p3UgcmEgdsOgIGzhuqV5IHThuqV0IGPhuqMgY8OhYyBiaeG6v24gY8OybiBs4bqhaSBsw6BtIGJp4bq/biDEkeG6p3UgdsOgbyB0cm9uZyB04bqtcCBIaXR0ZXJzLnRyYWluDQpiYWcuaGl0dGVycyA8LSByYW5kb21Gb3Jlc3QoU2FsYXJ5IH4gLiwgZGF0YSA9IEhpdHRlcnMudHJhaW4sIG10cnkgPSAxOSwgbnRyZWUgPSA1MDApDQpgYGANCg0KLSBtdHJ5ID0xMzogY8OzIDE5IHnhur91IHThu5EgxJHhuqd1IHbDoG8gxJHGsOG7o2Mgc+G7rSBk4bulbmcgY2hvIG3hu5dpIGzDumMgcGjDom4gY2hpYSBjw6J5IA0KLSBudHJlZSA9IDUwMDogc+G7kSBsxrDhu6NuZyBjw6J5IGzDoCA1MDANCg0KYGBge3J9DQojIMSQxrBhIHJhIGThu7EgxJFvw6FuIHRyw6puIHThuq1wIHRlc3QgLGJp4bq/biB5aGF0LmJhZyB0cuG6oyB24buBIGPDoWMgZ2nDoSB0cuG7iyBk4buxIMSRb8OhbiB0csOqbiB04bqtcCB0ZXN0DQp5aGF0LmJhZyA8LSBwcmVkaWN0KGJhZy5oaXR0ZXJzLCBuZXdkYXRhID0gSGl0dGVycy50ZXN0KQ0KIyBUw61uaCBNZWFuIFNxdWFyZWQgRXJyb3IgKE1TRSkgZ2nhu69hIGdpw6EgdHLhu4sgZOG7sSDEkW/DoW4gdsOgIGdpw6EgdHLhu4sgdGjhuq10DQptZWFuKCh5aGF0LmJhZyAtIEhpdHRlcnMudGVzdCRTYWxhcnkpXjIpDQpgYGANCg0KVGEgdGjhuqV5IE1TRSBj4bunYSBtw7QgaMOsbmggYmFnZ2luZyBsw6AgMC4yMjk5MzI0LCBuaOG7jyBoxqFuIHNvIHbhu5tpIG3DtCBow6xuaCBib29zdGluZygwLjI1NDAyNjUpDQo=