Teoría

El Bosque Aleatorio es un algoritmo de aprendizaje automático que combina el resultado de múltiples árboles de decisión para llegar a un resultado óptimo.

Ejemplo 1. Melbourne

En esta base de datos tenemos los precios de más de 13,000 casas de la ciudad de Melbourne.

Instalar paquetes y llamar librerías

# install.packages(c("tidyverse","rpart","rpart.plot","randomForest","modelr", "caret"))

suppressPackageStartupMessages({
  library(tidyverse)
  library(rpart)
  library(rpart.plot)
  library(randomForest)
  library(modelr) # errores
  library(caret) # particiones
})

Instalar paquetes y llamar librerías

df <- read.csv("~/Library/CloudStorage/OneDrive-InstitutoTecnologicoydeEstudiosSuperioresdeMonterrey/SEM 7/M2/melbourne.csv")

Entender la base de datos

summary(df)
##     Suburb            Address              Rooms            Type          
##  Length:13580       Length:13580       Min.   : 1.000   Length:13580      
##  Class :character   Class :character   1st Qu.: 2.000   Class :character  
##  Mode  :character   Mode  :character   Median : 3.000   Mode  :character  
##                                        Mean   : 2.938                     
##                                        3rd Qu.: 3.000                     
##                                        Max.   :10.000                     
##                                                                           
##      Price            Method            SellerG              Date          
##  Min.   :  85000   Length:13580       Length:13580       Length:13580      
##  1st Qu.: 650000   Class :character   Class :character   Class :character  
##  Median : 903000   Mode  :character   Mode  :character   Mode  :character  
##  Mean   :1075684                                                           
##  3rd Qu.:1330000                                                           
##  Max.   :9000000                                                           
##                                                                            
##     Distance        Postcode       Bedroom2         Bathroom    
##  Min.   : 0.00   Min.   :3000   Min.   : 0.000   Min.   :0.000  
##  1st Qu.: 6.10   1st Qu.:3044   1st Qu.: 2.000   1st Qu.:1.000  
##  Median : 9.20   Median :3084   Median : 3.000   Median :1.000  
##  Mean   :10.14   Mean   :3105   Mean   : 2.915   Mean   :1.534  
##  3rd Qu.:13.00   3rd Qu.:3148   3rd Qu.: 3.000   3rd Qu.:2.000  
##  Max.   :48.10   Max.   :3977   Max.   :20.000   Max.   :8.000  
##                                                                 
##       Car           Landsize         BuildingArea     YearBuilt   
##  Min.   : 0.00   Min.   :     0.0   Min.   :    0   Min.   :1196  
##  1st Qu.: 1.00   1st Qu.:   177.0   1st Qu.:   93   1st Qu.:1940  
##  Median : 2.00   Median :   440.0   Median :  126   Median :1970  
##  Mean   : 1.61   Mean   :   558.4   Mean   :  152   Mean   :1965  
##  3rd Qu.: 2.00   3rd Qu.:   651.0   3rd Qu.:  174   3rd Qu.:1999  
##  Max.   :10.00   Max.   :433014.0   Max.   :44515   Max.   :2018  
##  NA's   :62                         NA's   :6450    NA's   :5375  
##  CouncilArea          Lattitude        Longtitude     Regionname       
##  Length:13580       Min.   :-38.18   Min.   :144.4   Length:13580      
##  Class :character   1st Qu.:-37.86   1st Qu.:144.9   Class :character  
##  Mode  :character   Median :-37.80   Median :145.0   Mode  :character  
##                     Mean   :-37.81   Mean   :145.0                     
##                     3rd Qu.:-37.76   3rd Qu.:145.1                     
##                     Max.   :-37.41   Max.   :145.5                     
##                                                                        
##  Propertycount  
##  Min.   :  249  
##  1st Qu.: 4380  
##  Median : 6555  
##  Mean   : 7454  
##  3rd Qu.:10331  
##  Max.   :21650  
## 
str(df)
## 'data.frame':    13580 obs. of  21 variables:
##  $ Suburb       : chr  "Abbotsford" "Abbotsford" "Abbotsford" "Abbotsford" ...
##  $ Address      : chr  "85 Turner St" "25 Bloomburg St" "5 Charles St" "40 Federation La" ...
##  $ Rooms        : int  2 2 3 3 4 2 3 2 1 2 ...
##  $ Type         : chr  "h" "h" "h" "h" ...
##  $ Price        : num  1480000 1035000 1465000 850000 1600000 ...
##  $ Method       : chr  "S" "S" "SP" "PI" ...
##  $ SellerG      : chr  "Biggin" "Biggin" "Biggin" "Biggin" ...
##  $ Date         : chr  "3/12/2016" "4/02/2016" "4/03/2017" "4/03/2017" ...
##  $ Distance     : num  2.5 2.5 2.5 2.5 2.5 2.5 2.5 2.5 2.5 2.5 ...
##  $ Postcode     : num  3067 3067 3067 3067 3067 ...
##  $ Bedroom2     : num  2 2 3 3 3 2 4 2 1 3 ...
##  $ Bathroom     : num  1 1 2 2 1 1 2 1 1 1 ...
##  $ Car          : num  1 0 0 1 2 0 0 2 1 2 ...
##  $ Landsize     : num  202 156 134 94 120 181 245 256 0 220 ...
##  $ BuildingArea : num  NA 79 150 NA 142 NA 210 107 NA 75 ...
##  $ YearBuilt    : num  NA 1900 1900 NA 2014 ...
##  $ CouncilArea  : chr  "Yarra" "Yarra" "Yarra" "Yarra" ...
##  $ Lattitude    : num  -37.8 -37.8 -37.8 -37.8 -37.8 ...
##  $ Longtitude   : num  145 145 145 145 145 ...
##  $ Regionname   : chr  "Northern Metropolitan" "Northern Metropolitan" "Northern Metropolitan" "Northern Metropolitan" ...
##  $ Propertycount: num  4019 4019 4019 4019 4019 ...
# borrar na
df <- na.omit(df)

Árbol de Decisión

arbol <- rpart(Price ~ Rooms + Distance + Bedroom2 + Bathroom + Car + Landsize + BuildingArea + Propertycount + YearBuilt, data=df)
plot(arbol, uniform=TRUE)
text(arbol, cex=.5)

predict(arbol, head(df))
##       2       3       5       7       8      10 
## 1095996 1562641 1070605 2422140 1095996 1095996
head(df$Price)
## [1] 1035000 1465000 1600000 1876000 1636000 1097000
# MAE: Error cuadrado promedio (Ventaja: Mismas unidades)

prueba_arbol <- head(df)

mae_arbol <- mae(arbol, prueba_arbol)

Bosque Aleatorio

set.seed(123)
renglones_entrenamiento <-  createDataPartition(df$Price, p=0.8, list=FALSE)

entrenamiento <-  df[renglones_entrenamiento, ]
prueba <- df[-renglones_entrenamiento, ]

rf <- randomForest(Price ~ Rooms + Distance + Bedroom2 + Bathroom + Car + Landsize + BuildingArea + Propertycount + YearBuilt, data=entrenamiento, ntree=500, mtry=3, importance=TRUE)

resultado_entrenamiento <- predict(rf, entrenamiento)
resultado_prueba <- predict(rf, prueba)

mae_rf <- mae(rf, prueba)

resultados <- tibble(Modelo = c("Árbol de Decisión", "Bosque Aleatorio"), MAE = c(mae_arbol, mae_rf))
resultados
## # A tibble: 2 × 2
##   Modelo                MAE
##   <chr>               <dbl>
## 1 Árbol de Decisión 295863.
## 2 Bosque Aleatorio  213172.

Ejercicio 1. Rendimiento Automotriz

Importar la base de datos

# mpg, rendimiento de los carros como variable de interes
df2 <- mtcars

Entender la base de datos

Variable objetivo: mpg (rendimiento).

  • mpg: Millas por galón (rendimiento del combustible).
  • cyl: Número de cilindros del motor.
  • disp: Cilindrada del motor en pulgadas cúbicas (tamaño del motor).
  • hp: Potencia bruta del motor en caballos de fuerza.
  • drat: Relación del eje trasero (rear axle ratio).
  • wt: Peso del automóvil en miles de libras (1 = 1000 lbs).
  • qsec: Tiempo en segundos en recorrer 1/4 de milla.
  • vs: Tipo de motor:
    • 0 = motor en V
    • 1 = motor en línea
  • am: Tipo de transmisión:
    • 0 = automática
    • 1 = manual
  • gear: Número de marchas hacia adelante.
  • carb: Número de carburadores.
summary(df2)
##       mpg             cyl             disp             hp       
##  Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0  
##  1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5  
##  Median :19.20   Median :6.000   Median :196.3   Median :123.0  
##  Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7  
##  3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0  
##  Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0  
##       drat             wt             qsec             vs        
##  Min.   :2.760   Min.   :1.513   Min.   :14.50   Min.   :0.0000  
##  1st Qu.:3.080   1st Qu.:2.581   1st Qu.:16.89   1st Qu.:0.0000  
##  Median :3.695   Median :3.325   Median :17.71   Median :0.0000  
##  Mean   :3.597   Mean   :3.217   Mean   :17.85   Mean   :0.4375  
##  3rd Qu.:3.920   3rd Qu.:3.610   3rd Qu.:18.90   3rd Qu.:1.0000  
##  Max.   :4.930   Max.   :5.424   Max.   :22.90   Max.   :1.0000  
##        am              gear            carb      
##  Min.   :0.0000   Min.   :3.000   Min.   :1.000  
##  1st Qu.:0.0000   1st Qu.:3.000   1st Qu.:2.000  
##  Median :0.0000   Median :4.000   Median :2.000  
##  Mean   :0.4062   Mean   :3.688   Mean   :2.812  
##  3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:4.000  
##  Max.   :1.0000   Max.   :5.000   Max.   :8.000
str(df2)
## 'data.frame':    32 obs. of  11 variables:
##  $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
##  $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
##  $ disp: num  160 160 108 258 360 ...
##  $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
##  $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
##  $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
##  $ qsec: num  16.5 17 18.6 19.4 17 ...
##  $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
##  $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
##  $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
##  $ carb: num  4 4 1 1 2 1 4 2 2 4 ...

Árbol de Decisión

arbol2 <- rpart(mpg ~ cyl + disp + disp + hp + drat + wt + qsec + gear + carb, data=df2)
rpart.plot(
  arbol2,
  type = 2,          # splits on branches, labels on leaves
  extra = 101,       # show fitted value, #obs, %obs
)

predict(arbol2, head(df2))
##         Mazda RX4     Mazda RX4 Wag        Datsun 710    Hornet 4 Drive 
##          18.26429          18.26429          26.66364          18.26429 
## Hornet Sportabout           Valiant 
##          18.26429          18.26429
head(df2$mpg)
## [1] 21.0 21.0 22.8 21.4 18.7 18.1
# MAE: Error cuadrado promedio (Ventaja: Mismas unidades)

prueba_arbol2 <- head(df2)

mae_arbol2 <- mae(arbol2, prueba_arbol2)

Bosque Aleatorio

set.seed(123)
renglones_entrenamiento2 <-  createDataPartition(df2$mpg, p=0.8, list=FALSE)

entrenamiento2 <-  df2[renglones_entrenamiento2, ]
prueba2 <- df2[-renglones_entrenamiento2, ]

rf2 <- randomForest(mpg ~ cyl + disp + disp + hp + drat + wt + qsec + gear + carb, data=df2, ntree=500, mtry=3, importance=TRUE)

resultado_entrenamiento2 <- predict(rf2, entrenamiento2)
resultado_prueba2 <- predict(rf2, prueba2)

mae_rf2 <- mae(rf2, prueba2)

resultados2 <- tibble(Modelo = c("Árbol de Decisión", "Bosque Aleatorio"), MAE = c(mae_arbol2, mae_rf2))
resultados2
## # A tibble: 2 × 2
##   Modelo              MAE
##   <chr>             <dbl>
## 1 Árbol de Decisión 2.18 
## 2 Bosque Aleatorio  0.951
LS0tCnRpdGxlOiAiUmFuZG9tIEZvcmVzdCIKYXV0aG9yOiAiQW5uYSBEdXLDoW4gQTAxMjg1Njc0IgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogVFJVRQogICAgdGhlbWU6IGpvdXJuYWwKLS0tCgohW10oaHR0cHM6Ly9lbmNyeXB0ZWQtdGJuMC5nc3RhdGljLmNvbS9pbWFnZXM/cT10Ym46QU5kOUdjUnoxdnB4NDhXVTNxLTZQNVRDcWk1a2RvSlhTUmE5VFVRdnd3JnMpCgojIDxzcGFuIHN0eWxlPSJjb2xvcjpicm93biI+IFRlb3LDrWEgPC9zcGFuPgpFbCAqKkJvc3F1ZSBBbGVhdG9yaW8qKiBlcyB1biBhbGdvcml0bW8gZGUgYXByZW5kaXphamUgYXV0b23DoXRpY28gcXVlIGNvbWJpbmEgZWwgcmVzdWx0YWRvIGRlIG3Dumx0aXBsZXMgw6FyYm9sZXMgZGUgZGVjaXNpw7NuIHBhcmEgbGxlZ2FyIGEgdW4gcmVzdWx0YWRvIMOzcHRpbW8uCgojIDxzcGFuIHN0eWxlPSJjb2xvcjpicm93biI+IEVqZW1wbG8gMS4gTWVsYm91cm5lIDwvc3Bhbj4KRW4gZXN0YSBiYXNlIGRlIGRhdG9zIHRlbmVtb3MgbG9zIHByZWNpb3MgZGUgbcOhcyBkZSAxMywwMDAgY2FzYXMgZGUgbGEgY2l1ZGFkIGRlIE1lbGJvdXJuZS4KCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjpicm93biI+IEluc3RhbGFyIHBhcXVldGVzIHkgbGxhbWFyIGxpYnJlcsOtYXMgPC9zcGFuPgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKGMoInRpZHl2ZXJzZSIsInJwYXJ0IiwicnBhcnQucGxvdCIsInJhbmRvbUZvcmVzdCIsIm1vZGVsciIsICJjYXJldCIpKQoKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICBsaWJyYXJ5KHRpZHl2ZXJzZSkKICBsaWJyYXJ5KHJwYXJ0KQogIGxpYnJhcnkocnBhcnQucGxvdCkKICBsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKICBsaWJyYXJ5KG1vZGVscikgIyBlcnJvcmVzCiAgbGlicmFyeShjYXJldCkgIyBwYXJ0aWNpb25lcwp9KQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjpicm93biI+IEluc3RhbGFyIHBhcXVldGVzIHkgbGxhbWFyIGxpYnJlcsOtYXMgPC9zcGFuPgpgYGB7cn0KZGYgPC0gcmVhZC5jc3YoIn4vTGlicmFyeS9DbG91ZFN0b3JhZ2UvT25lRHJpdmUtSW5zdGl0dXRvVGVjbm9sb2dpY295ZGVFc3R1ZGlvc1N1cGVyaW9yZXNkZU1vbnRlcnJleS9TRU0gNy9NMi9tZWxib3VybmUuY3N2IikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6YnJvd24iPiBFbnRlbmRlciBsYSBiYXNlIGRlIGRhdG9zIDwvc3Bhbj4KYGBge3J9CnN1bW1hcnkoZGYpCnN0cihkZikKCiMgYm9ycmFyIG5hCmRmIDwtIG5hLm9taXQoZGYpCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJyb3duIj4gw4FyYm9sIGRlIERlY2lzacOzbiA8L3NwYW4+CmBgYHtyfQphcmJvbCA8LSBycGFydChQcmljZSB+IFJvb21zICsgRGlzdGFuY2UgKyBCZWRyb29tMiArIEJhdGhyb29tICsgQ2FyICsgTGFuZHNpemUgKyBCdWlsZGluZ0FyZWEgKyBQcm9wZXJ0eWNvdW50ICsgWWVhckJ1aWx0LCBkYXRhPWRmKQpwbG90KGFyYm9sLCB1bmlmb3JtPVRSVUUpCnRleHQoYXJib2wsIGNleD0uNSkKCnByZWRpY3QoYXJib2wsIGhlYWQoZGYpKQpoZWFkKGRmJFByaWNlKQoKIyBNQUU6IEVycm9yIGN1YWRyYWRvIHByb21lZGlvIChWZW50YWphOiBNaXNtYXMgdW5pZGFkZXMpCgpwcnVlYmFfYXJib2wgPC0gaGVhZChkZikKCm1hZV9hcmJvbCA8LSBtYWUoYXJib2wsIHBydWViYV9hcmJvbCkKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6YnJvd24iPiBCb3NxdWUgQWxlYXRvcmlvIDwvc3Bhbj4KYGBge3J9CnNldC5zZWVkKDEyMykKcmVuZ2xvbmVzX2VudHJlbmFtaWVudG8gPC0gIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGYkUHJpY2UsIHA9MC44LCBsaXN0PUZBTFNFKQoKZW50cmVuYW1pZW50byA8LSAgZGZbcmVuZ2xvbmVzX2VudHJlbmFtaWVudG8sIF0KcHJ1ZWJhIDwtIGRmWy1yZW5nbG9uZXNfZW50cmVuYW1pZW50bywgXQoKcmYgPC0gcmFuZG9tRm9yZXN0KFByaWNlIH4gUm9vbXMgKyBEaXN0YW5jZSArIEJlZHJvb20yICsgQmF0aHJvb20gKyBDYXIgKyBMYW5kc2l6ZSArIEJ1aWxkaW5nQXJlYSArIFByb3BlcnR5Y291bnQgKyBZZWFyQnVpbHQsIGRhdGE9ZW50cmVuYW1pZW50bywgbnRyZWU9NTAwLCBtdHJ5PTMsIGltcG9ydGFuY2U9VFJVRSkKCnJlc3VsdGFkb19lbnRyZW5hbWllbnRvIDwtIHByZWRpY3QocmYsIGVudHJlbmFtaWVudG8pCnJlc3VsdGFkb19wcnVlYmEgPC0gcHJlZGljdChyZiwgcHJ1ZWJhKQoKbWFlX3JmIDwtIG1hZShyZiwgcHJ1ZWJhKQoKcmVzdWx0YWRvcyA8LSB0aWJibGUoTW9kZWxvID0gYygiw4FyYm9sIGRlIERlY2lzacOzbiIsICJCb3NxdWUgQWxlYXRvcmlvIiksIE1BRSA9IGMobWFlX2FyYm9sLCBtYWVfcmYpKQpyZXN1bHRhZG9zCmBgYAoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IzhGQkM4RiI+IEVqZXJjaWNpbyAxLiBSZW5kaW1pZW50byBBdXRvbW90cml6IDwvc3Bhbj4KCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjojOEZCQzhGIj4gSW1wb3J0YXIgbGEgYmFzZSBkZSBkYXRvcyA8L3NwYW4+CmBgYHtyfQojIG1wZywgcmVuZGltaWVudG8gZGUgbG9zIGNhcnJvcyBjb21vIHZhcmlhYmxlIGRlIGludGVyZXMKZGYyIDwtIG10Y2FycwpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjojOEZCQzhGIj4gRW50ZW5kZXIgbGEgYmFzZSBkZSBkYXRvcyA8L3NwYW4+ClZhcmlhYmxlIG9iamV0aXZvOiAqKm1wZyoqIChyZW5kaW1pZW50bykuCgotICoqbXBnKio6IE1pbGxhcyBwb3IgZ2Fsw7NuIChyZW5kaW1pZW50byBkZWwgY29tYnVzdGlibGUpLiAgCi0gKipjeWwqKjogTsO6bWVybyBkZSBjaWxpbmRyb3MgZGVsIG1vdG9yLiAgCi0gKipkaXNwKio6IENpbGluZHJhZGEgZGVsIG1vdG9yIGVuIHB1bGdhZGFzIGPDumJpY2FzICh0YW1hw7FvIGRlbCBtb3RvcikuICAKLSAqKmhwKio6IFBvdGVuY2lhIGJydXRhIGRlbCBtb3RvciBlbiBjYWJhbGxvcyBkZSBmdWVyemEuICAKLSAqKmRyYXQqKjogUmVsYWNpw7NuIGRlbCBlamUgdHJhc2VybyAocmVhciBheGxlIHJhdGlvKS4gIAotICoqd3QqKjogUGVzbyBkZWwgYXV0b23Ds3ZpbCBlbiBtaWxlcyBkZSBsaWJyYXMgKDEgPSAxMDAwIGxicykuICAKLSAqKnFzZWMqKjogVGllbXBvIGVuIHNlZ3VuZG9zIGVuIHJlY29ycmVyIDEvNCBkZSBtaWxsYS4gIAotICoqdnMqKjogVGlwbyBkZSBtb3RvcjogIAogIC0gMCA9IG1vdG9yIGVuIFYgIAogIC0gMSA9IG1vdG9yIGVuIGzDrW5lYSAgCi0gKiphbSoqOiBUaXBvIGRlIHRyYW5zbWlzacOzbjogIAogIC0gMCA9IGF1dG9tw6F0aWNhICAKICAtIDEgPSBtYW51YWwgIAotICoqZ2VhcioqOiBOw7ptZXJvIGRlIG1hcmNoYXMgaGFjaWEgYWRlbGFudGUuICAKLSAqKmNhcmIqKjogTsO6bWVybyBkZSBjYXJidXJhZG9yZXMuICAKCmBgYHtyfQpzdW1tYXJ5KGRmMikKc3RyKGRmMikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IzhGQkM4RiI+IMOBcmJvbCBkZSBEZWNpc2nDs24gPC9zcGFuPgpgYGB7cn0KYXJib2wyIDwtIHJwYXJ0KG1wZyB+IGN5bCArIGRpc3AgKyBkaXNwICsgaHAgKyBkcmF0ICsgd3QgKyBxc2VjICsgZ2VhciArIGNhcmIsIGRhdGE9ZGYyKQpycGFydC5wbG90KAogIGFyYm9sMiwKICB0eXBlID0gMiwgICAgICAgICAgIyBzcGxpdHMgb24gYnJhbmNoZXMsIGxhYmVscyBvbiBsZWF2ZXMKICBleHRyYSA9IDEwMSwgICAgICAgIyBzaG93IGZpdHRlZCB2YWx1ZSwgI29icywgJW9icwopCgpwcmVkaWN0KGFyYm9sMiwgaGVhZChkZjIpKQpoZWFkKGRmMiRtcGcpCgojIE1BRTogRXJyb3IgY3VhZHJhZG8gcHJvbWVkaW8gKFZlbnRhamE6IE1pc21hcyB1bmlkYWRlcykKCnBydWViYV9hcmJvbDIgPC0gaGVhZChkZjIpCgptYWVfYXJib2wyIDwtIG1hZShhcmJvbDIsIHBydWViYV9hcmJvbDIpCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiM4RkJDOEYiPiBCb3NxdWUgQWxlYXRvcmlvIDwvc3Bhbj4KYGBge3J9CnNldC5zZWVkKDEyMykKcmVuZ2xvbmVzX2VudHJlbmFtaWVudG8yIDwtICBjcmVhdGVEYXRhUGFydGl0aW9uKGRmMiRtcGcsIHA9MC44LCBsaXN0PUZBTFNFKQoKZW50cmVuYW1pZW50bzIgPC0gIGRmMltyZW5nbG9uZXNfZW50cmVuYW1pZW50bzIsIF0KcHJ1ZWJhMiA8LSBkZjJbLXJlbmdsb25lc19lbnRyZW5hbWllbnRvMiwgXQoKcmYyIDwtIHJhbmRvbUZvcmVzdChtcGcgfiBjeWwgKyBkaXNwICsgZGlzcCArIGhwICsgZHJhdCArIHd0ICsgcXNlYyArIGdlYXIgKyBjYXJiLCBkYXRhPWRmMiwgbnRyZWU9NTAwLCBtdHJ5PTMsIGltcG9ydGFuY2U9VFJVRSkKCnJlc3VsdGFkb19lbnRyZW5hbWllbnRvMiA8LSBwcmVkaWN0KHJmMiwgZW50cmVuYW1pZW50bzIpCnJlc3VsdGFkb19wcnVlYmEyIDwtIHByZWRpY3QocmYyLCBwcnVlYmEyKQoKbWFlX3JmMiA8LSBtYWUocmYyLCBwcnVlYmEyKQoKcmVzdWx0YWRvczIgPC0gdGliYmxlKE1vZGVsbyA9IGMoIsOBcmJvbCBkZSBEZWNpc2nDs24iLCAiQm9zcXVlIEFsZWF0b3JpbyIpLCBNQUUgPSBjKG1hZV9hcmJvbDIsIG1hZV9yZjIpKQpyZXN1bHRhZG9zMgpgYGA=