This notebook contains the code samples found in Chapter 2, Section 1
of Deep
Learning with R. Note that the original text features far more
content, in particular further explanations and figures: in this
notebook, you will only find source code and related comments.
Let’s look at a concrete example of a neural network that uses the
Keras R package to learn to classify hand-written digits. Unless you
already have experience with Keras or similar libraries, you will not
understand everything about this first example right away. You probably
haven’t even installed Keras yet. Don’t worry, that is perfectly fine.
In the next chapter, we will review each element in our example and
explain them in detail. So don’t worry if some steps seem arbitrary or
look like magic to you! We’ve got to start somewhere.
The problem we’re trying to solve here is to classify grayscale
images of handwritten digits (28 pixels by 28 pixels) into their 10
categories (0 to 9). We’ll use the MNIST dataset, a classic dataset in
the machine-learning community, which has been around almost as long as
the field itself and has been intensively studied. It’s a set of 60,000
training images, plus 10,000 test images, assembled by the National
Institute of Standards and Technology (the NIST in MNIST) in the 1980s.
You can think of “solving” MNIST as the “Hello World” of deep
learning—it’s what you do to verify that your algorithms are working as
expected. As you become a machine-learning practitioner, you’ll see
MNIST come up over and over again, in scientific papers, blog posts, and
so on.
The MNIST dataset comes preloaded in Keras, in the form of
train
and test
lists, each of which includes a
set of images (x
) and associated labels
(y
):
library(keras)
mnist <- dataset_mnist()
train_images
and train_labels
form the
training set, the data that the model will learn from. The
model will then be tested on the test set,
test_images
and test_labels
. The images are
encoded as as 3D arrays, and the labels are a 1D array of digits,
ranging from 0 to 9. There is a one-to-one correspondence between the
images and the labels.
The R str()
function is a convenient way to get a quick
glimpse at the structure of an array. Let’s use it to have a look at the
training data:
Let’s have a look at the test data:
The workflow will be as follows: first we’ll feed the neural network
the training data, train_images
and
train_labels
. The network will then learn to associate
images and labels. 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 supposed to
understand everything about this example yet.
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 comes 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. A deep-learning model is like a sieve for data
processing, made of a succession of increasingly refined data
filters—the layers.
Here our network consists of a sequence of two layers, which are
densely connected (also called fully connected) 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.
To make the network ready for training, we need to pick three more
things, as part of the compilation step:
- A loss function—How the network will be able to measure how
good a job it’s doing on its training data, and thus how it will be able
to steer itself in the right direction.
- An optimizer—The mechanism through which the network will
update itself based on the data it sees and its loss function.
- Metrics to monitor during training and testing—Here we’ll
only care about accuracy (the fraction of the images that were correctly
classified).
The exact purpose of the loss function and the optimizer will be made
clear throughout the next two chapters.
Before training, we’ll 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, for
instance, were stored in an 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.
We also need to categorically encode the labels, a step which we
explain in chapter 3:
We are now ready to train our network, which in Keras is done via a
call to the fit
method of the network: we “fit” the model
to its training data.
Two quantities are being displayed during training: the “loss” of the
network over the training data, and the accuracy of the network over the
training data.
We quickly reach an accuracy of 0.989 (i.e. 98.9%) on the training
data. Now let’s check that our model performs well on the test set
too:
Our test set accuracy turns out to be 98.1% – that’s quite a bit
lower than the training set accuracy. This gap between training accuracy
and test accuracy is an example of “overfitting”, the fact that machine
learning models tend to perform worse on new data than on their training
data. Overfitting will be a central topic in chapter 3.
This concludes our first example – you just saw how you can build and
a train a neural network to classify handwritten digits in less than 20
lines of R code. In the next chapter, we’ll go into detail about every
moving piece we just previewed and clarify what’s going on behind the
scenes. You’ll learn about tensors, the data-storing objects going into
the network; about tensor operations, which layers are made of; and
about gradient descent, which allows your network to learn from its
training examples.
LS0tCnRpdGxlOiAiQSBmaXJzdCBsb29rIGF0IGEgbmV1cmFsIG5ldHdvcmsiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICB0aGVtZTogY2VydWxlYW4KICAgIGhpZ2hsaWdodDogdGV4dG1hdGUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQpgYGAKCioqKgoKVGhpcyBub3RlYm9vayBjb250YWlucyB0aGUgY29kZSBzYW1wbGVzIGZvdW5kIGluIENoYXB0ZXIgMiwgU2VjdGlvbiAxIG9mIFtEZWVwIExlYXJuaW5nIHdpdGggUl0oaHR0cHM6Ly93d3cubWFubmluZy5jb20vYm9va3MvZGVlcC1sZWFybmluZy13aXRoLXIpLiBOb3RlIHRoYXQgdGhlIG9yaWdpbmFsIHRleHQgZmVhdHVyZXMgZmFyIG1vcmUgY29udGVudCwgaW4gcGFydGljdWxhciBmdXJ0aGVyIGV4cGxhbmF0aW9ucyBhbmQgZmlndXJlczogaW4gdGhpcyBub3RlYm9vaywgeW91IHdpbGwgb25seSBmaW5kIHNvdXJjZSBjb2RlIGFuZCByZWxhdGVkIGNvbW1lbnRzLgoKKioqCgpMZXQncyBsb29rIGF0IGEgY29uY3JldGUgZXhhbXBsZSBvZiBhIG5ldXJhbCBuZXR3b3JrIHRoYXQgdXNlcyB0aGUgS2VyYXMgUiBwYWNrYWdlIHRvIGxlYXJuIHRvIGNsYXNzaWZ5IGhhbmQtd3JpdHRlbiBkaWdpdHMuIFVubGVzcyB5b3UgYWxyZWFkeSBoYXZlIGV4cGVyaWVuY2Ugd2l0aCBLZXJhcyBvciBzaW1pbGFyIGxpYnJhcmllcywgeW91IHdpbGwgbm90IHVuZGVyc3RhbmQgZXZlcnl0aGluZyBhYm91dCB0aGlzIGZpcnN0IGV4YW1wbGUgcmlnaHQgYXdheS4gWW91IHByb2JhYmx5IGhhdmVuJ3QgZXZlbiBpbnN0YWxsZWQgS2VyYXMgeWV0LiBEb24ndCB3b3JyeSwgdGhhdCBpcyBwZXJmZWN0bHkgZmluZS4gSW4gdGhlIG5leHQgY2hhcHRlciwgd2Ugd2lsbCByZXZpZXcgZWFjaCBlbGVtZW50IGluIG91ciBleGFtcGxlIGFuZCBleHBsYWluIHRoZW0gaW4gZGV0YWlsLiBTbyBkb24ndCB3b3JyeSBpZiBzb21lIHN0ZXBzIHNlZW0gYXJiaXRyYXJ5IG9yIGxvb2sgbGlrZSBtYWdpYyB0byB5b3UhIFdlJ3ZlIGdvdCB0byBzdGFydCBzb21ld2hlcmUuCgpUaGUgcHJvYmxlbSB3ZSdyZSB0cnlpbmcgdG8gc29sdmUgaGVyZSBpcyB0byBjbGFzc2lmeSBncmF5c2NhbGUgaW1hZ2VzIG9mIGhhbmR3cml0dGVuIGRpZ2l0cyAoMjggcGl4ZWxzIGJ5IDI4IHBpeGVscykgaW50byB0aGVpciAxMCBjYXRlZ29yaWVzICgwIHRvIDkpLiBXZSdsbCB1c2UgdGhlIE1OSVNUIGRhdGFzZXQsIGEgY2xhc3NpYyBkYXRhc2V0IGluIHRoZSBtYWNoaW5lLWxlYXJuaW5nIGNvbW11bml0eSwgd2hpY2ggaGFzIGJlZW4gYXJvdW5kIGFsbW9zdCBhcyBsb25nIGFzIHRoZSBmaWVsZCBpdHNlbGYgYW5kIGhhcyBiZWVuIGludGVuc2l2ZWx5IHN0dWRpZWQuIEl0J3MgYSBzZXQgb2YgNjAsMDAwIHRyYWluaW5nIGltYWdlcywgcGx1cyAxMCwwMDAgdGVzdCBpbWFnZXMsIGFzc2VtYmxlZCBieSB0aGUgTmF0aW9uYWwgSW5zdGl0dXRlIG9mIFN0YW5kYXJkcyBhbmQgVGVjaG5vbG9neSAodGhlIE5JU1QgaW4gTU5JU1QpIGluIHRoZSAxOTgwcy4gWW91IGNhbiB0aGluayBvZiAic29sdmluZyIgTU5JU1QgYXMgdGhlICJIZWxsbyBXb3JsZCIgb2YgZGVlcCBsZWFybmluZ+KAlGl0J3Mgd2hhdCB5b3UgZG8gdG8gdmVyaWZ5IHRoYXQgeW91ciBhbGdvcml0aG1zIGFyZSB3b3JraW5nIGFzIGV4cGVjdGVkLiBBcyB5b3UgYmVjb21lIGEgbWFjaGluZS1sZWFybmluZyBwcmFjdGl0aW9uZXIsIHlvdSdsbCBzZWUgTU5JU1QgY29tZSB1cCBvdmVyIGFuZCBvdmVyIGFnYWluLCBpbiBzY2llbnRpZmljIHBhcGVycywgYmxvZyBwb3N0cywgYW5kIHNvIG9uLiAKClRoZSBNTklTVCBkYXRhc2V0IGNvbWVzIHByZWxvYWRlZCBpbiBLZXJhcywgaW4gdGhlIGZvcm0gb2YgYHRyYWluYCBhbmQgYHRlc3RgIGxpc3RzLCBlYWNoIG9mIHdoaWNoIGluY2x1ZGVzIGEgc2V0IG9mIGltYWdlcyAoYHhgKSBhbmQgYXNzb2NpYXRlZCBsYWJlbHMgKGB5YCk6CgpgYGB7ciwgcmVzdWx0cz0naGlkZSd9CmxpYnJhcnkoa2VyYXMpCgptbmlzdCA8LSBkYXRhc2V0X21uaXN0KCkKdHJhaW5faW1hZ2VzIDwtIG1uaXN0JHRyYWluJHgKdHJhaW5fbGFiZWxzIDwtIG1uaXN0JHRyYWluJHkKdGVzdF9pbWFnZXMgPC0gbW5pc3QkdGVzdCR4CnRlc3RfbGFiZWxzIDwtIG1uaXN0JHRlc3QkeQpgYGAKCmB0cmFpbl9pbWFnZXNgIGFuZCBgdHJhaW5fbGFiZWxzYCBmb3JtIHRoZSBfdHJhaW5pbmcgc2V0XywgdGhlIGRhdGEgdGhhdCB0aGUgbW9kZWwgd2lsbCBsZWFybiBmcm9tLiBUaGUgbW9kZWwgd2lsbCB0aGVuIGJlIHRlc3RlZCBvbiB0aGUgIF90ZXN0IHNldF8sIGB0ZXN0X2ltYWdlc2AgYW5kIGB0ZXN0X2xhYmVsc2AuIFRoZSBpbWFnZXMgYXJlIGVuY29kZWQgYXMgYXMgM0QgYXJyYXlzLCBhbmQgdGhlIGxhYmVscyBhcmUgYSAxRCBhcnJheSBvZiBkaWdpdHMsIHJhbmdpbmcgZnJvbSAwIHRvIDkuIFRoZXJlIGlzIGEgb25lLXRvLW9uZSBjb3JyZXNwb25kZW5jZSBiZXR3ZWVuIHRoZSBpbWFnZXMgYW5kIHRoZSBsYWJlbHMuCgpUaGUgUiBgc3RyKClgIGZ1bmN0aW9uIGlzIGEgY29udmVuaWVudCB3YXkgdG8gZ2V0IGEgcXVpY2sgZ2xpbXBzZSBhdCB0aGUgc3RydWN0dXJlIG9mIGFuIGFycmF5LiBMZXQncyB1c2UgaXQgdG8gaGF2ZSBhIGxvb2sgYXQgdGhlIHRyYWluaW5nIGRhdGE6CgpgYGB7cn0Kc3RyKHRyYWluX2ltYWdlcykKYGBgCgpgYGB7cn0Kc3RyKHRyYWluX2xhYmVscykKYGBgCgpMZXQncyBoYXZlIGEgbG9vayBhdCB0aGUgdGVzdCBkYXRhOgoKYGBge3J9CnN0cih0ZXN0X2ltYWdlcykKYGBgCgpgYGB7cn0Kc3RyKHRlc3RfbGFiZWxzKQpgYGAKClRoZSB3b3JrZmxvdyB3aWxsIGJlIGFzIGZvbGxvd3M6IGZpcnN0IHdlJ2xsIGZlZWQgdGhlIG5ldXJhbCBuZXR3b3JrIHRoZSB0cmFpbmluZyBkYXRhLCBgdHJhaW5faW1hZ2VzYCBhbmQgYHRyYWluX2xhYmVsc2AuIFRoZSBuZXR3b3JrIHdpbGwgdGhlbiBsZWFybiB0byBhc3NvY2lhdGUgaW1hZ2VzIGFuZCBsYWJlbHMuIEZpbmFsbHksIHdlJ2xsIGFzayB0aGUgbmV0d29yayB0byBwcm9kdWNlIHByZWRpY3Rpb25zIGZvciBgdGVzdF9pbWFnZXNgLCBhbmQgd2UnbGwgdmVyaWZ5IHdoZXRoZXIgdGhlc2UgcHJlZGljdGlvbnMgbWF0Y2ggdGhlIGxhYmVscyBmcm9tIGB0ZXN0X2xhYmVsc2AuCgpMZXQncyBidWlsZCB0aGUgbmV0d29yayAtLSBhZ2FpbiwgcmVtZW1iZXIgdGhhdCB5b3UgYXJlbid0IHN1cHBvc2VkIHRvIHVuZGVyc3RhbmQgZXZlcnl0aGluZyBhYm91dCB0aGlzIGV4YW1wbGUgeWV0LgoKYGBge3J9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDUxMiwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBjKDI4ICogMjgpKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMCwgYWN0aXZhdGlvbiA9ICJzb2Z0bWF4IikKYGBgCgpUaGUgY29yZSBidWlsZGluZyBibG9jayBvZiBuZXVyYWwgbmV0d29ya3MgaXMgdGhlIF9sYXllcl8sIGEgZGF0YS1wcm9jZXNzaW5nIG1vZHVsZSB0aGF0IHlvdSBjYW4gdGhpbmsgb2YgYXMgYSBmaWx0ZXIgZm9yIGRhdGEuIFNvbWUgZGF0YSBjb21lcyBpbiwgYW5kIGl0IGNvbWVzIG91dCBpbiBhIG1vcmUgdXNlZnVsIGZvcm0uIFNwZWNpZmljYWxseSwgbGF5ZXJzIGV4dHJhY3QgX3JlcHJlc2VudGF0aW9uc18gb3V0IG9mIHRoZSBkYXRhIGZlZCBpbnRvIHRoZW3igJRob3BlZnVsbHkgcmVwcmVzZW50YXRpb25zIHRoYXQgYXJlIG1vcmUgbWVhbmluZ2Z1bCBmb3IgdGhlIHByb2JsZW0gYXQgaGFuZC4gTW9zdCBvZiBkZWVwIGxlYXJuaW5nIGNvbnNpc3RzIG9mIGNoYWluaW5nIHRvZ2V0aGVyIHNpbXBsZSBsYXllcnMgdGhhdCB3aWxsIGltcGxlbWVudCBhIGZvcm0gb2YgcHJvZ3Jlc3NpdmUgX2RhdGEgZGlzdGlsbGF0aW9uXy4gQSBkZWVwLWxlYXJuaW5nIG1vZGVsIGlzIGxpa2UgYSBzaWV2ZSBmb3IgZGF0YSBwcm9jZXNzaW5nLCBtYWRlIG9mIGEgc3VjY2Vzc2lvbiBvZiBpbmNyZWFzaW5nbHkgcmVmaW5lZCBkYXRhIGZpbHRlcnPigJR0aGUgbGF5ZXJzLgoKSGVyZSBvdXIgbmV0d29yayBjb25zaXN0cyBvZiBhIHNlcXVlbmNlIG9mIHR3byBsYXllcnMsIHdoaWNoIGFyZSBkZW5zZWx5IGNvbm5lY3RlZCAoYWxzbyBjYWxsZWQgX2Z1bGx5IGNvbm5lY3RlZF8pIG5ldXJhbCBsYXllcnMuIFRoZSBzZWNvbmQgKGFuZCBsYXN0KSBsYXllciBpcyBhIDEwLXdheSBfc29mdG1heF8gbGF5ZXIsIHdoaWNoIG1lYW5zIGl0IHdpbGwgcmV0dXJuIGFuIGFycmF5IG9mIDEwIHByb2JhYmlsaXR5IHNjb3JlcyAoc3VtbWluZyB0byAxKS4gRWFjaCBzY29yZSB3aWxsIGJlIHRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBjdXJyZW50IGRpZ2l0IGltYWdlIGJlbG9uZ3MgdG8gb25lIG9mIG91ciAxMCBkaWdpdCBjbGFzc2VzLgoKVG8gbWFrZSB0aGUgbmV0d29yayByZWFkeSBmb3IgdHJhaW5pbmcsIHdlIG5lZWQgdG8gcGljayB0aHJlZSBtb3JlIHRoaW5ncywgYXMgcGFydCBvZiB0aGUgX2NvbXBpbGF0aW9uXyBzdGVwOgoKKiBfQSBsb3NzIGZ1bmN0aW9uX+KAlEhvdyB0aGUgbmV0d29yayB3aWxsIGJlIGFibGUgdG8gbWVhc3VyZSBob3cgZ29vZCBhIGpvYiBpdCdzIGRvaW5nIG9uIGl0cyB0cmFpbmluZyBkYXRhLCBhbmQgdGh1cyBob3cgaXQgd2lsbCBiZSBhYmxlIHRvIHN0ZWVyIGl0c2VsZiBpbiB0aGUgcmlnaHQgZGlyZWN0aW9uLgoqIF9BbiBvcHRpbWl6ZXJf4oCUVGhlIG1lY2hhbmlzbSB0aHJvdWdoIHdoaWNoIHRoZSBuZXR3b3JrIHdpbGwgdXBkYXRlIGl0c2VsZiBiYXNlZCBvbiB0aGUgZGF0YSBpdCBzZWVzIGFuZCBpdHMgbG9zcyBmdW5jdGlvbi4KKiBfTWV0cmljcyB0byBtb25pdG9yIGR1cmluZyB0cmFpbmluZyBhbmQgdGVzdGluZ1/igJRIZXJlIHdlJ2xsIG9ubHkgY2FyZSBhYm91dCBhY2N1cmFjeSAodGhlIGZyYWN0aW9uIG9mIHRoZSBpbWFnZXMgdGhhdCB3ZXJlIGNvcnJlY3RseSBjbGFzc2lmaWVkKS4KClRoZSBleGFjdCBwdXJwb3NlIG9mIHRoZSBsb3NzIGZ1bmN0aW9uIGFuZCB0aGUgb3B0aW1pemVyIHdpbGwgYmUgbWFkZSBjbGVhciB0aHJvdWdob3V0IHRoZSBuZXh0IHR3byBjaGFwdGVycy4KCmBgYHtyfQpuZXR3b3JrICU+JSBjb21waWxlKAogIG9wdGltaXplciA9ICJybXNwcm9wIiwKICBsb3NzID0gImNhdGVnb3JpY2FsX2Nyb3NzZW50cm9weSIsCiAgbWV0cmljcyA9IGMoImFjY3VyYWN5IikKKQpgYGAKCkJlZm9yZSB0cmFpbmluZywgd2UnbGwgcHJlcHJvY2VzcyB0aGUgZGF0YSBieSByZXNoYXBpbmcgaXQgaW50byB0aGUgc2hhcGUgdGhlIG5ldHdvcmsgZXhwZWN0cyBhbmQgc2NhbGluZyBpdCBzbyB0aGF0IGFsbCB2YWx1ZXMgYXJlIGluIHRoZSBgWzAsIDFdYCBpbnRlcnZhbC4gUHJldmlvdXNseSwgb3VyIHRyYWluaW5nIGltYWdlcywgZm9yIGluc3RhbmNlLCB3ZXJlIHN0b3JlZCBpbiBhbiBhcnJheSBvZiBzaGFwZSBgKDYwMDAwLCAyOCwgMjgpYCBvZiB0eXBlIGludGVnZXIgd2l0aCB2YWx1ZXMgaW4gdGhlIGBbMCwgMjU1XWAgaW50ZXJ2YWwuIFdlIHRyYW5zZm9ybSBpdCBpbnRvIGEgZG91YmxlIGFycmF5IG9mIHNoYXBlIGAoNjAwMDAsIDI4ICogMjgpYCB3aXRoIHZhbHVlcyBiZXR3ZWVuIDAgYW5kIDEuCgpgYGB7cn0KdHJhaW5faW1hZ2VzIDwtIGFycmF5X3Jlc2hhcGUodHJhaW5faW1hZ2VzLCBjKDYwMDAwLCAyOCAqIDI4KSkKdHJhaW5faW1hZ2VzIDwtIHRyYWluX2ltYWdlcyAvIDI1NQoKdGVzdF9pbWFnZXMgPC0gYXJyYXlfcmVzaGFwZSh0ZXN0X2ltYWdlcywgYygxMDAwMCwgMjggKiAyOCkpCnRlc3RfaW1hZ2VzIDwtIHRlc3RfaW1hZ2VzIC8gMjU1CmBgYAoKV2UgYWxzbyBuZWVkIHRvIGNhdGVnb3JpY2FsbHkgZW5jb2RlIHRoZSBsYWJlbHMsIGEgc3RlcCB3aGljaCB3ZSBleHBsYWluIGluIGNoYXB0ZXIgMzoKCmBgYHtyfQp0cmFpbl9sYWJlbHMgPC0gdG9fY2F0ZWdvcmljYWwodHJhaW5fbGFiZWxzKQp0ZXN0X2xhYmVscyA8LSB0b19jYXRlZ29yaWNhbCh0ZXN0X2xhYmVscykKYGBgCgpXZSBhcmUgbm93IHJlYWR5IHRvIHRyYWluIG91ciBuZXR3b3JrLCB3aGljaCBpbiBLZXJhcyBpcyBkb25lIHZpYSBhIGNhbGwgdG8gdGhlIGBmaXRgIG1ldGhvZCBvZiB0aGUgbmV0d29yazogd2UgImZpdCIgdGhlIG1vZGVsIHRvIGl0cyB0cmFpbmluZyBkYXRhLgoKYGBge3IsIGVjaG89VFJVRSwgcmVzdWx0cz0naGlkZSd9Cm5ldHdvcmsgJT4lIGZpdCh0cmFpbl9pbWFnZXMsIHRyYWluX2xhYmVscywgZXBvY2hzID0gNSwgYmF0Y2hfc2l6ZSA9IDEyOCkKYGBgCgpUd28gcXVhbnRpdGllcyBhcmUgYmVpbmcgZGlzcGxheWVkIGR1cmluZyB0cmFpbmluZzogdGhlICJsb3NzIiBvZiB0aGUgbmV0d29yayBvdmVyIHRoZSB0cmFpbmluZyBkYXRhLCBhbmQgdGhlIGFjY3VyYWN5IG9mIHRoZSBuZXR3b3JrIG92ZXIgdGhlIHRyYWluaW5nIGRhdGEuCgpXZSBxdWlja2x5IHJlYWNoIGFuIGFjY3VyYWN5IG9mIDAuOTg5IChpLmUuIDk4LjklKSBvbiB0aGUgdHJhaW5pbmcgZGF0YS4gTm93IGxldCdzIGNoZWNrIHRoYXQgb3VyIG1vZGVsIHBlcmZvcm1zIHdlbGwgb24gdGhlIHRlc3Qgc2V0IHRvbzoKCmBgYHtyfQptZXRyaWNzIDwtIG5ldHdvcmsgJT4lIGV2YWx1YXRlKHRlc3RfaW1hZ2VzLCB0ZXN0X2xhYmVscywgdmVyYm9zZSA9IDApCm1ldHJpY3MKYGBgCgpPdXIgdGVzdCBzZXQgYWNjdXJhY3kgdHVybnMgb3V0IHRvIGJlIDk4LjElIC0tIHRoYXQncyBxdWl0ZSBhIGJpdCBsb3dlciB0aGFuIHRoZSB0cmFpbmluZyBzZXQgYWNjdXJhY3kuIFRoaXMgZ2FwIGJldHdlZW4gdHJhaW5pbmcgYWNjdXJhY3kgYW5kIHRlc3QgYWNjdXJhY3kgaXMgYW4gZXhhbXBsZSBvZiAib3ZlcmZpdHRpbmciLCB0aGUgZmFjdCB0aGF0IG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHRlbmQgdG8gcGVyZm9ybSB3b3JzZSBvbiBuZXcgZGF0YSB0aGFuIG9uIHRoZWlyIHRyYWluaW5nIGRhdGEuIE92ZXJmaXR0aW5nIHdpbGwgYmUgYSBjZW50cmFsIHRvcGljIGluIGNoYXB0ZXIgMy4KClRoaXMgY29uY2x1ZGVzIG91ciBmaXJzdCBleGFtcGxlIC0tIHlvdSBqdXN0IHNhdyBob3cgeW91IGNhbiBidWlsZCBhbmQgYSB0cmFpbiBhIG5ldXJhbCBuZXR3b3JrIHRvIGNsYXNzaWZ5IGhhbmR3cml0dGVuIGRpZ2l0cyBpbiBsZXNzIHRoYW4gMjAgbGluZXMgb2YgUiBjb2RlLiBJbiB0aGUgbmV4dCBjaGFwdGVyLCB3ZSdsbCBnbyBpbnRvIGRldGFpbCBhYm91dCBldmVyeSBtb3ZpbmcgcGllY2Ugd2UganVzdCBwcmV2aWV3ZWQgYW5kIGNsYXJpZnkgd2hhdCdzIGdvaW5nIG9uIGJlaGluZCB0aGUgc2NlbmVzLiBZb3UnbGwgbGVhcm4gYWJvdXQgdGVuc29ycywgdGhlIGRhdGEtc3RvcmluZyBvYmplY3RzIGdvaW5nIGludG8gdGhlIG5ldHdvcms7IGFib3V0IHRlbnNvciBvcGVyYXRpb25zLCB3aGljaCBsYXllcnMgYXJlIG1hZGUgb2Y7IGFuZCBhYm91dCBncmFkaWVudCBkZXNjZW50LCB3aGljaCBhbGxvd3MgeW91ciBuZXR3b3JrIHRvIGxlYXJuIGZyb20gaXRzIHRyYWluaW5nIGV4YW1wbGVzLgoK