Conjunto de datos

Este conjunto de datos contiene transacciones realizadas con tarjetas de crédito en septiembre de 2013 por titulares europeos. Este conjunto de datos prescribe transacciones que ocurrieron en dos días, donde tenemos 492 fraudes de 284.807 transacciones.

Contiene sólo variables numéricas de entrada que son el resultado de una transformación ACP (Análisis de componentes principales). Las características V1, V2, … V28 son los componentes principales, las únicas características que no han sido transformadas con ACP son ‘Tiempo’ y ‘Cantidad’. La característica ‘Tiempo’ contiene los segundos transcurridos entre cada transacción y la primera transacción en el conjunto de datos. La característica ‘Clase’ es la variable de respuesta y toma el valor 1 en caso de fraude y 0 en caso contrario.

Lectura de los datos

library(readr)
library(dplyr)
library(ggplot2)
library(caret)
fraud <- read_csv(file = "creditcard.csv", progress = FALSE)
fraud <- fraud %>%
    filter(complete.cases(.)) %>%
    mutate(Class = factor(Class))
glimpse(fraud)
Observations: 284,806
Variables: 31
$ Time   <int> 0, 0, 1, 1, 2, 2, 4, 7, 7, 9, 10, 10, 10, 11, 12, 12, ...
$ V1     <dbl> -1.3598071, 1.1918571, -1.3583541, -0.9662717, -1.1582...
$ V2     <dbl> -0.07278117, 0.26615071, -1.34016307, -0.18522601, 0.8...
$ V3     <dbl> 2.53634674, 0.16648011, 1.77320934, 1.79299334, 1.5487...
$ V4     <dbl> 1.37815522, 0.44815408, 0.37977959, -0.86329128, 0.403...
$ V5     <dbl> -0.33832077, 0.06001765, -0.50319813, -0.01030888, -0....
$ V6     <dbl> 0.46238778, -0.08236081, 1.80049938, 1.24720317, 0.095...
$ V7     <dbl> 0.239598554, -0.078802983, 0.791460956, 0.237608940, 0...
$ V8     <dbl> 0.098697901, 0.085101655, 0.247675787, 0.377435875, -0...
$ V9     <dbl> 0.3637870, -0.2554251, -1.5146543, -1.3870241, 0.81773...
$ V10    <dbl> 0.09079417, -0.16697441, 0.20764287, -0.05495192, 0.75...
$ V11    <dbl> -0.5515995, 1.6127267, 0.6245015, -0.2264873, -0.82284...
$ V12    <dbl> -0.61780086, 1.06523531, 0.06608369, 0.17822823, 0.538...
$ V13    <dbl> -0.99138985, 0.48909502, 0.71729273, 0.50775687, 1.345...
$ V14    <dbl> -0.31116935, -0.14377230, -0.16594592, -0.28792375, -1...
$ V15    <dbl> 1.468176972, 0.635558093, 2.345864949, -0.631418118, 0...
$ V16    <dbl> -0.47040053, 0.46391704, -2.89008319, -1.05964725, -0....
$ V17    <dbl> 0.207971242, -0.114804663, 1.109969379, -0.684092786, ...
$ V18    <dbl> 0.02579058, -0.18336127, -0.12135931, 1.96577500, -0.0...
$ V19    <dbl> 0.40399296, -0.14578304, -2.26185710, -1.23262197, 0.8...
$ V20    <dbl> 0.25141210, -0.06908314, 0.52497973, -0.20803778, 0.40...
$ V21    <dbl> -0.018306778, -0.225775248, 0.247998153, -0.108300452,...
$ V22    <dbl> 0.277837576, -0.638671953, 0.771679402, 0.005273597, 0...
$ V23    <dbl> -0.110473910, 0.101288021, 0.909412262, -0.190320519, ...
$ V24    <dbl> 0.06692807, -0.33984648, -0.68928096, -1.17557533, 0.1...
$ V25    <dbl> 0.12853936, 0.16717040, -0.32764183, 0.64737603, -0.20...
$ V26    <dbl> -0.18911484, 0.12589453, -0.13909657, -0.22192884, 0.5...
$ V27    <dbl> 0.133558377, -0.008983099, -0.055352794, 0.062722849, ...
$ V28    <dbl> -0.021053053, 0.014724169, -0.059751841, 0.061457629, ...
$ Amount <dbl> 149.62, 2.69, 378.66, 123.50, 69.99, 3.67, 4.99, 40.80...
$ Class  <fctr> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...

Modelos de ML

A continuación se ajustan y comparan tres modelos de Machine Learning para la clasificación de eventos de fraude sobre los datos ejemplo, se escogen los modelos de:

Entrenamiento y ajuste del Modelo

A continuación se configura el estudio de predicción mediante el entrenamiento del modelo utilizando la librería de ML caret.

Regresión Logística

Primero se crea la data de entrenamiento y prueba para el modelo, luego se realizan los procesos de validación cruzada para reducir los problemas de sobre ajuste (Para que el modelo no se acostumbre siempre a los datos donde se entreno y funcione bien cuando lleguen nuevas observaciones), por ultimo se ajusta el modelo y se calculan las predicciones en conjunto con la matriz de confusión y el AUC.

library(ROCR)
set.seed(8585)
inTrain <- createDataPartition(y = fraud$Class, 
                               p = 0.75, list = FALSE)
training <- fraud[inTrain,]
testing <- fraud[-inTrain,]
glm_model <- train(Class ~ ., training, method = "glm",
               trControl = trainControl(
                   method = "cv", number = 10,
                   verboseIter = FALSE
                   )
               )
pred_class <- predict(glm_model, testing)
confusionMatrix(pred_class, testing$Class)
Confusion Matrix and Statistics

          Reference
Prediction     0     1
         0 71069    45
         1     9    78
                                         
               Accuracy : 0.9992         
                 95% CI : (0.999, 0.9994)
    No Information Rate : 0.9983         
    P-Value [Acc > NIR] : 2.018e-12      
                                         
                  Kappa : 0.7425         
 Mcnemar's Test P-Value : 1.908e-06      
                                         
            Sensitivity : 0.9999         
            Specificity : 0.6341         
         Pos Pred Value : 0.9994         
         Neg Pred Value : 0.8966         
             Prevalence : 0.9983         
         Detection Rate : 0.9981         
   Detection Prevalence : 0.9988         
      Balanced Accuracy : 0.8170         
                                         
       'Positive' Class : 0              
                                         

Como se puede observar en la matriz de confusión la precisión del modelo se encuentra en el 99% un buen indicador de la calidad del modelo.

#### Calculos AUC 
#### 
pred_probs <- predict(glm_model, testing, type = "prob")
probs <- pred_probs[, 2]
auc_glm <- prediction(probs, testing$Class)
### Valor del área bajo la curva 
### 
perf_auc <- performance(auc_glm, "auc")
perf_auc@y.values[[1]]
[1] 0.9662719
perf <- performance(auc_glm, "tpr", "fpr")
plot(perf)

En este caso observamos que un área bajo la curva para este modelo queda en 0.9662719 lo que se tendrá en cuenta para escoger del modelo a utilizar.

Árboles de decisión

Primero se crea la data de entrenamiento y prueba para el modelo, luego se realizan los procesos de validación cruzada para reducir los problemas de sobre ajuste (Para que el modelo no se acostumbre siempre a los datos donde se entreno y funcione bien cuando lleguen nuevas observaciones), por ultimo se ajusta el modelo y se calculan las predicciones en conjunto con la matriz de confusión y el AUC.

library(ROCR)
library(rpart)
library(rattle)
library(rpart.plot)
library(RColorBrewer)
set.seed(8585)
inTrain <- createDataPartition(y = fraud$Class, 
                               p = 0.75, list = FALSE)
training <- fraud[inTrain,]
testing <- fraud[-inTrain,]
tree_model <- train(Class ~ ., training, method = "rpart",
               trControl = trainControl(
                   method = "cv", number = 10,
                   verboseIter = FALSE
                   )
               )
pred_class <- predict(tree_model, testing)
confusionMatrix(pred_class, testing$Class)
Confusion Matrix and Statistics

          Reference
Prediction     0     1
         0 71064    35
         1    14    88
                                          
               Accuracy : 0.9993          
                 95% CI : (0.9991, 0.9995)
    No Information Rate : 0.9983          
    P-Value [Acc > NIR] : 2.53e-14        
                                          
                  Kappa : 0.7819          
 Mcnemar's Test P-Value : 0.004275        
                                          
            Sensitivity : 0.9998          
            Specificity : 0.7154          
         Pos Pred Value : 0.9995          
         Neg Pred Value : 0.8627          
             Prevalence : 0.9983          
         Detection Rate : 0.9981          
   Detection Prevalence : 0.9986          
      Balanced Accuracy : 0.8576          
                                          
       'Positive' Class : 0               
                                          

Como se puede observar en la matriz de confusión la precisión del modelo se encuentra en el 99% un buen indicador de la calidad del modelo. A continuación se muestra el gráfico para las principales reglas mostradas por el algoritmo.

fancyRpartPlot(tree_model$finalModel)

#### Calculos AUC 
#### 
pred_probs <- predict(tree_model, testing, type = "prob")
probs <- pred_probs[, 2]
auc_tree <- prediction(probs, testing$Class)
### Valor del área bajo la curva 
### 
perf_auc <- performance(auc_tree, "auc")
perf_auc@y.values[[1]]
[1] 0.8576071
perf <- performance(auc_tree, "tpr", "fpr")
plot(perf)

En este caso observamos que un área bajo la curva para este modelo queda en 0.8576071 lo que se tendrá en cuenta para escoger del modelo a utilizar.

Comparación de modelos

Para observar de una forma más adecuada el desempeño de los modelos se propone una comparación mediante el criterio AUC, para determinar el modelo que da los mejores resultados

model_list <- list(item1 = glm_model, item2 = tree_model)
resamples <- resamples(model_list)
summary(resamples)

Call:
summary.resamples(object = resamples)

Models: item1, item2 
Number of resamples: 10 

Accuracy 
           Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
item1 0.9989233 0.9991573 0.9992041 0.9991807 0.9992509 0.9992978    0
item2 0.9989233 0.9991222 0.9991573 0.9991714 0.9992510 0.9992978    0

Kappa 
           Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
item1 0.5960002 0.7113878 0.7324638 0.7191762 0.7486942 0.7688859    0
item2 0.6561831 0.7198024 0.7456050 0.7415539 0.7757716 0.7834086    0

Conclusión

Los modelos propuestos aquí son de los más utilizados para el ajuste de funciones de clasificación y predicción no obstante al poseer una mejor infraestructura TI, se sugiere el uso de otros modelos que igual realizan la misma tarea de una manera más precisa como los modelos (random forest, bagging o boosting), sin embargo es de notar que ambos modelos propuestos son muy buenos en términos de precisión, pero la regresión logística da mayor área bajo la curva por lo que para este caso en especifico se sugiere su uso en producción. Por ultimo al escoger este modelo se recomienda entrar a detallar las importancia de variables y otras técnicas de reducción de sesgo y varianza para la afinación del modelo.

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayAtIEVqZW1wbG8gTUwgcGFyYSBkZXRlY2Npw7NuIGRlIGZyYXVkZSBlbiBUQyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgQ29uanVudG8gZGUgZGF0b3MgCgpFc3RlIFtjb25qdW50byBkZSBkYXRvc10oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYWxwb3p6L2NyZWRpdGNhcmRmcmF1ZCkgY29udGllbmUgdHJhbnNhY2Npb25lcyByZWFsaXphZGFzIGNvbiB0YXJqZXRhcyBkZSBjcsOpZGl0byBlbiBzZXB0aWVtYnJlIGRlIDIwMTMgcG9yIHRpdHVsYXJlcyBldXJvcGVvcy4gRXN0ZSBjb25qdW50byBkZSBkYXRvcyBwcmVzY3JpYmUgdHJhbnNhY2Npb25lcyBxdWUgb2N1cnJpZXJvbiBlbiBkb3MgZMOtYXMsIGRvbmRlIHRlbmVtb3MgNDkyIGZyYXVkZXMgZGUgMjg0LjgwNyB0cmFuc2FjY2lvbmVzLgoKQ29udGllbmUgc8OzbG8gdmFyaWFibGVzIG51bcOpcmljYXMgZGUgZW50cmFkYSBxdWUgc29uIGVsIHJlc3VsdGFkbyBkZSB1bmEgdHJhbnNmb3JtYWNpw7NuIEFDUCAoQW7DoWxpc2lzIGRlIGNvbXBvbmVudGVzIHByaW5jaXBhbGVzKS4gTGFzIGNhcmFjdGVyw61zdGljYXMgVjEsIFYyLCAuLi4gVjI4IHNvbiBsb3MgY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMsIGxhcyDDum5pY2FzIGNhcmFjdGVyw61zdGljYXMgcXVlIG5vIGhhbiBzaWRvIHRyYW5zZm9ybWFkYXMgY29uIEFDUCBzb24gJ1RpZW1wbycgeSAnQ2FudGlkYWQnLiBMYSBjYXJhY3RlcsOtc3RpY2EgJ1RpZW1wbycgY29udGllbmUgbG9zIHNlZ3VuZG9zIHRyYW5zY3Vycmlkb3MgZW50cmUgY2FkYSB0cmFuc2FjY2nDs24geSBsYSBwcmltZXJhIHRyYW5zYWNjacOzbiBlbiBlbCBjb25qdW50byBkZSBkYXRvcy4gTGEgY2FyYWN0ZXLDrXN0aWNhICdDbGFzZScgZXMgbGEgdmFyaWFibGUgZGUgcmVzcHVlc3RhIHkgdG9tYSBlbCB2YWxvciAxIGVuIGNhc28gZGUgZnJhdWRlIHkgMCBlbiBjYXNvIGNvbnRyYXJpby4KCiMjIExlY3R1cmEgZGUgbG9zIGRhdG9zIAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUV9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShjYXJldCkKCmZyYXVkIDwtIHJlYWRfY3N2KGZpbGUgPSAiY3JlZGl0Y2FyZC5jc3YiLCBwcm9ncmVzcyA9IEZBTFNFKQoKZnJhdWQgPC0gZnJhdWQgJT4lCiAgICBmaWx0ZXIoY29tcGxldGUuY2FzZXMoLikpICU+JQogICAgbXV0YXRlKENsYXNzID0gZmFjdG9yKENsYXNzKSkKCmdsaW1wc2UoZnJhdWQpCgpgYGAKCgojIyBNb2RlbG9zIGRlIE1MIAoKQSBjb250aW51YWNpw7NuIHNlIGFqdXN0YW4geSBjb21wYXJhbiB0cmVzIG1vZGVsb3MgZGUgX01hY2hpbmUgTGVhcm5pbmdfIHBhcmEgbGEgY2xhc2lmaWNhY2nDs24gZGUgZXZlbnRvcyBkZSBmcmF1ZGUgc29icmUgbG9zIGRhdG9zIGVqZW1wbG8sIHNlIGVzY29nZW4gbG9zIG1vZGVsb3MgZGU6CgotIFJlZ3Jlc2nDs24gTG9nw61zdGljYTogTW9kZWxvIHF1ZSBheXVkYSBhIGxhIGNsYXNpZmljYWNpw7NuIHkgcGVybWl0ZSBpbnRlcnByZXRhciBzdXMgY29lZmljaWVudGVzIGxvIHF1ZSBheXVkYSBhIGRldGVybWluYXIgbGFzIHJlbGFjaW9uZXMgZW50cmUgbGFzIHZhcmlhYmxlcyBleHBsaWNhdGl2YXMgeSBsYSByZXNwdWVzdGEgKEhhY2VyIGluZmVyZW5jaWEpLgoKLSDDgXJib2xlcyBkZSBkZWNpc2nDs246IEVzIHVuIG1vZGVsbyBubyBwYXJhbWV0cmljbyBxdWUgcGVybWl0ZSBkZXRlcm1pbmFyIHJlZ2xhcyBkZSBhc29jaWFjacOzbiBlbnRyZSBsYXMgdmFyaWFibGVzIGRlbCBtb2RlbG8sIGFkZW1hcyBkZSBzZXIgbXV5IGJ1ZW5vIHBhcmEgbGFzIHByZWRpY2Npb25lcyAgCgotIEJvc3F1ZXMgYWxlYXRvcmlvczogUHJlZGljZW4gbXV5IGJpZW4sIHBlcm8gbm8gcGVybWl0ZW4gbGEgaW50ZXJwcmV0YWNpw7NuIGRlbCBtb2RlbG8sIHV0aWxpemFuIHJlbXVlc3RyZW8gKFNvbiBjb21wdXRhY2lvbmFsbWVudGUgZXhoYXVzdGl2b3MsIHBvciBlc28gc29uIGJ1ZW5vcyBjb24gbGEgcHJlZGljY2nDs24pIAoKIyMgRW50cmVuYW1pZW50byB5IGFqdXN0ZSBkZWwgTW9kZWxvIAoKQSBjb250aW51YWNpw7NuIHNlIGNvbmZpZ3VyYSBlbCBlc3R1ZGlvIGRlIHByZWRpY2Npw7NuIG1lZGlhbnRlIGVsIGVudHJlbmFtaWVudG8gZGVsIG1vZGVsbyB1dGlsaXphbmRvIGxhIGxpYnJlcsOtYSBkZSBfTUxfIGBjYXJldGAuCgojIyBSZWdyZXNpw7NuIExvZ8Otc3RpY2EKClByaW1lcm8gc2UgY3JlYSBsYSBkYXRhIGRlIGVudHJlbmFtaWVudG8geSBwcnVlYmEgcGFyYSBlbCBtb2RlbG8sIGx1ZWdvIHNlIHJlYWxpemFuIGxvcyBwcm9jZXNvcyBkZSB2YWxpZGFjacOzbiBjcnV6YWRhIHBhcmEgcmVkdWNpciBsb3MgcHJvYmxlbWFzIGRlIHNvYnJlIGFqdXN0ZSAoUGFyYSBxdWUgZWwgbW9kZWxvIG5vIHNlIGFjb3N0dW1icmUgc2llbXByZSBhIGxvcyBkYXRvcyBkb25kZSBzZSBlbnRyZW5vIHkgZnVuY2lvbmUgYmllbiBjdWFuZG8gbGxlZ3VlbiBudWV2YXMgb2JzZXJ2YWNpb25lcyksIHBvciB1bHRpbW8gc2UgYWp1c3RhIGVsIG1vZGVsbyB5IHNlIGNhbGN1bGFuIGxhcyBwcmVkaWNjaW9uZXMgZW4gY29uanVudG8gY29uIGxhIG1hdHJpeiBkZSBjb25mdXNpw7NuIHkgZWwgQVVDLgoKYGBge3IsICBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFfQpsaWJyYXJ5KFJPQ1IpCnNldC5zZWVkKDg1ODUpCgppblRyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IGZyYXVkJENsYXNzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHAgPSAwLjc1LCBsaXN0ID0gRkFMU0UpCgp0cmFpbmluZyA8LSBmcmF1ZFtpblRyYWluLF0KdGVzdGluZyA8LSBmcmF1ZFstaW5UcmFpbixdCgpnbG1fbW9kZWwgPC0gdHJhaW4oQ2xhc3MgfiAuLCB0cmFpbmluZywgbWV0aG9kID0gImdsbSIsCiAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbCgKICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwLAogICAgICAgICAgICAgICAgICAgdmVyYm9zZUl0ZXIgPSBGQUxTRQogICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICApCgpwcmVkX2NsYXNzIDwtIHByZWRpY3QoZ2xtX21vZGVsLCB0ZXN0aW5nKQoKY29uZnVzaW9uTWF0cml4KHByZWRfY2xhc3MsIHRlc3RpbmckQ2xhc3MpCgoKYGBgCgpDb21vIHNlIHB1ZWRlIG9ic2VydmFyIGVuIGxhIG1hdHJpeiBkZSBjb25mdXNpw7NuIGxhIHByZWNpc2nDs24gZGVsIG1vZGVsbyBzZSBlbmN1ZW50cmEgZW4gZWwgOTklIHVuIGJ1ZW4gaW5kaWNhZG9yIGRlIGxhIGNhbGlkYWQgZGVsIG1vZGVsby4KCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9CiMjIyMgQ2FsY3Vsb3MgQVVDIAojIyMjIApwcmVkX3Byb2JzIDwtIHByZWRpY3QoZ2xtX21vZGVsLCB0ZXN0aW5nLCB0eXBlID0gInByb2IiKQpwcm9icyA8LSBwcmVkX3Byb2JzWywgMl0KYXVjX2dsbSA8LSBwcmVkaWN0aW9uKHByb2JzLCB0ZXN0aW5nJENsYXNzKQoKIyMjIFZhbG9yIGRlbCDDoXJlYSBiYWpvIGxhIGN1cnZhIAojIyMgCnBlcmZfYXVjIDwtIHBlcmZvcm1hbmNlKGF1Y19nbG0sICJhdWMiKQpwZXJmX2F1Y0B5LnZhbHVlc1tbMV1dCgpwZXJmIDwtIHBlcmZvcm1hbmNlKGF1Y19nbG0sICJ0cHIiLCAiZnByIikKcGxvdChwZXJmKQoKYGBgCgpFbiBlc3RlIGNhc28gb2JzZXJ2YW1vcyBxdWUgdW4gw6FyZWEgYmFqbyBsYSBjdXJ2YSBwYXJhIGVzdGUgbW9kZWxvIHF1ZWRhIGVuIDAuOTY2MjcxOSBsbyBxdWUgc2UgdGVuZHLDoSBlbiBjdWVudGEgcGFyYSBlc2NvZ2VyIGRlbCBtb2RlbG8gYSB1dGlsaXphci4KCgojIyDDgXJib2xlcyBkZSBkZWNpc2nDs24KClByaW1lcm8gc2UgY3JlYSBsYSBkYXRhIGRlIGVudHJlbmFtaWVudG8geSBwcnVlYmEgcGFyYSBlbCBtb2RlbG8sIGx1ZWdvIHNlIHJlYWxpemFuIGxvcyBwcm9jZXNvcyBkZSB2YWxpZGFjacOzbiBjcnV6YWRhIHBhcmEgcmVkdWNpciBsb3MgcHJvYmxlbWFzIGRlIHNvYnJlIGFqdXN0ZSAoUGFyYSBxdWUgZWwgbW9kZWxvIG5vIHNlIGFjb3N0dW1icmUgc2llbXByZSBhIGxvcyBkYXRvcyBkb25kZSBzZSBlbnRyZW5vIHkgZnVuY2lvbmUgYmllbiBjdWFuZG8gbGxlZ3VlbiBudWV2YXMgb2JzZXJ2YWNpb25lcyksIHBvciB1bHRpbW8gc2UgYWp1c3RhIGVsIG1vZGVsbyB5IHNlIGNhbGN1bGFuIGxhcyBwcmVkaWNjaW9uZXMgZW4gY29uanVudG8gY29uIGxhIG1hdHJpeiBkZSBjb25mdXNpw7NuIHkgZWwgQVVDLgoKYGBge3IsIGNhY2hlPVRSVUUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoUk9DUikKbGlicmFyeShycGFydCkKbGlicmFyeShyYXR0bGUpCmxpYnJhcnkocnBhcnQucGxvdCkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCgpzZXQuc2VlZCg4NTg1KQoKaW5UcmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSBmcmF1ZCRDbGFzcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwID0gMC43NSwgbGlzdCA9IEZBTFNFKQoKdHJhaW5pbmcgPC0gZnJhdWRbaW5UcmFpbixdCnRlc3RpbmcgPC0gZnJhdWRbLWluVHJhaW4sXQoKdHJlZV9tb2RlbCA8LSB0cmFpbihDbGFzcyB+IC4sIHRyYWluaW5nLCBtZXRob2QgPSAicnBhcnQiLAogICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2woCiAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCwKICAgICAgICAgICAgICAgICAgIHZlcmJvc2VJdGVyID0gRkFMU0UKICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgKQoKcHJlZF9jbGFzcyA8LSBwcmVkaWN0KHRyZWVfbW9kZWwsIHRlc3RpbmcpCgpjb25mdXNpb25NYXRyaXgocHJlZF9jbGFzcywgdGVzdGluZyRDbGFzcykKCmBgYAoKQ29tbyBzZSBwdWVkZSBvYnNlcnZhciBlbiBsYSBtYXRyaXogZGUgY29uZnVzacOzbiBsYSBwcmVjaXNpw7NuIGRlbCBtb2RlbG8gc2UgZW5jdWVudHJhIGVuIGVsIDk5JSB1biBidWVuIGluZGljYWRvciBkZSBsYSBjYWxpZGFkIGRlbCBtb2RlbG8uIEEgY29udGludWFjacOzbiBzZSBtdWVzdHJhIGVsIGdyw6FmaWNvIHBhcmEgbGFzIHByaW5jaXBhbGVzIHJlZ2xhcyBtb3N0cmFkYXMgcG9yIGVsIGFsZ29yaXRtby4KCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9CmZhbmN5UnBhcnRQbG90KHRyZWVfbW9kZWwkZmluYWxNb2RlbCkKYGBgCgpgYGB7ciwgY2FjaGU9VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyMjIyBDYWxjdWxvcyBBVUMgCiMjIyMgCnByZWRfcHJvYnMgPC0gcHJlZGljdCh0cmVlX21vZGVsLCB0ZXN0aW5nLCB0eXBlID0gInByb2IiKQpwcm9icyA8LSBwcmVkX3Byb2JzWywgMl0KYXVjX3RyZWUgPC0gcHJlZGljdGlvbihwcm9icywgdGVzdGluZyRDbGFzcykKCiMjIyBWYWxvciBkZWwgw6FyZWEgYmFqbyBsYSBjdXJ2YSAKIyMjIApwZXJmX2F1YyA8LSBwZXJmb3JtYW5jZShhdWNfdHJlZSwgImF1YyIpCnBlcmZfYXVjQHkudmFsdWVzW1sxXV0KCnBlcmYgPC0gcGVyZm9ybWFuY2UoYXVjX3RyZWUsICJ0cHIiLCAiZnByIikKcGxvdChwZXJmKQoKYGBgCgpFbiBlc3RlIGNhc28gb2JzZXJ2YW1vcyBxdWUgdW4gw6FyZWEgYmFqbyBsYSBjdXJ2YSBwYXJhIGVzdGUgbW9kZWxvIHF1ZWRhIGVuIDAuODU3NjA3MSBsbyBxdWUgc2UgdGVuZHLDoSBlbiBjdWVudGEgcGFyYSBlc2NvZ2VyIGRlbCBtb2RlbG8gYSB1dGlsaXphci4KCgoKIyMgQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgCgpQYXJhIG9ic2VydmFyIGRlIHVuYSBmb3JtYSBtw6FzIGFkZWN1YWRhIGVsIGRlc2VtcGXDsW8gZGUgbG9zIG1vZGVsb3Mgc2UgcHJvcG9uZSB1bmEgY29tcGFyYWNpw7NuIG1lZGlhbnRlIGVsIGNyaXRlcmlvIEFVQywgcGFyYSBkZXRlcm1pbmFyIGVsIG1vZGVsbyBxdWUgZGEgbG9zIG1lam9yZXMgcmVzdWx0YWRvcyAKCmBgYHtyLCBjYWNoZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKbW9kZWxfbGlzdCA8LSBsaXN0KGl0ZW0xID0gZ2xtX21vZGVsLCBpdGVtMiA9IHRyZWVfbW9kZWwpCnJlc2FtcGxlcyA8LSByZXNhbXBsZXMobW9kZWxfbGlzdCkKc3VtbWFyeShyZXNhbXBsZXMpCgpgYGAKCiMjIENvbmNsdXNpw7NuIAoKTG9zIG1vZGVsb3MgcHJvcHVlc3RvcyBhcXXDrSBzb24gZGUgbG9zIG3DoXMgdXRpbGl6YWRvcyBwYXJhIGVsIGFqdXN0ZSBkZSBmdW5jaW9uZXMgZGUgY2xhc2lmaWNhY2nDs24geSBwcmVkaWNjacOzbiBubyBvYnN0YW50ZSBhbCBwb3NlZXIgdW5hIG1lam9yIGluZnJhZXN0cnVjdHVyYSBUSSwgc2Ugc3VnaWVyZSBlbCB1c28gZGUgb3Ryb3MgbW9kZWxvcyBxdWUgaWd1YWwgcmVhbGl6YW4gbGEgbWlzbWEgdGFyZWEgZGUgdW5hIG1hbmVyYSBtw6FzIHByZWNpc2EgY29tbyBsb3MgbW9kZWxvcyAocmFuZG9tIGZvcmVzdCwgYmFnZ2luZyBvIGJvb3N0aW5nKSwgc2luIGVtYmFyZ28gZXMgZGUgbm90YXIgcXVlIGFtYm9zIG1vZGVsb3MgcHJvcHVlc3RvcyBzb24gbXV5IGJ1ZW5vcyBlbiB0w6lybWlub3MgZGUgcHJlY2lzacOzbiwgcGVybyBsYSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EgZGEgbWF5b3Igw6FyZWEgYmFqbyBsYSBjdXJ2YSBwb3IgbG8gcXVlIHBhcmEgZXN0ZSBjYXNvIGVuIGVzcGVjaWZpY28gc2Ugc3VnaWVyZSBzdSB1c28gZW4gcHJvZHVjY2nDs24uIFBvciB1bHRpbW8gYWwgZXNjb2dlciBlc3RlIG1vZGVsbyBzZSByZWNvbWllbmRhIGVudHJhciBhIGRldGFsbGFyIGxhcyBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMgeSBvdHJhcyB0w6ljbmljYXMgZGUgcmVkdWNjacOzbiBkZSBzZXNnbyB5IHZhcmlhbnphIHBhcmEgbGEgYWZpbmFjacOzbiBkZWwgbW9kZWxvLiAgIA==