This notebook contains the code samples found in Chapter 8, Section 5 of Deep Learning with R. Note that the original text features far more content, in particular further explanations and figures: in this notebook, you will only find source code and related comments.


A schematic GAN implementation

In this section, we’ll explain how to implement a GAN in Keras, in its barest form – because GANs are advanced, diving deeply into the technical details would be out of scope for this book. The specific implementation is a deep convolutional GAN (DCGAN): a GAN where the generator and discriminator are deep convnets. In particular, it uses a layer_conv_2d_transpose() for image upsampling in the generator.

We will train our GAN on images from CIFAR10, a dataset of 50,000 32x32 RGB images belong to 10 classes (5,000 images per class). To make things even easier, we will only use images belonging to the class “frog”.

Schematically, our GAN looks like this:

A bag of tricks

Training GANs and tuning GAN implementations is notoriously difficult. There are a number of known “tricks” that one should keep in mind. Like most things in deep learning, it is more alchemy than science: these tricks are really just heuristics, not theory-backed guidelines. They are backed by some level of intuitive understanding of the phenomenon at hand, and they are known to work well empirically, albeit not necessarily in every context.

Here are a few of the tricks that we leverage in our own implementation of a GAN generator and discriminator below. It is not an exhaustive list of GAN-related tricks; you will find many more across the GAN literature.

The generator

First, we develop a generator model, which turns a vector (from the latent space – during training it will sampled at random) into a candidate image. One of the many issues that commonly arise with GANs is that the generator gets stuck with generated images that look like noise. A possible solution is to use dropout on both the discriminator and generator.

library(keras)
latent_dim <- 32
height <- 32
width <- 32
channels <- 3
generator_input <- layer_input(shape = c(latent_dim))
generator_output <- generator_input %>% 
  
  # First, transform the input into a 16x16 128-channels feature map
  layer_dense(units = 128 * 16 * 16) %>%
  layer_activation_leaky_relu() %>% 
  layer_reshape(target_shape = c(16, 16, 128)) %>% 
  
  # Then, add a convolution layer
  layer_conv_2d(filters = 256, kernel_size = 5, 
                padding = "same") %>% 
  layer_activation_leaky_relu() %>% 
  
  # Upsample to 32x32
  layer_conv_2d_transpose(filters = 256, kernel_size = 4, 
                          strides = 2, padding = "same") %>% 
  layer_activation_leaky_relu() %>% 
  
  # Few more conv layers
  layer_conv_2d(filters = 256, kernel_size = 5, 
                padding = "same") %>% 
  layer_activation_leaky_relu() %>% 
  layer_conv_2d(filters = 256, kernel_size = 5, 
                padding = "same") %>% 
  layer_activation_leaky_relu() %>% 
  
  # Produce a 32x32 1-channel feature map
  layer_conv_2d(filters = channels, kernel_size = 7,
                activation = "tanh", padding = "same")
generator <- keras_model(generator_input, generator_output)
summary(generator)
____________________________________________________________________________________________________________________________________________________________________________
Layer (type)                                                                 Output Shape                                                         Param #                   
============================================================================================================================================================================
input_9 (InputLayer)                                                         (None, 32)                                                           0                         
____________________________________________________________________________________________________________________________________________________________________________
dense_64 (Dense)                                                             (None, 32768)                                                        1081344                   
____________________________________________________________________________________________________________________________________________________________________________
leaky_re_lu_6 (LeakyReLU)                                                    (None, 32768)                                                        0                         
____________________________________________________________________________________________________________________________________________________________________________
reshape_3 (Reshape)                                                          (None, 16, 16, 128)                                                  0                         
____________________________________________________________________________________________________________________________________________________________________________
conv2d_136 (Conv2D)                                                          (None, 16, 16, 256)                                                  819456                    
____________________________________________________________________________________________________________________________________________________________________________
leaky_re_lu_7 (LeakyReLU)                                                    (None, 16, 16, 256)                                                  0                         
____________________________________________________________________________________________________________________________________________________________________________
conv2d_transpose_3 (Conv2DTranspose)                                         (None, 32, 32, 256)                                                  1048832                   
____________________________________________________________________________________________________________________________________________________________________________
leaky_re_lu_8 (LeakyReLU)                                                    (None, 32, 32, 256)                                                  0                         
____________________________________________________________________________________________________________________________________________________________________________
conv2d_137 (Conv2D)                                                          (None, 32, 32, 256)                                                  1638656                   
____________________________________________________________________________________________________________________________________________________________________________
leaky_re_lu_9 (LeakyReLU)                                                    (None, 32, 32, 256)                                                  0                         
____________________________________________________________________________________________________________________________________________________________________________
conv2d_138 (Conv2D)                                                          (None, 32, 32, 256)                                                  1638656                   
____________________________________________________________________________________________________________________________________________________________________________
leaky_re_lu_10 (LeakyReLU)                                                   (None, 32, 32, 256)                                                  0                         
____________________________________________________________________________________________________________________________________________________________________________
conv2d_139 (Conv2D)                                                          (None, 32, 32, 3)                                                    37635                     
============================================================================================================================================================================
Total params: 6,264,579
Trainable params: 6,264,579
Non-trainable params: 0
____________________________________________________________________________________________________________________________________________________________________________

The discriminator

Then, we develop a discriminator model, that takes as input a candidate image (real or synthetic) and classifies it into one of two classes, either “generated image” or “real image that comes from the training set”.

discriminator_input <- layer_input(shape = c(height, width, channels))
discriminator_output <- discriminator_input %>% 
  layer_conv_2d(filters = 128, kernel_size = 3) %>% 
  layer_activation_leaky_relu() %>% 
  layer_conv_2d(filters = 128, kernel_size = 4, strides = 2) %>% 
  layer_activation_leaky_relu() %>% 
  layer_conv_2d(filters = 128, kernel_size = 4, strides = 2) %>% 
  layer_activation_leaky_relu() %>% 
  layer_conv_2d(filters = 128, kernel_size = 4, strides = 2) %>% 
  layer_activation_leaky_relu() %>% 
  layer_flatten() %>%
  # One dropout layer - important trick!
  layer_dropout(rate = 0.4) %>%  
  # Classification layer
  layer_dense(units = 1, activation = "sigmoid")
discriminator <- keras_model(discriminator_input, discriminator_output)
summary(discriminator)
____________________________________________________________________________________________________________________________________________________________________________
Layer (type)                                                                 Output Shape                                                         Param #                   
============================================================================================================================================================================
input_10 (InputLayer)                                                        (None, 32, 32, 3)                                                    0                         
____________________________________________________________________________________________________________________________________________________________________________
conv2d_140 (Conv2D)                                                          (None, 30, 30, 128)                                                  3584                      
____________________________________________________________________________________________________________________________________________________________________________
leaky_re_lu_11 (LeakyReLU)                                                   (None, 30, 30, 128)                                                  0                         
____________________________________________________________________________________________________________________________________________________________________________
conv2d_141 (Conv2D)                                                          (None, 14, 14, 128)                                                  262272                    
____________________________________________________________________________________________________________________________________________________________________________
leaky_re_lu_12 (LeakyReLU)                                                   (None, 14, 14, 128)                                                  0                         
____________________________________________________________________________________________________________________________________________________________________________
conv2d_142 (Conv2D)                                                          (None, 6, 6, 128)                                                    262272                    
____________________________________________________________________________________________________________________________________________________________________________
leaky_re_lu_13 (LeakyReLU)                                                   (None, 6, 6, 128)                                                    0                         
____________________________________________________________________________________________________________________________________________________________________________
conv2d_143 (Conv2D)                                                          (None, 2, 2, 128)                                                    262272                    
____________________________________________________________________________________________________________________________________________________________________________
leaky_re_lu_14 (LeakyReLU)                                                   (None, 2, 2, 128)                                                    0                         
____________________________________________________________________________________________________________________________________________________________________________
flatten_11 (Flatten)                                                         (None, 512)                                                          0                         
____________________________________________________________________________________________________________________________________________________________________________
dropout_5 (Dropout)                                                          (None, 512)                                                          0                         
____________________________________________________________________________________________________________________________________________________________________________
dense_65 (Dense)                                                             (None, 1)                                                            513                       
============================================================================================================================================================================
Total params: 790,913
Trainable params: 790,913
Non-trainable params: 0
____________________________________________________________________________________________________________________________________________________________________________
# To stabilize training, we use learning rate decay
# and gradient clipping (by value) in the optimizer.
discriminator_optimizer <- optimizer_rmsprop( 
  lr = 0.0008, 
  clipvalue = 1.0,
  decay = 1e-8
)
discriminator %>% compile(
  optimizer = discriminator_optimizer,
  loss = "binary_crossentropy"
)

The adversarial network

Finally, we setup the GAN, which chains the generator and the discriminator. This is the model that, when trained, will move the generator in a direction that improves its ability to fool the discriminator. This model turns latent space points into a classification decision, “fake” or “real”, and it is meant to be trained with labels that are always “these are real images”. So training gan will updates the weights of generator in a way that makes discriminator more likely to predict “real” when looking at fake images. Very importantly, we set the discriminator to be frozen during training (non-trainable): its weights will not be updated when training gan. If the discriminator weights could be updated during this process, then we would be training the discriminator to always predict “real”, which is not what we want!

# Set discriminator weights to non-trainable
# (will only apply to the `gan` model)
freeze_weights(discriminator) 
gan_input <- layer_input(shape = c(latent_dim))
gan_output <- discriminator(generator(gan_input))
gan <- keras_model(gan_input, gan_output)
gan_optimizer <- optimizer_rmsprop(
  lr = 0.0004, 
  clipvalue = 1.0, 
  decay = 1e-8
)
gan %>% compile(
  optimizer = gan_optimizer, 
  loss = "binary_crossentropy"
)

How to train your DCGAN

Now we can begin training. To recapitulate, this is what the training loop looks like schematically. For each epoch, we do the following:

Let’s implement it.

# Loads CIFAR10 data
cifar10 <- dataset_cifar10()
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIGdlbmVyYXRpdmUgYWR2ZXJzYXJpYWwgbmV0d29ya3MiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICB0aGVtZTogY2VydWxlYW4KICAgIGhpZ2hsaWdodDogdGV4dG1hdGUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQpgYGAKCioqKgoKVGhpcyBub3RlYm9vayBjb250YWlucyB0aGUgY29kZSBzYW1wbGVzIGZvdW5kIGluIENoYXB0ZXIgOCwgU2VjdGlvbiA1IG9mIFtEZWVwIExlYXJuaW5nIHdpdGggUl0oaHR0cHM6Ly93d3cubWFubmluZy5jb20vYm9va3MvZGVlcC1sZWFybmluZy13aXRoLXIpLiBOb3RlIHRoYXQgdGhlIG9yaWdpbmFsIHRleHQgZmVhdHVyZXMgZmFyIG1vcmUgY29udGVudCwgaW4gcGFydGljdWxhciBmdXJ0aGVyIGV4cGxhbmF0aW9ucyBhbmQgZmlndXJlczogaW4gdGhpcyBub3RlYm9vaywgeW91IHdpbGwgb25seSBmaW5kIHNvdXJjZSBjb2RlIGFuZCByZWxhdGVkIGNvbW1lbnRzLgoKKioqCgojIyBBIHNjaGVtYXRpYyBHQU4gaW1wbGVtZW50YXRpb24KCgpJbiB0aGlzIHNlY3Rpb24sIHdlJ2xsIGV4cGxhaW4gaG93IHRvIGltcGxlbWVudCBhIEdBTiBpbiBLZXJhcywgaW4gaXRzIGJhcmVzdCBmb3JtIC0tIGJlY2F1c2UgR0FOcyBhcmUgYWR2YW5jZWQsIGRpdmluZyBkZWVwbHkgaW50byB0aGUgdGVjaG5pY2FsIGRldGFpbHMgd291bGQgYmUgb3V0IG9mIHNjb3BlIGZvciB0aGlzIGJvb2suIFRoZSBzcGVjaWZpYyBpbXBsZW1lbnRhdGlvbiBpcyBhIF9kZWVwIGNvbnZvbHV0aW9uYWwgR0FOXyAoRENHQU4pOiBhIEdBTiB3aGVyZSB0aGUgZ2VuZXJhdG9yIGFuZCBkaXNjcmltaW5hdG9yIGFyZSBkZWVwIGNvbnZuZXRzLiBJbiBwYXJ0aWN1bGFyLCBpdCB1c2VzIGEgYGxheWVyX2NvbnZfMmRfdHJhbnNwb3NlKClgIGZvciBpbWFnZSB1cHNhbXBsaW5nIGluIHRoZSBnZW5lcmF0b3IuCgpXZSB3aWxsIHRyYWluIG91ciBHQU4gb24gaW1hZ2VzIGZyb20gQ0lGQVIxMCwgYSBkYXRhc2V0IG9mIDUwLDAwMCAzMngzMiBSR0IgaW1hZ2VzIGJlbG9uZyB0byAxMCBjbGFzc2VzICg1LDAwMCBpbWFnZXMgcGVyIGNsYXNzKS4gVG8gbWFrZSAKdGhpbmdzIGV2ZW4gZWFzaWVyLCB3ZSB3aWxsIG9ubHkgdXNlIGltYWdlcyBiZWxvbmdpbmcgdG8gdGhlIGNsYXNzICJmcm9nIi4KClNjaGVtYXRpY2FsbHksIG91ciBHQU4gbG9va3MgbGlrZSB0aGlzOgoKKiBBIGBnZW5lcmF0b3JgIG5ldHdvcmsgbWFwcyB2ZWN0b3JzIG9mIHNoYXBlIGAobGF0ZW50X2RpbSlgIHRvIGltYWdlcyBvZiBzaGFwZSBgKDMyLCAzMiwgMylgLgoqIEEgYGRpc2NyaW1pbmF0b3JgIG5ldHdvcmsgbWFwcyBpbWFnZXMgb2Ygc2hhcGUgKDMyLCAzMiwgMykgdG8gYSBiaW5hcnkgc2NvcmUgZXN0aW1hdGluZyB0aGUgcHJvYmFiaWxpdHkgdGhhdCB0aGUgaW1hZ2UgaXMgcmVhbC4KKiBBIGBnYW5gIG5ldHdvcmsgY2hhaW5zIHRoZSBnZW5lcmF0b3IgYW5kIHRoZSBkaXNjcmltaW5hdG9yIHRvZ2V0aGVyOiBgZ2FuKHgpIDwtIGRpc2NyaW1pbmF0b3IoZ2VuZXJhdG9yKHgpKWAuIFRodXMgdGhpcyBgZ2FuYCBuZXR3b3JrIG1hcHMgbGF0ZW50IHNwYWNlIHZlY3RvcnMgdG8gdGhlIGRpc2NyaW1pbmF0b3IncyBhc3Nlc3NtZW50IG9mIHRoZSByZWFsaXNtIG9mIHRoZXNlIGxhdGVudCB2ZWN0b3JzIGFzIGRlY29kZWQgYnkgdGhlIGdlbmVyYXRvci4KKiBXZSB0cmFpbiB0aGUgZGlzY3JpbWluYXRvciB1c2luZyBleGFtcGxlcyBvZiByZWFsIGFuZCBmYWtlIGltYWdlcyBhbG9uZyB3aXRoICJyZWFsIi8iZmFrZSIgbGFiZWxzLCBhcyB3ZSB3b3VsZCB0cmFpbiBhbnkgcmVndWxhciBpbWFnZSBjbGFzc2lmaWNhdGlvbiBtb2RlbC4KKiBUbyB0cmFpbiB0aGUgZ2VuZXJhdG9yLCB3ZSB1c2UgdGhlIGdyYWRpZW50cyBvZiB0aGUgZ2VuZXJhdG9yJ3Mgd2VpZ2h0cyB3aXRoIHJlZ2FyZCB0byB0aGUgbG9zcyBvZiB0aGUgYGdhbmAgbW9kZWwuIFRoaXMgbWVhbnMgdGhhdCwgYXQgZXZlcnkgc3RlcCwgd2UgbW92ZSB0aGUgd2VpZ2h0cyBvZiB0aGUgZ2VuZXJhdG9yIGluIGEgZGlyZWN0aW9uIHRoYXQgd2lsbCBtYWtlIHRoZSBkaXNjcmltaW5hdG9yIG1vcmUgbGlrZWx5IHRvIGNsYXNzaWZ5IGFzICJyZWFsIiB0aGUgaW1hZ2VzIGRlY29kZWQgYnkgdGhlIGdlbmVyYXRvci4gSS5lLiB3ZSB0cmFpbiB0aGUgZ2VuZXJhdG9yIHRvIGZvb2wgdGhlIGRpc2NyaW1pbmF0b3IuCgojIyBBIGJhZyBvZiB0cmlja3MKClRyYWluaW5nIEdBTnMgYW5kIHR1bmluZyBHQU4gaW1wbGVtZW50YXRpb25zIGlzIG5vdG9yaW91c2x5IGRpZmZpY3VsdC4gVGhlcmUgYXJlIGEgbnVtYmVyIG9mIGtub3duICJ0cmlja3MiIHRoYXQgb25lIHNob3VsZCBrZWVwIGluIG1pbmQuIExpa2UgbW9zdCB0aGluZ3MgaW4gZGVlcCBsZWFybmluZywgaXQgaXMgbW9yZSBhbGNoZW15IHRoYW4gc2NpZW5jZTogdGhlc2UgdHJpY2tzIGFyZSByZWFsbHkganVzdCBoZXVyaXN0aWNzLCBub3QgdGhlb3J5LWJhY2tlZCBndWlkZWxpbmVzLiBUaGV5IGFyZSBiYWNrZWQgYnkgc29tZSBsZXZlbCBvZiBpbnR1aXRpdmUgdW5kZXJzdGFuZGluZyBvZiB0aGUgcGhlbm9tZW5vbiBhdCBoYW5kLCBhbmQgdGhleSBhcmUga25vd24gdG8gd29yayB3ZWxsIGVtcGlyaWNhbGx5LCBhbGJlaXQgbm90IG5lY2Vzc2FyaWx5IGluIGV2ZXJ5IGNvbnRleHQuCgpIZXJlIGFyZSBhIGZldyBvZiB0aGUgdHJpY2tzIHRoYXQgd2UgbGV2ZXJhZ2UgaW4gb3VyIG93biBpbXBsZW1lbnRhdGlvbiBvZiBhIEdBTiBnZW5lcmF0b3IgYW5kIGRpc2NyaW1pbmF0b3IgYmVsb3cuIEl0IGlzIG5vdCBhbiBleGhhdXN0aXZlIGxpc3Qgb2YgR0FOLXJlbGF0ZWQgdHJpY2tzOyB5b3Ugd2lsbCBmaW5kIG1hbnkgbW9yZSBhY3Jvc3MgdGhlIEdBTiBsaXRlcmF0dXJlLgoKKiBXZSB1c2UgYHRhbmhgIGFzIHRoZSBsYXN0IGFjdGl2YXRpb24gaW4gdGhlIGdlbmVyYXRvciwgaW5zdGVhZCBvZiBgc2lnbW9pZGAsIHdoaWNoIGlzIG1vcmUgY29tbW9ubHkgZm91bmQgaW4gb3RoZXIgdHlwZXMgb2YgbW9kZWxzLgoqIFdlIHNhbXBsZSBwb2ludHMgZnJvbSB0aGUgbGF0ZW50IHNwYWNlIHVzaW5nIGEgX25vcm1hbCBkaXN0cmlidXRpb25fIChHYXVzc2lhbiBkaXN0cmlidXRpb24pLCBub3QgYSB1bmlmb3JtIGRpc3RyaWJ1dGlvbi4KKiBTdG9jaGFzdGljaXR5IGlzIGdvb2QgdG8gaW5kdWNlIHJvYnVzdG5lc3MuIEJlY2F1c2UgR0FOIHRyYWluaW5nIHJlc3VsdHMgaW4gYSBkeW5hbWljIGVxdWlsaWJyaXVtLCBHQU5zIGFyZSBsaWtlbHkgdG8gZ2V0IHN0dWNrIGluIGFsbCBzb3J0cyBvZiB3YXlzLiBJbnRyb2R1Y2luZyByYW5kb21uZXNzIGR1cmluZyB0cmFpbmluZyBoZWxwcyBwcmV2ZW50IHRoaXMuIFdlIGludHJvZHVjZSByYW5kb21uZXNzIGluIHR3byB3YXlzOiBieSB1c2luZyBkcm9wb3V0IGluIHRoZSBkaXNjcmltaW5hdG9yIGFuZCBieSBhZGRpbmcgcmFuZG9tIG5vaXNlIHRvIHRoZSBsYWJlbHMgZm9yIHRoZSBkaXNjcmltaW5hdG9yLgoqIFNwYXJzZSBncmFkaWVudHMgY2FuIGhpbmRlciBHQU4gdHJhaW5pbmcuIEluIGRlZXAgbGVhcm5pbmcsIHNwYXJzaXR5IGlzIG9mdGVuIGEgZGVzaXJhYmxlIHByb3BlcnR5LCBidXQgbm90IGluIEdBTnMuIFR3byB0aGluZ3MgY2FuIGluZHVjZSBncmFkaWVudCBzcGFyc2l0eTogbWF4IHBvb2xpbmcgb3BlcmF0aW9ucyBhbmQgUmVMVSBhY3RpdmF0aW9ucy4gSW5zdGVhZCBvZiBtYXggcG9vbGluZywgd2UgcmVjb21tZW5kIHVzaW5nIHN0cmlkZWQgY29udm9sdXRpb25zIGZvciBkb3duc2FtcGxpbmcsIGFuZCB3ZSByZWNvbW1lbmQgdXNpbmcgYSBgbGF5ZXJfYWN0aXZhdGlvbl9sZWFreV9yZWx1KClgIGluc3RlYWQgb2YgYSBSZUxVIGFjdGl2YXRpb24uIEl0J3Mgc2ltaWxhciB0byBSZUxVLCBidXQgaXQgcmVsYXhlcyBzcGFyc2l0eSBjb25zdHJhaW50cyBieSBhbGxvd2luZyBzbWFsbCBuZWdhdGl2ZSBhY3RpdmF0aW9uIHZhbHVlcy4KKiBJbiBnZW5lcmF0ZWQgaW1hZ2VzLCBpdCdzIGNvbW1vbiB0byBzZWUgY2hlY2tlcmJvYXJkIGFydGlmYWN0cyBjYXVzZWQgYnkgdW5lcXVhbCBjb3ZlcmFnZSBvZiB0aGUgcGl4ZWwgc3BhY2UgaW4gdGhlIGdlbmVyYXRvciAoc2VlIGZpZ3VyZSA4LjE3KS4gVG8gZml4IHRoaXMsIHdlIHVzZSBhIGtlcm5lbCBzaXplIHRoYXQgaXMgZGl2aXNpYmxlIGJ5IHRoZSBzdHJpZGUgc2l6ZSB3aGVuZXZlciB3ZSB1c2UgYSBzdHJpZGVkIGBsYXllcl9jb252XzJkX3RyYW5zcG9zZSgpYCBvciBgbGF5ZXJfY29udl8yZCgpYCBpbiBib3RoIHRoZSBnZW5lcmF0b3IgYW5kIHRoZSBkaXNjcmltaW5hdG9yLgoKIyMgVGhlIGdlbmVyYXRvcgoKRmlyc3QsIHdlIGRldmVsb3AgYSBgZ2VuZXJhdG9yYCBtb2RlbCwgd2hpY2ggdHVybnMgYSB2ZWN0b3IgKGZyb20gdGhlIGxhdGVudCBzcGFjZSAtLSBkdXJpbmcgdHJhaW5pbmcgaXQgd2lsbCBzYW1wbGVkIGF0IHJhbmRvbSkgaW50byBhIGNhbmRpZGF0ZSBpbWFnZS4gT25lIG9mIHRoZSBtYW55IGlzc3VlcyB0aGF0IGNvbW1vbmx5IGFyaXNlIHdpdGggR0FOcyBpcyB0aGF0IHRoZSBnZW5lcmF0b3IgZ2V0cyBzdHVjayB3aXRoIGdlbmVyYXRlZCBpbWFnZXMgdGhhdCBsb29rIGxpa2Ugbm9pc2UuIEEgcG9zc2libGUgc29sdXRpb24gaXMgdG8gdXNlIGRyb3BvdXQgb24gYm90aCB0aGUgZGlzY3JpbWluYXRvciBhbmQgZ2VuZXJhdG9yLgoKYGBge3J9CmxpYnJhcnkoa2VyYXMpCgpsYXRlbnRfZGltIDwtIDMyCmhlaWdodCA8LSAzMgp3aWR0aCA8LSAzMgpjaGFubmVscyA8LSAzCgpnZW5lcmF0b3JfaW5wdXQgPC0gbGF5ZXJfaW5wdXQoc2hhcGUgPSBjKGxhdGVudF9kaW0pKQoKZ2VuZXJhdG9yX291dHB1dCA8LSBnZW5lcmF0b3JfaW5wdXQgJT4lIAogIAogICMgRmlyc3QsIHRyYW5zZm9ybSB0aGUgaW5wdXQgaW50byBhIDE2eDE2IDEyOC1jaGFubmVscyBmZWF0dXJlIG1hcAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4ICogMTYgKiAxNikgJT4lCiAgbGF5ZXJfYWN0aXZhdGlvbl9sZWFreV9yZWx1KCkgJT4lIAogIGxheWVyX3Jlc2hhcGUodGFyZ2V0X3NoYXBlID0gYygxNiwgMTYsIDEyOCkpICU+JSAKICAKICAjIFRoZW4sIGFkZCBhIGNvbnZvbHV0aW9uIGxheWVyCiAgbGF5ZXJfY29udl8yZChmaWx0ZXJzID0gMjU2LCBrZXJuZWxfc2l6ZSA9IDUsIAogICAgICAgICAgICAgICAgcGFkZGluZyA9ICJzYW1lIikgJT4lIAogIGxheWVyX2FjdGl2YXRpb25fbGVha3lfcmVsdSgpICU+JSAKICAKICAjIFVwc2FtcGxlIHRvIDMyeDMyCiAgbGF5ZXJfY29udl8yZF90cmFuc3Bvc2UoZmlsdGVycyA9IDI1Niwga2VybmVsX3NpemUgPSA0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpZGVzID0gMiwgcGFkZGluZyA9ICJzYW1lIikgJT4lIAogIGxheWVyX2FjdGl2YXRpb25fbGVha3lfcmVsdSgpICU+JSAKICAKICAjIEZldyBtb3JlIGNvbnYgbGF5ZXJzCiAgbGF5ZXJfY29udl8yZChmaWx0ZXJzID0gMjU2LCBrZXJuZWxfc2l6ZSA9IDUsIAogICAgICAgICAgICAgICAgcGFkZGluZyA9ICJzYW1lIikgJT4lIAogIGxheWVyX2FjdGl2YXRpb25fbGVha3lfcmVsdSgpICU+JSAKICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSAyNTYsIGtlcm5lbF9zaXplID0gNSwgCiAgICAgICAgICAgICAgICBwYWRkaW5nID0gInNhbWUiKSAlPiUgCiAgbGF5ZXJfYWN0aXZhdGlvbl9sZWFreV9yZWx1KCkgJT4lIAogIAogICMgUHJvZHVjZSBhIDMyeDMyIDEtY2hhbm5lbCBmZWF0dXJlIG1hcAogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IGNoYW5uZWxzLCBrZXJuZWxfc2l6ZSA9IDcsCiAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gInRhbmgiLCBwYWRkaW5nID0gInNhbWUiKQoKZ2VuZXJhdG9yIDwtIGtlcmFzX21vZGVsKGdlbmVyYXRvcl9pbnB1dCwgZ2VuZXJhdG9yX291dHB1dCkKc3VtbWFyeShnZW5lcmF0b3IpCmBgYAoKIyMgVGhlIGRpc2NyaW1pbmF0b3IKCgpUaGVuLCB3ZSBkZXZlbG9wIGEgYGRpc2NyaW1pbmF0b3JgIG1vZGVsLCB0aGF0IHRha2VzIGFzIGlucHV0IGEgY2FuZGlkYXRlIGltYWdlIChyZWFsIG9yIHN5bnRoZXRpYykgYW5kIGNsYXNzaWZpZXMgaXQgaW50byBvbmUgb2YgdHdvIGNsYXNzZXMsIGVpdGhlciAiZ2VuZXJhdGVkIGltYWdlIiBvciAicmVhbCBpbWFnZSB0aGF0IGNvbWVzIGZyb20gdGhlIHRyYWluaW5nIHNldCIuCgpgYGB7cn0KZGlzY3JpbWluYXRvcl9pbnB1dCA8LSBsYXllcl9pbnB1dChzaGFwZSA9IGMoaGVpZ2h0LCB3aWR0aCwgY2hhbm5lbHMpKQoKZGlzY3JpbWluYXRvcl9vdXRwdXQgPC0gZGlzY3JpbWluYXRvcl9pbnB1dCAlPiUgCiAgbGF5ZXJfY29udl8yZChmaWx0ZXJzID0gMTI4LCBrZXJuZWxfc2l6ZSA9IDMpICU+JSAKICBsYXllcl9hY3RpdmF0aW9uX2xlYWt5X3JlbHUoKSAlPiUgCiAgbGF5ZXJfY29udl8yZChmaWx0ZXJzID0gMTI4LCBrZXJuZWxfc2l6ZSA9IDQsIHN0cmlkZXMgPSAyKSAlPiUgCiAgbGF5ZXJfYWN0aXZhdGlvbl9sZWFreV9yZWx1KCkgJT4lIAogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDEyOCwga2VybmVsX3NpemUgPSA0LCBzdHJpZGVzID0gMikgJT4lIAogIGxheWVyX2FjdGl2YXRpb25fbGVha3lfcmVsdSgpICU+JSAKICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSAxMjgsIGtlcm5lbF9zaXplID0gNCwgc3RyaWRlcyA9IDIpICU+JSAKICBsYXllcl9hY3RpdmF0aW9uX2xlYWt5X3JlbHUoKSAlPiUgCiAgbGF5ZXJfZmxhdHRlbigpICU+JQogICMgT25lIGRyb3BvdXQgbGF5ZXIgLSBpbXBvcnRhbnQgdHJpY2shCiAgbGF5ZXJfZHJvcG91dChyYXRlID0gMC40KSAlPiUgIAogICMgQ2xhc3NpZmljYXRpb24gbGF5ZXIKICBsYXllcl9kZW5zZSh1bml0cyA9IDEsIGFjdGl2YXRpb24gPSAic2lnbW9pZCIpCgpkaXNjcmltaW5hdG9yIDwtIGtlcmFzX21vZGVsKGRpc2NyaW1pbmF0b3JfaW5wdXQsIGRpc2NyaW1pbmF0b3Jfb3V0cHV0KQpzdW1tYXJ5KGRpc2NyaW1pbmF0b3IpCgojIFRvIHN0YWJpbGl6ZSB0cmFpbmluZywgd2UgdXNlIGxlYXJuaW5nIHJhdGUgZGVjYXkKIyBhbmQgZ3JhZGllbnQgY2xpcHBpbmcgKGJ5IHZhbHVlKSBpbiB0aGUgb3B0aW1pemVyLgpkaXNjcmltaW5hdG9yX29wdGltaXplciA8LSBvcHRpbWl6ZXJfcm1zcHJvcCggCiAgbHIgPSAwLjAwMDgsIAogIGNsaXB2YWx1ZSA9IDEuMCwKICBkZWNheSA9IDFlLTgKKQoKZGlzY3JpbWluYXRvciAlPiUgY29tcGlsZSgKICBvcHRpbWl6ZXIgPSBkaXNjcmltaW5hdG9yX29wdGltaXplciwKICBsb3NzID0gImJpbmFyeV9jcm9zc2VudHJvcHkiCikKYGBgCgojIyBUaGUgYWR2ZXJzYXJpYWwgbmV0d29yawoKRmluYWxseSwgd2Ugc2V0dXAgdGhlIEdBTiwgd2hpY2ggY2hhaW5zIHRoZSBnZW5lcmF0b3IgYW5kIHRoZSBkaXNjcmltaW5hdG9yLiBUaGlzIGlzIHRoZSBtb2RlbCB0aGF0LCB3aGVuIHRyYWluZWQsIHdpbGwgbW92ZSB0aGUgZ2VuZXJhdG9yIGluIGEgZGlyZWN0aW9uIHRoYXQgaW1wcm92ZXMgaXRzIGFiaWxpdHkgdG8gZm9vbCB0aGUgZGlzY3JpbWluYXRvci4gVGhpcyBtb2RlbCB0dXJucyBsYXRlbnQgc3BhY2UgcG9pbnRzIGludG8gYSBjbGFzc2lmaWNhdGlvbiBkZWNpc2lvbiwgImZha2UiIG9yICJyZWFsIiwgYW5kIGl0IGlzIG1lYW50IHRvIGJlIHRyYWluZWQgd2l0aCBsYWJlbHMgdGhhdCBhcmUgYWx3YXlzICJ0aGVzZSBhcmUgcmVhbCBpbWFnZXMiLiBTbyB0cmFpbmluZyBgZ2FuYCB3aWxsIHVwZGF0ZXMgdGhlIHdlaWdodHMgb2YgYGdlbmVyYXRvcmAgaW4gYSB3YXkgdGhhdCBtYWtlcyBgZGlzY3JpbWluYXRvcmAgbW9yZSBsaWtlbHkgdG8gcHJlZGljdCAicmVhbCIgd2hlbiBsb29raW5nIGF0IGZha2UgaW1hZ2VzLiBWZXJ5IGltcG9ydGFudGx5LCB3ZSBzZXQgdGhlIGRpc2NyaW1pbmF0b3IgdG8gYmUgZnJvemVuIGR1cmluZyB0cmFpbmluZyAobm9uLXRyYWluYWJsZSk6IGl0cyB3ZWlnaHRzIHdpbGwgbm90IGJlIHVwZGF0ZWQgd2hlbiB0cmFpbmluZyBgZ2FuYC4gSWYgdGhlIGRpc2NyaW1pbmF0b3Igd2VpZ2h0cyBjb3VsZCBiZSB1cGRhdGVkIGR1cmluZyB0aGlzIHByb2Nlc3MsIHRoZW4gd2Ugd291bGQgYmUgdHJhaW5pbmcgdGhlIGRpc2NyaW1pbmF0b3IgdG8gYWx3YXlzIHByZWRpY3QgInJlYWwiLCB3aGljaCBpcyBub3Qgd2hhdCB3ZSB3YW50IQoKYGBge3J9CiMgU2V0IGRpc2NyaW1pbmF0b3Igd2VpZ2h0cyB0byBub24tdHJhaW5hYmxlCiMgKHdpbGwgb25seSBhcHBseSB0byB0aGUgYGdhbmAgbW9kZWwpCmZyZWV6ZV93ZWlnaHRzKGRpc2NyaW1pbmF0b3IpIAoKZ2FuX2lucHV0IDwtIGxheWVyX2lucHV0KHNoYXBlID0gYyhsYXRlbnRfZGltKSkKZ2FuX291dHB1dCA8LSBkaXNjcmltaW5hdG9yKGdlbmVyYXRvcihnYW5faW5wdXQpKQpnYW4gPC0ga2VyYXNfbW9kZWwoZ2FuX2lucHV0LCBnYW5fb3V0cHV0KQoKZ2FuX29wdGltaXplciA8LSBvcHRpbWl6ZXJfcm1zcHJvcCgKICBsciA9IDAuMDAwNCwgCiAgY2xpcHZhbHVlID0gMS4wLCAKICBkZWNheSA9IDFlLTgKKQoKZ2FuICU+JSBjb21waWxlKAogIG9wdGltaXplciA9IGdhbl9vcHRpbWl6ZXIsIAogIGxvc3MgPSAiYmluYXJ5X2Nyb3NzZW50cm9weSIKKQpgYGAKCiMjIEhvdyB0byB0cmFpbiB5b3VyIERDR0FOCgpOb3cgd2UgY2FuIGJlZ2luIHRyYWluaW5nLiBUbyByZWNhcGl0dWxhdGUsIHRoaXMgaXMgd2hhdCB0aGUgdHJhaW5pbmcgbG9vcCBsb29rcyBsaWtlIHNjaGVtYXRpY2FsbHkuIEZvciBlYWNoIGVwb2NoLCB3ZSBkbyB0aGUgZm9sbG93aW5nOgoKKiBEcmF3IHJhbmRvbSBwb2ludHMgaW4gdGhlIGxhdGVudCBzcGFjZSAocmFuZG9tIG5vaXNlKS4KKiBHZW5lcmF0ZSBpbWFnZXMgd2l0aCBgZ2VuZXJhdG9yYCB1c2luZyB0aGlzIHJhbmRvbSBub2lzZS4KKiBNaXggdGhlIGdlbmVyYXRlZCBpbWFnZXMgd2l0aCByZWFsIG9uZXMuCiogVHJhaW4gYGRpc2NyaW1pbmF0b3JgIHVzaW5nIHRoZXNlIG1peGVkIGltYWdlcywgd2l0aCBjb3JyZXNwb25kaW5nIHRhcmdldHM6IGVpdGhlciAicmVhbCIgKGZvciB0aGUgcmVhbCBpbWFnZXMpIG9yICJmYWtlIiAoZm9yIHRoZSBnZW5lcmF0ZWQgaW1hZ2VzKS4KKiBEcmF3IG5ldyByYW5kb20gcG9pbnRzIGluIHRoZSBsYXRlbnQgc3BhY2UuCiogVHJhaW4gYGdhbmAgdXNpbmcgdGhlc2UgcmFuZG9tIHZlY3RvcnMsIHdpdGggdGFyZ2V0cyB0aGF0IGFsbCBzYXkgInRoZXNlIGFyZSByZWFsIGltYWdlcy4iIFRoaXMgdXBkYXRlcyB0aGUgd2VpZ2h0cyBvZiB0aGUgZ2VuZXJhdG9yIChvbmx5LCBiZWNhdXNlIHRoZSBkaXNjcmltaW5hdG9yIGlzIGZyb3plbiBpbnNpZGUgYGdhbmApIHRvIG1vdmUgdGhlbSB0b3dhcmQgZ2V0dGluZyB0aGUgZGlzY3JpbWluYXRvciB0byBwcmVkaWN0ICJ0aGVzZSBhcmUgcmVhbCBpbWFnZXMiIGZvciBnZW5lcmF0ZWQgaW1hZ2VzOiB0aGF0IGlzLCB0aGlzIHRyYWlucyB0aGUgZ2VuZXJhdG9yIHRvIGZvb2wgdGhlIGRpc2NyaW1pbmF0b3IuCgpMZXQncyBpbXBsZW1lbnQgaXQuCgpgYGB7ciwgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJ30KIyBMb2FkcyBDSUZBUjEwIGRhdGEKY2lmYXIxMCA8LSBkYXRhc2V0X2NpZmFyMTAoKQpjKGMoeF90cmFpbiwgeV90cmFpbiksIGMoeF90ZXN0LCB5X3Rlc3QpKSAlPC0lIGNpZmFyMTAKCiMgU2VsZWN0cyBmcm9nIGltYWdlcyAoY2xhc3MgNikKeF90cmFpbiA8LSB4X3RyYWluW2FzLmludGVnZXIoeV90cmFpbikgPT0gNiwsLF0gCiMgTm9ybWFsaXplcyBkYXRhCnhfdHJhaW4gPC0geF90cmFpbiAvIDI1NQoKaXRlcmF0aW9ucyA8LSAxMDAwMApiYXRjaF9zaXplIDwtIDIwCnNhdmVfZGlyIDwtICJnYW5faW1hZ2VzIgpkaXIuY3JlYXRlKHNhdmVfZGlyKQoKIyBTdGFydCB0aGUgdHJhaW5pbmcgbG9vcApzdGFydCA8LSAxCgpmb3IgKHN0ZXAgaW4gMTppdGVyYXRpb25zKSB7CiAgCiAgIyBTYW1wbGVzIHJhbmRvbSBwb2ludHMgaW4gdGhlIGxhdGVudCBzcGFjZQogIHJhbmRvbV9sYXRlbnRfdmVjdG9ycyA8LSBtYXRyaXgocm5vcm0oYmF0Y2hfc2l6ZSAqIGxhdGVudF9kaW0pLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3cgPSBiYXRjaF9zaXplLCBuY29sID0gbGF0ZW50X2RpbSkKICAKICAjIERlY29kZXMgdGhlbSB0byBmYWtlIGltYWdlcwogIGdlbmVyYXRlZF9pbWFnZXMgPC0gZ2VuZXJhdG9yICU+JSBwcmVkaWN0KHJhbmRvbV9sYXRlbnRfdmVjdG9ycykKICAKICAjIENvbWJpbmVzIHRoZW0gd2l0aCByZWFsIGltYWdlcwogIHN0b3AgPC0gc3RhcnQgKyBiYXRjaF9zaXplIC0gMSAKICByZWFsX2ltYWdlcyA8LSB4X3RyYWluW3N0YXJ0OnN0b3AsLCxdCiAgcm93cyA8LSBucm93KHJlYWxfaW1hZ2VzKQogIGNvbWJpbmVkX2ltYWdlcyA8LSBhcnJheSgwLCBkaW0gPSBjKHJvd3MgKiAyLCBkaW0ocmVhbF9pbWFnZXMpWy0xXSkpCiAgY29tYmluZWRfaW1hZ2VzWzE6cm93cywsLF0gPC0gZ2VuZXJhdGVkX2ltYWdlcwogIGNvbWJpbmVkX2ltYWdlc1socm93cysxKToocm93cyoyKSwsLF0gPC0gcmVhbF9pbWFnZXMKIAogICMgQXNzZW1ibGVzIGxhYmVscyBkaXNjcmltaW5hdGluZyByZWFsIGZyb20gZmFrZSBpbWFnZXMKICBsYWJlbHMgPC0gcmJpbmQobWF0cml4KDEsIG5yb3cgPSBiYXRjaF9zaXplLCBuY29sID0gMSksCiAgICAgICAgICAgICAgICAgIG1hdHJpeCgwLCBucm93ID0gYmF0Y2hfc2l6ZSwgbmNvbCA9IDEpKQogIAogICMgQWRkcyByYW5kb20gbm9pc2UgdG8gdGhlIGxhYmVscyAtLSBhbiBpbXBvcnRhbnQgdHJpY2shCiAgbGFiZWxzIDwtIGxhYmVscyArICgwLjUgKiBhcnJheShydW5pZihwcm9kKGRpbShsYWJlbHMpKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaW0gPSBkaW0obGFiZWxzKSkpCiAgCiAgIyBUcmFpbnMgdGhlIGRpc2NyaW1pbmF0b3IKICBkX2xvc3MgPC0gZGlzY3JpbWluYXRvciAlPiUgdHJhaW5fb25fYmF0Y2goY29tYmluZWRfaW1hZ2VzLCBsYWJlbHMpIAogIAogICMgU2FtcGxlcyByYW5kb20gcG9pbnRzIGluIHRoZSBsYXRlbnQgc3BhY2UKICByYW5kb21fbGF0ZW50X3ZlY3RvcnMgPC0gbWF0cml4KHJub3JtKGJhdGNoX3NpemUgKiBsYXRlbnRfZGltKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm93ID0gYmF0Y2hfc2l6ZSwgbmNvbCA9IGxhdGVudF9kaW0pCiAgCiAgIyBBc3NlbWJsZXMgbGFiZWxzIHRoYXQgc2F5ICJhbGwgcmVhbCBpbWFnZXMiCiAgbWlzbGVhZGluZ190YXJnZXRzIDwtIGFycmF5KDAsIGRpbSA9IGMoYmF0Y2hfc2l6ZSwgMSkpCiAgCiAgIyBUcmFpbnMgdGhlIGdlbmVyYXRvciAodmlhIHRoZSBnYW4gbW9kZWwsIHdoZXJlIHRoZSAKICAjIGRpc2NyaW1pbmF0b3Igd2VpZ2h0cyBhcmUgZnJvemVuKQogIGFfbG9zcyA8LSBnYW4gJT4lIHRyYWluX29uX2JhdGNoKCAKICAgIHJhbmRvbV9sYXRlbnRfdmVjdG9ycywgCiAgICBtaXNsZWFkaW5nX3RhcmdldHMKICApICAKICAKICBzdGFydCA8LSBzdGFydCArIGJhdGNoX3NpemUKICBpZiAoc3RhcnQgPiAobnJvdyh4X3RyYWluKSAtIGJhdGNoX3NpemUpKQogICAgc3RhcnQgPC0gMQogIAogICMgT2NjYXNpb25hbGx5IHNhdmVzIGltYWdlcwogIGlmIChzdGVwICUlIDEwMCA9PSAwKSB7IAogICAgCiAgICAjIFNhdmVzIG1vZGVsIHdlaWdodHMKICAgIHNhdmVfbW9kZWxfd2VpZ2h0c19oZGY1KGdhbiwgImdhbi5oNSIpCiAgICAKICAgICMgUHJpbnRzIG1ldHJpY3MKICAgIGNhdCgiZGlzY3JpbWluYXRvciBsb3NzOiIsIGRfbG9zcywgIlxuIikKICAgIGNhdCgiYWR2ZXJzYXJpYWwgbG9zczoiLCBhX2xvc3MsICJcbiIpICAKICAgIAogICAgIyBTYXZlcyBvbmUgZ2VuZXJhdGVkIGltYWdlCiAgICBpbWFnZV9hcnJheV9zYXZlKAogICAgICBnZW5lcmF0ZWRfaW1hZ2VzWzEsLCxdICogMjU1LCAKICAgICAgcGF0aCA9IGZpbGUucGF0aChzYXZlX2RpciwgcGFzdGUwKCJnZW5lcmF0ZWRfZnJvZyIsIHN0ZXAsICIucG5nIikpCiAgICApCiAgIAogICAgIyBTYXZlcyBvbmUgcmVhbCBpbWFnZSBmb3IgY29tcGFyaXNvbgogICAgaW1hZ2VfYXJyYXlfc2F2ZSgKICAgICAgcmVhbF9pbWFnZXNbMSwsLF0gKiAyNTUsIAogICAgICBwYXRoID0gZmlsZS5wYXRoKHNhdmVfZGlyLCBwYXN0ZTAoInJlYWxfZnJvZyIsIHN0ZXAsICIucG5nIikpCiAgICApCiAgfQp9CmBgYAoKCgo=