library(tidyverse)
library(magrittr)

Introduction to Keras

  • There are quite a bunch of deep learning frameworks around, from the older Caffee and Theano to Google’s Tensorflow and the newer Pytorch (which is increasingly trending in research).
  • However, during the rest of this course, 95% of our deep learning exercises will be done using Keras
  • Keras is a deep-learning framework that provides a convenient way to define and train almost any kind of deep-learning model. Keras was initially developed for researchers, with the aim of enabling fast experimentation.

It has the following advantages:

  • User-friendly API which makes it easy to quickly prototype deep learning models.
  • Built-in support for convolutional networks (for computer vision), recurrent networks (for sequence processing), and any combination of both.
  • Supports arbitrary network architectures: multi-input or multi-output models, layer sharing, model sharing, etc., is therefore appropriate for building essentially any deep learning model, from a memory network to a neural Turing machine.
  • Is capable of running on top of multiple back-ends including TensorFlow, CNTK, or Theano.
  • Allows the same code to run on CPU or on GPU, and has strong multi-GPU, distributed storage, and training support (Google cloud, Spark, HDF5…)
  • Can easily be integrated in AI products (Apple CoreML, TensorFlow Android runtime, R or Python webapp backend such as a Shiny or Flask app)

It is widely adapted in academia and industry (Google, Netflix, Uber, CERN, Yelp, Square etc.), and is also a popular framework on Kaggle, the machine-learning competition website, where almost every recent deep-learning competition has been won using Keras models. While Google’s TensorFlow is even more popular, keep in mind that Keras can use Tensorflow (and other popular DL frameworks) as backend, and allows less cumbersome and more high-level

  • So, after all, Keras represents a wonderful high-level starter, fast and easy implementable, and in most cases flexible enough to do whatever you feel like.

Sidenote: The weird name (Keras) means horn in Greek, and is a reference to ancient Greek literature. Eg., in Odyssey, supernatural dream spirits are divided between those who deceive men with false visions (arriving to Earth through a gate of ivory), and those who announce a future that will come to pass (arriving through a gate of horn). So, enough history lessons, let’s run our first deep learning model!

# Load our main tool
library(keras)

Our first deep learning model

Introduction

  • Well, its about time to get serious. We will dive straight in, and use a simple deep learning model on the classical Mnist dataset.
  • This is the original data used by Jan LeCun and his team to fit an ANN that identifies handwritten digits for the US postal service.
  • It consists of quite a bunch of samples of handwritten dicites together with their correct label. The wandwritten dicits here conveniently come as a 28x28 greyscale matrix, making them a good starter to warm up. Lets do that.

Load our data and get ready

# Load our data
mnist <- dataset_mnist()
mnist %>%
  glimpse()
List of 2
 $ train:List of 2
  ..$ x: int [1:60000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
  ..$ y: int [1:60000(1d)] 5 0 4 1 9 2 1 3 1 4 ...
 $ test :List of 2
  ..$ x: int [1:10000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
  ..$ y: int [1:10000(1d)] 7 2 1 0 4 1 4 9 5 9 ...
# sepperate in train and test
train_images <- mnist$train$x
train_labels <- mnist$train$y
test_images <- mnist$test$x
test_labels <- mnist$test$y
  • Lets take a look at the structure.
glimpse(train_images)
 int [1:60000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
glimpse(train_labels)
 int [1:60000(1d)] 5 0 4 1 9 2 1 3 1 4 ...
digit <- train_images[5,,]
digit[,8:20] # I crop it a bit, otherwise the columns dont fit on one page
      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13]
 [1,]    0    0    0    0    0    0    0    0    0     0     0     0     0
 [2,]    0    0    0    0    0    0    0    0    0     0     0     0     0
 [3,]    0    0    0    0    0    0    0    0    0     0     0     0     0
 [4,]    0    0    0    0    0    0    0    0    0     0     0     0     0
 [5,]    0    0    0    0    0    0    0    0    0     0     0     0     0
 [6,]    0    0    0    0    0    0    0    0    0     0     0     0     0
 [7,]    0    0    0    0    0    0    0    0    0     0     0     0     0
 [8,]    0    0    0    0    0   55  148  210  253   253   113    87   148
 [9,]    0    0    0    0   87  232  252  253  189   210   252   252   253
[10,]    0    0    4   57  242  252  190   65    5    12   182   252   253
[11,]    0    0   96  252  252  183   14    0    0    92   252   252   225
[12,]    0  132  253  252  146   14    0    0    0   215   252   252    79
[13,]  126  253  247  176    9    0    0    8   78   245   253   129     0
[14,]  232  252  176    0    0    0   36  201  252   252   169    11     0
[15,]  252  252   30   22  119  197  241  253  252   251    77     0     0
[16,]  231  252  253  252  252  252  226  227  252   231     0     0     0
[17,]   55  235  253  217  138   42   24  192  252   143     0     0     0
[18,]    0    0    0    0    0    0   62  255  253   109     0     0     0
[19,]    0    0    0    0    0    0   71  253  252    21     0     0     0
[20,]    0    0    0    0    0    0    0  253  252    21     0     0     0
[21,]    0    0    0    0    0    0   71  253  252    21     0     0     0
[22,]    0    0    0    0    0    0  106  253  252    21     0     0     0
[23,]    0    0    0    0    0    0   45  255  253    21     0     0     0
[24,]    0    0    0    0    0    0    0  218  252    56     0     0     0
[25,]    0    0    0    0    0    0    0   96  252   189    42     0     0
[26,]    0    0    0    0    0    0    0   14  184   252   170    11     0
[27,]    0    0    0    0    0    0    0    0   14   147   252    42     0
[28,]    0    0    0    0    0    0    0    0    0     0     0     0     0

To make it more tangible, lets plot one:

digit %>% as.raster(max = 255) %>% plot()

rm(digits)

Define the Keras model

The workflow will be as follows:

  1. First, we’ll feed the neural network the training data, train_images and train_labels.
  2. The network will then learn to associate images and labels.
  3. Finally, we’ll ask the network to produce predictions for test_images, and we’ll verify whether these predictions match the labels from test_labels.

Let’s build the network - again, remember that you aren’t expected to understand everything about this example yet.

Building a model in Keras that can be fitted on your data involves two steps:

  1. Defining the networks architecture interms of layers and their shape.
  2. Compiling the model, and defining the loss function, evaluation metric, and optimizer.
network <- keras_model_sequential() %>% 
  layer_dense(units = 512, activation = "relu", input_shape = c(28 * 28)) %>%
  layer_dense(units = 10, activation = "softmax")

Notice that the layer stacking in R is done via the well-known %>%, in Pyhton with .. That’s about the main difference between both implementations.

  • The core building block of neural networks is the layer, a data-processing module that you can think of as a filter for data. Some data goes in, and it comes out in a more useful form.

  • Specifically, layers extract representations out of the data fed into them - hopefully, representations that are more meaningful for the problem at hand.

  • Most of deep learning consists of chaining together simple layers that will implement a form of progressive data distillation.

  • Here, our network consists of a sequence of two layers, which are densely connected (layer_dense) neural layers.

  • The second (and last) layer is a 10-way softmax layer, which means it will return an array of 10 probability scores (summing to 1).

  • Each score will be the probability that the current digit image belongs to one of our 10 digit classes. So, we defined a network with overall 634 cells, consisting of:

    1. input layer: 28x28 = 512 cells
    2. intermediate layer : 28x28 = 512 cells
    3. Output layer: 10 cells
  • To make the network ready for training, we need to pick three more things, as part of the compilation step:

    1. Loss function: How the network will be able to measure its performance on the training data, and thus how it will be able to steer itself in the right direction.
    2. Optimizer: The mechanism through which the network will update itself based on the data it sees and its loss function.
    3. Metrics Here, we’ll only care about accuracy (the fraction of the images that were correctly classified).
  • While we are already familiar with defining metrics to optimize, defining an optimizer and loss function is new. We will dig into that later.

  • Notice that the compile() function modifies the network in place. We will talk about all of them later in a bit more detail.

network %>% compile(
  optimizer = "rmsprop",
  loss = "categorical_crossentropy",
  metrics = c("accuracy")
)

Lets inspect our final setup:

summary(network)
Model: "sequential_1"
___________________________________________________________________________________________________
Layer (type)                                Output Shape                            Param #        
===================================================================================================
dense_2 (Dense)                             (None, 512)                             401920         
___________________________________________________________________________________________________
dense_3 (Dense)                             (None, 10)                              5130           
===================================================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
___________________________________________________________________________________________________

Well’ we see that a network of this size has quite a large number of trainable parameters (all edge-weights, meaning 512x512 + 512x10).

Preprocess the data

  • Before training the model, preprocess the data by reshaping it into the shape the network expects and scaling it so that all values are in the [0, 1] interval.
  • Previously, our training images were stored in an 3d array of shape (60000, 28, 28) of type integer with values in the [0, 255] interval.
  • We transform it into a double array of shape (60000, 28 * 28) with values between 0 and 1.
train_images <- array_reshape(train_images, c(60000, 28 * 28))
train_images <- train_images / 255 # To scale between 0 and 1

test_images <- array_reshape(test_images, c(10000, 28 * 28))
test_images <- test_images / 255 # To scale between 0 and 1
  • Note that we use the array_reshape() rather than the dim() function to reshape the array. I explain why later, when we talk about tensor reshaping.
  • Lastly, we also need to categorically encode the labels.
train_labels <- to_categorical(train_labels)
test_labels <- to_categorical(test_labels)

Run the network

We’re now ready to train the network via Keras fit() function. We save the output in an object we call history.net.

set.seed(1337)
history.net <- network %>% fit(x = train_images, 
                               y = train_labels, 
                               epochs = 10, # How often shall we re-run the model on the whole sample
                               batch_size = 128, # How many observations should be included in every batch
                               validation_split = 0.25 # If we want to do a  cross-validation in the training
                               )
Epoch 1/10

  1/352 [..............................] - ETA: 0s - loss: 2.3864 - accuracy: 0.1328
 11/352 [..............................] - ETA: 1s - loss: 1.2659 - accuracy: 0.6236
 22/352 [>.............................] - ETA: 1s - loss: 0.9197 - accuracy: 0.7365
 31/352 [=>............................] - ETA: 1s - loss: 0.7930 - accuracy: 0.7712
 44/352 [==>...........................] - ETA: 1s - loss: 0.6916 - accuracy: 0.7972
 57/352 [===>..........................] - ETA: 1s - loss: 0.6179 - accuracy: 0.8189
 68/352 [====>.........................] - ETA: 1s - loss: 0.5747 - accuracy: 0.8316
 79/352 [=====>........................] - ETA: 1s - loss: 0.5402 - accuracy: 0.8424
 91/352 [======>.......................] - ETA: 1s - loss: 0.5104 - accuracy: 0.8515
102/352 [=======>......................] - ETA: 1s - loss: 0.4856 - accuracy: 0.8584
113/352 [========>.....................] - ETA: 1s - loss: 0.4659 - accuracy: 0.8642
125/352 [=========>....................] - ETA: 1s - loss: 0.4507 - accuracy: 0.8687
137/352 [==========>...................] - ETA: 0s - loss: 0.4332 - accuracy: 0.8739
150/352 [===========>..................] - ETA: 0s - loss: 0.4188 - accuracy: 0.8781
163/352 [============>.................] - ETA: 0s - loss: 0.4035 - accuracy: 0.8828
176/352 [==============>...............] - ETA: 0s - loss: 0.3913 - accuracy: 0.8857
187/352 [==============>...............] - ETA: 0s - loss: 0.3822 - accuracy: 0.8885
197/352 [===============>..............] - ETA: 0s - loss: 0.3739 - accuracy: 0.8909
208/352 [================>.............] - ETA: 0s - loss: 0.3653 - accuracy: 0.8930
219/352 [=================>............] - ETA: 0s - loss: 0.3585 - accuracy: 0.8952
231/352 [==================>...........] - ETA: 0s - loss: 0.3503 - accuracy: 0.8976
241/352 [===================>..........] - ETA: 0s - loss: 0.3437 - accuracy: 0.8994
252/352 [====================>.........] - ETA: 0s - loss: 0.3374 - accuracy: 0.9013
262/352 [=====================>........] - ETA: 0s - loss: 0.3331 - accuracy: 0.9024
271/352 [======================>.......] - ETA: 0s - loss: 0.3281 - accuracy: 0.9039
281/352 [======================>.......] - ETA: 0s - loss: 0.3236 - accuracy: 0.9052
291/352 [=======================>......] - ETA: 0s - loss: 0.3189 - accuracy: 0.9065
302/352 [========================>.....] - ETA: 0s - loss: 0.3139 - accuracy: 0.9080
312/352 [=========================>....] - ETA: 0s - loss: 0.3087 - accuracy: 0.9094
322/352 [==========================>...] - ETA: 0s - loss: 0.3050 - accuracy: 0.9106
333/352 [===========================>..] - ETA: 0s - loss: 0.3001 - accuracy: 0.9119
345/352 [============================>.] - ETA: 0s - loss: 0.2952 - accuracy: 0.9134
352/352 [==============================] - 2s 5ms/step - loss: 0.2928 - accuracy: 0.9142

352/352 [==============================] - 3s 8ms/step - loss: 0.2928 - accuracy: 0.9142 - val_loss: 0.1599 - val_accuracy: 0.9533
Epoch 2/10

  1/352 [..............................] - ETA: 0s - loss: 0.1682 - accuracy: 0.9609
 13/352 [>.............................] - ETA: 1s - loss: 0.1348 - accuracy: 0.9627
 25/352 [=>............................] - ETA: 1s - loss: 0.1295 - accuracy: 0.9638
 38/352 [==>...........................] - ETA: 1s - loss: 0.1383 - accuracy: 0.9624
 49/352 [===>..........................] - ETA: 1s - loss: 0.1387 - accuracy: 0.9617
 61/352 [====>.........................] - ETA: 1s - loss: 0.1312 - accuracy: 0.9625
 73/352 [=====>........................] - ETA: 1s - loss: 0.1293 - accuracy: 0.9625
 83/352 [======>.......................] - ETA: 1s - loss: 0.1304 - accuracy: 0.9618
 96/352 [=======>......................] - ETA: 1s - loss: 0.1286 - accuracy: 0.9627
108/352 [========>.....................] - ETA: 1s - loss: 0.1296 - accuracy: 0.9622
119/352 [=========>....................] - ETA: 1s - loss: 0.1265 - accuracy: 0.9632
130/352 [==========>...................] - ETA: 0s - loss: 0.1268 - accuracy: 0.9635
141/352 [===========>..................] - ETA: 0s - loss: 0.1276 - accuracy: 0.9632
151/352 [===========>..................] - ETA: 0s - loss: 0.1276 - accuracy: 0.9635
162/352 [============>.................] - ETA: 0s - loss: 0.1288 - accuracy: 0.9631
173/352 [=============>................] - ETA: 0s - loss: 0.1286 - accuracy: 0.9637
181/352 [==============>...............] - ETA: 0s - loss: 0.1279 - accuracy: 0.9639
187/352 [==============>...............] - ETA: 0s - loss: 0.1282 - accuracy: 0.9639
195/352 [===============>..............] - ETA: 0s - loss: 0.1275 - accuracy: 0.9638
203/352 [================>.............] - ETA: 0s - loss: 0.1271 - accuracy: 0.9639
206/352 [================>.............] - ETA: 0s - loss: 0.1277 - accuracy: 0.9636
213/352 [=================>............] - ETA: 0s - loss: 0.1278 - accuracy: 0.9635
220/352 [=================>............] - ETA: 0s - loss: 0.1272 - accuracy: 0.9637
229/352 [==================>...........] - ETA: 0s - loss: 0.1268 - accuracy: 0.9636
236/352 [===================>..........] - ETA: 0s - loss: 0.1266 - accuracy: 0.9636
245/352 [===================>..........] - ETA: 0s - loss: 0.1265 - accuracy: 0.9636
254/352 [====================>.........] - ETA: 0s - loss: 0.1265 - accuracy: 0.9634
263/352 [=====================>........] - ETA: 0s - loss: 0.1244 - accuracy: 0.9641
272/352 [======================>.......] - ETA: 0s - loss: 0.1238 - accuracy: 0.9642
282/352 [=======================>......] - ETA: 0s - loss: 0.1233 - accuracy: 0.9643
291/352 [=======================>......] - ETA: 0s - loss: 0.1233 - accuracy: 0.9642
299/352 [========================>.....] - ETA: 0s - loss: 0.1228 - accuracy: 0.9644
310/352 [=========================>....] - ETA: 0s - loss: 0.1228 - accuracy: 0.9643
321/352 [==========================>...] - ETA: 0s - loss: 0.1223 - accuracy: 0.9644
332/352 [===========================>..] - ETA: 0s - loss: 0.1218 - accuracy: 0.9644
343/352 [============================>.] - ETA: 0s - loss: 0.1214 - accuracy: 0.9647
352/352 [==============================] - 2s 5ms/step - loss: 0.1205 - accuracy: 0.9648

352/352 [==============================] - 2s 7ms/step - loss: 0.1205 - accuracy: 0.9648 - val_loss: 0.1126 - val_accuracy: 0.9659
Epoch 3/10

  1/352 [..............................] - ETA: 0s - loss: 0.0267 - accuracy: 1.0000
 12/352 [>.............................] - ETA: 1s - loss: 0.0690 - accuracy: 0.9805
 24/352 [=>............................] - ETA: 1s - loss: 0.0708 - accuracy: 0.9798
 36/352 [==>...........................] - ETA: 1s - loss: 0.0740 - accuracy: 0.9783
 50/352 [===>..........................] - ETA: 1s - loss: 0.0780 - accuracy: 0.9759
 63/352 [====>.........................] - ETA: 1s - loss: 0.0783 - accuracy: 0.9763
 77/352 [=====>........................] - ETA: 1s - loss: 0.0803 - accuracy: 0.9761
 91/352 [======>.......................] - ETA: 1s - loss: 0.0777 - accuracy: 0.9769
104/352 [=======>......................] - ETA: 0s - loss: 0.0774 - accuracy: 0.9770
117/352 [========>.....................] - ETA: 0s - loss: 0.0769 - accuracy: 0.9769
130/352 [==========>...................] - ETA: 0s - loss: 0.0788 - accuracy: 0.9764
143/352 [===========>..................] - ETA: 0s - loss: 0.0781 - accuracy: 0.9766
155/352 [============>.................] - ETA: 0s - loss: 0.0793 - accuracy: 0.9762
168/352 [=============>................] - ETA: 0s - loss: 0.0778 - accuracy: 0.9767
180/352 [==============>...............] - ETA: 0s - loss: 0.0765 - accuracy: 0.9771
193/352 [===============>..............] - ETA: 0s - loss: 0.0782 - accuracy: 0.9767
206/352 [================>.............] - ETA: 0s - loss: 0.0787 - accuracy: 0.9767
217/352 [=================>............] - ETA: 0s - loss: 0.0778 - accuracy: 0.9771
228/352 [==================>...........] - ETA: 0s - loss: 0.0790 - accuracy: 0.9769
239/352 [===================>..........] - ETA: 0s - loss: 0.0785 - accuracy: 0.9770
251/352 [====================>.........] - ETA: 0s - loss: 0.0779 - accuracy: 0.9770
261/352 [=====================>........] - ETA: 0s - loss: 0.0784 - accuracy: 0.9768
272/352 [======================>.......] - ETA: 0s - loss: 0.0777 - accuracy: 0.9771
282/352 [=======================>......] - ETA: 0s - loss: 0.0773 - accuracy: 0.9772
292/352 [=======================>......] - ETA: 0s - loss: 0.0774 - accuracy: 0.9772
303/352 [========================>.....] - ETA: 0s - loss: 0.0775 - accuracy: 0.9771
314/352 [=========================>....] - ETA: 0s - loss: 0.0773 - accuracy: 0.9772
325/352 [==========================>...] - ETA: 0s - loss: 0.0771 - accuracy: 0.9773
336/352 [===========================>..] - ETA: 0s - loss: 0.0775 - accuracy: 0.9771
347/352 [============================>.] - ETA: 0s - loss: 0.0774 - accuracy: 0.9771
352/352 [==============================] - 2s 4ms/step - loss: 0.0775 - accuracy: 0.9772

352/352 [==============================] - 2s 6ms/step - loss: 0.0775 - accuracy: 0.9772 - val_loss: 0.1008 - val_accuracy: 0.9710
Epoch 4/10

  1/352 [..............................] - ETA: 0s - loss: 0.0433 - accuracy: 0.9922
 13/352 [>.............................] - ETA: 1s - loss: 0.0487 - accuracy: 0.9862
 26/352 [=>............................] - ETA: 1s - loss: 0.0523 - accuracy: 0.9838
 39/352 [==>...........................] - ETA: 1s - loss: 0.0490 - accuracy: 0.9848
 52/352 [===>..........................] - ETA: 1s - loss: 0.0492 - accuracy: 0.9847
 66/352 [====>.........................] - ETA: 1s - loss: 0.0516 - accuracy: 0.9846
 80/352 [=====>........................] - ETA: 1s - loss: 0.0526 - accuracy: 0.9841
 94/352 [=======>......................] - ETA: 0s - loss: 0.0532 - accuracy: 0.9841
105/352 [=======>......................] - ETA: 0s - loss: 0.0533 - accuracy: 0.9841
117/352 [========>.....................] - ETA: 0s - loss: 0.0527 - accuracy: 0.9844
128/352 [=========>....................] - ETA: 0s - loss: 0.0522 - accuracy: 0.9846
137/352 [==========>...................] - ETA: 0s - loss: 0.0522 - accuracy: 0.9847
148/352 [===========>..................] - ETA: 0s - loss: 0.0527 - accuracy: 0.9847
160/352 [============>.................] - ETA: 0s - loss: 0.0529 - accuracy: 0.9846
170/352 [=============>................] - ETA: 0s - loss: 0.0528 - accuracy: 0.9847
180/352 [==============>...............] - ETA: 0s - loss: 0.0529 - accuracy: 0.9849
191/352 [===============>..............] - ETA: 0s - loss: 0.0537 - accuracy: 0.9845
201/352 [================>.............] - ETA: 0s - loss: 0.0551 - accuracy: 0.9842
212/352 [=================>............] - ETA: 0s - loss: 0.0544 - accuracy: 0.9844
223/352 [==================>...........] - ETA: 0s - loss: 0.0553 - accuracy: 0.9841
234/352 [==================>...........] - ETA: 0s - loss: 0.0561 - accuracy: 0.9840
244/352 [===================>..........] - ETA: 0s - loss: 0.0563 - accuracy: 0.9839
254/352 [====================>.........] - ETA: 0s - loss: 0.0561 - accuracy: 0.9840
265/352 [=====================>........] - ETA: 0s - loss: 0.0557 - accuracy: 0.9842
276/352 [======================>.......] - ETA: 0s - loss: 0.0554 - accuracy: 0.9841
287/352 [=======================>......] - ETA: 0s - loss: 0.0551 - accuracy: 0.9841
300/352 [========================>.....] - ETA: 0s - loss: 0.0552 - accuracy: 0.9840
313/352 [=========================>....] - ETA: 0s - loss: 0.0556 - accuracy: 0.9839
325/352 [==========================>...] - ETA: 0s - loss: 0.0561 - accuracy: 0.9836
338/352 [===========================>..] - ETA: 0s - loss: 0.0556 - accuracy: 0.9838
350/352 [============================>.] - ETA: 0s - loss: 0.0554 - accuracy: 0.9838
352/352 [==============================] - 2s 4ms/step - loss: 0.0555 - accuracy: 0.9838

352/352 [==============================] - 2s 6ms/step - loss: 0.0555 - accuracy: 0.9838 - val_loss: 0.0989 - val_accuracy: 0.9717
Epoch 5/10

  1/352 [..............................] - ETA: 0s - loss: 0.0369 - accuracy: 0.9922
 15/352 [>.............................] - ETA: 1s - loss: 0.0299 - accuracy: 0.9922
 29/352 [=>............................] - ETA: 1s - loss: 0.0310 - accuracy: 0.9906
 43/352 [==>...........................] - ETA: 1s - loss: 0.0334 - accuracy: 0.9896
 56/352 [===>..........................] - ETA: 1s - loss: 0.0356 - accuracy: 0.9891
 69/352 [====>.........................] - ETA: 1s - loss: 0.0369 - accuracy: 0.9887
 82/352 [=====>........................] - ETA: 1s - loss: 0.0368 - accuracy: 0.9889
 93/352 [======>.......................] - ETA: 1s - loss: 0.0358 - accuracy: 0.9894
103/352 [=======>......................] - ETA: 1s - loss: 0.0356 - accuracy: 0.9894
116/352 [========>.....................] - ETA: 0s - loss: 0.0358 - accuracy: 0.9894
129/352 [=========>....................] - ETA: 0s - loss: 0.0375 - accuracy: 0.9891
137/352 [==========>...................] - ETA: 0s - loss: 0.0372 - accuracy: 0.9890
148/352 [===========>..................] - ETA: 0s - loss: 0.0365 - accuracy: 0.9894
158/352 [============>.................] - ETA: 0s - loss: 0.0371 - accuracy: 0.9893
170/352 [=============>................] - ETA: 0s - loss: 0.0374 - accuracy: 0.9892
181/352 [==============>...............] - ETA: 0s - loss: 0.0380 - accuracy: 0.9892
193/352 [===============>..............] - ETA: 0s - loss: 0.0380 - accuracy: 0.9891
204/352 [================>.............] - ETA: 0s - loss: 0.0386 - accuracy: 0.9889
214/352 [=================>............] - ETA: 0s - loss: 0.0384 - accuracy: 0.9888
225/352 [==================>...........] - ETA: 0s - loss: 0.0388 - accuracy: 0.9888
237/352 [===================>..........] - ETA: 0s - loss: 0.0385 - accuracy: 0.9888
247/352 [====================>.........] - ETA: 0s - loss: 0.0387 - accuracy: 0.9887
259/352 [=====================>........] - ETA: 0s - loss: 0.0396 - accuracy: 0.9884
271/352 [======================>.......] - ETA: 0s - loss: 0.0403 - accuracy: 0.9882
281/352 [======================>.......] - ETA: 0s - loss: 0.0401 - accuracy: 0.9883
292/352 [=======================>......] - ETA: 0s - loss: 0.0404 - accuracy: 0.9881
304/352 [========================>.....] - ETA: 0s - loss: 0.0405 - accuracy: 0.9881
316/352 [=========================>....] - ETA: 0s - loss: 0.0411 - accuracy: 0.9879
327/352 [==========================>...] - ETA: 0s - loss: 0.0408 - accuracy: 0.9880
337/352 [===========================>..] - ETA: 0s - loss: 0.0406 - accuracy: 0.9880
349/352 [============================>.] - ETA: 0s - loss: 0.0411 - accuracy: 0.9878
352/352 [==============================] - 2s 4ms/step - loss: 0.0411 - accuracy: 0.9878

352/352 [==============================] - 2s 6ms/step - loss: 0.0411 - accuracy: 0.9878 - val_loss: 0.0870 - val_accuracy: 0.9747
Epoch 6/10

  1/352 [..............................] - ETA: 0s - loss: 0.0442 - accuracy: 0.9922
 13/352 [>.............................] - ETA: 1s - loss: 0.0279 - accuracy: 0.9916
 26/352 [=>............................] - ETA: 1s - loss: 0.0247 - accuracy: 0.9928
 38/352 [==>...........................] - ETA: 1s - loss: 0.0273 - accuracy: 0.9920
 50/352 [===>..........................] - ETA: 1s - loss: 0.0250 - accuracy: 0.9928
 62/352 [====>.........................] - ETA: 1s - loss: 0.0250 - accuracy: 0.9931
 73/352 [=====>........................] - ETA: 1s - loss: 0.0257 - accuracy: 0.9924
 86/352 [======>.......................] - ETA: 1s - loss: 0.0279 - accuracy: 0.9914
 98/352 [=======>......................] - ETA: 1s - loss: 0.0289 - accuracy: 0.9913
110/352 [========>.....................] - ETA: 1s - loss: 0.0285 - accuracy: 0.9915
121/352 [=========>....................] - ETA: 0s - loss: 0.0280 - accuracy: 0.9916
133/352 [==========>...................] - ETA: 0s - loss: 0.0293 - accuracy: 0.9909
145/352 [===========>..................] - ETA: 0s - loss: 0.0294 - accuracy: 0.9907
157/352 [============>.................] - ETA: 0s - loss: 0.0291 - accuracy: 0.9907
168/352 [=============>................] - ETA: 0s - loss: 0.0298 - accuracy: 0.9905
180/352 [==============>...............] - ETA: 0s - loss: 0.0303 - accuracy: 0.9905
190/352 [===============>..............] - ETA: 0s - loss: 0.0308 - accuracy: 0.9905
203/352 [================>.............] - ETA: 0s - loss: 0.0315 - accuracy: 0.9901
215/352 [=================>............] - ETA: 0s - loss: 0.0316 - accuracy: 0.9902
228/352 [==================>...........] - ETA: 0s - loss: 0.0313 - accuracy: 0.9904
241/352 [===================>..........] - ETA: 0s - loss: 0.0312 - accuracy: 0.9905
253/352 [====================>.........] - ETA: 0s - loss: 0.0312 - accuracy: 0.9905
265/352 [=====================>........] - ETA: 0s - loss: 0.0308 - accuracy: 0.9906
276/352 [======================>.......] - ETA: 0s - loss: 0.0304 - accuracy: 0.9907
288/352 [=======================>......] - ETA: 0s - loss: 0.0302 - accuracy: 0.9907
300/352 [========================>.....] - ETA: 0s - loss: 0.0302 - accuracy: 0.9908
312/352 [=========================>....] - ETA: 0s - loss: 0.0301 - accuracy: 0.9908
324/352 [==========================>...] - ETA: 0s - loss: 0.0301 - accuracy: 0.9908
336/352 [===========================>..] - ETA: 0s - loss: 0.0308 - accuracy: 0.9908
349/352 [============================>.] - ETA: 0s - loss: 0.0305 - accuracy: 0.9910
352/352 [==============================] - 2s 4ms/step - loss: 0.0303 - accuracy: 0.9910

352/352 [==============================] - 2s 5ms/step - loss: 0.0303 - accuracy: 0.9910 - val_loss: 0.1008 - val_accuracy: 0.9727
Epoch 7/10

  1/352 [..............................] - ETA: 0s - loss: 0.0514 - accuracy: 0.9766
 15/352 [>.............................] - ETA: 1s - loss: 0.0237 - accuracy: 0.9927
 29/352 [=>............................] - ETA: 1s - loss: 0.0246 - accuracy: 0.9925
 43/352 [==>...........................] - ETA: 1s - loss: 0.0216 - accuracy: 0.9933
 55/352 [===>..........................] - ETA: 1s - loss: 0.0217 - accuracy: 0.9935
 68/352 [====>.........................] - ETA: 1s - loss: 0.0212 - accuracy: 0.9938
 83/352 [======>.......................] - ETA: 1s - loss: 0.0233 - accuracy: 0.9933
 97/352 [=======>......................] - ETA: 0s - loss: 0.0232 - accuracy: 0.9936
111/352 [========>.....................] - ETA: 0s - loss: 0.0231 - accuracy: 0.9935
125/352 [=========>....................] - ETA: 0s - loss: 0.0231 - accuracy: 0.9936
139/352 [==========>...................] - ETA: 0s - loss: 0.0231 - accuracy: 0.9934
152/352 [===========>..................] - ETA: 0s - loss: 0.0229 - accuracy: 0.9935
165/352 [=============>................] - ETA: 0s - loss: 0.0236 - accuracy: 0.9936
178/352 [==============>...............] - ETA: 0s - loss: 0.0237 - accuracy: 0.9935
190/352 [===============>..............] - ETA: 0s - loss: 0.0232 - accuracy: 0.9935
203/352 [================>.............] - ETA: 0s - loss: 0.0229 - accuracy: 0.9935
215/352 [=================>............] - ETA: 0s - loss: 0.0231 - accuracy: 0.9935
226/352 [==================>...........] - ETA: 0s - loss: 0.0232 - accuracy: 0.9934
239/352 [===================>..........] - ETA: 0s - loss: 0.0230 - accuracy: 0.9934
253/352 [====================>.........] - ETA: 0s - loss: 0.0228 - accuracy: 0.9934
264/352 [=====================>........] - ETA: 0s - loss: 0.0227 - accuracy: 0.9934
277/352 [======================>.......] - ETA: 0s - loss: 0.0225 - accuracy: 0.9935
288/352 [=======================>......] - ETA: 0s - loss: 0.0227 - accuracy: 0.9934
299/352 [========================>.....] - ETA: 0s - loss: 0.0225 - accuracy: 0.9933
312/352 [=========================>....] - ETA: 0s - loss: 0.0226 - accuracy: 0.9934
323/352 [==========================>...] - ETA: 0s - loss: 0.0226 - accuracy: 0.9933
334/352 [===========================>..] - ETA: 0s - loss: 0.0225 - accuracy: 0.9934
344/352 [============================>.] - ETA: 0s - loss: 0.0228 - accuracy: 0.9933
352/352 [==============================] - 1s 4ms/step - loss: 0.0229 - accuracy: 0.9933

352/352 [==============================] - 2s 5ms/step - loss: 0.0229 - accuracy: 0.9933 - val_loss: 0.0875 - val_accuracy: 0.9768
Epoch 8/10

  1/352 [..............................] - ETA: 0s - loss: 0.0032 - accuracy: 1.0000
 14/352 [>.............................] - ETA: 1s - loss: 0.0201 - accuracy: 0.9950
 28/352 [=>............................] - ETA: 1s - loss: 0.0150 - accuracy: 0.9961
 41/352 [==>...........................] - ETA: 1s - loss: 0.0148 - accuracy: 0.9962
 53/352 [===>..........................] - ETA: 1s - loss: 0.0145 - accuracy: 0.9960
 66/352 [====>.........................] - ETA: 1s - loss: 0.0152 - accuracy: 0.9959
 80/352 [=====>........................] - ETA: 1s - loss: 0.0145 - accuracy: 0.9961
 92/352 [======>.......................] - ETA: 1s - loss: 0.0146 - accuracy: 0.9962
104/352 [=======>......................] - ETA: 0s - loss: 0.0163 - accuracy: 0.9958
115/352 [========>.....................] - ETA: 0s - loss: 0.0162 - accuracy: 0.9957
126/352 [=========>....................] - ETA: 0s - loss: 0.0163 - accuracy: 0.9957
138/352 [==========>...................] - ETA: 0s - loss: 0.0161 - accuracy: 0.9959
150/352 [===========>..................] - ETA: 0s - loss: 0.0157 - accuracy: 0.9959
162/352 [============>.................] - ETA: 0s - loss: 0.0155 - accuracy: 0.9960
174/352 [=============>................] - ETA: 0s - loss: 0.0157 - accuracy: 0.9960
186/352 [==============>...............] - ETA: 0s - loss: 0.0155 - accuracy: 0.9960
197/352 [===============>..............] - ETA: 0s - loss: 0.0159 - accuracy: 0.9958
209/352 [================>.............] - ETA: 0s - loss: 0.0158 - accuracy: 0.9958
220/352 [=================>............] - ETA: 0s - loss: 0.0166 - accuracy: 0.9956
231/352 [==================>...........] - ETA: 0s - loss: 0.0169 - accuracy: 0.9955
244/352 [===================>..........] - ETA: 0s - loss: 0.0167 - accuracy: 0.9955
256/352 [====================>.........] - ETA: 0s - loss: 0.0169 - accuracy: 0.9954
268/352 [=====================>........] - ETA: 0s - loss: 0.0168 - accuracy: 0.9954
282/352 [=======================>......] - ETA: 0s - loss: 0.0171 - accuracy: 0.9954
296/352 [========================>.....] - ETA: 0s - loss: 0.0173 - accuracy: 0.9954
306/352 [=========================>....] - ETA: 0s - loss: 0.0172 - accuracy: 0.9954
317/352 [==========================>...] - ETA: 0s - loss: 0.0173 - accuracy: 0.9952
328/352 [==========================>...] - ETA: 0s - loss: 0.0175 - accuracy: 0.9952
340/352 [===========================>..] - ETA: 0s - loss: 0.0174 - accuracy: 0.9952
352/352 [==============================] - 2s 5ms/step - loss: 0.0176 - accuracy: 0.9952

352/352 [==============================] - 2s 6ms/step - loss: 0.0176 - accuracy: 0.9952 - val_loss: 0.0888 - val_accuracy: 0.9761
Epoch 9/10

  1/352 [..............................] - ETA: 0s - loss: 0.0402 - accuracy: 0.9844
 13/352 [>.............................] - ETA: 1s - loss: 0.0091 - accuracy: 0.9976
 25/352 [=>............................] - ETA: 1s - loss: 0.0107 - accuracy: 0.9975
 30/352 [=>............................] - ETA: 1s - loss: 0.0114 - accuracy: 0.9971
 36/352 [==>...........................] - ETA: 1s - loss: 0.0121 - accuracy: 0.9970
 48/352 [===>..........................] - ETA: 1s - loss: 0.0131 - accuracy: 0.9972
 64/352 [====>.........................] - ETA: 1s - loss: 0.0117 - accuracy: 0.9976
 75/352 [=====>........................] - ETA: 1s - loss: 0.0117 - accuracy: 0.9974
 85/352 [======>.......................] - ETA: 1s - loss: 0.0114 - accuracy: 0.9974
 97/352 [=======>......................] - ETA: 1s - loss: 0.0122 - accuracy: 0.9972
109/352 [========>.....................] - ETA: 1s - loss: 0.0117 - accuracy: 0.9973
121/352 [=========>....................] - ETA: 1s - loss: 0.0125 - accuracy: 0.9971
133/352 [==========>...................] - ETA: 1s - loss: 0.0124 - accuracy: 0.9971
145/352 [===========>..................] - ETA: 0s - loss: 0.0120 - accuracy: 0.9972
158/352 [============>.................] - ETA: 0s - loss: 0.0133 - accuracy: 0.9969
170/352 [=============>................] - ETA: 0s - loss: 0.0130 - accuracy: 0.9969
182/352 [==============>...............] - ETA: 0s - loss: 0.0131 - accuracy: 0.9968
194/352 [===============>..............] - ETA: 0s - loss: 0.0134 - accuracy: 0.9967
206/352 [================>.............] - ETA: 0s - loss: 0.0131 - accuracy: 0.9968
217/352 [=================>............] - ETA: 0s - loss: 0.0130 - accuracy: 0.9968
229/352 [==================>...........] - ETA: 0s - loss: 0.0135 - accuracy: 0.9966
241/352 [===================>..........] - ETA: 0s - loss: 0.0134 - accuracy: 0.9965
254/352 [====================>.........] - ETA: 0s - loss: 0.0132 - accuracy: 0.9966
267/352 [=====================>........] - ETA: 0s - loss: 0.0132 - accuracy: 0.9965
279/352 [======================>.......] - ETA: 0s - loss: 0.0129 - accuracy: 0.9966
291/352 [=======================>......] - ETA: 0s - loss: 0.0128 - accuracy: 0.9967
302/352 [========================>.....] - ETA: 0s - loss: 0.0129 - accuracy: 0.9966
310/352 [=========================>....] - ETA: 0s - loss: 0.0128 - accuracy: 0.9966
321/352 [==========================>...] - ETA: 0s - loss: 0.0129 - accuracy: 0.9967
332/352 [===========================>..] - ETA: 0s - loss: 0.0128 - accuracy: 0.9967
343/352 [============================>.] - ETA: 0s - loss: 0.0127 - accuracy: 0.9968
352/352 [==============================] - 2s 5ms/step - loss: 0.0128 - accuracy: 0.9967

352/352 [==============================] - 2s 6ms/step - loss: 0.0128 - accuracy: 0.9967 - val_loss: 0.0889 - val_accuracy: 0.9783
Epoch 10/10

  1/352 [..............................] - ETA: 0s - loss: 0.0685 - accuracy: 0.9922
 15/352 [>.............................] - ETA: 1s - loss: 0.0120 - accuracy: 0.9969
 29/352 [=>............................] - ETA: 1s - loss: 0.0086 - accuracy: 0.9978
 42/352 [==>...........................] - ETA: 1s - loss: 0.0080 - accuracy: 0.9980
 56/352 [===>..........................] - ETA: 1s - loss: 0.0079 - accuracy: 0.9980
 67/352 [====>.........................] - ETA: 1s - loss: 0.0081 - accuracy: 0.9980
 78/352 [=====>........................] - ETA: 1s - loss: 0.0074 - accuracy: 0.9983
 91/352 [======>.......................] - ETA: 1s - loss: 0.0075 - accuracy: 0.9983
104/352 [=======>......................] - ETA: 0s - loss: 0.0081 - accuracy: 0.9979
115/352 [========>.....................] - ETA: 0s - loss: 0.0083 - accuracy: 0.9978
127/352 [=========>....................] - ETA: 0s - loss: 0.0088 - accuracy: 0.9977
139/352 [==========>...................] - ETA: 0s - loss: 0.0085 - accuracy: 0.9978
151/352 [===========>..................] - ETA: 0s - loss: 0.0089 - accuracy: 0.9977
161/352 [============>.................] - ETA: 0s - loss: 0.0091 - accuracy: 0.9974
171/352 [=============>................] - ETA: 0s - loss: 0.0089 - accuracy: 0.9975
177/352 [==============>...............] - ETA: 0s - loss: 0.0091 - accuracy: 0.9975
188/352 [===============>..............] - ETA: 0s - loss: 0.0091 - accuracy: 0.9975
199/352 [===============>..............] - ETA: 0s - loss: 0.0089 - accuracy: 0.9975
209/352 [================>.............] - ETA: 0s - loss: 0.0089 - accuracy: 0.9975
220/352 [=================>............] - ETA: 0s - loss: 0.0087 - accuracy: 0.9976
231/352 [==================>...........] - ETA: 0s - loss: 0.0087 - accuracy: 0.9976
241/352 [===================>..........] - ETA: 0s - loss: 0.0086 - accuracy: 0.9976
252/352 [====================>.........] - ETA: 0s - loss: 0.0089 - accuracy: 0.9975
262/352 [=====================>........] - ETA: 0s - loss: 0.0089 - accuracy: 0.9975
272/352 [======================>.......] - ETA: 0s - loss: 0.0088 - accuracy: 0.9975
283/352 [=======================>......] - ETA: 0s - loss: 0.0089 - accuracy: 0.9975
293/352 [=======================>......] - ETA: 0s - loss: 0.0090 - accuracy: 0.9974
304/352 [========================>.....] - ETA: 0s - loss: 0.0089 - accuracy: 0.9975
313/352 [=========================>....] - ETA: 0s - loss: 0.0096 - accuracy: 0.9973
323/352 [==========================>...] - ETA: 0s - loss: 0.0097 - accuracy: 0.9972
332/352 [===========================>..] - ETA: 0s - loss: 0.0099 - accuracy: 0.9972
342/352 [============================>.] - ETA: 0s - loss: 0.0100 - accuracy: 0.9972
352/352 [==============================] - 2s 5ms/step - loss: 0.0101 - accuracy: 0.9972

352/352 [==============================] - 2s 6ms/step - loss: 0.0101 - accuracy: 0.9972 - val_loss: 0.0924 - val_accuracy: 0.9765
  • Two quantities are displayed in the log during training: the loss and accuracy of the network over the training data during the subsequent epochs (new training runs after re-adjusting the weights).
  • Notice that the measures improve in every epoch. We quickly reach an accuracy (98.9% on the training data.
  • Notice that fit() adjusts the weights of the network without explicly assigning it into a new object. history.net therefore only contains the history of the models prediction metrics through the different epochs, in case we would like to inspect it. Let’s take a look:
history.net

Final epoch (plot to see history):
        loss: 0.01011
    accuracy: 0.9972
    val_loss: 0.09245
val_accuracy: 0.9765 
history.net %>% glimpse()
List of 2
 $ params :List of 3
  ..$ verbose: int 1
  ..$ epochs : int 10
  ..$ steps  : int 352
 $ metrics:List of 4
  ..$ loss        : num [1:10] 0.2928 0.1205 0.0775 0.0555 0.0411 ...
  ..$ accuracy    : num [1:10] 0.914 0.965 0.977 0.984 0.988 ...
  ..$ val_loss    : num [1:10] 0.1599 0.1126 0.1008 0.0989 0.087 ...
  ..$ val_accuracy: num [1:10] 0.953 0.966 0.971 0.972 0.975 ...
 - attr(*, "class")= chr "keras_training_history"

We can also visualize these metrics through the epocs.

history.net %>% plot(smooth = TRUE)

  • Interestingly, we already see that our model overfits. Meaning, while accuracy in our training set tends to further increase through the epocs, it starts over time to decrease in our validation set. T
  • here are different ways to fight that, such as defining a layer_dropout, or to tell the model to pick stop running further epocs as soon as the validation accuracy drops. However, we will for now just move on.
  • For now, let’s check if the model performs well out-of-sample on the test-set:
metrics <- network %>% evaluate(test_images, test_labels)

  1/313 [..............................] - ETA: 0s - loss: 0.0036 - accuracy: 1.0000
 38/313 [==>...........................] - ETA: 0s - loss: 0.0640 - accuracy: 0.9794
 70/313 [=====>........................] - ETA: 0s - loss: 0.0922 - accuracy: 0.9737
102/313 [========>.....................] - ETA: 0s - loss: 0.0913 - accuracy: 0.9767
133/313 [===========>..................] - ETA: 0s - loss: 0.0965 - accuracy: 0.9751
162/313 [==============>...............] - ETA: 0s - loss: 0.0906 - accuracy: 0.9767
197/313 [=================>............] - ETA: 0s - loss: 0.0852 - accuracy: 0.9778
227/313 [====================>.........] - ETA: 0s - loss: 0.0796 - accuracy: 0.9794
256/313 [=======================>......] - ETA: 0s - loss: 0.0723 - accuracy: 0.9810
288/313 [==========================>...] - ETA: 0s - loss: 0.0693 - accuracy: 0.9821
313/313 [==============================] - 0s 2ms/step - loss: 0.0692 - accuracy: 0.9821

313/313 [==============================] - 0s 2ms/step - loss: 0.0692 - accuracy: 0.9821
metrics
      loss   accuracy 
0.06924154 0.98210001 
  • Ok, so far so good. I think that’s a decent accuracy for such an ad-hoc model. Whith a bit of tinkering, we surely could get it to 99%. But thats a task for another time…
  • Lets go back to basic and revise a bit what we have done so far.

Data representations for neural networks

  • In the previous example, we started from data stored in multidimensional arrays, also called tensors.
  • In general, most current ML systems use tensors as their basic data structure. Tensors are fundamental to the field-so fundamental that Google’s TensorFlow was named after them. So what’s a tensor?
  • Tensors are a generalization of vectors and matrices to an arbitrary number of dimensions (note that in the context of tensors, a dimension is often called an axis).
  • In R, vectors are used to create and manipulate 1D tensors, and matrices are used for 2D tensors. For higher-level dimensions, array objects (which support any number of dimensions) are used.

Key tensor-attributes

A tensor is defined by three key attributes:

  1. Number of axes (rank): For instance, a 3D tensor has three axes, and a matrix has two axes.
  2. Shape: This is an integer vector that describes how many dimensions the tensor has along each axis.
  3. Data type: This is the type of the data contained in the tensor; for instance, a tensor’s type could be integer or double. On rare occasions, you may see a character tensor.

Tensor reshaping

Remember that we before did not use the dim() but the array_reshape() function to manipulate our input tensors.

train_images <- array_reshape(train_images, c(60000, 28 * 28))
str(train_images)
 num [1:60000, 1:784] 0 0 0 0 0 0 0 0 0 0 ...
dim(train_images)
[1] 60000   784
  • This is an R specific thingy, so that the data is reinterpreted using row-major semantics (as opposed to Rs default column-major semantics), which is in turn compatible with the way the numerical libraries called by Keras (NumPy, TensorFlow, and so on) interpret array dimensions.
  • You should always use the array_reshape() function when reshaping R arrays that will be passed to Keras.
  • Reshaping a tensor means rearranging its rows and columns to match a target shape.
  • Naturally, the reshaped tensor has the same total number of coefficients as the initial tensor. Lets do a simple examples:
x <- matrix(c(0:5),
            nrow = 3, ncol = 2, byrow = TRUE)
x
     [,1] [,2]
[1,]    0    1
[2,]    2    3
[3,]    4    5
x <- array_reshape(x, dim = c(3, 2))
x
     [,1] [,2]
[1,]    0    1
[2,]    2    3
[3,]    4    5
x <- array_reshape(x, dim = c(2, 3))
x
     [,1] [,2] [,3]
[1,]    0    1    2
[2,]    3    4    5
  • A special case of reshaping that’s commonly encountered is transposition.
  • Transposing a matrix means exchanging its rows and its columns, so that x[i,] becomes x[, i]. The t() function can be used to transpose a matrix:
x <- t(x)
x
     [,1] [,2]
[1,]    0    3
[2,]    1    4
[3,]    2    5
rm(x)

Geometric interpretation of tensor operations

layer <- layer_dense(units = 32, input_shape = c(784))
  • We’re creating a layer that will only accept as input 2D tensors where the first dimension is 784 (the first dimension, the batch dimension, is unspecified, and thus any value would be accepted). This layer will return a tensor where the first dimension has been transformed to be 32.
  • Thus this layer can only be connected to a downstream layer that expects 32-dimensional vectors as its input. When using Keras, you don’t have to worry about compatibility, because the layers you add to your models are dynamically built to match the shape of the incoming layer.
  • For instance, suppose you write the following:
model <- keras_model_sequential() %>%
  layer_dense(units = 32, input_shape = c(784)) %>%
  layer_dense(units = 32)
model
Model
Model: "sequential_2"
___________________________________________________________________________________________________
Layer (type)                                Output Shape                            Param #        
===================================================================================================
dense_4 (Dense)                             (None, 32)                              25120          
___________________________________________________________________________________________________
dense_5 (Dense)                             (None, 32)                              1056           
===================================================================================================
Total params: 26,176
Trainable params: 26,176
Non-trainable params: 0
___________________________________________________________________________________________________
# devtools::install_github("andrie/deepviz")
library(deepviz)
plot_model(model)
  • The second layer didn’t receive an input shape argument-instead, it automatically inferred its input shape as being the output shape of the layer that came before.

  • Picking the right network architecture is more an art than a science; and although there are some best practices and principles you can rely on, only practice can help you become a proper neural-network architect.

  • Here, we will simit ourself to a simple feed-forward network, where every layer is only connected to the following one. For now, there are two key architecture decisions to be made about such a stack of dense layers:

    1. How many layers to use?
    2. How many hidden units to choose for each layer?
    3. Which activation function to use?
rm(layer, model)

Activation functions

  • As we already saw, we can define activation functiopns for the different activation functions for every layer. While we within the intermediate layers seldomely switch between different activation functions, the one we define for the output layer critically depends on the shape of our desired output data.
  • A brief reminder: Activation functions transform the input weights of a cell to its output. Without them, the dense layer would consist of two linear operations-a dot product and an addition:
  • In order to get access to a much richer hypothesis space that would benefit from deep representations, you need a non-linearity, or activation function.
    • relu is the most popular activation function in deep learning, but there are many other candidates, which all come with similarly strange names: prelu, elu, and so on. A relu (rectified linear unit) is a function meant to zero out negative values, and commonly used for intermediate layers (formerly, almost all layers where modelled with sigmoid, but nowadays its proven that for intermediate layers relu mostly works better).

Our output layer, however, should model a binary choice (yes/no classification). For such a model, we would in a 2-class problem commonly use a a sigmoid function, which we already know from logistic regression models. It “squashes” arbitrary values into the [0, 1] interval, outputting something that can be interpreted as a probability.

However, since we have a multi-class prediction problem, we choose softmax, which squashes the outputs of each unit to be between 0 and 1, just like a sigmoid, but it also divides each output such that the total sum of the outputs is equal to 1. The output is equivalent to a categorical probability distribution, it tells you the probability that any of the classes are true.

If you are interested regarding the different types of layers in Keras, check the reference site with all layers implemented. Furthermore, types of activation functions are discussed HERE

Loss functions and optimizers: keys to configuring the learning process

Once the network architecture is defined, you still have to choose two more things:

  • Loss function (objective function): The quantity that will be minimized during training. It represents a measure of success for the task at hand.

  • Optimizer: Determines how the network will be updated based on the loss function. It implements a specific variant of stochastic gradient descent (SGD).

  • Choosing the right objective and function for the right problem is extremely important: your network will take any shortcut it can, to minimize the loss; so if the objective doesn’t fully correlate with success for the task at hand, your network will end up doing things you may not have wanted.

  • HERE you find a brief overview on different loss functions. Fortunately, when it comes to common problems such as classification, regression, and sequence prediction, there are simple guidelines you can follow to choose the correct loss.

  • Take this rule-of-thumb table as a good starter:

  • With respect to the optimizer: We will cover that later. There are a bunch of different around, most variants of the Stochastic Gradient Descent (SGD), Batch (vanilla) Gradient Descent, and Mini-Batch Gradient Descent.
  • HERE and HERE you find a nice summary for the interested reader that wants to now more. Currently (that might change soon, since everything in DL movers fast), its common knowledge that if you have nmo strong reasons to believe so, RMSprop (an unpublished, adaptive learning rate method proposed by Geoff Hinton) with standard learning rates works just well.

Reviewing our initial example

Let’s go back to the first example and review each piece of it in the light of what we have learned up to now: This was the input data:

mnist <- dataset_mnist()

train_images <- mnist$train$x
train_images <- array_reshape(train_images, c(60000, 28 * 28))
train_images <- train_images / 255

test_images <- mnist$test$x
test_images <- array_reshape(test_images, c(10000, 28 * 28))
test_images <- test_images / 255
  • Now you understand that the input images are stored in tensors of shape (60000, 784) (training data) and (10000, 784) (test data), respectively.
  • This was our network:
network <- keras_model_sequential() %>%
  layer_dense(units = 512, activation = "relu", input_shape = c(28*28)) %>%
  layer_dense(units = 10, activation = "softmax")
  • Now you understand that this network consists of a chain of two dense layers, that each layer applies a few simple tensor operations to the input data, and that these operations involve weight tensors.
  • We know that layer_dense() creates fully connected layers, so there exists a weight between every element of one with every element of the following layer.
  • Weight tensors, which are attributes of the layers, are where the knowledge of the network persists. We know the 2nd layer has 512 cells, the final output layer 10 (equal to the number of classes to predict). Finally, we know that every cell also contains a non-linear activation function, such as relu, sigmoid, or softmax.

This was the network-compilation step:

network network %>% compile(
  optimizer = "rmsprop",
  loss = "categorical_crossentropy",
  metrics = "accuracy"
  )
  • Now you understand that categorical_crossentropy (a measure how pure the predicted classes are) is a type of a `loss`` function that’s used as a feedback signal for learning the weight tensors, and which the training phase will attempt to minimize.
  • You also know that this reduction of the loss happens via mini-batch stochastic gradient descent. The exact rules governing a specific use of gradient descent are defined by the rmsprop optimizer passed as the first argument.

Finally, this was the training loop:

network %>% fit(x = train_images, 
                y = train_labels, 
                epochs = 10, 
                batch_size = 128)
  • Now you understand what happens when you call fit(): the network will start to iterate on the training data in mini-batches of 128 samples, 10 times over (each iteration over all the training data is called an ?epoch?).
  • At each iteration, the network will compute the gradients of the weights with regard to the loss on the batch, and update the weights accordingly.
  • After these ?10? epochs, the network will have performed ?2,345? gradient updates (?469? per ?epoch?), and the loss of the network will be sufficiently low that the network will be capable of classifying handwritten digits with high accuracy.

At this point, you already know most of what there is to know about the basics of neural networks.

However, there is still some stuff to come, namely:

  1. How to use architectures other that the simple feed-forward one.
  2. How to fight overfitting
  3. How to specify training routines and parameter grid-search
  4. And some more…

But for that, there will be other sessions top come…

Example on tabular data

  • Just to make it a bit lss abstract and work with tabular data, lets give it a shot with classifying penguins :)

Load data

library(tidymodels)
data <- read_csv("https://github.com/allisonhorst/palmerpenguins/raw/5b5891f01b52ae26ad8cb9755ec93672f49328a8/data/penguins_size.csv")
data %>% glimpse()
Rows: 344
Columns: 7
$ species_short     <chr> "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", …
$ island            <chr> "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torg…
$ culmen_length_mm  <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, 42.0, 37.8, 37.8, …
$ culmen_depth_mm   <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, 20.2, 17.1, 17.3, …
$ flipper_length_mm <dbl> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186, 180, 182, 191, 1…
$ body_mass_g       <dbl> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, 4250, 3300, 3700, …
$ sex               <chr> "MALE", "FEMALE", "FEMALE", NA, "FEMALE", "MALE", "FEMALE", "MALE", NA…

TRain TEst split

data %<>%
  rename(y = species_short) %>%
  relocate(y) %>%
  drop_na()
data_split <- initial_split(data, prop = 0.75, strata = y)

data_train <- data_split  %>%  training()
data_test <- data_split %>% testing()

Preprocessing recipe

data_recipe <- data_train %>%
  recipe(y ~.) %>%
  step_center(all_numeric(), -all_outcomes()) %>% # Centers all numeric variables to mean = 0
  step_scale(all_numeric(), -all_outcomes()) %>% # scales all numeric variables to sd = 1
  step_dummy(all_nominal(), one_hot = TRUE) %>%
  prep()
x_train <- juice(data_recipe) %>% select(-starts_with('y')) %>% as.matrix()
x_test <- bake(data_recipe, new_data = data_test) %>% select(-starts_with('y')) %>% as.matrix()
y_train <- juice(data_recipe)  %>% select(starts_with('y')) %>% as.matrix()
y_test <- bake(data_recipe, new_data = data_test) %>% select(starts_with('y')) %>% as.matrix()

Define the network

model_keras <- keras_model_sequential()
model_keras %>% 
  # First hidden layer
  layer_dense(
    units              = 12, 
    activation         = "relu", 
    input_shape        = ncol(x_train)) %>% 
  # Dropout to prevent overfitting
  layer_dropout(rate = 0.1) %>%
  # Second hidden layer
  layer_dense(
    units              = 12, 
    activation         = "relu") %>% 
  # Dropout to prevent overfitting
  layer_dropout(rate = 0.1) %>%
  # Output layer
  layer_dense(
    units              = ncol(y_train), 
    activation         = "softmax") 
model_keras %>% 
  compile(
    optimizer = "adam",
    loss = "categorical_crossentropy",
    metrics = "accuracy"
  )
model_keras_hist <- model_keras  %>% fit(x = x_train, 
                                         y = y_train, 
                                         epochs = 10, # How often shall we re-run the model on the whole sample
                                         batch_size = 12, # How many observations should be included in every batch
                                         validation_split = 0.25 # If we want to do a  cross-validation in the training
                                         )
Epoch 1/10

 1/16 [>.............................] - ETA: 0s - loss: 1.1291 - accuracy: 0.5000
16/16 [==============================] - 0s 2ms/step - loss: 1.2027 - accuracy: 0.3085

16/16 [==============================] - 1s 47ms/step - loss: 1.2027 - accuracy: 0.3085 - val_loss: 1.3124 - val_accuracy: 0.0000e+00
Epoch 2/10

 1/16 [>.............................] - ETA: 0s - loss: 1.3300 - accuracy: 0.1667
16/16 [==============================] - 0s 1ms/step - loss: 1.1194 - accuracy: 0.3830

16/16 [==============================] - 0s 12ms/step - loss: 1.1194 - accuracy: 0.3830 - val_loss: 1.2318 - val_accuracy: 0.0000e+00
Epoch 3/10

 1/16 [>.............................] - ETA: 0s - loss: 1.0494 - accuracy: 0.5000
16/16 [==============================] - 0s 1ms/step - loss: 1.0029 - accuracy: 0.5532

16/16 [==============================] - 0s 12ms/step - loss: 1.0029 - accuracy: 0.5532 - val_loss: 1.1818 - val_accuracy: 0.0159
Epoch 4/10

 1/16 [>.............................] - ETA: 0s - loss: 0.9049 - accuracy: 0.7500
16/16 [==============================] - 0s 1ms/step - loss: 0.9466 - accuracy: 0.6170

16/16 [==============================] - 0s 13ms/step - loss: 0.9466 - accuracy: 0.6170 - val_loss: 1.1483 - val_accuracy: 0.0794
Epoch 5/10

 1/16 [>.............................] - ETA: 0s - loss: 0.8350 - accuracy: 0.7500
16/16 [==============================] - 0s 2ms/step - loss: 0.8769 - accuracy: 0.6755

16/16 [==============================] - 0s 13ms/step - loss: 0.8769 - accuracy: 0.6755 - val_loss: 1.1026 - val_accuracy: 0.2063
Epoch 6/10

 1/16 [>.............................] - ETA: 0s - loss: 0.8247 - accuracy: 0.6667
16/16 [==============================] - 0s 1ms/step - loss: 0.8050 - accuracy: 0.7660

16/16 [==============================] - 0s 12ms/step - loss: 0.8050 - accuracy: 0.7660 - val_loss: 1.0505 - val_accuracy: 0.5873
Epoch 7/10

 1/16 [>.............................] - ETA: 0s - loss: 0.7771 - accuracy: 0.7500
16/16 [==============================] - 0s 2ms/step - loss: 0.7296 - accuracy: 0.8298

16/16 [==============================] - 0s 13ms/step - loss: 0.7296 - accuracy: 0.8298 - val_loss: 0.9910 - val_accuracy: 0.6825
Epoch 8/10

 1/16 [>.............................] - ETA: 0s - loss: 0.6809 - accuracy: 0.9167
16/16 [==============================] - 0s 2ms/step - loss: 0.6871 - accuracy: 0.8404

16/16 [==============================] - 0s 12ms/step - loss: 0.6871 - accuracy: 0.8404 - val_loss: 0.9175 - val_accuracy: 0.7460
Epoch 9/10

 1/16 [>.............................] - ETA: 0s - loss: 0.6663 - accuracy: 0.7500
16/16 [==============================] - 0s 3ms/step - loss: 0.6088 - accuracy: 0.8617

16/16 [==============================] - 0s 15ms/step - loss: 0.6088 - accuracy: 0.8617 - val_loss: 0.8506 - val_accuracy: 0.7937
Epoch 10/10

 1/16 [>.............................] - ETA: 0s - loss: 0.7198 - accuracy: 0.8333
16/16 [==============================] - 0s 1ms/step - loss: 0.5699 - accuracy: 0.8777

16/16 [==============================] - 0s 12ms/step - loss: 0.5699 - accuracy: 0.8777 - val_loss: 0.7716 - val_accuracy: 0.8571

Your Turn

Using the Keras API and reference (Python or R) manual and in particular construct additional simple models (with appropriate metrics):

  • Regression example
  • Multi-label example

Also consider further resources mentioned below

Endnotes

References

More info

You can find more info about:

  • keras here: Excellent documentation, tutorials, and resources regarding keras, maintained by Rstudio

Online Resources

Datacamp * Introduction to TensorFlow in R: A bit low-level, but a good intro for starters * Also follow the Python intros, they might still be helpful for you.

Others

Books

  • François Chollet & J. J. Allaire (2018). Deep Learning with R, Manning Publications: Good book, but not for free. Find it here. though, in case of interest.

Session info

sessionInfo()
LS0tCnRpdGxlOiAnTmV1cmFsIE5ldHdvcmtzIEFwcGxpY2F0aW9uOiBFY29zeXN0ZW0gJiBzaW1wbGUgQU5OcyAoUiknCmF1dGhvcjogIkRhbmllbCBTLiBIYWluIChkc2hAYnVzaW5lc3MuYWF1LmRrKSIKZGF0ZTogIlVwZGF0ZWQgYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgIHRoZW1lOiBmbGF0bHkKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyMjIEdlbmVyaWMgcHJlYW1ibGUKcm0obGlzdD1scygpKQpTeXMuc2V0ZW52KExBTkcgPSAiZW4iKSAjIEZvciBlbmdsaXNoIGxhbmd1YWdlCm9wdGlvbnMoc2NpcGVuID0gNSkgIyBUbyBkZWFjdGl2YXRlIGFubm95aW5nIHNjaWVudGlmaWMgbnVtYmVyIG5vdGF0aW9uCgojIyMgS25pdHIgb3B0aW9ucwpsaWJyYXJ5KGtuaXRyKSAjIEZvciBkaXNwbGF5IG9mIHRoZSBtYXJrZG93bgprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZz1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgY29tbWVudD1GQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbj0iY2VudGVyIgogICAgICAgICAgICAgICAgICAgICApCmBgYAoKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShtYWdyaXR0cikKYGBgCgoKIyBJbnRyb2R1Y3Rpb24gdG8gYEtlcmFzYAoKKiBUaGVyZSBhcmUgcXVpdGUgYSBidW5jaCBvZiBkZWVwIGxlYXJuaW5nIGZyYW1ld29ya3MgYXJvdW5kLCBmcm9tIHRoZSBvbGRlciBgQ2FmZmVlYCBhbmQgYFRoZWFub2AgdG8gR29vZ2xlJ3MgYFRlbnNvcmZsb3dgIGFuZCB0aGUgbmV3ZXIgYFB5dG9yY2hgICh3aGljaCBpcyBpbmNyZWFzaW5nbHkgdHJlbmRpbmcgaW4gcmVzZWFyY2gpLiAKKiBIb3dldmVyLCBkdXJpbmcgdGhlIHJlc3Qgb2YgdGhpcyBjb3Vyc2UsIDk1JSBvZiBvdXIgZGVlcCBsZWFybmluZyBleGVyY2lzZXMgd2lsbCBiZSBkb25lIHVzaW5nIGBLZXJhc2AKKiBLZXJhcyBpcyBhIGRlZXAtbGVhcm5pbmcgZnJhbWV3b3JrIHRoYXQgcHJvdmlkZXMgYSBjb252ZW5pZW50IHdheSB0byBkZWZpbmUgYW5kIHRyYWluIGFsbW9zdCBhbnkga2luZCBvZiBkZWVwLWxlYXJuaW5nIG1vZGVsLiBLZXJhcyB3YXMgaW5pdGlhbGx5IGRldmVsb3BlZCBmb3IgcmVzZWFyY2hlcnMsIHdpdGggdGhlIGFpbSBvZiBlbmFibGluZyBmYXN0IGV4cGVyaW1lbnRhdGlvbi4gCgpJdCBoYXMgdGhlIGZvbGxvd2luZyBhZHZhbnRhZ2VzOgoKKiBVc2VyLWZyaWVuZGx5IEFQSSB3aGljaCBtYWtlcyBpdCBlYXN5IHRvIHF1aWNrbHkgcHJvdG90eXBlIGRlZXAgbGVhcm5pbmcgbW9kZWxzLgoqIEJ1aWx0LWluIHN1cHBvcnQgZm9yIGNvbnZvbHV0aW9uYWwgbmV0d29ya3MgKGZvciBjb21wdXRlciB2aXNpb24pLCByZWN1cnJlbnQgbmV0d29ya3MgKGZvciBzZXF1ZW5jZSBwcm9jZXNzaW5nKSwgYW5kIGFueSBjb21iaW5hdGlvbiBvZiBib3RoLgoqIFN1cHBvcnRzIGFyYml0cmFyeSBuZXR3b3JrIGFyY2hpdGVjdHVyZXM6IG11bHRpLWlucHV0IG9yIG11bHRpLW91dHB1dCBtb2RlbHMsIGxheWVyIHNoYXJpbmcsIG1vZGVsIHNoYXJpbmcsIGV0Yy4sIGlzIHRoZXJlZm9yZSBhcHByb3ByaWF0ZSBmb3IgYnVpbGRpbmcgZXNzZW50aWFsbHkgYW55IGRlZXAgbGVhcm5pbmcgbW9kZWwsIGZyb20gYSBtZW1vcnkgbmV0d29yayB0byBhIG5ldXJhbCBUdXJpbmcgbWFjaGluZS4KKiBJcyBjYXBhYmxlIG9mIHJ1bm5pbmcgb24gdG9wIG9mIG11bHRpcGxlIGJhY2stZW5kcyBpbmNsdWRpbmcgYFRlbnNvckZsb3dgLCBgQ05US2AsIG9yIGBUaGVhbm9gLgoqIEFsbG93cyB0aGUgc2FtZSBjb2RlIHRvIHJ1biBvbiBDUFUgb3Igb24gR1BVLCBhbmQgaGFzIHN0cm9uZyBtdWx0aS1HUFUsIGRpc3RyaWJ1dGVkIHN0b3JhZ2UsIGFuZCB0cmFpbmluZyBzdXBwb3J0IChgR29vZ2xlIGNsb3VkYCwgYFNwYXJrYCwgYEhERjVgLi4uKQoqIENhbiBlYXNpbHkgYmUgaW50ZWdyYXRlZCBpbiBBSSBwcm9kdWN0cyAoQXBwbGUgYENvcmVNTGAsIGBUZW5zb3JGbG93YCBBbmRyb2lkIHJ1bnRpbWUsICBgUmAgb3IgYFB5dGhvbmAgd2ViYXBwIGJhY2tlbmQgc3VjaCBhcyBhIGBTaGlueWAgb3IgYEZsYXNrYCBhcHApCgpJdCBpcyB3aWRlbHkgYWRhcHRlZCBpbiBhY2FkZW1pYSBhbmQgaW5kdXN0cnkgKEdvb2dsZSwgTmV0ZmxpeCwgVWJlciwgQ0VSTiwgWWVscCwgU3F1YXJlIGV0Yy4pLCBhbmQgaXMgYWxzbyBhIHBvcHVsYXIgZnJhbWV3b3JrIG9uIEthZ2dsZSwgdGhlIG1hY2hpbmUtbGVhcm5pbmcgY29tcGV0aXRpb24gd2Vic2l0ZSwgd2hlcmUgYWxtb3N0IGV2ZXJ5IHJlY2VudCBkZWVwLWxlYXJuaW5nIGNvbXBldGl0aW9uIGhhcyBiZWVuIHdvbiB1c2luZyBgS2VyYXNgIG1vZGVscy4gV2hpbGUgR29vZ2xlJ3MgYFRlbnNvckZsb3dgIGlzIGV2ZW4gbW9yZSBwb3B1bGFyLCBrZWVwIGluIG1pbmQgdGhhdCBgS2VyYXNgIGNhbiB1c2UgYFRlbnNvcmZsb3dgIChhbmQgb3RoZXIgcG9wdWxhciBETCBmcmFtZXdvcmtzKSBhcyBiYWNrZW5kLCBhbmQgYWxsb3dzIGxlc3MgY3VtYmVyc29tZSBhbmQgbW9yZSBoaWdoLWxldmVsIAoKPGltZyB3aWR0aD0iNDklIiBzcmM9Imh0dHBzOi8vc2RzLWFhdS5naXRodWIuaW8vU0RTLW1hc3Rlci8wMF9tZWRpYS9kbF9mcmFtZXdvcmtzXzEuanBnIi8+CjxpbWcgd2lkdGg9IjQ5JSIgc3JjPSJodHRwczovL3Nkcy1hYXUuZ2l0aHViLmlvL1NEUy1tYXN0ZXIvMDBfbWVkaWEvZGxfZnJhbWV3b3Jrc18yLnBuZyIvPgoKKiBTbywgYWZ0ZXIgYWxsLCBgS2VyYXNgIHJlcHJlc2VudHMgYSB3b25kZXJmdWwgaGlnaC1sZXZlbCBzdGFydGVyLCBmYXN0IGFuZCBlYXN5IGltcGxlbWVudGFibGUsIGFuZCBpbiBtb3N0IGNhc2VzIGZsZXhpYmxlIGVub3VnaCB0byBkbyB3aGF0ZXZlciB5b3UgZmVlbCBsaWtlLgoKIVtdKGh0dHBzOi8vc2RzLWFhdS5naXRodWIuaW8vU0RTLW1hc3Rlci8wMF9tZWRpYS9ETF9rZXJhc193dGYucG5nKXt3aWR0aD03NTBweH0KCioqU2lkZW5vdGU6KiogVGhlIHdlaXJkIG5hbWUgKGBLZXJhc2ApIG1lYW5zICpob3JuKiBpbiBHcmVlaywgYW5kIGlzIGEgcmVmZXJlbmNlIHRvIGFuY2llbnQgR3JlZWsgbGl0ZXJhdHVyZS4gRWcuLCBpbiBPZHlzc2V5LCBzdXBlcm5hdHVyYWwgKmRyZWFtIHNwaXJpdHMqIGFyZSBkaXZpZGVkIGJldHdlZW4gdGhvc2Ugd2hvIGRlY2VpdmUgbWVuIHdpdGggZmFsc2UgdmlzaW9ucyAoYXJyaXZpbmcgdG8gRWFydGggdGhyb3VnaCBhIGdhdGUgb2YgaXZvcnkpLCBhbmQgdGhvc2Ugd2hvIGFubm91bmNlIGEgZnV0dXJlIHRoYXQgd2lsbCBjb21lIHRvIHBhc3MgKGFycml2aW5nIHRocm91Z2ggYSBnYXRlIG9mIGhvcm4pLiBTbywgZW5vdWdoIGhpc3RvcnkgbGVzc29ucywgbGV0J3MgcnVuIG91ciBmaXJzdCBkZWVwIGxlYXJuaW5nIG1vZGVsIQoKYGBge3J9CiMgTG9hZCBvdXIgbWFpbiB0b29sCmxpYnJhcnkoa2VyYXMpCmBgYAoKIyBPdXIgZmlyc3QgZGVlcCBsZWFybmluZyBtb2RlbAoKIyMgSW50cm9kdWN0aW9uCgoqIFdlbGwsIGl0cyBhYm91dCB0aW1lIHRvIGdldCBzZXJpb3VzLiBXZSB3aWxsIGRpdmUgc3RyYWlnaHQgaW4sIGFuZCB1c2UgYSBzaW1wbGUgZGVlcCBsZWFybmluZyBtb2RlbCBvbiB0aGUgY2xhc3NpY2FsIGBNbmlzdGAgZGF0YXNldC4gCiogVGhpcyBpcyB0aGUgb3JpZ2luYWwgZGF0YSB1c2VkIGJ5IEphbiBMZUN1biBhbmQgaGlzIHRlYW0gdG8gZml0IGFuIEFOTiB0aGF0IGlkZW50aWZpZXMgaGFuZHdyaXR0ZW4gZGlnaXRzIGZvciB0aGUgVVMgcG9zdGFsIHNlcnZpY2UuIAoqIEl0IGNvbnNpc3RzIG9mIHF1aXRlIGEgYnVuY2ggb2Ygc2FtcGxlcyBvZiBoYW5kd3JpdHRlbiBkaWNpdGVzIHRvZ2V0aGVyIHdpdGggdGhlaXIgY29ycmVjdCBsYWJlbC4gVGhlIHdhbmR3cml0dGVuIGRpY2l0cyBoZXJlIGNvbnZlbmllbnRseSBjb21lIGFzIGEgMjh4MjggZ3JleXNjYWxlIG1hdHJpeCwgbWFraW5nIHRoZW0gYSBnb29kIHN0YXJ0ZXIgdG8gd2FybSB1cC4gTGV0cyBkbyB0aGF0LgoKCiMjIExvYWQgb3VyIGRhdGEgYW5kIGdldCByZWFkeQoKYGBge3J9CiMgTG9hZCBvdXIgZGF0YQptbmlzdCA8LSBkYXRhc2V0X21uaXN0KCkKYGBgCgpgYGB7cn0KbW5pc3QgJT4lCiAgZ2xpbXBzZSgpCmBgYAoKCmBgYHtyfQojIHNlcHBlcmF0ZSBpbiB0cmFpbiBhbmQgdGVzdAp0cmFpbl9pbWFnZXMgPC0gbW5pc3QkdHJhaW4keAp0cmFpbl9sYWJlbHMgPC0gbW5pc3QkdHJhaW4keQp0ZXN0X2ltYWdlcyA8LSBtbmlzdCR0ZXN0JHgKdGVzdF9sYWJlbHMgPC0gbW5pc3QkdGVzdCR5CmBgYAoKKiBMZXRzIHRha2UgYSBsb29rIGF0IHRoZSBzdHJ1Y3R1cmUuCgpgYGB7cn0KZ2xpbXBzZSh0cmFpbl9pbWFnZXMpCmBgYAoKYGBge3J9CmdsaW1wc2UodHJhaW5fbGFiZWxzKQpgYGAKCmBgYHtyfQpkaWdpdCA8LSB0cmFpbl9pbWFnZXNbNSwsXQpkaWdpdFssODoyMF0gIyBJIGNyb3AgaXQgYSBiaXQsIG90aGVyd2lzZSB0aGUgY29sdW1ucyBkb250IGZpdCBvbiBvbmUgcGFnZQpgYGAKClRvIG1ha2UgaXQgbW9yZSB0YW5naWJsZSwgbGV0cyBwbG90IG9uZToKCmBgYHtyfQpkaWdpdCAlPiUgYXMucmFzdGVyKG1heCA9IDI1NSkgJT4lIHBsb3QoKQpgYGAKCmBgYHtyfQpybShkaWdpdHMpCmBgYAoKCiMjIERlZmluZSB0aGUgYEtlcmFzYCBtb2RlbAoKVGhlIHdvcmtmbG93IHdpbGwgYmUgYXMgZm9sbG93czogCgoxLiBGaXJzdCwgd2UnbGwgZmVlZCB0aGUgbmV1cmFsIG5ldHdvcmsgdGhlIHRyYWluaW5nIGRhdGEsIGB0cmFpbl9pbWFnZXNgIGFuZCBgdHJhaW5fbGFiZWxzYC4gCjIuIFRoZSBuZXR3b3JrIHdpbGwgdGhlbiBsZWFybiB0byBhc3NvY2lhdGUgaW1hZ2VzIGFuZCBsYWJlbHMuIAozLiBGaW5hbGx5LCB3ZSdsbCBhc2sgdGhlIG5ldHdvcmsgdG8gcHJvZHVjZSBwcmVkaWN0aW9ucyBmb3IgYHRlc3RfaW1hZ2VzYCwgYW5kIHdlJ2xsIHZlcmlmeSB3aGV0aGVyIHRoZXNlIHByZWRpY3Rpb25zIG1hdGNoIHRoZSBsYWJlbHMgZnJvbSBgdGVzdF9sYWJlbHNgLgoKTGV0J3MgYnVpbGQgdGhlIG5ldHdvcmsgLSBhZ2FpbiwgcmVtZW1iZXIgdGhhdCB5b3UgYXJlbid0IGV4cGVjdGVkIHRvIHVuZGVyc3RhbmQgZXZlcnl0aGluZyBhYm91dCB0aGlzIGV4YW1wbGUgeWV0LgoKQnVpbGRpbmcgYSBtb2RlbCBpbiBgS2VyYXNgIHRoYXQgY2FuIGJlIGZpdHRlZCBvbiB5b3VyIGRhdGEgaW52b2x2ZXMgdHdvIHN0ZXBzOgoKMS4gRGVmaW5pbmcgdGhlIG5ldHdvcmtzIGFyY2hpdGVjdHVyZSBpbnRlcm1zIG9mIGxheWVycyBhbmQgdGhlaXIgc2hhcGUuCjIuIENvbXBpbGluZyB0aGUgbW9kZWwsIGFuZCBkZWZpbmluZyB0aGUgbG9zcyBmdW5jdGlvbiwgZXZhbHVhdGlvbiBtZXRyaWMsIGFuZCBvcHRpbWl6ZXIuCgpgYGB7cn0KbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gNTEyLCBhY3RpdmF0aW9uID0gInJlbHUiLCBpbnB1dF9zaGFwZSA9IGMoMjggKiAyOCkpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTAsIGFjdGl2YXRpb24gPSAic29mdG1heCIpCmBgYAoKTm90aWNlIHRoYXQgdGhlIGxheWVyIHN0YWNraW5nIGluIGBSYCBpcyBkb25lIHZpYSB0aGUgd2VsbC1rbm93biBgJT4lYCwgaW4gYFB5aHRvbmAgd2l0aCBgLmAuIFRoYXQncyBhYm91dCB0aGUgbWFpbiBkaWZmZXJlbmNlIGJldHdlZW4gYm90aCBpbXBsZW1lbnRhdGlvbnMuIAoKKiBUaGUgY29yZSBidWlsZGluZyBibG9jayBvZiBuZXVyYWwgbmV0d29ya3MgaXMgdGhlICoqbGF5ZXIqKiwgYSBkYXRhLXByb2Nlc3NpbmcgbW9kdWxlIHRoYXQgeW91IGNhbiB0aGluayBvZiBhcyBhIGZpbHRlciBmb3IgZGF0YS4gU29tZSBkYXRhIGdvZXMgaW4sIGFuZCBpdCBjb21lcyBvdXQgaW4gYSBtb3JlIHVzZWZ1bCBmb3JtLiAKKiBTcGVjaWZpY2FsbHksIGxheWVycyBleHRyYWN0IHJlcHJlc2VudGF0aW9ucyBvdXQgb2YgdGhlIGRhdGEgZmVkIGludG8gdGhlbSAtIGhvcGVmdWxseSwgcmVwcmVzZW50YXRpb25zIHRoYXQgYXJlIG1vcmUgbWVhbmluZ2Z1bCBmb3IgdGhlIHByb2JsZW0gYXQgaGFuZC4gCiogTW9zdCBvZiBkZWVwIGxlYXJuaW5nIGNvbnNpc3RzIG9mIGNoYWluaW5nIHRvZ2V0aGVyIHNpbXBsZSBsYXllcnMgdGhhdCB3aWxsIGltcGxlbWVudCBhIGZvcm0gb2YgcHJvZ3Jlc3NpdmUgZGF0YSBkaXN0aWxsYXRpb24uIAoKKiBIZXJlLCBvdXIgbmV0d29yayBjb25zaXN0cyBvZiBhIHNlcXVlbmNlIG9mIHR3byBsYXllcnMsIHdoaWNoIGFyZSAqKmRlbnNlbHkgY29ubmVjdGVkKiogKGBsYXllcl9kZW5zZWApIG5ldXJhbCBsYXllcnMuIAoqIFRoZSBzZWNvbmQgKGFuZCBsYXN0KSBsYXllciBpcyBhIDEwLXdheSBgc29mdG1heGAgbGF5ZXIsIHdoaWNoIG1lYW5zIGl0IHdpbGwgcmV0dXJuIGFuIGFycmF5IG9mIDEwIHByb2JhYmlsaXR5IHNjb3JlcyAoc3VtbWluZyB0byAxKS4gCiogRWFjaCBzY29yZSB3aWxsIGJlIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBjdXJyZW50IGRpZ2l0IGltYWdlIGJlbG9uZ3MgdG8gb25lIG9mIG91ciAxMCBkaWdpdCBjbGFzc2VzLiBTbywgd2UgZGVmaW5lZCBhIG5ldHdvcmsgd2l0aCBvdmVyYWxsIDYzNCBjZWxscywgY29uc2lzdGluZyBvZjoKICAgMS4gaW5wdXQgbGF5ZXI6IDI4eDI4ID0gNTEyIGNlbGxzCiAgIDIuIGludGVybWVkaWF0ZSBsYXllciA6IDI4eDI4ICA9IDUxMiBjZWxscwogICAzLiBPdXRwdXQgbGF5ZXI6IDEwIGNlbGxzCgoqIFRvIG1ha2UgdGhlIG5ldHdvcmsgcmVhZHkgZm9yIHRyYWluaW5nLCB3ZSBuZWVkIHRvIHBpY2sgdGhyZWUgbW9yZSB0aGluZ3MsIGFzIHBhcnQgb2YgdGhlIGNvbXBpbGF0aW9uIHN0ZXA6CiAgIDEuICoqTG9zcyBmdW5jdGlvbjoqKiBIb3cgdGhlIG5ldHdvcmsgd2lsbCBiZSBhYmxlIHRvIG1lYXN1cmUgaXRzIHBlcmZvcm1hbmNlIG9uIHRoZSB0cmFpbmluZyBkYXRhLCBhbmQgdGh1cyBob3cgaXQgd2lsbCBiZSBhYmxlIHRvIHN0ZWVyIGl0c2VsZiBpbiB0aGUgcmlnaHQgZGlyZWN0aW9uLgogICAyLiAqKk9wdGltaXplcjoqKiBUaGUgbWVjaGFuaXNtIHRocm91Z2ggd2hpY2ggdGhlIG5ldHdvcmsgd2lsbCB1cGRhdGUgaXRzZWxmIGJhc2VkIG9uIHRoZSBkYXRhIGl0IHNlZXMgYW5kIGl0cyBsb3NzIGZ1bmN0aW9uLgogICAzLiAqKk1ldHJpY3MqKiBIZXJlLCB3ZSdsbCBvbmx5IGNhcmUgYWJvdXQgYWNjdXJhY3kgKHRoZSBmcmFjdGlvbiBvZiB0aGUgaW1hZ2VzIHRoYXQgd2VyZSBjb3JyZWN0bHkgY2xhc3NpZmllZCkuCgoqIFdoaWxlIHdlIGFyZSBhbHJlYWR5IGZhbWlsaWFyIHdpdGggZGVmaW5pbmcgbWV0cmljcyB0byBvcHRpbWl6ZSwgZGVmaW5pbmcgYW4gb3B0aW1pemVyIGFuZCBsb3NzIGZ1bmN0aW9uIGlzIG5ldy4gV2Ugd2lsbCBkaWcgaW50byB0aGF0IGxhdGVyLiAKKiBOb3RpY2UgdGhhdCB0aGUgYGNvbXBpbGUoKWAgZnVuY3Rpb24gbW9kaWZpZXMgdGhlIG5ldHdvcmsgaW4gcGxhY2UuIFdlIHdpbGwgdGFsayBhYm91dCBhbGwgb2YgdGhlbSBsYXRlciBpbiBhIGJpdCBtb3JlIGRldGFpbC4KCmBgYHtyfQpuZXR3b3JrICU+JSBjb21waWxlKAogIG9wdGltaXplciA9ICJybXNwcm9wIiwKICBsb3NzID0gImNhdGVnb3JpY2FsX2Nyb3NzZW50cm9weSIsCiAgbWV0cmljcyA9IGMoImFjY3VyYWN5IikKKQpgYGAKCkxldHMgaW5zcGVjdCBvdXIgZmluYWwgc2V0dXA6CgpgYGB7cn0Kc3VtbWFyeShuZXR3b3JrKQpgYGAKCldlbGwnIHdlIHNlZSB0aGF0IGEgbmV0d29yayBvZiB0aGlzIHNpemUgaGFzIHF1aXRlIGEgbGFyZ2UgbnVtYmVyIG9mIHRyYWluYWJsZSBwYXJhbWV0ZXJzIChhbGwgZWRnZS13ZWlnaHRzLCBtZWFuaW5nIDUxMng1MTIgKyA1MTJ4MTApLgoKIyMgUHJlcHJvY2VzcyB0aGUgZGF0YQoKKiBCZWZvcmUgdHJhaW5pbmcgdGhlIG1vZGVsLCBwcmVwcm9jZXNzIHRoZSBkYXRhIGJ5IHJlc2hhcGluZyBpdCBpbnRvIHRoZSBzaGFwZSB0aGUgbmV0d29yayBleHBlY3RzIGFuZCBzY2FsaW5nIGl0IHNvIHRoYXQgYWxsIHZhbHVlcyBhcmUgaW4gdGhlIGBbMCwgMV1gIGludGVydmFsLiAKKiBQcmV2aW91c2x5LCBvdXIgdHJhaW5pbmcgaW1hZ2VzIHdlcmUgc3RvcmVkIGluIGFuIDNkIGFycmF5IG9mIHNoYXBlIGAoNjAwMDAsIDI4LCAyOClgIG9mIHR5cGUgaW50ZWdlciB3aXRoIHZhbHVlcyBpbiB0aGUgYFswLCAyNTVdYCBpbnRlcnZhbC4gCiogV2UgdHJhbnNmb3JtIGl0IGludG8gYSBkb3VibGUgYXJyYXkgb2Ygc2hhcGUgYCg2MDAwMCwgMjggKiAyOClgIHdpdGggdmFsdWVzIGJldHdlZW4gYDBgIGFuZCBgMWAuCgpgYGB7cn0KdHJhaW5faW1hZ2VzIDwtIGFycmF5X3Jlc2hhcGUodHJhaW5faW1hZ2VzLCBjKDYwMDAwLCAyOCAqIDI4KSkKdHJhaW5faW1hZ2VzIDwtIHRyYWluX2ltYWdlcyAvIDI1NSAjIFRvIHNjYWxlIGJldHdlZW4gMCBhbmQgMQoKdGVzdF9pbWFnZXMgPC0gYXJyYXlfcmVzaGFwZSh0ZXN0X2ltYWdlcywgYygxMDAwMCwgMjggKiAyOCkpCnRlc3RfaW1hZ2VzIDwtIHRlc3RfaW1hZ2VzIC8gMjU1ICMgVG8gc2NhbGUgYmV0d2VlbiAwIGFuZCAxCmBgYAoKCiogTm90ZSB0aGF0IHdlIHVzZSB0aGUgYGFycmF5X3Jlc2hhcGUoKWAgcmF0aGVyIHRoYW4gdGhlIGBkaW0oKWAgZnVuY3Rpb24gdG8gcmVzaGFwZSB0aGUgYXJyYXkuIEkgZXhwbGFpbiB3aHkgbGF0ZXIsIHdoZW4gd2UgdGFsayBhYm91dCAqKnRlbnNvciByZXNoYXBpbmcqKi4KKiBMYXN0bHksIHdlIGFsc28gbmVlZCB0byBjYXRlZ29yaWNhbGx5IGVuY29kZSB0aGUgbGFiZWxzLgoKYGBge3J9CnRyYWluX2xhYmVscyA8LSB0b19jYXRlZ29yaWNhbCh0cmFpbl9sYWJlbHMpCnRlc3RfbGFiZWxzIDwtIHRvX2NhdGVnb3JpY2FsKHRlc3RfbGFiZWxzKQpgYGAKCiMjIFJ1biB0aGUgbmV0d29yawoKV2UncmUgbm93IHJlYWR5IHRvIHRyYWluIHRoZSBuZXR3b3JrIHZpYSBgS2VyYXNgIGBmaXQoKWAgZnVuY3Rpb24uIFdlIHNhdmUgdGhlIG91dHB1dCBpbiBhbiBvYmplY3Qgd2UgY2FsbCBgaGlzdG9yeS5uZXRgLiAKCmBgYHtyfQpzZXQuc2VlZCgxMzM3KQpoaXN0b3J5Lm5ldCA8LSBuZXR3b3JrICU+JSBmaXQoeCA9IHRyYWluX2ltYWdlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gdHJhaW5fbGFiZWxzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVwb2NocyA9IDEwLCAjIEhvdyBvZnRlbiBzaGFsbCB3ZSByZS1ydW4gdGhlIG1vZGVsIG9uIHRoZSB3aG9sZSBzYW1wbGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhdGNoX3NpemUgPSAxMjgsICMgSG93IG1hbnkgb2JzZXJ2YXRpb25zIHNob3VsZCBiZSBpbmNsdWRlZCBpbiBldmVyeSBiYXRjaAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMjUgIyBJZiB3ZSB3YW50IHRvIGRvIGEgIGNyb3NzLXZhbGlkYXRpb24gaW4gdGhlIHRyYWluaW5nCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCmBgYAoKKiBUd28gcXVhbnRpdGllcyBhcmUgZGlzcGxheWVkIGluIHRoZSBsb2cgZHVyaW5nIHRyYWluaW5nOiB0aGUgbG9zcyBhbmQgYWNjdXJhY3kgb2YgdGhlIG5ldHdvcmsgb3ZlciB0aGUgdHJhaW5pbmcgZGF0YSBkdXJpbmcgdGhlIHN1YnNlcXVlbnQgZXBvY2hzIChuZXcgdHJhaW5pbmcgcnVucyBhZnRlciByZS1hZGp1c3RpbmcgdGhlIHdlaWdodHMpLiAKKiBOb3RpY2UgdGhhdCB0aGUgbWVhc3VyZXMgaW1wcm92ZSBpbiBldmVyeSBlcG9jaC4gV2UgcXVpY2tseSByZWFjaCBhbiBhY2N1cmFjeSAoOTguOSUgb24gdGhlIHRyYWluaW5nIGRhdGEuIAoqIE5vdGljZSB0aGF0IGBmaXQoKWAgYWRqdXN0cyB0aGUgd2VpZ2h0cyBvZiB0aGUgbmV0d29yayB3aXRob3V0IGV4cGxpY2x5IGFzc2lnbmluZyBpdCBpbnRvIGEgbmV3IG9iamVjdC4gYGhpc3RvcnkubmV0YCB0aGVyZWZvcmUgb25seSBjb250YWlucyB0aGUgaGlzdG9yeSBvZiB0aGUgbW9kZWxzIHByZWRpY3Rpb24gbWV0cmljcyB0aHJvdWdoIHRoZSBkaWZmZXJlbnQgZXBvY2hzLCBpbiBjYXNlIHdlIHdvdWxkIGxpa2UgdG8gaW5zcGVjdCBpdC4gTGV0J3MgdGFrZSBhIGxvb2s6CgpgYGB7cn0KaGlzdG9yeS5uZXQKYGBgCgpgYGB7cn0KaGlzdG9yeS5uZXQgJT4lIGdsaW1wc2UoKQpgYGAKCldlIGNhbiBhbHNvIHZpc3VhbGl6ZSB0aGVzZSBtZXRyaWNzIHRocm91Z2ggdGhlIGVwb2NzLgoKYGBge3J9Cmhpc3RvcnkubmV0ICU+JSBwbG90KHNtb290aCA9IFRSVUUpCmBgYAoKKiBJbnRlcmVzdGluZ2x5LCB3ZSBhbHJlYWR5IHNlZSB0aGF0IG91ciBtb2RlbCBvdmVyZml0cy4gTWVhbmluZywgd2hpbGUgYWNjdXJhY3kgaW4gb3VyIHRyYWluaW5nIHNldCB0ZW5kcyB0byBmdXJ0aGVyIGluY3JlYXNlIHRocm91Z2ggdGhlIGVwb2NzLCBpdCBzdGFydHMgb3ZlciB0aW1lIHRvIGRlY3JlYXNlIGluIG91ciB2YWxpZGF0aW9uIHNldC4gVAoqIGhlcmUgYXJlIGRpZmZlcmVudCB3YXlzIHRvIGZpZ2h0IHRoYXQsIHN1Y2ggYXMgZGVmaW5pbmcgYSBgbGF5ZXJfZHJvcG91dGAsIG9yIHRvIHRlbGwgdGhlIG1vZGVsIHRvIHBpY2sgc3RvcCBydW5uaW5nIGZ1cnRoZXIgZXBvY3MgYXMgc29vbiBhcyB0aGUgdmFsaWRhdGlvbiBhY2N1cmFjeSBkcm9wcy4gSG93ZXZlciwgd2Ugd2lsbCBmb3Igbm93IGp1c3QgbW92ZSBvbi4KKiBGb3Igbm93LCBsZXQncyBjaGVjayBpZiB0aGUgbW9kZWwgcGVyZm9ybXMgd2VsbCBvdXQtb2Ytc2FtcGxlIG9uIHRoZSB0ZXN0LXNldDoKCmBgYHtyfQptZXRyaWNzIDwtIG5ldHdvcmsgJT4lIGV2YWx1YXRlKHRlc3RfaW1hZ2VzLCB0ZXN0X2xhYmVscykKYGBgCgpgYGB7cn0KbWV0cmljcwpgYGAKCiogT2ssIHNvIGZhciBzbyBnb29kLiBJIHRoaW5rIHRoYXQncyBhIGRlY2VudCBhY2N1cmFjeSBmb3Igc3VjaCBhbiBhZC1ob2MgbW9kZWwuIFdoaXRoIGEgYml0IG9mIHRpbmtlcmluZywgd2Ugc3VyZWx5IGNvdWxkIGdldCBpdCB0byA5OSUuIEJ1dCB0aGF0cyBhIHRhc2sgZm9yIGFub3RoZXIgdGltZS4uLgoqIExldHMgZ28gYmFjayB0byBiYXNpYyBhbmQgcmV2aXNlIGEgYml0IHdoYXQgd2UgaGF2ZSBkb25lIHNvIGZhci4KCiMgRGF0YSByZXByZXNlbnRhdGlvbnMgZm9yIG5ldXJhbCBuZXR3b3JrcwoKKiBJbiB0aGUgcHJldmlvdXMgZXhhbXBsZSwgd2Ugc3RhcnRlZCBmcm9tIGRhdGEgc3RvcmVkIGluIG11bHRpZGltZW5zaW9uYWwgYXJyYXlzLCBhbHNvIGNhbGxlZCAqKnRlbnNvcnMqKi4gCiogSW4gZ2VuZXJhbCwgbW9zdCBjdXJyZW50IE1MIHN5c3RlbXMgdXNlIHRlbnNvcnMgYXMgdGhlaXIgYmFzaWMgZGF0YSBzdHJ1Y3R1cmUuIFRlbnNvcnMgYXJlIGZ1bmRhbWVudGFsIHRvIHRoZSBmaWVsZC1zbyBmdW5kYW1lbnRhbCB0aGF0IEdvb2dsZSdzICoqVGVuc29yRmxvdyoqIHdhcyBuYW1lZCBhZnRlciB0aGVtLiBTbyB3aGF0J3MgYSB0ZW5zb3I/CiogVGVuc29ycyBhcmUgYSBnZW5lcmFsaXphdGlvbiBvZiB2ZWN0b3JzIGFuZCBtYXRyaWNlcyB0byBhbiBhcmJpdHJhcnkgbnVtYmVyIG9mIGRpbWVuc2lvbnMgKG5vdGUgdGhhdCBpbiB0aGUgY29udGV4dCBvZiB0ZW5zb3JzLCBhIGRpbWVuc2lvbiBpcyBvZnRlbiBjYWxsZWQgYW4gYXhpcykuIAoqIEluIGBSYCwgdmVjdG9ycyBhcmUgdXNlZCB0byBjcmVhdGUgYW5kIG1hbmlwdWxhdGUgMUQgdGVuc29ycywgYW5kIG1hdHJpY2VzIGFyZSB1c2VkIGZvciAyRCB0ZW5zb3JzLiBGb3IgaGlnaGVyLWxldmVsIGRpbWVuc2lvbnMsIGFycmF5IG9iamVjdHMgKHdoaWNoIHN1cHBvcnQgYW55IG51bWJlciBvZiBkaW1lbnNpb25zKSBhcmUgdXNlZC4KCiMjIEtleSB0ZW5zb3ItYXR0cmlidXRlcwoKQSB0ZW5zb3IgaXMgZGVmaW5lZCBieSB0aHJlZSBrZXkgYXR0cmlidXRlczoKCjEuICoqTnVtYmVyIG9mIGF4ZXMqKiAocmFuayk6IEZvciBpbnN0YW5jZSwgYSAzRCB0ZW5zb3IgaGFzIHRocmVlIGF4ZXMsIGFuZCBhIG1hdHJpeCBoYXMgdHdvIGF4ZXMuCjIuICoqU2hhcGU6KiogVGhpcyBpcyBhbiBpbnRlZ2VyIHZlY3RvciB0aGF0IGRlc2NyaWJlcyBob3cgbWFueSBkaW1lbnNpb25zIHRoZSB0ZW5zb3IgaGFzIGFsb25nIGVhY2ggYXhpcy4gCjMuICoqRGF0YSB0eXBlOioqIFRoaXMgaXMgdGhlIHR5cGUgb2YgdGhlIGRhdGEgY29udGFpbmVkIGluIHRoZSB0ZW5zb3I7IGZvciBpbnN0YW5jZSwgYSB0ZW5zb3IncyB0eXBlIGNvdWxkIGJlIGludGVnZXIgb3IgZG91YmxlLiBPbiByYXJlIG9jY2FzaW9ucywgeW91IG1heSBzZWUgYSBjaGFyYWN0ZXIgdGVuc29yLiAKCiMjIFRlbnNvciByZXNoYXBpbmcKClJlbWVtYmVyIHRoYXQgd2UgYmVmb3JlIGRpZCBub3QgdXNlIHRoZSBgZGltKClgIGJ1dCB0aGUgYGFycmF5X3Jlc2hhcGUoKWAgZnVuY3Rpb24gdG8gbWFuaXB1bGF0ZSBvdXIgaW5wdXQgdGVuc29ycy4KCmBgYHtyfQp0cmFpbl9pbWFnZXMgPC0gYXJyYXlfcmVzaGFwZSh0cmFpbl9pbWFnZXMsIGMoNjAwMDAsIDI4ICogMjgpKQpgYGAKCmBgYHtyfQpzdHIodHJhaW5faW1hZ2VzKQpgYGAKCmBgYHtyfQpkaW0odHJhaW5faW1hZ2VzKQpgYGAKCiogVGhpcyBpcyBhbiBgUmAgc3BlY2lmaWMgdGhpbmd5LCBzbyB0aGF0IHRoZSBkYXRhIGlzIHJlaW50ZXJwcmV0ZWQgdXNpbmcgcm93LW1ham9yIHNlbWFudGljcyAoYXMgb3Bwb3NlZCB0byBgUmBzIGRlZmF1bHQgY29sdW1uLW1ham9yIHNlbWFudGljcyksIHdoaWNoIGlzIGluIHR1cm4gY29tcGF0aWJsZSB3aXRoIHRoZSB3YXkgdGhlIG51bWVyaWNhbCBsaWJyYXJpZXMgY2FsbGVkIGJ5IGBLZXJhc2AgKGBOdW1QeWAsIGBUZW5zb3JGbG93YCwgYW5kIHNvIG9uKSBpbnRlcnByZXQgYXJyYXkgZGltZW5zaW9ucy4gCiogWW91IHNob3VsZCBhbHdheXMgdXNlIHRoZSBgYXJyYXlfcmVzaGFwZSgpYCBmdW5jdGlvbiB3aGVuIHJlc2hhcGluZyBgUmAgYXJyYXlzIHRoYXQgd2lsbCBiZSBwYXNzZWQgdG8gYEtlcmFzYC4KKiBSZXNoYXBpbmcgYSB0ZW5zb3IgbWVhbnMgcmVhcnJhbmdpbmcgaXRzIHJvd3MgYW5kIGNvbHVtbnMgdG8gbWF0Y2ggYSB0YXJnZXQgc2hhcGUuIAoqIE5hdHVyYWxseSwgdGhlIHJlc2hhcGVkIHRlbnNvciBoYXMgdGhlIHNhbWUgdG90YWwgbnVtYmVyIG9mIGNvZWZmaWNpZW50cyBhcyB0aGUgaW5pdGlhbCB0ZW5zb3IuIExldHMgZG8gYSBzaW1wbGUgZXhhbXBsZXM6CgpgYGB7cn0KeCA8LSBtYXRyaXgoYygwOjUpLAogICAgICAgICAgICBucm93ID0gMywgbmNvbCA9IDIsIGJ5cm93ID0gVFJVRSkKeApgYGAKCmBgYHtyfQp4IDwtIGFycmF5X3Jlc2hhcGUoeCwgZGltID0gYygzLCAyKSkKeApgYGAKCmBgYHtyfQp4IDwtIGFycmF5X3Jlc2hhcGUoeCwgZGltID0gYygyLCAzKSkKeApgYGAKCiogQSBzcGVjaWFsIGNhc2Ugb2YgcmVzaGFwaW5nIHRoYXQncyBjb21tb25seSBlbmNvdW50ZXJlZCBpcyAqdHJhbnNwb3NpdGlvbiouIAoqIFRyYW5zcG9zaW5nIGEgbWF0cml4IG1lYW5zIGV4Y2hhbmdpbmcgaXRzIHJvd3MgYW5kIGl0cyBjb2x1bW5zLCBzbyB0aGF0IGB4W2ksXWAgYmVjb21lcyBgeFssIGldYC4gVGhlIGB0KClgIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHRvIHRyYW5zcG9zZSBhIG1hdHJpeDoKCgpgYGB7cn0KeCA8LSB0KHgpCngKYGBgCgpgYGB7cn0Kcm0oeCkKYGBgCgoKIyMgR2VvbWV0cmljIGludGVycHJldGF0aW9uIG9mIHRlbnNvciBvcGVyYXRpb25zCgpgYGB7cixldmFsPUZBTFNFfQpsYXllciA8LSBsYXllcl9kZW5zZSh1bml0cyA9IDMyLCBpbnB1dF9zaGFwZSA9IGMoNzg0KSkKYGBgCgoqIFdlJ3JlIGNyZWF0aW5nIGEgbGF5ZXIgdGhhdCB3aWxsIG9ubHkgYWNjZXB0IGFzIGlucHV0IDJEIHRlbnNvcnMgd2hlcmUgdGhlIGZpcnN0IGRpbWVuc2lvbiBpcyA3ODQgKHRoZSBmaXJzdCBkaW1lbnNpb24sIHRoZSBiYXRjaCBkaW1lbnNpb24sIGlzIHVuc3BlY2lmaWVkLCBhbmQgdGh1cyBhbnkgdmFsdWUgd291bGQgYmUgYWNjZXB0ZWQpLiBUaGlzIGxheWVyIHdpbGwgcmV0dXJuIGEgdGVuc29yIHdoZXJlIHRoZSBmaXJzdCBkaW1lbnNpb24gaGFzIGJlZW4gdHJhbnNmb3JtZWQgdG8gYmUgMzIuCiogVGh1cyB0aGlzIGxheWVyIGNhbiBvbmx5IGJlIGNvbm5lY3RlZCB0byBhIGRvd25zdHJlYW0gbGF5ZXIgdGhhdCBleHBlY3RzIDMyLWRpbWVuc2lvbmFsIHZlY3RvcnMgYXMgaXRzIGlucHV0LiBXaGVuIHVzaW5nIEtlcmFzLCB5b3UgZG9uJ3QgaGF2ZSB0byB3b3JyeSBhYm91dCBjb21wYXRpYmlsaXR5LCBiZWNhdXNlIHRoZSBsYXllcnMgeW91IGFkZCB0byB5b3VyIG1vZGVscyBhcmUgZHluYW1pY2FsbHkgYnVpbHQgdG8gbWF0Y2ggdGhlIHNoYXBlIG9mIHRoZSBpbmNvbWluZyBsYXllci4gCiogRm9yIGluc3RhbmNlLCBzdXBwb3NlIHlvdSB3cml0ZSB0aGUgZm9sbG93aW5nOgoKYGBge3J9Cm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDMyLCBpbnB1dF9zaGFwZSA9IGMoNzg0KSkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAzMikKYGBgCgpgYGB7cn0KbW9kZWwKYGBgCgpgYGB7cn0KIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImFuZHJpZS9kZWVwdml6IikKbGlicmFyeShkZWVwdml6KQpwbG90X21vZGVsKG1vZGVsKQpgYGAKCiogVGhlIHNlY29uZCBsYXllciBkaWRuJ3QgcmVjZWl2ZSBhbiBpbnB1dCBzaGFwZSBhcmd1bWVudC1pbnN0ZWFkLCBpdCBhdXRvbWF0aWNhbGx5IGluZmVycmVkIGl0cyBpbnB1dCBzaGFwZSBhcyBiZWluZyB0aGUgb3V0cHV0IHNoYXBlIG9mIHRoZSBsYXllciB0aGF0IGNhbWUgYmVmb3JlLgoKKiBQaWNraW5nIHRoZSByaWdodCBuZXR3b3JrIGFyY2hpdGVjdHVyZSBpcyBtb3JlIGFuIGFydCB0aGFuIGEgc2NpZW5jZTsgYW5kIGFsdGhvdWdoIHRoZXJlIGFyZSBzb21lIGJlc3QgcHJhY3RpY2VzIGFuZCBwcmluY2lwbGVzIHlvdSBjYW4gcmVseSBvbiwgb25seSBwcmFjdGljZSBjYW4gaGVscCB5b3UgYmVjb21lIGEgcHJvcGVyIG5ldXJhbC1uZXR3b3JrIGFyY2hpdGVjdC4gCiogSGVyZSwgd2Ugd2lsbCBzaW1pdCBvdXJzZWxmIHRvIGEgc2ltcGxlIGZlZWQtZm9yd2FyZCBuZXR3b3JrLCB3aGVyZSBldmVyeSBsYXllciBpcyBvbmx5IGNvbm5lY3RlZCB0byB0aGUgZm9sbG93aW5nIG9uZS4gRm9yIG5vdywgdGhlcmUgYXJlIHR3byBrZXkgYXJjaGl0ZWN0dXJlIGRlY2lzaW9ucyB0byBiZSBtYWRlIGFib3V0IHN1Y2ggYSBzdGFjayBvZiBkZW5zZSBsYXllcnM6CiAgIDEuIEhvdyBtYW55IGxheWVycyB0byB1c2U/CiAgIDIuIEhvdyBtYW55IGhpZGRlbiB1bml0cyB0byBjaG9vc2UgZm9yIGVhY2ggbGF5ZXI/CiAgIDMuIFdoaWNoIGFjdGl2YXRpb24gZnVuY3Rpb24gdG8gdXNlPwoKYGBge3J9CnJtKGxheWVyLCBtb2RlbCkKYGBgCgoKIyMgQWN0aXZhdGlvbiBmdW5jdGlvbnMKCiogQXMgd2UgYWxyZWFkeSBzYXcsIHdlIGNhbiBkZWZpbmUgYWN0aXZhdGlvbiBmdW5jdGlvcG5zIGZvciB0aGUgZGlmZmVyZW50IGFjdGl2YXRpb24gZnVuY3Rpb25zIGZvciBldmVyeSBsYXllci4gV2hpbGUgd2Ugd2l0aGluIHRoZSBpbnRlcm1lZGlhdGUgbGF5ZXJzIHNlbGRvbWVseSBzd2l0Y2ggYmV0d2VlbiBkaWZmZXJlbnQgYWN0aXZhdGlvbiBmdW5jdGlvbnMsIHRoZSBvbmUgd2UgZGVmaW5lIGZvciB0aGUgb3V0cHV0IGxheWVyIGNyaXRpY2FsbHkgZGVwZW5kcyBvbiB0aGUgc2hhcGUgb2Ygb3VyIGRlc2lyZWQgb3V0cHV0IGRhdGEuCiogQSBicmllZiByZW1pbmRlcjogQWN0aXZhdGlvbiBmdW5jdGlvbnMgdHJhbnNmb3JtIHRoZSBpbnB1dCB3ZWlnaHRzIG9mIGEgY2VsbCB0byBpdHMgb3V0cHV0LiBXaXRob3V0IHRoZW0sIHRoZSBkZW5zZSBsYXllciB3b3VsZCBjb25zaXN0IG9mIHR3byBsaW5lYXIgb3BlcmF0aW9ucy1hIGRvdCBwcm9kdWN0IGFuZCBhbiBhZGRpdGlvbjoKKiBJbiBvcmRlciB0byBnZXQgYWNjZXNzIHRvIGEgbXVjaCByaWNoZXIgaHlwb3RoZXNpcyBzcGFjZSB0aGF0IHdvdWxkIGJlbmVmaXQgZnJvbSBkZWVwIHJlcHJlc2VudGF0aW9ucywgeW91IG5lZWQgYSBub24tbGluZWFyaXR5LCBvciBhY3RpdmF0aW9uIGZ1bmN0aW9uLiAKICAgKiBgcmVsdWAgaXMgdGhlIG1vc3QgcG9wdWxhciBhY3RpdmF0aW9uIGZ1bmN0aW9uIGluIGRlZXAgbGVhcm5pbmcsIGJ1dCB0aGVyZSBhcmUgbWFueSBvdGhlciBjYW5kaWRhdGVzLCB3aGljaCBhbGwgY29tZSB3aXRoIHNpbWlsYXJseSBzdHJhbmdlIG5hbWVzOiBgcHJlbHVgLCBgZWx1YCwgYW5kIHNvIG9uLiBBIGByZWx1YCAocmVjdGlmaWVkIGxpbmVhciB1bml0KSBpcyBhIGZ1bmN0aW9uIG1lYW50IHRvIHplcm8gb3V0IG5lZ2F0aXZlIHZhbHVlcywgYW5kIGNvbW1vbmx5IHVzZWQgZm9yIGludGVybWVkaWF0ZSBsYXllcnMgKGZvcm1lcmx5LCBhbG1vc3QgYWxsIGxheWVycyB3aGVyZSBtb2RlbGxlZCB3aXRoIGBzaWdtb2lkYCwgYnV0IG5vd2FkYXlzIGl0cyBwcm92ZW4gdGhhdCBmb3IgaW50ZXJtZWRpYXRlIGxheWVycyBgcmVsdWAgbW9zdGx5IHdvcmtzIGJldHRlcikuCgohW10oaHR0cHM6Ly9zZHMtYWF1LmdpdGh1Yi5pby9TRFMtbWFzdGVyLzAwX21lZGlhL0RMX2FjdGl2YXRpb25fMS5qcGcpe3dpZHRoPTUwMHB4fQoKT3VyIG91dHB1dCBsYXllciwgaG93ZXZlciwgc2hvdWxkIG1vZGVsIGEgYmluYXJ5IGNob2ljZSAoeWVzL25vIGNsYXNzaWZpY2F0aW9uKS4gRm9yIHN1Y2ggYSBtb2RlbCwgd2Ugd291bGQgaW4gYSAyLWNsYXNzIHByb2JsZW0gY29tbW9ubHkgdXNlIGEgYSBgc2lnbW9pZGAgZnVuY3Rpb24sIHdoaWNoIHdlIGFscmVhZHkga25vdyBmcm9tIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzLiBJdCAic3F1YXNoZXMiIGFyYml0cmFyeSB2YWx1ZXMgaW50byB0aGUgYFswLCAxXWAgaW50ZXJ2YWwsIG91dHB1dHRpbmcgc29tZXRoaW5nIHRoYXQgY2FuIGJlIGludGVycHJldGVkIGFzIGEgcHJvYmFiaWxpdHkuCgohW10oaHR0cHM6Ly9zZHMtYWF1LmdpdGh1Yi5pby9TRFMtbWFzdGVyLzAwX21lZGlhL0RMX2FjdGl2YXRpb25fMi5qcGcpe3dpZHRoPTUwMHB4fQoKSG93ZXZlciwgc2luY2Ugd2UgaGF2ZSBhIG11bHRpLWNsYXNzIHByZWRpY3Rpb24gcHJvYmxlbSwgd2UgY2hvb3NlIGBzb2Z0bWF4YCwgd2hpY2ggc3F1YXNoZXMgdGhlIG91dHB1dHMgb2YgZWFjaCB1bml0IHRvIGJlIGJldHdlZW4gYDBgIGFuZCBgMWAsIGp1c3QgbGlrZSBhIGBzaWdtb2lkYCwgYnV0IGl0IGFsc28gZGl2aWRlcyBlYWNoIG91dHB1dCBzdWNoIHRoYXQgdGhlIHRvdGFsIHN1bSBvZiB0aGUgb3V0cHV0cyBpcyBlcXVhbCB0byBgMWAuIFRoZSBvdXRwdXQgaXMgZXF1aXZhbGVudCB0byBhIGNhdGVnb3JpY2FsIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbiwgaXQgdGVsbHMgeW91IHRoZSBwcm9iYWJpbGl0eSB0aGF0IGFueSBvZiB0aGUgY2xhc3NlcyBhcmUgdHJ1ZS4KCklmIHlvdSBhcmUgaW50ZXJlc3RlZCByZWdhcmRpbmcgdGhlIGRpZmZlcmVudCB0eXBlcyBvZiBsYXllcnMgaW4gYEtlcmFzYCwgY2hlY2sgW3RoZSByZWZlcmVuY2Ugc2l0ZV0oaHR0cHM6Ly9rZXJhcy5yc3R1ZGlvLmNvbS9yZWZlcmVuY2UvaW5kZXguaHRtbCkgd2l0aCBhbGwgbGF5ZXJzIGltcGxlbWVudGVkLiBGdXJ0aGVybW9yZSwgdHlwZXMgb2YgYWN0aXZhdGlvbiBmdW5jdGlvbnMgYXJlIGRpc2N1c3NlZCBbSEVSRV0oaHR0cHM6Ly9tZWRpdW0uY29tL3RoZS10aGVvcnktb2YtZXZlcnl0aGluZy91bmRlcnN0YW5kaW5nLWFjdGl2YXRpb24tZnVuY3Rpb25zLWluLW5ldXJhbC1uZXR3b3Jrcy05NDkxMjYyODg0ZTApCgoKIyMgTG9zcyBmdW5jdGlvbnMgYW5kIG9wdGltaXplcnM6IGtleXMgdG8gY29uZmlndXJpbmcgdGhlIGxlYXJuaW5nIHByb2Nlc3MKCk9uY2UgdGhlIG5ldHdvcmsgYXJjaGl0ZWN0dXJlIGlzIGRlZmluZWQsIHlvdSBzdGlsbCBoYXZlIHRvIGNob29zZSB0d28gbW9yZSB0aGluZ3M6CgoqICoqTG9zcyBmdW5jdGlvbioqIChvYmplY3RpdmUgZnVuY3Rpb24pOiBUaGUgcXVhbnRpdHkgdGhhdCB3aWxsIGJlIG1pbmltaXplZCBkdXJpbmcgdHJhaW5pbmcuIEl0IHJlcHJlc2VudHMgYSBtZWFzdXJlIG9mIHN1Y2Nlc3MgZm9yIHRoZSB0YXNrIGF0IGhhbmQuCiogKipPcHRpbWl6ZXI6KiogRGV0ZXJtaW5lcyBob3cgdGhlIG5ldHdvcmsgd2lsbCBiZSB1cGRhdGVkIGJhc2VkIG9uIHRoZSBsb3NzIGZ1bmN0aW9uLiBJdCBpbXBsZW1lbnRzIGEgc3BlY2lmaWMgdmFyaWFudCBvZiBzdG9jaGFzdGljIGdyYWRpZW50IGRlc2NlbnQgKFNHRCkuCgoqIENob29zaW5nIHRoZSByaWdodCBvYmplY3RpdmUgYW5kIGZ1bmN0aW9uIGZvciB0aGUgcmlnaHQgcHJvYmxlbSBpcyBleHRyZW1lbHkgaW1wb3J0YW50OiB5b3VyIG5ldHdvcmsgd2lsbCB0YWtlIGFueSBzaG9ydGN1dCBpdCBjYW4sIHRvIG1pbmltaXplIHRoZSBsb3NzOyBzbyBpZiB0aGUgb2JqZWN0aXZlIGRvZXNuJ3QgZnVsbHkgY29ycmVsYXRlIHdpdGggc3VjY2VzcyBmb3IgdGhlIHRhc2sgYXQgaGFuZCwgeW91ciBuZXR3b3JrIHdpbGwgZW5kIHVwIGRvaW5nIHRoaW5ncyB5b3UgbWF5IG5vdCBoYXZlIHdhbnRlZC4gCgoqIFtIRVJFXShodHRwczovL3Rvd2FyZHNkYXRhc2NpZW5jZS5jb20vdW5kZXJzdGFuZGluZy1kaWZmZXJlbnQtbG9zcy1mdW5jdGlvbnMtZm9yLW5ldXJhbC1uZXR3b3Jrcy1kZDFlZDAyNzQ3MTgpIHlvdSBmaW5kIGEgYnJpZWYgb3ZlcnZpZXcgb24gZGlmZmVyZW50IGxvc3MgZnVuY3Rpb25zLiBGb3J0dW5hdGVseSwgd2hlbiBpdCBjb21lcyB0byBjb21tb24gcHJvYmxlbXMgc3VjaCBhcyBjbGFzc2lmaWNhdGlvbiwgcmVncmVzc2lvbiwgYW5kIHNlcXVlbmNlIHByZWRpY3Rpb24sIHRoZXJlIGFyZSBzaW1wbGUgZ3VpZGVsaW5lcyB5b3UgY2FuIGZvbGxvdyB0byBjaG9vc2UgdGhlIGNvcnJlY3QgbG9zcy4gCiogVGFrZSB0aGlzIHJ1bGUtb2YtdGh1bWIgdGFibGUgYXMgYSBnb29kIHN0YXJ0ZXI6CgohW10oaHR0cHM6Ly9zZHMtYWF1LmdpdGh1Yi5pby9TRFMtbWFzdGVyLzAwX21lZGlhL0RMX3RhYmxlX2Fubl9jb25maWcucG5nKXt3aWR0aD03NTBweH0KCiogV2l0aCByZXNwZWN0IHRvIHRoZSBgb3B0aW1pemVyYDogV2Ugd2lsbCBjb3ZlciB0aGF0IGxhdGVyLiBUaGVyZSBhcmUgYSBidW5jaCBvZiBkaWZmZXJlbnQgYXJvdW5kLCBtb3N0IHZhcmlhbnRzIG9mIHRoZSAqKlN0b2NoYXN0aWMgR3JhZGllbnQgRGVzY2VudCAoU0dEKSoqLCAqKkJhdGNoICh2YW5pbGxhKSBHcmFkaWVudCBEZXNjZW50KiosIGFuZCAqKk1pbmktQmF0Y2ggR3JhZGllbnQgRGVzY2VudCoqLiAKKiBbSEVSRV0oaHR0cHM6Ly9tZWRpdW0uY29tL0BzZG9zaGk1Nzkvb3B0aW1pemVycy1mb3ItdHJhaW5pbmctbmV1cmFsLW5ldHdvcmstNTk0NTBkNzFjYWY2KSBhbmQgW0hFUkVdKGh0dHA6Ly9ydWRlci5pby9vcHRpbWl6aW5nLWdyYWRpZW50LWRlc2NlbnQvKSB5b3UgZmluZCBhIG5pY2Ugc3VtbWFyeSBmb3IgdGhlIGludGVyZXN0ZWQgcmVhZGVyIHRoYXQgd2FudHMgdG8gbm93IG1vcmUuIEN1cnJlbnRseSAodGhhdCBtaWdodCBjaGFuZ2Ugc29vbiwgc2luY2UgZXZlcnl0aGluZyBpbiBETCBtb3ZlcnMgZmFzdCksIGl0cyBjb21tb24ga25vd2xlZGdlIHRoYXQgaWYgeW91IGhhdmUgbm1vIHN0cm9uZyByZWFzb25zIHRvIGJlbGlldmUgc28sIGBSTVNwcm9wYCAoYW4gdW5wdWJsaXNoZWQsIGFkYXB0aXZlIGxlYXJuaW5nIHJhdGUgbWV0aG9kIHByb3Bvc2VkIGJ5IEdlb2ZmIEhpbnRvbikgd2l0aCBzdGFuZGFyZCBsZWFybmluZyByYXRlcyB3b3JrcyBqdXN0IHdlbGwuCgojIFJldmlld2luZyBvdXIgaW5pdGlhbCBleGFtcGxlCgpMZXQncyBnbyBiYWNrIHRvIHRoZSBmaXJzdCBleGFtcGxlIGFuZCByZXZpZXcgZWFjaCBwaWVjZSBvZiBpdCBpbiB0aGUgbGlnaHQgb2Ygd2hhdCB3ZSBoYXZlIGxlYXJuZWQgdXAgdG8gbm93OiAgVGhpcyB3YXMgdGhlIGlucHV0IGRhdGE6CgpgYGB7cixldmFsPUZBTFNFfQptbmlzdCA8LSBkYXRhc2V0X21uaXN0KCkKCnRyYWluX2ltYWdlcyA8LSBtbmlzdCR0cmFpbiR4CnRyYWluX2ltYWdlcyA8LSBhcnJheV9yZXNoYXBlKHRyYWluX2ltYWdlcywgYyg2MDAwMCwgMjggKiAyOCkpCnRyYWluX2ltYWdlcyA8LSB0cmFpbl9pbWFnZXMgLyAyNTUKCnRlc3RfaW1hZ2VzIDwtIG1uaXN0JHRlc3QkeAp0ZXN0X2ltYWdlcyA8LSBhcnJheV9yZXNoYXBlKHRlc3RfaW1hZ2VzLCBjKDEwMDAwLCAyOCAqIDI4KSkKdGVzdF9pbWFnZXMgPC0gdGVzdF9pbWFnZXMgLyAyNTUKYGBgCgoKKiBOb3cgeW91IHVuZGVyc3RhbmQgdGhhdCB0aGUgaW5wdXQgaW1hZ2VzIGFyZSBzdG9yZWQgaW4gdGVuc29ycyBvZiBzaGFwZSBgKDYwMDAwLCA3ODQpYCAodHJhaW5pbmcgZGF0YSkgYW5kIGAoMTAwMDAsIDc4NClgICh0ZXN0IGRhdGEpLCByZXNwZWN0aXZlbHkuCiogVGhpcyB3YXMgb3VyIG5ldHdvcms6CgpgYGB7cixldmFsPUZBTFNFfQpuZXR3b3JrIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDUxMiwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBjKDI4KjI4KSkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMCwgYWN0aXZhdGlvbiA9ICJzb2Z0bWF4IikKYGBgCgoqIE5vdyB5b3UgdW5kZXJzdGFuZCB0aGF0IHRoaXMgbmV0d29yayBjb25zaXN0cyBvZiBhIGNoYWluIG9mIHR3byBkZW5zZSBsYXllcnMsIHRoYXQgZWFjaCBsYXllciBhcHBsaWVzIGEgZmV3IHNpbXBsZSB0ZW5zb3Igb3BlcmF0aW9ucyB0byB0aGUgaW5wdXQgZGF0YSwgYW5kIHRoYXQgdGhlc2Ugb3BlcmF0aW9ucyBpbnZvbHZlIHdlaWdodCB0ZW5zb3JzLiAKKiBXZSBrbm93IHRoYXQgYGxheWVyX2RlbnNlKClgIGNyZWF0ZXMgZnVsbHkgY29ubmVjdGVkIGxheWVycywgc28gdGhlcmUgZXhpc3RzIGEgd2VpZ2h0IGJldHdlZW4gZXZlcnkgZWxlbWVudCBvZiBvbmUgd2l0aCBldmVyeSBlbGVtZW50IG9mIHRoZSBmb2xsb3dpbmcgbGF5ZXIuCiogV2VpZ2h0IHRlbnNvcnMsIHdoaWNoIGFyZSBhdHRyaWJ1dGVzIG9mIHRoZSBsYXllcnMsIGFyZSB3aGVyZSB0aGUga25vd2xlZGdlIG9mIHRoZSBuZXR3b3JrIHBlcnNpc3RzLiBXZSBrbm93IHRoZSAybmQgbGF5ZXIgaGFzIGA1MTJgIGNlbGxzLCB0aGUgZmluYWwgb3V0cHV0IGxheWVyIGAxMGAgKGVxdWFsIHRvIHRoZSBudW1iZXIgb2YgY2xhc3NlcyB0byBwcmVkaWN0KS4gRmluYWxseSwgd2Uga25vdyB0aGF0IGV2ZXJ5IGNlbGwgYWxzbyBjb250YWlucyBhIG5vbi1saW5lYXIgYWN0aXZhdGlvbiBmdW5jdGlvbiwgc3VjaCBhcyBgcmVsdWAsIGBzaWdtb2lkYCwgb3IgYHNvZnRtYXhgLgoKVGhpcyB3YXMgdGhlIG5ldHdvcmstY29tcGlsYXRpb24gc3RlcDoKCmBgYHtyLGV2YWw9RkFMU0V9Cm5ldHdvcmsgbmV0d29yayAlPiUgY29tcGlsZSgKICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsCiAgbG9zcyA9ICJjYXRlZ29yaWNhbF9jcm9zc2VudHJvcHkiLAogIG1ldHJpY3MgPSAiYWNjdXJhY3kiCiAgKQpgYGAKCiogTm93IHlvdSB1bmRlcnN0YW5kIHRoYXQgYGNhdGVnb3JpY2FsX2Nyb3NzZW50cm9weWAgKGEgbWVhc3VyZSBob3cgcHVyZSB0aGUgcHJlZGljdGVkIGNsYXNzZXMgYXJlKSBpcyBhIHR5cGUgb2YgYSBgbG9zc2BgIGZ1bmN0aW9uIHRoYXQncyB1c2VkIGFzIGEgZmVlZGJhY2sgc2lnbmFsIGZvciBsZWFybmluZyB0aGUgd2VpZ2h0IHRlbnNvcnMsIGFuZCB3aGljaCB0aGUgdHJhaW5pbmcgcGhhc2Ugd2lsbCBhdHRlbXB0IHRvIG1pbmltaXplLiAKKiBZb3UgYWxzbyBrbm93IHRoYXQgdGhpcyByZWR1Y3Rpb24gb2YgdGhlIGxvc3MgaGFwcGVucyB2aWEgbWluaS1iYXRjaCBzdG9jaGFzdGljIGdyYWRpZW50IGRlc2NlbnQuIFRoZSBleGFjdCBydWxlcyBnb3Zlcm5pbmcgYSBzcGVjaWZpYyB1c2Ugb2YgZ3JhZGllbnQgZGVzY2VudCBhcmUgZGVmaW5lZCBieSB0aGUgYHJtc3Byb3BgIG9wdGltaXplciBwYXNzZWQgYXMgdGhlIGZpcnN0IGFyZ3VtZW50LgoKRmluYWxseSwgdGhpcyB3YXMgdGhlIHRyYWluaW5nIGxvb3A6CgpgYGB7cixldmFsPUZBTFNFfQpuZXR3b3JrICU+JSBmaXQoeCA9IHRyYWluX2ltYWdlcywgCiAgICAgICAgICAgICAgICB5ID0gdHJhaW5fbGFiZWxzLCAKICAgICAgICAgICAgICAgIGVwb2NocyA9IDEwLCAKICAgICAgICAgICAgICAgIGJhdGNoX3NpemUgPSAxMjgpCmBgYAoKCiogTm93IHlvdSB1bmRlcnN0YW5kIHdoYXQgaGFwcGVucyB3aGVuIHlvdSBjYWxsIGBmaXQoKWA6IHRoZSBuZXR3b3JrIHdpbGwgc3RhcnQgdG8gaXRlcmF0ZSBvbiB0aGUgdHJhaW5pbmcgZGF0YSBpbiBtaW5pLWJhdGNoZXMgb2YgMTI4IHNhbXBsZXMsIDEwIHRpbWVzIG92ZXIgKGVhY2ggaXRlcmF0aW9uIG92ZXIgYWxsIHRoZSB0cmFpbmluZyBkYXRhIGlzIGNhbGxlZCBhbiA/ZXBvY2g/KS4gCiogQXQgZWFjaCBpdGVyYXRpb24sIHRoZSBuZXR3b3JrIHdpbGwgY29tcHV0ZSB0aGUgZ3JhZGllbnRzIG9mIHRoZSB3ZWlnaHRzIHdpdGggcmVnYXJkIHRvIHRoZSBsb3NzIG9uIHRoZSBiYXRjaCwgYW5kIHVwZGF0ZSB0aGUgd2VpZ2h0cyBhY2NvcmRpbmdseS4gCiogQWZ0ZXIgdGhlc2UgPzEwPyBlcG9jaHMsIHRoZSBuZXR3b3JrIHdpbGwgaGF2ZSBwZXJmb3JtZWQgPzIsMzQ1PyBncmFkaWVudCB1cGRhdGVzICg/NDY5PyBwZXIgP2Vwb2NoPyksIGFuZCB0aGUgbG9zcyBvZiB0aGUgbmV0d29yayB3aWxsIGJlIHN1ZmZpY2llbnRseSBsb3cgdGhhdCB0aGUgbmV0d29yayB3aWxsIGJlIGNhcGFibGUgb2YgY2xhc3NpZnlpbmcgaGFuZHdyaXR0ZW4gZGlnaXRzIHdpdGggaGlnaCBhY2N1cmFjeS4KCkF0IHRoaXMgcG9pbnQsIHlvdSBhbHJlYWR5IGtub3cgbW9zdCBvZiB3aGF0IHRoZXJlIGlzIHRvIGtub3cgYWJvdXQgdGhlIGJhc2ljcyBvZiBuZXVyYWwgbmV0d29ya3MuCgpIb3dldmVyLCB0aGVyZSBpcyBzdGlsbCBzb21lIHN0dWZmIHRvIGNvbWUsIG5hbWVseToKCjEuIEhvdyB0byB1c2UgYXJjaGl0ZWN0dXJlcyBvdGhlciB0aGF0IHRoZSBzaW1wbGUgKipmZWVkLWZvcndhcmQqKiBvbmUuCjIuIEhvdyB0byBmaWdodCBvdmVyZml0dGluZwozLiBIb3cgdG8gc3BlY2lmeSB0cmFpbmluZyByb3V0aW5lcyBhbmQgcGFyYW1ldGVyIGdyaWQtc2VhcmNoCjQuIEFuZCBzb21lIG1vcmUuLi4KCkJ1dCBmb3IgdGhhdCwgdGhlcmUgd2lsbCBiZSBvdGhlciBzZXNzaW9ucyB0b3AgY29tZS4uLgoKIyBFeGFtcGxlIG9uIHRhYnVsYXIgZGF0YQoKKiBKdXN0IHRvIG1ha2UgaXQgYSBiaXQgbHNzIGFic3RyYWN0IGFuZCB3b3JrIHdpdGggdGFidWxhciBkYXRhLCBsZXRzIGdpdmUgaXQgYSBzaG90IHdpdGggY2xhc3NpZnlpbmcgcGVuZ3VpbnMgOikKCiMjIExvYWQgZGF0YQoKYGBge3J9CmxpYnJhcnkodGlkeW1vZGVscykKYGBgCgpgYGB7cn0KZGF0YSA8LSByZWFkX2NzdigiaHR0cHM6Ly9naXRodWIuY29tL2FsbGlzb25ob3JzdC9wYWxtZXJwZW5ndWlucy9yYXcvNWI1ODkxZjAxYjUyYWUyNmFkOGNiOTc1NWVjOTM2NzJmNDkzMjhhOC9kYXRhL3Blbmd1aW5zX3NpemUuY3N2IikKYGBgCgpgYGB7cn0KZGF0YSAlPiUgZ2xpbXBzZSgpCmBgYAoKIyMgVFJhaW4gVEVzdCBzcGxpdApgYGB7cn0KZGF0YSAlPD4lCiAgcmVuYW1lKHkgPSBzcGVjaWVzX3Nob3J0KSAlPiUKICByZWxvY2F0ZSh5KSAlPiUKICBkcm9wX25hKCkKYGBgCgpgYGB7cn0KZGF0YV9zcGxpdCA8LSB0aWR5bW9kZWxzOjppbml0aWFsX3NwbGl0KGRhdGEsIHByb3AgPSAwLjc1LCBzdHJhdGEgPSB5KQoKZGF0YV90cmFpbiA8LSBkYXRhX3NwbGl0ICAlPiUgIHRyYWluaW5nKCkKZGF0YV90ZXN0IDwtIGRhdGFfc3BsaXQgJT4lIHRlc3RpbmcoKQpgYGAKCiMjIFByZXByb2Nlc3NpbmcgcmVjaXBlCgpgYGB7cn0KZGF0YV9yZWNpcGUgPC0gZGF0YV90cmFpbiAlPiUKICByZWNpcGUoeSB+LikgJT4lCiAgc3RlcF9jZW50ZXIoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUgIyBDZW50ZXJzIGFsbCBudW1lcmljIHZhcmlhYmxlcyB0byBtZWFuID0gMAogIHN0ZXBfc2NhbGUoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUgIyBzY2FsZXMgYWxsIG51bWVyaWMgdmFyaWFibGVzIHRvIHNkID0gMQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgb25lX2hvdCA9IFRSVUUpICU+JQogIHByZXAoKQpgYGAKCmBgYHtyfQp4X3RyYWluIDwtIGp1aWNlKGRhdGFfcmVjaXBlKSAlPiUgc2VsZWN0KC1zdGFydHNfd2l0aCgneScpKSAlPiUgYXMubWF0cml4KCkKeF90ZXN0IDwtIGJha2UoZGF0YV9yZWNpcGUsIG5ld19kYXRhID0gZGF0YV90ZXN0KSAlPiUgc2VsZWN0KC1zdGFydHNfd2l0aCgneScpKSAlPiUgYXMubWF0cml4KCkKYGBgCgpgYGB7cn0KeV90cmFpbiA8LSBqdWljZShkYXRhX3JlY2lwZSkgICU+JSBzZWxlY3Qoc3RhcnRzX3dpdGgoJ3knKSkgJT4lIGFzLm1hdHJpeCgpCnlfdGVzdCA8LSBiYWtlKGRhdGFfcmVjaXBlLCBuZXdfZGF0YSA9IGRhdGFfdGVzdCkgJT4lIHNlbGVjdChzdGFydHNfd2l0aCgneScpKSAlPiUgYXMubWF0cml4KCkKYGBgCgojIERlZmluZSB0aGUgbmV0d29yawoKYGBge3J9Cm1vZGVsX2tlcmFzIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKQpgYGAKCmBgYHtyfQptb2RlbF9rZXJhcyAlPiUgCiAgIyBGaXJzdCBoaWRkZW4gbGF5ZXIKICBsYXllcl9kZW5zZSgKICAgIHVuaXRzICAgICAgICAgICAgICA9IDEyLCAKICAgIGFjdGl2YXRpb24gICAgICAgICA9ICJyZWx1IiwgCiAgICBpbnB1dF9zaGFwZSAgICAgICAgPSBuY29sKHhfdHJhaW4pKSAlPiUgCiAgIyBEcm9wb3V0IHRvIHByZXZlbnQgb3ZlcmZpdHRpbmcKICBsYXllcl9kcm9wb3V0KHJhdGUgPSAwLjEpICU+JQogICMgU2Vjb25kIGhpZGRlbiBsYXllcgogIGxheWVyX2RlbnNlKAogICAgdW5pdHMgICAgICAgICAgICAgID0gMTIsIAogICAgYWN0aXZhdGlvbiAgICAgICAgID0gInJlbHUiKSAlPiUgCiAgIyBEcm9wb3V0IHRvIHByZXZlbnQgb3ZlcmZpdHRpbmcKICBsYXllcl9kcm9wb3V0KHJhdGUgPSAwLjEpICU+JQogICMgT3V0cHV0IGxheWVyCiAgbGF5ZXJfZGVuc2UoCiAgICB1bml0cyAgICAgICAgICAgICAgPSBuY29sKHlfdHJhaW4pLCAKICAgIGFjdGl2YXRpb24gICAgICAgICA9ICJzb2Z0bWF4IikgCmBgYAoKYGBge3J9Cm1vZGVsX2tlcmFzICU+JSAKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gImFkYW0iLAogICAgbG9zcyA9ICJjYXRlZ29yaWNhbF9jcm9zc2VudHJvcHkiLAogICAgbWV0cmljcyA9ICJhY2N1cmFjeSIKICApCmBgYAoKYGBge3J9Cm1vZGVsX2tlcmFzX2hpc3QgPC0gbW9kZWxfa2VyYXMgICU+JSBmaXQoeCA9IHhfdHJhaW4sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSB5X3RyYWluLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlcG9jaHMgPSAxMCwgIyBIb3cgb2Z0ZW4gc2hhbGwgd2UgcmUtcnVuIHRoZSBtb2RlbCBvbiB0aGUgd2hvbGUgc2FtcGxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmF0Y2hfc2l6ZSA9IDEyLCAjIEhvdyBtYW55IG9ic2VydmF0aW9ucyBzaG91bGQgYmUgaW5jbHVkZWQgaW4gZXZlcnkgYmF0Y2gKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX3NwbGl0ID0gMC4yNSAjIElmIHdlIHdhbnQgdG8gZG8gYSAgY3Jvc3MtdmFsaWRhdGlvbiBpbiB0aGUgdHJhaW5pbmcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCmBgYAoKIyBZb3VyIFR1cm4KClVzaW5nIHRoZSBLZXJhcyBBUEkgYW5kIHJlZmVyZW5jZSAoW1B5dGhvbl0oaHR0cHM6Ly9rZXJhcy5pby8pIG9yIFtSXShodHRwczovL2tlcmFzLnJzdHVkaW8uY29tLykpIG1hbnVhbCBhbmQgaW4gcGFydGljdWxhciBjb25zdHJ1Y3QgYWRkaXRpb25hbCBzaW1wbGUgbW9kZWxzICh3aXRoIGFwcHJvcHJpYXRlIG1ldHJpY3MpOgoKKiBSZWdyZXNzaW9uIGV4YW1wbGUKKiBNdWx0aS1sYWJlbCBleGFtcGxlCgpBbHNvIGNvbnNpZGVyIGZ1cnRoZXIgcmVzb3VyY2VzIG1lbnRpb25lZCBiZWxvdwoKIyBFbmRub3RlcwoKIyMjIFJlZmVyZW5jZXMKCiMjIyBNb3JlIGluZm8KCllvdSBjYW4gZmluZCBtb3JlIGluZm8gYWJvdXQ6CgoqIGBrZXJhc2AgW2hlcmVdKGh0dHBzOi8va2VyYXMucnN0dWRpby5jb20vKTogRXhjZWxsZW50IGRvY3VtZW50YXRpb24sIHR1dG9yaWFscywgYW5kIHJlc291cmNlcyByZWdhcmRpbmcgYGtlcmFzYCwgbWFpbnRhaW5lZCBieSBSc3R1ZGlvCgojIyMgT25saW5lIFJlc291cmNlcwoKRGF0YWNhbXAKICAgKiBbSW50cm9kdWN0aW9uIHRvIFRlbnNvckZsb3cgaW4gUl0oaHR0cHM6Ly9sZWFybi5kYXRhY2FtcC5jb20vY291cnNlcy9pbnRyb2R1Y3Rpb24tdG8tdGVuc29yZmxvdy1pbi1yKTogQSBiaXQgbG93LWxldmVsLCBidXQgYSBnb29kIGludHJvIGZvciBzdGFydGVycwogICAqIEFsc28gZm9sbG93IHRoZSBQeXRob24gaW50cm9zLCB0aGV5IG1pZ2h0IHN0aWxsIGJlIGhlbHBmdWwgZm9yIHlvdS4KICAgCk90aGVycwoKKiBbUlN0dWRpbyBBSSBibG9nXShodHRwczovL2Jsb2dzLnJzdHVkaW8uY29tL2FpLyk6IEV4Y2VsbGVudCBzb3VyY2UgZm9yIGZyZXF1ZW50IHRvcmNoL2tlcmFzIGV4ZXJjaXNlcyBhbmQgYW5ub3VuY2VtZW50cyB3aXRoaW4gdGggUiBlY29zeXN0ZW0KKiBbUiBNYXJrZG93biBOb3RlYm9va3MgZm9yICJEZWVwIExlYXJuaW5nIHdpdGggUiJdKGh0dHBzOi8vZ2l0aHViLmNvbS9za2V5ZGFuL2RlZXAtbGVhcm5pbmctd2l0aC1yLW5vdGVib29rcyk6IEEgY29sbGVjdGlvbiBvZiBleGVyY2lzZXMvdHV0b3JpYWxzL2RlbW9zIGZyb20gdGhlIFtEZWVwIExlYXJuaW5nIHdpdGggUl0oaHR0cHM6Ly93d3cubWFubmluZy5jb20vYm9va3MvZGVlcC1sZWFybmluZy13aXRoLXIpIGJvb2suIEdvb2QgaWxsdXN0cmF0aW9ucyBvZiBkaWZmZW50IHR5cGVzIG9mIE1MIHByb2JsZW1zIGFuZCB0aGVpciBzb2x1dGlvbnMuCgpCb29rcwoKKiBGcmFuw6dvaXMgQ2hvbGxldCAmIEouIEouIEFsbGFpcmUgKDIwMTgpLiBEZWVwIExlYXJuaW5nIHdpdGggUiwgTWFubmluZyBQdWJsaWNhdGlvbnM6IEdvb2QgYm9vaywgYnV0IG5vdCBmb3IgZnJlZS4gRmluZCBpdCBbaGVyZV0oaHR0cHM6Ly93d3cubWFubmluZy5jb20vYm9va3MvZGVlcC1sZWFybmluZy13aXRoLXIpLiB0aG91Z2gsIGluIGNhc2Ugb2YgaW50ZXJlc3QuCgoKIyMjIFNlc3Npb24gaW5mbwoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBg