Support Vector Classifier

utilizaremos la funcion svm para la clasificacion vectorial

set.seed(1)
 x=matrix(rnorm (20*2), ncol=2)
 y=c(rep(-1,10), rep(1,10))
 x[y==1,]=x[y==1,] + 1
 plot(x, col=(3-y))

crearemos un data frame con factor codificando a y, la siguiente grafica mostrará donde se parte el espacio

dat=data.frame(x=x, y=as.factor(y))
library(e1071)
svmfit=svm(y∼., data=dat , kernel ="linear", cost=10,scale=FALSE)
plot(svmfit , dat)

Notemos que los valores estan clasificados entre -1 y 1 para cada una de las casificaciones, el vector de indices nos indican las identidades del proceso

svmfit$index
[1]  1  2  5  7 14 16 17

en la descripcion del svm vemos que le costo es de 10 y clasifica 4 para la primera clase y 3 para la segunda (el grupo en parentisis)

 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

Ahora bien como reduciremos el costo, veremos que se incrementa la cantidad de entidades a operar

 svmfit=svm(y∼., data=dat , kernel ="linear", cost =0.1,scale=FALSE)
 plot(svmfit , dat)

 svmfit$index
 [1]  1  2  3  4  5  7  9 10 12 13 14 15 16 17 18 20

la libreria e1071 contiene la funcion tune que correra un cross validation tomemos encuenta que le estamos indicando un kernel lineal al procedimiento

library(e1071)
set.seed(1)
tune.out=tune(svm ,y∼.,data=dat ,kernel ="linear",
ranges=list(cost=c(0.001, 0.01, 0.1, 1,5,10,100) ))
summary (tune.out)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.1 

- Detailed performance results:
NA

la respuesta esta considerando el modelo del mejor costo con valor de .1 , para extraer el modelo

bestmod=tune.out$best.model
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

haremos el tes data basado en los factores esperados, tomemos encuentra que las categorias tendra su matriz de confusion par avalidar el metodo

 xtest=matrix(rnorm (20*2) , ncol=2)
 ytest=sample (c(-1,1), 20, rep=TRUE)
 xtest[ytest==1,]= xtest[ytest==1,] + 1
 testdat=data.frame(x=xtest , y=as.factor(ytest))
 ypred=predict (bestmod ,testdat)
 table(predict =ypred , truth=testdat$y )
       truth
predict -1  1
     -1 10  5
     1   3  2

utilizando svm sobre el modelo de costo ya previamente estimado

svmfit=svm(y∼., data=dat , kernel ="linear", cost =.01,scale=FALSE)
 ypred=predict (svmfit ,testdat )
 table(predict =ypred , truth=testdat$y )
       truth
predict -1  1
     -1 11  7
     1   2  0
 x[y==1,]=x[y==1,]+0.5
 plot(x, col=(y+5)/2, pch =19)

ahora bien la linea es a duras penas separable por loq ue operaremos un costo pobre

dat=data.frame(x=x,y=as.factor(y))
 svmfit=svm(y∼., data=dat , kernel ="linear", cost=1e5)
 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
 svmfit=svm(y∼., data=dat , kernel ="linear", cost=1)
 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
 plot(svmfit ,dat)

como podemos observar usando cost=1 no clasificamos correctamente las observaciones del training, por consiguiente una reduccion al costo da una pqueña mejora

Support Vector Machine

con el objetivo de encajar mejor el modelo de svm utilizaremos un kernel no lineal , para nuestro caso las opciones podran ser polynomial o radial

set.seed(1)
 x=matrix(rnorm (200*2) , ncol=2)
 x[1:100,]=x[1:100,]+2
 x[101:150 ,]=x[101:150,]-2
 y=c(rep(1,150) ,rep(2,50))
 dat=data.frame(x=x,y=as.factor(y))
 plot(x, col=y)

la data sera segmentada al azar dentro del training y el testing , para el caso de clasificaciones radiales, nuestra vairable a realizar tweaking sera gamma

train=sample (200,100)
svmfit=svm(y∼., data=dat[train ,], kernel ="radial", gamma=1,cost=1)
plot(svmfit , dat[train ,])

summary(svmfit)

Call:
svm(formula = y ~ ., data = dat[train, ], kernel = "radial", gamma = 1, cost = 1)


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

Number of Support Vectors:  39

 ( 22 17 )


Number of Classes:  2 

Levels: 
 1 2

entonces si reducimos la cantidad de costo podemos reducir el error asociado al training

svmfit=svm(y∼., data=dat[train ,], kernel ="radial",gamma=1,cost=1e5)
 plot(svmfit ,dat[train ,])

como vimos en el ejercicio anterior tune nos permitira mejorar nuestro clasificador vectorial

set.seed(1)
 tune.out=tune(svm , y∼., data=dat[train ,], kernel ="radial",
 ranges=list(cost=c(0.1,1,10,100,1000),
 gamma=c(0.5,1,2,3,4) ))
 summary (tune.out)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.11 

- Detailed performance results:
NA
table(true=dat[-train ,"y"], pred=predict (tune.out$best.model ,newdata =dat[-train ,]))
    pred
true  1  2
   1 72  5
   2  4 19

bajo este modelo solo 11% esta mal clasificado

ROC Curves

Dado un vector que contiene un clasificador numerico para cada bservacion y un vector que contiene la clase ROC estara aplicado por SVM

Empezamos definiendo nuestra funcion

library(ROCR)
 rocplot =function (pred , truth , ...){
 predob = prediction (pred , truth)
 perf = performance (predob , "tpr", "fpr")
 plot(perf ,...)}

tomemos encuenta que

X = (X1, X2,…,Xp)T tomara la forma de la combinacion lineal m βˆ0 + βˆ1X1 + βˆ2X2 + … + βˆpXp.

svmfit.opt=svm(y∼., data=dat[train ,], kernel ="radial",
gamma=2, cost=1, decision.values =T)
 fitted =attributes (predict (svmfit.opt ,dat[train ,], decision.values=TRUE))$decision.values
 par(mfrow=c(1,2))
 rocplot(fitted ,dat[train ,"y"], main="Training Data")

tomemos encuenta que ROC curves estan en la data del traingin y nosotros estamos interesados mas en la prediccion por lo que es necesario hacer ajustes al proceso de la definicion del modelo

 svmfit.flex=svm(y∼., data=dat[train ,], kernel ="radial",
gamma=50, cost=1, decision.values =T)
 fitted=attributes (predict (svmfit.flex ,dat[train ,], decision.values=T))$decision.values
 #rocplot(fitted ,dat[train ,"y"],add=T,col="red ")

fitted =attributes(predict (svmfit.opt ,dat[-train ,], decision.values=T))$decision.values
 rocplot (fitted ,dat[-train ,"y"], main="Test Data")
 fitted=attributes(predict (svmfit.flex ,dat[- train ,], decision.values=T))$decision.values
 rocplot (fitted ,dat[-train ,"y"],add=T,col="red")

LS0tDQp0aXRsZTogImxhYjk2IHZlY3RvciBtYWNoaW5lcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQojIFN1cHBvcnQgVmVjdG9yIENsYXNzaWZpZXINCnV0aWxpemFyZW1vcyBsYSBmdW5jaW9uIHN2bSBwYXJhIGxhIGNsYXNpZmljYWNpb24gdmVjdG9yaWFsDQpgYGB7cn0NCnNldC5zZWVkKDEpDQogeD1tYXRyaXgocm5vcm0gKDIwKjIpLCBuY29sPTIpDQogeT1jKHJlcCgtMSwxMCksIHJlcCgxLDEwKSkNCiB4W3k9PTEsXT14W3k9PTEsXSArIDENCiBwbG90KHgsIGNvbD0oMy15KSkNCg0KYGBgDQpjcmVhcmVtb3MgdW4gZGF0YSBmcmFtZSBjb24gZmFjdG9yIGNvZGlmaWNhbmRvIGEgeSwgbGEgc2lndWllbnRlIGdyYWZpY2EgbW9zdHJhcsOhIGRvbmRlIHNlIHBhcnRlIGVsIGVzcGFjaW8gDQoNCmBgYHtyfQ0KZGF0PWRhdGEuZnJhbWUoeD14LCB5PWFzLmZhY3Rvcih5KSkNCmxpYnJhcnkoZTEwNzEpDQpzdm1maXQ9c3ZtKHniiLwuLCBkYXRhPWRhdCAsIGtlcm5lbCA9ImxpbmVhciIsIGNvc3Q9MTAsc2NhbGU9RkFMU0UpDQpwbG90KHN2bWZpdCAsIGRhdCkNCg0KYGBgDQoNCk5vdGVtb3MgcXVlIGxvcyB2YWxvcmVzIGVzdGFuIGNsYXNpZmljYWRvcyBlbnRyZSAtMSB5IDEgcGFyYSBjYWRhIHVuYSBkZSBsYXMgY2FzaWZpY2FjaW9uZXMsIGVsIHZlY3RvciBkZSBpbmRpY2VzIG5vcyBpbmRpY2FuIGxhcyBpZGVudGlkYWRlcyBkZWwgcHJvY2Vzbw0KYGBge3J9DQpzdm1maXQkaW5kZXgNCmBgYA0KZW4gbGEgZGVzY3JpcGNpb24gZGVsIHN2bSB2ZW1vcyBxdWUgbGUgY29zdG8gZXMgZGUgMTAgeSBjbGFzaWZpY2EgNCBwYXJhIGxhIHByaW1lcmEgY2xhc2UgeSAzIHBhcmEgbGEgc2VndW5kYSAoZWwgZ3J1cG8gZW4gcGFyZW50aXNpcykNCmBgYHtyfQ0KIHN1bW1hcnkoc3ZtZml0KQ0KDQpgYGANCkFob3JhIGJpZW4gY29tbyByZWR1Y2lyZW1vcyBlbCBjb3N0bywgdmVyZW1vcyBxdWUgc2UgaW5jcmVtZW50YSBsYSBjYW50aWRhZCBkZSBlbnRpZGFkZXMgYSBvcGVyYXINCmBgYHtyfQ0KIHN2bWZpdD1zdm0oeeKIvC4sIGRhdGE9ZGF0ICwga2VybmVsID0ibGluZWFyIiwgY29zdCA9MC4xLHNjYWxlPUZBTFNFKQ0KIHBsb3Qoc3ZtZml0ICwgZGF0KQ0KIHN2bWZpdCRpbmRleA0KYGBgDQpsYSBsaWJyZXJpYSBlMTA3MSBjb250aWVuZSBsYSBmdW5jaW9uIHR1bmUgcXVlIGNvcnJlcmEgdW4gY3Jvc3MgdmFsaWRhdGlvbiB0b21lbW9zIGVuY3VlbnRhIHF1ZSBsZSBlc3RhbW9zIGluZGljYW5kbyB1biBrZXJuZWwgbGluZWFsIGFsIHByb2NlZGltaWVudG8NCmBgYHtyfQ0KbGlicmFyeShlMTA3MSkNCnNldC5zZWVkKDEpDQp0dW5lLm91dD10dW5lKHN2bSAseeKIvC4sZGF0YT1kYXQgLGtlcm5lbCA9ImxpbmVhciIsDQpyYW5nZXM9bGlzdChjb3N0PWMoMC4wMDEsIDAuMDEsIDAuMSwgMSw1LDEwLDEwMCkgKSkNCnN1bW1hcnkgKHR1bmUub3V0KQ0KYGBgDQpsYSByZXNwdWVzdGEgZXN0YSBjb25zaWRlcmFuZG8gZWwgbW9kZWxvIGRlbCBtZWpvciBjb3N0byBjb24gdmFsb3IgZGUgLjEgLCBwYXJhIGV4dHJhZXIgZWwgbW9kZWxvIA0KYGBge3J9DQpiZXN0bW9kPXR1bmUub3V0JGJlc3QubW9kZWwNCnN1bW1hcnkoYmVzdG1vZCkNCg0KYGBgDQpoYXJlbW9zIGVsIHRlcyBkYXRhIGJhc2FkbyBlbiBsb3MgZmFjdG9yZXMgZXNwZXJhZG9zLCB0b21lbW9zIGVuY3VlbnRyYSBxdWUgbGFzIGNhdGVnb3JpYXMgdGVuZHJhIHN1IG1hdHJpeiBkZSBjb25mdXNpb24gcGFyIGF2YWxpZGFyIGVsIG1ldG9kbw0KDQpgYGB7cn0NCiB4dGVzdD1tYXRyaXgocm5vcm0gKDIwKjIpICwgbmNvbD0yKQ0KIHl0ZXN0PXNhbXBsZSAoYygtMSwxKSwgMjAsIHJlcD1UUlVFKQ0KIHh0ZXN0W3l0ZXN0PT0xLF09IHh0ZXN0W3l0ZXN0PT0xLF0gKyAxDQogdGVzdGRhdD1kYXRhLmZyYW1lKHg9eHRlc3QgLCB5PWFzLmZhY3Rvcih5dGVzdCkpDQogeXByZWQ9cHJlZGljdCAoYmVzdG1vZCAsdGVzdGRhdCkNCiB0YWJsZShwcmVkaWN0ID15cHJlZCAsIHRydXRoPXRlc3RkYXQkeSApDQoNCmBgYA0KdXRpbGl6YW5kbyBzdm0gc29icmUgZWwgbW9kZWxvIGRlIGNvc3RvIHlhIHByZXZpYW1lbnRlIGVzdGltYWRvDQpgYGB7cn0NCnN2bWZpdD1zdm0oeeKIvC4sIGRhdGE9ZGF0ICwga2VybmVsID0ibGluZWFyIiwgY29zdCA9LjAxLHNjYWxlPUZBTFNFKQ0KIHlwcmVkPXByZWRpY3QgKHN2bWZpdCAsdGVzdGRhdCApDQogdGFibGUocHJlZGljdCA9eXByZWQgLCB0cnV0aD10ZXN0ZGF0JHkgKQ0KIHhbeT09MSxdPXhbeT09MSxdKzAuNQ0KIHBsb3QoeCwgY29sPSh5KzUpLzIsIHBjaCA9MTkpDQpgYGANCmFob3JhIGJpZW4gbGEgbGluZWEgZXMgYSBkdXJhcyBwZW5hcyBzZXBhcmFibGUgcG9yIGxvcSB1ZSBvcGVyYXJlbW9zIHVuIGNvc3RvIHBvYnJlDQpgYGB7cn0NCmRhdD1kYXRhLmZyYW1lKHg9eCx5PWFzLmZhY3Rvcih5KSkNCiBzdm1maXQ9c3ZtKHniiLwuLCBkYXRhPWRhdCAsIGtlcm5lbCA9ImxpbmVhciIsIGNvc3Q9MWU1KQ0KIHN1bW1hcnkoc3ZtZml0KQ0KYGBgDQpgYGB7cn0NCiBzdm1maXQ9c3ZtKHniiLwuLCBkYXRhPWRhdCAsIGtlcm5lbCA9ImxpbmVhciIsIGNvc3Q9MSkNCiBzdW1tYXJ5KHN2bWZpdCkNCiBwbG90KHN2bWZpdCAsZGF0KQ0KYGBgDQoNCmNvbW8gcG9kZW1vcyBvYnNlcnZhciB1c2FuZG8gY29zdD0xIG5vIGNsYXNpZmljYW1vcyBjb3JyZWN0YW1lbnRlIGxhcyBvYnNlcnZhY2lvbmVzIGRlbCB0cmFpbmluZywgcG9yIGNvbnNpZ3VpZW50ZSB1bmEgcmVkdWNjaW9uIGFsIGNvc3RvIGRhIHVuYSBwcXVlw7FhIG1lam9yYQ0KDQoNCiMgU3VwcG9ydCBWZWN0b3IgTWFjaGluZQ0KY29uIGVsIG9iamV0aXZvIGRlIGVuY2FqYXIgbWVqb3IgZWwgbW9kZWxvIGRlIHN2bSB1dGlsaXphcmVtb3MgdW4ga2VybmVsIG5vIGxpbmVhbCAsIHBhcmEgbnVlc3RybyBjYXNvIGxhcyBvcGNpb25lcyBwb2RyYW4gc2VyIHBvbHlub21pYWwgbyByYWRpYWwNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCiB4PW1hdHJpeChybm9ybSAoMjAwKjIpICwgbmNvbD0yKQ0KIHhbMToxMDAsXT14WzE6MTAwLF0rMg0KIHhbMTAxOjE1MCAsXT14WzEwMToxNTAsXS0yDQogeT1jKHJlcCgxLDE1MCkgLHJlcCgyLDUwKSkNCiBkYXQ9ZGF0YS5mcmFtZSh4PXgseT1hcy5mYWN0b3IoeSkpDQogcGxvdCh4LCBjb2w9eSkNCmBgYA0KbGEgZGF0YSBzZXJhIHNlZ21lbnRhZGEgYWwgYXphciBkZW50cm8gZGVsIHRyYWluaW5nIHkgZWwgdGVzdGluZyAsIHBhcmEgZWwgY2FzbyBkZSBjbGFzaWZpY2FjaW9uZXMgcmFkaWFsZXMsIG51ZXN0cmEgdmFpcmFibGUgYSByZWFsaXphciB0d2Vha2luZyBzZXJhIGdhbW1hDQoNCmBgYHtyfQ0KdHJhaW49c2FtcGxlICgyMDAsMTAwKQ0Kc3ZtZml0PXN2bSh54oi8LiwgZGF0YT1kYXRbdHJhaW4gLF0sIGtlcm5lbCA9InJhZGlhbCIsIGdhbW1hPTEsY29zdD0xKQ0KcGxvdChzdm1maXQgLCBkYXRbdHJhaW4gLF0pDQpzdW1tYXJ5KHN2bWZpdCkNCmBgYA0KZW50b25jZXMgc2kgcmVkdWNpbW9zIGxhIGNhbnRpZGFkIGRlIGNvc3RvIHBvZGVtb3MgcmVkdWNpciBlbCBlcnJvciBhc29jaWFkbyBhbCB0cmFpbmluZw0KDQpgYGB7cn0NCmBgYA0KDQoNCmBgYHtyfQ0Kc3ZtZml0PXN2bSh54oi8LiwgZGF0YT1kYXRbdHJhaW4gLF0sIGtlcm5lbCA9InJhZGlhbCIsZ2FtbWE9MSxjb3N0PTFlNSkNCiBwbG90KHN2bWZpdCAsZGF0W3RyYWluICxdKQ0KDQpgYGANCmNvbW8gdmltb3MgZW4gZWwgZWplcmNpY2lvIGFudGVyaW9yIHR1bmUgbm9zIHBlcm1pdGlyYSBtZWpvcmFyIG51ZXN0cm8gY2xhc2lmaWNhZG9yIHZlY3RvcmlhbA0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KIHR1bmUub3V0PXR1bmUoc3ZtICwgeeKIvC4sIGRhdGE9ZGF0W3RyYWluICxdLCBrZXJuZWwgPSJyYWRpYWwiLA0KIHJhbmdlcz1saXN0KGNvc3Q9YygwLjEsMSwxMCwxMDAsMTAwMCksDQogZ2FtbWE9YygwLjUsMSwyLDMsNCkgKSkNCiBzdW1tYXJ5ICh0dW5lLm91dCkNCmBgYA0KDQoNCmBgYHtyfQ0KdGFibGUodHJ1ZT1kYXRbLXRyYWluICwieSJdLCBwcmVkPXByZWRpY3QgKHR1bmUub3V0JGJlc3QubW9kZWwgLG5ld2RhdGEgPWRhdFstdHJhaW4gLF0pKQ0KYGBgDQpiYWpvIGVzdGUgbW9kZWxvIHNvbG8gMTElIGVzdGEgbWFsIGNsYXNpZmljYWRvDQoNCiMgUk9DIEN1cnZlcw0KDQpEYWRvIHVuIHZlY3RvciBxdWUgY29udGllbmUgdW4gY2xhc2lmaWNhZG9yIG51bWVyaWNvIHBhcmEgY2FkYSBic2VydmFjaW9uIHkgdW4gdmVjdG9yIHF1ZSBjb250aWVuZSBsYSBjbGFzZSBST0MgZXN0YXJhIGFwbGljYWRvIHBvciBTVk0NCg0KRW1wZXphbW9zIGRlZmluaWVuZG8gbnVlc3RyYSBmdW5jaW9uIA0KYGBge3J9DQpsaWJyYXJ5KFJPQ1IpDQogcm9jcGxvdCA9ZnVuY3Rpb24gKHByZWQgLCB0cnV0aCAsIC4uLil7DQogcHJlZG9iID0gcHJlZGljdGlvbiAocHJlZCAsIHRydXRoKQ0KIHBlcmYgPSBwZXJmb3JtYW5jZSAocHJlZG9iICwgInRwciIsICJmcHIiKQ0KIHBsb3QocGVyZiAsLi4uKX0NCg0KDQpgYGANCnRvbWVtb3MgZW5jdWVudGEgcXVlIA0KDQpYID0gKFgxLCBYMiwuLi4sWHApVA0KdG9tYXJhIGxhIGZvcm1hIGRlIGxhIGNvbWJpbmFjaW9uIGxpbmVhbCANCm0gzrLLhjAgKyDOssuGMVgxICsgzrLLhjJYMiArIC4uLiArIM6yy4ZwWHAuDQoNCmBgYHtyfQ0Kc3ZtZml0Lm9wdD1zdm0oeeKIvC4sIGRhdGE9ZGF0W3RyYWluICxdLCBrZXJuZWwgPSJyYWRpYWwiLA0KZ2FtbWE9MiwgY29zdD0xLCBkZWNpc2lvbi52YWx1ZXMgPVQpDQogZml0dGVkID1hdHRyaWJ1dGVzIChwcmVkaWN0IChzdm1maXQub3B0ICxkYXRbdHJhaW4gLF0sIGRlY2lzaW9uLnZhbHVlcz1UUlVFKSkkZGVjaXNpb24udmFsdWVzDQogcGFyKG1mcm93PWMoMSwyKSkNCiByb2NwbG90KGZpdHRlZCAsZGF0W3RyYWluICwieSJdLCBtYWluPSJUcmFpbmluZyBEYXRhIikNCmBgYA0KDQp0b21lbW9zIGVuY3VlbnRhIHF1ZSBST0MgY3VydmVzIGVzdGFuIGVuIGxhIGRhdGEgZGVsIHRyYWluZ2luIHkgbm9zb3Ryb3MgZXN0YW1vcyBpbnRlcmVzYWRvcyBtYXMgZW4gbGEgcHJlZGljY2lvbiBwb3IgbG8gcXVlIGVzIG5lY2VzYXJpbyBoYWNlciBhanVzdGVzIGFsIHByb2Nlc28gZGUgbGEgZGVmaW5pY2lvbiBkZWwgbW9kZWxvDQoNCg0KYGBge3J9DQogc3ZtZml0LmZsZXg9c3ZtKHniiLwuLCBkYXRhPWRhdFt0cmFpbiAsXSwga2VybmVsID0icmFkaWFsIiwNCmdhbW1hPTUwLCBjb3N0PTEsIGRlY2lzaW9uLnZhbHVlcyA9VCkNCiBmaXR0ZWQ9YXR0cmlidXRlcyAocHJlZGljdCAoc3ZtZml0LmZsZXggLGRhdFt0cmFpbiAsXSwgZGVjaXNpb24udmFsdWVzPVQpKSRkZWNpc2lvbi52YWx1ZXMNCiAjcm9jcGxvdChmaXR0ZWQgLGRhdFt0cmFpbiAsInkiXSxhZGQ9VCxjb2w9InJlZCAiKQ0KDQpmaXR0ZWQgPWF0dHJpYnV0ZXMocHJlZGljdCAoc3ZtZml0Lm9wdCAsZGF0Wy10cmFpbiAsXSwgZGVjaXNpb24udmFsdWVzPVQpKSRkZWNpc2lvbi52YWx1ZXMNCiByb2NwbG90IChmaXR0ZWQgLGRhdFstdHJhaW4gLCJ5Il0sIG1haW49IlRlc3QgRGF0YSIpDQogZml0dGVkPWF0dHJpYnV0ZXMocHJlZGljdCAoc3ZtZml0LmZsZXggLGRhdFstIHRyYWluICxdLCBkZWNpc2lvbi52YWx1ZXM9VCkpJGRlY2lzaW9uLnZhbHVlcw0KIHJvY3Bsb3QgKGZpdHRlZCAsZGF0Wy10cmFpbiAsInkiXSxhZGQ9VCxjb2w9InJlZCIpDQpgYGANCg0K