Prompt: Use deep learning from H2o package to train your data set. Investigate prediction performance (limit to 2-3 layers) on multiple runs using H2o package (experiment by varying parameter such as numbers of layers, numbers of nodes, etc.,)

We are also provided with a few options for the data set to use for this exercise. One of these options is the MNIST digit classification data set. I have not done much work with images, and quite a bit of classification exercises, so I am going to with this digit data.

To begin, we need to load the required packages and read in the data for interpretation. First step is to take a look at the training set. I have gone ahead and extracted the training set. Below we can see the first 4 digits.

library(jsonlite)
library(caret)
library(h2o)
library(ggplot2)
library(data.table)
library(e1071)
setwd('C:\\Users\\JP\\Downloads')
The working directory was changed to C:/Users/JP/Downloads inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the the working directory for notebook chunks.
data = file("C:\\Users\\JP\\Downloads\\train-images.idx3-ubyte", "rb")
readBin(data, integer(), n=4, endian="big")
[1]  2051 60000    28    28
m = matrix(readBin(data,integer(), size=4, n=28*28, endian="big"),28,28)
par(mfrow=c(2,2))
for(i in 1:4){m = matrix(readBin(data,integer(), size=1, n=28*28, endian="big"),28,28);image(m[,28:1])}

It looks like the data is loaded in properly, and we have what we need to begin. Now we start up h2o and get the data loaded in. I found a blog post that helps us get the data into the appropriate format to begin testing models for interpretation. We will use those functions to get stared. The blog post can be found here: link

h2o.init()
 Connection successful!

R is connected to the H2O cluster: 
    H2O cluster uptime:         4 hours 31 minutes 
    H2O cluster version:        3.10.4.4 
    H2O cluster version age:    22 days  
    H2O cluster name:           H2O_started_from_R_JP_pzr504 
    H2O cluster total nodes:    1 
    H2O cluster total memory:   3.34 GB 
    H2O cluster total cores:    4 
    H2O cluster allowed cores:  2 
    H2O cluster healthy:        TRUE 
    H2O Connection ip:          localhost 
    H2O Connection port:        54321 
    H2O Connection proxy:       NA 
    H2O Internal Security:      FALSE 
    R Version:                  R version 3.3.3 (2017-03-06) 
setwd('C:\\Users\\JP\\Downloads')
The working directory was changed to C:/Users/JP/Downloads inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the the working directory for notebook chunks.
load_image_file <- function(filename) {
  ret = list()
  f = file(filename,'rb')
  readBin(f,'integer',n=1,size=4,endian='big')
  ret$n = readBin(f,'integer',n=1,size=4,endian='big')
  nrow = readBin(f,'integer',n=1,size=4,endian='big')
  ncol = readBin(f,'integer',n=1,size=4,endian='big')
  x = readBin(f,'integer',n=ret$n*nrow*ncol,size=1,signed=F)
  ret$x = matrix(x, ncol=nrow*ncol, byrow=T)
  close(f)
  ret
}
load_label_file <- function(filename) { 
  f = file(filename,'rb')
  readBin(f,'integer',n=1,size=4,endian='big')
  n = readBin(f,'integer',n=1,size=4,endian='big')
  y = readBin(f,'integer',n=n,size=1,signed=F)
  close(f)
  y
}
imagetraining<-as.data.frame(load_image_file("C:\\Users\\JP\\Downloads\\train-images.idx3-ubyte"))
imagetest<-as.data.frame(load_image_file("C:\\Users\\JP\\Downloads\\t10k-images.idx3-ubyte"))
labeltraining<-as.factor(load_label_file("C:\\Users\\JP\\Downloads\\train-labels.idx1-ubyte"))
labeltest<-as.factor(load_label_file("C:\\Users\\JP\\Downloads\\t10k-labels.idx1-ubyte"))
imagetraining[,1]<-labeltraining
imagetest[,1]<-labeltest
Training<-imagetraining
Test<-imagetest 

Now that the data is loaded in and ready to go, we can start by looking at different models using h2o. We will convert to h2o objects and begin running some models and use the caret package to see how we do.

We did not do very well here. There appears to be quite a few 7’s in our results set, which is not actually the case. Let’s try a few things to change it up. We had originally only had 2 nodes for each of the three layers and only 10 epochs. Let’s increase these numbers and see how we do. We will also expiriment with addinf 5-fold data validation for a third model and in a 4th model we will reduce the input dropout ratio, which is a feature that controls what ratio of features is dropped for a training row. I figure if we are adding validation, we could lower this number.

model2<-h2o.deeplearning(x=x,y="n",training_frame = TrainingH,validation_frame = TestH,distribution = "multinomial",activation="RectifierWithDropout",hidden = c(50,50,50),input_dropout_ratio = .2,sparse=T,epochs=100)

model3<-h2o.deeplearning(x=x,y="n",training_frame = TrainingH,validation_frame = TestH,distribution = "multinomial",activation="RectifierWithDropout",hidden = c(30,30,30),input_dropout_ratio = .2,sparse=T,epochs=50,nfolds=5)

model4<-h2o.deeplearning(x=x,y="n",training_frame = TrainingH,validation_frame = TestH,distribution = "multinomial",activation="RectifierWithDropout",hidden = c(30,30,30),input_dropout_ratio = .1,sparse=T,epochs=50,nfolds=5)

We make a few models, now it is time to put them to the test. It would not be hard to not beat our predictions earlier, but let’s see. These models take a significantly longer amount of time to run.

Model 2
head(TestH[,1])

head(results2)
caret::confusionMatrix(unlist(results2),Test$n)$overall
      Accuracy          Kappa  AccuracyLower  AccuracyUpper   AccuracyNull AccuracyPValue  McnemarPValue 
     0.9384000      0.9315299      0.9335086      0.9430341      0.1135000      0.0000000            NaN 

Very high accuracy in model 2 as we would expect with such a wide increase in the number of operations that are performed.

Model 3
head(TestH[,1])

head(results3)
caret::confusionMatrix(unlist(results3),Test$n)$overall
      Accuracy          Kappa  AccuracyLower  AccuracyUpper   AccuracyNull AccuracyPValue  McnemarPValue 
     0.8029000      0.7808491      0.7949654      0.8106578      0.1135000      0.0000000            NaN 

Not quite as accurate, this could be due simply to the fact that we reduced the layers. Let’s see if our input dropout ratio change has any impact on the accuracy.

Model 4
head(TestH[,1])

head(results4)
caret::confusionMatrix(unlist(results4),Test$n)$overall
      Accuracy          Kappa  AccuracyLower  AccuracyUpper   AccuracyNull AccuracyPValue  McnemarPValue 
     0.8790000      0.8654560      0.8724474      0.8853309      0.1135000      0.0000000            NaN 

Model 4 was an improvement on model 3, but the second model with the most layers has performed the best so far in terms of accuracy.

The options appear to be very unlimited. If we were going for the highest accuracy and the best model, we could make many models, and do things such as determine variable importance in order to find more and more accurate models. For the purposes of this exercise though, I would feel comfortable using our second model to make classificaitons. While it is not 100% reliable, it did very well in classifying the digits. It is very obvious that the strength is great when the first few digits completely line up.

LS0tDQp0aXRsZTogIldlZWsgOCBQcm9qZWN0IExlYXJuaW5nIHdpdGggaDJvIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQphdXRob3I6IEpvaG4gTmV2aWxsZQ0KLS0tDQoNClByb21wdDogIFVzZSBkZWVwIGxlYXJuaW5nIGZyb20gSDJvIHBhY2thZ2UgdG8gdHJhaW4geW91ciBkYXRhIHNldC4gSW52ZXN0aWdhdGUgcHJlZGljdGlvbiBwZXJmb3JtYW5jZSAobGltaXQgdG8gMi0zIGxheWVycykgb24gbXVsdGlwbGUgcnVucyB1c2luZyBIMm8gcGFja2FnZSAoZXhwZXJpbWVudCBieSB2YXJ5aW5nIHBhcmFtZXRlciBzdWNoIGFzIG51bWJlcnMgb2YgbGF5ZXJzLCBudW1iZXJzIG9mIG5vZGVzLCBldGMuLCkNCg0KV2UgYXJlIGFsc28gcHJvdmlkZWQgd2l0aCBhIGZldyBvcHRpb25zIGZvciB0aGUgZGF0YSBzZXQgdG8gdXNlIGZvciB0aGlzIGV4ZXJjaXNlLiAgT25lIG9mIHRoZXNlIG9wdGlvbnMgaXMgdGhlIE1OSVNUIGRpZ2l0IGNsYXNzaWZpY2F0aW9uIGRhdGEgc2V0LiAgSSBoYXZlIG5vdCBkb25lIG11Y2ggd29yayB3aXRoIGltYWdlcywgYW5kIHF1aXRlIGEgYml0IG9mIGNsYXNzaWZpY2F0aW9uIGV4ZXJjaXNlcywgc28gSSBhbSBnb2luZyB0byB3aXRoIHRoaXMgZGlnaXQgZGF0YS4NCg0KVG8gYmVnaW4sIHdlIG5lZWQgdG8gbG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXMgYW5kIHJlYWQgaW4gdGhlIGRhdGEgZm9yIGludGVycHJldGF0aW9uLiAgRmlyc3Qgc3RlcCBpcyB0byB0YWtlIGEgbG9vayBhdCB0aGUgdHJhaW5pbmcgc2V0LiAgSSBoYXZlIGdvbmUgYWhlYWQgYW5kIGV4dHJhY3RlZCB0aGUgdHJhaW5pbmcgc2V0LiAgQmVsb3cgd2UgY2FuIHNlZSB0aGUgZmlyc3QgNCBkaWdpdHMuDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KGpzb25saXRlKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoaDJvKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShlMTA3MSkNCg0Kc2V0d2QoJ0M6XFxVc2Vyc1xcSlBcXERvd25sb2FkcycpDQpkYXRhID0gZmlsZSgiQzpcXFVzZXJzXFxKUFxcRG93bmxvYWRzXFx0cmFpbi1pbWFnZXMuaWR4My11Ynl0ZSIsICJyYiIpDQoNCnJlYWRCaW4oZGF0YSwgaW50ZWdlcigpLCBuPTQsIGVuZGlhbj0iYmlnIikNCg0KbSA9IG1hdHJpeChyZWFkQmluKGRhdGEsaW50ZWdlcigpLCBzaXplPTQsIG49MjgqMjgsIGVuZGlhbj0iYmlnIiksMjgsMjgpDQoNCnBhcihtZnJvdz1jKDIsMikpDQoNCmZvcihpIGluIDE6NCl7bSA9IG1hdHJpeChyZWFkQmluKGRhdGEsaW50ZWdlcigpLCBzaXplPTEsIG49MjgqMjgsIGVuZGlhbj0iYmlnIiksMjgsMjgpO2ltYWdlKG1bLDI4OjFdKX0NCmBgYA0KDQpJdCBsb29rcyBsaWtlIHRoZSBkYXRhIGlzIGxvYWRlZCBpbiBwcm9wZXJseSwgYW5kIHdlIGhhdmUgd2hhdCB3ZSBuZWVkIHRvIGJlZ2luLiBOb3cgd2Ugc3RhcnQgdXAgaDJvIGFuZCBnZXQgdGhlIGRhdGEgbG9hZGVkIGluLiAgSSBmb3VuZCBhIGJsb2cgcG9zdCB0aGF0IGhlbHBzIHVzIGdldCB0aGUgZGF0YSBpbnRvIHRoZSBhcHByb3ByaWF0ZSBmb3JtYXQgdG8gYmVnaW4gdGVzdGluZyBtb2RlbHMgZm9yIGludGVycHJldGF0aW9uLiAgV2Ugd2lsbCB1c2UgdGhvc2UgZnVuY3Rpb25zIHRvIGdldCBzdGFyZWQuICBUaGUgYmxvZyBwb3N0IGNhbiBiZSBmb3VuZCBoZXJlOiAgIFtsaW5rXShodHRwczovL2NoYXJsZXNoc2xpYW8ud29yZHByZXNzLmNvbS8yMDE3LzA0LzE1L2EtaDJvLWZubi1tb2RlbC1mb3ItbW5pc3QvKQ0KDQpgYGB7cn0NCmgyby5pbml0KCkNCg0Kc2V0d2QoJ0M6XFxVc2Vyc1xcSlBcXERvd25sb2FkcycpDQpsb2FkX2ltYWdlX2ZpbGUgPC0gZnVuY3Rpb24oZmlsZW5hbWUpIHsNCiAgcmV0ID0gbGlzdCgpDQogIGYgPSBmaWxlKGZpbGVuYW1lLCdyYicpDQogIHJlYWRCaW4oZiwnaW50ZWdlcicsbj0xLHNpemU9NCxlbmRpYW49J2JpZycpDQogIHJldCRuID0gcmVhZEJpbihmLCdpbnRlZ2VyJyxuPTEsc2l6ZT00LGVuZGlhbj0nYmlnJykNCiAgbnJvdyA9IHJlYWRCaW4oZiwnaW50ZWdlcicsbj0xLHNpemU9NCxlbmRpYW49J2JpZycpDQogIG5jb2wgPSByZWFkQmluKGYsJ2ludGVnZXInLG49MSxzaXplPTQsZW5kaWFuPSdiaWcnKQ0KICB4ID0gcmVhZEJpbihmLCdpbnRlZ2VyJyxuPXJldCRuKm5yb3cqbmNvbCxzaXplPTEsc2lnbmVkPUYpDQogIHJldCR4ID0gbWF0cml4KHgsIG5jb2w9bnJvdypuY29sLCBieXJvdz1UKQ0KICBjbG9zZShmKQ0KICByZXQNCn0NCmxvYWRfbGFiZWxfZmlsZSA8LSBmdW5jdGlvbihmaWxlbmFtZSkgeyANCiAgZiA9IGZpbGUoZmlsZW5hbWUsJ3JiJykNCiAgcmVhZEJpbihmLCdpbnRlZ2VyJyxuPTEsc2l6ZT00LGVuZGlhbj0nYmlnJykNCiAgbiA9IHJlYWRCaW4oZiwnaW50ZWdlcicsbj0xLHNpemU9NCxlbmRpYW49J2JpZycpDQogIHkgPSByZWFkQmluKGYsJ2ludGVnZXInLG49bixzaXplPTEsc2lnbmVkPUYpDQogIGNsb3NlKGYpDQogIHkNCn0NCmltYWdldHJhaW5pbmc8LWFzLmRhdGEuZnJhbWUobG9hZF9pbWFnZV9maWxlKCJDOlxcVXNlcnNcXEpQXFxEb3dubG9hZHNcXHRyYWluLWltYWdlcy5pZHgzLXVieXRlIikpDQppbWFnZXRlc3Q8LWFzLmRhdGEuZnJhbWUobG9hZF9pbWFnZV9maWxlKCJDOlxcVXNlcnNcXEpQXFxEb3dubG9hZHNcXHQxMGstaW1hZ2VzLmlkeDMtdWJ5dGUiKSkNCmxhYmVsdHJhaW5pbmc8LWFzLmZhY3Rvcihsb2FkX2xhYmVsX2ZpbGUoIkM6XFxVc2Vyc1xcSlBcXERvd25sb2Fkc1xcdHJhaW4tbGFiZWxzLmlkeDEtdWJ5dGUiKSkNCmxhYmVsdGVzdDwtYXMuZmFjdG9yKGxvYWRfbGFiZWxfZmlsZSgiQzpcXFVzZXJzXFxKUFxcRG93bmxvYWRzXFx0MTBrLWxhYmVscy5pZHgxLXVieXRlIikpDQppbWFnZXRyYWluaW5nWywxXTwtbGFiZWx0cmFpbmluZw0KaW1hZ2V0ZXN0WywxXTwtbGFiZWx0ZXN0DQpUcmFpbmluZzwtaW1hZ2V0cmFpbmluZw0KVGVzdDwtaW1hZ2V0ZXN0IA0KDQpgYGANCg0KTm93IHRoYXQgdGhlIGRhdGEgaXMgbG9hZGVkIGluIGFuZCByZWFkeSB0byBnbywgd2UgY2FuIHN0YXJ0IGJ5IGxvb2tpbmcgYXQgZGlmZmVyZW50IG1vZGVscyB1c2luZyBoMm8uICBXZSB3aWxsIGNvbnZlcnQgdG8gaDJvIG9iamVjdHMgYW5kIGJlZ2luIHJ1bm5pbmcgc29tZSBtb2RlbHMgYW5kIHVzZSB0aGUgY2FyZXQgcGFja2FnZSB0byBzZWUgaG93IHdlIGRvLg0KDQpgYGB7cn0NClRyYWluaW5nSDwtYXMuaDJvKFRyYWluaW5nLGRlc3RpbmF0aW9uPSJUcmFpbmluZ0giKQ0KVGVzdEg8LWFzLmgybyhUZXN0LGRlc3RpbmF0aW9uPSJUZXN0IikNCg0KDQoNCng8LWNvbG5hbWVzKFRyYWluaW5nSFssLTFdKQ0KDQptb2RlbDwtaDJvLmRlZXBsZWFybmluZyh4PXgseT0ibiIsdHJhaW5pbmdfZnJhbWUgPSBUcmFpbmluZ0gsdmFsaWRhdGlvbl9mcmFtZSA9IFRlc3RILGRpc3RyaWJ1dGlvbiA9ICJtdWx0aW5vbWlhbCIsYWN0aXZhdGlvbj0iUmVjdGlmaWVyV2l0aERyb3BvdXQiLGhpZGRlbiA9IGMoMiwyLDIpLGlucHV0X2Ryb3BvdXRfcmF0aW8gPSAuMixzcGFyc2U9VCxlcG9jaHM9MTApDQoNCnN1bW1hcnkobW9kZWwpDQoNCk1vZGVsUmVzdWx0PC1oMm8ucHJlZGljdChtb2RlbCxUZXN0SCkNCnJlc3VsdHM8LWFzLmRhdGEuZnJhbWUoTW9kZWxSZXN1bHRbLDFdKQ0KaGVhZChUZXN0SFssMV0pDQpoZWFkKHJlc3VsdHMpDQpjYXJldDo6Y29uZnVzaW9uTWF0cml4KHVubGlzdChyZXN1bHRzKSxUZXN0JG4pJG92ZXJhbGwNCmBgYA0KDQpXZSBkaWQgbm90IGRvIHZlcnkgd2VsbCBoZXJlLiAgVGhlcmUgYXBwZWFycyB0byBiZSBxdWl0ZSBhIGZldyA3J3MgaW4gb3VyIHJlc3VsdHMgc2V0LCB3aGljaCBpcyBub3QgYWN0dWFsbHkgdGhlIGNhc2UuICBMZXQncyB0cnkgYSBmZXcgdGhpbmdzIHRvIGNoYW5nZSBpdCB1cC4gIFdlIGhhZCBvcmlnaW5hbGx5IG9ubHkgaGFkIDIgbm9kZXMgZm9yIGVhY2ggb2YgdGhlIHRocmVlIGxheWVycyBhbmQgb25seSAxMCBlcG9jaHMuICBMZXQncyBpbmNyZWFzZSB0aGVzZSBudW1iZXJzIGFuZCBzZWUgaG93IHdlIGRvLiAgV2Ugd2lsbCBhbHNvIGV4cGlyaW1lbnQgd2l0aCBhZGRpbmYgNS1mb2xkIGRhdGEgdmFsaWRhdGlvbiBmb3IgYSB0aGlyZCBtb2RlbCBhbmQgaW4gYSA0dGggbW9kZWwgd2Ugd2lsbCByZWR1Y2UgdGhlIGlucHV0IGRyb3BvdXQgcmF0aW8sIHdoaWNoIGlzIGEgZmVhdHVyZSB0aGF0IGNvbnRyb2xzIHdoYXQgcmF0aW8gb2YgZmVhdHVyZXMgaXMgZHJvcHBlZCBmb3IgYSB0cmFpbmluZyByb3cuICBJIGZpZ3VyZSBpZiB3ZSBhcmUgYWRkaW5nIHZhbGlkYXRpb24sIHdlIGNvdWxkIGxvd2VyIHRoaXMgbnVtYmVyLg0KDQpgYGB7cn0NCm1vZGVsMjwtaDJvLmRlZXBsZWFybmluZyh4PXgseT0ibiIsdHJhaW5pbmdfZnJhbWUgPSBUcmFpbmluZ0gsdmFsaWRhdGlvbl9mcmFtZSA9IFRlc3RILGRpc3RyaWJ1dGlvbiA9ICJtdWx0aW5vbWlhbCIsYWN0aXZhdGlvbj0iUmVjdGlmaWVyV2l0aERyb3BvdXQiLGhpZGRlbiA9IGMoNTAsNTAsNTApLGlucHV0X2Ryb3BvdXRfcmF0aW8gPSAuMixzcGFyc2U9VCxlcG9jaHM9MTAwKQ0KDQptb2RlbDM8LWgyby5kZWVwbGVhcm5pbmcoeD14LHk9Im4iLHRyYWluaW5nX2ZyYW1lID0gVHJhaW5pbmdILHZhbGlkYXRpb25fZnJhbWUgPSBUZXN0SCxkaXN0cmlidXRpb24gPSAibXVsdGlub21pYWwiLGFjdGl2YXRpb249IlJlY3RpZmllcldpdGhEcm9wb3V0IixoaWRkZW4gPSBjKDMwLDMwLDMwKSxpbnB1dF9kcm9wb3V0X3JhdGlvID0gLjIsc3BhcnNlPVQsZXBvY2hzPTUwLG5mb2xkcz01KQ0KDQptb2RlbDQ8LWgyby5kZWVwbGVhcm5pbmcoeD14LHk9Im4iLHRyYWluaW5nX2ZyYW1lID0gVHJhaW5pbmdILHZhbGlkYXRpb25fZnJhbWUgPSBUZXN0SCxkaXN0cmlidXRpb24gPSAibXVsdGlub21pYWwiLGFjdGl2YXRpb249IlJlY3RpZmllcldpdGhEcm9wb3V0IixoaWRkZW4gPSBjKDMwLDMwLDMwKSxpbnB1dF9kcm9wb3V0X3JhdGlvID0gLjEsc3BhcnNlPVQsZXBvY2hzPTUwLG5mb2xkcz01KQ0KYGBgDQoNCldlIG1ha2UgYSBmZXcgbW9kZWxzLCBub3cgaXQgaXMgdGltZSB0byBwdXQgdGhlbSB0byB0aGUgdGVzdC4gIEl0IHdvdWxkIG5vdCBiZSBoYXJkIHRvIG5vdCBiZWF0IG91ciBwcmVkaWN0aW9ucyBlYXJsaWVyLCBidXQgbGV0J3Mgc2VlLiAgVGhlc2UgbW9kZWxzIHRha2UgYSBzaWduaWZpY2FudGx5IGxvbmdlciBhbW91bnQgb2YgdGltZSB0byBydW4uDQoNCiMjIyMjIE1vZGVsIDINCmBgYHtyfQ0KTW9kZWxSZXN1bHQyPC1oMm8ucHJlZGljdChtb2RlbDIsVGVzdEgpDQpyZXN1bHRzMjwtYXMuZGF0YS5mcmFtZShNb2RlbFJlc3VsdDJbLDFdKQ0KaGVhZChUZXN0SFssMV0pDQpoZWFkKHJlc3VsdHMyKQ0KY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeCh1bmxpc3QocmVzdWx0czIpLFRlc3Qkbikkb3ZlcmFsbA0KYGBgDQoNClZlcnkgaGlnaCBhY2N1cmFjeSBpbiBtb2RlbCAyIGFzIHdlIHdvdWxkIGV4cGVjdCB3aXRoIHN1Y2ggYSB3aWRlIGluY3JlYXNlIGluIHRoZSBudW1iZXIgb2Ygb3BlcmF0aW9ucyB0aGF0IGFyZSBwZXJmb3JtZWQuIA0KDQojIyMjIyBNb2RlbCAzDQpgYGB7cn0NCk1vZGVsUmVzdWx0MzwtaDJvLnByZWRpY3QobW9kZWwzLFRlc3RIKQ0KcmVzdWx0czM8LWFzLmRhdGEuZnJhbWUoTW9kZWxSZXN1bHQzWywxXSkNCmhlYWQoVGVzdEhbLDFdKQ0KaGVhZChyZXN1bHRzMykNCmNhcmV0Ojpjb25mdXNpb25NYXRyaXgodW5saXN0KHJlc3VsdHMzKSxUZXN0JG4pJG92ZXJhbGwNCg0KYGBgDQpOb3QgcXVpdGUgYXMgYWNjdXJhdGUsIHRoaXMgY291bGQgYmUgZHVlIHNpbXBseSB0byB0aGUgZmFjdCB0aGF0IHdlIHJlZHVjZWQgdGhlIGxheWVycy4gIExldCdzIHNlZSBpZiBvdXIgaW5wdXQgZHJvcG91dCByYXRpbyBjaGFuZ2UgaGFzIGFueSBpbXBhY3Qgb24gdGhlIGFjY3VyYWN5Lg0KDQojIyMjIyBNb2RlbCA0DQoNCmBgYHtyfQ0KTW9kZWxSZXN1bHQ0PC1oMm8ucHJlZGljdChtb2RlbDQsVGVzdEgpDQpyZXN1bHRzNDwtYXMuZGF0YS5mcmFtZShNb2RlbFJlc3VsdDRbLDFdKQ0KaGVhZChUZXN0SFssMV0pDQpoZWFkKHJlc3VsdHM0KQ0KY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeCh1bmxpc3QocmVzdWx0czQpLFRlc3Qkbikkb3ZlcmFsbA0KYGBgDQpNb2RlbCA0IHdhcyBhbiBpbXByb3ZlbWVudCBvbiBtb2RlbCAzLCBidXQgdGhlIHNlY29uZCBtb2RlbCB3aXRoIHRoZSBtb3N0IGxheWVycyBoYXMgcGVyZm9ybWVkIHRoZSBiZXN0IHNvIGZhciBpbiB0ZXJtcyBvZiBhY2N1cmFjeS4NCg0KVGhlIG9wdGlvbnMgYXBwZWFyIHRvIGJlIHZlcnkgdW5saW1pdGVkLiAgSWYgd2Ugd2VyZSBnb2luZyBmb3IgdGhlIGhpZ2hlc3QgYWNjdXJhY3kgYW5kIHRoZSBiZXN0IG1vZGVsLCB3ZSBjb3VsZCBtYWtlIG1hbnkgbW9kZWxzLCBhbmQgZG8gdGhpbmdzIHN1Y2ggYXMgZGV0ZXJtaW5lIHZhcmlhYmxlIGltcG9ydGFuY2UgaW4gb3JkZXIgdG8gZmluZCBtb3JlIGFuZCBtb3JlIGFjY3VyYXRlIG1vZGVscy4gIEZvciB0aGUgcHVycG9zZXMgb2YgdGhpcyBleGVyY2lzZSB0aG91Z2gsIEkgd291bGQgZmVlbCBjb21mb3J0YWJsZSB1c2luZyBvdXIgc2Vjb25kIG1vZGVsIHRvIG1ha2UgY2xhc3NpZmljYWl0b25zLiAgV2hpbGUgaXQgaXMgbm90IDEwMCUgcmVsaWFibGUsIGl0IGRpZCB2ZXJ5IHdlbGwgaW4gY2xhc3NpZnlpbmcgdGhlIGRpZ2l0cy4gIEl0IGlzIHZlcnkgb2J2aW91cyB0aGF0IHRoZSBzdHJlbmd0aCBpcyBncmVhdCB3aGVuIHRoZSBmaXJzdCBmZXcgZGlnaXRzIGNvbXBsZXRlbHkgbGluZSB1cC4=