Tên: Mai Huy

MSSV: 43.01.104.065

Số thứ tự: 08

# 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 ma trận chứa 40 giá trị ngẫu nhiên , số lượng cột là 2 để tạo thành ma trận có kích thước 20x2 
x=matrix(rnorm(20*2) , ncol=2)
# Tạo một vector chứa 20 giá trị, 10 giá trị đầu là -1, 10 giá trị sau là 1
y=c(rep(-1,10), rep(1,10))
# Tăng giá trị của 10 dòng sau của ma trận X lên 1 ứng với giá trị vector y = 1
x[y==1,] = x[y==1 ,]+1
# Hiển thị biểu đồ phân tán để xem những quan sát được tạo trong x và y có độc lập tuyến tính hay không
plot(x, col=(3-y))

# Tạo một dữ liệu từ x và y ở trên, với y đã được chuyển thành thuộc tính factor
dat=data.frame(x=x, y=as.factor(y))
# Load thư viện e1071
library(e1071)
# Tạo mô hình SVM từ tập data được tạo
svmfit = svm(y~., data=dat, kernel="linear", cost=10,scale=FALSE)
svmfit

Call:
svm(formula = y ~ ., data = dat, kernel = "linear", cost = 10, scale = FALSE)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  linear 
       cost:  10 

Number of Support Vectors:  7

số lượng điểm nằm trên 2 đường biên của 2 phân lớp là 7, tham số cost = 10, SVM-kernel dạng tuyến tính, loại mô hình SVM là classification, Scale = true để mô hình không chia tỉ lệ mỗi feature và có giá trị trung bình = 0, độ lệch chuẩn = 1

# Hiển thị biểu đồ biểu diễn dữ liệu với x1 và x2 lần lượt trục tung và trục tung, bên trong đồ thị các điểm quan sát đã được phân lớp thông qua mô hình SVM, mmàu đỏ thuộc về y=1, màu vàng thuộc về y = -1
plot(svmfit, dat)

# Hiển thị những điểm support vector
svmfit$index
[1]  1  2  5  7 14 16 17
# Phân tích mô hình SVM vừa được tạo
summary(svmfit)

Call:
svm(formula = y ~ ., data = dat, kernel = "linear", cost = 10, scale = FALSE)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  linear 
       cost:  10 

Number of Support Vectors:  7

 ( 4 3 )


Number of Classes:  2 

Levels: 
 -1 1
# Tạo mô hình SVM với y là đầu ra và đầu vào là x, kernel là dạng linear, tham số cost = 0.1, mô hình không chia tỉ lệ các đặc trưng
svmfit = svm(y~., data=dat,kernel="linear", cost=0.1,scale=FALSE)
# Hiển thị biểu đồ biểu diễn dữ liệu với x1 và x2 lần lượt trục tung và trục tung, bên trong đồ thị các điểm quan sát đã được phân lớp thông qua mô hình SVM, mmàu đỏ thuộc về y=1, màu vàng thuộc về y = -1
plot(svmfit, dat)

# Hiển thị những điểm support vector
svmfit$index
 [1]  1  2  3  4  5  7  9 10 12 13 14 15 16 17 18 20

Ta thấy rằng với tham số cost nhỏ hơn, mô hình SVM có nhiều điểm support vector hơn do độ rộng margin đã được gia tăng

# 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)
# Hàm tune dùng để thử nghiệm từng tham số cost có giá trị khác nhau cho mô hình SVM tương tự ở trên
tune.out=tune(svm ,y∼.,data=dat ,kernel ="linear", ranges=list(cost=c(0.001, 0.01, 0.1, 1,5,10,100) ))
# Phân tích kết quả sau khi thử nghiệm các giá trị cost khác nhau 
summary(tune.out)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.05 

- Detailed performance results:
NA
# bestmod trả về mô hình với giá trị cost là tốt nhất
bestmod=tune.out$best.model
# Phân tích mô hình SVM tối ưu nhất
summary(bestmod)

Call:
best.tune(method = svm, train.x = y ~ ., data = dat, ranges = list(cost = c(0.001, 0.01, 0.1, 1, 5, 10, 
    100)), kernel = "linear")


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  linear 
       cost:  0.1 

Number of Support Vectors:  16

 ( 8 8 )


Number of Classes:  2 

Levels: 
 -1 1
# Tạo một ma trận chứa 40 giá trị ngẫu nhiên , số lượng cột là 2 để tạo thành ma trận có kích thước 20x2
xtest=matrix(rnorm(20*2) , ncol=2)
# Tạo một vector chứa 20 giá trị, 10 giá trị đầu là -1, 10 giá trị sau là 1
ytest=sample(c(-1,1), 20, rep=TRUE)

Tạo một bộ test cho mô hình

# Tăng giá trị của 10 dòng sau của ma trận X lên 1 ứng với giá trị vector y = 1
xtest[ytest==1,]=xtest[ytest==1,] + 1
# Tạo một dữ liệu từ xtest và ytest ở trên, với ytest đã được chuyển thành thuộc tính factor
testdat=data.frame(x=xtest, y=as.factor(ytest))
# Tiến hành đưa ra dự đoán trên tập testdat dựa trên mô hình tốt nhất với giá trị cost = 0.1
ypred=predict(bestmod,testdat)
# 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(predict =ypred, truth=testdat$y)
       truth
predict -1 1
     -1  9 1
     1   2 8

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

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

# Tạo mô hình SVM với y là đầu ra và đầu vào là x, kernel là dạng linear, tham số cost = 0.01, mô hình không chia tỉ lệ các đặc trưng
svmfit = svm(y~., data=dat, kernel="linear", cost=.01,scale=FALSE)
# Tiến hành đưa ra dự đoán trên tập testdat dựa trên mô hình với giá trị cost = 0.01
ypred=predict(svmfit,testdat)
# 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(predict =ypred, truth=testdat$y)
       truth
predict -1  1
     -1 11  6
     1   0  3

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

=> Tỉ lệ phân loại sai là khoảng 30%, cao hơn so với mô hình trước

# Tăng giá trị của 10 dòng sau của ma trận X lên 0.5 ứng với giá trị vector y = 1
x[y==1,] = x[y == 1 ,]+0.5

VỚi x và y hoàn toàn khác biệt tuyến tính

# Hiển thị biểu đồ cho x và y đã được thay đổi giá trị
plot(x, col=(y+5)/2, pch=19)

# Tạo một dữ liệu từ x và y ở trên, với y đã được chuyển thành thuộc tính factor
dat = data.frame(x=x,y=as.factor(y))
# Tạo mô hình SVM với y là đầu ra và đầu vào là x, kernel là dạng linear, tham số cost = 1e5 rất lớn để cho không quan sát nào bị phân loại sai
svmfit=svm(y~., data=dat, kernel="linear", cost=1e5)
# Phân tích mô hình SVM ở trên
summary(svmfit)

Call:
svm(formula = y ~ ., data = dat, kernel = "linear", cost = 1e+05)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  linear 
       cost:  1e+05 

Number of Support Vectors:  3

 ( 1 2 )


Number of Classes:  2 

Levels: 
 -1 1
# Tạo mô hình SVM với y là đầu ra và đầu vào là x, kernel là dạng linear, điều chỉnh tham số cost nhỏ lại = 1
svmfit=svm(y~., data=dat, kernel = "linear", cost = 1)
# Phân tích mô hình SVM ở trên
summary(svmfit)

Call:
svm(formula = y ~ ., data = dat, kernel = "linear", cost = 1)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  linear 
       cost:  1 

Number of Support Vectors:  7

 ( 4 3 )


Number of Classes:  2 

Levels: 
 -1 1
# Hiển thị biểu đồ biểu diễn dữ liệu với x1 và x2 lần lượt trục tung và trục tung, bên trong đồ thị các điểm quan sát đã được phân lớp thông qua mô hình SVM, mmàu đỏ thuộc về y=1, màu vàng thuộc về y = -1
plot(svmfit,dat)

LS0tDQp0aXRsZTogIlN1cHBvcnQgVmVjdG9yIE1hY2hpbmUiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCiMjIyBUw6puOiBNYWkgSHV5DQojIyMgTVNTVjogNDMuMDEuMTA0LjA2NQ0KIyMjIFPhu5EgdGjhu6kgdOG7sTogMDgNCg0KYGBge3J9DQojIHNldC5zZWVkIGTDuW5nIMSR4buDIHTDoWkgdOG6oW8gbmjhu69uZyB2ZWN0b3IgcmFuZG9tIGdp4buRbmcgbmhhdSB0aGVvIHTGsMahbmcg4bupbmcgduG7m2kgZ2nDoSB0cuG7iyDEkcaw4bujYyDEkcawYSB2w6BvIGjDoG0gc2VlZA0Kc2V0LnNlZWQoMSkNCiMgVOG6oW8gbeG7mXQgbWEgdHLhuq1uIGNo4bupYSA0MCBnacOhIHRy4buLIG5n4bqrdSBuaGnDqm4gLCBz4buRIGzGsOG7o25nIGPhu5l0IGzDoCAyIMSR4buDIHThuqFvIHRow6BuaCBtYSB0cuG6rW4gY8OzIGvDrWNoIHRoxrDhu5tjIDIweDIgDQp4PW1hdHJpeChybm9ybSgyMCoyKSAsIG5jb2w9MikNCiMgVOG6oW8gbeG7mXQgdmVjdG9yIGNo4bupYSAyMCBnacOhIHRy4buLLCAxMCBnacOhIHRy4buLIMSR4bqndSBsw6AgLTEsIDEwIGdpw6EgdHLhu4sgc2F1IGzDoCAxDQp5PWMocmVwKC0xLDEwKSwgcmVwKDEsMTApKQ0KYGBgDQoNCmBgYHtyfQ0KIyBUxINuZyBnacOhIHRy4buLIGPhu6dhIDEwIGTDsm5nIHNhdSBj4bunYSBtYSB0cuG6rW4gWCBsw6puIDEg4bupbmcgduG7m2kgZ2nDoSB0cuG7iyB2ZWN0b3IgeSA9IDENCnhbeT09MSxdID0geFt5PT0xICxdKzENCiMgSGnhu4NuIHRo4buLIGJp4buDdSDEkeG7kyBwaMOibiB0w6FuIMSR4buDIHhlbSBuaOG7r25nIHF1YW4gc8OhdCDEkcaw4bujYyB04bqhbyB0cm9uZyB4IHbDoCB5IGPDsyDEkeG7mWMgbOG6rXAgdHV54bq/biB0w61uaCBoYXkga2jDtG5nDQpwbG90KHgsIGNvbD0oMy15KSkNCmBgYA0KDQoNCg0KDQpgYGB7cn0NCiMgVOG6oW8gbeG7mXQgZOG7ryBsaeG7h3UgdOG7qyB4IHbDoCB5IOG7nyB0csOqbiwgduG7m2kgeSDEkcOjIMSRxrDhu6NjIGNodXnhu4NuIHRow6BuaCB0aHXhu5ljIHTDrW5oIGZhY3Rvcg0KZGF0PWRhdGEuZnJhbWUoeD14LCB5PWFzLmZhY3Rvcih5KSkNCmBgYA0KDQpgYGB7cn0NCiMgTG9hZCB0aMawIHZp4buHbiBlMTA3MQ0KbGlicmFyeShlMTA3MSkNCmBgYA0KDQpgYGB7cn0NCiMgVOG6oW8gbcO0IGjDrG5oIFNWTSB04burIHThuq1wIGRhdGEgxJHGsOG7o2MgdOG6oW8NCnN2bWZpdCA9IHN2bSh5fi4sIGRhdGE9ZGF0LCBrZXJuZWw9ImxpbmVhciIsIGNvc3Q9MTAsc2NhbGU9RkFMU0UpDQpzdm1maXQNCg0KYGBgDQoNCnPhu5EgbMaw4bujbmcgxJFp4buDbSBu4bqxbSB0csOqbiAyIMSRxrDhu51uZyBiacOqbiBj4bunYSAyIHBow6JuIGzhu5twIGzDoCA3LCB0aGFtIHPhu5EgY29zdCA9IDEwLCBTVk0ta2VybmVsIGThuqFuZyB0dXnhur9uIHTDrW5oLCBsb+G6oWkgbcO0IGjDrG5oIFNWTSBsw6AgY2xhc3NpZmljYXRpb24sIFNjYWxlID0gdHJ1ZSDEkeG7gyBtw7QgaMOsbmgga2jDtG5nIGNoaWEgdOG7iSBs4buHIG3hu5dpIGZlYXR1cmUgdsOgIGPDsyBnacOhIHRy4buLIHRydW5nIGLDrG5oID0gMCwgxJHhu5kgbOG7h2NoIGNodeG6qW4gPSAxIA0KDQoNCmBgYHtyfQ0KIyBIaeG7g24gdGjhu4sgYmnhu4N1IMSR4buTIGJp4buDdSBkaeG7hW4gZOG7ryBsaeG7h3UgduG7m2kgeDEgdsOgIHgyIGzhuqduIGzGsOG7o3QgdHLhu6VjIHR1bmcgdsOgIHRy4bulYyB0dW5nLCBiw6puIHRyb25nIMSR4buTIHRo4buLIGPDoWMgxJFp4buDbSBxdWFuIHPDoXQgxJHDoyDEkcaw4bujYyBwaMOibiBs4bubcCB0aMO0bmcgcXVhIG3DtCBow6xuaCBTVk0sIG1tw6B1IMSR4buPIHRodeG7mWMgduG7gSB5PTEsIG3DoHUgdsOgbmcgdGh14buZYyB24buBIHkgPSAtMQ0KcGxvdChzdm1maXQsIGRhdCkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBIaeG7g24gdGjhu4sgbmjhu69uZyDEkWnhu4NtIHN1cHBvcnQgdmVjdG9yDQpzdm1maXQkaW5kZXgNCmBgYA0KDQpgYGB7cn0NCiMgUGjDom4gdMOtY2ggbcO0IGjDrG5oIFNWTSB24burYSDEkcaw4bujYyB04bqhbw0Kc3VtbWFyeShzdm1maXQpDQpgYGANCg0KLSBNw7QgaMOsbmgg4bufIGThuqFuZyBDbGFzc2lmaWNhdGlvbg0KLSBLZXJuZWwg4bufIGThuqFuZyB0dXnhur9uIHTDrW5oDQotIFRoYW0gc+G7kSBjb3N0ID0gMTANCi0gU+G7kSBsxrDhu6NuZyDEkWnhu4NtIG7hurFtIHRyw6puIDIgxJHGsOG7nW5nIGJpw6puIGzDoCA3LCA0IGNobyBs4bubcCAxIHbDoCAzIGNobyBs4bubcCAyDQotIFPhu5EgbMaw4bujbmcgcGjDom4gbOG7m3AgOiAyDQotIEdpw6EgdHLhu4sgMiBs4bubcCBsw6AgLTEgdsOgIDENCg0KYGBge3J9DQojIFThuqFvIG3DtCBow6xuaCBTVk0gduG7m2kgeSBsw6AgxJHhuqd1IHJhIHbDoCDEkeG6p3UgdsOgbyBsw6AgeCwga2VybmVsIGzDoCBk4bqhbmcgbGluZWFyLCB0aGFtIHPhu5EgY29zdCA9IDAuMSwgbcO0IGjDrG5oIGtow7RuZyBjaGlhIHThu4kgbOG7hyBjw6FjIMSR4bq3YyB0csawbmcNCnN2bWZpdCA9IHN2bSh5fi4sIGRhdGE9ZGF0LGtlcm5lbD0ibGluZWFyIiwgY29zdD0wLjEsc2NhbGU9RkFMU0UpDQpgYGANCg0KYGBge3J9DQojIEhp4buDbiB0aOG7iyBiaeG7g3UgxJHhu5MgYmnhu4N1IGRp4buFbiBk4buvIGxp4buHdSB24bubaSB4MSB2w6AgeDIgbOG6p24gbMaw4bujdCB0cuG7pWMgdHVuZyB2w6AgdHLhu6VjIHR1bmcsIGLDqm4gdHJvbmcgxJHhu5MgdGjhu4sgY8OhYyDEkWnhu4NtIHF1YW4gc8OhdCDEkcOjIMSRxrDhu6NjIHBow6JuIGzhu5twIHRow7RuZyBxdWEgbcO0IGjDrG5oIFNWTSwgbW3DoHUgxJHhu48gdGh14buZYyB24buBIHk9MSwgbcOgdSB2w6BuZyB0aHXhu5ljIHbhu4EgeSA9IC0xDQpwbG90KHN2bWZpdCwgZGF0KQ0KYGBgDQoNCmBgYHtyfQ0KIyBIaeG7g24gdGjhu4sgbmjhu69uZyDEkWnhu4NtIHN1cHBvcnQgdmVjdG9yDQpzdm1maXQkaW5kZXgNCmBgYA0KDQpUYSB0aOG6pXkgcuG6sW5nIHbhu5tpIHRoYW0gc+G7kSBjb3N0IG5o4buPIGjGoW4sIG3DtCBow6xuaCBTVk0gY8OzIG5oaeG7gXUgxJFp4buDbSBzdXBwb3J0IHZlY3RvciBoxqFuIGRvIMSR4buZIHLhu5luZyBtYXJnaW4gxJHDoyDEkcaw4bujYyBnaWEgdMSDbmcNCg0KYGBge3J9DQojIHNldC5zZWVkIGTDuW5nIMSR4buDIHTDoWkgdOG6oW8gbmjhu69uZyB2ZWN0b3IgcmFuZG9tIGdp4buRbmcgbmhhdSB0aGVvIHTGsMahbmcg4bupbmcgduG7m2kgZ2nDoSB0cuG7iyDEkcaw4bujYyDEkcawYSB2w6BvIGjDoG0gc2VlZA0Kc2V0LnNlZWQgKDEpDQojIEjDoG0gdHVuZSBkw7luZyDEkeG7gyB0aOG7rSBuZ2hp4buHbSB04burbmcgdGhhbSBz4buRIGNvc3QgY8OzIGdpw6EgdHLhu4sga2jDoWMgbmhhdSBjaG8gbcO0IGjDrG5oIFNWTSB0xrDGoW5nIHThu7Eg4bufIHRyw6puDQp0dW5lLm91dD10dW5lKHN2bSAseeKIvC4sZGF0YT1kYXQgLGtlcm5lbCA9ImxpbmVhciIsIHJhbmdlcz1saXN0KGNvc3Q9YygwLjAwMSwgMC4wMSwgMC4xLCAxLDUsMTAsMTAwKSApKQ0KYGBgDQoNCmBgYHtyfQ0KIyBQaMOibiB0w61jaCBr4bq/dCBxdeG6oyBzYXUga2hpIHRo4butIG5naGnhu4dtIGPDoWMgZ2nDoSB0cuG7iyBjb3N0IGtow6FjIG5oYXUgDQpzdW1tYXJ5KHR1bmUub3V0KQ0KDQpgYGANCg0KLSBQaMawxqFuZyB0aOG7qWMgbOG6pXkgbeG6q3UgxJHhu4MgdGjhu60gbmdoaeG7h20gOiAxMC1mb2xkIGNyb3NzIHZhbGlkYXRpb24NCi0gVuG7m2kgZ2nDoSB0cuG7iyB0aGFtIHPhu5EgY29zdCA9IDAuMSBtw7QgaMOsbmggY2hvIHJhIMSRxrDhu6NjIHNhaSBz4buRIGzhu5dpIHRyw6puIHThuq1wIGNyb3NzLXZhbGlkYXRpb24gbMOgIG5o4buPIG5o4bqldCAoMC4wNSkNCi0gSMOgbSBzdW1tYXJ5IGPFqW5nIGNobyBiaeG6v3QgZ2nDoSB0cuG7iyBzYWkgc+G7kSBs4buXaSB0csOqbiB04bqtcCBjcm9zcy12YWxpZGF0aW9uIOG7qW5nIHbhu5tpIG3hu5dpIGdpw6EgdHLhu4sgY29zIGPFqW5nIG5oxrAgZ2nDoSB0cuG7iyBwaMOibiB0w6FuIGPhu6dhIG7Dsw0KDQpgYGB7cn0NCiMgYmVzdG1vZCB0cuG6oyB24buBIG3DtCBow6xuaCB24bubaSBnacOhIHRy4buLIGNvc3QgbMOgIHThu5F0IG5o4bqldA0KYmVzdG1vZD10dW5lLm91dCRiZXN0Lm1vZGVsDQojIFBow6JuIHTDrWNoIG3DtCBow6xuaCBTVk0gdOG7kWkgxrB1IG5o4bqldA0Kc3VtbWFyeShiZXN0bW9kKQ0KYGBgDQoNCi0gTcO0IGjDrG5oIOG7nyBk4bqhbmcgQ2xhc3NpZmljYXRpb24NCi0gS2VybmVsIOG7nyBk4bqhbmcgdHV54bq/biB0w61uaA0KLSBUaGFtIHPhu5EgY29zdCA9IDAuMQ0KLSBT4buRIGzGsOG7o25nIMSRaeG7g20gbuG6sW0gdHLDqm4gMiDEkcaw4budbmcgYmnDqm4gbMOgIDE2LCA4IGNobyBs4bubcCAxIHbDoCA4IGNobyBs4bubcCAyDQotIFPhu5EgbMaw4bujbmcgcGjDom4gbOG7m3AgOiAyDQotIEdpw6EgdHLhu4sgMiBs4bubcCBsw6AgLTEgdsOgIDENCg0KYGBge3J9DQojIFThuqFvIG3hu5l0IG1hIHRy4bqtbiBjaOG7qWEgNDAgZ2nDoSB0cuG7iyBuZ+G6q3Ugbmhpw6puICwgc+G7kSBsxrDhu6NuZyBj4buZdCBsw6AgMiDEkeG7gyB04bqhbyB0aMOgbmggbWEgdHLhuq1uIGPDsyBrw61jaCB0aMaw4bubYyAyMHgyDQp4dGVzdD1tYXRyaXgocm5vcm0oMjAqMikgLCBuY29sPTIpDQojIFThuqFvIG3hu5l0IHZlY3RvciBjaOG7qWEgMjAgZ2nDoSB0cuG7iywgMTAgZ2nDoSB0cuG7iyDEkeG6p3UgbMOgIC0xLCAxMCBnacOhIHRy4buLIHNhdSBsw6AgMQ0KeXRlc3Q9c2FtcGxlKGMoLTEsMSksIDIwLCByZXA9VFJVRSkNCmBgYA0KDQpU4bqhbyBt4buZdCBi4buZIHRlc3QgY2hvIG3DtCBow6xuaA0KDQpgYGB7cn0NCiMgVMSDbmcgZ2nDoSB0cuG7iyBj4bunYSAxMCBkw7JuZyBzYXUgY+G7p2EgbWEgdHLhuq1uIFggbMOqbiAxIOG7qW5nIHbhu5tpIGdpw6EgdHLhu4sgdmVjdG9yIHkgPSAxDQp4dGVzdFt5dGVzdD09MSxdPXh0ZXN0W3l0ZXN0PT0xLF0gKyAxDQojIFThuqFvIG3hu5l0IGThu68gbGnhu4d1IHThu6sgeHRlc3QgdsOgIHl0ZXN0IOG7nyB0csOqbiwgduG7m2kgeXRlc3QgxJHDoyDEkcaw4bujYyBjaHV54buDbiB0aMOgbmggdGh14buZYyB0w61uaCBmYWN0b3INCnRlc3RkYXQ9ZGF0YS5mcmFtZSh4PXh0ZXN0LCB5PWFzLmZhY3Rvcih5dGVzdCkpDQpgYGANCg0KYGBge3J9DQojIFRp4bq/biBow6BuaCDEkcawYSByYSBk4buxIMSRb8OhbiB0csOqbiB04bqtcCB0ZXN0ZGF0IGThu7FhIHRyw6puIG3DtCBow6xuaCB04buRdCBuaOG6pXQgduG7m2kgZ2nDoSB0cuG7iyBjb3N0ID0gMC4xDQp5cHJlZD1wcmVkaWN0KGJlc3Rtb2QsdGVzdGRhdCkNCiMgRMO5bmcgaMOgbSB0YWJsZSgpIMSR4buDIHThuqFvIHJhIG3hu5l0IG1hIHRy4bqtbiDEkeG7gyBxdXnhur90IMSR4buLbmggeGVtIGPDsyBiYW8gbmhpw6p1IHF1YW4gc8OhdCDEkcaw4bujYyBwaMOibiBsb+G6oWkgxJHDum5nLCBiYW8gbmhpw6p1IGLhu4sgcGjDom4gbG/huqFpIHNhaQ0KdGFibGUocHJlZGljdCA9eXByZWQsIHRydXRoPXRlc3RkYXQkeSkNCmBgYA0KDQpU4buJIGzhu4cgc+G7kSBsxrDhu6NuZyBxdWFuIHPDoXQgxJHGsOG7o2MgcGjDom4gbG/huqFpIMSRw7puZyB0cm9uZyB04bqtcCB0ZXN0IGzDoCA6ICg4KzkpLzIwID0gMC44NQ0KDQo9PiBU4buJIGzhu4cgcGjDom4gbG/huqFpIHNhaSBsw6Aga2hv4bqjbmcgMTUlDQoNCmBgYHtyfQ0KIyBU4bqhbyBtw7QgaMOsbmggU1ZNIHbhu5tpIHkgbMOgIMSR4bqndSByYSB2w6AgxJHhuqd1IHbDoG8gbMOgIHgsIGtlcm5lbCBsw6AgZOG6oW5nIGxpbmVhciwgdGhhbSBz4buRIGNvc3QgPSAwLjAxLCBtw7QgaMOsbmgga2jDtG5nIGNoaWEgdOG7iSBs4buHIGPDoWMgxJHhurdjIHRyxrBuZw0Kc3ZtZml0ID0gc3ZtKHl+LiwgZGF0YT1kYXQsIGtlcm5lbD0ibGluZWFyIiwgY29zdD0uMDEsc2NhbGU9RkFMU0UpDQojIFRp4bq/biBow6BuaCDEkcawYSByYSBk4buxIMSRb8OhbiB0csOqbiB04bqtcCB0ZXN0ZGF0IGThu7FhIHRyw6puIG3DtCBow6xuaCB24bubaSBnacOhIHRy4buLIGNvc3QgPSAwLjAxDQp5cHJlZD1wcmVkaWN0KHN2bWZpdCx0ZXN0ZGF0KQ0KIyBEw7luZyBow6BtIHRhYmxlKCkgxJHhu4MgdOG6oW8gcmEgbeG7mXQgbWEgdHLhuq1uIMSR4buDIHF1eeG6v3QgxJHhu4tuaCB4ZW0gY8OzIGJhbyBuaGnDqnUgcXVhbiBzw6F0IMSRxrDhu6NjIHBow6JuIGxv4bqhaSDEkcO6bmcsIGJhbyBuaGnDqnUgYuG7iyBwaMOibiBsb+G6oWkgc2FpDQp0YWJsZShwcmVkaWN0ID15cHJlZCwgdHJ1dGg9dGVzdGRhdCR5KQ0KYGBgDQoNClThu4kgbOG7hyBz4buRIGzGsOG7o25nIHF1YW4gc8OhdCDEkcaw4bujYyBwaMOibiBsb+G6oWkgxJHDum5nIHRyb25nIHThuq1wIHRlc3QgbMOgIDogKDExKzMpLzIwID0gMC43DQoNCj0+IFThu4kgbOG7hyBwaMOibiBsb+G6oWkgc2FpIGzDoCBraG/huqNuZyAzMCUsIGNhbyBoxqFuIHNvIHbhu5tpIG3DtCBow6xuaCB0csaw4bubYw0KDQpgYGB7cn0NCiMgVMSDbmcgZ2nDoSB0cuG7iyBj4bunYSAxMCBkw7JuZyBzYXUgY+G7p2EgbWEgdHLhuq1uIFggbMOqbiAwLjUg4bupbmcgduG7m2kgZ2nDoSB0cuG7iyB2ZWN0b3IgeSA9IDENCnhbeT09MSxdID0geFt5ID09IDEgLF0rMC41DQpgYGANCg0KVuG7mmkgeCB2w6AgeSBob8OgbiB0b8OgbiBraMOhYyBiaeG7h3QgdHV54bq/biB0w61uaA0KDQpgYGB7cn0NCiMgSGnhu4NuIHRo4buLIGJp4buDdSDEkeG7kyBjaG8geCB2w6AgeSDEkcOjIMSRxrDhu6NjIHRoYXkgxJHhu5VpIGdpw6EgdHLhu4sNCnBsb3QoeCwgY29sPSh5KzUpLzIsIHBjaD0xOSkNCmBgYA0KDQpgYGB7cn0NCiMgVOG6oW8gbeG7mXQgZOG7ryBsaeG7h3UgdOG7qyB4IHbDoCB5IOG7nyB0csOqbiwgduG7m2kgeSDEkcOjIMSRxrDhu6NjIGNodXnhu4NuIHRow6BuaCB0aHXhu5ljIHTDrW5oIGZhY3Rvcg0KZGF0ID0gZGF0YS5mcmFtZSh4PXgseT1hcy5mYWN0b3IoeSkpDQojIFThuqFvIG3DtCBow6xuaCBTVk0gduG7m2kgeSBsw6AgxJHhuqd1IHJhIHbDoCDEkeG6p3UgdsOgbyBsw6AgeCwga2VybmVsIGzDoCBk4bqhbmcgbGluZWFyLCB0aGFtIHPhu5EgY29zdCA9IDFlNSBy4bqldCBs4bubbiDEkeG7gyBjaG8ga2jDtG5nIHF1YW4gc8OhdCBuw6BvIGLhu4sgcGjDom4gbG/huqFpIHNhaQ0Kc3ZtZml0PXN2bSh5fi4sIGRhdGE9ZGF0LCBrZXJuZWw9ImxpbmVhciIsIGNvc3Q9MWU1KQ0KIyBQaMOibiB0w61jaCBtw7QgaMOsbmggU1ZNIOG7nyB0csOqbg0Kc3VtbWFyeShzdm1maXQpDQpgYGANCg0KLSBNw7QgaMOsbmgg4bufIGThuqFuZyBDbGFzc2lmaWNhdGlvbg0KLSBLZXJuZWwg4bufIGThuqFuZyB0dXnhur9uIHTDrW5oDQotIFRoYW0gc+G7kSBjb3N0ID0gIDFlKzA1IA0KLSBT4buRIGzGsOG7o25nIMSRaeG7g20gbuG6sW0gdHLDqm4gMiDEkcaw4budbmcgYmnDqm4gY2jhu4kgbMOgIDMsIDEgY2hvIGzhu5twIDEgdsOgIDIgY2hvIGzhu5twIDIsIG1hcmdpbiBj4bunYSBtw7QgaMOsbmggcuG6pXQgaOG6uXANCi0gU+G7kSBsxrDhu6NuZyBwaMOibiBs4bubcCA6IDINCi0gR2nDoSB0cuG7iyAyIGzhu5twIGzDoCAtMSB2w6AgMQ0KDQoNCmBgYHtyfQ0KIyBU4bqhbyBtw7QgaMOsbmggU1ZNIHbhu5tpIHkgbMOgIMSR4bqndSByYSB2w6AgxJHhuqd1IHbDoG8gbMOgIHgsIGtlcm5lbCBsw6AgZOG6oW5nIGxpbmVhciwgxJFp4buBdSBjaOG7iW5oIHRoYW0gc+G7kSBjb3N0IG5o4buPIGzhuqFpID0gMQ0Kc3ZtZml0PXN2bSh5fi4sIGRhdGE9ZGF0LCBrZXJuZWwgPSAibGluZWFyIiwgY29zdCA9IDEpDQojIFBow6JuIHTDrWNoIG3DtCBow6xuaCBTVk0g4bufIHRyw6puDQpzdW1tYXJ5KHN2bWZpdCkNCmBgYA0KDQotIE3DtCBow6xuaCDhu58gZOG6oW5nIENsYXNzaWZpY2F0aW9uDQotIEtlcm5lbCDhu58gZOG6oW5nIHR1eeG6v24gdMOtbmgNCi0gVGhhbSBz4buRIGNvc3QgPSAgMSANCi0gU+G7kSBsxrDhu6NuZyDEkWnhu4NtIG7hurFtIHRyw6puIDIgxJHGsOG7nW5nIGJpw6puIGzDoCA3LCA0IGNobyBs4bubcCAxIHbDoCAzIGNobyBs4bubcCAyLCB24bubaSBjb3N0ID0gMSB0aMOsIG3DtCBow6xuaCBjw7MgdGjhu4MgYuG7iyBwaMOibiBsb+G6oWkgc2FpIG3hu5l0IHbDoGkgxJFp4buDbSBxdWFuIHPDoXQgbmjGsG5nIHF1YW4gdHLhu41uZyBsw6AgxJHhu5kgcuG7mW5nIG1hcmdpbiBz4bq9IGzhu5tuIGjGoW4sIG3DtCBow6xuaCBz4bq9IGThu7EgxJFvw6FuIHThu5F0IGjGoW4gdHLDqm4gdOG6rXAgdGVzdCBzbyB24bubaSBtw7QgaMOsbmggdHLDqm4NCi0gU+G7kSBsxrDhu6NuZyBwaMOibiBs4bubcCA6IDINCi0gR2nDoSB0cuG7iyAyIGzhu5twIGzDoCAtMSB2w6AgMQ0KDQpgYGB7cn0NCiMgSGnhu4NuIHRo4buLIGJp4buDdSDEkeG7kyBiaeG7g3UgZGnhu4VuIGThu68gbGnhu4d1IHbhu5tpIHgxIHbDoCB4MiBs4bqnbiBsxrDhu6N0IHRy4bulYyB0dW5nIHbDoCB0cuG7pWMgdHVuZywgYsOqbiB0cm9uZyDEkeG7kyB0aOG7iyBjw6FjIMSRaeG7g20gcXVhbiBzw6F0IMSRw6MgxJHGsOG7o2MgcGjDom4gbOG7m3AgdGjDtG5nIHF1YSBtw7QgaMOsbmggU1ZNLCBtbcOgdSDEkeG7jyB0aHXhu5ljIHbhu4EgeT0xLCBtw6B1IHbDoG5nIHRodeG7mWMgduG7gSB5ID0gLTENCnBsb3Qoc3ZtZml0LGRhdCkNCmBgYA0KDQo=