Laboratorio Validacion Cruzada y Bootstrap

En este laboratorio (Basado en el laboratorio 5.3 del libro ISLR) exploraremos tecnicas de remuestreo.

La Validacion Del Enfoque

En el capitulo 5 del libro ISLR se exploran los usos del enfoque de las validaciones para poder estimar la tasa de error, de como entrenar diferentes modelos lineares utilizando data sets Auto.

Para esta laboratorio empesaremos utilizando la funcion sample para generar un dataset con los indices que vamos a utilizar para entrenar el modelo.

library(ISLR)
set.seed(1)
train <- sample(392, 196)
train
  [1] 105 146 224 354  79 348 365 255 242  24 388  68 262 391 292 188 270 372 143 290 387 382 384  47  99 142
 [27]   5 140 317 124 175 217 178  67 297 239 283  39 257 379 289 228 275 194 185 274   9 165 252 238 164 294
 [53] 149  83 383  34 107 174 222 136 304  98 152 110 214  85 157 250  28 356 329 376 111 336 330 323 347 123
 [79] 245 301 333 334 363 101 234  63 218  38  75  44  73  18 193 263 233 237 135 121 357 360 192 103 371 287
[105] 183  62 305 137 299 170 276 206 100 295  42   4 198  29 315 362 321 296 131 369 203 122 312  55  61 326
[131] 151  21  10 167 240 154 144 271 251 129 173 380  60  65 181 112 303 288  26 211 340 385 373 109 120  43
[157] 125 313 249  50 359 207 291 179 201  94  15  76 163 225 386 186 189  86 339 195 311 160 130 300 307  41
[183] 187 106 314  40 284 370 213 247 256 258 261 375  57 117

Ahora calculamos los test index

test
  [1]   1   2   3   6   7   8  11  12  13  14  16  17  19  20  22  23  25  27  30  31  32  33  35  36  37  45
 [27]  46  48  49  51  52  53  54  56  58  59  64  66  69  70  71  72  74  77  78  80  81  82  84  87  88  89
 [53]  90  91  92  93  95  96  97 102 104 108 113 114 115 116 118 119 126 127 128 132 133 134 138 139 141 145
 [79] 147 148 150 153 155 156 158 159 161 162 166 168 169 171 172 176 177 180 182 184 190 191 196 197 199 200
[105] 202 204 205 208 209 210 212 215 216 219 220 221 223 226 227 229 230 231 232 235 236 241 243 244 246 248
[131] 253 254 259 260 264 265 266 267 268 269 272 273 277 278 279 280 281 282 285 286 293 298 302 306 308 309
[157] 310 316 318 319 320 322 324 325 327 328 331 332 335 337 338 341 342 343 344 345 346 349 350 351 352 353
[183] 355 358 361 364 366 367 368 374 377 378 381 389 390 392

Ahora basado en esto obtenemos un dataset con 196 valores entre 1 y 392, ahora con este dataset vamos a generar una regresion lineal y utilizando el dataset Auto:

str(Auto)
'data.frame':   392 obs. of  9 variables:
 $ mpg         : num  18 15 18 16 17 15 14 14 14 15 ...
 $ cylinders   : num  8 8 8 8 8 8 8 8 8 8 ...
 $ displacement: num  307 350 318 304 302 429 454 440 455 390 ...
 $ horsepower  : num  130 165 150 150 140 198 220 215 225 190 ...
 $ weight      : num  3504 3693 3436 3433 3449 ...
 $ acceleration: num  12 11.5 11 12 10.5 10 9 8.5 10 8.5 ...
 $ year        : num  70 70 70 70 70 70 70 70 70 70 ...
 $ origin      : num  1 1 1 1 1 1 1 1 1 1 ...
 $ name        : Factor w/ 304 levels "amc ambassador brougham",..: 49 36 231 14 161 141 54 223 241 2 ...
lm_fit <- lm(mpg~horsepower, data=Auto, subset=train)
lm_fit

Call:
lm(formula = mpg ~ horsepower, data = Auto, subset = train)

Coefficients:
(Intercept)   horsepower  
    40.3404      -0.1617  

Vemos que para esta regresion obtenemos que \(\beta_{0}=40.3404\) y \(\beta_{1}=-016.17\), ahora procedemo a calcular el MSE utilizando el test:

mean(((Auto$mpg - predict(lm_fit, Auto))[test])^2)
[1] 26.14142

Con esto vemos que el fit para la regresion lineal es de 26.14142, ahora utilizando la funcion poly vamos a probrar el fit para una funcion cuadratica:

lm_fit_2 <- lm(Auto$mpg~poly(Auto$horsepower, 2), data=Auto, subset=train)
mean(((Auto$mpg - predict(lm_fit_2, Auto))[test])^2)
[1] 19.82259

y la cubica:

lm_fit_3 <- lm(Auto$mpg~poly(Auto$horsepower, 3), data=Auto, subset=train)
mean(((Auto$mpg - predict(lm_fit_3, Auto))[test])^2)
[1] 19.78252

Como vemos los resultados finales son \(mse=26.14142\) \(mse^2=19.82259\) \(mse^3=19.78252\), aparentemente la regresion cubica es la que mejor fit tiene, pero ahora intenemos recalcular el dataset de entrenamiento:

set.seed(2)
train <- sample(392,196)
test <- (1:392)[-train]

y calculamos las 3 regresiones y su mse

lm_fit <- lm(mpg~horsepower, data=Auto, subset=train)
mean(((Auto$mpg-predict(lm_fit, Auto))[test])^2)
[1] 23.29559
lm_fit_2 <- lm(mpg~poly(horsepower,2), data=Auto, subset=train)
mean(((Auto$mpg-predict(lm_fit_2, Auto))[test])^2)
[1] 18.90124
lm_fit_3 <- lm(mpg~poly(horsepower,3), data=Auto, subset=train)
mean(((Auto$mpg-predict(lm_fit_3, Auto))[test])^2)
[1] 19.2574

Ahora al recalcular la muestra los resultados son \(mse=23.29559\) \(mse^2=18.90124\) \(mse^3=19.2574\), con lo que la regresion cuadratica nos da un mejor fit que las anteriores.

Validacion Cruzada Leave-One-Out

Para utilizar el LOOCV utlizaremos la funcion glm, cuando utilizamos esta funcion sin utilizar el parametro familiy="binomial", la funcion glm crea una regresion lineal como la funcion lm, podemos hacer la prueba:

glm_fit <- glm(mpg~horsepower, data=Auto)
coef(glm_fit)
(Intercept)  horsepower 
 39.9358610  -0.1578447 

ahora utilizando lm

lm_fit <- lm(mpg~horsepower, data=Auto)
coef(lm_fit)
(Intercept)  horsepower 
 39.9358610  -0.1578447 

Vemos que para ambas funciones \(\beta_{0}=39.9358610\) y \(\beta_{1}=-0.1578447\). Por esta razon utilizaremos glm y utilizaremos cv.glm para realizar la validacion cruzada:

library(boot)
glm_fit <- glm(mpg~horsepower, data=Auto)
cv_error <- cv.glm(Auto, glm_fit)
cv_error$delta
[1] 24.23151 24.23114

Como vemos cv_error nos da dos valores, los cuales son identicos, ahora procederemos a calcular estos indicadores delta para los polinimos desde 1 hasta 5.

glm_error_function <- function(grado, dataset){
  glm_fit <- glm(mpg~poly(horsepower, grado), data=dataset)
  return(cv.glm(dataset, glm_fit)$delta[1])
}
cv_error <- lapply(1:5, glm_error_function, dataset=Auto)
cv_error
[[1]]
[1] 24.23151

[[2]]
[1] 19.24821

[[3]]
[1] 19.33498

[[4]]
[1] 19.42443

[[5]]
[1] 19.03321

Esta funcion nos permite ver que hay una gran mejora entre el polinomio lineal y el cuadratico, pero no hay una mejor considerable pasando de ahi.

Validacion cruzada K-fold

Utilizando la funcion cv.glm podemos tambien implementar la funcion Kfold, para ello haremos la misma implementacion de la funcion anterior, utilizando \(k=10\) el cual es un valor comun a utilizar en este tipo de ejercicios:

set.seed(17)
glm_error_k <- function(grado, dataset){
  glm_fit <- glm(mpg~poly(horsepower, grado), data=dataset)
  return (cv.glm(dataset, glm_fit, K=10)$delta[1])
}
cv_error_10 <- lapply(1:5, glm_error_k, dataset=Auto)
cv_error_10
[[1]]
[1] 24.2052

[[2]]
[1] 19.18924

[[3]]
[1] 19.30662

[[4]]
[1] 19.33799

[[5]]
[1] 18.87911

Al utilizar esta nueva funcion vemos que los resultados son muy similares con la ventaja que tarda mucho menos en computar la validacion. Pero seguimos viendo que el mejor fit es el cuadratico.

El Bootstrap

Una de las ventajas de hacer Bootstrap es que no requiere complicadas funciones matematicas, para ello haremos uso de la funcion boot() presente en la libreria boot. Comenzaremos definiendo la funcion alpha_fn:

Esta funcion calcula las \(\alpha\) de los indices que se le provea segun un dataset:

alpha_fn(Portfolio, 1:100)
[1] 0.5758321

Ahora lo haremos utilizando la funcion sample para utilizar los indices sin orden:

set.seed(1)
alpha_fn(Portfolio, sample(100, 100, replace = T))
[1] 0.5963833

Ahora podemos calcular el Bootstrap utilizando la funcion boot y la funcion alpha_fn y procedemos a ejecutar esta prueba 1000 veces:

boot(Portfolio, alpha_fn, R=1000)

ORDINARY NONPARAMETRIC BOOTSTRAP


Call:
boot(data = Portfolio, statistic = alpha_fn, R = 1000)


Bootstrap Statistics :
     original        bias    std. error
t1* 0.5758321 -7.315422e-05  0.08861826

Con esto vemos que \(\hat{\alpha}=0.5758\) y que el \(SE(\hat{\alpha})=0.08861\).

Estimando La Exactitud De Un Modelo De Regresion Lineal

La tecnica de bootstrap puede ser muy efectiva para medir la variabilidad de los coeficientes estimados y de prediccion desde un punto de vista de aprendisaje estadistico. Para ello estimaremos la variabilidad de \(\beta_{0}\) y \(\beta_{1}\) de la funcion \(Y=\beta_{0} + \beta_{1}X\) donde \(Y=mpg\) y \(X=horsepower\) del dataset Auto y compararemos los estimados obtenidos usando el bootstrap de las formulas de \(SE(\hat{\beta}_{0})\) y \(SE(\hat{\beta}_{1})\).

boot_fn <- function(data, index) {
  return (coef(lm(mpg~horsepower, data=data, subset = index)))
}
boot_fn(Auto, 1:392)
(Intercept)  horsepower 
 39.9358610  -0.1578447 

Ahora usaremos la funcion con los indices desordenados:

set.seed(1)
boot_fn(Auto, sample(392, 392, replace=T))
(Intercept)  horsepower 
 38.7387134  -0.1481952 
boot_fn(Auto, sample(392, 392, replace=T))
(Intercept)  horsepower 
 40.0383086  -0.1596104 

Ahora usaremos la funcion boot para computar los errores standard de 1000 estimaciones de bootstrap:

boot(Auto, boot_fn, 1000)

ORDINARY NONPARAMETRIC BOOTSTRAP


Call:
boot(data = Auto, statistic = boot_fn, R = 1000)


Bootstrap Statistics :
      original      bias    std. error
t1* 39.9358610  0.02972191 0.860007896
t2* -0.1578447 -0.00030823 0.007404467

Como vemos \(SE(\hat{\beta_{0}})=0.8600\) y \(SE(\hat{\beta_{1}})=0.0074\), ahora revisemos el resumen:

summary(lm(mpg~horsepower, data=Auto))$coef
              Estimate  Std. Error   t value      Pr(>|t|)
(Intercept) 39.9358610 0.717498656  55.65984 1.220362e-187
horsepower  -0.1578447 0.006445501 -24.48914  7.031989e-81

El resumen nos indica que \(SE(\hat{\beta_{0}})=0.7175\) y \(SE(\hat{\beta_{1}})=0.0064\) distinto de los valores que obtuvimos al realizar el bootstrapping, esto es un problema con el algoritmod e bootstrap? No realmente ya que la ecuacion de \(\alpha^2\) que se plantean en el libro, lo que depende de varias asumciones y ruido que conlleva dicha varaible. Ahora realicemos la misma prueba, utilizando la ecuacion cuadratica para lo que calcularemos \(SE(\hat{\beta_{0}})\), \(SE(\hat{\beta_{1}})\) y \(SE(\hat{\beta_{2}})\)

boot_fn <- function(data, index){
  return(coefficients(lm(mpg~horsepower+I(horsepower^2), data=data, subset=index)))
}
set.seed(1)
boot(Auto, boot_fn, 1000)

ORDINARY NONPARAMETRIC BOOTSTRAP


Call:
boot(data = Auto, statistic = boot_fn, R = 1000)


Bootstrap Statistics :
        original        bias     std. error
t1* 56.900099702  6.098115e-03 2.0944855842
t2* -0.466189630 -1.777108e-04 0.0334123802
t3*  0.001230536  1.324315e-06 0.0001208339
summary(lm(mpg~horsepower+I(horsepower^2), data=Auto))$coef
                    Estimate   Std. Error   t value      Pr(>|t|)
(Intercept)     56.900099702 1.8004268063  31.60367 1.740911e-109
horsepower      -0.466189630 0.0311246171 -14.97816  2.289429e-40
I(horsepower^2)  0.001230536 0.0001220759  10.08009  2.196340e-21

Como ya lo hemos visto, al definir un modelo cuadratico los coeficientes si dan el mismo resultando, con lo cual terminamos de definir que el modelo cuadratico es el que mejor se ajusta.

LS0tCnRpdGxlOiAiRmlhYmlsaWRhZCwgSG9qYSBkZSBUcmFiYWpvIDUiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIExhYm9yYXRvcmlvIFZhbGlkYWNpb24gQ3J1emFkYSB5IEJvb3RzdHJhcAoKRW4gZXN0ZSBsYWJvcmF0b3JpbyAoQmFzYWRvIGVuIGVsIGxhYm9yYXRvcmlvIDUuMyBkZWwgbGlicm8gSVNMUikgZXhwbG9yYXJlbW9zIHRlY25pY2FzIGRlIHJlbXVlc3RyZW8uCgoKIyMjIExhIFZhbGlkYWNpb24gRGVsIEVuZm9xdWUKCkVuIGVsIGNhcGl0dWxvIDUgZGVsIGxpYnJvIElTTFIgc2UgZXhwbG9yYW4gbG9zIHVzb3MgZGVsIGVuZm9xdWUgZGUgbGFzIHZhbGlkYWNpb25lcyBwYXJhIHBvZGVyIGVzdGltYXIgbGEgdGFzYSBkZSBlcnJvciwgZGUgY29tbyBlbnRyZW5hciBkaWZlcmVudGVzIG1vZGVsb3MgbGluZWFyZXMgdXRpbGl6YW5kbyBkYXRhIHNldHMgQXV0by4KClBhcmEgZXN0YSBsYWJvcmF0b3JpbyBlbXBlc2FyZW1vcyB1dGlsaXphbmRvIGxhIGZ1bmNpb24gYHNhbXBsZWAgcGFyYSBnZW5lcmFyIHVuIGRhdGFzZXQgY29uIGxvcyBpbmRpY2VzIHF1ZSB2YW1vcyBhIHV0aWxpemFyIHBhcmEgZW50cmVuYXIgZWwgbW9kZWxvLgoKYGBge3J9CmxpYnJhcnkoSVNMUikKc2V0LnNlZWQoMSkKdHJhaW4gPC0gc2FtcGxlKDM5MiwgMTk2KQp0cmFpbgoKCmBgYApBaG9yYSBjYWxjdWxhbW9zIGxvcyB0ZXN0IGluZGV4CgpgYGB7cn0KdGVzdCA8LSAoMTozOTIpWy10cmFpbl0KdGVzdApgYGAKCgpBaG9yYSBiYXNhZG8gZW4gZXN0byBvYnRlbmVtb3MgdW4gZGF0YXNldCBjb24gMTk2IHZhbG9yZXMgZW50cmUgMSB5IDM5MiwgYWhvcmEgY29uIGVzdGUgZGF0YXNldCB2YW1vcyBhIGdlbmVyYXIgdW5hIHJlZ3Jlc2lvbiBsaW5lYWwgeSB1dGlsaXphbmRvIGVsIGRhdGFzZXQgQXV0bzoKCmBgYHtyfQpzdHIoQXV0bykKYGBgCgpgYGB7cn0KbG1fZml0IDwtIGxtKG1wZ35ob3JzZXBvd2VyLCBkYXRhPUF1dG8sIHN1YnNldD10cmFpbikKbG1fZml0CmBgYApWZW1vcyBxdWUgcGFyYSBlc3RhIHJlZ3Jlc2lvbiBvYnRlbmVtb3MgcXVlICRcYmV0YV97MH09NDAuMzQwNCQgeSAkXGJldGFfezF9PS0wMTYuMTckLCBhaG9yYSBwcm9jZWRlbW8gYSBjYWxjdWxhciBlbCBNU0UgdXRpbGl6YW5kbyBlbCB0ZXN0OgoKYGBge3J9Cm1lYW4oKChBdXRvJG1wZyAtIHByZWRpY3QobG1fZml0LCBBdXRvKSlbdGVzdF0pXjIpCmBgYApDb24gZXN0byB2ZW1vcyBxdWUgZWwgZml0IHBhcmEgbGEgcmVncmVzaW9uIGxpbmVhbCBlcyBkZSAyNi4xNDE0MiwgYWhvcmEgdXRpbGl6YW5kbyBsYSBmdW5jaW9uIGBwb2x5YCB2YW1vcyBhIHByb2JyYXIgZWwgZml0IHBhcmEgdW5hIGZ1bmNpb24gY3VhZHJhdGljYToKCmBgYHtyfQpsbV9maXRfMiA8LSBsbShBdXRvJG1wZ35wb2x5KEF1dG8kaG9yc2Vwb3dlciwgMiksIGRhdGE9QXV0bywgc3Vic2V0PXRyYWluKQptZWFuKCgoQXV0byRtcGcgLSBwcmVkaWN0KGxtX2ZpdF8yLCBBdXRvKSlbdGVzdF0pXjIpCmBgYAp5IGxhIGN1YmljYToKCmBgYHtyfQpsbV9maXRfMyA8LSBsbShBdXRvJG1wZ35wb2x5KEF1dG8kaG9yc2Vwb3dlciwgMyksIGRhdGE9QXV0bywgc3Vic2V0PXRyYWluKQptZWFuKCgoQXV0byRtcGcgLSBwcmVkaWN0KGxtX2ZpdF8zLCBBdXRvKSlbdGVzdF0pXjIpCmBgYAoKQ29tbyB2ZW1vcyBsb3MgcmVzdWx0YWRvcyBmaW5hbGVzIHNvbiAkbXNlPTI2LjE0MTQyJCAkbXNlXjI9MTkuODIyNTkkICRtc2VeMz0xOS43ODI1MiQsIGFwYXJlbnRlbWVudGUgbGEgcmVncmVzaW9uIGN1YmljYSBlcyBsYSBxdWUgbWVqb3IgZml0IHRpZW5lLCBwZXJvIGFob3JhIGludGVuZW1vcyByZWNhbGN1bGFyIGVsIGRhdGFzZXQgZGUgZW50cmVuYW1pZW50bzoKCmBgYHtyfQpzZXQuc2VlZCgyKQoKdHJhaW4gPC0gc2FtcGxlKDM5MiwxOTYpCnRlc3QgPC0gKDE6MzkyKVstdHJhaW5dCmBgYAp5IGNhbGN1bGFtb3MgbGFzIDMgcmVncmVzaW9uZXMgeSBzdSBtc2UKCmBgYHtyfQpsbV9maXQgPC0gbG0obXBnfmhvcnNlcG93ZXIsIGRhdGE9QXV0bywgc3Vic2V0PXRyYWluKQptZWFuKCgoQXV0byRtcGctcHJlZGljdChsbV9maXQsIEF1dG8pKVt0ZXN0XSleMikKYGBgCmBgYHtyfQpsbV9maXRfMiA8LSBsbShtcGd+cG9seShob3JzZXBvd2VyLDIpLCBkYXRhPUF1dG8sIHN1YnNldD10cmFpbikKbWVhbigoKEF1dG8kbXBnLXByZWRpY3QobG1fZml0XzIsIEF1dG8pKVt0ZXN0XSleMikKYGBgCmBgYHtyfQpsbV9maXRfMyA8LSBsbShtcGd+cG9seShob3JzZXBvd2VyLDMpLCBkYXRhPUF1dG8sIHN1YnNldD10cmFpbikKbWVhbigoKEF1dG8kbXBnLXByZWRpY3QobG1fZml0XzMsIEF1dG8pKVt0ZXN0XSleMikKYGBgCkFob3JhIGFsIHJlY2FsY3VsYXIgbGEgbXVlc3RyYSBsb3MgcmVzdWx0YWRvcyBzb24gJG1zZT0yMy4yOTU1OSQgJG1zZV4yPTE4LjkwMTI0JCAkbXNlXjM9MTkuMjU3NCQsIGNvbiBsbyBxdWUgbGEgcmVncmVzaW9uIGN1YWRyYXRpY2Egbm9zIGRhIHVuIG1lam9yIGZpdCBxdWUgbGFzIGFudGVyaW9yZXMuCgojIyMgVmFsaWRhY2lvbiBDcnV6YWRhIExlYXZlLU9uZS1PdXQKClBhcmEgdXRpbGl6YXIgZWwgTE9PQ1YgdXRsaXphcmVtb3MgbGEgZnVuY2lvbiBgZ2xtYCwgY3VhbmRvIHV0aWxpemFtb3MgZXN0YSBmdW5jaW9uIHNpbiB1dGlsaXphciBlbCBwYXJhbWV0cm8gYGZhbWlsaXk9ImJpbm9taWFsImAsIGxhIGZ1bmNpb24gYGdsbWAgY3JlYSB1bmEgcmVncmVzaW9uIGxpbmVhbCBjb21vIGxhIGZ1bmNpb24gYGxtYCwgcG9kZW1vcyBoYWNlciBsYSBwcnVlYmE6CgpgYGB7cn0KZ2xtX2ZpdCA8LSBnbG0obXBnfmhvcnNlcG93ZXIsIGRhdGE9QXV0bykKY29lZihnbG1fZml0KQpgYGAKYWhvcmEgdXRpbGl6YW5kbyBgbG1gCgpgYGB7cn0KbG1fZml0IDwtIGxtKG1wZ35ob3JzZXBvd2VyLCBkYXRhPUF1dG8pCmNvZWYobG1fZml0KQpgYGAKVmVtb3MgcXVlIHBhcmEgYW1iYXMgZnVuY2lvbmVzICRcYmV0YV97MH09MzkuOTM1ODYxMCQgeSAkXGJldGFfezF9PS0wLjE1Nzg0NDckLiBQb3IgZXN0YSByYXpvbiB1dGlsaXphcmVtb3MgYGdsbWAgeSB1dGlsaXphcmVtb3MgYGN2LmdsbWAgcGFyYSByZWFsaXphciBsYSB2YWxpZGFjaW9uIGNydXphZGE6CgpgYGB7cn0KbGlicmFyeShib290KQpnbG1fZml0IDwtIGdsbShtcGd+aG9yc2Vwb3dlciwgZGF0YT1BdXRvKQpjdl9lcnJvciA8LSBjdi5nbG0oQXV0bywgZ2xtX2ZpdCkKY3ZfZXJyb3IkZGVsdGEKYGBgCgpDb21vIHZlbW9zIGBjdl9lcnJvcmAgbm9zIGRhIGRvcyB2YWxvcmVzLCBsb3MgY3VhbGVzIHNvbiBpZGVudGljb3MsIGFob3JhIHByb2NlZGVyZW1vcyBhIGNhbGN1bGFyIGVzdG9zIGluZGljYWRvcmVzIGRlbHRhIHBhcmEgbG9zIHBvbGluaW1vcyBkZXNkZSAxIGhhc3RhIDUuCgpgYGB7cn0KZ2xtX2Vycm9yX2Z1bmN0aW9uIDwtIGZ1bmN0aW9uKGdyYWRvLCBkYXRhc2V0KXsKICBnbG1fZml0IDwtIGdsbShtcGd+cG9seShob3JzZXBvd2VyLCBncmFkbyksIGRhdGE9ZGF0YXNldCkKICByZXR1cm4oY3YuZ2xtKGRhdGFzZXQsIGdsbV9maXQpJGRlbHRhWzFdKQp9CmN2X2Vycm9yIDwtIGxhcHBseSgxOjUsIGdsbV9lcnJvcl9mdW5jdGlvbiwgZGF0YXNldD1BdXRvKQpjdl9lcnJvcgpgYGAKRXN0YSBmdW5jaW9uIG5vcyBwZXJtaXRlIHZlciBxdWUgaGF5IHVuYSBncmFuIG1lam9yYSBlbnRyZSBlbCBwb2xpbm9taW8gbGluZWFsIHkgZWwgY3VhZHJhdGljbywgcGVybyBubyBoYXkgdW5hIG1lam9yIGNvbnNpZGVyYWJsZSBwYXNhbmRvIGRlIGFoaS4KCiMjIyBWYWxpZGFjaW9uIGNydXphZGEgSy1mb2xkCgpVdGlsaXphbmRvIGxhIGZ1bmNpb24gYGN2LmdsbWAgcG9kZW1vcyB0YW1iaWVuIGltcGxlbWVudGFyIGxhIGZ1bmNpb24gS2ZvbGQsIHBhcmEgZWxsbyBoYXJlbW9zIGxhIG1pc21hIGltcGxlbWVudGFjaW9uIGRlIGxhIGZ1bmNpb24gYW50ZXJpb3IsIHV0aWxpemFuZG8gJGs9MTAkIGVsIGN1YWwgZXMgdW4gdmFsb3IgY29tdW4gYSB1dGlsaXphciBlbiBlc3RlIHRpcG8gZGUgZWplcmNpY2lvczoKCmBgYHtyfQpzZXQuc2VlZCgxNykKCmdsbV9lcnJvcl9rIDwtIGZ1bmN0aW9uKGdyYWRvLCBkYXRhc2V0KXsKICBnbG1fZml0IDwtIGdsbShtcGd+cG9seShob3JzZXBvd2VyLCBncmFkbyksIGRhdGE9ZGF0YXNldCkKICByZXR1cm4gKGN2LmdsbShkYXRhc2V0LCBnbG1fZml0LCBLPTEwKSRkZWx0YVsxXSkKfQoKY3ZfZXJyb3JfMTAgPC0gbGFwcGx5KDE6NSwgZ2xtX2Vycm9yX2ssIGRhdGFzZXQ9QXV0bykKY3ZfZXJyb3JfMTAKYGBgCgpBbCB1dGlsaXphciBlc3RhIG51ZXZhIGZ1bmNpb24gdmVtb3MgcXVlIGxvcyByZXN1bHRhZG9zIHNvbiBtdXkgc2ltaWxhcmVzIGNvbiBsYSB2ZW50YWphIHF1ZSB0YXJkYSBtdWNobyBtZW5vcyBlbiBjb21wdXRhciBsYSB2YWxpZGFjaW9uLiBQZXJvIHNlZ3VpbW9zIHZpZW5kbyBxdWUgZWwgbWVqb3IgZml0IGVzIGVsIGN1YWRyYXRpY28uCgojIyMgRWwgQm9vdHN0cmFwCgpVbmEgZGUgbGFzIHZlbnRhamFzIGRlIGhhY2VyIEJvb3RzdHJhcCBlcyBxdWUgbm8gcmVxdWllcmUgY29tcGxpY2FkYXMgZnVuY2lvbmVzIG1hdGVtYXRpY2FzLCBwYXJhIGVsbG8gaGFyZW1vcyB1c28gZGUgbGEgZnVuY2lvbiBgYm9vdCgpYCBwcmVzZW50ZSBlbiBsYSBsaWJyZXJpYSBgYm9vdGAuIENvbWVuemFyZW1vcyBkZWZpbmllbmRvIGxhIGZ1bmNpb24gYGFscGhhX2ZuYDoKCmBgYHtyfQphbHBoYV9mbiA8LSBmdW5jdGlvbihkYXRhLCBpbmRleCl7CiAgWCA8LSBkYXRhJFhbaW5kZXhdCiAgWSA8LSBkYXRhJFlbaW5kZXhdCiAgCiAgcmV0dXJuICgodmFyKFkpIC0gY292KFgsIFkpKS8gKHZhcihYKSArIHZhcihZKSAtIDIqY292KFgsIFkpKSkKfQpgYGAKCkVzdGEgZnVuY2lvbiBjYWxjdWxhIGxhcyAkXGFscGhhJCBkZSBsb3MgaW5kaWNlcyBxdWUgc2UgbGUgcHJvdmVhIHNlZ3VuIHVuIGRhdGFzZXQ6CgpgYGB7cn0KYWxwaGFfZm4oUG9ydGZvbGlvLCAxOjEwMCApCmBgYAoKQWhvcmEgbG8gaGFyZW1vcyB1dGlsaXphbmRvIGxhIGZ1bmNpb24gYHNhbXBsZWAgcGFyYSB1dGlsaXphciBsb3MgaW5kaWNlcyBzaW4gb3JkZW46CgpgYGB7cn0Kc2V0LnNlZWQoMSkKYWxwaGFfZm4oUG9ydGZvbGlvLCBzYW1wbGUoMTAwLCAxMDAsIHJlcGxhY2UgPSBUKSkKYGBgCgpBaG9yYSBwb2RlbW9zIGNhbGN1bGFyIGVsIEJvb3RzdHJhcCB1dGlsaXphbmRvIGxhIGZ1bmNpb24gYGJvb3RgIHkgbGEgZnVuY2lvbiBgYWxwaGFfZm5gIHkgcHJvY2VkZW1vcyBhIGVqZWN1dGFyIGVzdGEgcHJ1ZWJhIDEwMDAgdmVjZXM6CgpgYGB7cn0KYm9vdChQb3J0Zm9saW8sIGFscGhhX2ZuLCBSPTEwMDApCmBgYAoKQ29uIGVzdG8gdmVtb3MgcXVlICRcaGF0e1xhbHBoYX09MC41NzU4JCB5IHF1ZSBlbCAkU0UoXGhhdHtcYWxwaGF9KT0wLjA4ODYxJC4gCgojIyMjIEVzdGltYW5kbyBMYSBFeGFjdGl0dWQgRGUgVW4gTW9kZWxvIERlIFJlZ3Jlc2lvbiBMaW5lYWwKCkxhIHRlY25pY2EgZGUgYm9vdHN0cmFwIHB1ZWRlIHNlciBtdXkgZWZlY3RpdmEgcGFyYSBtZWRpciBsYSB2YXJpYWJpbGlkYWQgZGUgbG9zIGNvZWZpY2llbnRlcyBlc3RpbWFkb3MgeSBkZSBwcmVkaWNjaW9uIGRlc2RlIHVuIHB1bnRvIGRlIHZpc3RhIGRlIGFwcmVuZGlzYWplIGVzdGFkaXN0aWNvLiBQYXJhIGVsbG8gZXN0aW1hcmVtb3MgbGEgdmFyaWFiaWxpZGFkIGRlICRcYmV0YV97MH0kIHkgJFxiZXRhX3sxfSQgZGUgbGEgZnVuY2lvbiAkWT1cYmV0YV97MH0gKyBcYmV0YV97MX1YJCBkb25kZSAkWT1tcGckIHkgJFg9aG9yc2Vwb3dlciQgZGVsIGRhdGFzZXQgYEF1dG9gIHkgY29tcGFyYXJlbW9zIGxvcyBlc3RpbWFkb3Mgb2J0ZW5pZG9zIHVzYW5kbyBlbCBib290c3RyYXAgZGUgbGFzIGZvcm11bGFzIGRlICRTRShcaGF0e1xiZXRhfV97MH0pJCB5ICRTRShcaGF0e1xiZXRhfV97MX0pJC4KCmBgYHtyfQpib290X2ZuIDwtIGZ1bmN0aW9uKGRhdGEsIGluZGV4KSB7CiAgcmV0dXJuIChjb2VmKGxtKG1wZ35ob3JzZXBvd2VyLCBkYXRhPWRhdGEsIHN1YnNldCA9IGluZGV4KSkpCn0KCmJvb3RfZm4oQXV0bywgMTozOTIpCmBgYAoKQWhvcmEgdXNhcmVtb3MgbGEgZnVuY2lvbiBjb24gbG9zIGluZGljZXMgZGVzb3JkZW5hZG9zOgoKYGBge3J9CnNldC5zZWVkKDEpCmJvb3RfZm4oQXV0bywgc2FtcGxlKDM5MiwgMzkyLCByZXBsYWNlPVQpKQpgYGAKCmBgYHtyfQpib290X2ZuKEF1dG8sIHNhbXBsZSgzOTIsIDM5MiwgcmVwbGFjZT1UKSkKYGBgCgpBaG9yYSB1c2FyZW1vcyBsYSBmdW5jaW9uIGBib290YCBwYXJhIGNvbXB1dGFyIGxvcyBlcnJvcmVzIHN0YW5kYXJkIGRlIDEwMDAgZXN0aW1hY2lvbmVzIGRlIGJvb3RzdHJhcDoKCmBgYHtyfQpib290KEF1dG8sIGJvb3RfZm4sIDEwMDApCmBgYAoKQ29tbyB2ZW1vcyAkU0UoXGhhdHtcYmV0YV97MH19KT0wLjg2MDAkIHkgJFNFKFxoYXR7XGJldGFfezF9fSk9MC4wMDc0JCwgYWhvcmEgcmV2aXNlbW9zIGVsIHJlc3VtZW46CgpgYGB7cn0Kc3VtbWFyeShsbShtcGd+aG9yc2Vwb3dlciwgZGF0YT1BdXRvKSkkY29lZgpgYGAKCkVsIHJlc3VtZW4gbm9zIGluZGljYSBxdWUgJFNFKFxoYXR7XGJldGFfezB9fSk9MC43MTc1JCB5ICRTRShcaGF0e1xiZXRhX3sxfX0pPTAuMDA2NCQgZGlzdGludG8gZGUgbG9zIHZhbG9yZXMgcXVlIG9idHV2aW1vcyBhbCByZWFsaXphciBlbCBib290c3RyYXBwaW5nLCBlc3RvIGVzIHVuIHByb2JsZW1hIGNvbiBlbCBhbGdvcml0bW9kIGUgYm9vdHN0cmFwPyBObyByZWFsbWVudGUgeWEgcXVlIGxhIGVjdWFjaW9uIGRlICRcYWxwaGFeMiQgcXVlIHNlIHBsYW50ZWFuIGVuIGVsIGxpYnJvLCBsbyBxdWUgZGVwZW5kZSBkZSB2YXJpYXMgYXN1bWNpb25lcyB5IHJ1aWRvIHF1ZSBjb25sbGV2YSBkaWNoYSB2YXJhaWJsZS4gQWhvcmEgcmVhbGljZW1vcyBsYSBtaXNtYSBwcnVlYmEsIHV0aWxpemFuZG8gbGEgZWN1YWNpb24gY3VhZHJhdGljYSBwYXJhIGxvIHF1ZSBjYWxjdWxhcmVtb3MgJFNFKFxoYXR7XGJldGFfezB9fSkkLCAkU0UoXGhhdHtcYmV0YV97MX19KSQgeSAkU0UoXGhhdHtcYmV0YV97Mn19KSQKCmBgYHtyfQpib290X2ZuIDwtIGZ1bmN0aW9uKGRhdGEsIGluZGV4KXsKICByZXR1cm4oY29lZmZpY2llbnRzKGxtKG1wZ35ob3JzZXBvd2VyK0koaG9yc2Vwb3dlcl4yKSwgZGF0YT1kYXRhLCBzdWJzZXQ9aW5kZXgpKSkKfQoKc2V0LnNlZWQoMSkKCmJvb3QoQXV0bywgYm9vdF9mbiwgMTAwMCkKYGBgCgpgYGB7cn0Kc3VtbWFyeShsbShtcGd+aG9yc2Vwb3dlcitJKGhvcnNlcG93ZXJeMiksIGRhdGE9QXV0bykpJGNvZWYKYGBgCgpDb21vIHlhIGxvIGhlbW9zIHZpc3RvLCBhbCBkZWZpbmlyIHVuIG1vZGVsbyBjdWFkcmF0aWNvIGxvcyBjb2VmaWNpZW50ZXMgc2kgZGFuIGVsIG1pc21vIHJlc3VsdGFuZG8sIGNvbiBsbyBjdWFsIHRlcm1pbmFtb3MgZGUgZGVmaW5pciBxdWUgZWwgbW9kZWxvIGN1YWRyYXRpY28gZXMgZWwgcXVlIG1lam9yIHNlIGFqdXN0YS4=