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.Melborne

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

Llamar libererías

library(tidyverse)
library(rpart.plot)
library(randomForest)
library(modelr)
library(caret)

Cargar la base de datos

df <- read.csv("C:\\Users\\erik-\\OneDrive\\Documentos\\Escuela\\Universidad\\7ºSemestre\\Modulo_2\\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 ...
df <- na.omit(df)

Árbol de Decisión

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

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
prueba_arbol <- head(df)

# MAE: Error cuadrado promedio (Ventaja: Mismas unidades)
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 + YearBuilt, data = df, 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  106740.

Conclusiones

En la comparativa de ambos modelos (Ábrol de Decisión y Bosque aleatorio), podemos identificar que el modelo de bosque aleatorio presenta un menor nivel de error que el árbol de decisión. Por lo tanto el mejor modelo para predecir el precio de las casas en Melbourne es el de Bosque Aleatorio.

Ejemplo 2. Automotriz

df2 <- mtcars

Entender la base de datos

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 ...
df <- na.omit(df2)

Árbol de Decisión

arbol2 <- rpart(mpg ~ + cyl + disp + hp + drat + wt + qsec + gear + carb , data = df2)
rpart.plot(arbol2, type = 2, extra = 101)

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
prueba_arbol2 <- head(df2)

# MAE: Error cuadrado promedio (Ventaja: Mismas unidades)
mae_arbol_2 <- mae(arbol2,prueba_arbol2)

Bosque Aleatorio

set.seed(123)
renglones_entrenamiento <- createDataPartition(df2$mpg, p = 0.8, list = FALSE)
entrenamiento <- df2[renglones_entrenamiento,]
prueba <- df2[-renglones_entrenamiento,]

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

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

mae_rf_2 <- mae(rf_2, prueba)

resultados <- tibble(Modelo = c("Árbol de Decisión", "Bosque Aleatorio"), MAE = c(mae_arbol_2, mae_rf_2))
resultados
## # A tibble: 2 × 2
##   Modelo              MAE
##   <chr>             <dbl>
## 1 Árbol de Decisión 2.18 
## 2 Bosque Aleatorio  0.951

Conclusiones

En la comparativa de ambos modelos , podemos identificar que el modelo de bosque aleatorio presenta un menor nivel de error que el árbol de decisión. Por lo tanto el mejor modelo para predecir el rendimiento de un auto es el de Bosque Aleatorio.

LS0tDQp0aXRsZTogIkJvc3F1ZXNfQWxlYXRvcmlvcyINCmF1dGhvcjogIkVyaWsgR29uemFsZXoiDQpkYXRlOiAiMjAyNS0wOC0yNyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRydWUgDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KICAgIHRoZW1lOiBqb3VybmFsDQotLS0NCg0KDQohW10oaHR0cHM6Ly9jb250ZW50LnI5Y2RuLm5ldC9yaW1nL2RpbWcvZTcvZTIvYTA5MmU5M2ItY2l0eS0xMzk5OC0xNjQxZWFiYThhMy5qcGc/d2lkdGg9MTIwMCZoZWlnaHQ9NjMwJnhoaW50PTEwMTYmeWhpbnQ9MTAyNCZjcm9wPXRydWUpDQoNCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6YnJvd247Ij5UZW9yw61hIDwvc3Bhbj4NCg0KRWwgKkJvc3F1ZSBBbGVhdG9yaW8qIGVzIHVuIGFsZ29yaXRtbyBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyBxdWUgY29tYmluYSBlbCByZXN1bHRhZG8gZGUgbcO6bHRpcGxlcyDDoXJib2xlcyBkZSBkZWNpc2nDs24gcGFyYSBsbGVnYXIgYSB1biByZXN1bHRhZG8gw7NwdGltby4NCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6YnJvd247Ij5FamVtcGxvIDEuTWVsYm9ybmUgPC9zcGFuPg0KRW4gZXN0YSBiYXNlIGRlIGRhdG9zIHRlbmVtb3MgbG9zIHByZWNpb3MgZGUgbcOhcyAxMywwMDAgY2FkYWQgZGUgbGEgY2l1ZGFkIGRlIE1lbGJvcm5lLg0KDQojIDxzcGFuIHN0eWxlID0gImNvbG9yOmJyb3duOyI+IExsYW1hciBsaWJlcmVyw61hcyAgPC9zcGFuPg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpsaWJyYXJ5KG1vZGVscikNCmxpYnJhcnkoY2FyZXQpDQpgYGANCg0KDQojIDxzcGFuIHN0eWxlID0gImNvbG9yOmJyb3duOyI+IENhcmdhciBsYSBiYXNlIGRlIGRhdG9zICA8L3NwYW4+DQpgYGB7cn0NCmRmIDwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXGVyaWstXFxPbmVEcml2ZVxcRG9jdW1lbnRvc1xcRXNjdWVsYVxcVW5pdmVyc2lkYWRcXDfCulNlbWVzdHJlXFxNb2R1bG9fMlxcbWVsYm91cm5lLmNzdiIpDQpgYGANCg0KDQojIDxzcGFuIHN0eWxlID0gImNvbG9yOmJyb3duOyI+IEVudGVuZGVyIGxhIGJhc2UgZGUgZGF0b3MgIDwvc3Bhbj4NCmBgYHtyfQ0Kc3VtbWFyeShkZikNCnN0cihkZikNCmRmIDwtIG5hLm9taXQoZGYpDQpgYGANCiMgPHNwYW4gc3R5bGUgPSAiY29sb3I6YnJvd247Ij4gw4FyYm9sIGRlIERlY2lzacOzbiA8L3NwYW4+DQpgYGB7cn0NCmFyYm9sIDwtIHJwYXJ0KFByaWNlIH4gUm9vbXMgKyBEaXN0YW5jZSArIEJlZHJvb20yICsgQmF0aHJvb20gKyBDYXIgKyBMYW5kc2l6ZSArIEJ1aWxkaW5nQXJlYSArIFllYXJCdWlsdCwgZGF0YSA9IGRmKQ0KcGxvdChhcmJvbCwgdW5pZm9ybSA9IFRSVUUpDQp0ZXh0KGFyYm9sLCBjZXggPSAuNikNCg0KcHJlZGljdChhcmJvbCwgaGVhZChkZikpDQpoZWFkKGRmJFByaWNlKQ0KDQpwcnVlYmFfYXJib2wgPC0gaGVhZChkZikNCg0KIyBNQUU6IEVycm9yIGN1YWRyYWRvIHByb21lZGlvIChWZW50YWphOiBNaXNtYXMgdW5pZGFkZXMpDQptYWVfYXJib2wgPC0gbWFlKGFyYm9sLHBydWViYV9hcmJvbCkNCmBgYA0KDQojIDxzcGFuIHN0eWxlID0gImNvbG9yOmJyb3duOyI+IEJvc3F1ZSBBbGVhdG9yaW8gPC9zcGFuPg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpyZW5nbG9uZXNfZW50cmVuYW1pZW50byA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRmJFByaWNlLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQplbnRyZW5hbWllbnRvIDwtIGRmW3Jlbmdsb25lc19lbnRyZW5hbWllbnRvLF0NCnBydWViYSA8LSBkZlstcmVuZ2xvbmVzX2VudHJlbmFtaWVudG8sXQ0KDQpyZiA8LSByYW5kb21Gb3Jlc3QoUHJpY2UgfiBSb29tcyArIERpc3RhbmNlICsgQmVkcm9vbTIgKyBCYXRocm9vbSArIENhciArIExhbmRzaXplICsgQnVpbGRpbmdBcmVhICsgWWVhckJ1aWx0LCBkYXRhID0gZGYsIG50cmVlID0gNTAwLCBtdHJ5ID0gMywgaW1wb3J0YW5jZSA9IFRSVUUpDQoNCnJlc3VsdGFkb19lbnRyZW5hbWllbnRvIDwtIHByZWRpY3QocmYsIGVudHJlbmFtaWVudG8pDQpyZXN1bHRhZG9fcHJ1ZWJhIDwtIHByZWRpY3QocmYsIHBydWViYSkNCg0KbWFlX3JmIDwtIG1hZShyZiwgcHJ1ZWJhKQ0KDQpyZXN1bHRhZG9zIDwtIHRpYmJsZShNb2RlbG8gPSBjKCLDgXJib2wgZGUgRGVjaXNpw7NuIiwgIkJvc3F1ZSBBbGVhdG9yaW8iKSwgTUFFID0gYyhtYWVfYXJib2wsIG1hZV9yZikpDQpyZXN1bHRhZG9zDQpgYGANCg0KDQojIDxzcGFuIHN0eWxlID0gImNvbG9yOmJyb3duOyI+IENvbmNsdXNpb25lcyA8L3NwYW4+DQpFbiBsYSBjb21wYXJhdGl2YSBkZSBhbWJvcyBtb2RlbG9zICjDgWJyb2wgZGUgRGVjaXNpw7NuIHkgQm9zcXVlIGFsZWF0b3JpbyksIHBvZGVtb3MgaWRlbnRpZmljYXIgcXVlIGVsIG1vZGVsbyBkZSBib3NxdWUgYWxlYXRvcmlvIHByZXNlbnRhIHVuIG1lbm9yIG5pdmVsIGRlIGVycm9yIHF1ZSBlbCDDoXJib2wgZGUgZGVjaXNpw7NuLiBQb3IgbG8gdGFudG8gZWwgbWVqb3IgbW9kZWxvIHBhcmEgcHJlZGVjaXIgZWwgcHJlY2lvIGRlIGxhcyBjYXNhcyBlbiBNZWxib3VybmUgZXMgZWwgZGUgKipCb3NxdWUgQWxlYXRvcmlvKiouDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmJyb3duOyI+IEVqZW1wbG8gMi4gQXV0b21vdHJpeiA8L3NwYW4+DQpgYGB7cn0NCmRmMiA8LSBtdGNhcnMNCmBgYA0KDQoNCiMgPHNwYW4gc3R5bGUgPSAiY29sb3I6YnJvd247Ij4gRW50ZW5kZXIgbGEgYmFzZSBkZSBkYXRvcyAgPC9zcGFuPg0KYGBge3J9DQpzdW1tYXJ5KGRmMikNCnN0cihkZjIpDQpkZiA8LSBuYS5vbWl0KGRmMikNCmBgYA0KDQoNCg0KDQojIDxzcGFuIHN0eWxlID0gImNvbG9yOmJyb3duOyI+IMOBcmJvbCBkZSBEZWNpc2nDs24gPC9zcGFuPg0KYGBge3J9DQphcmJvbDIgPC0gcnBhcnQobXBnIH4gKyBjeWwgKyBkaXNwICsgaHAgKyBkcmF0ICsgd3QgKyBxc2VjICsgZ2VhciArIGNhcmIgLCBkYXRhID0gZGYyKQ0KcnBhcnQucGxvdChhcmJvbDIsIHR5cGUgPSAyLCBleHRyYSA9IDEwMSkNCg0KcHJlZGljdChhcmJvbDIsIGhlYWQoZGYyKSkNCmhlYWQoZGYyJG1wZykNCg0KcHJ1ZWJhX2FyYm9sMiA8LSBoZWFkKGRmMikNCg0KIyBNQUU6IEVycm9yIGN1YWRyYWRvIHByb21lZGlvIChWZW50YWphOiBNaXNtYXMgdW5pZGFkZXMpDQptYWVfYXJib2xfMiA8LSBtYWUoYXJib2wyLHBydWViYV9hcmJvbDIpDQpgYGANCg0KIyA8c3BhbiBzdHlsZSA9ICJjb2xvcjpicm93bjsiPiBCb3NxdWUgQWxlYXRvcmlvIDwvc3Bhbj4NCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KcmVuZ2xvbmVzX2VudHJlbmFtaWVudG8gPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkZjIkbXBnLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQplbnRyZW5hbWllbnRvIDwtIGRmMltyZW5nbG9uZXNfZW50cmVuYW1pZW50byxdDQpwcnVlYmEgPC0gZGYyWy1yZW5nbG9uZXNfZW50cmVuYW1pZW50byxdDQoNCnJmXzIgPC0gcmFuZG9tRm9yZXN0KG1wZyB+ICsgY3lsICsgZGlzcCArIGhwICsgZHJhdCArIHd0ICsgcXNlYyArIGdlYXIgKyBjYXJiICwgZGF0YSA9IGRmLCBudHJlZSA9IDUwMCwgbXRyeSA9IDMsIGltcG9ydGFuY2UgPSBUUlVFKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50byA8LSBwcmVkaWN0KHJmXzIsIGVudHJlbmFtaWVudG8pDQpyZXN1bHRhZG9fcHJ1ZWJhIDwtIHByZWRpY3QocmZfMiwgcHJ1ZWJhKQ0KDQptYWVfcmZfMiA8LSBtYWUocmZfMiwgcHJ1ZWJhKQ0KDQpyZXN1bHRhZG9zIDwtIHRpYmJsZShNb2RlbG8gPSBjKCLDgXJib2wgZGUgRGVjaXNpw7NuIiwgIkJvc3F1ZSBBbGVhdG9yaW8iKSwgTUFFID0gYyhtYWVfYXJib2xfMiwgbWFlX3JmXzIpKQ0KcmVzdWx0YWRvcw0KYGBgDQoNCg0KIyA8c3BhbiBzdHlsZSA9ICJjb2xvcjpicm93bjsiPiBDb25jbHVzaW9uZXMgPC9zcGFuPg0KRW4gbGEgY29tcGFyYXRpdmEgZGUgYW1ib3MgbW9kZWxvcyAsIHBvZGVtb3MgaWRlbnRpZmljYXIgcXVlIGVsIG1vZGVsbyBkZSBib3NxdWUgYWxlYXRvcmlvIHByZXNlbnRhIHVuIG1lbm9yIG5pdmVsIGRlIGVycm9yIHF1ZSBlbCDDoXJib2wgZGUgZGVjaXNpw7NuLiBQb3IgbG8gdGFudG8gZWwgbWVqb3IgbW9kZWxvIHBhcmEgcHJlZGVjaXIgZWwgcmVuZGltaWVudG8gZGUgdW4gYXV0byBlcyBlbCBkZSAqKkJvc3F1ZSBBbGVhdG9yaW8qKi4=