DecisionTree

Jose Nuñez, Mario Salcedo, Julio Mejia, Carlos Valle

05/04/2022

Introducción

Como se explicó anteriormente los árboles de decisión son un método usado en diversas disciplinas usado como un modelo de predicción; estos tienen ciertas similitudes con los diagramas de flujo, en donde en ciertos puntos se basan en la toma de decisiones mediante unas reglas en específico.

Para realizar un ejercicio de ejemplo se utilizara un conjunto de datos sobre la producción del vino en el Machine Learning Repository, fuente: * https://archive.ics.uci.edu/ml/datasets/Wine.

Producción de Vino

Paquetes Utilizados

library(pacman) 
p_load("tidyverse", "rpart", "rpart.plot", "caret")

Importando los Datos

Se necesitan descargar dos archivos. El primero contiene todos los datos que seran utilizados y en el segundo contiene una descripción que mas adelante tendrá una utilidad importante.

# Datos
download.file("https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data", "wine.data")

# Información
download.file("https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.names", "wine.names")
vino <- readLines("wine.data", n = 10)

Podemos utilizar la función read.table para leer el archivo, ya que es una función diseñada para leer tablas de datos con su propia estructura.

vino <- read.table("wine.data", sep = ",", header = FALSE)

Comprobamos si el archivo wine.names tiene respuestas.

readLines("wine.names", n = 10)
##  [1] "1. Title of Database: Wine recognition data"                                    
##  [2] "\tUpdated Sept 21, 1998 by C.Blake : Added attribute information"                
##  [3] ""                                                                               
##  [4] "2. Sources:"                                                                    
##  [5] "   (a) Forina, M. et al, PARVUS - An Extendible Package for Data"               
##  [6] "       Exploration, Classification and Correlation. Institute of Pharmaceutical"
##  [7] "       and Food Analysis and Technologies, Via Brigata Salerno, "               
##  [8] "       16147 Genoa, Italy."                                                     
##  [9] ""                                                                               
## [10] "   (b) Stefan Aeberhard, email: stefan@coral.cs.jcu.edu.au"

Se puede crear una copia de los archivos a manera de respaldo con la extensión .txt para convertirlo en un bloc de notas.

file.copy(from = "wine.names", to = "wine_names.txt")
## [1] FALSE
file.show("wine_names.txt")
summary(vino)
##        V1              V2              V3              V4       
##  Min.   :1.000   Min.   :11.03   Min.   :0.740   Min.   :1.360  
##  1st Qu.:1.000   1st Qu.:12.36   1st Qu.:1.603   1st Qu.:2.210  
##  Median :2.000   Median :13.05   Median :1.865   Median :2.360  
##  Mean   :1.938   Mean   :13.00   Mean   :2.336   Mean   :2.367  
##  3rd Qu.:3.000   3rd Qu.:13.68   3rd Qu.:3.083   3rd Qu.:2.558  
##  Max.   :3.000   Max.   :14.83   Max.   :5.800   Max.   :3.230  
##        V5              V6               V7              V8       
##  Min.   :10.60   Min.   : 70.00   Min.   :0.980   Min.   :0.340  
##  1st Qu.:17.20   1st Qu.: 88.00   1st Qu.:1.742   1st Qu.:1.205  
##  Median :19.50   Median : 98.00   Median :2.355   Median :2.135  
##  Mean   :19.49   Mean   : 99.74   Mean   :2.295   Mean   :2.029  
##  3rd Qu.:21.50   3rd Qu.:107.00   3rd Qu.:2.800   3rd Qu.:2.875  
##  Max.   :30.00   Max.   :162.00   Max.   :3.880   Max.   :5.080  
##        V9              V10             V11              V12        
##  Min.   :0.1300   Min.   :0.410   Min.   : 1.280   Min.   :0.4800  
##  1st Qu.:0.2700   1st Qu.:1.250   1st Qu.: 3.220   1st Qu.:0.7825  
##  Median :0.3400   Median :1.555   Median : 4.690   Median :0.9650  
##  Mean   :0.3619   Mean   :1.591   Mean   : 5.058   Mean   :0.9574  
##  3rd Qu.:0.4375   3rd Qu.:1.950   3rd Qu.: 6.200   3rd Qu.:1.1200  
##  Max.   :0.6600   Max.   :3.580   Max.   :13.000   Max.   :1.7100  
##       V13             V14        
##  Min.   :1.270   Min.   : 278.0  
##  1st Qu.:1.938   1st Qu.: 500.5  
##  Median :2.780   Median : 673.5  
##  Mean   :2.612   Mean   : 746.9  
##  3rd Qu.:3.170   3rd Qu.: 985.0  
##  Max.   :4.000   Max.   :1680.0
nombres <- 
  readLines("wine_names.txt")[58:70] %>% 
  gsub("[[:cntrl:]].*\\)", "", .) %>% 
  trimws() %>% 
  tolower() %>% 
  gsub(" |/", "_", .) %>% 
  # Agregamos el nombre "tipo", para nuestra primera columna con los tipos de vino
  c("tipo", .)
names(vino) <- nombres 
vino <- vino %>% 
  mutate_at("tipo", factor)

Creando los sets de prueba

Se necesita un set de entrenamiento para poder generar un modelo predictivo al igual que un set de prueba, con el fin de hacer mas eficaz el modelo a la hora de hacer predicciones. Usamos la función sample_frac() de dplyr para obtener un subconjunto de nuestros datos, que consiste en 70% del total de ellos. Usamos también set.seed() para que este ejemplo sea reproducible.

set.seed(1649)
vino_entrenamiento <- sample_frac(vino, .7)

Con setdiff() de dplyr, obtenemos el subconjunto de datos complementario al de entrenamiento para nuestro set de prueba, esto es, el 30% restante.

vino_prueba <- setdiff(vino, vino_entrenamiento)

Utilizando el Modelo

Usamos la librería rpart para entrenar nuestro modelo. Esta función nos pide una formula para especificar la variable objetivo de la clasificación. La formula que usaremos es tipo ~ ., la cual expresa que intentaremos clasificar tipo usando a todas las demás variables como predictoras.

arbol_1 <- rpart(formula = tipo ~ ., data = vino_entrenamiento)

Evaluación y Analisis de los Datos

arbol_1
## n= 125 
## 
## node), split, n, loss, yval, (yprob)
##       * denotes terminal node
## 
## 1) root 125 75 2 (0.35200000 0.40000000 0.24800000)  
##   2) proline>=755 48  6 1 (0.87500000 0.04166667 0.08333333)  
##     4) flavanoids>=2.35 41  1 1 (0.97560976 0.02439024 0.00000000) *
##     5) flavanoids< 2.35 7  3 3 (0.28571429 0.14285714 0.57142857) *
##   3) proline< 755 77 29 2 (0.02597403 0.62337662 0.35064935)  
##     6) flavanoids>=1.265 51  4 2 (0.03921569 0.92156863 0.03921569) *
##     7) flavanoids< 1.265 26  1 3 (0.00000000 0.03846154 0.96153846) *
rpart.plot(arbol_1)

Como podemos observar en el gráfico vemos una serie de rectángulos, estos representan los nodos del árbol con su regla de clasificación. Los colores de cada nodo dependen de la categoría maroyitaria de los datos que agrupa.

Dentro del rectángulo de cada nodo se nos muestra qué proporción de casos pertenecen a cada categoría y la proporción del total de datos que han sido agrupados allí. Por ejemplo, el rectángulo en el extremo inferior izquierdo de la gráfica tiene 98% de casos en el tipo 1, y 2% en los tipo 2 y 0% en el tipo 3, que representan 33% de todos los datos.

Usamos la función precict() con nuestro set de prueba para generar un vector con los valores predichos por el modelo que hemos entrenado, especificamos el parámetro type = “class”.

prediccion_1 <- predict(arbol_1, newdata = vino_prueba, type = "class")

Cruzamos la predicción con los datos reales de nuestro set de prueba para generar una matriz de confusión, usando confusionMatrix() de caret.

confusionMatrix(prediccion_1, vino_prueba[["tipo"]])
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  1  2  3
##          1 15  0  0
##          2  0 15  3
##          3  0  6 14
## 
## Overall Statistics
##                                          
##                Accuracy : 0.8302         
##                  95% CI : (0.702, 0.9193)
##     No Information Rate : 0.3962         
##     P-Value [Acc > NIR] : 1.106e-10      
##                                          
##                   Kappa : 0.7444         
##                                          
##  Mcnemar's Test P-Value : NA             
## 
## Statistics by Class:
## 
##                      Class: 1 Class: 2 Class: 3
## Sensitivity             1.000   0.7143   0.8235
## Specificity             1.000   0.9062   0.8333
## Pos Pred Value          1.000   0.8333   0.7000
## Neg Pred Value          1.000   0.8286   0.9091
## Prevalence              0.283   0.3962   0.3208
## Detection Rate          0.283   0.2830   0.2642
## Detection Prevalence    0.283   0.3396   0.3774
## Balanced Accuracy       1.000   0.8103   0.8284

Segundo Árbol

Ahora se realizará un segundo árbol con sets de entrenamineto y pruebas totalmente distitnas al primero.

set.seed(7439)
vino_entrenamiento_2 <- sample_frac(vino, .7)

vino_prueba_2 <- setdiff(vino, vino_entrenamiento)

arbol_2 <- rpart(formula = tipo ~ ., data = vino_entrenamiento_2)

prediccion_2 <- predict(arbol_2, newdata = vino_prueba_2, type = "class")
rpart.plot(arbol_2)

confusionMatrix(prediccion_2, vino_prueba_2[["tipo"]])
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  1  2  3
##          1 14  0  0
##          2  1 21  0
##          3  0  0 17
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9811          
##                  95% CI : (0.8993, 0.9995)
##     No Information Rate : 0.3962          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.9713          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: 1 Class: 2 Class: 3
## Sensitivity            0.9333   1.0000   1.0000
## Specificity            1.0000   0.9688   1.0000
## Pos Pred Value         1.0000   0.9545   1.0000
## Neg Pred Value         0.9744   1.0000   1.0000
## Prevalence             0.2830   0.3962   0.3208
## Detection Rate         0.2642   0.3962   0.3208
## Detection Prevalence   0.2642   0.4151   0.3208
## Balanced Accuracy      0.9667   0.9844   1.0000

Ahora los resultados han tenido una mejora notable a la hora de predecir en comparación al primero. Sin embargo, es importante notar que este segundo modelo es diferente con respecto al anterior en el orden que fueron hechas las particiones, pero es idéntico en cuanto a las variables usadas para separar grupos.

Podemos cambiar los datos usados en los sets de entrenamiento y prueba y obtener resultados distintos.

set.seed(8476)
vino_entrenamiento_3 <- sample_frac(vino, .7)

vino_prueba_3 <- setdiff(vino, vino_entrenamiento)

arbol_3 <- rpart(formula = tipo ~ ., data = vino_entrenamiento_3)

prediccion_3 <- predict(arbol_3, newdata = vino_prueba_3, type = "class")

rpart.plot(arbol_3)

confusionMatrix(prediccion_3, vino_prueba_3[["tipo"]])
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  1  2  3
##          1 15  4  0
##          2  0 17  0
##          3  0  0 17
## 
## Overall Statistics
##                                           
##                Accuracy : 0.9245          
##                  95% CI : (0.8179, 0.9791)
##     No Information Rate : 0.3962          
##     P-Value [Acc > NIR] : 8.174e-16       
##                                           
##                   Kappa : 0.8871          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: 1 Class: 2 Class: 3
## Sensitivity            1.0000   0.8095   1.0000
## Specificity            0.8947   1.0000   1.0000
## Pos Pred Value         0.7895   1.0000   1.0000
## Neg Pred Value         1.0000   0.8889   1.0000
## Prevalence             0.2830   0.3962   0.3208
## Detection Rate         0.2830   0.3208   0.3208
## Detection Prevalence   0.3585   0.3208   0.3208
## Balanced Accuracy      0.9474   0.9048   1.0000

Esta ocasión hemos obtenido una precisión en nuestras predicciones similar al primer modelo que generamos, pero ahora una de las variables usadas en la partición es diferente.

En todos estos ejemplos, lo único que hemos cambiado son nuestros sets de prueba y entrenamiento, ningún otro parámetro ha cambiado.

Conclusión

Para concluir tomando en cuenta la definición, ventajas y desventajas de los árboles de decisión podemos implementarlos gracias al paquete rpart que ofrece R y así este nos ayuda a crear una gráfica que nos ayude a realizar predicciones. Generalmente en ocasiones se necesitan realizar algunos sets de entrenamiento más para llegar a una buena precisión de los datos.

Descarga este código

xfun::embed_file("DecisionTree.Rmd")

Download DecisionTree.Rmd

LS0tDQp0aXRsZTogIkRlY2lzaW9uVHJlZSINCmF1dGhvcjogIkpvc2UgTnXDsWV6LCBNYXJpbyBTYWxjZWRvLCBKdWxpbyBNZWppYSwgQ2FybG9zIFZhbGxlIg0KZGF0ZTogIjA1LzA0LzIwMjIiDQpvdXRwdXQ6IA0KICBybWRmb3JtYXRzOjpkb3duY3V0ZToNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgZGVmYXVsdDogZGFyaw0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojIEludHJvZHVjY2nDs24NCg0KQ29tbyBzZSBleHBsaWPDsyBhbnRlcmlvcm1lbnRlIGxvcyDDoXJib2xlcyBkZSBkZWNpc2nDs24gc29uIHVuIG3DqXRvZG8gdXNhZG8gZW4gZGl2ZXJzYXMgZGlzY2lwbGluYXMgdXNhZG8gY29tbyB1biBtb2RlbG8gZGUgcHJlZGljY2nDs247IGVzdG9zIHRpZW5lbiBjaWVydGFzIHNpbWlsaXR1ZGVzIGNvbiBsb3MgZGlhZ3JhbWFzIGRlIGZsdWpvLCBlbiBkb25kZSBlbiBjaWVydG9zIHB1bnRvcyBzZSBiYXNhbiBlbiBsYSB0b21hIGRlIGRlY2lzaW9uZXMgbWVkaWFudGUgdW5hcyByZWdsYXMgZW4gZXNwZWPDrWZpY28uIA0KDQpQYXJhIHJlYWxpemFyIHVuIGVqZXJjaWNpbyBkZSBlamVtcGxvIHNlIHV0aWxpemFyYSB1biBjb25qdW50byBkZSBkYXRvcyBzb2JyZSBsYSBwcm9kdWNjacOzbiBkZWwgdmlubyBlbiBlbCBNYWNoaW5lIExlYXJuaW5nIFJlcG9zaXRvcnksIGZ1ZW50ZTogKiBodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvZGF0YXNldHMvV2luZS4gDQoNCiFbUHJvZHVjY2nDs24gZGUgVmlub10odmluby5qcGcpICANCg0KIyBQYXF1ZXRlcyBVdGlsaXphZG9zIA0KDQpgYGB7cn0NCmxpYnJhcnkocGFjbWFuKSANCnBfbG9hZCgidGlkeXZlcnNlIiwgInJwYXJ0IiwgInJwYXJ0LnBsb3QiLCAiY2FyZXQiKQ0KYGBgDQoNCiMgSW1wb3J0YW5kbyBsb3MgRGF0b3MgIA0KDQpTZSBuZWNlc2l0YW4gZGVzY2FyZ2FyIGRvcyBhcmNoaXZvcy4gRWwgcHJpbWVybyBjb250aWVuZSB0b2RvcyBsb3MgZGF0b3MgcXVlIHNlcmFuIHV0aWxpemFkb3MgeSBlbiBlbCBzZWd1bmRvIGNvbnRpZW5lIHVuYSBkZXNjcmlwY2nDs24gcXVlIG1hcyBhZGVsYW50ZSB0ZW5kcsOhIHVuYSB1dGlsaWRhZCBpbXBvcnRhbnRlLiANCg0KYGBge3J9DQojIERhdG9zDQpkb3dubG9hZC5maWxlKCJodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvd2luZS93aW5lLmRhdGEiLCAid2luZS5kYXRhIikNCg0KIyBJbmZvcm1hY2nDs24NCmRvd25sb2FkLmZpbGUoImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy93aW5lL3dpbmUubmFtZXMiLCAid2luZS5uYW1lcyIpDQpgYGANCg0KYGBge3J9DQp2aW5vIDwtIHJlYWRMaW5lcygid2luZS5kYXRhIiwgbiA9IDEwKQ0KYGBgDQoNClBvZGVtb3MgdXRpbGl6YXIgbGEgZnVuY2nDs24gcmVhZC50YWJsZSBwYXJhIGxlZXIgZWwgYXJjaGl2bywgeWEgcXVlIGVzIHVuYSBmdW5jacOzbiBkaXNlw7FhZGEgcGFyYSBsZWVyIHRhYmxhcyBkZSBkYXRvcyBjb24gc3UgcHJvcGlhIGVzdHJ1Y3R1cmEuDQoNCmBgYHtyfQ0KdmlubyA8LSByZWFkLnRhYmxlKCJ3aW5lLmRhdGEiLCBzZXAgPSAiLCIsIGhlYWRlciA9IEZBTFNFKQ0KYGBgDQoNCkNvbXByb2JhbW9zIHNpIGVsIGFyY2hpdm8gd2luZS5uYW1lcyB0aWVuZSByZXNwdWVzdGFzLiANCg0KYGBge3J9DQpyZWFkTGluZXMoIndpbmUubmFtZXMiLCBuID0gMTApDQpgYGANCg0KU2UgcHVlZGUgY3JlYXIgdW5hIGNvcGlhIGRlIGxvcyBhcmNoaXZvcyBhIG1hbmVyYSBkZSByZXNwYWxkbyBjb24gbGEgZXh0ZW5zacOzbiAudHh0IHBhcmEgY29udmVydGlybG8gZW4gdW4gYmxvYyBkZSBub3Rhcy4gDQoNCmBgYHtyfQ0KZmlsZS5jb3B5KGZyb20gPSAid2luZS5uYW1lcyIsIHRvID0gIndpbmVfbmFtZXMudHh0IikNCg0KZmlsZS5zaG93KCJ3aW5lX25hbWVzLnR4dCIpDQpgYGANCmBgYHtyfQ0Kc3VtbWFyeSh2aW5vKQ0KYGBgDQpgYGB7cn0NCm5vbWJyZXMgPC0gDQogIHJlYWRMaW5lcygid2luZV9uYW1lcy50eHQiKVs1ODo3MF0gJT4lIA0KICBnc3ViKCJbWzpjbnRybDpdXS4qXFwpIiwgIiIsIC4pICU+JSANCiAgdHJpbXdzKCkgJT4lIA0KICB0b2xvd2VyKCkgJT4lIA0KICBnc3ViKCIgfC8iLCAiXyIsIC4pICU+JSANCiAgIyBBZ3JlZ2Ftb3MgZWwgbm9tYnJlICJ0aXBvIiwgcGFyYSBudWVzdHJhIHByaW1lcmEgY29sdW1uYSBjb24gbG9zIHRpcG9zIGRlIHZpbm8NCiAgYygidGlwbyIsIC4pDQpgYGANCg0KYGBge3J9DQpuYW1lcyh2aW5vKSA8LSBub21icmVzIA0KYGBgDQoNCmBgYHtyfQ0KdmlubyA8LSB2aW5vICU+JSANCiAgbXV0YXRlX2F0KCJ0aXBvIiwgZmFjdG9yKQ0KYGBgDQoNCiMgQ3JlYW5kbyBsb3Mgc2V0cyBkZSBwcnVlYmENCg0KU2UgbmVjZXNpdGEgdW4gc2V0IGRlIGVudHJlbmFtaWVudG8gcGFyYSBwb2RlciBnZW5lcmFyIHVuIG1vZGVsbyBwcmVkaWN0aXZvIGFsIGlndWFsIHF1ZSB1biBzZXQgZGUgcHJ1ZWJhLCBjb24gZWwgZmluIGRlIGhhY2VyIG1hcyBlZmljYXogZWwgbW9kZWxvIGEgbGEgaG9yYSBkZSBoYWNlciBwcmVkaWNjaW9uZXMuIA0KVXNhbW9zIGxhIGZ1bmNpw7NuIHNhbXBsZV9mcmFjKCkgZGUgZHBseXIgcGFyYSBvYnRlbmVyIHVuIHN1YmNvbmp1bnRvIGRlIG51ZXN0cm9zIGRhdG9zLCBxdWUgY29uc2lzdGUgZW4gNzAlIGRlbCB0b3RhbCBkZSBlbGxvcy4gVXNhbW9zIHRhbWJpw6luIHNldC5zZWVkKCkgcGFyYSBxdWUgZXN0ZSBlamVtcGxvIHNlYSByZXByb2R1Y2libGUuIA0KDQpgYGB7cn0NCnNldC5zZWVkKDE2NDkpDQp2aW5vX2VudHJlbmFtaWVudG8gPC0gc2FtcGxlX2ZyYWModmlubywgLjcpDQpgYGANCg0KQ29uIHNldGRpZmYoKSBkZSBkcGx5ciwgb2J0ZW5lbW9zIGVsIHN1YmNvbmp1bnRvIGRlIGRhdG9zIGNvbXBsZW1lbnRhcmlvIGFsIGRlIGVudHJlbmFtaWVudG8gcGFyYSBudWVzdHJvIHNldCBkZSBwcnVlYmEsIGVzdG8gZXMsIGVsIDMwJSByZXN0YW50ZS4NCg0KYGBge3J9DQp2aW5vX3BydWViYSA8LSBzZXRkaWZmKHZpbm8sIHZpbm9fZW50cmVuYW1pZW50bykNCmBgYA0KDQojIFV0aWxpemFuZG8gZWwgTW9kZWxvIA0KDQpVc2Ftb3MgbGEgbGlicmVyw61hIHJwYXJ0IHBhcmEgZW50cmVuYXIgbnVlc3RybyBtb2RlbG8uIEVzdGEgZnVuY2nDs24gbm9zIHBpZGUgdW5hIGZvcm11bGEgcGFyYSBlc3BlY2lmaWNhciBsYSB2YXJpYWJsZSBvYmpldGl2byBkZSBsYSBjbGFzaWZpY2FjacOzbi4gTGEgZm9ybXVsYSBxdWUgdXNhcmVtb3MgZXMgdGlwbyB+IC4sIGxhIGN1YWwgZXhwcmVzYSBxdWUgaW50ZW50YXJlbW9zIGNsYXNpZmljYXIgdGlwbyB1c2FuZG8gYSB0b2RhcyBsYXMgZGVtw6FzIHZhcmlhYmxlcyBjb21vIHByZWRpY3RvcmFzLiANCg0KYGBge3J9DQphcmJvbF8xIDwtIHJwYXJ0KGZvcm11bGEgPSB0aXBvIH4gLiwgZGF0YSA9IHZpbm9fZW50cmVuYW1pZW50bykNCmBgYA0KDQojIEV2YWx1YWNpw7NuIHkgQW5hbGlzaXMgZGUgbG9zIERhdG9zDQoNCmBgYHtyfQ0KYXJib2xfMQ0KYGBgDQpgYGB7cn0NCnJwYXJ0LnBsb3QoYXJib2xfMSkNCmBgYA0KQ29tbyBwb2RlbW9zIG9ic2VydmFyIGVuIGVsIGdyw6FmaWNvIHZlbW9zIHVuYSBzZXJpZSBkZSByZWN0w6FuZ3Vsb3MsIGVzdG9zIHJlcHJlc2VudGFuIGxvcyBub2RvcyBkZWwgw6FyYm9sIGNvbiBzdSByZWdsYSBkZSBjbGFzaWZpY2FjacOzbi4gTG9zIGNvbG9yZXMgZGUgY2FkYSBub2RvIGRlcGVuZGVuIGRlIGxhIGNhdGVnb3LDrWEgbWFyb3lpdGFyaWEgZGUgbG9zIGRhdG9zIHF1ZSBhZ3J1cGEuIA0KDQpEZW50cm8gZGVsIHJlY3TDoW5ndWxvIGRlIGNhZGEgbm9kbyBzZSBub3MgbXVlc3RyYSBxdcOpIHByb3BvcmNpw7NuIGRlIGNhc29zIHBlcnRlbmVjZW4gYSBjYWRhIGNhdGVnb3LDrWEgeSBsYSBwcm9wb3JjacOzbiBkZWwgdG90YWwgZGUgZGF0b3MgcXVlIGhhbiBzaWRvIGFncnVwYWRvcyBhbGzDrS4gUG9yIGVqZW1wbG8sIGVsIHJlY3TDoW5ndWxvIGVuIGVsIGV4dHJlbW8gaW5mZXJpb3IgaXpxdWllcmRvIGRlIGxhIGdyw6FmaWNhIHRpZW5lIDk4JSBkZSBjYXNvcyBlbiBlbCB0aXBvIDEsIHkgMiUgZW4gbG9zIHRpcG8gMiB5IDAlIGVuIGVsIHRpcG8gMywgcXVlIHJlcHJlc2VudGFuIDMzJSBkZSB0b2RvcyBsb3MgZGF0b3MuIA0KDQpVc2Ftb3MgbGEgZnVuY2nDs24gcHJlY2ljdCgpIGNvbiBudWVzdHJvIHNldCBkZSBwcnVlYmEgcGFyYSBnZW5lcmFyIHVuIHZlY3RvciBjb24gbG9zIHZhbG9yZXMgcHJlZGljaG9zIHBvciBlbCBtb2RlbG8gcXVlIGhlbW9zIGVudHJlbmFkbywgZXNwZWNpZmljYW1vcyBlbCBwYXLDoW1ldHJvIHR5cGUgPSAiY2xhc3MiLiANCg0KYGBge3J9DQpwcmVkaWNjaW9uXzEgPC0gcHJlZGljdChhcmJvbF8xLCBuZXdkYXRhID0gdmlub19wcnVlYmEsIHR5cGUgPSAiY2xhc3MiKQ0KYGBgDQoNCkNydXphbW9zIGxhIHByZWRpY2Npw7NuIGNvbiBsb3MgZGF0b3MgcmVhbGVzIGRlIG51ZXN0cm8gc2V0IGRlIHBydWViYSBwYXJhIGdlbmVyYXIgdW5hIG1hdHJpeiBkZSBjb25mdXNpw7NuLCB1c2FuZG8gY29uZnVzaW9uTWF0cml4KCkgZGUgY2FyZXQuIA0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWNjaW9uXzEsIHZpbm9fcHJ1ZWJhW1sidGlwbyJdXSkNCmBgYA0KIyBTZWd1bmRvIMOBcmJvbCANCg0KQWhvcmEgc2UgcmVhbGl6YXLDoSB1biBzZWd1bmRvIMOhcmJvbCBjb24gc2V0cyBkZSBlbnRyZW5hbWluZXRvIHkgcHJ1ZWJhcyB0b3RhbG1lbnRlIGRpc3RpdG5hcyBhbCBwcmltZXJvLiANCg0KYGBge3J9DQpzZXQuc2VlZCg3NDM5KQ0Kdmlub19lbnRyZW5hbWllbnRvXzIgPC0gc2FtcGxlX2ZyYWModmlubywgLjcpDQoNCnZpbm9fcHJ1ZWJhXzIgPC0gc2V0ZGlmZih2aW5vLCB2aW5vX2VudHJlbmFtaWVudG8pDQoNCmFyYm9sXzIgPC0gcnBhcnQoZm9ybXVsYSA9IHRpcG8gfiAuLCBkYXRhID0gdmlub19lbnRyZW5hbWllbnRvXzIpDQoNCnByZWRpY2Npb25fMiA8LSBwcmVkaWN0KGFyYm9sXzIsIG5ld2RhdGEgPSB2aW5vX3BydWViYV8yLCB0eXBlID0gImNsYXNzIikNCmBgYA0KDQpgYGB7cn0NCnJwYXJ0LnBsb3QoYXJib2xfMikNCmBgYA0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWNjaW9uXzIsIHZpbm9fcHJ1ZWJhXzJbWyJ0aXBvIl1dKQ0KYGBgDQpBaG9yYSBsb3MgcmVzdWx0YWRvcyBoYW4gdGVuaWRvIHVuYSBtZWpvcmEgbm90YWJsZSBhIGxhIGhvcmEgZGUgcHJlZGVjaXIgZW4gY29tcGFyYWNpw7NuIGFsIHByaW1lcm8uDQpTaW4gZW1iYXJnbywgZXMgaW1wb3J0YW50ZSBub3RhciBxdWUgZXN0ZSBzZWd1bmRvIG1vZGVsbyBlcyBkaWZlcmVudGUgY29uIHJlc3BlY3RvIGFsIGFudGVyaW9yIGVuIGVsIG9yZGVuIHF1ZSBmdWVyb24gaGVjaGFzIGxhcyBwYXJ0aWNpb25lcywgcGVybyBlcyBpZMOpbnRpY28gZW4gY3VhbnRvIGEgbGFzIHZhcmlhYmxlcyB1c2FkYXMgcGFyYSBzZXBhcmFyIGdydXBvcy4NCg0KUG9kZW1vcyBjYW1iaWFyIGxvcyBkYXRvcyB1c2Fkb3MgZW4gbG9zIHNldHMgZGUgZW50cmVuYW1pZW50byB5IHBydWViYSB5IG9idGVuZXIgcmVzdWx0YWRvcyBkaXN0aW50b3MuIA0KYGBge3J9DQpzZXQuc2VlZCg4NDc2KQ0Kdmlub19lbnRyZW5hbWllbnRvXzMgPC0gc2FtcGxlX2ZyYWModmlubywgLjcpDQoNCnZpbm9fcHJ1ZWJhXzMgPC0gc2V0ZGlmZih2aW5vLCB2aW5vX2VudHJlbmFtaWVudG8pDQoNCmFyYm9sXzMgPC0gcnBhcnQoZm9ybXVsYSA9IHRpcG8gfiAuLCBkYXRhID0gdmlub19lbnRyZW5hbWllbnRvXzMpDQoNCnByZWRpY2Npb25fMyA8LSBwcmVkaWN0KGFyYm9sXzMsIG5ld2RhdGEgPSB2aW5vX3BydWViYV8zLCB0eXBlID0gImNsYXNzIikNCg0KcnBhcnQucGxvdChhcmJvbF8zKQ0KYGBgDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWNjaW9uXzMsIHZpbm9fcHJ1ZWJhXzNbWyJ0aXBvIl1dKQ0KYGBgDQpFc3RhIG9jYXNpw7NuIGhlbW9zIG9idGVuaWRvIHVuYSBwcmVjaXNpw7NuIGVuIG51ZXN0cmFzIHByZWRpY2Npb25lcyBzaW1pbGFyIGFsIHByaW1lciBtb2RlbG8gcXVlIGdlbmVyYW1vcywgcGVybyBhaG9yYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyB1c2FkYXMgZW4gbGEgcGFydGljacOzbiBlcyBkaWZlcmVudGUuDQoNCkVuIHRvZG9zIGVzdG9zIGVqZW1wbG9zLCBsbyDDum5pY28gcXVlIGhlbW9zIGNhbWJpYWRvIHNvbiBudWVzdHJvcyBzZXRzIGRlIHBydWViYSB5IGVudHJlbmFtaWVudG8sIG5pbmfDum4gb3RybyBwYXLDoW1ldHJvIGhhIGNhbWJpYWRvLiAgDQoNCiMgQ29uY2x1c2nDs24gDQoNClBhcmEgY29uY2x1aXIgdG9tYW5kbyBlbiBjdWVudGEgbGEgZGVmaW5pY2nDs24sIHZlbnRhamFzIHkgZGVzdmVudGFqYXMgZGUgbG9zIMOhcmJvbGVzIGRlIGRlY2lzacOzbiBwb2RlbW9zIGltcGxlbWVudGFybG9zIGdyYWNpYXMgYWwgcGFxdWV0ZSBycGFydCBxdWUgb2ZyZWNlIFIgeSBhc8OtIGVzdGUgbm9zIGF5dWRhIGEgY3JlYXIgdW5hIGdyw6FmaWNhIHF1ZSBub3MgYXl1ZGUgYSByZWFsaXphciBwcmVkaWNjaW9uZXMuIEdlbmVyYWxtZW50ZSBlbiBvY2FzaW9uZXMgc2UgbmVjZXNpdGFuIHJlYWxpemFyIGFsZ3Vub3Mgc2V0cyBkZSBlbnRyZW5hbWllbnRvIG3DoXMgcGFyYSBsbGVnYXIgYSB1bmEgYnVlbmEgcHJlY2lzacOzbiBkZSBsb3MgZGF0b3MuICANCg0KIyBEZXNjYXJnYSBlc3RlIGPDs2RpZ28gDQoNCmBgYHtyfQ0KeGZ1bjo6ZW1iZWRfZmlsZSgiRGVjaXNpb25UcmVlLlJtZCIpDQpgYGA=