In this module, we are going to use a pretrained CNN model to perform image classification on our dogs vs. cats images.
Learning objectives:
- Why using pretrained models can be efficient and effective.
- How to perform feature extraction with pretrained models.
- How you can fine tune a pretrained model and run end-to-end.
Requirements
library(keras)
Data
We are working with the same dogs and cats images as before.
# define the directories:
if (stringr::str_detect(here::here(), "conf-2020-user")) {
image_dir <- "/home/conf-2020-user/data/dogs-vs-cats"
} else {
image_dir <- here::here("materials", "data", "dogs-vs-cats")
}
train_dir <- file.path(image_dir, "train")
valid_dir <- file.path(image_dir, "validation")
test_dir <- file.path(image_dir, "test")
Transfer Learning
There are two main ways we can apply a pretrained model to perform a CNN
- Feature extraction: Use the convolutional base to do feature engineering on our images and then feed into a new densely connected classifier.
- Most efficient
- Does not require GPUs
- Does not “personalize” feature extraction to the problem at hand
- Likely leaves room for improvement
- Fine tune a pretrained model and run end-to-end: Build a full sequential model with the convolutional base and a new densely connected classifier and train the entire model with some or all of the convolutional base layers frozen.
- Computationally demanding
- Often requires GPUs
- Tweaks pretrained convolution layers to extract problem-specific features
- Maximize performance
Transfer Learning: End-to-End
⚠️⚠️ ONLY RUN ON GPU!! ⚠️⚠️
The above approach performed pretty well. However, we can see that we are still overfitting, which may be reducing model performance. An alternative approach is to run a pretrained model from end-to-end. This approach is much slower and computationally intense; however, it offers greater flexibility in using and adjusting the pretrained model because it lets you:
- use data augmentation to decrease overfitting (and usually increase model performance).
- fine tune parts of the pretrained model.
The following approach simply plugs the pretrained convolution base into a sequential model but freezes the convolution base weights.
Combining a densely-connected neural network with the convolutional base
In this case we can literally plug in our conv_base
within our model architecture.
model <- keras_model_sequential() %>%
conv_base %>%
layer_flatten() %>%
layer_dense(units = 256, activation = "relu") %>%
layer_dense(units = 1, activation = "sigmoid")
model
Freezing layers
Before you compile and train the model, it’s important to freeze the convolutional base weights. This prevents the weights from being updated during training. If you don’t do this then the representations found in the pretrained model will be modified and, potentially, completely destroyed.
cat(length(model$trainable_weights), "trainable weight tensors before freezing.\n")
freeze_weights(conv_base)
cat(length(model$trainable_weights), "trainable weight tensors before freezing.\n")
Training the model end-to-end with a frozen convolutional base
The following trains the model end-to-end using all CNN logic that you have seen before:
- data augmentation
- image generator
- compile our model
- train our model
train_datagen = image_data_generator(
rescale = 1/255,
rotation_range = 40,
width_shift_range = 0.2,
height_shift_range = 0.2,
shear_range = 0.2,
zoom_range = 0.2,
horizontal_flip = TRUE,
fill_mode = "nearest"
)
test_datagen <- image_data_generator(rescale = 1/255)
train_generator <- flow_images_from_directory(
train_dir,
train_datagen,
target_size = c(150, 150),
batch_size = 20,
class_mode = "binary"
)
validation_generator <- flow_images_from_directory(
validation_dir,
test_datagen,
target_size = c(150, 150),
batch_size = 20,
class_mode = "binary"
)
model %>% compile(
loss = "binary_crossentropy",
optimizer = optimizer_rmsprop(lr = 1e-5),
metrics = c("accuracy")
)
history2 <- model %>% fit_generator(
train_generator,
steps_per_epoch = 100,
epochs = 30,
validation_data = validation_generator,
validation_steps = 50
)
plot(history2)
Transfer Learning: Fine Tune
Another widely used technique for using pretrained models, is to unfreeze a few of the convolutional base and allow those weights to be updated. Recall that the early layers in a CNN identify detailed edges and shapes. Later layers put these edges and shapes together to make higher order parts of the images we are trying to classify (i.e. cat ears, dog tails).
The more our images deviate from the images used to create the pretrained model, then the more likely you will want to retrain the last few layers, which will make the edge and shape features more relevant to your problem.
To fine-tune a pretrained model you:
- Add your custom network on top of an already-trained base network (executed in the
CNN-base-and-classifier
code chunk).
- Freeze the base network (executed in the
freeze-parameters
code chunk).
- Train the part you added (executed in the
train-end-to-end
code chunk).
- Unfreeze some layers in the base network.
- Jointly train both these layers and the part you added.
We already did steps 1-3. The following executes steps 4 and 5.
Unfreeze some layers in the base network
unfreeze_weights(conv_base, from = "block3_conv1")
Jointly train both these layers and the part you added
model %>% compile(
loss = "binary_crossentropy",
optimizer = optimizer_rmsprop(lr = 1e-5),
metrics = c("accuracy")
)
history2 <- model %>% fit_generator(
train_generator,
steps_per_epoch = 100,
epochs = 100,
validation_data = validation_generator,
validation_steps = 50
)
plot(history2)
LS0tCnRpdGxlOiAiQ29tcHV0ZXIgdmlzaW9uICYgQ05OczogVHJhbnNmZXIgTGVhcm5pbmciCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFKQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfYncoKSkKYGBgCgpJbiB0aGlzIG1vZHVsZSwgd2UgYXJlIGdvaW5nIHRvIHVzZSBhIF9fX3ByZXRyYWluZWRfX18gQ05OIG1vZGVsIHRvIHBlcmZvcm0KaW1hZ2UgY2xhc3NpZmljYXRpb24gb24gb3VyIGRvZ3MgdnMuIGNhdHMgaW1hZ2VzLiAKCkxlYXJuaW5nIG9iamVjdGl2ZXM6CgotIFdoeSB1c2luZyBwcmV0cmFpbmVkIG1vZGVscyBjYW4gYmUgZWZmaWNpZW50IGFuZCBlZmZlY3RpdmUuCi0gSG93IHRvIHBlcmZvcm0gZmVhdHVyZSBleHRyYWN0aW9uIHdpdGggcHJldHJhaW5lZCBtb2RlbHMuCi0gSG93IHlvdSBjYW4gZmluZSB0dW5lIGEgcHJldHJhaW5lZCBtb2RlbCBhbmQgcnVuIGVuZC10by1lbmQuCgojIFJlcXVpcmVtZW50cwoKYGBge3J9CmxpYnJhcnkoa2VyYXMpCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShnZ3Bsb3QyKQpgYGAKCiMgRGF0YQoKV2UgYXJlIHdvcmtpbmcgd2l0aCB0aGUgc2FtZSBkb2dzIGFuZCBjYXRzIGltYWdlcyBhcyBiZWZvcmUuCgpgYGB7ciBpbWFnZS1maWxlLXBhdGhzfQojIGRlZmluZSB0aGUgZGlyZWN0b3JpZXM6CmlmIChzdHJpbmdyOjpzdHJfZGV0ZWN0KGhlcmU6OmhlcmUoKSwgImNvbmYtMjAyMC11c2VyIikpIHsKICBpbWFnZV9kaXIgPC0gIi9ob21lL2NvbmYtMjAyMC11c2VyL2RhdGEvZG9ncy12cy1jYXRzIgp9IGVsc2UgewogIGltYWdlX2RpciA8LSBoZXJlOjpoZXJlKCJtYXRlcmlhbHMiLCAiZGF0YSIsICJkb2dzLXZzLWNhdHMiKQp9CnRyYWluX2RpciA8LSBmaWxlLnBhdGgoaW1hZ2VfZGlyLCAidHJhaW4iKQp2YWxpZF9kaXIgPC0gZmlsZS5wYXRoKGltYWdlX2RpciwgInZhbGlkYXRpb24iKQp0ZXN0X2RpciA8LSBmaWxlLnBhdGgoaW1hZ2VfZGlyLCAidGVzdCIpCmBgYAoKIyBUcmFuc2ZlciBMZWFybmluZwoKVGhlcmUgYXJlIF90d28gbWFpbl8gd2F5cyB3ZSBjYW4gYXBwbHkgYSBwcmV0cmFpbmVkIG1vZGVsIHRvIHBlcmZvcm0gYSBDTk4KCjEuIF9fRmVhdHVyZSBleHRyYWN0aW9uX186IFVzZSB0aGUgY29udm9sdXRpb25hbCBiYXNlIHRvIGRvIGZlYXR1cmUgZW5naW5lZXJpbmcKICAgb24gb3VyIGltYWdlcyBhbmQgdGhlbiBmZWVkIGludG8gYSBuZXcgZGVuc2VseSBjb25uZWN0ZWQgY2xhc3NpZmllci4KICAgLSBNb3N0IGVmZmljaWVudAogICAtIERvZXMgbm90IHJlcXVpcmUgR1BVcwogICAtIERvZXMgbm90ICJwZXJzb25hbGl6ZSIgZmVhdHVyZSBleHRyYWN0aW9uIHRvIHRoZSBwcm9ibGVtIGF0IGhhbmQKICAgLSBMaWtlbHkgbGVhdmVzIHJvb20gZm9yIGltcHJvdmVtZW50CiAgIAoyLiBfX0ZpbmUgdHVuZSBhIHByZXRyYWluZWQgbW9kZWwgYW5kIHJ1biBlbmQtdG8tZW5kX186IEJ1aWxkIGEgZnVsbCBzZXF1ZW50aWFsCiAgIG1vZGVsIHdpdGggdGhlIGNvbnZvbHV0aW9uYWwgYmFzZSBhbmQgYSBuZXcgZGVuc2VseSBjb25uZWN0ZWQgY2xhc3NpZmllciBhbmQKICAgdHJhaW4gdGhlIGVudGlyZSBtb2RlbCB3aXRoIHNvbWUgb3IgYWxsIG9mIHRoZSBjb252b2x1dGlvbmFsIGJhc2UgbGF5ZXJzCiAgIGZyb3plbi4KICAgLSBDb21wdXRhdGlvbmFsbHkgZGVtYW5kaW5nCiAgIC0gT2Z0ZW4gcmVxdWlyZXMgR1BVcwogICAtIFR3ZWFrcyBwcmV0cmFpbmVkIGNvbnZvbHV0aW9uIGxheWVycyB0byBleHRyYWN0IHByb2JsZW0tc3BlY2lmaWMgZmVhdHVyZXMKICAgLSBNYXhpbWl6ZSBwZXJmb3JtYW5jZQogICAKIVtdKGltYWdlcy9zd2FwcGluZ19mY19jbGFzc2lmaWVyLnBuZykKCiMgIFRyYW5zZmVyIExlYXJuaW5nOiBGZWF0dXJlIEV4dHJhY3Rpb24KCiMjIEdldCBvdXIgcHJldHJhaW5lZCBtb2RlbAoKVGhlcmUgYXJlIHNldmVyYWwgcHJldHJhaW5lZCBtb2RlbHMgYXZhaWxhYmxlIHZpYSBrZXJhczsgdGhleSBhbGwgc3RhcnQgd2l0aCAKYGFwcGxpY2F0aW9uX2AuIEhlcmUgd2UnbGwgdXNlIHRoZSBWR0cxNiBtb2RlbDsgaXQgaXMgaW50dWl0aXZlIHRvIHVuZGVyc3RhbmQgCnRoZSBtb2RlbCBzdHJ1Y3R1cmUsIGRvZXMgYSBnb29kIGpvYiB3aXRoIHRoaXMgdGFzaywgYW5kIGlzIGdvb2QgZm9yIHRlYWNoaW5nCnB1cnBvc2VzLgoKQmVzdCBwcmFjdGljZSBmb3IgcGlja2luZyBwcmV0cmFpbmVkIG1vZGVscwoKLSBTdGFydCB3aXRoIHNtYWxsZXIgbW9kZWxzIChpLmUuIFZHRzE2LCBWR0cxOSwgUmVzbmV0MzQpCi0gQ2hlY2sgb3V0IHRoZSBkYXRhIHRoZSBtb2RlbCB3YXMgYnVpbHQgb24gYW5kIHNlZSBob3cgaXQgYWxpZ25zIHRvIHlvdXIgZGF0YS4KICBNb3N0IGNvbW1vbiBpcyBbSW1hZ2VOZXRdKGh0dHA6Ly9pbWFnZS1uZXQub3JnL2Fib3V0LW92ZXJ2aWV3KQoKQXBwbHlpbmcgcHJldHJhaW5lZCBtb2RlbHMgdGhhdCBhcmUgYWxyZWFkeSBzdXBwbGllZCBieSBrZXJhcyBpcyBzaW1wbGU6CgotIGB3ZWlnaHRzYDogUmVwcmVzZW50cyB0aGUgd2VpZ2h0cyB0byB1c2UuIE1vc3QgcHJldHJhaW5lZCBtb2RlbHMgYXJlIGJ1aWx0IG9uIAogIGltYWdlbmV0IGFuZCB1c2luZyB0aGVzZSB3ZWlnaHRzIHRlbmRzIHRvIGRvIHdlbGwuCi0gYGluY2x1ZGVfdG9wYDogV2hldGhlciB0byBpbmNsdWRlIHRoZSBmdWxseS1jb25uZWN0ZWQgZGVuc2UgY2xhc3NpZmllci4gVHlwaWNhbGx5LCAKICB3ZSB3YW50IHRoZSBjbGFzc2lmaWVyIHRvIGJlIHNwZWNpZmljIHRvIG91ciBwcm9ibGVtLgotIGBpbnB1dF9zaGFwZWA6IFRoZSBzaGFwZSBvZiBvdXIgbnB1dHMgKDE1MHgxNTAgcGl4ZWwgaW1hZ2VzIHcvMyBjb2xvciBjaGFubmVscykuCgpfX1RpcF9fOiBDaGVjayBvdXQgdGhlIFt0Zmh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vdGZodWIpIHBhY2thZ2Ugd2hpY2gKbWFrZXMgaXQgZWFzeSB0byBpbnRlcmFjdCB3aXRoIFtUZW5zb3JGbG93IEh1Yl0oaHR0cHM6Ly93d3cudGVuc29yZmxvdy5vcmcvaHViKSwKYSBsaWJhcnJ5IGZvciBwdWJsaWNhdGlvbiwgZGlzY292ZXJ5LCBhbmQgY29uc3VtcHRpb24gb2YgcmVzdXNhYmxlIG1vZGVscy4KCmBgYHtyIHByZXRyYWluZWQtbW9kZWx9CmNvbnZfYmFzZSA8LSBhcHBsaWNhdGlvbl92Z2cxNigKICB3ZWlnaHRzID0gImltYWdlbmV0IiwKICBpbmNsdWRlX3RvcCA9IEZBTFNFLAogIGlucHV0X3NoYXBlID0gYygxNTAsIDE1MCwgMykKKQpgYGAKCmBgYHtyIHZnZzE2LW1vZGVsLXN0cnVjdHVyZX0Kc3VtbWFyeShjb252X2Jhc2UpCmBgYAoKIyMgRXh0cmFjdGluZyBmZWF0dXJlcyB1c2luZyB0aGUgcHJldHJhaW5lZCBjb252b2x1dGlvbmFsIGJhc2UKClRoaXMgc2VlbXMgYSBsaXR0bGUgZGF1bnRpbmcgdG8gdW5kZXJzdGFuZCBidXQgdGhpcyBpcyBqdXN0IGltcGxlbWVudGluZyBtb3JlIG9mIAphIG1hbnVhbCBhcHByb2FjaCB0byB3aGF0IHlvdSBhbHJlYWR5IGhhdmUgYmVlbiBkb2luZy4gSGVyZSB3ZSBjcmVhdGUgYSBmdW5jdGlvbiAKdGhhdCB3aWxsOgoKMS4gQ3JlYXRlIGFuIGVtcHR5IHRlbnNvciB0byBob2xkIG91ciB0cmFuc2Zvcm1lZCBmZWF0dXJlcyBhbmQgbGFiZWxzLgoyLiBDcmVhdGUgb3VyIGRhdGEgYmF0Y2ggaW1wb3J0aW5nIGdlbmVyYXRvci4gSGVyZSB3ZSB3aWxsIHVzZSBiYXRjaCBzaXplIG9mIDIwIAogICBzaW1wbHkgYmVjYXVzZSB3ZSBoYXZlIDIwMDAgdHJhaW5pbmcgaW1hZ2VzIGFuZCAxMDAwIHZhbGlkYXRpb24gaW1hZ2VzIGFuZCAKICAgYSBiYXRjaCBzaXplIG9mIDIwIGRpdmlkZXMgbmljZWx5IHRvIGJvdGggb2YgdGhlc2UgdmFsdWVzLgozLiBMb29wIHRocm91Z2ggb3VyIHRyYWluaW5nIGRhdGE6CiAgIGEuIGltcG9ydCBhIGJhdGNoIG9mIGltYWdlcwogICBiLiBhcHBseSB0aGUgcHJldHJhaW5lZCBiYXNlIHRvIG91ciBpbWFnZXMgdG8gb3V0cHV0IHRoZSBwcmVkaWN0ZWQgZmVhdHVyZXMKICAgYy4gYWRkIHRoZXNlIG5ldyBmZWF0dXJlcyBhbmQgdGhlIGxhYmVscyB0byBvdXIgdGVuc29yCiAgIGQuIGNvbnRpbnVlIHRvIGRvIHRoaXMgdW50aWwgb3VyIG51bWJlciBvZiBpdGVyYXRpb25zIHggYmF0Y2ggc2l6ZSBlcXVhbHMgb3IgCiAgICAgIGV4Y2VlZHMgb3VyIHRvdGFsIG51bWJlciBvZiBzYW1wbGVzLgoKYGBge3IgaW1hZ2UtZ2VuZXJhdG9yLWZlYXR1cmUtZXh0cmFjdGlvbn0KCmRhdGFnZW4gPC0gaW1hZ2VfZGF0YV9nZW5lcmF0b3IocmVzY2FsZSA9IDEvMjU1KQpiYXRjaF9zaXplIDwtIDIwCgpleHRyYWN0X2ZlYXR1cmVzIDwtIGZ1bmN0aW9uKGRpcmVjdG9yeSwgc2FtcGxlX2NvdW50KSB7CiAgCiAgZmVhdHVyZXMgPC0gYXJyYXkoMCwgZGltID0gYyhzYW1wbGVfY291bnQsIDQsIDQsIDUxMikpICAgIyBzdGVwIDEKICBsYWJlbHMgPC0gYXJyYXkoMCwgZGltID0gYyhzYW1wbGVfY291bnQpKSAgICAgICAgICAgICAgICAjIHN0ZXAgMQogIAogIGdlbmVyYXRvciA8LSBmbG93X2ltYWdlc19mcm9tX2RpcmVjdG9yeSggICAgICAgICAgICAgICAgICMgc3RlcCAyCiAgICBkaXJlY3RvcnkgPSBkaXJlY3RvcnksICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICBnZW5lcmF0b3IgPSBkYXRhZ2VuLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICB0YXJnZXRfc2l6ZSA9IGMoMTUwLCAxNTApLAogICAgYmF0Y2hfc2l6ZSA9IGJhdGNoX3NpemUsCiAgICBjbGFzc19tb2RlID0gImJpbmFyeSIKICApCiAgCiAgaSA8LSAwCiAgd2hpbGUgKFRSVUUpIHsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBzdGVwIDMKICAgIG1lc3NhZ2UoIlByb2Nlc3NpbmcgYmF0Y2ggIiwgaSArIDEsICIgb2YgIiwgY2VpbGluZyhzYW1wbGVfY291bnQgLyBiYXRjaF9zaXplKSkKICAgIGJhdGNoIDwtIGdlbmVyYXRvcl9uZXh0KGdlbmVyYXRvcikgICAgICAgICAgICAgICAgICAgICAjIHN0ZXAgM2EKICAgIGlucHV0c19iYXRjaCA8LSBiYXRjaFtbMV1dCiAgICBsYWJlbHNfYmF0Y2ggPC0gYmF0Y2hbWzJdXQogICAgZmVhdHVyZXNfYmF0Y2ggPC0gY29udl9iYXNlICU+JSBwcmVkaWN0KGlucHV0c19iYXRjaCkgICMgc3RlcCAzYgogICAgaW5kZXhfcmFuZ2UgPC0gKChpICogYmF0Y2hfc2l6ZSkgKyAxKTooKGkgKyAxKSAqIGJhdGNoX3NpemUpCiAgICBmZWF0dXJlc1tpbmRleF9yYW5nZSwsLF0gPC0gZmVhdHVyZXNfYmF0Y2ggICAgICAgICAgICAgIyBzdGVwIDNjCiAgICBsYWJlbHNbaW5kZXhfcmFuZ2VdIDwtIGxhYmVsc19iYXRjaCAgICAgICAgICAgICAgICAgICAgIyBzdGVwIDNjCiAgICBpIDwtIGkgKyAxCiAgICBpZiAoaSAqIGJhdGNoX3NpemUgPj0gc2FtcGxlX2NvdW50KSBicmVhayAgICAgICAgICAgICAgIyBzdGVwIDNkCiAgfQogIAogIGxpc3QoCiAgICBmZWF0dXJlcyA9IGZlYXR1cmVzLAogICAgbGFiZWxzID0gbGFiZWxzCiAgKSAKfQpgYGAKCiBMZXQncyBhcHBseSB0aGlzIGZ1bmN0aW9uIHRvIG91ciB0cmFpbmluZywgdmFsaWRhdGlvbiwgYW5kIHRlc3QgZGF0YQogCiAqKldpdGhvdXQgYSBHUFUgdGhpcyB3aWxsIHRha2UgYXBwcm94aW1hdGVseSA1IG1pbnV0ZXMgdG8gZXhlY3V0ZSoqCiAKYGBge3IgZXhlY3V0ZS1mZWF0dXJlLWV4dHJhY3Rpb259CnRyYWluIDwtIGV4dHJhY3RfZmVhdHVyZXModHJhaW5fZGlyLCAyMDAwKQp2YWxpZGF0aW9uIDwtIGV4dHJhY3RfZmVhdHVyZXModmFsaWRfZGlyLCAxMDAwKQp0ZXN0IDwtIGV4dHJhY3RfZmVhdHVyZXModGVzdF9kaXIsIDEwMDApCmBgYAoKIyMgUmVzaGFwZSBmZWF0dXJlcwoKVGhlIGV4dHJhY3RlZCBmZWF0dXJlcyB3aWxsIGJlIGEgNEQgdGVuc29yIChzYW1wbGVzLCA0LCA0LCA1MTIpLiAgV2UgY2FuIHNlZSB0aGlzIAppbiB0aGUgbGFzdCBsYXllciBvZiBvdXIgYGNvbnZfYmFzZWAgbW9kZWwgYWJvdmUgKGBibG9jazVfcG9vbCAoTWF4UG9vbGluZzJEKWApLiAKCkNvbnNlcXVlbnRseSwgd2UgbmVlZCB0byByZXNoYXBlIChmbGF0dGVuKSB0aGVzZSBpbnRvIGEgMkQgdGVuc29yIHRvIGZlZWQgaW50byAKYSBkZW5zZWx5IGNvbm5lY3RlZCBjbGFzc2lmaWVyLiBUaGlzIHJlc3VsdHMgaW4gYSAyRCB0ZW5zb3Igb2Ygc2l6ZSAKKHNhbXBsZXMsIDQgKiA0ICogNTEyID0gODE5MikuCgpgYGB7ciByZXNoYXBlLWZlYXR1cmVzfQpyZXNoYXBlX2ZlYXR1cmVzIDwtIGZ1bmN0aW9uKGZlYXR1cmVzKSB7CiAgYXJyYXlfcmVzaGFwZShmZWF0dXJlcywgZGltID0gYyhucm93KGZlYXR1cmVzKSwgNCAqIDQgKiA1MTIpKQp9Cgp0cmFpbiRmZWF0dXJlcyA8LSByZXNoYXBlX2ZlYXR1cmVzKHRyYWluJGZlYXR1cmVzKQp2YWxpZGF0aW9uJGZlYXR1cmVzIDwtIHJlc2hhcGVfZmVhdHVyZXModmFsaWRhdGlvbiRmZWF0dXJlcykKdGVzdCRmZWF0dXJlcyA8LSByZXNoYXBlX2ZlYXR1cmVzKHRlc3QkZmVhdHVyZXMpCgpkaW0odHJhaW4kZmVhdHVyZXMpCmBgYAoKIyMgRGVmaW5lIGRlbnNlbHkgY29ubmVjdGVkIGNsYXNzaWZpZXIKCldlJ3ZlIGV4dHJhY3RlZCBhbmQgZmxhdHRlbmVkIG91ciBmZWF0dXJlcyBmcm9tIHRoZSBjb252b2x1dGlvbiBsYXllcnMgc28gbm93CndlIG9ubHkgbmVlZCB0byBidWlsZCB0aGUgZGVuc2VseSBjb25uZWN0ZWQgY2xhc3NpZmllciBwb3J0aW9uIG9mIG91ciBtb2RlbC4KCmBgYHtyIG1vZGVsLWNsYXNzaWZpZXJ9Cm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDI1NiwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSA0ICogNCAqIDUxMikgJT4lCiAgbGF5ZXJfZHJvcG91dChyYXRlID0gMC41KSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEsIGFjdGl2YXRpb24gPSAic2lnbW9pZCIpCgpzdW1tYXJ5KG1vZGVsKQpgYGAKCk5vdyB3ZSBjYW4gY29tcGlsZSBhbmQgdHJhaW4gb3VyIG1vZGVsLiBUaGlzIHdpbGwgdHJhaW4gcXVpY2tseSwgdGFraW5nCmFwcHJveGltYXRlbHkgMSBtaW4gd2hlbiB0cmFpbmVkIG9uIHlvdXIgbG9jYWwgQ1BVLiBPdXIgdmFsaWRhdGlvbiBsb3NzIGFsc28KaW1wcm92ZXMgb3ZlciB0aGUgcHJldmlvdXMgQ05OIHdlIGJ1aWx0LgoKYGBge3IgdHJhaW4tbW9kZWx9Cm1vZGVsICU+JSBjb21waWxlKAogIG9wdGltaXplciA9IG9wdGltaXplcl9ybXNwcm9wKGxyID0gMC4wMDAxKSwKICBsb3NzID0gImJpbmFyeV9jcm9zc2VudHJvcHkiLAogIG1ldHJpY3MgPSBjKCJhY2N1cmFjeSIpCikKCmhpc3RvcnkxIDwtIG1vZGVsICU+JSBmaXQoCiAgdHJhaW4kZmVhdHVyZXMsIHRyYWluJGxhYmVscywKICBlcG9jaHMgPSAzMCwKICBiYXRjaF9zaXplID0gMzIsCiAgdmFsaWRhdGlvbl9kYXRhID0gbGlzdCh2YWxpZGF0aW9uJGZlYXR1cmVzLCB2YWxpZGF0aW9uJGxhYmVscyksCiAgY2FsbGJhY2tzID0gbGlzdCgKICAgIGNhbGxiYWNrX2Vhcmx5X3N0b3BwaW5nKHBhdGllbmNlID0gMTApLAogICAgY2FsbGJhY2tfcmVkdWNlX2xyX29uX3BsYXRlYXUocGF0aWVuY2UgPSAyKQogICkKKQpgYGAKClNvIHdlIGFjaGVpdmVkIGEgc2lnbmlmaWNhbnQgZGVjcmVhc2UgaW4gb3VyIGxvc3Mgc2NvcmUsIGluY3JlYXNlZCBvdXIgYWNjdXJhY3kgCnRvIDkwJSwgYW5kIGRpZCBzbyBpbiBhIGZyYWN0aW9uIG9mIHRoZSB0aW1lIQoKYGBge3Igc2Vjb25kLW1vZGVsLXJlc3VsdHN9CmJlc3RfZXBvY2ggPC0gd2hpY2gubWluKGhpc3RvcnkxJG1ldHJpY3MkdmFsX2xvc3MpCmJlc3RfbG9zcyA8LSBoaXN0b3J5MSRtZXRyaWNzJHZhbF9sb3NzW2Jlc3RfZXBvY2hdICU+JSByb3VuZCgzKQpiZXN0X2FjYyA8LSBoaXN0b3J5MSRtZXRyaWNzJHZhbF9hY2N1cmFjeVtiZXN0X2Vwb2NoXSAlPiUgcm91bmQoMykKCmdsdWUoIk91ciBvcHRpbWFsIGxvc3MgaXMge2Jlc3RfbG9zc30gd2l0aCBhbiBhY2N1cmFjeSBvZiB7YmVzdF9hY2N9IikKYGBgCgpgYGB7ciBwbG90LW1vZGVsMS1oaXN0b3J5fQpwbG90KGhpc3RvcnkxKSArIAogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIGxlbmd0aChoaXN0b3J5MSRtZXRyaWNzJHZhbF9sb3NzKSkpCmBgYAoKCiMgIFRyYW5zZmVyIExlYXJuaW5nOiBFbmQtdG8tRW5kCgrimqDvuI/imqDvuI8gT05MWSBSVU4gT04gR1BVISEg4pqg77iP4pqg77iPCgpUaGUgYWJvdmUgYXBwcm9hY2ggcGVyZm9ybWVkIHByZXR0eSB3ZWxsLiBIb3dldmVyLCB3ZSBjYW4gc2VlIHRoYXQgd2UgYXJlIHN0aWxsIApvdmVyZml0dGluZywgd2hpY2ggbWF5IGJlIHJlZHVjaW5nIG1vZGVsIHBlcmZvcm1hbmNlLiBBbiBhbHRlcm5hdGl2ZSBhcHByb2FjaCBpcyAKdG8gcnVuIGEgcHJldHJhaW5lZCBtb2RlbCBmcm9tIGVuZC10by1lbmQuIFRoaXMgYXBwcm9hY2ggaXMgbXVjaCBzbG93ZXIgYW5kIApjb21wdXRhdGlvbmFsbHkgaW50ZW5zZTsgaG93ZXZlciwgaXQgb2ZmZXJzIGdyZWF0ZXIgZmxleGliaWxpdHkgaW4gdXNpbmcgYW5kIAphZGp1c3RpbmcgdGhlIHByZXRyYWluZWQgbW9kZWwgYmVjYXVzZSBpdCBsZXRzIHlvdToKCjEuIHVzZSBkYXRhIGF1Z21lbnRhdGlvbiB0byBkZWNyZWFzZSBvdmVyZml0dGluZyAoYW5kIHVzdWFsbHkgaW5jcmVhc2UgbW9kZWwgCiAgIHBlcmZvcm1hbmNlKS4KMi4gZmluZSB0dW5lIHBhcnRzIG9mIHRoZSBwcmV0cmFpbmVkIG1vZGVsLgoKVGhlIGZvbGxvd2luZyBhcHByb2FjaCBzaW1wbHkgcGx1Z3MgdGhlIHByZXRyYWluZWQgY29udm9sdXRpb24gYmFzZSBpbnRvIGEgCnNlcXVlbnRpYWwgbW9kZWwgYnV0IF9fX2ZyZWV6ZXNfX18gdGhlIGNvbnZvbHV0aW9uIGJhc2Ugd2VpZ2h0cy4KCiMjIENvbWJpbmluZyBhIGRlbnNlbHktY29ubmVjdGVkIG5ldXJhbCBuZXR3b3JrIHdpdGggdGhlIGNvbnZvbHV0aW9uYWwgYmFzZQoKSW4gdGhpcyBjYXNlIHdlIGNhbiBsaXRlcmFsbHkgcGx1ZyBpbiBvdXIgYGNvbnZfYmFzZWAgd2l0aGluIG91ciBtb2RlbCAKYXJjaGl0ZWN0dXJlLiAKCmBgYHtyIENOTi1iYXNlLWFuZC1jbGFzc2lmaWVyfQoKbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQogIGNvbnZfYmFzZSAlPiUKICBsYXllcl9mbGF0dGVuKCkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAyNTYsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9ICJzaWdtb2lkIikKCm1vZGVsCmBgYAoKIyMgRnJlZXppbmcgbGF5ZXJzCgpCZWZvcmUgeW91IGNvbXBpbGUgYW5kIHRyYWluIHRoZSBtb2RlbCwgaXQncyBpbXBvcnRhbnQgdG8gZnJlZXplIHRoZSBjb252b2x1dGlvbmFsIApiYXNlIHdlaWdodHMuIFRoaXMgcHJldmVudHMgdGhlIHdlaWdodHMgZnJvbSBiZWluZyB1cGRhdGVkIGR1cmluZyB0cmFpbmluZy4gSWYgCnlvdSBkb24ndCBkbyB0aGlzIHRoZW4gdGhlIHJlcHJlc2VudGF0aW9ucyBmb3VuZCBpbiB0aGUgcHJldHJhaW5lZCBtb2RlbCB3aWxsIGJlIAptb2RpZmllZCBhbmQsIHBvdGVudGlhbGx5LCBjb21wbGV0ZWx5IGRlc3Ryb3llZC4KCmBgYHtyIGZyZWV6ZS1wYXJhbWV0ZXJzfQpjYXQobGVuZ3RoKG1vZGVsJHRyYWluYWJsZV93ZWlnaHRzKSwgInRyYWluYWJsZSB3ZWlnaHQgdGVuc29ycyBiZWZvcmUgZnJlZXppbmcuXG4iKQoKZnJlZXplX3dlaWdodHMoY29udl9iYXNlKQoKY2F0KGxlbmd0aChtb2RlbCR0cmFpbmFibGVfd2VpZ2h0cyksICJ0cmFpbmFibGUgd2VpZ2h0IHRlbnNvcnMgYmVmb3JlIGZyZWV6aW5nLlxuIikKYGBgCgojIyBUcmFpbmluZyB0aGUgbW9kZWwgZW5kLXRvLWVuZCB3aXRoIGEgZnJvemVuIGNvbnZvbHV0aW9uYWwgYmFzZQoKVGhlIGZvbGxvd2luZyB0cmFpbnMgdGhlIG1vZGVsIGVuZC10by1lbmQgdXNpbmcgYWxsIENOTiBsb2dpYyB0aGF0IHlvdSBoYXZlIHNlZW4gCmJlZm9yZToKCjEuIGRhdGEgYXVnbWVudGF0aW9uCjIuIGltYWdlIGdlbmVyYXRvcgozLiBjb21waWxlIG91ciBtb2RlbAo0LiB0cmFpbiBvdXIgbW9kZWwKCmBgYHtyIHRyYWluLWVuZC10by1lbmR9Cgp0cmFpbl9kYXRhZ2VuID0gaW1hZ2VfZGF0YV9nZW5lcmF0b3IoCiAgcmVzY2FsZSA9IDEvMjU1LAogIHJvdGF0aW9uX3JhbmdlID0gNDAsCiAgd2lkdGhfc2hpZnRfcmFuZ2UgPSAwLjIsCiAgaGVpZ2h0X3NoaWZ0X3JhbmdlID0gMC4yLAogIHNoZWFyX3JhbmdlID0gMC4yLAogIHpvb21fcmFuZ2UgPSAwLjIsCiAgaG9yaXpvbnRhbF9mbGlwID0gVFJVRSwKICBmaWxsX21vZGUgPSAibmVhcmVzdCIKKQoKdGVzdF9kYXRhZ2VuIDwtIGltYWdlX2RhdGFfZ2VuZXJhdG9yKHJlc2NhbGUgPSAxLzI1NSkKCnRyYWluX2dlbmVyYXRvciA8LSBmbG93X2ltYWdlc19mcm9tX2RpcmVjdG9yeSgKICB0cmFpbl9kaXIsCiAgdHJhaW5fZGF0YWdlbiwKICB0YXJnZXRfc2l6ZSA9IGMoMTUwLCAxNTApLAogIGJhdGNoX3NpemUgPSAyMCwKICBjbGFzc19tb2RlID0gImJpbmFyeSIKKQoKdmFsaWRhdGlvbl9nZW5lcmF0b3IgPC0gZmxvd19pbWFnZXNfZnJvbV9kaXJlY3RvcnkoCiAgdmFsaWRhdGlvbl9kaXIsCiAgdGVzdF9kYXRhZ2VuLAogIHRhcmdldF9zaXplID0gYygxNTAsIDE1MCksCiAgYmF0Y2hfc2l6ZSA9IDIwLAogIGNsYXNzX21vZGUgPSAiYmluYXJ5IgopCgptb2RlbCAlPiUgY29tcGlsZSgKICBsb3NzID0gImJpbmFyeV9jcm9zc2VudHJvcHkiLAogIG9wdGltaXplciA9IG9wdGltaXplcl9ybXNwcm9wKGxyID0gMWUtNSksCiAgbWV0cmljcyA9IGMoImFjY3VyYWN5IikKKQoKaGlzdG9yeTIgPC0gbW9kZWwgJT4lIGZpdF9nZW5lcmF0b3IoCiAgdHJhaW5fZ2VuZXJhdG9yLAogIHN0ZXBzX3Blcl9lcG9jaCA9IDEwMCwKICBlcG9jaHMgPSAzMCwKICB2YWxpZGF0aW9uX2RhdGEgPSB2YWxpZGF0aW9uX2dlbmVyYXRvciwKICB2YWxpZGF0aW9uX3N0ZXBzID0gNTAKKQoKYGBgCgpgYGB7ciBwbG90LW1vZGVsMi1oaXN0b3J5fQpwbG90KGhpc3RvcnkyKQpgYGAKCgojICBUcmFuc2ZlciBMZWFybmluZzogRmluZSBUdW5lCgpBbm90aGVyIHdpZGVseSB1c2VkIHRlY2huaXF1ZSBmb3IgdXNpbmcgcHJldHJhaW5lZCBtb2RlbHMsIGlzIHRvIHVuZnJlZXplIGEgZmV3IApvZiB0aGUgY29udm9sdXRpb25hbCBiYXNlIGFuZCBhbGxvdyB0aG9zZSB3ZWlnaHRzIHRvIGJlIHVwZGF0ZWQuIFJlY2FsbCB0aGF0IAp0aGUgZWFybHkgbGF5ZXJzIGluIGEgQ05OIGlkZW50aWZ5IGRldGFpbGVkIGVkZ2VzIGFuZCBzaGFwZXMuIExhdGVyIGxheWVycyBwdXQgCnRoZXNlIGVkZ2VzIGFuZCBzaGFwZXMgdG9nZXRoZXIgdG8gbWFrZSBoaWdoZXIgb3JkZXIgcGFydHMgb2YgdGhlIGltYWdlcyB3ZSBhcmUgCnRyeWluZyB0byBjbGFzc2lmeSAoaS5lLiBjYXQgZWFycywgZG9nIHRhaWxzKS4gCgpUaGUgbW9yZSBvdXIgaW1hZ2VzIGRldmlhdGUgZnJvbSB0aGUgaW1hZ2VzIHVzZWQgdG8gY3JlYXRlIHRoZSBwcmV0cmFpbmVkIG1vZGVsLCAKdGhlbiB0aGUgbW9yZSBsaWtlbHkgeW91IHdpbGwgd2FudCB0byByZXRyYWluIHRoZSBsYXN0IGZldyBsYXllcnMsIHdoaWNoIHdpbGwgCm1ha2UgdGhlIGVkZ2UgYW5kIHNoYXBlIGZlYXR1cmVzIG1vcmUgcmVsZXZhbnQgdG8geW91ciBwcm9ibGVtLgoKVG8gZmluZS10dW5lIGEgcHJldHJhaW5lZCBtb2RlbCB5b3U6CgoxLiBBZGQgeW91ciBjdXN0b20gbmV0d29yayBvbiB0b3Agb2YgYW4gYWxyZWFkeS10cmFpbmVkIGJhc2UgbmV0d29yayAoZXhlY3V0ZWQgCiAgIGluIHRoZSBgQ05OLWJhc2UtYW5kLWNsYXNzaWZpZXJgIGNvZGUgY2h1bmspLgoyLiBGcmVlemUgdGhlIGJhc2UgbmV0d29yayAoZXhlY3V0ZWQgaW4gdGhlIGBmcmVlemUtcGFyYW1ldGVyc2AgY29kZSBjaHVuaykuCjMuIFRyYWluIHRoZSBwYXJ0IHlvdSBhZGRlZCAoZXhlY3V0ZWQgaW4gdGhlIGB0cmFpbi1lbmQtdG8tZW5kYCBjb2RlIGNodW5rKS4KNC4gVW5mcmVlemUgc29tZSBsYXllcnMgaW4gdGhlIGJhc2UgbmV0d29yay4KNS4gSm9pbnRseSB0cmFpbiBib3RoIHRoZXNlIGxheWVycyBhbmQgdGhlIHBhcnQgeW91IGFkZGVkLgoKV2UgYWxyZWFkeSBkaWQgc3RlcHMgMS0zLiBUaGUgZm9sbG93aW5nIGV4ZWN1dGVzIHN0ZXBzIDQgYW5kIDUuCgojIyBVbmZyZWV6ZSBzb21lIGxheWVycyBpbiB0aGUgYmFzZSBuZXR3b3JrCgpgYGB7ciB1bmZyZWV6ZS1zb21lLWxheWVyc30KdW5mcmVlemVfd2VpZ2h0cyhjb252X2Jhc2UsIGZyb20gPSAiYmxvY2szX2NvbnYxIikKYGBgCgoKIyMgSm9pbnRseSB0cmFpbiBib3RoIHRoZXNlIGxheWVycyBhbmQgdGhlIHBhcnQgeW91IGFkZGVkCgpgYGB7ciBmaW5lLXR1bmUtbW9kZWx9Cm1vZGVsICU+JSBjb21waWxlKAogIGxvc3MgPSAiYmluYXJ5X2Nyb3NzZW50cm9weSIsCiAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAxZS01KSwKICBtZXRyaWNzID0gYygiYWNjdXJhY3kiKQopCgpoaXN0b3J5MiA8LSBtb2RlbCAlPiUgZml0X2dlbmVyYXRvcigKICB0cmFpbl9nZW5lcmF0b3IsCiAgc3RlcHNfcGVyX2Vwb2NoID0gMTAwLAogIGVwb2NocyA9IDEwMCwKICB2YWxpZGF0aW9uX2RhdGEgPSB2YWxpZGF0aW9uX2dlbmVyYXRvciwKICB2YWxpZGF0aW9uX3N0ZXBzID0gNTAKKQpgYGAKCmBgYHtyIHBsb3QtbW9kZWwzLWhpc3Rvcnl9CnBsb3QoaGlzdG9yeTIpCmBgYAoKIyBUYWtld2F5cwoKKiBQcmUtdHJhaW5lZCBtb2RlbHMgY2FuIGJlIGVmZmljaWVudCBhbmQgZWZmZWN0aXZlIGZvciBwcm9ibGVtcyB0aGF0IGFsaWduIHRvCmNvbW1vbiBjb21wdXRlciB2aXNpb24gKGFuZCBvdGhlcikgdGFza3MuCgoqIE1hbnkgcHJlLWV4aXN0aW5nIG1vZGVscyBleGlzdCBvbiBbVGVuc29yRmxvdyBIdWJdKGh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2h1YikKICBhbmQgYXJlIHdvcnRoIHJlc2VhcmNoaW5nLgoKKiBUaHJlZSBtYWluIGFwcHJvYWNoZXMgZXhpc3RzIGZvciB0cmFuc2ZlciBsZWFybmluZzsgdGhlIGRlY2lzaW9uIGRlcGVuZHMgb24KICB5b3VyIGRlc2lyZSB0byBtYXhpbWl6ZSBlZmZpY2llbmN5IChjb21wdXRlIHRpbWUpIHZzIGVmZmVjdGl2ZW5lc3MgKGFjY3VyYWN5KToKICAgLSBmZWF0dXJlIGV4dHJhY3Rpb24KICAgLSBlbmQtdG8tZW5kIHRyYWluaW5nIHdpdGggZXhpc3RpbmcgQ05OIHdlaWdodHMKICAgLSBlbmQtdG9lbmQgdHJhaW5pbmcgd2hpbGUgZmluZS10dW5pbmcgbGF0dGVyIENOTiBsYXllciB3ZWlnaHRzCgpb8J+PoF0oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8tY29uZi0yMDIwL2RsLWtlcmFzLXRmKQ==