library(tidyverse)
library(magrittr)

library(keras)

Intro to Convolutional Neural Networks and Computer Vision

On Cats, Dogs and Hotdogs

In this notebook you will learn about the different building blocks that form a convolutional neural net as well as how we can build one using Keras. CNNs are the kind of neural networks that really require computational resources and therefore, you should consider using Colab/Kaggle with GPU (or TPU support if you can figure it out) support to run that. If you run it on your own computer without a GPU things will take a lot of time…like a lot!

Getting the data

unz(temp,  exdir = "dataset")
Error in unz(temp, exdir = "dataset") : 
  unused argument (exdir = "dataset")
list.files(path = "dataset", include.dirs = TRUE) 
[1] "single_prediction" "test_set"          "training_set"     
list.files(path = "dataset/training_set") %>% head()
[1] "cats" "dogs"

The data is actually a folder with 3 folders inside it. A training_set, a test_set and another one for try-outs

In each the training and test_set folders we have 2 folders again. One for cats and one for dogs.

list.files(path = "dataset/training_set/cats") %>% head()
list.files(path = "dataset/training_set/dogs") %>% head()

Here some examples:

Now think, you are a computer and need to classify that. :-O

Preprocessing image data

  • Now the “data-engineering” part, which i find a bit tricky.
  • Our input are a bunch of jpeg images with cats and dogs.
  • Obviousely (I hope), we are not going to load the images into memory with pandas or something like that. Rather we are going to stream them during training one batch at a time.
  • However, we cannot just throw a jpeg at the network. That wouldn’t be nice. We need to transform the images to matrices on the fly.

Formatting & streaming the data

I first define a few other parameters in the beginning to make adapting as easy as possible.

# list of fruits to modle
class_list <- c('cats', 'dogs')

# number of output classes (i.e. fruits)
output_n <- length(class_list)

# image size to scale down to (original images are 100 x 100 px)
img_width <- 64
img_height <- 64
target_size <- c(img_width, img_height)

# RGB = 3 channels
channels <- 3

# path to image folders
train_files_path <- 'dataset/training_set'
test_iles_path <- 'dataset/test_set'

Image augmentation

Another thing that we also will do is “image augmentation”.

Image Augmentations techniques are methods of artificially increasing the variations of images in our data-set by using horizontal/vertical flips, rotations, variations in brightness of images, horizontal/vertical shifts etc.

You can read more on that and in general about generators here.

* The handy image_data_generator() and flow_images_from_directory() functions can be used to load images from a directory. * If you want to use data augmentation, you can directly define how and in what way you want to augment your images

# optional data augmentation
train_data_gen = image_data_generator(
  rescale = 1/255, #,
  shear_range = 0.2,
  zoom_range = 0.2,
  horizontal_flip = TRUE,
  #fill_mode = "nearest",
  #rotation_range = 40,
  #width_shift_range = 0.2,
  #height_shift_range = 0.2
)

# Validation data shouldn't be augmented! But it should also be scaled.
test_data_gen <- image_data_generator(
  rescale = 1/255
  )  

Now we load the images into memory and resize them.

# training images
train_image_array_gen <- flow_images_from_directory(train_files_path, 
                                          train_data_gen,
                                          target_size = target_size,
                                          class_mode = "binary",
                                          classes = class_list,
                                          batch_size = 32,
                                          seed = 1337)
Found 8000 images belonging to 2 classes.
# validation images
test_image_array_gen <- flow_images_from_directory(test_files_path, 
                                          test_data_gen,
                                          target_size = target_size,
                                          class_mode = "binary",
                                          classes = class_list,
                                          batch_size = 32,
                                          seed = 1337)
Found 2000 images belonging to 2 classes.
table(factor(train_image_array_gen$classes))

   0    1 
4000 4000 
  • We now want to save the class indicies in order to be able to match it with the predictions later
train_image_array_gen$class_indices
$cats
[1] 0

$dogs
[1] 1
classes_indices <- train_image_array_gen$class_indices

Defining the model

model <- keras_model_sequential() 

Model Architecture

Step 1: Adding a convolutional layer

# Step 1 - Convolution - This is new
model <- model %>%
  layer_conv_2d(filter = 32, 
                kernel_size = c(3,3), 
                padding = "same", 
                input_shape = c(img_width, img_height, channels),
                activation = 'relu') 
2020-11-19 10:10:25.008724: I tensorflow/core/platform/cpu_feature_guard.cc:143] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2020-11-19 10:10:25.026455: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x7f80c1477430 initialized for platform Host (this does not guarantee that XLA will be used). Devices:
2020-11-19 10:10:25.026471: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version
  • We use 32 different filters that will be built as 3x3 matrices. We also specify that our input shape is 64x64x3, meaning that we have 3 matrices (for RGB) of 64 pixels each side.

First, we should perhaps get an overall picture of how a CNN architecture looks.

alt text

Step 2: Add MAxPooling

model
Model
Model: "sequential"
_________________________________________________________________________________________________________________________________________________________________
Layer (type)                                                            Output Shape                                                    Param #                  
=================================================================================================================================================================
conv2d (Conv2D)                                                         (None, 64, 64, 32)                                              896                      
_________________________________________________________________________________________________________________________________________________________________
max_pooling2d (MaxPooling2D)                                            (None, 32, 32, 32)                                              0                        
=================================================================================================================================================================
Total params: 896
Trainable params: 896
Non-trainable params: 0
_________________________________________________________________________________________________________________________________________________________________
  • Adding that layer requires just to specify the size of the “pool” - and we are done.
  • Now, let’s check out something fun:

Repeat :)

  • We add to more layers of the same structure.
model %>%
  layer_conv_2d(filter = 32, kernel_size = c(3,3), padding = "same",  input_shape = c(img_width, img_height, channels), activation = 'relu') %>%
  layer_max_pooling_2d(pool_size = c(2,2)) 

Step 3: Flattening

# Step 3 - Flattening
model %>%
   layer_flatten()
  • This layer is easy: Take all pooled features and line them up in one long vector, then convatenate.

Step 4: Dense layer

  • Finally: We add a “regular” artificial neural net including a bit of dropout (not really needed but why not)
model %>%
  layer_dense(units = 128, activation = 'relu') %>%
  layer_dropout(rate = 0.2)

Output Layer

  • The final layer has a sigmoid activation function due to the binary classification problem.
model %>%
  layer_dense(units = 1, activation = 'sigmoid') 
  • Lets se what we finally got.
model %>% summary()
Model: "sequential"
_________________________________________________________________________________________________________________________________________________________________
Layer (type)                                                            Output Shape                                                    Param #                  
=================================================================================================================================================================
conv2d (Conv2D)                                                         (None, 64, 64, 32)                                              896                      
_________________________________________________________________________________________________________________________________________________________________
max_pooling2d (MaxPooling2D)                                            (None, 32, 32, 32)                                              0                        
_________________________________________________________________________________________________________________________________________________________________
conv2d_1 (Conv2D)                                                       (None, 32, 32, 32)                                              9248                     
_________________________________________________________________________________________________________________________________________________________________
max_pooling2d_1 (MaxPooling2D)                                          (None, 16, 16, 32)                                              0                        
_________________________________________________________________________________________________________________________________________________________________
flatten (Flatten)                                                       (None, 8192)                                                    0                        
_________________________________________________________________________________________________________________________________________________________________
dense (Dense)                                                           (None, 128)                                                     1048704                  
_________________________________________________________________________________________________________________________________________________________________
dropout (Dropout)                                                       (None, 128)                                                     0                        
_________________________________________________________________________________________________________________________________________________________________
dense_1 (Dense)                                                         (None, 1)                                                       129                      
=================================================================================================================================================================
Total params: 1,058,977
Trainable params: 1,058,977
Non-trainable params: 0
_________________________________________________________________________________________________________________________________________________________________
# deepviz::plot_model(model) # Visualize if you like

Compile

# compile
model %>% compile(
  loss = "binary_crossentropy",
  optimizer = 'adam',
  metrics = "accuracy"
)

Train the network

  • And now we can train the network
set.seed(1337)
# And now we can train the network
hist <- model %>% fit_generator(
  train_image_array_gen,
  steps_per_epoch = 800,
  epochs = 2, 
  validation_data = test_image_array_gen,
  validation_steps = 100,
  verbose = FALSE,
  )
hist %>% plot()

model %>% evaluate_generator(test_image_array_gen, steps = 500)
     loss  accuracy 
0.4832610 0.7717994 

Try out predicting new images

  • Here we have two tryout images…
list.files(path = "dataset/single_prediction") 
[1] "cat_or_dog_1.jpg" "cat_or_dog_2.jpg"
img <- image_load('dataset/single_prediction/cat_or_dog_1.jpg', target_size = target_size)
img %<>%
  image_to_array() %>% 
  array_reshape(c(1, dim(.)))
  
res <- predict(model, img)
ifelse(res == 0, 'cat', 'dog')
     [,1] 
[1,] "dog"

Lets plot the image and see if we where right

library(magick) # For reading and working with images
image_read('dataset/single_prediction/cat_or_dog_1.jpg') %>% as.raster() %>% plot()

Ok, now we can delete all the images again…

unlink("dataset", recursive = TRUE)

Endnotes

References

More info

You can find more info about:

Session info

sessionInfo()
LS0tCnRpdGxlOiAnTmV1cmFsIE5ldHdvcmtzIEFwcGxpY2F0aW9uOiBDb252b2x1dGlvbmFsIE5ldXJhbCBOZXR3b3JrcyBmb3IgSW1hZ2UgZGF0YSAoUiknCmF1dGhvcjogIkRhbmllbCBTLiBIYWluIChkc2hAYnVzaW5lc3MuYWF1LmRrKSIKZGF0ZTogIlVwZGF0ZWQgYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgIHRoZW1lOiBmbGF0bHkKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyMjIEdlbmVyaWMgcHJlYW1ibGUKcm0obGlzdD1scygpKQpTeXMuc2V0ZW52KExBTkcgPSAiZW4iKSAjIEZvciBlbmdsaXNoIGxhbmd1YWdlCm9wdGlvbnMoc2NpcGVuID0gNSkgIyBUbyBkZWFjdGl2YXRlIGFubm95aW5nIHNjaWVudGlmaWMgbnVtYmVyIG5vdGF0aW9uCgojIyMgS25pdHIgb3B0aW9ucwpsaWJyYXJ5KGtuaXRyKSAjIEZvciBkaXNwbGF5IG9mIHRoZSBtYXJrZG93bgprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZz1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgY29tbWVudD1GQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbj0iY2VudGVyIgogICAgICAgICAgICAgICAgICAgICApCmBgYAoKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShtYWdyaXR0cikKCmxpYnJhcnkoa2VyYXMpCmBgYAoKIyBJbnRybyB0byBDb252b2x1dGlvbmFsIE5ldXJhbCBOZXR3b3JrcyBhbmQgQ29tcHV0ZXIgVmlzaW9uCgojIyBPbiBDYXRzLCBEb2dzIGFuZCBIb3Rkb2dzCgpJbiB0aGlzIG5vdGVib29rIHlvdSB3aWxsIGxlYXJuIGFib3V0IHRoZSBkaWZmZXJlbnQgYnVpbGRpbmcgYmxvY2tzIHRoYXQgZm9ybSBhIGNvbnZvbHV0aW9uYWwgbmV1cmFsIG5ldCBhcyB3ZWxsIGFzIGhvdyB3ZSBjYW4gYnVpbGQgb25lIHVzaW5nIEtlcmFzLiBDTk5zIGFyZSB0aGUga2luZCBvZiBuZXVyYWwgbmV0d29ya3MgdGhhdCByZWFsbHkgcmVxdWlyZSBjb21wdXRhdGlvbmFsIHJlc291cmNlcyBhbmQgdGhlcmVmb3JlLCB5b3Ugc2hvdWxkIGNvbnNpZGVyIHVzaW5nIENvbGFiL0thZ2dsZSB3aXRoIEdQVSAob3IgVFBVIHN1cHBvcnQgaWYgeW91IGNhbiBmaWd1cmUgaXQgb3V0KSBzdXBwb3J0IHRvIHJ1biB0aGF0LiBJZiB5b3UgcnVuIGl0IG9uIHlvdXIgb3duIGNvbXB1dGVyIHdpdGhvdXQgYSBHUFUgdGhpbmdzIHdpbGwgdGFrZSBhIGxvdCBvZiB0aW1lLi4ubGlrZSBhIGxvdCEKCiMjIEdldHRpbmcgdGhlIGRhdGEKCmBgYHtyfQojIExldCdzIHN0YXJ0IGJ5IGRvd25sb2FkaW5nIGFuZCBleHBsb3JpbmcgdGhlIGRhdGEKdGVtcCA8LSB0ZW1wZmlsZSgpCmRvd25sb2FkLmZpbGUoJ2h0dHBzOi8vc3RvcmFnZS5nb29nbGVhcGlzLmNvbS9zZHMtZmlsZS10cmFuc2Zlci9kYXRhc2V0LnppcCcsdGVtcCkKdW56aXAodGVtcCkKdW5saW5rKHRlbXApCmBgYAoKYGBge3J9Cmxpc3QuZmlsZXMocGF0aCA9ICJkYXRhc2V0IiwgaW5jbHVkZS5kaXJzID0gVFJVRSkgCmBgYAoKYGBge3J9Cmxpc3QuZmlsZXMocGF0aCA9ICJkYXRhc2V0L3RyYWluaW5nX3NldCIpICU+JSBoZWFkKCkKYGBgCgpUaGUgZGF0YSBpcyBhY3R1YWxseSBhIGZvbGRlciB3aXRoIDMgZm9sZGVycyBpbnNpZGUgaXQuIEEgKnRyYWluaW5nX3NldCosIGEgKnRlc3Rfc2V0KiBhbmQgYW5vdGhlciBvbmUgZm9yIHRyeS1vdXRzCgpJbiBlYWNoIHRoZSB0cmFpbmluZyBhbmQgdGVzdF9zZXQgZm9sZGVycyB3ZSBoYXZlIDIgZm9sZGVycyBhZ2Fpbi4gT25lIGZvciBjYXRzIGFuZCBvbmUgZm9yIGRvZ3MuCgpgYGB7cn0KbGlzdC5maWxlcyhwYXRoID0gImRhdGFzZXQvdHJhaW5pbmdfc2V0L2NhdHMiKSAlPiUgaGVhZCgpCmxpc3QuZmlsZXMocGF0aCA9ICJkYXRhc2V0L3RyYWluaW5nX3NldC9kb2dzIikgJT4lIGhlYWQoKQpgYGAKCkhlcmUgc29tZSBleGFtcGxlczoKCiFbXShodHRwczovL3NvdXJjZS51bnNwbGFzaC5jb20vN0FJREU4UHJ2QTAvMjAweDMwMCkKIVtdKGh0dHBzOi8vc291cmNlLnVuc3BsYXNoLmNvbS9oN1ZCSlJCY2llTS8yMDB4MzAwKQoKTm93IHRoaW5rLCB5b3UgYXJlIGEgY29tcHV0ZXIgYW5kIG5lZWQgdG8gY2xhc3NpZnkgdGhhdC4gOi1PCgojIFByZXByb2Nlc3NpbmcgaW1hZ2UgZGF0YQoKKiBOb3cgdGhlICJkYXRhLWVuZ2luZWVyaW5nIiBwYXJ0LCB3aGljaCBpIGZpbmQgYSBiaXQgdHJpY2t5LgoqIE91ciBpbnB1dCBhcmUgYSBidW5jaCBvZiBqcGVnIGltYWdlcyB3aXRoIGNhdHMgYW5kIGRvZ3MuCiogT2J2aW91c2VseSAoSSBob3BlKSwgd2UgYXJlIG5vdCBnb2luZyB0byBsb2FkIHRoZSBpbWFnZXMgaW50byBtZW1vcnkgd2l0aCBwYW5kYXMgb3Igc29tZXRoaW5nIGxpa2UgdGhhdC4gUmF0aGVyIHdlIGFyZSBnb2luZyB0byBzdHJlYW0gdGhlbSBkdXJpbmcgdHJhaW5pbmcgb25lIGJhdGNoIGF0IGEgdGltZS4gCiogSG93ZXZlciwgd2UgY2Fubm90IGp1c3QgdGhyb3cgYSBqcGVnIGF0IHRoZSBuZXR3b3JrLiBUaGF0IHdvdWxkbid0IGJlIG5pY2UuIFdlIG5lZWQgdG8gdHJhbnNmb3JtIHRoZSBpbWFnZXMgdG8gbWF0cmljZXMgb24gdGhlIGZseS4gCgojIyBGb3JtYXR0aW5nICYgc3RyZWFtaW5nIHRoZSBkYXRhCgpJIGZpcnN0IGRlZmluZSBhIGZldyBvdGhlciBwYXJhbWV0ZXJzIGluIHRoZSBiZWdpbm5pbmcgdG8gbWFrZSBhZGFwdGluZyBhcyBlYXN5IGFzIHBvc3NpYmxlLgoKYGBge3J9CiMgbGlzdCBvZiBvdXRjb21lIGNsYXNzZXMKY2xhc3NfbGlzdCA8LSBjKCdjYXRzJywgJ2RvZ3MnKQoKIyBudW1iZXIgb2Ygb3V0cHV0IGNsYXNzZXMgKGkuZS4gZnJ1aXRzKQpvdXRwdXRfbiA8LSBsZW5ndGgoY2xhc3NfbGlzdCkKCiMgaW1hZ2Ugc2l6ZSB0byBzY2FsZSBkb3duIHRvIChvcmlnaW5hbCBpbWFnZXMgYXJlIDEwMCB4IDEwMCBweCkKaW1nX3dpZHRoIDwtIDY0CmltZ19oZWlnaHQgPC0gNjQKdGFyZ2V0X3NpemUgPC0gYyhpbWdfd2lkdGgsIGltZ19oZWlnaHQpCgojIFJHQiA9IDMgY2hhbm5lbHMKY2hhbm5lbHMgPC0gMwoKIyBwYXRoIHRvIGltYWdlIGZvbGRlcnMKdHJhaW5fZmlsZXNfcGF0aCA8LSAnZGF0YXNldC90cmFpbmluZ19zZXQnCnRlc3RfaWxlc19wYXRoIDwtICdkYXRhc2V0L3Rlc3Rfc2V0JwpgYGAKCgoKCgojIyBJbWFnZSBhdWdtZW50YXRpb24KCkFub3RoZXIgdGhpbmcgdGhhdCB3ZSBhbHNvIHdpbGwgZG8gaXMgImltYWdlIGF1Z21lbnRhdGlvbiIuIAoKPiBJbWFnZSBBdWdtZW50YXRpb25zIHRlY2huaXF1ZXMgYXJlIG1ldGhvZHMgb2YgYXJ0aWZpY2lhbGx5IGluY3JlYXNpbmcgdGhlIHZhcmlhdGlvbnMgb2YgaW1hZ2VzIGluIG91ciBkYXRhLXNldCBieSB1c2luZyBob3Jpem9udGFsL3ZlcnRpY2FsIGZsaXBzLCByb3RhdGlvbnMsIHZhcmlhdGlvbnMgaW4gYnJpZ2h0bmVzcyBvZiBpbWFnZXMsIGhvcml6b250YWwvdmVydGljYWwgc2hpZnRzIGV0Yy4KCllvdSBjYW4gcmVhZCBtb3JlIG9uIHRoYXQgYW5kIGluIGdlbmVyYWwgYWJvdXQgZ2VuZXJhdG9ycyBbaGVyZV0oaHR0cHM6Ly9tZWRpdW0uY29tL0BhcmluZGFtYmFpZHlhMTY4L2h0dHBzLW1lZGl1bS1jb20tYXJpbmRhbWJhaWR5YTE2OC11c2luZy1rZXJhcy1pbWFnZWRhdGFnZW5lcmF0b3ItYjk0YTg3Y2RlZmFkKS4KCiFbXShodHRwczovL2Nkbi1pbWFnZXMtMS5tZWRpdW0uY29tL21heC8xNjAwLzEqclpSWVdnMHZlNmJadjItY3RFdFZYZy5wbmcpCgohW10oaHR0cHM6Ly9jZG4taW1hZ2VzLTEubWVkaXVtLmNvbS9tYXgvMTYwMC8xKjBhTXAzVFczcnhDVUwxSkZtZUpqOVEucG5nKQoqIFRoZSBoYW5keSBgaW1hZ2VfZGF0YV9nZW5lcmF0b3IoKWAgYW5kIGBmbG93X2ltYWdlc19mcm9tX2RpcmVjdG9yeSgpYCBmdW5jdGlvbnMgY2FuIGJlIHVzZWQgdG8gbG9hZCBpbWFnZXMgZnJvbSBhIGRpcmVjdG9yeS4gCiogSWYgeW91IHdhbnQgdG8gdXNlIGRhdGEgYXVnbWVudGF0aW9uLCB5b3UgY2FuIGRpcmVjdGx5IGRlZmluZSBob3cgYW5kIGluIHdoYXQgd2F5IHlvdSB3YW50IHRvIGF1Z21lbnQgeW91ciBpbWFnZXMKCmBgYHtyfQojIG9wdGlvbmFsIGRhdGEgYXVnbWVudGF0aW9uCnRyYWluX2RhdGFfZ2VuID0gaW1hZ2VfZGF0YV9nZW5lcmF0b3IoCiAgcmVzY2FsZSA9IDEvMjU1LCAjLAogIHNoZWFyX3JhbmdlID0gMC4yLAogIHpvb21fcmFuZ2UgPSAwLjIsCiAgaG9yaXpvbnRhbF9mbGlwID0gVFJVRSwKICAjZmlsbF9tb2RlID0gIm5lYXJlc3QiLAogICNyb3RhdGlvbl9yYW5nZSA9IDQwLAogICN3aWR0aF9zaGlmdF9yYW5nZSA9IDAuMiwKICAjaGVpZ2h0X3NoaWZ0X3JhbmdlID0gMC4yCikKCiMgVmFsaWRhdGlvbiBkYXRhIHNob3VsZG4ndCBiZSBhdWdtZW50ZWQhIEJ1dCBpdCBzaG91bGQgYWxzbyBiZSBzY2FsZWQuCnRlc3RfZGF0YV9nZW4gPC0gaW1hZ2VfZGF0YV9nZW5lcmF0b3IoCiAgcmVzY2FsZSA9IDEvMjU1CiAgKSAgCmBgYAoKTm93IHdlIGxvYWQgdGhlIGltYWdlcyBpbnRvIG1lbW9yeSBhbmQgcmVzaXplIHRoZW0uCgpgYGB7cn0KIyB0cmFpbmluZyBpbWFnZXMKdHJhaW5faW1hZ2VfYXJyYXlfZ2VuIDwtIGZsb3dfaW1hZ2VzX2Zyb21fZGlyZWN0b3J5KHRyYWluX2ZpbGVzX3BhdGgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9kYXRhX2dlbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0X3NpemUgPSB0YXJnZXRfc2l6ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3NfbW9kZSA9ICJiaW5hcnkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzc2VzID0gY2xhc3NfbGlzdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hfc2l6ZSA9IDMyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMTMzNykKCiMgdmFsaWRhdGlvbiBpbWFnZXMKdGVzdF9pbWFnZV9hcnJheV9nZW4gPC0gZmxvd19pbWFnZXNfZnJvbV9kaXJlY3RvcnkodGVzdF9maWxlc19wYXRoLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9kYXRhX2dlbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0X3NpemUgPSB0YXJnZXRfc2l6ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3NfbW9kZSA9ICJiaW5hcnkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzc2VzID0gY2xhc3NfbGlzdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hfc2l6ZSA9IDMyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMTMzNykKYGBgCgpgYGB7cn0KdGFibGUoZmFjdG9yKHRyYWluX2ltYWdlX2FycmF5X2dlbiRjbGFzc2VzKSkKYGBgCgoqIFdlIG5vdyB3YW50IHRvIHNhdmUgdGhlIGNsYXNzIGluZGljaWVzIGluIG9yZGVyIHRvIGJlIGFibGUgdG8gbWF0Y2ggaXQgd2l0aCB0aGUgcHJlZGljdGlvbnMgbGF0ZXIKCmBgYHtyfQp0cmFpbl9pbWFnZV9hcnJheV9nZW4kY2xhc3NfaW5kaWNlcwpgYGAKCmBgYHtyfQpjbGFzc2VzX2luZGljZXMgPC0gdHJhaW5faW1hZ2VfYXJyYXlfZ2VuJGNsYXNzX2luZGljZXMKYGBgCgojIERlZmluaW5nIHRoZSBtb2RlbAoKYGBge3J9Cm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAKYGBgCgojIyBNb2RlbCBBcmNoaXRlY3R1cmUKCiMjIyBTdGVwIDE6IEFkZGluZyBhIGNvbnZvbHV0aW9uYWwgbGF5ZXIKYGBge3J9CiMgU3RlcCAxIC0gQ29udm9sdXRpb24gLSBUaGlzIGlzIG5ldwptb2RlbCAlPiUKICBsYXllcl9jb252XzJkKGZpbHRlciA9IDMyLCAKICAgICAgICAgICAgICAgIGtlcm5lbF9zaXplID0gYygzLDMpLCAKICAgICAgICAgICAgICAgIHBhZGRpbmcgPSAic2FtZSIsIAogICAgICAgICAgICAgICAgaW5wdXRfc2hhcGUgPSBjKGltZ193aWR0aCwgaW1nX2hlaWdodCwgY2hhbm5lbHMpLAogICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICdyZWx1JykgCmBgYAoKKiBXZSB1c2UgMzIgZGlmZmVyZW50IGZpbHRlcnMgdGhhdCB3aWxsIGJlIGJ1aWx0IGFzIDN4MyBtYXRyaWNlcy4gV2UgYWxzbyBzcGVjaWZ5IHRoYXQgb3VyIGlucHV0IHNoYXBlIGlzIDY0eDY0eDMsIG1lYW5pbmcgdGhhdCB3ZSBoYXZlIDMgbWF0cmljZXMgKGZvciBSR0IpIG9mIDY0IHBpeGVscyBlYWNoIHNpZGUuCgoKRmlyc3QsIHdlIHNob3VsZCBwZXJoYXBzIGdldCBhbiBvdmVyYWxsIHBpY3R1cmUgb2YgaG93IGEgQ05OIGFyY2hpdGVjdHVyZSBsb29rcy4KCiFbYWx0IHRleHRdKGh0dHBzOi8vY2RuLWltYWdlcy0xLm1lZGl1bS5jb20vbWF4LzIwMDAvMSp3NXBlQ0stQWVTSTlEMFBSVDhvaVp3LnBuZykKCiMjIyBTdGVwIDI6IEFkZCBNQXhQb29saW5nCgpgYGB7cn0KbW9kZWwgJT4lCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLDIpKSAKYGBgCgoqIEFkZGluZyB0aGF0IGxheWVyIHJlcXVpcmVzIGp1c3QgdG8gc3BlY2lmeSB0aGUgc2l6ZSBvZiB0aGUgInBvb2wiIC0gYW5kIHdlIGFyZSBkb25lLiAKKiBOb3csIGxldCdzIGNoZWNrIG91dCBbc29tZXRoaW5nIGZ1bjpdKGh0dHA6Ly9zY3MucnllcnNvbi5jYS9+YWhhcmxleS92aXMvY29udi8pCgojIyMgUmVwZWF0IDopCgoqIFdlIGFkZCB0byBtb3JlIGxheWVycyBvZiB0aGUgc2FtZSBzdHJ1Y3R1cmUuCgpgYGB7cn0KbW9kZWwgJT4lCiAgbGF5ZXJfY29udl8yZChmaWx0ZXIgPSAzMiwga2VybmVsX3NpemUgPSBjKDMsMyksIHBhZGRpbmcgPSAic2FtZSIsICBpbnB1dF9zaGFwZSA9IGMoaW1nX3dpZHRoLCBpbWdfaGVpZ2h0LCBjaGFubmVscyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JQogIGxheWVyX21heF9wb29saW5nXzJkKHBvb2xfc2l6ZSA9IGMoMiwyKSkgCmBgYAoKIyMjIFN0ZXAgMzogRmxhdHRlbmluZwoKYGBge3J9CiMgU3RlcCAzIC0gRmxhdHRlbmluZwptb2RlbCAlPiUKICAgbGF5ZXJfZmxhdHRlbigpCmBgYAoKKiBUaGlzIGxheWVyIGlzIGVhc3k6IFRha2UgYWxsIHBvb2xlZCBmZWF0dXJlcyBhbmQgbGluZSB0aGVtIHVwIGluIG9uZSBsb25nIHZlY3RvciwgdGhlbiBjb252YXRlbmF0ZS4KCiMjIyBTdGVwIDQ6IERlbnNlIGxheWVyCgoqIEZpbmFsbHk6IFdlIGFkZCBhICJyZWd1bGFyIiBhcnRpZmljaWFsIG5ldXJhbCBuZXQgaW5jbHVkaW5nIGEgYml0IG9mIGRyb3BvdXQgKG5vdCByZWFsbHkgbmVlZGVkIGJ1dCB3aHkgbm90KQoKYGBge3J9Cm1vZGVsICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gJ3JlbHUnKSAlPiUKICBsYXllcl9kcm9wb3V0KHJhdGUgPSAwLjIpCmBgYAoKCiMjIyBPdXRwdXQgTGF5ZXIKCiogVGhlIGZpbmFsIGxheWVyIGhhcyBhIHNpZ21vaWQgYWN0aXZhdGlvbiBmdW5jdGlvbiBkdWUgdG8gdGhlIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtLgoKYGBge3J9Cm1vZGVsICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9ICdzaWdtb2lkJykgCmBgYAoKKiBMZXRzIHNlIHdoYXQgd2UgZmluYWxseSBnb3QuCgpgYGB7cn0KbW9kZWwgJT4lIHN1bW1hcnkoKQpgYGAKCmBgYHtyfQojIGRlZXB2aXo6OnBsb3RfbW9kZWwobW9kZWwpICMgVmlzdWFsaXplIGlmIHlvdSBsaWtlCmBgYAoKIyMgQ29tcGlsZSAKCmBgYHtyfQojIGNvbXBpbGUKbW9kZWwgJT4lIGNvbXBpbGUoCiAgbG9zcyA9ICJiaW5hcnlfY3Jvc3NlbnRyb3B5IiwKICBvcHRpbWl6ZXIgPSAnYWRhbScsCiAgbWV0cmljcyA9ICJhY2N1cmFjeSIKKQpgYGAKCiMjIFRyYWluIHRoZSBuZXR3b3JrCgoqIEFuZCBub3cgd2UgY2FuIHRyYWluIHRoZSBuZXR3b3JrCgpgYGB7cn0Kc2V0LnNlZWQoMTMzNykKIyBBbmQgbm93IHdlIGNhbiB0cmFpbiB0aGUgbmV0d29yawpoaXN0IDwtIG1vZGVsICU+JSBmaXRfZ2VuZXJhdG9yKAogIHRyYWluX2ltYWdlX2FycmF5X2dlbiwKICBzdGVwc19wZXJfZXBvY2ggPSA4MDAsCiAgZXBvY2hzID0gMiwgCiAgdmFsaWRhdGlvbl9kYXRhID0gdGVzdF9pbWFnZV9hcnJheV9nZW4sCiAgdmFsaWRhdGlvbl9zdGVwcyA9IDEwMCwKICB2ZXJib3NlID0gRkFMU0UsCiAgKQpgYGAKCmBgYHtyfQpoaXN0ICU+JSBwbG90KCkKYGBgCgoKYGBge3J9Cm1vZGVsICU+JSBldmFsdWF0ZV9nZW5lcmF0b3IodGVzdF9pbWFnZV9hcnJheV9nZW4sIHN0ZXBzID0gNTAwKQpgYGAKCiMgVHJ5IG91dCBwcmVkaWN0aW5nIG5ldyBpbWFnZXMKCiogSGVyZSB3ZSBoYXZlIHR3byB0cnlvdXQgaW1hZ2VzLi4uCgpgYGB7cn0KbGlzdC5maWxlcyhwYXRoID0gImRhdGFzZXQvc2luZ2xlX3ByZWRpY3Rpb24iKSAKYGBgCgpgYGB7cn0KaW1nIDwtIGltYWdlX2xvYWQoJ2RhdGFzZXQvc2luZ2xlX3ByZWRpY3Rpb24vY2F0X29yX2RvZ18xLmpwZycsIHRhcmdldF9zaXplID0gdGFyZ2V0X3NpemUpCmBgYAoKCmBgYHtyfQppbWcgJTw+JQogIGltYWdlX3RvX2FycmF5KCkgJT4lIAogIGFycmF5X3Jlc2hhcGUoYygxLCBkaW0oLikpKQpgYGAKCmBgYHtyfQpyZXMgPC0gcHJlZGljdChtb2RlbCwgaW1nKQpgYGAKCmBgYHtyfQppZmVsc2UocmVzID09IDAsICdjYXQnLCAnZG9nJykKYGBgCgpMZXRzIHBsb3QgdGhlIGltYWdlIGFuZCBzZWUgaWYgd2Ugd2hlcmUgcmlnaHQKCmBgYHtyfQpsaWJyYXJ5KG1hZ2ljaykgIyBGb3IgcmVhZGluZyBhbmQgd29ya2luZyB3aXRoIGltYWdlcwppbWFnZV9yZWFkKCdkYXRhc2V0L3NpbmdsZV9wcmVkaWN0aW9uL2NhdF9vcl9kb2dfMS5qcGcnKSAlPiUgYXMucmFzdGVyKCkgJT4lIHBsb3QoKQpgYGAKCk9rLCBub3cgd2UgY2FuIGRlbGV0ZSBhbGwgdGhlIGltYWdlcyBhZ2Fpbi4uLgoKYGBge3J9CiMgdW5saW5rKCJkYXRhc2V0IiwgcmVjdXJzaXZlID0gVFJVRSkKYGBgCgoKIyBZb3VyIHR1cm46CgohW2FsdCB0ZXh0XShodHRwczovL2Nkbi1pbWFnZXMtMS5tZWRpdW0uY29tL21heC8yMDAwLzEqemxrY29aNlRmQW5udjc4ZU9sTG5wdy5qcGVnKQoKW0J1aWxkIGEgaG90ZG9nLW5vdC1ob3Rkb2cgY2xhc3NpZmllcl0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYW5zYmVja2VyL2hvdC1kb2ctbm90LWhvdC1kb2cpCgpZb3UgY2FuIGdldCB0aGUgZGF0YSBoZXJlOiBodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vc2RzLWZpbGUtdHJhbnNmZXIvaG90LWRvZy1ub3QtaG90LWRvZy56aXAgYSBiaXQgZmFzdGVyLi4uCgoKIyBFbmRub3RlcwoKIyMjIFJlZmVyZW5jZXMKCiMjIyBNb3JlIGluZm8KWW91IGNhbiBmaW5kIG1vcmUgaW5mbyBhYm91dDoKCiogYGtlcmFzYCBbaGVyZV0oaHR0cHM6Ly9rZXJhcy5yc3R1ZGlvLmNvbS8pCgojIyMgU2Vzc2lvbiBpbmZvCgpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAKCgoKCgo=