A first look at a neural network

Building a functional neural net from the MNIST dataset represents a “hello world” problem.

MNIST consists of 28 x 28 pixels greyscale images representing numbers between 0 and 9. This forms the neural net input which is trained on a set of integers represented by the integer datatype.

In this example, we build a functional neural net from this dataset.

Loading the MNIST dataset in Keras

Images are in x and labels are in y:

  • x is a 3D array. In this case, 60,000 2D sub-arrays composed of 28 x 28 integers with values from [0, 255], with each integer representing a shade on the greyscale, which is then assigned to a pixel, which is displayed as an image on a grid.

  • y contains a 2D array containing the actual integer being represented visually by x.

library(keras)

# Training data
mnist <- dataset_mnist()
train_images <- mnist$train$x
train_labels <- mnist$train$y

# Plot the image formed by the 2nd subtensor in the dataset
digit <- train_images[2,,]
plot(as.raster(digit, max = 255))


# Testing data
test_images <- mnist$test$x
test_labels <- mnist$test$y

Training data

str(train_images)
 int [1:60000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
str(train_labels)
 int [1:60000(1d)] 5 0 4 1 9 2 1 3 1 4 ...

Testing data

str(test_images)
 int [1:10000, 1:28, 1:28] 0 0 0 0 0 0 0 0 0 0 ...
str(test_labels)
 int [1:10000(1d)] 7 2 1 0 4 1 4 9 5 9 ...

The network architecture

Setup the neural network architecture that we will feed the training data into to yield a model that we will test with the testing data.

The tensor operation that will be applied to the input or training data is designated by “relu” in the model, where “relu” translates, in R, to the following: output = pmax(W %*% input) + b), 0).

In words, for this case, it means:

  1. Take the dot product between the 2D input tensor (training data) and a weighting tensor, W (kernel),
  2. Add the result to the 1D vector, b (bias), and
  3. Find the parallel max between the resultant 1D vector and a 1D vector of zeroes.

These operations are then repeated for successive applications of network layers (i.e., forward passes), by minimizing a loss function,1 until the data is effectively “unfolded”. The weights (or trainable parameters), W and b, contain the information learned by the network from exposure to training data and are adjusted slightly after computing network loss between iterations.2

If we take the derivative of the resulting tensor operation (i.e., the gradient of the loss), the direction of increasing loss can be efficiently calculated (i.e., a backward pass) allowing for efficient determination of new weighting functions between iterations.

This is the heart of solving neural nets. This general method is called gradient descent.

network <- keras_model_sequential() %>%
  layer_dense(units = 512, activation = "relu", input_shape = c(28 * 28)) %>%
  layer_dense(units = 10, activation = "softmax")

The compilation step

To make the network ready for training, we specify 3 more options to form the compilation step. The network is modified in place (note use of %>%) rather than returning a new network object. Keras models are modified in place because they are directed acyclic graphs of layers whose state is updated during training.

categorical_crossentropy is the loss function used as a feedback signal for learning the weight tensors, and which the training phase will attempt to minimize. 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.

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

Preparing the image data

Preprocess the data by reshaping it into the shape the network expects and scaling it so that all values are in [0, 1].

array_reshape() is used instead of dim<-() since we are dealing with tensors. Also, so that the data is reinterpreted using row-major semantics (as opposed to R’s 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.

train_images <- array_reshape(train_images, c(60000, 28 * 28))
train_images <- train_images / 255

test_images <- array_reshape(test_images, c(10000, 28 * 28))
test_images <- test_images / 255

Preparing the labels

Categorically encode the labels.

train_labels <- to_categorical(train_labels)
test_labels <- to_categorical(test_labels)

Train the network

In Keras, this is done by using the fit method. That is, the model is fit to its training data.

The network will start to iterate on the training data in mini-batches of 128 samples, 5 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.

network %>% fit(train_images, train_labels, epochs = 5, batch_size = 128)
Epoch 1/5

  1/469 [..............................] - ETA: 5:07 - loss: 2.3593 - accuracy: 0.0938
  2/469 [..............................] - ETA: 25s - loss: 2.1967 - accuracy: 0.2148 
  4/469 [..............................] - ETA: 18s - loss: 1.7626 - accuracy: 0.4453
 10/469 [..............................] - ETA: 9s - loss: 1.3313 - accuracy: 0.6070 
 17/469 [>.............................] - ETA: 6s - loss: 1.0430 - accuracy: 0.7050
 26/469 [>.............................] - ETA: 5s - loss: 0.8749 - accuracy: 0.7551
 38/469 [=>............................] - ETA: 3s - loss: 0.7478 - accuracy: 0.7895
 49/469 [==>...........................] - ETA: 3s - loss: 0.6722 - accuracy: 0.8098
 60/469 [==>...........................] - ETA: 3s - loss: 0.6160 - accuracy: 0.8257
 69/469 [===>..........................] - ETA: 2s - loss: 0.5786 - accuracy: 0.8361
 81/469 [====>.........................] - ETA: 2s - loss: 0.5413 - accuracy: 0.8456
 92/469 [====>.........................] - ETA: 2s - loss: 0.5128 - accuracy: 0.8539
106/469 [=====>........................] - ETA: 2s - loss: 0.4863 - accuracy: 0.8606
121/469 [======>.......................] - ETA: 2s - loss: 0.4637 - accuracy: 0.8666
132/469 [=======>......................] - ETA: 1s - loss: 0.4482 - accuracy: 0.8716
140/469 [=======>......................] - ETA: 1s - loss: 0.4368 - accuracy: 0.8748
149/469 [========>.....................] - ETA: 1s - loss: 0.4270 - accuracy: 0.8779
165/469 [=========>....................] - ETA: 1s - loss: 0.4090 - accuracy: 0.8828
180/469 [==========>...................] - ETA: 1s - loss: 0.3932 - accuracy: 0.8874
193/469 [===========>..................] - ETA: 1s - loss: 0.3822 - accuracy: 0.8904
208/469 [============>.................] - ETA: 1s - loss: 0.3695 - accuracy: 0.8940
227/469 [=============>................] - ETA: 1s - loss: 0.3565 - accuracy: 0.8971
249/469 [==============>...............] - ETA: 1s - loss: 0.3422 - accuracy: 0.9009
270/469 [================>.............] - ETA: 0s - loss: 0.3295 - accuracy: 0.9045
284/469 [=================>............] - ETA: 0s - loss: 0.3219 - accuracy: 0.9066
301/469 [==================>...........] - ETA: 0s - loss: 0.3153 - accuracy: 0.9082
312/469 [==================>...........] - ETA: 0s - loss: 0.3111 - accuracy: 0.9095
330/469 [====================>.........] - ETA: 0s - loss: 0.3037 - accuracy: 0.9117
352/469 [=====================>........] - ETA: 0s - loss: 0.2948 - accuracy: 0.9145
374/469 [======================>.......] - ETA: 0s - loss: 0.2868 - accuracy: 0.9165
397/469 [========================>.....] - ETA: 0s - loss: 0.2798 - accuracy: 0.9185
417/469 [=========================>....] - ETA: 0s - loss: 0.2747 - accuracy: 0.9199
440/469 [===========================>..] - ETA: 0s - loss: 0.2672 - accuracy: 0.9219
458/469 [============================>.] - ETA: 0s - loss: 0.2621 - accuracy: 0.9235
469/469 [==============================] - 2s 4ms/step - loss: 0.2601 - accuracy: 0.9242

469/469 [==============================] - 2s 4ms/step - loss: 0.2601 - accuracy: 0.9242
Epoch 2/5

  1/469 [..............................] - ETA: 1s - loss: 0.0840 - accuracy: 0.9766
 19/469 [>.............................] - ETA: 1s - loss: 0.1283 - accuracy: 0.9659
 38/469 [=>............................] - ETA: 1s - loss: 0.1086 - accuracy: 0.9702
 59/469 [==>...........................] - ETA: 1s - loss: 0.1105 - accuracy: 0.9688
 79/469 [====>.........................] - ETA: 1s - loss: 0.1114 - accuracy: 0.9679
 98/469 [=====>........................] - ETA: 0s - loss: 0.1116 - accuracy: 0.9682
119/469 [======>.......................] - ETA: 0s - loss: 0.1135 - accuracy: 0.9674
139/469 [=======>......................] - ETA: 0s - loss: 0.1122 - accuracy: 0.9680
155/469 [========>.....................] - ETA: 0s - loss: 0.1137 - accuracy: 0.9677
168/469 [=========>....................] - ETA: 0s - loss: 0.1137 - accuracy: 0.9673
188/469 [===========>..................] - ETA: 0s - loss: 0.1114 - accuracy: 0.9682
208/469 [============>.................] - ETA: 0s - loss: 0.1119 - accuracy: 0.9680
228/469 [=============>................] - ETA: 0s - loss: 0.1114 - accuracy: 0.9680
248/469 [==============>...............] - ETA: 0s - loss: 0.1111 - accuracy: 0.9681
269/469 [================>.............] - ETA: 0s - loss: 0.1102 - accuracy: 0.9684
293/469 [=================>............] - ETA: 0s - loss: 0.1103 - accuracy: 0.9683
317/469 [===================>..........] - ETA: 0s - loss: 0.1087 - accuracy: 0.9687
340/469 [====================>.........] - ETA: 0s - loss: 0.1084 - accuracy: 0.9688
364/469 [======================>.......] - ETA: 0s - loss: 0.1078 - accuracy: 0.9688
386/469 [=======================>......] - ETA: 0s - loss: 0.1068 - accuracy: 0.9690
412/469 [=========================>....] - ETA: 0s - loss: 0.1050 - accuracy: 0.9695
436/469 [==========================>...] - ETA: 0s - loss: 0.1041 - accuracy: 0.9696
460/469 [============================>.] - ETA: 0s - loss: 0.1033 - accuracy: 0.9697
469/469 [==============================] - 1s 2ms/step - loss: 0.1032 - accuracy: 0.9697

469/469 [==============================] - 1s 2ms/step - loss: 0.1032 - accuracy: 0.9697
Epoch 3/5

  1/469 [..............................] - ETA: 1s - loss: 0.0965 - accuracy: 0.9609
 24/469 [>.............................] - ETA: 0s - loss: 0.0713 - accuracy: 0.9775
 48/469 [==>...........................] - ETA: 0s - loss: 0.0688 - accuracy: 0.9780
 72/469 [===>..........................] - ETA: 0s - loss: 0.0712 - accuracy: 0.9784
 97/469 [=====>........................] - ETA: 0s - loss: 0.0700 - accuracy: 0.9787
121/469 [======>.......................] - ETA: 0s - loss: 0.0710 - accuracy: 0.9788
145/469 [========>.....................] - ETA: 0s - loss: 0.0698 - accuracy: 0.9792
155/469 [========>.....................] - ETA: 0s - loss: 0.0689 - accuracy: 0.9795
166/469 [=========>....................] - ETA: 0s - loss: 0.0686 - accuracy: 0.9796
181/469 [==========>...................] - ETA: 0s - loss: 0.0688 - accuracy: 0.9798
194/469 [===========>..................] - ETA: 0s - loss: 0.0708 - accuracy: 0.9791
217/469 [============>.................] - ETA: 0s - loss: 0.0702 - accuracy: 0.9792
241/469 [==============>...............] - ETA: 0s - loss: 0.0698 - accuracy: 0.9793
266/469 [================>.............] - ETA: 0s - loss: 0.0695 - accuracy: 0.9797
289/469 [=================>............] - ETA: 0s - loss: 0.0695 - accuracy: 0.9797
312/469 [==================>...........] - ETA: 0s - loss: 0.0688 - accuracy: 0.9798
335/469 [====================>.........] - ETA: 0s - loss: 0.0688 - accuracy: 0.9796
358/469 [=====================>........] - ETA: 0s - loss: 0.0684 - accuracy: 0.9797
380/469 [=======================>......] - ETA: 0s - loss: 0.0687 - accuracy: 0.9797
402/469 [========================>.....] - ETA: 0s - loss: 0.0686 - accuracy: 0.9795
424/469 [==========================>...] - ETA: 0s - loss: 0.0681 - accuracy: 0.9797
446/469 [===========================>..] - ETA: 0s - loss: 0.0677 - accuracy: 0.9799
469/469 [==============================] - 1s 2ms/step - loss: 0.0676 - accuracy: 0.9799

469/469 [==============================] - 1s 2ms/step - loss: 0.0676 - accuracy: 0.9799
Epoch 4/5

  1/469 [..............................] - ETA: 1s - loss: 0.0169 - accuracy: 0.9922
 23/469 [>.............................] - ETA: 1s - loss: 0.0419 - accuracy: 0.9885
 40/469 [=>............................] - ETA: 1s - loss: 0.0462 - accuracy: 0.9865
 57/469 [==>...........................] - ETA: 1s - loss: 0.0479 - accuracy: 0.9857
 78/469 [===>..........................] - ETA: 1s - loss: 0.0481 - accuracy: 0.9859
 99/469 [=====>........................] - ETA: 0s - loss: 0.0493 - accuracy: 0.9853
119/469 [======>.......................] - ETA: 0s - loss: 0.0492 - accuracy: 0.9858
140/469 [=======>......................] - ETA: 0s - loss: 0.0492 - accuracy: 0.9855
162/469 [=========>....................] - ETA: 0s - loss: 0.0495 - accuracy: 0.9855
184/469 [==========>...................] - ETA: 0s - loss: 0.0489 - accuracy: 0.9857
206/469 [============>.................] - ETA: 0s - loss: 0.0488 - accuracy: 0.9860
228/469 [=============>................] - ETA: 0s - loss: 0.0488 - accuracy: 0.9858
251/469 [===============>..............] - ETA: 0s - loss: 0.0488 - accuracy: 0.9857
274/469 [================>.............] - ETA: 0s - loss: 0.0485 - accuracy: 0.9857
297/469 [=================>............] - ETA: 0s - loss: 0.0479 - accuracy: 0.9860
320/469 [===================>..........] - ETA: 0s - loss: 0.0477 - accuracy: 0.9859
343/469 [====================>.........] - ETA: 0s - loss: 0.0482 - accuracy: 0.9858
366/469 [======================>.......] - ETA: 0s - loss: 0.0482 - accuracy: 0.9858
390/469 [=======================>......] - ETA: 0s - loss: 0.0482 - accuracy: 0.9858
414/469 [=========================>....] - ETA: 0s - loss: 0.0487 - accuracy: 0.9856
438/469 [===========================>..] - ETA: 0s - loss: 0.0483 - accuracy: 0.9858
462/469 [============================>.] - ETA: 0s - loss: 0.0486 - accuracy: 0.9857
469/469 [==============================] - 1s 2ms/step - loss: 0.0488 - accuracy: 0.9857

469/469 [==============================] - 1s 2ms/step - loss: 0.0488 - accuracy: 0.9857
Epoch 5/5

  1/469 [..............................] - ETA: 1s - loss: 0.0459 - accuracy: 0.9766
 24/469 [>.............................] - ETA: 0s - loss: 0.0378 - accuracy: 0.9896
 47/469 [==>...........................] - ETA: 0s - loss: 0.0347 - accuracy: 0.9895
 69/469 [===>..........................] - ETA: 0s - loss: 0.0364 - accuracy: 0.9881
 91/469 [====>.........................] - ETA: 0s - loss: 0.0336 - accuracy: 0.9892
114/469 [======>.......................] - ETA: 0s - loss: 0.0355 - accuracy: 0.9883
138/469 [=======>......................] - ETA: 0s - loss: 0.0352 - accuracy: 0.9884
161/469 [=========>....................] - ETA: 0s - loss: 0.0358 - accuracy: 0.9885
185/469 [==========>...................] - ETA: 0s - loss: 0.0363 - accuracy: 0.9881
209/469 [============>.................] - ETA: 0s - loss: 0.0370 - accuracy: 0.9882
232/469 [=============>................] - ETA: 0s - loss: 0.0371 - accuracy: 0.9881
257/469 [===============>..............] - ETA: 0s - loss: 0.0371 - accuracy: 0.9882
282/469 [=================>............] - ETA: 0s - loss: 0.0368 - accuracy: 0.9883
306/469 [==================>...........] - ETA: 0s - loss: 0.0374 - accuracy: 0.9882
328/469 [===================>..........] - ETA: 0s - loss: 0.0374 - accuracy: 0.9882
348/469 [=====================>........] - ETA: 0s - loss: 0.0375 - accuracy: 0.9881
369/469 [======================>.......] - ETA: 0s - loss: 0.0376 - accuracy: 0.9881
391/469 [========================>.....] - ETA: 0s - loss: 0.0377 - accuracy: 0.9880
413/469 [=========================>....] - ETA: 0s - loss: 0.0375 - accuracy: 0.9881
435/469 [==========================>...] - ETA: 0s - loss: 0.0369 - accuracy: 0.9883
456/469 [============================>.] - ETA: 0s - loss: 0.0369 - accuracy: 0.9883
469/469 [==============================] - 1s 2ms/step - loss: 0.0371 - accuracy: 0.9882

469/469 [==============================] - 1s 2ms/step - loss: 0.0371 - accuracy: 0.9882

Two quantities are displayed during training: the loss of the network over the training data, and the accuracy of the network over the training data, which is displayed here. The final accuracy is 99%.

Loss and accuracy by epoch. After the 5 epochs, the network has performed 2,345 gradient updates (469 per epoch). The loss of the network is sufficiently low that the network is capable of classifying handwritten digits with high accuracy.

Testing the network

Check that the model performs on the test set too.

metrics <- network %>% evaluate(test_images, test_labels)

  1/313 [..............................] - ETA: 18s - loss: 0.0046 - accuracy: 1.0000
 66/313 [=====>........................] - ETA: 0s - loss: 0.0904 - accuracy: 0.9740 
161/313 [==============>...............] - ETA: 0s - loss: 0.0863 - accuracy: 0.9750
265/313 [========================>.....] - ETA: 0s - loss: 0.0681 - accuracy: 0.9794
313/313 [==============================] - 0s 568us/step - loss: 0.0670 - accuracy: 0.9794

313/313 [==============================] - 0s 569us/step - loss: 0.0670 - accuracy: 0.9794
metrics
      loss   accuracy 
0.06699257 0.97939998 

The test set accuracy turns out to be 98% – less than the model accuracy from training due to overfitting.

Generate predictions for the first 10 samples of the test set.

network %>% predict(test_images[1:10,]) %>% k_argmax() %>% as.numeric()
 [1] 7 2 1 0 4 1 4 9 5 9

  1. It should represent a measure of success for the task you’re trying to solve.↩︎

  2. The weights are filled with small random initial values during a step called random initialization.↩︎

LS0tCnRpdGxlOiAiKkRlZXAgTGVhcm5pbmcgV2l0aCBSKjogQ2hhcHRlciAyIEV4ZXJjaXNlIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIEEgZmlyc3QgbG9vayBhdCBhIG5ldXJhbCBuZXR3b3JrCgpCdWlsZGluZyBhIGZ1bmN0aW9uYWwgbmV1cmFsIG5ldCBmcm9tIHRoZSBNTklTVCBkYXRhc2V0IHJlcHJlc2VudHMgYSAiaGVsbG8gd29ybGQiIHByb2JsZW0uCgpNTklTVCBjb25zaXN0cyBvZiAyOCB4IDI4IHBpeGVscyBncmV5c2NhbGUgaW1hZ2VzIHJlcHJlc2VudGluZyBudW1iZXJzIGJldHdlZW4gMCBhbmQgOS4gVGhpcyBmb3JtcyB0aGUgbmV1cmFsIG5ldCBpbnB1dCB3aGljaCBpcyB0cmFpbmVkIG9uIGEgc2V0IG9mIGludGVnZXJzIHJlcHJlc2VudGVkIGJ5IHRoZSBgaW50ZWdlcmAgZGF0YXR5cGUuCgpJbiB0aGlzIGV4YW1wbGUsIHdlIGJ1aWxkIGEgZnVuY3Rpb25hbCBuZXVyYWwgbmV0IGZyb20gdGhpcyBkYXRhc2V0LgoKIyMgTG9hZGluZyB0aGUgTU5JU1QgZGF0YXNldCBpbiBLZXJhcwoKSW1hZ2VzIGFyZSBpbiBgeGAgYW5kIGxhYmVscyBhcmUgaW4gYHlgOgoKLSBgeGAgaXMgYSAzRCBhcnJheS4gSW4gdGhpcyBjYXNlLCA2MCwwMDAgMkQgc3ViLWFycmF5cyBjb21wb3NlZCBvZiAyOCB4IDI4IGludGVnZXJzIHdpdGggdmFsdWVzIGZyb20gWzAsIDI1NV0sIHdpdGggZWFjaCBpbnRlZ2VyIHJlcHJlc2VudGluZyBhIHNoYWRlIG9uIHRoZSBncmV5c2NhbGUsIHdoaWNoIGlzIHRoZW4gYXNzaWduZWQgdG8gYSBwaXhlbCwgd2hpY2ggaXMgZGlzcGxheWVkIGFzIGFuIGltYWdlIG9uIGEgZ3JpZC4KCi0gYHlgIGNvbnRhaW5zIGEgMkQgYXJyYXkgY29udGFpbmluZyB0aGUgYWN0dWFsIGludGVnZXIgYmVpbmcgcmVwcmVzZW50ZWQgdmlzdWFsbHkgYnkgYHhgLgoKYGBge3J9CmxpYnJhcnkoa2VyYXMpCgojIFRyYWluaW5nIGRhdGEKbW5pc3QgPC0gZGF0YXNldF9tbmlzdCgpCnRyYWluX2ltYWdlcyA8LSBtbmlzdCR0cmFpbiR4CnRyYWluX2xhYmVscyA8LSBtbmlzdCR0cmFpbiR5CgojIFBsb3QgdGhlIGltYWdlIGZvcm1lZCBieSB0aGUgMm5kIHN1YnRlbnNvciBpbiB0aGUgZGF0YXNldApkaWdpdCA8LSB0cmFpbl9pbWFnZXNbMiwsXQpwbG90KGFzLnJhc3RlcihkaWdpdCwgbWF4ID0gMjU1KSkKCiMgVGVzdGluZyBkYXRhCnRlc3RfaW1hZ2VzIDwtIG1uaXN0JHRlc3QkeAp0ZXN0X2xhYmVscyA8LSBtbmlzdCR0ZXN0JHkKYGBgCgojIyMgVHJhaW5pbmcgZGF0YQoKYGBge3J9CnN0cih0cmFpbl9pbWFnZXMpCnN0cih0cmFpbl9sYWJlbHMpCmBgYAoKIyMjIFRlc3RpbmcgZGF0YQoKYGBge3J9CnN0cih0ZXN0X2ltYWdlcykKc3RyKHRlc3RfbGFiZWxzKQpgYGAKCiMjIFRoZSBuZXR3b3JrIGFyY2hpdGVjdHVyZQoKU2V0dXAgdGhlIG5ldXJhbCBuZXR3b3JrIGFyY2hpdGVjdHVyZSB0aGF0IHdlIHdpbGwgZmVlZCB0aGUgdHJhaW5pbmcgZGF0YSBpbnRvIHRvIHlpZWxkIGEgbW9kZWwgdGhhdCB3ZSB3aWxsIHRlc3Qgd2l0aCB0aGUgdGVzdGluZyBkYXRhLgoKVGhlIHRlbnNvciBvcGVyYXRpb24gdGhhdCB3aWxsIGJlIGFwcGxpZWQgdG8gdGhlIGlucHV0IG9yIHRyYWluaW5nIGRhdGEgaXMgZGVzaWduYXRlZCBieSAicmVsdSIgaW4gdGhlIG1vZGVsLCB3aGVyZSAicmVsdSIgdHJhbnNsYXRlcywgaW4gUiwgdG8gdGhlIGZvbGxvd2luZzogYG91dHB1dCA9IHBtYXgoVyAlKiUgaW5wdXQpICsgYiksIDApYC4KCkluIHdvcmRzLCBmb3IgdGhpcyBjYXNlLCBpdCBtZWFuczoKCjEuIFRha2UgdGhlIGRvdCBwcm9kdWN0IGJldHdlZW4gdGhlIDJEIGBpbnB1dGAgdGVuc29yICh0cmFpbmluZyBkYXRhKSBhbmQgYSB3ZWlnaHRpbmcgdGVuc29yLCBgV2AgKGtlcm5lbCksCjIuIEFkZCB0aGUgcmVzdWx0IHRvIHRoZSAxRCB2ZWN0b3IsIGBiYCAoYmlhcyksIGFuZAozLiBGaW5kIHRoZSBwYXJhbGxlbCBtYXggYmV0d2VlbiB0aGUgcmVzdWx0YW50IDFEIHZlY3RvciBhbmQgYSAxRCB2ZWN0b3Igb2YgemVyb2VzLgoKVGhlc2Ugb3BlcmF0aW9ucyBhcmUgdGhlbiByZXBlYXRlZCBmb3Igc3VjY2Vzc2l2ZSBhcHBsaWNhdGlvbnMgb2YgbmV0d29yayBsYXllcnMgKGkuZS4sIGZvcndhcmQgcGFzc2VzKSwgYnkgbWluaW1pemluZyBhIGxvc3MgZnVuY3Rpb24sW14xXSB1bnRpbCB0aGUgZGF0YSBpcyBlZmZlY3RpdmVseSAidW5mb2xkZWQiLiBUaGUgd2VpZ2h0cyAob3IgdHJhaW5hYmxlIHBhcmFtZXRlcnMpLCBgV2AgYW5kIGBiYCwgY29udGFpbiB0aGUgaW5mb3JtYXRpb24gbGVhcm5lZCBieSB0aGUgbmV0d29yayBmcm9tIGV4cG9zdXJlIHRvIHRyYWluaW5nIGRhdGEgYW5kIGFyZSBhZGp1c3RlZCBzbGlnaHRseSBhZnRlciBjb21wdXRpbmcgbmV0d29yayBsb3NzIGJldHdlZW4gaXRlcmF0aW9ucy5bXjJdCgpJZiB3ZSB0YWtlIHRoZSBkZXJpdmF0aXZlIG9mIHRoZSByZXN1bHRpbmcgdGVuc29yIG9wZXJhdGlvbiAoaS5lLiwgdGhlIGdyYWRpZW50IG9mIHRoZSBsb3NzKSwgdGhlIGRpcmVjdGlvbiBvZiBpbmNyZWFzaW5nIGxvc3MgY2FuIGJlIGVmZmljaWVudGx5IGNhbGN1bGF0ZWQgKGkuZS4sIGEgYmFja3dhcmQgcGFzcykgYWxsb3dpbmcgZm9yIGVmZmljaWVudCBkZXRlcm1pbmF0aW9uIG9mIG5ldyB3ZWlnaHRpbmcgZnVuY3Rpb25zIGJldHdlZW4gaXRlcmF0aW9ucy4KClRoaXMgaXMgdGhlIGhlYXJ0IG9mIHNvbHZpbmcgbmV1cmFsIG5ldHMuIFRoaXMgZ2VuZXJhbCBtZXRob2QgaXMgY2FsbGVkICoqKmdyYWRpZW50IGRlc2NlbnQqKiouCgpbXjFdOiBJdCBzaG91bGQgcmVwcmVzZW50IGEgbWVhc3VyZSBvZiBzdWNjZXNzIGZvciB0aGUgdGFzayB5b3XigJlyZSB0cnlpbmcgdG8gc29sdmUuClteMl06IFRoZSB3ZWlnaHRzIGFyZSBmaWxsZWQgd2l0aCBzbWFsbCByYW5kb20gaW5pdGlhbCB2YWx1ZXMgZHVyaW5nIGEgc3RlcCBjYWxsZWQgcmFuZG9tIGluaXRpYWxpemF0aW9uLgoKYGBge3J9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gNTEyLCBhY3RpdmF0aW9uID0gInJlbHUiLCBpbnB1dF9zaGFwZSA9IGMoMjggKiAyOCkpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTAsIGFjdGl2YXRpb24gPSAic29mdG1heCIpCmBgYAoKIyMgVGhlIGNvbXBpbGF0aW9uIHN0ZXAKClRvIG1ha2UgdGhlIG5ldHdvcmsgcmVhZHkgZm9yIHRyYWluaW5nLCB3ZSBzcGVjaWZ5IDMgbW9yZSBvcHRpb25zIHRvIGZvcm0gdGhlIGNvbXBpbGF0aW9uIHN0ZXAuIFRoZSBuZXR3b3JrIGlzIG1vZGlmaWVkIGluIHBsYWNlIChub3RlIHVzZSBvZiBgJT4lYCkgcmF0aGVyIHRoYW4gcmV0dXJuaW5nIGEgbmV3IG5ldHdvcmsgb2JqZWN0LiBLZXJhcyBtb2RlbHMgYXJlIG1vZGlmaWVkIGluIHBsYWNlIGJlY2F1c2UgdGhleSBhcmUgZGlyZWN0ZWQgYWN5Y2xpYyBncmFwaHMgb2YgbGF5ZXJzIHdob3NlIHN0YXRlIGlzIHVwZGF0ZWQgZHVyaW5nIHRyYWluaW5nLgoKYGNhdGVnb3JpY2FsX2Nyb3NzZW50cm9weWAgaXMgdGhlIGxvc3MgZnVuY3Rpb24gdXNlZCBhcyBhIGZlZWRiYWNrIHNpZ25hbCBmb3IgbGVhcm5pbmcgdGhlIHdlaWdodCB0ZW5zb3JzLCBhbmQgd2hpY2ggdGhlIHRyYWluaW5nIHBoYXNlIHdpbGwgYXR0ZW1wdCB0byBtaW5pbWl6ZS4gVGhpcyByZWR1Y3Rpb24gb2YgdGhlIGxvc3MgaGFwcGVucyB2aWEgbWluaS1iYXRjaCBzdG9jaGFzdGljIGdyYWRpZW50IGRlc2NlbnQuIFRoZSBleGFjdCBydWxlcyBnb3Zlcm5pbmcgYSBzcGVjaWZpYyB1c2Ugb2YgZ3JhZGllbnQgZGVzY2VudCBhcmUgZGVmaW5lZCBieSB0aGUgYHJtc3Byb3BgIG9wdGltaXplciBwYXNzZWQgYXMgdGhlIGZpcnN0IGFyZ3VtZW50LgoKYGBge3J9Cm5ldHdvcmsgJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gInJtc3Byb3AiLAogIGxvc3MgPSAiY2F0ZWdvcmljYWxfY3Jvc3NlbnRyb3B5IiwKICBtZXRyaWNzID0gYygiYWNjdXJhY3kiKQopCmBgYAoKIyMgUHJlcGFyaW5nIHRoZSBpbWFnZSBkYXRhCgpQcmVwcm9jZXNzIHRoZSBkYXRhIGJ5IHJlc2hhcGluZyBpdCBpbnRvIHRoZSBzaGFwZSB0aGUgbmV0d29yayBleHBlY3RzIGFuZCBzY2FsaW5nIGl0IHNvIHRoYXQgYWxsIHZhbHVlcyBhcmUgaW4gIFswLCAxXS4KCmBhcnJheV9yZXNoYXBlKClgIGlzIHVzZWQgaW5zdGVhZCBvZiBgZGltPC0oKWAgc2luY2Ugd2UgYXJlIGRlYWxpbmcgd2l0aCB0ZW5zb3JzLiBBbHNvLCBzbyB0aGF0IHRoZSBkYXRhIGlzIHJlaW50ZXJwcmV0ZWQgdXNpbmcgcm93LW1ham9yIHNlbWFudGljcyAoYXMgb3Bwb3NlZCB0byBS4oCZcyBkZWZhdWx0IGNvbHVtbi1tYWpvciBzZW1hbnRpY3MpLCB3aGljaCBpcyBpbiB0dXJuIGNvbXBhdGlibGUgd2l0aCB0aGUgd2F5IHRoZSBudW1lcmljYWwgbGlicmFyaWVzIGNhbGxlZCBieSBLZXJhcyAoTnVtUHksIFRlbnNvckZsb3csIGFuZCBzbyBvbikgaW50ZXJwcmV0IGFycmF5IGRpbWVuc2lvbnMuICoqKllvdSBzaG91bGQgYWx3YXlzIHVzZSB0aGUgYGFycmF5X3Jlc2hhcGUoKWAgZnVuY3Rpb24gd2hlbiByZXNoYXBpbmcgUiBhcnJheXMgdGhhdCB3aWxsIGJlIHBhc3NlZCB0byBLZXJhcy4qKioKCmBgYHtyfQp0cmFpbl9pbWFnZXMgPC0gYXJyYXlfcmVzaGFwZSh0cmFpbl9pbWFnZXMsIGMoNjAwMDAsIDI4ICogMjgpKQp0cmFpbl9pbWFnZXMgPC0gdHJhaW5faW1hZ2VzIC8gMjU1Cgp0ZXN0X2ltYWdlcyA8LSBhcnJheV9yZXNoYXBlKHRlc3RfaW1hZ2VzLCBjKDEwMDAwLCAyOCAqIDI4KSkKdGVzdF9pbWFnZXMgPC0gdGVzdF9pbWFnZXMgLyAyNTUKYGBgCgojIyBQcmVwYXJpbmcgdGhlIGxhYmVscwoKQ2F0ZWdvcmljYWxseSBlbmNvZGUgdGhlIGxhYmVscy4KIApgYGB7cn0KdHJhaW5fbGFiZWxzIDwtIHRvX2NhdGVnb3JpY2FsKHRyYWluX2xhYmVscykKdGVzdF9sYWJlbHMgPC0gdG9fY2F0ZWdvcmljYWwodGVzdF9sYWJlbHMpCmBgYAogCiMjIFRyYWluIHRoZSBuZXR3b3JrCiAKSW4gS2VyYXMsIHRoaXMgaXMgZG9uZSBieSB1c2luZyB0aGUgYGZpdGAgbWV0aG9kLiBUaGF0IGlzLCB0aGUgbW9kZWwgaXMgZml0IHRvIGl0cyB0cmFpbmluZyBkYXRhLgoKVGhlIG5ldHdvcmsgd2lsbCBzdGFydCB0byBpdGVyYXRlIG9uIHRoZSB0cmFpbmluZyBkYXRhIGluIG1pbmktYmF0Y2hlcyBvZiAxMjggc2FtcGxlcywgNSB0aW1lcyBvdmVyIChlYWNoIGl0ZXJhdGlvbiBvdmVyIGFsbCB0aGUgdHJhaW5pbmcgZGF0YSBpcyBjYWxsZWQgYW4gZXBvY2gpLiBBdCBlYWNoIGl0ZXJhdGlvbiwgdGhlIG5ldHdvcmsgd2lsbCBjb21wdXRlIHRoZSBncmFkaWVudHMgb2YgdGhlIHdlaWdodHMgd2l0aCByZWdhcmQgdG8gdGhlIGxvc3Mgb24gdGhlIGJhdGNoLCBhbmQgdXBkYXRlIHRoZSB3ZWlnaHRzIGFjY29yZGluZ2x5LgoKYGBge3J9Cm5ldHdvcmsgJT4lIGZpdCh0cmFpbl9pbWFnZXMsIHRyYWluX2xhYmVscywgZXBvY2hzID0gNSwgYmF0Y2hfc2l6ZSA9IDEyOCkKYGBgCgpUd28gcXVhbnRpdGllcyBhcmUgZGlzcGxheWVkIGR1cmluZyB0cmFpbmluZzogdGhlIGxvc3Mgb2YgdGhlIG5ldHdvcmsgb3ZlciB0aGUgdHJhaW5pbmcgZGF0YSwgYW5kIHRoZSBhY2N1cmFjeSBvZiB0aGUgbmV0d29yayBvdmVyIHRoZSB0cmFpbmluZyBkYXRhLCB3aGljaCBpcyBkaXNwbGF5ZWQgaGVyZS4gVGhlIGZpbmFsIGFjY3VyYWN5IGlzIDk5JS4KCiFbTG9zcyBhbmQgYWNjdXJhY3kgYnkgZXBvY2guXShsb3NzQWNjdXJhY3lQbG90LnBuZykKQWZ0ZXIgdGhlIDUgZXBvY2hzLCB0aGUgbmV0d29yayBoYXMgcGVyZm9ybWVkIDIsMzQ1IGdyYWRpZW50IHVwZGF0ZXMgKDQ2OSBwZXIgZXBvY2gpLiBUaGUgbG9zcyBvZiB0aGUgbmV0d29yayBpcyBzdWZmaWNpZW50bHkgbG93IHRoYXQgdGhlIG5ldHdvcmsgaXMgY2FwYWJsZSBvZiBjbGFzc2lmeWluZyBoYW5kd3JpdHRlbiBkaWdpdHMgd2l0aCBoaWdoIGFjY3VyYWN5LgoKIyMgVGVzdGluZyB0aGUgbmV0d29yawoKQ2hlY2sgdGhhdCB0aGUgbW9kZWwgcGVyZm9ybXMgb24gdGhlIHRlc3Qgc2V0IHRvby4KCmBgYHtyfQptZXRyaWNzIDwtIG5ldHdvcmsgJT4lIGV2YWx1YXRlKHRlc3RfaW1hZ2VzLCB0ZXN0X2xhYmVscykKbWV0cmljcwpgYGAKClRoZSB0ZXN0IHNldCBhY2N1cmFjeSB0dXJucyBvdXQgdG8gYmUgOTglIC0tIGxlc3MgdGhhbiB0aGUgbW9kZWwgYWNjdXJhY3kgZnJvbSB0cmFpbmluZyBkdWUgdG8gb3ZlcmZpdHRpbmcuCgpHZW5lcmF0ZSBwcmVkaWN0aW9ucyBmb3IgdGhlIGZpcnN0IDEwIHNhbXBsZXMgb2YgdGhlIHRlc3Qgc2V0LgoKYGBge3J9Cm5ldHdvcmsgJT4lIHByZWRpY3QodGVzdF9pbWFnZXNbMToxMCxdKSAlPiUga19hcmdtYXgoKSAlPiUgYXMubnVtZXJpYygpCmBgYAoK