Teoría

El paquete caret (Clasification And REgression Training) es un paquete integral con una amplia variedad de algoritmos para el aprendizaje automático.

Instalar paquetes y llamar librerías

#install.packages("ggplot2") #Graficas con mejor diseño
library(ggplot2)
#install.packages("lattice") #Crear gráficos 
library(lattice)
#install.packages("datasets") #Usar la base de datos "Iris"
#install.packages("caret") #Algoritmos de aprendizaje automático
library(caret)
library(datasets)
#install.packages("DataExplorer")
library(DataExplorer)
#install.packages("mlbench")
library(mlbench)

Crear base de datos

data(BreastCancer)
df <- data.frame(BreastCancer)

# Eliminar columna "ID"
df <- df[, -1]

# Eliminar filas con NA
df <- na.omit(df)

df$Cl.thickness <- as.numeric(df$Cl.thickness)
df$Cell.size <- as.numeric(df$Cell.size)
df$Cell.shape <- as.numeric(df$Cell.shape)
df$Marg.adhesion <- as.numeric(df$Marg.adhesion)
df$Epith.c.size <- as.numeric(df$Epith.c.size)
df$Bare.nuclei <- as.numeric(df$Bare.nuclei)
df$Bl.cromatin <- as.numeric(df$Bl.cromatin)
df$Normal.nucleoli <- as.numeric(df$Normal.nucleoli)
df$Mitoses <- as.numeric(df$Mitoses)
df$Class <- as.factor(df$Class)

Análisis exploratorio

summary(df)
##   Cl.thickness      Cell.size        Cell.shape     Marg.adhesion  
##  Min.   : 1.000   Min.   : 1.000   Min.   : 1.000   Min.   : 1.00  
##  1st Qu.: 2.000   1st Qu.: 1.000   1st Qu.: 1.000   1st Qu.: 1.00  
##  Median : 4.000   Median : 1.000   Median : 1.000   Median : 1.00  
##  Mean   : 4.442   Mean   : 3.151   Mean   : 3.215   Mean   : 2.83  
##  3rd Qu.: 6.000   3rd Qu.: 5.000   3rd Qu.: 5.000   3rd Qu.: 4.00  
##  Max.   :10.000   Max.   :10.000   Max.   :10.000   Max.   :10.00  
##   Epith.c.size     Bare.nuclei      Bl.cromatin     Normal.nucleoli
##  Min.   : 1.000   Min.   : 1.000   Min.   : 1.000   Min.   : 1.00  
##  1st Qu.: 2.000   1st Qu.: 1.000   1st Qu.: 2.000   1st Qu.: 1.00  
##  Median : 2.000   Median : 1.000   Median : 3.000   Median : 1.00  
##  Mean   : 3.234   Mean   : 3.545   Mean   : 3.445   Mean   : 2.87  
##  3rd Qu.: 4.000   3rd Qu.: 6.000   3rd Qu.: 5.000   3rd Qu.: 4.00  
##  Max.   :10.000   Max.   :10.000   Max.   :10.000   Max.   :10.00  
##     Mitoses            Class    
##  Min.   :1.000   benign   :444  
##  1st Qu.:1.000   malignant:239  
##  Median :1.000                  
##  Mean   :1.583                  
##  3rd Qu.:1.000                  
##  Max.   :9.000
str(df)
## 'data.frame':    683 obs. of  10 variables:
##  $ Cl.thickness   : num  5 5 3 6 4 8 1 2 2 4 ...
##  $ Cell.size      : num  1 4 1 8 1 10 1 1 1 2 ...
##  $ Cell.shape     : num  1 4 1 8 1 10 1 2 1 1 ...
##  $ Marg.adhesion  : num  1 5 1 1 3 8 1 1 1 1 ...
##  $ Epith.c.size   : num  2 7 2 3 2 7 2 2 2 2 ...
##  $ Bare.nuclei    : num  1 10 2 4 1 10 10 1 1 1 ...
##  $ Bl.cromatin    : num  3 3 3 3 3 9 3 3 1 2 ...
##  $ Normal.nucleoli: num  1 2 1 7 1 7 1 1 1 1 ...
##  $ Mitoses        : num  1 1 1 1 1 1 1 1 5 1 ...
##  $ Class          : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 2 1 1 1 1 ...
##  - attr(*, "na.action")= 'omit' Named int [1:16] 24 41 140 146 159 165 236 250 276 293 ...
##   ..- attr(*, "names")= chr [1:16] "24" "41" "140" "146" ...
plot_missing(df)

plot_histogram(df)

plot_correlation(df)

plot_boxplot(df, by ="Class")

** Nota: La variable que queremos predecir debe tener formato de FACTOR.**

Partir datos 80-20

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

Distintos tipos de Métodos para Modelar

Los métodos más utilizados para modelar aprendizaje automático son:

  • SVM: Support Vector Machine o Máquina de Vectores de Soporte. Hay varios subtipos: Lineal (svmLinear), Radial (svmRadial), Polinómico (svmPoly), etc.
  • Árbol de Decisión: rpart
  • Redes Neuronales: nnet
  • Random Foresto Bosques Aleatorios: rf

1. Modelo con el método svmLinear

modelo1 <- train(Class ~ ., data=entrenamiento,
                method = "svmLinear",  # Cambiar
                preProcess= c("scale","center"),
                trControl = trainControl(method="cv", number=10),
                tuneGrid = data.frame(C=1) #Cuando es svmLinear
                )

resultado_entrenamiento1 <- predict(modelo1, entrenamiento)
resultado_prueba1 <- predict(modelo1, prueba)

# Matriz de Confusión
mcre1 <- confusionMatrix(resultado_entrenamiento1, entrenamiento$Class) # matriz de confusión del resultado del entrenamiento
mcre1
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign       347         7
##   malignant      9       185
##                                          
##                Accuracy : 0.9708         
##                  95% CI : (0.953, 0.9832)
##     No Information Rate : 0.6496         
##     P-Value [Acc > NIR] : <2e-16         
##                                          
##                   Kappa : 0.936          
##                                          
##  Mcnemar's Test P-Value : 0.8026         
##                                          
##             Sensitivity : 0.9747         
##             Specificity : 0.9635         
##          Pos Pred Value : 0.9802         
##          Neg Pred Value : 0.9536         
##              Prevalence : 0.6496         
##          Detection Rate : 0.6332         
##    Detection Prevalence : 0.6460         
##       Balanced Accuracy : 0.9691         
##                                          
##        'Positive' Class : benign         
## 
mcrp1 <- confusionMatrix(resultado_prueba1, prueba$Class) # matriz de confusión del resultado de la prueba 
mcrp1
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign        87         2
##   malignant      1        45
##                                           
##                Accuracy : 0.9778          
##                  95% CI : (0.9364, 0.9954)
##     No Information Rate : 0.6519          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.9508          
##                                           
##  Mcnemar's Test P-Value : 1               
##                                           
##             Sensitivity : 0.9886          
##             Specificity : 0.9574          
##          Pos Pred Value : 0.9775          
##          Neg Pred Value : 0.9783          
##              Prevalence : 0.6519          
##          Detection Rate : 0.6444          
##    Detection Prevalence : 0.6593          
##       Balanced Accuracy : 0.9730          
##                                           
##        'Positive' Class : benign          
## 

2. Modelo con el método svmRadial

modelo2 <- train(Class ~ ., data=entrenamiento,
                method = "svmRadial",  # Cambiar
                preProcess= c("scale","center"),
                trControl = trainControl(method="cv", number=10),
                tuneGrid = data.frame(sigma=1,C=1) # Cambiar
                )

resultado_entrenamiento2 <- predict(modelo2, entrenamiento)
resultado_prueba2 <- predict(modelo2, prueba)

# Matriz de Confusión
mcre2 <- confusionMatrix(resultado_entrenamiento2, entrenamiento$Class) # matriz de confusión del resultado del entrenamiento
mcre2
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign       354         0
##   malignant      2       192
##                                           
##                Accuracy : 0.9964          
##                  95% CI : (0.9869, 0.9996)
##     No Information Rate : 0.6496          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.992           
##                                           
##  Mcnemar's Test P-Value : 0.4795          
##                                           
##             Sensitivity : 0.9944          
##             Specificity : 1.0000          
##          Pos Pred Value : 1.0000          
##          Neg Pred Value : 0.9897          
##              Prevalence : 0.6496          
##          Detection Rate : 0.6460          
##    Detection Prevalence : 0.6460          
##       Balanced Accuracy : 0.9972          
##                                           
##        'Positive' Class : benign          
## 
mcrp2 <- confusionMatrix(resultado_prueba2, prueba$Class) # matriz de confusión del resultado de la prueba 
mcrp2
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign        82         0
##   malignant      6        47
##                                           
##                Accuracy : 0.9556          
##                  95% CI : (0.9058, 0.9835)
##     No Information Rate : 0.6519          
##     P-Value [Acc > NIR] : < 2e-16         
##                                           
##                   Kappa : 0.9049          
##                                           
##  Mcnemar's Test P-Value : 0.04123         
##                                           
##             Sensitivity : 0.9318          
##             Specificity : 1.0000          
##          Pos Pred Value : 1.0000          
##          Neg Pred Value : 0.8868          
##              Prevalence : 0.6519          
##          Detection Rate : 0.6074          
##    Detection Prevalence : 0.6074          
##       Balanced Accuracy : 0.9659          
##                                           
##        'Positive' Class : benign          
## 

3. Modelo con el método svmPoly

modelo3 <- train(Class ~ ., data=entrenamiento,
                method = "svmPoly",  # Cambiar
                preProcess= c("scale","center"),
                trControl = trainControl(method="cv", number=10),
                tuneGrid = data.frame(degree=1,scale=1,C=1) # Cambiar
                )

resultado_entrenamiento3 <- predict(modelo3, entrenamiento)
resultado_prueba3 <- predict(modelo3, prueba)

# Matriz de Confusión
mcre3 <- confusionMatrix(resultado_entrenamiento3, entrenamiento$Class) # matriz de confusión del resultado del entrenamiento
mcre3
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign       347         7
##   malignant      9       185
##                                          
##                Accuracy : 0.9708         
##                  95% CI : (0.953, 0.9832)
##     No Information Rate : 0.6496         
##     P-Value [Acc > NIR] : <2e-16         
##                                          
##                   Kappa : 0.936          
##                                          
##  Mcnemar's Test P-Value : 0.8026         
##                                          
##             Sensitivity : 0.9747         
##             Specificity : 0.9635         
##          Pos Pred Value : 0.9802         
##          Neg Pred Value : 0.9536         
##              Prevalence : 0.6496         
##          Detection Rate : 0.6332         
##    Detection Prevalence : 0.6460         
##       Balanced Accuracy : 0.9691         
##                                          
##        'Positive' Class : benign         
## 
mcrp3 <- confusionMatrix(resultado_prueba3, prueba$Class) # matriz de confusión del resultado de la prueba 
mcrp3
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign        87         2
##   malignant      1        45
##                                           
##                Accuracy : 0.9778          
##                  95% CI : (0.9364, 0.9954)
##     No Information Rate : 0.6519          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.9508          
##                                           
##  Mcnemar's Test P-Value : 1               
##                                           
##             Sensitivity : 0.9886          
##             Specificity : 0.9574          
##          Pos Pred Value : 0.9775          
##          Neg Pred Value : 0.9783          
##              Prevalence : 0.6519          
##          Detection Rate : 0.6444          
##    Detection Prevalence : 0.6593          
##       Balanced Accuracy : 0.9730          
##                                           
##        'Positive' Class : benign          
## 

4. Modelo con el método rpart

modelo4 <- train(Class ~ ., data=entrenamiento,
                method = "rpart",  # Cambiar
                preProcess= c("scale","center"),
                trControl = trainControl(method="cv", number=10),
                tuneLength = 10 # Cambiar
                )

resultado_entrenamiento4 <- predict(modelo4, entrenamiento)
resultado_prueba4 <- predict(modelo4, prueba)

# Matriz de Confusión
mcre4 <- confusionMatrix(resultado_entrenamiento4, entrenamiento$Class) # matriz de confusión del resultado del entrenamiento
mcre4
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign       345         9
##   malignant     11       183
##                                           
##                Accuracy : 0.9635          
##                  95% CI : (0.9442, 0.9776)
##     No Information Rate : 0.6496          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.92            
##                                           
##  Mcnemar's Test P-Value : 0.8231          
##                                           
##             Sensitivity : 0.9691          
##             Specificity : 0.9531          
##          Pos Pred Value : 0.9746          
##          Neg Pred Value : 0.9433          
##              Prevalence : 0.6496          
##          Detection Rate : 0.6296          
##    Detection Prevalence : 0.6460          
##       Balanced Accuracy : 0.9611          
##                                           
##        'Positive' Class : benign          
## 
mcrp4 <- confusionMatrix(resultado_prueba4, prueba$Class) # matriz de confusión del resultado de la prueba 
mcrp4
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign        87         5
##   malignant      1        42
##                                           
##                Accuracy : 0.9556          
##                  95% CI : (0.9058, 0.9835)
##     No Information Rate : 0.6519          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.9001          
##                                           
##  Mcnemar's Test P-Value : 0.2207          
##                                           
##             Sensitivity : 0.9886          
##             Specificity : 0.8936          
##          Pos Pred Value : 0.9457          
##          Neg Pred Value : 0.9767          
##              Prevalence : 0.6519          
##          Detection Rate : 0.6444          
##    Detection Prevalence : 0.6815          
##       Balanced Accuracy : 0.9411          
##                                           
##        'Positive' Class : benign          
## 

5. Modelo con el método nnet

modelo5 <- train(Class ~ ., data=entrenamiento,
                method = "nnet",  # Cambiar
                preProcess= c("scale","center"),
                trControl = trainControl(method="cv", number=10),
                trace = FALSE
                        # Cambiar
                )

resultado_entrenamiento5 <- predict(modelo5, entrenamiento)
resultado_prueba5 <- predict(modelo5, prueba)

# Matriz de Confusión
mcre5 <- confusionMatrix(resultado_entrenamiento5, entrenamiento$Class) # matriz de confusión del resultado del entrenamiento
mcre5
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign       351         0
##   malignant      5       192
##                                          
##                Accuracy : 0.9909         
##                  95% CI : (0.9788, 0.997)
##     No Information Rate : 0.6496         
##     P-Value [Acc > NIR] : < 2e-16        
##                                          
##                   Kappa : 0.9801         
##                                          
##  Mcnemar's Test P-Value : 0.07364        
##                                          
##             Sensitivity : 0.9860         
##             Specificity : 1.0000         
##          Pos Pred Value : 1.0000         
##          Neg Pred Value : 0.9746         
##              Prevalence : 0.6496         
##          Detection Rate : 0.6405         
##    Detection Prevalence : 0.6405         
##       Balanced Accuracy : 0.9930         
##                                          
##        'Positive' Class : benign         
## 
mcrp5 <- confusionMatrix(resultado_prueba5, prueba$Class) # matriz de confusión del resultado de la prueba 
mcrp5
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign        86         4
##   malignant      2        43
##                                           
##                Accuracy : 0.9556          
##                  95% CI : (0.9058, 0.9835)
##     No Information Rate : 0.6519          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.9011          
##                                           
##  Mcnemar's Test P-Value : 0.6831          
##                                           
##             Sensitivity : 0.9773          
##             Specificity : 0.9149          
##          Pos Pred Value : 0.9556          
##          Neg Pred Value : 0.9556          
##              Prevalence : 0.6519          
##          Detection Rate : 0.6370          
##    Detection Prevalence : 0.6667          
##       Balanced Accuracy : 0.9461          
##                                           
##        'Positive' Class : benign          
## 

6. Modelo con el método rf

modelo6 <- train(Class ~ ., data=entrenamiento,
                method = "rf",  # Cambiar
                preProcess= c("scale","center"),
                trControl = trainControl(method="cv", number=10),
                tuneGrid = expand.grid(mtry = c(2,4,6))  # Cambiar
                )

resultado_entrenamiento6 <- predict(modelo6, entrenamiento)
resultado_prueba6 <- predict(modelo6, prueba)

# Matriz de Confusión
mcre6 <- confusionMatrix(resultado_entrenamiento6, entrenamiento$Class) # matriz de confusión del resultado del entrenamiento
mcre6
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign       356         1
##   malignant      0       191
##                                      
##                Accuracy : 0.9982     
##                  95% CI : (0.9899, 1)
##     No Information Rate : 0.6496     
##     P-Value [Acc > NIR] : <2e-16     
##                                      
##                   Kappa : 0.996      
##                                      
##  Mcnemar's Test P-Value : 1          
##                                      
##             Sensitivity : 1.0000     
##             Specificity : 0.9948     
##          Pos Pred Value : 0.9972     
##          Neg Pred Value : 1.0000     
##              Prevalence : 0.6496     
##          Detection Rate : 0.6496     
##    Detection Prevalence : 0.6515     
##       Balanced Accuracy : 0.9974     
##                                      
##        'Positive' Class : benign     
## 
mcrp6 <- confusionMatrix(resultado_prueba6, prueba$Class) # matriz de confusión del resultado de la prueba 
mcrp6
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  benign malignant
##   benign        85         1
##   malignant      3        46
##                                           
##                Accuracy : 0.9704          
##                  95% CI : (0.9259, 0.9919)
##     No Information Rate : 0.6519          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.9354          
##                                           
##  Mcnemar's Test P-Value : 0.6171          
##                                           
##             Sensitivity : 0.9659          
##             Specificity : 0.9787          
##          Pos Pred Value : 0.9884          
##          Neg Pred Value : 0.9388          
##              Prevalence : 0.6519          
##          Detection Rate : 0.6296          
##    Detection Prevalence : 0.6370          
##       Balanced Accuracy : 0.9723          
##                                           
##        'Positive' Class : benign          
## 

Resumen de Resultados

resultados <- data.frame(
  "svmLinear" = c(mcre1$overall["Accuracy"], mcrp1$overall["Accuracy"]),
  "svmRadial" = c(mcre2$overall["Accuracy"], mcrp2$overall["Accuracy"]),
  "svmPoly" = c(mcre3$overall["Accuracy"], mcrp3$overall["Accuracy"]),
  "rpart" = c(mcre4$overall["Accuracy"], mcrp4$overall["Accuracy"]),
  "nnet" = c(mcre5$overall["Accuracy"], mcrp5$overall["Accuracy"]),
  "rf" = c(mcre6$overall["Accuracy"], mcrp6$overall["Accuracy"])
)
rownames(resultados) <- c("Precisión de entrenamiento", "Precisión de prueba")
resultados
##                            svmLinear svmRadial   svmPoly     rpart      nnet
## Precisión de entrenamiento 0.9708029 0.9963504 0.9708029 0.9635036 0.9908759
## Precisión de prueba        0.9777778 0.9555556 0.9777778 0.9555556 0.9555556
##                                   rf
## Precisión de entrenamiento 0.9981752
## Precisión de prueba        0.9703704

Conclusiones

Los modelos con los métodos de svmRadial y nnet presentan sobreajuste, ya que tienen una alta precisión en entrenamiento, pero baja en prueba.

Acorde al resumen de resultados, el mejor modelo es el de Máquina de Vectores de Soporte Lineal porque es el más sencillo de interpretar y el de mejor precisión tanto en entrenamiento como en prueba.

LS0tDQp0aXRsZTogIkFwcmVuZGl6YWplIEF1dG9tw6F0aWNvOiBCcmVhc3QgQ2FuY2VyIg0KYXV0aG9yOiAiTmF5ZWxpIFBlw7FhIE1hcnTDrW5leiAtIEEwMTM2ODUxNiINCmRhdGU6ICIyMDI0LTAyLTI5Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUgDQotLS0NCg0KIVtdKEM6XFxVc2Vyc1xcbmF5ZWxcXERvd25sb2Fkc1xcY20uanBnKQ0KDQojIFRlb3LDrWENCkVsIHBhcXVldGUgKmNhcmV0IChDbGFzaWZpY2F0aW9uIEFuZCBSRWdyZXNzaW9uIFRyYWluaW5nKSogZXMgdW4gcGFxdWV0ZSBpbnRlZ3JhbCBjb24gdW5hIGFtcGxpYSB2YXJpZWRhZCBkZSBhbGdvcml0bW9zIHBhcmEgZWwgYXByZW5kaXphamUgYXV0b23DoXRpY28uIA0KDQojIEluc3RhbGFyIHBhcXVldGVzIHkgbGxhbWFyIGxpYnJlcsOtYXMNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQojaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpICNHcmFmaWNhcyBjb24gbWVqb3IgZGlzZcOxbw0KbGlicmFyeShnZ3Bsb3QyKQ0KI2luc3RhbGwucGFja2FnZXMoImxhdHRpY2UiKSAjQ3JlYXIgZ3LDoWZpY29zIA0KbGlicmFyeShsYXR0aWNlKQ0KI2luc3RhbGwucGFja2FnZXMoImRhdGFzZXRzIikgI1VzYXIgbGEgYmFzZSBkZSBkYXRvcyAiSXJpcyINCiNpbnN0YWxsLnBhY2thZ2VzKCJjYXJldCIpICNBbGdvcml0bW9zIGRlIGFwcmVuZGl6YWplIGF1dG9tw6F0aWNvDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShkYXRhc2V0cykNCiNpbnN0YWxsLnBhY2thZ2VzKCJEYXRhRXhwbG9yZXIiKQ0KbGlicmFyeShEYXRhRXhwbG9yZXIpDQojaW5zdGFsbC5wYWNrYWdlcygibWxiZW5jaCIpDQpsaWJyYXJ5KG1sYmVuY2gpDQpgYGANCg0KIyBDcmVhciBiYXNlIGRlIGRhdG9zDQpgYGB7cn0NCmRhdGEoQnJlYXN0Q2FuY2VyKQ0KZGYgPC0gZGF0YS5mcmFtZShCcmVhc3RDYW5jZXIpDQoNCiMgRWxpbWluYXIgY29sdW1uYSAiSUQiDQpkZiA8LSBkZlssIC0xXQ0KDQojIEVsaW1pbmFyIGZpbGFzIGNvbiBOQQ0KZGYgPC0gbmEub21pdChkZikNCg0KZGYkQ2wudGhpY2tuZXNzIDwtIGFzLm51bWVyaWMoZGYkQ2wudGhpY2tuZXNzKQ0KZGYkQ2VsbC5zaXplIDwtIGFzLm51bWVyaWMoZGYkQ2VsbC5zaXplKQ0KZGYkQ2VsbC5zaGFwZSA8LSBhcy5udW1lcmljKGRmJENlbGwuc2hhcGUpDQpkZiRNYXJnLmFkaGVzaW9uIDwtIGFzLm51bWVyaWMoZGYkTWFyZy5hZGhlc2lvbikNCmRmJEVwaXRoLmMuc2l6ZSA8LSBhcy5udW1lcmljKGRmJEVwaXRoLmMuc2l6ZSkNCmRmJEJhcmUubnVjbGVpIDwtIGFzLm51bWVyaWMoZGYkQmFyZS5udWNsZWkpDQpkZiRCbC5jcm9tYXRpbiA8LSBhcy5udW1lcmljKGRmJEJsLmNyb21hdGluKQ0KZGYkTm9ybWFsLm51Y2xlb2xpIDwtIGFzLm51bWVyaWMoZGYkTm9ybWFsLm51Y2xlb2xpKQ0KZGYkTWl0b3NlcyA8LSBhcy5udW1lcmljKGRmJE1pdG9zZXMpDQpkZiRDbGFzcyA8LSBhcy5mYWN0b3IoZGYkQ2xhc3MpDQpgYGANCg0KIyBBbsOhbGlzaXMgZXhwbG9yYXRvcmlvDQpgYGB7cn0NCnN1bW1hcnkoZGYpDQpzdHIoZGYpDQpwbG90X21pc3NpbmcoZGYpDQpwbG90X2hpc3RvZ3JhbShkZikNCnBsb3RfY29ycmVsYXRpb24oZGYpDQpwbG90X2JveHBsb3QoZGYsIGJ5ID0iQ2xhc3MiKQ0KYGBgDQoNCioqIE5vdGE6IExhIHZhcmlhYmxlIHF1ZSBxdWVyZW1vcyBwcmVkZWNpciBkZWJlIHRlbmVyIGZvcm1hdG8gZGUgRkFDVE9SLioqDQoNCiMgUGFydGlyIGRhdG9zIDgwLTIwDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCnJlbmdsb25lc19lbnRyZW5hbWllbnRvIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGYkQ2xhc3MsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkNCmVudHJlbmFtaWVudG8gPC0gZGZbcmVuZ2xvbmVzX2VudHJlbmFtaWVudG8sIF0NCnBydWViYSA8LSBkZlstcmVuZ2xvbmVzX2VudHJlbmFtaWVudG8sIF0NCmBgYA0KDQojIERpc3RpbnRvcyB0aXBvcyBkZSBNw6l0b2RvcyBwYXJhIE1vZGVsYXIgIA0KTG9zIG3DqXRvZG9zIG3DoXMgdXRpbGl6YWRvcyBwYXJhIG1vZGVsYXIgYXByZW5kaXphamUgYXV0b23DoXRpY28gc29uOiAgDQoNCiAqICoqU1ZNKio6ICpTdXBwb3J0IFZlY3RvciBNYWNoaW5lKiBvIE3DoXF1aW5hIGRlIFZlY3RvcmVzIGRlIFNvcG9ydGUuIEhheSB2YXJpb3Mgc3VidGlwb3M6IExpbmVhbCAoc3ZtTGluZWFyKSwgUmFkaWFsIChzdm1SYWRpYWwpLCBQb2xpbsOzbWljbyAoc3ZtUG9seSksIGV0Yy4gIA0KICogKirDgXJib2wgZGUgRGVjaXNpw7NuKio6IHJwYXJ0ICANCiAqICoqUmVkZXMgTmV1cm9uYWxlcyoqOiBubmV0ICANCiAqICoqUmFuZG9tIEZvcmVzdCoqbyBCb3NxdWVzIEFsZWF0b3Jpb3M6IHJmICANCg0KIyAxLiBNb2RlbG8gY29uIGVsIG3DqXRvZG8gc3ZtTGluZWFyDQpgYGB7cn0NCm1vZGVsbzEgPC0gdHJhaW4oQ2xhc3MgfiAuLCBkYXRhPWVudHJlbmFtaWVudG8sDQogICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bUxpbmVhciIsICAjIENhbWJpYXINCiAgICAgICAgICAgICAgICBwcmVQcm9jZXNzPSBjKCJzY2FsZSIsImNlbnRlciIpLA0KICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTEwKSwNCiAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoQz0xKSAjQ3VhbmRvIGVzIHN2bUxpbmVhcg0KICAgICAgICAgICAgICAgICkNCg0KcmVzdWx0YWRvX2VudHJlbmFtaWVudG8xIDwtIHByZWRpY3QobW9kZWxvMSwgZW50cmVuYW1pZW50bykNCnJlc3VsdGFkb19wcnVlYmExIDwtIHByZWRpY3QobW9kZWxvMSwgcHJ1ZWJhKQ0KDQojIE1hdHJpeiBkZSBDb25mdXNpw7NuDQptY3JlMSA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX2VudHJlbmFtaWVudG8xLCBlbnRyZW5hbWllbnRvJENsYXNzKSAjIG1hdHJpeiBkZSBjb25mdXNpw7NuIGRlbCByZXN1bHRhZG8gZGVsIGVudHJlbmFtaWVudG8NCm1jcmUxDQptY3JwMSA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYTEsIHBydWViYSRDbGFzcykgIyBtYXRyaXogZGUgY29uZnVzacOzbiBkZWwgcmVzdWx0YWRvIGRlIGxhIHBydWViYSANCm1jcnAxDQpgYGANCg0KIyAyLiBNb2RlbG8gY29uIGVsIG3DqXRvZG8gc3ZtUmFkaWFsDQpgYGB7cn0NCm1vZGVsbzIgPC0gdHJhaW4oQ2xhc3MgfiAuLCBkYXRhPWVudHJlbmFtaWVudG8sDQogICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVJhZGlhbCIsICAjIENhbWJpYXINCiAgICAgICAgICAgICAgICBwcmVQcm9jZXNzPSBjKCJzY2FsZSIsImNlbnRlciIpLA0KICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTEwKSwNCiAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoc2lnbWE9MSxDPTEpICMgQ2FtYmlhcg0KICAgICAgICAgICAgICAgICkNCg0KcmVzdWx0YWRvX2VudHJlbmFtaWVudG8yIDwtIHByZWRpY3QobW9kZWxvMiwgZW50cmVuYW1pZW50bykNCnJlc3VsdGFkb19wcnVlYmEyIDwtIHByZWRpY3QobW9kZWxvMiwgcHJ1ZWJhKQ0KDQojIE1hdHJpeiBkZSBDb25mdXNpw7NuDQptY3JlMiA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX2VudHJlbmFtaWVudG8yLCBlbnRyZW5hbWllbnRvJENsYXNzKSAjIG1hdHJpeiBkZSBjb25mdXNpw7NuIGRlbCByZXN1bHRhZG8gZGVsIGVudHJlbmFtaWVudG8NCm1jcmUyDQptY3JwMiA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYTIsIHBydWViYSRDbGFzcykgIyBtYXRyaXogZGUgY29uZnVzacOzbiBkZWwgcmVzdWx0YWRvIGRlIGxhIHBydWViYSANCm1jcnAyDQpgYGANCg0KIyAzLiBNb2RlbG8gY29uIGVsIG3DqXRvZG8gc3ZtUG9seQ0KYGBge3J9DQptb2RlbG8zIDwtIHRyYWluKENsYXNzIH4gLiwgZGF0YT1lbnRyZW5hbWllbnRvLA0KICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJzdm1Qb2x5IiwgICMgQ2FtYmlhcg0KICAgICAgICAgICAgICAgIHByZVByb2Nlc3M9IGMoInNjYWxlIiwiY2VudGVyIiksDQogICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCBudW1iZXI9MTApLA0KICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZGF0YS5mcmFtZShkZWdyZWU9MSxzY2FsZT0xLEM9MSkgIyBDYW1iaWFyDQogICAgICAgICAgICAgICAgKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzMgPC0gcHJlZGljdChtb2RlbG8zLCBlbnRyZW5hbWllbnRvKQ0KcmVzdWx0YWRvX3BydWViYTMgPC0gcHJlZGljdChtb2RlbG8zLCBwcnVlYmEpDQoNCiMgTWF0cml6IGRlIENvbmZ1c2nDs24NCm1jcmUzIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzMsIGVudHJlbmFtaWVudG8kQ2xhc3MpICMgbWF0cml6IGRlIGNvbmZ1c2nDs24gZGVsIHJlc3VsdGFkbyBkZWwgZW50cmVuYW1pZW50bw0KbWNyZTMNCm1jcnAzIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fcHJ1ZWJhMywgcHJ1ZWJhJENsYXNzKSAjIG1hdHJpeiBkZSBjb25mdXNpw7NuIGRlbCByZXN1bHRhZG8gZGUgbGEgcHJ1ZWJhIA0KbWNycDMNCmBgYA0KDQojIDQuIE1vZGVsbyBjb24gZWwgbcOpdG9kbyBycGFydA0KYGBge3J9DQptb2RlbG80IDwtIHRyYWluKENsYXNzIH4gLiwgZGF0YT1lbnRyZW5hbWllbnRvLA0KICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJycGFydCIsICAjIENhbWJpYXINCiAgICAgICAgICAgICAgICBwcmVQcm9jZXNzPSBjKCJzY2FsZSIsImNlbnRlciIpLA0KICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTEwKSwNCiAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTAgIyBDYW1iaWFyDQogICAgICAgICAgICAgICAgKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzQgPC0gcHJlZGljdChtb2RlbG80LCBlbnRyZW5hbWllbnRvKQ0KcmVzdWx0YWRvX3BydWViYTQgPC0gcHJlZGljdChtb2RlbG80LCBwcnVlYmEpDQoNCiMgTWF0cml6IGRlIENvbmZ1c2nDs24NCm1jcmU0IDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzQsIGVudHJlbmFtaWVudG8kQ2xhc3MpICMgbWF0cml6IGRlIGNvbmZ1c2nDs24gZGVsIHJlc3VsdGFkbyBkZWwgZW50cmVuYW1pZW50bw0KbWNyZTQNCm1jcnA0IDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fcHJ1ZWJhNCwgcHJ1ZWJhJENsYXNzKSAjIG1hdHJpeiBkZSBjb25mdXNpw7NuIGRlbCByZXN1bHRhZG8gZGUgbGEgcHJ1ZWJhIA0KbWNycDQNCmBgYA0KDQojIDUuIE1vZGVsbyBjb24gZWwgbcOpdG9kbyBubmV0DQpgYGB7cn0NCm1vZGVsbzUgPC0gdHJhaW4oQ2xhc3MgfiAuLCBkYXRhPWVudHJlbmFtaWVudG8sDQogICAgICAgICAgICAgICAgbWV0aG9kID0gIm5uZXQiLCAgIyBDYW1iaWFyDQogICAgICAgICAgICAgICAgcHJlUHJvY2Vzcz0gYygic2NhbGUiLCJjZW50ZXIiKSwNCiAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj0xMCksDQogICAgICAgICAgICAgICAgdHJhY2UgPSBGQUxTRQ0KICAgICAgICAgICAgICAgICAgICAgICAgIyBDYW1iaWFyDQogICAgICAgICAgICAgICAgKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzUgPC0gcHJlZGljdChtb2RlbG81LCBlbnRyZW5hbWllbnRvKQ0KcmVzdWx0YWRvX3BydWViYTUgPC0gcHJlZGljdChtb2RlbG81LCBwcnVlYmEpDQoNCiMgTWF0cml6IGRlIENvbmZ1c2nDs24NCm1jcmU1IDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzUsIGVudHJlbmFtaWVudG8kQ2xhc3MpICMgbWF0cml6IGRlIGNvbmZ1c2nDs24gZGVsIHJlc3VsdGFkbyBkZWwgZW50cmVuYW1pZW50bw0KbWNyZTUNCm1jcnA1IDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fcHJ1ZWJhNSwgcHJ1ZWJhJENsYXNzKSAjIG1hdHJpeiBkZSBjb25mdXNpw7NuIGRlbCByZXN1bHRhZG8gZGUgbGEgcHJ1ZWJhIA0KbWNycDUNCmBgYA0KDQojIDYuIE1vZGVsbyBjb24gZWwgbcOpdG9kbyByZg0KYGBge3J9DQptb2RlbG82IDwtIHRyYWluKENsYXNzIH4gLiwgZGF0YT1lbnRyZW5hbWllbnRvLA0KICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZiIsICAjIENhbWJpYXINCiAgICAgICAgICAgICAgICBwcmVQcm9jZXNzPSBjKCJzY2FsZSIsImNlbnRlciIpLA0KICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTEwKSwNCiAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKG10cnkgPSBjKDIsNCw2KSkgICMgQ2FtYmlhcg0KICAgICAgICAgICAgICAgICkNCg0KcmVzdWx0YWRvX2VudHJlbmFtaWVudG82IDwtIHByZWRpY3QobW9kZWxvNiwgZW50cmVuYW1pZW50bykNCnJlc3VsdGFkb19wcnVlYmE2IDwtIHByZWRpY3QobW9kZWxvNiwgcHJ1ZWJhKQ0KDQojIE1hdHJpeiBkZSBDb25mdXNpw7NuDQptY3JlNiA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX2VudHJlbmFtaWVudG82LCBlbnRyZW5hbWllbnRvJENsYXNzKSAjIG1hdHJpeiBkZSBjb25mdXNpw7NuIGRlbCByZXN1bHRhZG8gZGVsIGVudHJlbmFtaWVudG8NCm1jcmU2DQptY3JwNiA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYTYsIHBydWViYSRDbGFzcykgIyBtYXRyaXogZGUgY29uZnVzacOzbiBkZWwgcmVzdWx0YWRvIGRlIGxhIHBydWViYSANCm1jcnA2DQpgYGANCg0KIyBSZXN1bWVuIGRlIFJlc3VsdGFkb3MNCmBgYHtyfQ0KcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKA0KICAic3ZtTGluZWFyIiA9IGMobWNyZTEkb3ZlcmFsbFsiQWNjdXJhY3kiXSwgbWNycDEkb3ZlcmFsbFsiQWNjdXJhY3kiXSksDQogICJzdm1SYWRpYWwiID0gYyhtY3JlMiRvdmVyYWxsWyJBY2N1cmFjeSJdLCBtY3JwMiRvdmVyYWxsWyJBY2N1cmFjeSJdKSwNCiAgInN2bVBvbHkiID0gYyhtY3JlMyRvdmVyYWxsWyJBY2N1cmFjeSJdLCBtY3JwMyRvdmVyYWxsWyJBY2N1cmFjeSJdKSwNCiAgInJwYXJ0IiA9IGMobWNyZTQkb3ZlcmFsbFsiQWNjdXJhY3kiXSwgbWNycDQkb3ZlcmFsbFsiQWNjdXJhY3kiXSksDQogICJubmV0IiA9IGMobWNyZTUkb3ZlcmFsbFsiQWNjdXJhY3kiXSwgbWNycDUkb3ZlcmFsbFsiQWNjdXJhY3kiXSksDQogICJyZiIgPSBjKG1jcmU2JG92ZXJhbGxbIkFjY3VyYWN5Il0sIG1jcnA2JG92ZXJhbGxbIkFjY3VyYWN5Il0pDQopDQpyb3duYW1lcyhyZXN1bHRhZG9zKSA8LSBjKCJQcmVjaXNpw7NuIGRlIGVudHJlbmFtaWVudG8iLCAiUHJlY2lzacOzbiBkZSBwcnVlYmEiKQ0KcmVzdWx0YWRvcw0KYGBgDQoNCiMgQ29uY2x1c2lvbmVzDQpMb3MgbW9kZWxvcyBjb24gbG9zIG3DqXRvZG9zIGRlIHN2bVJhZGlhbCB5IG5uZXQgcHJlc2VudGFuIHNvYnJlYWp1c3RlLCB5YSBxdWUgdGllbmVuIHVuYSBhbHRhIHByZWNpc2nDs24gZW4gZW50cmVuYW1pZW50bywgcGVybyBiYWphIGVuIHBydWViYS4gIA0KDQpBY29yZGUgYWwgcmVzdW1lbiBkZSByZXN1bHRhZG9zLCBlbCBtZWpvciBtb2RlbG8gZXMgZWwgZGUgKipNw6FxdWluYSBkZSBWZWN0b3JlcyBkZSBTb3BvcnRlIExpbmVhbCoqIHBvcnF1ZSBlcyBlbCBtw6FzIHNlbmNpbGxvIGRlIGludGVycHJldGFyIHkgZWwgZGUgbWVqb3IgcHJlY2lzacOzbiB0YW50byBlbiBlbnRyZW5hbWllbnRvIGNvbW8gZW4gcHJ1ZWJhLg0KDQo=