This module is designed to provide you an introduction to the keras API, deep learning and some of the key components that make DL algorithms run. Throughout this module you will learn about:

  • Perceptrons
  • Gradient descent
  • Activation functions
  • Learning rate & momentum
  • Model capacity (width vs. depth)
  • Learning curves
  • Batch size

Package Requirements

Loading

Let’s load the keras package along with a couple other packages we’ll use.

library(keras)       # for modeling
library(tidyverse)   # for wrangling & visualization
library(glue)        # for string literals

Installation

Normally, you will be starting out from scratch and need to install and set up keras on your own laptop. First, you need to install keras from CRAN. Once the package is installed, you need to install the Keras and TensorFlow Python packages, which is what the R Keras and TensorFlow packages communicate with. keras simplifies this with install_keras() which allows for:

  • both GPU & CPU options setups
  • installation in a virtual or conda environment
  • setup for Theano & CNTK backends rather than TensorFlow

See https://keras.rstudio.com/ for details.

# install keras if necessary
# install.packages("keras")

# default CPU-based installations of Keras and TensorFlow
# install_keras()

# for GPU installation
# install_keras(tensorflow = "gpu")

For this workshop we will be using a cloud environment to ensure we are all operating in common environment and Keras and TensorFlow have already been installed.

Simple Linear Regression

For our first example, let’s look at a very simple linear problem based on data with:

  • obs = 1,000
  • intercept = 30
  • slope = 5
  • with a little bit of random noise
n <- 1000   # n observations
b <- 30     # intercept
a <- 5      # slope

set.seed(123)
(df <- tibble(
  x = runif(n, min = -1, max = 1),
  y = b + a*x + rnorm(n)
))

Simple regression with OLS

I’m assuming we’re all familiar with the OLS methodology for linear regression modeling where our objective function (aka loss) is:

\[loss = MSE = \frac{1}{n}\sum^n_{i-1}(Y_i - \hat Y_i)^2\]

If we apply OLS to our data, we get:

  • estimated intercept = 30.01
  • estimated slope = 4.97
  • loss score (MSE) = 1.002
lm_model <- summary(lm(y ~ x, data = df))
lm_model

We can illustrate this model fit to our data:

mse <- lm_model[["sigma"]]

ggplot(df, aes(x, y)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE) +
  ggtitle(glue("MSE = {mse}"))

Simple regression with a perceptron

Now, let’s illustrate performing a similar process but with a basic building block of neural networks – the perceptron.

To model with keras we need our data to be in tensors. We’ll discuss tensors more later but for now just realize that:

  • 1D tensor = vector
  • 2D tensor = matrix
x <- as.matrix(df$x)
y <- df$y

Training a neural network model consists of 3 steps:

  1. Define model architecture
  2. Define how our model is going to learn
  3. Train our model

1. Define model architecture

Defining an architecture includes defining the type of model and the arrangement of layers:

model <- keras_model_sequential() %>%
  layer_dense(units = 1, input_shape = ncol(x))
  • Define structure of model
    • Sequential: linear layers (i.e. MLP, CNN, RNN, LSTM)
    • Functional: adds more flexibility (i.e. combining CNN & LSTM to predict probability of cancer)
  • Define arrangement and shape of layers
    • layer_dense: a single layer of nodes
    • units = 1: a single perceptron unit in our layer
    • input_shape: we need to tell our first layer how many inputs to expect

2. Define how our model is going to learn

The whole goal of training a neural network is to find the optimal set of parameter weights (aka coefficients in OLS-speak).

Our goal is to find weights that minimize the loss score

We define how our model is going to learn with compile():

model %>% compile(
  optimizer = "sgd",
  loss = "mse"
)
  • Optimizer: neural networks learn via backpropagation. There are various backpropagation algorithms but we’ll start with the most basic… stochastic gradient descent (SGD).

  • loss: how do we want to measure our model’s error. keras comes with many built-in loss functions and we can even create custom loss functions. Here, we’ll use MSE.

3. Train our model

The last thing we need is how to train our model, which we do with fit():

history <- model %>% fit(x, y, batch_size = 32, epochs = 10)
  • x: feature tensor
  • y: target tensor
  • batch_size: pick observations from our training data, perform forward pass, compute loss score, compute gradient, perform backward pass, update our weight (default = 32).
  • epoch: 1 epoch = one forward pass and one backward pass of all the training examples. We’re repeating that 10 times (default = 10).

Putting it all together

Let’s put all three steps together and train our model. Here’s a visual depiction:

And here’s the code:

# 1. Define model architecture
model <- keras_model_sequential() %>%
  layer_dense(units = 1, input_shape = ncol(x))

# 2. Define how our model is going to learn
model %>% compile(
  optimizer = "sgd",
  loss = "mse"
)

# 3. Train our model
history <- model %>% fit(x, y, epochs = 10)

Whereas in OLS we call the intercept and slope parameters “coefficients”, in neural networks we call them weights. We can see that after 10 epochs our weights are getting close to the underlying “truth” values (slope = 5, intercept = 30) but also notice that our MSE loss score is still decreasing after 10 epochs.

get_weights(model)

Models are object oriented

Note that modeling with keras/tensorflow in R may feel a bit different than other modeling packages you’ve used in R. Since keras/tensorflow are reticulated from Python, the model is an object oriented object with Python attributes.

  1. Object oriented - our model object changes without assignment:
    • model %>% compile() changed our model object by adding the optimizer and loss parameter arguments
    • model %>% fit() will continue to build onto our existing model
# let's execute one more epoch. Note how our loss decreases from the last epoch
# above
model %>% fit(x, y, epoch = 1)

# our model weights get updated from this last epoch and continue to get closer
# to the underlying true values
get_weights(model)
  1. Our model is a Python object - there will be things you can not directly access because they are Python objects. However, for most things that you want to access there will be a function to export them:
# the model weights are held in Python numpy arrays
model$weights

# we use helper functions to export these kinds of objects
get_weights(model)

Your Turn (3 min)

  1. Fill in the blanks below and train the model for 25 epochs.
  2. Explore the history object.
  3. What are the final weights for this model? How do they compare to the underlying intercept (30) and slope (5)?
# 1. Define model architecture
model <- keras_model_sequential() %>%
  layer_dense(units = ___, input_shape = ____)

# 2. Define how our model is going to learn
model %>% compile(
  optimizer = "sgd",
  loss = ____
)

# 3. Train our model
history <- model %>% fit(x, y, epochs = ____)

Gradient descent

We can see the progression of the gradient descent process by examining the weights our model produces after each epoch. This is not something you will do often but, rather, helps make the gradient descent process more concrete.

# data frame to dump our results
model_est <- expand_grid(
  epoch = 1:25,
  a_sgd = NA,
  b_sgd = NA
)

# 1. Define model architecture
model <- keras_model_sequential() %>%
  layer_dense(units = 1, input_shape = ncol(x))

# 2. Define how our model is going to learn
model %>% compile(
  optimizer = "sgd",
  loss = "mse"
)

# 3. Train our model for 25 epochs and record the weights after each epoch
for (row in seq_len(nrow(model_est))) {
  
  history <- model %>% fit(x, y, epoch = 1, verbose = FALSE)
  
  current_wts <- get_weights(model)

  model_est[row, "a_sgd"] <- current_wts[[1]]
  model_est[row, "b_sgd"] <- current_wts[[2]]
}

The following table shows the estimate slope (a_sgd) and intercept (b_sgd) produced by SGD after each epoch.

model_est
history

We can visualize our linear model after each epoch with the following. Note how each epoch results in a linear prediction (dotted) that gets closer to the truth (blue line). After 25 epochs our model basically converges to the same results (yellow dotted line).

We can see this with our loss (MSE) that nearly equates the OLS MSE (1.00187).

epoch_pred <- merge(df, model_est, all = TRUE) %>%
  mutate(pred = b_sgd + a_sgd*x)

last_epoch <- filter(epoch_pred, epoch == max(epoch))

ggplot(data = df, aes(x, y)) +
  geom_point(alpha = 0.1) +
  geom_smooth(method = "lm", se = FALSE, size = 1.5) +
  geom_line(data = epoch_pred, aes(x, pred, group = epoch), lty = "dotted") +
  geom_line(data = last_epoch, aes(x, pred, group = epoch), 
            lty = "dotted", color = "yellow", size = 2) +
  ggtitle(glue("MSE = {history$metrics$loss}"))

Key Takeaways

  • A basic single perceptron computes the same transformation as OLS:

\[\hat y = bias + weight_1 \times x_1 + weight_2 \times x_2 + \dots + weight_n \times x_n\]

  • Neural networks learn via gradient descent - an iterative approach of predicting with a forward pass, measuring the gradient of the error, and performing a backward pass to update the weights based on the gradient.

Binary Classification

Let’s do the same process but now we’ll do so with a binary classification problem (i.e. predicting yes vs. no response).

set.seed(123)
generated <- mlbench::mlbench.simplex(n = 1000, d = 1, sd = .3)
x <- generated$x
y <- ifelse(generated$classes == 1, 0, 1)

(df <- tibble(x = as.vector(x), y = y))

Our generated data has some overlap so there is no linear seperation without having some error. Note than when discussing binary classification problems, we will mainly use the crossentropy (aka log loss) loss function.

glm_model <- glm(y ~ x, family = binomial(link = "logit"), data = df)
crossentropy <- MLmetrics::LogLoss(glm_model$fitted.values, df$y)

ggplot(df, aes(x, y)) +
  geom_point(aes(color = as.factor(y)), size = 2, show.legend = FALSE) +
  geom_smooth(method = "glm", method.args = list(family = "binomial"), se = FALSE) +
  ggtitle(glue("crossentropy = {crossentropy}")) +
  ylab("probability y = 1")

Sigmoid Activation Function

When predicting a binary response, we typically want to predict a real value between 0-1 representing the probability of the positive binary class. Unfortunately our regular perceptron creates a linear transformation. However, we can apply an activation function to transform this linear transformation to a non-linear transformation.

When predicting a binary response, we use a sigmoid activation to convert our linear transformation to a 0-1 probability of the positive class.

\[sigmoid(y) = \frac{1}{1+e^{-y}}\]

When predicting a binary response, we need to make the following changes to our code:

  • Add activation = "sigmoid" to the layer that is predicting the output.
  • Note that since we are predicting the probability from 0-1 for our response, we keep units = 1.
  • loss - we change loss = "binary_crossentropy" to use the crossentropy / log loss objective function.
model <- keras_model_sequential() %>%
  layer_dense(units = 1, input_shape = ncol(x), activation = "sigmoid")

model %>% compile(
  optimizer = "sgd",
  loss = "binary_crossentropy"
)

(history <- model %>% fit(x, y, epochs = 50, verbose = FALSE))

We see that our loss is quite a bit off from our logistic regression model.

df %>%
  mutate(pred = predict(model, x) %>% as.vector()) %>%
  ggplot(aes(x, y)) +
  geom_point(aes(color = as.factor(y)), size = 2, show.legend = FALSE) +
  geom_smooth(method = "glm", method.args = list(family = "binomial"), se = FALSE) +
  geom_line(aes(y = pred), lty = "dashed") +
  ggtitle(glue("crossentropy = {min(history$metrics$loss)}")) +
  ylab("probability of y = 1")

However, if we look at our loss scores, we see that they are still improving, its just taking a long time. Plus, it looks like there is more improvement that can be made to our loss.

plot(history)

Learning rate and momentum

An important parameter in gradient descent is the size of the steps which is controlled by the learning rate. If the learning rate is…

  • too small: the algorithm will take many iterations (steps) to find the minimum
  • too large: you might jump across the minimum and end up further away than when you started

The default learning rate for SGD is 0.01. Unfortunately with this rate, it will take over 1,000 epochs to reach a loss score comparable to logistic regression. However, we can customize our optimizer with optimizer_sdg():

model <- keras_model_sequential() %>%
  layer_dense(units = 1, input_shape = ncol(x), activation = "sigmoid")

model %>% compile(
  optimizer = optimizer_sgd(lr = 0.1),
  loss = "binary_crossentropy"
)

(history <- model %>% fit(x, y, epochs = 50, verbose = FALSE))

Another common approach to adjust our learning rate is to add momentum. Adding momentum allows our learning rate to adapt. Momentum simply adds a fraction of the previous weight update to the current one.

Let’s add some momentum to our learning rate. We see that our loss improves even more within the same number of epochs.

model <- keras_model_sequential() %>%
  layer_dense(units = 1, input_shape = ncol(x), activation = "sigmoid")

model %>% compile(
  optimizer = optimizer_sgd(lr = 0.1, momentum = 0.5),
  loss = "binary_crossentropy"
)

(history <- model %>% fit(x, y, epochs = 50, verbose = FALSE))

Your Turn (3 min)

  1. Try different combinations of learning rate and momentum. A few rules of 👍:
    • Typically, we start assessing learning rates in log values ranges of [1e-1, 1e-7] (i.e. 0.1, 0.01, …, 0.0000001).
    • Momentum is typically > 0.5 and often in the 0.9-0.99 range.
  2. Plot the loss learning curve.
  3. How does your final loss compare to logistic regression?
model <- keras_model_sequential() %>%
  layer_dense(units = 1, input_shape = ncol(x), activation = ____)

model %>% compile(
  optimizer = ____,
  loss = "binary_crossentropy"
)

history <- model %>% fit(x, y, epochs = 50, verbose = FALSE)

Key takeaways

  • Activation functions:
    • We use activation functions to transform the perceptron’s linear equation to a non-linear form.
    • For binary classification problems we use the “sigmoid” activation to convert our predictions to a 0-1 probability.
  • Learning rate:
    • We can control the rate of learning by increasing & decreasing the learning rate.
    • We can make this learning rate adaptive to the curvature of our loss gradient by incorporating momentum.

Non-linear Patterns

As our datasets get larger or include non-linearities, our model needs to become more sophisticated. For this example, we’ll stick with one predictor variable but we’ll add a non-linearity component:

set.seed(123)
df <- tibble(
  x = seq(from = -1, to = 2 * pi, length = n),
  e = rnorm(n, sd = 0.2),
  y = sin(x) + e
)

ggplot(df, aes(x, y)) +
  geom_point(alpha = 0.5) +
  geom_smooth(se = FALSE)

Again, let’s extract our feature and target tensors:

x <- as.matrix(df$x)
y <- df$y

As our underlying model has more complexity, we add hidden layers to capture non-linearities and interactions. We call these neural network models multi-layer perceptrons (MLPs); also referred to as densely connected feed forward networks.

We can add a hidden layers by adding additional layer_dense() functions to our model architecture. For example, the following code would create an MLP with:

  • 3 hidden layers:
    • each hidden layer has 16 nodes
    • only the first hidden layer requires input_shape
    • each hidden layer uses a ReLU activation function (we’ll discuss shortly)
  • the last layer_dense() is always the output layer
    • activation function for output layer is always dependent on the problem
      • regression: NULL
      • binary classification: activation = "signmoid"
      • multi-class classification: activation = "softmax"
model <- keras_model_sequential() %>%
  layer_dense(units = 16, input_shape = ncol(x), activation = "relu") %>%
  layer_dense(units = 16, activation = "relu") %>%
  layer_dense(units = 16, activation = "relu") %>%
  layer_dense(units = 1)

Why ReLU

The rectified linear activation function is very simple; if the linear transformation within the perceptron results in a negative number then the output is 0. If its positive then its that value.

\[ReLU = max(0, z)\]

Benefits (see http://proceedings.mlr.press/v15/glorot11a/glorot11a.pdf):

  • Simple geometric transformations can produce very complex patterns.
  • Computational simplicity (easy to compute the gradient)
  • Representational sparcity (forcing 0s results in sparse outputs)
  • Linearity (reduces vanishing gradient descent - discussed later)

Let’s see this in action:

model <- keras_model_sequential() %>%
  layer_dense(units = 16, input_shape = ncol(x), activation = "relu") %>%
  layer_dense(units = 1)

model %>% compile(
  optimizer = optimizer_sgd(lr = 0.01, momentum = .9),
  loss = "mse"
)

(history <- model %>% fit(x, y, epochs = 50, verbose = FALSE))
df %>%
  mutate(pred = predict(model, x) %>% as.vector()) %>%
  ggplot(aes(x, y)) +
  geom_point(alpha = 0.25) +
  geom_smooth(se = FALSE) +
  geom_line(aes(y = pred), lty = "dashed", color = "red", size = 1)

Model capacity

Model capacity determines the extend to which our model can capture underlying relationships and patterns. We control model capacity with:

  1. width: number of units in a layer
    • Rule of 👍: typically use powers of 2 (i.e. 16, 32, 64, 128, 256, 512)
  2. depth: number of hidden layers
    • Rule of 👍: we often see better performance (accuracy & compute efficiency) by increasing the number of layers moreso than nodes.

Let’s add 2 hidden layers, each with 16 units:

model <- keras_model_sequential() %>%
  layer_dense(units = 16, input_shape = ncol(x), activation = "relu") %>%
  layer_dense(units = 16, activation = "relu") %>%
  layer_dense(units = 1)

model %>% compile(
  optimizer = optimizer_sgd(lr = 0.01, momentum = .9),
  loss = "mse"
)

(history <- model %>% fit(x, y, epochs = 50, verbose = FALSE))

Looking at how our predicted values fit the true underlying model:

df %>%
  mutate(pred = predict(model, x) %>% as.vector()) %>%
  ggplot(aes(x, y)) +
  geom_point(alpha = 0.25) +
  geom_smooth(se = FALSE) +
  geom_line(aes(y = pred), lty = "dashed", color = "red", size = 1)

Your Turn (3 min)

  1. Try using only one hidden layer and increase the width to 32, 64, 128, 256
  2. Try adding a second layer and increase the width of each layer progressively
    • Rule of 👍: when we add more layers we typically have the following patterns:
      • tunnel shaped: each hidden layer has the same number of units
      • funnel shaped: hidden layers progressively get smaller
model <- keras_model_sequential() %>%
  layer_dense(units = ____, input_shape = ____, activation = ____) %>%
  layer_dense(units = 1)

model %>% compile(
  optimizer = optimizer_sgd(lr = 0.01, momentum = .9),
  loss = "mse"
)

(history <- model %>% fit(x, y, epochs = 50, verbose = FALSE))

Run the following to see how your adjusted model fits the actual data:

df %>%
  mutate(pred = predict(model, x) %>% as.vector()) %>%
  ggplot(aes(x, y)) +
  geom_point(alpha = 0.25) +
  geom_smooth(se = FALSE) +
  geom_line(aes(y = pred), lty = "dashed", color = "red", size = 1)

Key Takeaways

  • Hidden layers almost always use the ReLU activation function (this should be your default).
  • Control model capacity by width and depth (adding depth typically outperforms simply focusing on width).

Multi-predictor Multi-class Classification

Let’s get a little more complicated now and look at a dataset that has:

  • 3 predictor variables
  • 4 response classes
set.seed(123)
generated <- mlbench::mlbench.simplex(n = n*2, d = 3, sd = 0.3)

plot(generated)
# 3 features
head(generated$x)

# 4 response categories
head(generated$classes)

Preparing our data takes a little more effort in this case:

  • features: our features are already a matrix so we’re good
  • response: our response is a factor which we are going to convert to a matrix:
    • to_categorical dummy encodes our classes. This allows us to compute the predicted probability for each class
    • to_categorical expects a zero-based input from 0-n (Python 😒)
x <- generated$x
y <- generated$classes %>% as.numeric()
y <- to_categorical(y - 1)
n_classes <- ncol(y)

# our preprocesses response
head(y)

Fit model using validation

In practice we are unable to visualize the fit of our data to understand variance-bias tradeoff (i.e. are we over or underfitting our data). Consequently, we rely on using a validation set and what we call learning curves.

  • validation_split: will train model on first 80% of data and use the last 20% of data to see assess performance.
  • metrics: often we want to assess alternative metrics along with our loss score.
model <- keras_model_sequential() %>%
  layer_dense(units = 16, input_shape = ncol(x), activation = "relu") %>%
  layer_dense(units = n_classes, activation = "softmax")

model %>% compile(
  optimizer = optimizer_sgd(lr = 0.01, momentum = .9),
  loss = "categorical_crossentropy",
  metrics = "accuracy"
)

history <- model %>% fit(
  x, y, 
  batch_size = 32, 
  epochs = 20, 
  validation_split = 0.2
  )

Our learning curve shows some unique behavior. We can learn a lot about our model by paying attention to learning curves (see this extra notebook: https://rstudio-conf-2020.github.io/dl-keras-tf/notebooks/learning-curve-diagnostics.nb.html)

history

plot(history)

In this case, the problem is that our data is ordered so the last 20% of our data contains only one class. So we always want to make sure we are randomizing our data.

set.seed(123)
randomize <- sample(seq_len(n), size = n, replace = FALSE)
x <- x[randomize, ]
y <- y[randomize, ]

Now let’s try the same model again.

model <- keras_model_sequential() %>%
  layer_dense(units = 16, input_shape = ncol(x), activation = "relu") %>%
  layer_dense(units = n_classes, activation = "softmax")

model %>% compile(
  optimizer = optimizer_sgd(lr = 0.01, momentum = .9),
  loss = "categorical_crossentropy",
  metrics = "accuracy"
)

history <- model %>% fit(
  x, y, 
  batch_size = 32, 
  epochs = 20, 
  validation_split = 0.2
  )

Our results look much better. Our loss curve shows a few things that we always want to strive for:

  • The training and validation loss curves are very close to one another. Typically, there will be a gap between the two but our goal should be to minimize this gap. This leads to a more stable model that generalizes better
  • We prefer that our validation loss is above the training loss (when our metric is designed for “lower-is-better”). If our validation loss below (better) then the training loss then that typically means we are underfitting and we should increase capacity.
  • Our validation loss has stopped improving, which means we have trained it for enough epochs.
  • We want our validation loss to be as smooth as possible (iratic behavior means unstable generalization).
history

plot(history)

Effects of batch size

So far we’ve just been using the default batch size of 32. However, there are other options:

  • Batch gradient descent: computes the derivative of the gradient based on the entire dataset (all observations).
    • provides more accurate and smooth gradient descent but…
    • scales horribly to large data
  • Stochastic gradient descent: randomly selects an individual observations, computes gradients and updates model weights after this single observation has been evaluated.
    • provides quick feedback so the model learns quickly and…
    • results in noisy gradient descent which helps avoid local minimums but…
    • noisy gradient descent makes it hard to converge on global minimum and…
    • can result in unstable generalization
  • Mini-batch gradient descent: randomly selects a subset of observations, computes gradients and updates model weights after this subset has been evaluated.
    • Balances efficiencies of batch vs. stochastic
    • Balances robust convergence of batch with some stochastic nature to minimize local minima.
    • But one more hyperparameter to think about.
    • Most common: \(2^s\): 32, 64, 128, 256, 512

Go ahead and try:

  1. batch_size = 1 (stochastic gradient descent)
  2. batch_size = nrow(x) (batch gradient descent)
  3. batch_size = b where b equals 16, 32, 64, 128

Note: batch size and learning rate often interact and should be tuned together.

model <- keras_model_sequential() %>%
  layer_dense(units = 16, input_shape = ncol(x), activation = "relu") %>%
  layer_dense(units = n_classes, activation = "softmax")

model %>% compile(
  optimizer = optimizer_sgd(lr = 0.01, momentum = .9),
  loss = "categorical_crossentropy",
  metrics = "accuracy"
)

history <- model %>% fit(
  x, y, 
  batch_size = ____, 
  epochs = 20, 
  validation_split = 0.2
  )

Making predictions

When predicting with classification models we can either predict the class (based on probability > 0.5) or predict the probabilities for each class:

# predicting probabilities
model %>% predict(x) %>% head()

# predicting classes
model %>% predict_classes(x) %>% head()

Key Takeaways

  • Monitor the learning curves to diagnose model performance.
  • Batch size effects our learning curve. Mini-batch sizes of 32, 64, 128, 256 tend to perform best.

Mini-Project (time dependent)

Time to work with some real (although unexciting) data - iris. This data contains:

  • 4 features (Sepal.Length, Sepal.Width, Petal.Length, Petal.Width)
  • 3 response classes (Species = setosa, versicolor, verginica)
head(iris)

Data prep

First step is to prepare our data. This involves converting our features to a tensor (aka matrix). Also, since our response variable is multi-class, we want to convert it to a 2D tensor (aka matrix). We also need to randomize the data.

These steps are provided for you:

# convert features to a tensor (aka matrix)
x <- iris[1:4] %>% as.matrix()

# convert response to a multi-class tensor (aka matrix)
y <- iris$Species %>% as.numeric()
y <- to_categorical(y - 1)

# randomize data
set.seed(123)
total_obs <- nrow(x)
randomize <- sample(seq_len(total_obs), size =  total_obs, replace = TRUE)
x <- x[randomize, ]
y <- y[randomize, ]

Note that our response tensor (y) has 3 colums. These columns relate alphabetically to our response classes:

  • column 1 = setosa
  • column 2 = versicolor
  • column 3 = virginica
head(y)

Modeling

Start with the following:

  • 1 hidden layer with 16 units
  • learning rate of 0.01 and no momentum
  • 20 epochs with batch sizes of 32
  • validation split of 20%

Then start adjusting the following:

  • learning rate (maybe add momentum)
  • model capacity (try wider and/or deeper capacity)
  • batch size (do larger or smaller batch sizes help performance)
  • epochs (do you need more or less epochs to reach a minimum validation loss)
# define architecture
model <- keras_model_sequential() %>%
  layer_dense(units = ____, input_shape = ____, activation = ____) %>%
  layer_dense(units = ____, activation = ____)

# define learning procedure
model %>% compile(
  optimizer = optimizer_sgd(lr = ____),
  loss = "categorical_crossentropy",
  metrics = "accuracy"
)

# train model
history <- model %>% fit(
  x, y, 
  batch_size = ____, 
  epochs = ____, 
  validation_split = ____
  )

Summary

This module is meant to only introduce some of the key ingredients involved in training a basic MLP model. However, as the mini-project probably demonstrated, it didn’t do much to help you understand how to put these ingredients together in a methodolical approach to maximize model performance. The next module aims to fill this gap and provide some best practices for training a model.

🏠

LS0tCnRpdGxlOiAiTWFpbiBJbmdyZWRpZW50cyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UpCmdncGxvdDI6OnRoZW1lX3NldChnZ3Bsb3QyOjp0aGVtZV9taW5pbWFsKCkpCmBgYAoKVGhpcyBtb2R1bGUgaXMgZGVzaWduZWQgdG8gcHJvdmlkZSB5b3UgYW4gaW50cm9kdWN0aW9uIHRvIHRoZSBrZXJhcyBBUEksIGRlZXAKbGVhcm5pbmcgYW5kIHNvbWUgb2YgdGhlIGtleSBjb21wb25lbnRzIHRoYXQgbWFrZSBETCBhbGdvcml0aG1zIHJ1bi4gVGhyb3VnaG91dAp0aGlzIG1vZHVsZSB5b3Ugd2lsbCBsZWFybiBhYm91dDoKCiogUGVyY2VwdHJvbnMKKiBHcmFkaWVudCBkZXNjZW50CiogQWN0aXZhdGlvbiBmdW5jdGlvbnMKKiBMZWFybmluZyByYXRlICYgbW9tZW50dW0KKiBNb2RlbCBjYXBhY2l0eSAod2lkdGggdnMuIGRlcHRoKQoqIExlYXJuaW5nIGN1cnZlcwoqIEJhdGNoIHNpemUKCiMgUGFja2FnZSBSZXF1aXJlbWVudHMgey50YWJzZXQgLnRhYnNldC1mYWRlfQoKIyMgTG9hZGluZwoKTGV0J3MgbG9hZCB0aGUgYGtlcmFzYCBwYWNrYWdlIGFsb25nIHdpdGggYSBjb3VwbGUgb3RoZXIgcGFja2FnZXMgd2UnbGwgdXNlLgoKYGBge3J9CmxpYnJhcnkoa2VyYXMpICAgICAgICMgZm9yIG1vZGVsaW5nCmxpYnJhcnkodGlkeXZlcnNlKSAgICMgZm9yIHdyYW5nbGluZyAmIHZpc3VhbGl6YXRpb24KbGlicmFyeShnbHVlKSAgICAgICAgIyBmb3Igc3RyaW5nIGxpdGVyYWxzCmBgYAoKIyMgSW5zdGFsbGF0aW9uCgpOb3JtYWxseSwgeW91IHdpbGwgYmUgc3RhcnRpbmcgb3V0IGZyb20gc2NyYXRjaCBhbmQgbmVlZCB0byBpbnN0YWxsIGFuZCBzZXQgdXAKa2VyYXMgb24geW91ciBvd24gbGFwdG9wLiBGaXJzdCwgeW91IG5lZWQgdG8gaW5zdGFsbCBrZXJhcyBmcm9tIENSQU4uIE9uY2UgdGhlCnBhY2thZ2UgaXMgaW5zdGFsbGVkLCB5b3UgbmVlZCB0byBpbnN0YWxsIHRoZSBLZXJhcyBhbmQgVGVuc29yRmxvdyBQeXRob24KcGFja2FnZXMsIHdoaWNoIGlzIHdoYXQgdGhlIFIgS2VyYXMgYW5kIFRlbnNvckZsb3cgcGFja2FnZXMgY29tbXVuaWNhdGUKd2l0aC4ga2VyYXMgc2ltcGxpZmllcyB0aGlzIHdpdGggYGluc3RhbGxfa2VyYXMoKWAgd2hpY2ggYWxsb3dzIGZvcjoKCiogYm90aCBHUFUgJiBDUFUgb3B0aW9ucyBzZXR1cHMKKiBpbnN0YWxsYXRpb24gaW4gYSB2aXJ0dWFsIG9yIGNvbmRhIGVudmlyb25tZW50Ciogc2V0dXAgZm9yIFRoZWFubyAmIENOVEsgYmFja2VuZHMgcmF0aGVyIHRoYW4gVGVuc29yRmxvdwoKU2VlIGh0dHBzOi8va2VyYXMucnN0dWRpby5jb20vIGZvciBkZXRhaWxzLgoKYGBge3IgaW5zdGFsbCwgZXZhbCA9IEZBTFNFfQojIGluc3RhbGwga2VyYXMgaWYgbmVjZXNzYXJ5CiMgaW5zdGFsbC5wYWNrYWdlcygia2VyYXMiKQoKIyBkZWZhdWx0IENQVS1iYXNlZCBpbnN0YWxsYXRpb25zIG9mIEtlcmFzIGFuZCBUZW5zb3JGbG93CiMgaW5zdGFsbF9rZXJhcygpCgojIGZvciBHUFUgaW5zdGFsbGF0aW9uCiMgaW5zdGFsbF9rZXJhcyh0ZW5zb3JmbG93ID0gImdwdSIpCmBgYAoKRm9yIHRoaXMgd29ya3Nob3Agd2Ugd2lsbCBiZSB1c2luZyBhIGNsb3VkIGVudmlyb25tZW50IHRvIGVuc3VyZSB3ZSBhcmUgYWxsIApvcGVyYXRpbmcgaW4gY29tbW9uIGVudmlyb25tZW50IGFuZCBLZXJhcyBhbmQgVGVuc29yRmxvdyBoYXZlIGFscmVhZHkgYmVlbiAKaW5zdGFsbGVkLgoKIyBTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb24KCkZvciBvdXIgZmlyc3QgZXhhbXBsZSwgbGV0J3MgbG9vayBhdCBhIHZlcnkgc2ltcGxlIGxpbmVhciBwcm9ibGVtIGJhc2VkIG9uIGRhdGEKd2l0aDoKCiogb2JzID0gMSwwMDAKKiBpbnRlcmNlcHQgPSAzMAoqIHNsb3BlID0gNQoqIHdpdGggYSBsaXR0bGUgYml0IG9mIHJhbmRvbSBub2lzZQoKYGBge3J9Cm4gPC0gMTAwMCAgICMgbiBvYnNlcnZhdGlvbnMKYiA8LSAzMCAgICAgIyBpbnRlcmNlcHQKYSA8LSA1ICAgICAgIyBzbG9wZQoKc2V0LnNlZWQoMTIzKQooZGYgPC0gdGliYmxlKAogIHggPSBydW5pZihuLCBtaW4gPSAtMSwgbWF4ID0gMSksCiAgeSA9IGIgKyBhKnggKyBybm9ybShuKQopKQpgYGAKCiMjIFNpbXBsZSByZWdyZXNzaW9uIHdpdGggT0xTCgpJJ20gYXNzdW1pbmcgd2UncmUgYWxsIGZhbWlsaWFyIHdpdGggdGhlIE9MUyBtZXRob2RvbG9neSBmb3IgbGluZWFyIHJlZ3Jlc3Npb24KbW9kZWxpbmcgd2hlcmUgb3VyIG9iamVjdGl2ZSBmdW5jdGlvbiAoYWthIF9fX2xvc3NfX18pIGlzOgoKJCRsb3NzID0gTVNFID0gXGZyYWN7MX17bn1cc3VtXm5fe2ktMX0oWV9pIC0gXGhhdCBZX2kpXjIkJAoKSWYgd2UgYXBwbHkgT0xTIHRvIG91ciBkYXRhLCB3ZSBnZXQ6CgoqIGVzdGltYXRlZCBpbnRlcmNlcHQgPSAzMC4wMQoqIGVzdGltYXRlZCBzbG9wZSA9IDQuOTcKKiBsb3NzIHNjb3JlIChNU0UpID0gMS4wMDIKCmBgYHtyfQpsbV9tb2RlbCA8LSBzdW1tYXJ5KGxtKHkgfiB4LCBkYXRhID0gZGYpKQpsbV9tb2RlbApgYGAKCldlIGNhbiBpbGx1c3RyYXRlIHRoaXMgbW9kZWwgZml0IHRvIG91ciBkYXRhOgoKYGBge3J9Cm1zZSA8LSBsbV9tb2RlbFtbInNpZ21hIl1dCgpnZ3Bsb3QoZGYsIGFlcyh4LCB5KSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArCiAgZ2d0aXRsZShnbHVlKCJNU0UgPSB7bXNlfSIpKQpgYGAKCiMjIFNpbXBsZSByZWdyZXNzaW9uIHdpdGggYSBwZXJjZXB0cm9uCgpOb3csIGxldCdzIGlsbHVzdHJhdGUgcGVyZm9ybWluZyBhIHNpbWlsYXIgcHJvY2VzcyBidXQgd2l0aCBhIGJhc2ljIGJ1aWxkaW5nCmJsb2NrIG9mIG5ldXJhbCBuZXR3b3JrcyAtLSB0aGUgcGVyY2VwdHJvbi4KClRvIG1vZGVsIHdpdGgga2VyYXMgd2UgbmVlZCBvdXIgZGF0YSB0byBiZSBpbiBfX190ZW5zb3JzX19fLiBXZSdsbCBkaXNjdXNzCnRlbnNvcnMgbW9yZSBsYXRlciBidXQgZm9yIG5vdyBqdXN0IHJlYWxpemUgdGhhdDoKCiogMUQgdGVuc29yID0gdmVjdG9yCiogMkQgdGVuc29yID0gbWF0cml4CgpgYGB7cn0KeCA8LSBhcy5tYXRyaXgoZGYkeCkKeSA8LSBkZiR5CmBgYAoKVHJhaW5pbmcgYSBuZXVyYWwgbmV0d29yayBtb2RlbCBjb25zaXN0cyBvZiAzIHN0ZXBzOgoKMS4gRGVmaW5lIG1vZGVsIGFyY2hpdGVjdHVyZQoyLiBEZWZpbmUgaG93IG91ciBtb2RlbCBpcyBnb2luZyB0byBsZWFybgozLiBUcmFpbiBvdXIgbW9kZWwKCiMjIyAxLiBEZWZpbmUgbW9kZWwgYXJjaGl0ZWN0dXJlCgpEZWZpbmluZyBhbiBhcmNoaXRlY3R1cmUgaW5jbHVkZXMgZGVmaW5pbmcgdGhlIHR5cGUgb2YgbW9kZWwgYW5kIHRoZSBhcnJhbmdlbWVudApvZiBsYXllcnM6CgpgYGB7fQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxLCBpbnB1dF9zaGFwZSA9IG5jb2woeCkpCmBgYAoKKiBEZWZpbmUgc3RydWN0dXJlIG9mIG1vZGVsCiAgICAtIFNlcXVlbnRpYWw6IGxpbmVhciBsYXllcnMgKGkuZS4gTUxQLCBDTk4sIFJOTiwgTFNUTSkKICAgIC0gRnVuY3Rpb25hbDogYWRkcyBtb3JlIGZsZXhpYmlsaXR5IChpLmUuIGNvbWJpbmluZyBDTk4gJiBMU1RNIHRvIHByZWRpY3QKICAgICAgcHJvYmFiaWxpdHkgb2YgY2FuY2VyKQoqIERlZmluZSBhcnJhbmdlbWVudCBhbmQgc2hhcGUgb2YgbGF5ZXJzCiAgICAtIGBsYXllcl9kZW5zZWA6IGEgc2luZ2xlIGxheWVyIG9mIG5vZGVzCiAgICAtIGB1bml0cyA9IDFgOiBhIHNpbmdsZSBwZXJjZXB0cm9uIHVuaXQgaW4gb3VyIGxheWVyCiAgICAtIGBpbnB1dF9zaGFwZWA6IHdlIG5lZWQgdG8gdGVsbCBvdXIgZmlyc3QgbGF5ZXIgaG93IG1hbnkgaW5wdXRzIHRvIGV4cGVjdAoKIVtdKGltYWdlcy9wZXJjZXB0cm9uLnBuZykKCgojIyMgMi4gRGVmaW5lIGhvdyBvdXIgbW9kZWwgaXMgZ29pbmcgdG8gbGVhcm4KClRoZSB3aG9sZSBnb2FsIG9mIHRyYWluaW5nIGEgbmV1cmFsIG5ldHdvcmsgaXMgdG8gZmluZCB0aGUgb3B0aW1hbCBzZXQgb2YKcGFyYW1ldGVyIHdlaWdodHMgKGFrYSBjb2VmZmljaWVudHMgaW4gT0xTLXNwZWFrKS4gCgo+IF9fX091ciBnb2FsIGlzIHRvIGZpbmQgd2VpZ2h0cyB0aGF0IG1pbmltaXplIHRoZSBsb3NzIHNjb3JlX19fCgpXZSBkZWZpbmUgaG93IG91ciBtb2RlbCBpcyBnb2luZyB0byBsZWFybiB3aXRoIGBjb21waWxlKClgOgoKYGBge30KbW9kZWwgJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gInNnZCIsCiAgbG9zcyA9ICJtc2UiCikKYGBgCgotIF9fT3B0aW1pemVyX186IG5ldXJhbCBuZXR3b3JrcyBsZWFybiB2aWEgX19fYmFja3Byb3BhZ2F0aW9uX19fLiBUaGVyZSBhcmUKICB2YXJpb3VzIF9fX2JhY2twcm9wYWdhdGlvbl9fXyBhbGdvcml0aG1zIGJ1dCB3ZSdsbCBzdGFydCB3aXRoIHRoZSBtb3N0IGJhc2ljLi4uCiAgc3RvY2hhc3RpYyBncmFkaWVudCBkZXNjZW50IChTR0QpLgogIAotIF9fbG9zc19fOiBob3cgZG8gd2Ugd2FudCB0byBtZWFzdXJlIG91ciBtb2RlbCdzIGVycm9yLiBrZXJhcyBjb21lcyB3aXRoIG1hbnkKICBidWlsdC1pbiBsb3NzIGZ1bmN0aW9ucyBhbmQgd2UgY2FuIGV2ZW4gY3JlYXRlIGN1c3RvbSBsb3NzIGZ1bmN0aW9ucy4gSGVyZSwKICB3ZSdsbCB1c2UgTVNFLgoKIVtdKGltYWdlcy9zZ2QucG5nKQoKCiMjIyAzLiBUcmFpbiBvdXIgbW9kZWwKClRoZSBsYXN0IHRoaW5nIHdlIG5lZWQgaXMgaG93IHRvIHRyYWluIG91ciBtb2RlbCwgd2hpY2ggd2UgZG8gd2l0aCBgZml0KClgOgoKYGBge30KaGlzdG9yeSA8LSBtb2RlbCAlPiUgZml0KHgsIHksIGJhdGNoX3NpemUgPSAzMiwgZXBvY2hzID0gMTApCmBgYAoKKiBgeGA6IGZlYXR1cmUgdGVuc29yCiogYHlgOiB0YXJnZXQgdGVuc29yCiogYGJhdGNoX3NpemVgOiBwaWNrIG9ic2VydmF0aW9ucyBmcm9tIG91ciB0cmFpbmluZyBkYXRhLCBwZXJmb3JtIGZvcndhcmQgcGFzcywKICAgY29tcHV0ZSBsb3NzIHNjb3JlLCBjb21wdXRlIGdyYWRpZW50LCBwZXJmb3JtIGJhY2t3YXJkIHBhc3MsIHVwZGF0ZSBvdXIKICAgd2VpZ2h0IChkZWZhdWx0ID0gMzIpLgoqIGBlcG9jaGA6IDEgZXBvY2ggPSBvbmUgZm9yd2FyZCBwYXNzIGFuZCBvbmUgYmFja3dhcmQgcGFzcyBvZiBhbGwgdGhlIHRyYWluaW5nCiAgIGV4YW1wbGVzLiBXZSdyZSByZXBlYXRpbmcgdGhhdCAxMCB0aW1lcyAoZGVmYXVsdCA9IDEwKS4KCgojIyMgUHV0dGluZyBpdCBhbGwgdG9nZXRoZXIKCkxldCdzIHB1dCBhbGwgdGhyZWUgc3RlcHMgdG9nZXRoZXIgYW5kIHRyYWluIG91ciBtb2RlbC4gSGVyZSdzIGEgdmlzdWFsCmRlcGljdGlvbjoKCiFbXShpbWFnZXMvcHV0X3RvZ2V0aGVyLnBuZykKCkFuZCBoZXJlJ3MgdGhlIGNvZGU6CgpgYGB7cn0KIyAxLiBEZWZpbmUgbW9kZWwgYXJjaGl0ZWN0dXJlCm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEsIGlucHV0X3NoYXBlID0gbmNvbCh4KSkKCiMgMi4gRGVmaW5lIGhvdyBvdXIgbW9kZWwgaXMgZ29pbmcgdG8gbGVhcm4KbW9kZWwgJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gInNnZCIsCiAgbG9zcyA9ICJtc2UiCikKCiMgMy4gVHJhaW4gb3VyIG1vZGVsCmhpc3RvcnkgPC0gbW9kZWwgJT4lIGZpdCh4LCB5LCBlcG9jaHMgPSAxMCkKYGBgCgpXaGVyZWFzIGluIE9MUyB3ZSBjYWxsIHRoZSBpbnRlcmNlcHQgYW5kIHNsb3BlIHBhcmFtZXRlcnMgImNvZWZmaWNpZW50cyIsIGluCm5ldXJhbCBuZXR3b3JrcyB3ZSBjYWxsIHRoZW0gX19fd2VpZ2h0c19fXy4gV2UgY2FuIHNlZSB0aGF0IGFmdGVyIDEwIGVwb2NocyBvdXIKd2VpZ2h0cyBhcmUgZ2V0dGluZyBjbG9zZSB0byB0aGUgdW5kZXJseWluZyAidHJ1dGgiIHZhbHVlcyAoc2xvcGUgPSA1LAppbnRlcmNlcHQgPSAzMCkgYnV0IGFsc28gbm90aWNlIHRoYXQgb3VyIE1TRSBsb3NzIHNjb3JlIGlzIHN0aWxsIGRlY3JlYXNpbmcKYWZ0ZXIgMTAgZXBvY2hzLgoKYGBge3J9CmdldF93ZWlnaHRzKG1vZGVsKQpgYGAKCiMjIyBNb2RlbHMgYXJlIG9iamVjdCBvcmllbnRlZAoKTm90ZSB0aGF0IG1vZGVsaW5nIHdpdGgga2VyYXMvdGVuc29yZmxvdyBpbiBSIG1heSBmZWVsIGEgYml0IGRpZmZlcmVudCB0aGFuCm90aGVyIG1vZGVsaW5nIHBhY2thZ2VzIHlvdSd2ZSB1c2VkIGluIFIuIFNpbmNlIGtlcmFzL3RlbnNvcmZsb3cgYXJlIHJldGljdWxhdGVkCmZyb20gUHl0aG9uLCB0aGUgbW9kZWwgaXMgYW4gX19fb2JqZWN0IG9yaWVudGVkX19fIG9iamVjdCB3aXRoIFB5dGhvbiBhdHRyaWJ1dGVzLgoKMS4gT2JqZWN0IG9yaWVudGVkIC0gb3VyIG1vZGVsIG9iamVjdCBjaGFuZ2VzIHdpdGhvdXQgYXNzaWdubWVudDoKICAgLSBgbW9kZWwgJT4lIGNvbXBpbGUoKWAgY2hhbmdlZCBvdXIgbW9kZWwgb2JqZWN0IGJ5IGFkZGluZyB0aGUgb3B0aW1pemVyIGFuZAogICAgIGxvc3MgcGFyYW1ldGVyIGFyZ3VtZW50cwogICAtIGBtb2RlbCAlPiUgZml0KClgIHdpbGwgY29udGludWUgdG8gYnVpbGQgb250byBvdXIgZXhpc3RpbmcgbW9kZWwKICAgCmBgYHtyfQojIGxldCdzIGV4ZWN1dGUgb25lIG1vcmUgZXBvY2guIE5vdGUgaG93IG91ciBsb3NzIGRlY3JlYXNlcyBmcm9tIHRoZSBsYXN0IGVwb2NoCiMgYWJvdmUKbW9kZWwgJT4lIGZpdCh4LCB5LCBlcG9jaCA9IDEpCgojIG91ciBtb2RlbCB3ZWlnaHRzIGdldCB1cGRhdGVkIGZyb20gdGhpcyBsYXN0IGVwb2NoIGFuZCBjb250aW51ZSB0byBnZXQgY2xvc2VyCiMgdG8gdGhlIHVuZGVybHlpbmcgdHJ1ZSB2YWx1ZXMKZ2V0X3dlaWdodHMobW9kZWwpCmBgYAoKMi4gT3VyIG1vZGVsIGlzIGEgUHl0aG9uIG9iamVjdCAtIHRoZXJlIHdpbGwgYmUgdGhpbmdzIHlvdSBjYW4gbm90IGRpcmVjdGx5CiAgIGFjY2VzcyBiZWNhdXNlIHRoZXkgYXJlIFB5dGhvbiBvYmplY3RzLiBIb3dldmVyLCBmb3IgbW9zdCB0aGluZ3MgdGhhdCB5b3Ugd2FudAogICB0byBhY2Nlc3MgdGhlcmUgd2lsbCBiZSBhIGZ1bmN0aW9uIHRvIGV4cG9ydCB0aGVtOgogICAKYGBge3J9CiMgdGhlIG1vZGVsIHdlaWdodHMgYXJlIGhlbGQgaW4gUHl0aG9uIG51bXB5IGFycmF5cwptb2RlbCR3ZWlnaHRzCgojIHdlIHVzZSBoZWxwZXIgZnVuY3Rpb25zIHRvIGV4cG9ydCB0aGVzZSBraW5kcyBvZiBvYmplY3RzCmdldF93ZWlnaHRzKG1vZGVsKQpgYGAKCgojIyBZb3VyIFR1cm4gKDMgbWluKQoKMS4gRmlsbCBpbiB0aGUgYmxhbmtzIGJlbG93IGFuZCB0cmFpbiB0aGUgbW9kZWwgZm9yIDI1IGVwb2Nocy4KMi4gRXhwbG9yZSB0aGUgYGhpc3RvcnlgIG9iamVjdC4KMy4gV2hhdCBhcmUgdGhlIGZpbmFsIHdlaWdodHMgZm9yIHRoaXMgbW9kZWw/IEhvdyBkbyB0aGV5IGNvbXBhcmUgdG8gdGhlCiAgIHVuZGVybHlpbmcgaW50ZXJjZXB0ICgzMCkgYW5kIHNsb3BlICg1KT8KCmBgYHtyfQojIDEuIERlZmluZSBtb2RlbCBhcmNoaXRlY3R1cmUKbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gX19fLCBpbnB1dF9zaGFwZSA9IF9fX18pCgojIDIuIERlZmluZSBob3cgb3VyIG1vZGVsIGlzIGdvaW5nIHRvIGxlYXJuCm1vZGVsICU+JSBjb21waWxlKAogIG9wdGltaXplciA9ICJzZ2QiLAogIGxvc3MgPSBfX19fCikKCiMgMy4gVHJhaW4gb3VyIG1vZGVsCmhpc3RvcnkgPC0gbW9kZWwgJT4lIGZpdCh4LCB5LCBlcG9jaHMgPSBfX19fKQpgYGAKCiMjIEdyYWRpZW50IGRlc2NlbnQKCldlIGNhbiBzZWUgdGhlIHByb2dyZXNzaW9uIG9mIHRoZSBncmFkaWVudCBkZXNjZW50IHByb2Nlc3MgYnkgZXhhbWluaW5nIHRoZQp3ZWlnaHRzIG91ciBtb2RlbCBwcm9kdWNlcyBhZnRlciBlYWNoIGVwb2NoLiBUaGlzIGlzIG5vdCBzb21ldGhpbmcgeW91IHdpbGwKZG8gb2Z0ZW4gYnV0LCByYXRoZXIsIGhlbHBzIG1ha2UgdGhlIGdyYWRpZW50IGRlc2NlbnQgcHJvY2VzcyBtb3JlIGNvbmNyZXRlLgoKYGBge3J9CiMgZGF0YSBmcmFtZSB0byBkdW1wIG91ciByZXN1bHRzCm1vZGVsX2VzdCA8LSBleHBhbmRfZ3JpZCgKICBlcG9jaCA9IDE6MjUsCiAgYV9zZ2QgPSBOQSwKICBiX3NnZCA9IE5BCikKCiMgMS4gRGVmaW5lIG1vZGVsIGFyY2hpdGVjdHVyZQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxLCBpbnB1dF9zaGFwZSA9IG5jb2woeCkpCgojIDIuIERlZmluZSBob3cgb3VyIG1vZGVsIGlzIGdvaW5nIHRvIGxlYXJuCm1vZGVsICU+JSBjb21waWxlKAogIG9wdGltaXplciA9ICJzZ2QiLAogIGxvc3MgPSAibXNlIgopCgojIDMuIFRyYWluIG91ciBtb2RlbCBmb3IgMjUgZXBvY2hzIGFuZCByZWNvcmQgdGhlIHdlaWdodHMgYWZ0ZXIgZWFjaCBlcG9jaApmb3IgKHJvdyBpbiBzZXFfbGVuKG5yb3cobW9kZWxfZXN0KSkpIHsKICAKICBoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXQoeCwgeSwgZXBvY2ggPSAxLCB2ZXJib3NlID0gRkFMU0UpCiAgCiAgY3VycmVudF93dHMgPC0gZ2V0X3dlaWdodHMobW9kZWwpCgogIG1vZGVsX2VzdFtyb3csICJhX3NnZCJdIDwtIGN1cnJlbnRfd3RzW1sxXV0KICBtb2RlbF9lc3Rbcm93LCAiYl9zZ2QiXSA8LSBjdXJyZW50X3d0c1tbMl1dCn0KYGBgCgpUaGUgZm9sbG93aW5nIHRhYmxlIHNob3dzIHRoZSBlc3RpbWF0ZSBzbG9wZSAoYGFfc2dkYCkgYW5kIGludGVyY2VwdCAoYGJfc2dkYCkKcHJvZHVjZWQgYnkgU0dEIGFmdGVyIGVhY2ggZXBvY2guCgpgYGB7cn0KbW9kZWxfZXN0CmBgYAoKYGBge3J9Cmhpc3RvcnkKYGBgCgpXZSBjYW4gdmlzdWFsaXplIG91ciBsaW5lYXIgbW9kZWwgYWZ0ZXIgZWFjaCBlcG9jaCB3aXRoIHRoZSBmb2xsb3dpbmcuIE5vdGUgaG93CmVhY2ggZXBvY2ggcmVzdWx0cyBpbiBhIGxpbmVhciBwcmVkaWN0aW9uIChkb3R0ZWQpIHRoYXQgZ2V0cyBjbG9zZXIgdG8gdGhlIHRydXRoCihibHVlIGxpbmUpLiBBZnRlciAyNSBlcG9jaHMgb3VyIG1vZGVsIGJhc2ljYWxseSBjb252ZXJnZXMgdG8gdGhlIHNhbWUgcmVzdWx0cwooeWVsbG93IGRvdHRlZCBsaW5lKS4KCldlIGNhbiBzZWUgdGhpcyB3aXRoIG91ciBsb3NzIChNU0UpIHRoYXQgbmVhcmx5IGVxdWF0ZXMgdGhlIE9MUyBNU0UgKDEuMDAxODcpLgoKYGBge3J9CmVwb2NoX3ByZWQgPC0gbWVyZ2UoZGYsIG1vZGVsX2VzdCwgYWxsID0gVFJVRSkgJT4lCiAgbXV0YXRlKHByZWQgPSBiX3NnZCArIGFfc2dkKngpCgpsYXN0X2Vwb2NoIDwtIGZpbHRlcihlcG9jaF9wcmVkLCBlcG9jaCA9PSBtYXgoZXBvY2gpKQoKZ2dwbG90KGRhdGEgPSBkZiwgYWVzKHgsIHkpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIHNpemUgPSAxLjUpICsKICBnZW9tX2xpbmUoZGF0YSA9IGVwb2NoX3ByZWQsIGFlcyh4LCBwcmVkLCBncm91cCA9IGVwb2NoKSwgbHR5ID0gImRvdHRlZCIpICsKICBnZW9tX2xpbmUoZGF0YSA9IGxhc3RfZXBvY2gsIGFlcyh4LCBwcmVkLCBncm91cCA9IGVwb2NoKSwgCiAgICAgICAgICAgIGx0eSA9ICJkb3R0ZWQiLCBjb2xvciA9ICJ5ZWxsb3ciLCBzaXplID0gMikgKwogIGdndGl0bGUoZ2x1ZSgiTVNFID0ge2hpc3RvcnkkbWV0cmljcyRsb3NzfSIpKQpgYGAKCiMjIEtleSBUYWtlYXdheXMKCiogQSBiYXNpYyBzaW5nbGUgcGVyY2VwdHJvbiBjb21wdXRlcyB0aGUgc2FtZSB0cmFuc2Zvcm1hdGlvbiBhcyBPTFM6CgokJFxoYXQgeSA9IGJpYXMgKyB3ZWlnaHRfMSBcdGltZXMgeF8xICsgd2VpZ2h0XzIgXHRpbWVzIHhfMiArIFxkb3RzICsgd2VpZ2h0X24gXHRpbWVzIHhfbiQkCgoqIE5ldXJhbCBuZXR3b3JrcyBsZWFybiB2aWEgZ3JhZGllbnQgZGVzY2VudCAtIGFuIGl0ZXJhdGl2ZSBhcHByb2FjaCBvZiBwcmVkaWN0aW5nCiAgd2l0aCBhIGZvcndhcmQgcGFzcywgbWVhc3VyaW5nIHRoZSBncmFkaWVudCBvZiB0aGUgZXJyb3IsIGFuZCBwZXJmb3JtaW5nIGEKICBiYWNrd2FyZCBwYXNzIHRvIHVwZGF0ZSB0aGUgd2VpZ2h0cyBiYXNlZCBvbiB0aGUgZ3JhZGllbnQuCgoKIyBCaW5hcnkgQ2xhc3NpZmljYXRpb24KCkxldCdzIGRvIHRoZSBzYW1lIHByb2Nlc3MgYnV0IG5vdyB3ZSdsbCBkbyBzbyB3aXRoIGEgYmluYXJ5IGNsYXNzaWZpY2F0aW9uCnByb2JsZW0gKGkuZS4gcHJlZGljdGluZyB5ZXMgdnMuIG5vIHJlc3BvbnNlKS4KCmBgYHtyfQpzZXQuc2VlZCgxMjMpCmdlbmVyYXRlZCA8LSBtbGJlbmNoOjptbGJlbmNoLnNpbXBsZXgobiA9IDEwMDAsIGQgPSAxLCBzZCA9IC4zKQp4IDwtIGdlbmVyYXRlZCR4CnkgPC0gaWZlbHNlKGdlbmVyYXRlZCRjbGFzc2VzID09IDEsIDAsIDEpCgooZGYgPC0gdGliYmxlKHggPSBhcy52ZWN0b3IoeCksIHkgPSB5KSkKYGBgCgpPdXIgZ2VuZXJhdGVkIGRhdGEgaGFzIHNvbWUgb3ZlcmxhcCBzbyB0aGVyZSBpcyBubyBsaW5lYXIgc2VwZXJhdGlvbiB3aXRob3V0CmhhdmluZyBzb21lIGVycm9yLiBOb3RlIHRoYW4gd2hlbiBkaXNjdXNzaW5nIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtcywgd2UKd2lsbCBtYWlubHkgdXNlIHRoZSBjcm9zc2VudHJvcHkgKGFrYSBsb2cgbG9zcykgbG9zcyBmdW5jdGlvbi4KCmBgYHtyfQpnbG1fbW9kZWwgPC0gZ2xtKHkgfiB4LCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IiksIGRhdGEgPSBkZikKY3Jvc3NlbnRyb3B5IDwtIE1MbWV0cmljczo6TG9nTG9zcyhnbG1fbW9kZWwkZml0dGVkLnZhbHVlcywgZGYkeSkKCmdncGxvdChkZiwgYWVzKHgsIHkpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBhcy5mYWN0b3IoeSkpLCBzaXplID0gMiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJnbG0iLCBtZXRob2QuYXJncyA9IGxpc3QoZmFtaWx5ID0gImJpbm9taWFsIiksIHNlID0gRkFMU0UpICsKICBnZ3RpdGxlKGdsdWUoImNyb3NzZW50cm9weSA9IHtjcm9zc2VudHJvcHl9IikpICsKICB5bGFiKCJwcm9iYWJpbGl0eSB5ID0gMSIpCmBgYAoKIyMgU2lnbW9pZCBBY3RpdmF0aW9uIEZ1bmN0aW9uCgpXaGVuIHByZWRpY3RpbmcgYSBiaW5hcnkgcmVzcG9uc2UsIHdlIHR5cGljYWxseSB3YW50IHRvIHByZWRpY3QgYSByZWFsIHZhbHVlCmJldHdlZW4gMC0xIHJlcHJlc2VudGluZyB0aGUgcHJvYmFiaWxpdHkgb2YgdGhlIHBvc2l0aXZlIGJpbmFyeSBjbGFzcy4KVW5mb3J0dW5hdGVseSBvdXIgcmVndWxhciBwZXJjZXB0cm9uIGNyZWF0ZXMgYSBsaW5lYXIgdHJhbnNmb3JtYXRpb24uIEhvd2V2ZXIsCndlIGNhbiBhcHBseSBhbiBfX19hY3RpdmF0aW9uIGZ1bmN0aW9uX19fIHRvIHRyYW5zZm9ybSB0aGlzIGxpbmVhciB0cmFuc2Zvcm1hdGlvbgp0byBhIG5vbi1saW5lYXIgdHJhbnNmb3JtYXRpb24uCgpXaGVuIHByZWRpY3RpbmcgYSBiaW5hcnkgcmVzcG9uc2UsIHdlIHVzZSBhIF9fX3NpZ21vaWRfX18gYWN0aXZhdGlvbiB0byBjb252ZXJ0Cm91ciBsaW5lYXIgdHJhbnNmb3JtYXRpb24gdG8gYSAwLTEgcHJvYmFiaWxpdHkgb2YgdGhlIHBvc2l0aXZlIGNsYXNzLgoKJCRzaWdtb2lkKHkpID0gXGZyYWN7MX17MStlXnsteX19JCQKCiFbXShpbWFnZXMvc2lnbW9pZC5qcGcpCgpXaGVuIHByZWRpY3RpbmcgYSBiaW5hcnkgcmVzcG9uc2UsIHdlIG5lZWQgdG8gbWFrZSB0aGUgZm9sbG93aW5nIGNoYW5nZXMgdG8gb3VyCmNvZGU6CgoqIEFkZCBgYWN0aXZhdGlvbiA9ICJzaWdtb2lkImAgdG8gdGhlIGxheWVyIHRoYXQgaXMgcHJlZGljdGluZyB0aGUgb3V0cHV0LgoqIE5vdGUgdGhhdCBzaW5jZSB3ZSBhcmUgcHJlZGljdGluZyB0aGUgcHJvYmFiaWxpdHkgZnJvbSAwLTEgZm9yIG91ciByZXNwb25zZSwKICB3ZSBrZWVwIGB1bml0cyA9IDFgLgoqIGxvc3MgLSB3ZSBjaGFuZ2UgYGxvc3MgPSAiYmluYXJ5X2Nyb3NzZW50cm9weSJgIHRvIHVzZSB0aGUgY3Jvc3NlbnRyb3B5IC8gbG9nCiAgbG9zcyBvYmplY3RpdmUgZnVuY3Rpb24uCgpgYGB7cn0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgaW5wdXRfc2hhcGUgPSBuY29sKHgpLCBhY3RpdmF0aW9uID0gInNpZ21vaWQiKQoKbW9kZWwgJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gInNnZCIsCiAgbG9zcyA9ICJiaW5hcnlfY3Jvc3NlbnRyb3B5IgopCgooaGlzdG9yeSA8LSBtb2RlbCAlPiUgZml0KHgsIHksIGVwb2NocyA9IDUwLCB2ZXJib3NlID0gRkFMU0UpKQpgYGAKCldlIHNlZSB0aGF0IG91ciBsb3NzIGlzIHF1aXRlIGEgYml0IG9mZiBmcm9tIG91ciBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLgoKYGBge3J9CmRmICU+JQogIG11dGF0ZShwcmVkID0gcHJlZGljdChtb2RlbCwgeCkgJT4lIGFzLnZlY3RvcigpKSAlPiUKICBnZ3Bsb3QoYWVzKHgsIHkpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBhcy5mYWN0b3IoeSkpLCBzaXplID0gMiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJnbG0iLCBtZXRob2QuYXJncyA9IGxpc3QoZmFtaWx5ID0gImJpbm9taWFsIiksIHNlID0gRkFMU0UpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkKSwgbHR5ID0gImRhc2hlZCIpICsKICBnZ3RpdGxlKGdsdWUoImNyb3NzZW50cm9weSA9IHttaW4oaGlzdG9yeSRtZXRyaWNzJGxvc3MpfSIpKSArCiAgeWxhYigicHJvYmFiaWxpdHkgb2YgeSA9IDEiKQpgYGAKCkhvd2V2ZXIsIGlmIHdlIGxvb2sgYXQgb3VyIGxvc3Mgc2NvcmVzLCB3ZSBzZWUgdGhhdCB0aGV5IGFyZSBzdGlsbCBpbXByb3ZpbmcsCml0cyBqdXN0IHRha2luZyBhIGxvbmcgdGltZS4gUGx1cywgaXQgbG9va3MgbGlrZSB0aGVyZSBpcyBtb3JlIGltcHJvdmVtZW50IHRoYXQKY2FuIGJlIG1hZGUgdG8gb3VyIGxvc3MuCgpgYGB7cn0KcGxvdChoaXN0b3J5KQpgYGAKCiMjIExlYXJuaW5nIHJhdGUgYW5kIG1vbWVudHVtCgpBbiBpbXBvcnRhbnQgcGFyYW1ldGVyIGluIGdyYWRpZW50IGRlc2NlbnQgaXMgdGhlIHNpemUgb2YgdGhlIHN0ZXBzIHdoaWNoIGlzCmNvbnRyb2xsZWQgYnkgdGhlIF9fX2xlYXJuaW5nIHJhdGVfX18uIElmIHRoZSBsZWFybmluZyByYXRlIGlzLi4uCgoqIHRvbyBzbWFsbDogdGhlIGFsZ29yaXRobSB3aWxsIHRha2UgbWFueSBpdGVyYXRpb25zIChzdGVwcykgdG8gZmluZCB0aGUgbWluaW11bQoqIHRvbyBsYXJnZTogeW91IG1pZ2h0IGp1bXAgYWNyb3NzIHRoZSBtaW5pbXVtIGFuZCBlbmQgdXAgZnVydGhlciBhd2F5IHRoYW4gd2hlbgogIHlvdSBzdGFydGVkCiAgCiFbXShpbWFnZXMvbHIucG5nKQoKVGhlIGRlZmF1bHQgbGVhcm5pbmcgcmF0ZSBmb3IgU0dEIGlzIDAuMDEuIFVuZm9ydHVuYXRlbHkgd2l0aCB0aGlzIHJhdGUsIGl0IHdpbGwKdGFrZSBvdmVyIDEsMDAwIGVwb2NocyB0byByZWFjaCBhIGxvc3Mgc2NvcmUgY29tcGFyYWJsZSB0byBsb2dpc3RpYyByZWdyZXNzaW9uLgpIb3dldmVyLCB3ZSBjYW4gY3VzdG9taXplIG91ciBvcHRpbWl6ZXIgd2l0aCBgb3B0aW1pemVyX3NkZygpYDoKCmBgYHtyfQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxLCBpbnB1dF9zaGFwZSA9IG5jb2woeCksIGFjdGl2YXRpb24gPSAic2lnbW9pZCIpCgptb2RlbCAlPiUgY29tcGlsZSgKICBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfc2dkKGxyID0gMC4xKSwKICBsb3NzID0gImJpbmFyeV9jcm9zc2VudHJvcHkiCikKCihoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXQoeCwgeSwgZXBvY2hzID0gNTAsIHZlcmJvc2UgPSBGQUxTRSkpCmBgYAoKQW5vdGhlciBjb21tb24gYXBwcm9hY2ggdG8gYWRqdXN0IG91ciBsZWFybmluZyByYXRlIGlzIHRvIGFkZCBfX19tb21lbnR1bV9fXy4KQWRkaW5nIG1vbWVudHVtIGFsbG93cyBvdXIgbGVhcm5pbmcgcmF0ZSB0byBhZGFwdC4gTW9tZW50dW0gc2ltcGx5IGFkZHMgYQpmcmFjdGlvbiBvZiB0aGUgcHJldmlvdXMgd2VpZ2h0IHVwZGF0ZSB0byB0aGUgY3VycmVudCBvbmUuCgohW10oaW1hZ2VzL21vbWVudHVtLmdpZikKCkxldCdzIGFkZCBzb21lIG1vbWVudHVtIHRvIG91ciBsZWFybmluZyByYXRlLiBXZSBzZWUgdGhhdCBvdXIgbG9zcyBpbXByb3ZlcwpldmVuIG1vcmUgd2l0aGluIHRoZSBzYW1lIG51bWJlciBvZiBlcG9jaHMuCgpgYGB7cn0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgaW5wdXRfc2hhcGUgPSBuY29sKHgpLCBhY3RpdmF0aW9uID0gInNpZ21vaWQiKQoKbW9kZWwgJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gb3B0aW1pemVyX3NnZChsciA9IDAuMSwgbW9tZW50dW0gPSAwLjUpLAogIGxvc3MgPSAiYmluYXJ5X2Nyb3NzZW50cm9weSIKKQoKKGhpc3RvcnkgPC0gbW9kZWwgJT4lIGZpdCh4LCB5LCBlcG9jaHMgPSA1MCwgdmVyYm9zZSA9IEZBTFNFKSkKYGBgCgojIyBZb3VyIFR1cm4gKDMgbWluKQoKMS4gVHJ5IGRpZmZlcmVudCBjb21iaW5hdGlvbnMgb2YgbGVhcm5pbmcgcmF0ZSBhbmQgbW9tZW50dW0uIEEgZmV3IHJ1bGVzIG9mIPCfkY06CiAgICogVHlwaWNhbGx5LCB3ZSBzdGFydCBhc3Nlc3NpbmcgbGVhcm5pbmcgcmF0ZXMgaW4gbG9nIHZhbHVlcyByYW5nZXMgb2YgWzFlLTEsIDFlLTddCiAgICAgKGkuZS4gMC4xLCAwLjAxLCAuLi4sIDAuMDAwMDAwMSkuCiAgICogTW9tZW50dW0gaXMgdHlwaWNhbGx5ID4gMC41IGFuZCBvZnRlbiBpbiB0aGUgMC45LTAuOTkgcmFuZ2UuCjIuIFBsb3QgdGhlIGxvc3MgbGVhcm5pbmcgY3VydmUuCjMuIEhvdyBkb2VzIHlvdXIgZmluYWwgbG9zcyBjb21wYXJlIHRvIGxvZ2lzdGljIHJlZ3Jlc3Npb24/CgpgYGB7cn0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgaW5wdXRfc2hhcGUgPSBuY29sKHgpLCBhY3RpdmF0aW9uID0gX19fXykKCm1vZGVsICU+JSBjb21waWxlKAogIG9wdGltaXplciA9IF9fX18sCiAgbG9zcyA9ICJiaW5hcnlfY3Jvc3NlbnRyb3B5IgopCgpoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXQoeCwgeSwgZXBvY2hzID0gNTAsIHZlcmJvc2UgPSBGQUxTRSkKYGBgCgoKIyMgS2V5IHRha2Vhd2F5cwoKKiBBY3RpdmF0aW9uIGZ1bmN0aW9uczoKICAgLSBXZSB1c2UgYWN0aXZhdGlvbiBmdW5jdGlvbnMgdG8gdHJhbnNmb3JtIHRoZSBwZXJjZXB0cm9uJ3MgbGluZWFyIGVxdWF0aW9uCiAgICAgdG8gYSBub24tbGluZWFyIGZvcm0uCiAgIC0gRm9yIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtcyB3ZSB1c2UgdGhlICJzaWdtb2lkIiBhY3RpdmF0aW9uIHRvCiAgICAgY29udmVydCBvdXIgcHJlZGljdGlvbnMgdG8gYSAwLTEgcHJvYmFiaWxpdHkuCiogTGVhcm5pbmcgcmF0ZToKICAgLSBXZSBjYW4gY29udHJvbCB0aGUgcmF0ZSBvZiBsZWFybmluZyBieSBpbmNyZWFzaW5nICYgZGVjcmVhc2luZyB0aGUgbGVhcm5pbmcKICAgICByYXRlLgogICAtIFdlIGNhbiBtYWtlIHRoaXMgbGVhcm5pbmcgcmF0ZSBhZGFwdGl2ZSB0byB0aGUgY3VydmF0dXJlIG9mIG91ciBsb3NzCiAgICAgZ3JhZGllbnQgYnkgaW5jb3Jwb3JhdGluZyBtb21lbnR1bS4KCgojIE5vbi1saW5lYXIgUGF0dGVybnMKCkFzIG91ciBkYXRhc2V0cyBnZXQgbGFyZ2VyIG9yIGluY2x1ZGUgbm9uLWxpbmVhcml0aWVzLCBvdXIgbW9kZWwgbmVlZHMgdG8gYmVjb21lCm1vcmUgc29waGlzdGljYXRlZC4gRm9yIHRoaXMgZXhhbXBsZSwgd2UnbGwgc3RpY2sgd2l0aCBvbmUgcHJlZGljdG9yIHZhcmlhYmxlCmJ1dCB3ZSdsbCBhZGQgYSBub24tbGluZWFyaXR5IGNvbXBvbmVudDoKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCmRmIDwtIHRpYmJsZSgKICB4ID0gc2VxKGZyb20gPSAtMSwgdG8gPSAyICogcGksIGxlbmd0aCA9IG4pLAogIGUgPSBybm9ybShuLCBzZCA9IDAuMiksCiAgeSA9IHNpbih4KSArIGUKKQoKZ2dwbG90KGRmLCBhZXMoeCwgeSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkKYGBgCgpBZ2FpbiwgbGV0J3MgZXh0cmFjdCBvdXIgZmVhdHVyZSBhbmQgdGFyZ2V0IHRlbnNvcnM6CgpgYGB7cn0KeCA8LSBhcy5tYXRyaXgoZGYkeCkKeSA8LSBkZiR5CmBgYAoKQXMgb3VyIHVuZGVybHlpbmcgbW9kZWwgaGFzIG1vcmUgY29tcGxleGl0eSwgd2UgYWRkIGhpZGRlbiBsYXllcnMgdG8gY2FwdHVyZQpub24tbGluZWFyaXRpZXMgYW5kIGludGVyYWN0aW9ucy4gV2UgY2FsbCB0aGVzZSBuZXVyYWwgbmV0d29yayBtb2RlbHMgCl9fX211bHRpLWxheWVyIHBlcmNlcHRyb25zX19fIChNTFBzKTsgYWxzbyByZWZlcnJlZCB0byBhcyBfX19kZW5zZWx5IGNvbm5lY3RlZApmZWVkIGZvcndhcmRfX18gbmV0d29ya3MuCgohW10oaW1hZ2VzL2Jhc2ljX21scC5wbmcpCgpXZSBjYW4gYWRkIGEgaGlkZGVuIGxheWVycyBieSBhZGRpbmcgYWRkaXRpb25hbCBgbGF5ZXJfZGVuc2UoKWAgZnVuY3Rpb25zIHRvIG91cgptb2RlbCBhcmNoaXRlY3R1cmUuIEZvciBleGFtcGxlLCB0aGUgZm9sbG93aW5nIGNvZGUgd291bGQgY3JlYXRlIGFuIE1MUCB3aXRoOgoKKiAzIGhpZGRlbiBsYXllcnM6IAogICAtIGVhY2ggaGlkZGVuIGxheWVyIGhhcyAxNiBub2RlcwogICAtIG9ubHkgdGhlIGZpcnN0IGhpZGRlbiBsYXllciByZXF1aXJlcyBgaW5wdXRfc2hhcGVgCiAgIC0gZWFjaCBoaWRkZW4gbGF5ZXIgdXNlcyBhIFJlTFUgYWN0aXZhdGlvbiBmdW5jdGlvbiAod2UnbGwgZGlzY3VzcyBzaG9ydGx5KQoqIHRoZSBsYXN0IGBsYXllcl9kZW5zZSgpYCBpcyBhbHdheXMgdGhlIG91dHB1dCBsYXllcgogICAtIGFjdGl2YXRpb24gZnVuY3Rpb24gZm9yIG91dHB1dCBsYXllciBpcyBhbHdheXMgZGVwZW5kZW50IG9uIHRoZSBwcm9ibGVtCiAgICAgIC0gcmVncmVzc2lvbjogTlVMTAogICAgICAtIGJpbmFyeSBjbGFzc2lmaWNhdGlvbjogYGFjdGl2YXRpb24gPSAic2lnbm1vaWQiYAogICAgICAtIG11bHRpLWNsYXNzIGNsYXNzaWZpY2F0aW9uOiBgYWN0aXZhdGlvbiA9ICJzb2Z0bWF4ImAKCmBgYHt9Cm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDE2LCBpbnB1dF9zaGFwZSA9IG5jb2woeCksIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTYsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTYsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSkKYGBgCgojIyBXaHkgUmVMVQoKVGhlIHJlY3RpZmllZCBsaW5lYXIgYWN0aXZhdGlvbiBmdW5jdGlvbiBpcyB2ZXJ5IHNpbXBsZTsgaWYgdGhlIGxpbmVhcgp0cmFuc2Zvcm1hdGlvbiB3aXRoaW4gdGhlIHBlcmNlcHRyb24gcmVzdWx0cyBpbiBhIG5lZ2F0aXZlIG51bWJlciB0aGVuIHRoZQpvdXRwdXQgaXMgMC4gSWYgaXRzIHBvc2l0aXZlIHRoZW4gaXRzIHRoYXQgdmFsdWUuCgokJFJlTFUgPSBtYXgoMCwgeikkJAoKIVtdKGltYWdlcy9SZUxVLnBuZykKCkJlbmVmaXRzIChzZWUgaHR0cDovL3Byb2NlZWRpbmdzLm1sci5wcmVzcy92MTUvZ2xvcm90MTFhL2dsb3JvdDExYS5wZGYpOgoKLSBTaW1wbGUgZ2VvbWV0cmljIHRyYW5zZm9ybWF0aW9ucyBjYW4gcHJvZHVjZSB2ZXJ5IGNvbXBsZXggcGF0dGVybnMuCi0gQ29tcHV0YXRpb25hbCBzaW1wbGljaXR5IChlYXN5IHRvIGNvbXB1dGUgdGhlIGdyYWRpZW50KQotIFJlcHJlc2VudGF0aW9uYWwgc3BhcmNpdHkgKGZvcmNpbmcgMHMgcmVzdWx0cyBpbiBzcGFyc2Ugb3V0cHV0cykKLSBMaW5lYXJpdHkgKHJlZHVjZXMgdmFuaXNoaW5nIGdyYWRpZW50IGRlc2NlbnQgLSBkaXNjdXNzZWQgbGF0ZXIpCgohW10oaW1hZ2VzL29yaWdhbWkuZ2lmKQoKTGV0J3Mgc2VlIHRoaXMgaW4gYWN0aW9uOgoKYGBge3J9Cm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDE2LCBpbnB1dF9zaGFwZSA9IG5jb2woeCksIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSkKCm1vZGVsICU+JSBjb21waWxlKAogIG9wdGltaXplciA9IG9wdGltaXplcl9zZ2QobHIgPSAwLjAxLCBtb21lbnR1bSA9IC45KSwKICBsb3NzID0gIm1zZSIKKQoKKGhpc3RvcnkgPC0gbW9kZWwgJT4lIGZpdCh4LCB5LCBlcG9jaHMgPSA1MCwgdmVyYm9zZSA9IEZBTFNFKSkKYGBgCgpgYGB7cn0KZGYgJT4lCiAgbXV0YXRlKHByZWQgPSBwcmVkaWN0KG1vZGVsLCB4KSAlPiUgYXMudmVjdG9yKCkpICU+JQogIGdncGxvdChhZXMoeCwgeSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yNSkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkKSwgbHR5ID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIsIHNpemUgPSAxKQpgYGAKCiMjIE1vZGVsIGNhcGFjaXR5CgpfX19Nb2RlbCBjYXBhY2l0eV9fXyBkZXRlcm1pbmVzIHRoZSBleHRlbmQgdG8gd2hpY2ggb3VyIG1vZGVsIGNhbiBjYXB0dXJlCnVuZGVybHlpbmcgcmVsYXRpb25zaGlwcyBhbmQgcGF0dGVybnMuIFdlIGNvbnRyb2wgbW9kZWwgY2FwYWNpdHkgd2l0aDoKCjEuIF9fd2lkdGhfXzogbnVtYmVyIG9mIHVuaXRzIGluIGEgbGF5ZXIKICAgLSBSdWxlIG9mIPCfkY06IHR5cGljYWxseSB1c2UgcG93ZXJzIG9mIDIgKGkuZS4gMTYsIDMyLCA2NCwgMTI4LCAyNTYsIDUxMikKMi4gX19kZXB0aF9fOiBudW1iZXIgb2YgaGlkZGVuIGxheWVycwogICAtIFJ1bGUgb2Yg8J+RjTogd2Ugb2Z0ZW4gc2VlIGJldHRlciBwZXJmb3JtYW5jZSAoYWNjdXJhY3kgJiBjb21wdXRlIGVmZmljaWVuY3kpCiAgICAgYnkgaW5jcmVhc2luZyB0aGUgbnVtYmVyIG9mIGxheWVycyBtb3Jlc28gdGhhbiBub2Rlcy4gCiAgICAgCkxldCdzIGFkZCAyIGhpZGRlbiBsYXllcnMsIGVhY2ggd2l0aCAxNiB1bml0czoKCmBgYHtyfQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxNiwgaW5wdXRfc2hhcGUgPSBuY29sKHgpLCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDE2LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEpCgptb2RlbCAlPiUgY29tcGlsZSgKICBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfc2dkKGxyID0gMC4wMSwgbW9tZW50dW0gPSAuOSksCiAgbG9zcyA9ICJtc2UiCikKCihoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXQoeCwgeSwgZXBvY2hzID0gNTAsIHZlcmJvc2UgPSBGQUxTRSkpCmBgYAoKTG9va2luZyBhdCBob3cgb3VyIHByZWRpY3RlZCB2YWx1ZXMgZml0IHRoZSB0cnVlIHVuZGVybHlpbmcgbW9kZWw6CgpgYGB7cn0KZGYgJT4lCiAgbXV0YXRlKHByZWQgPSBwcmVkaWN0KG1vZGVsLCB4KSAlPiUgYXMudmVjdG9yKCkpICU+JQogIGdncGxvdChhZXMoeCwgeSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yNSkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkKSwgbHR5ID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIsIHNpemUgPSAxKQpgYGAKCiMjIFlvdXIgVHVybiAoMyBtaW4pCgoxLiBUcnkgdXNpbmcgb25seSBvbmUgaGlkZGVuIGxheWVyIGFuZCBpbmNyZWFzZSB0aGUgd2lkdGggdG8gMzIsIDY0LCAxMjgsIDI1NgoyLiBUcnkgYWRkaW5nIGEgc2Vjb25kIGxheWVyIGFuZCBpbmNyZWFzZSB0aGUgd2lkdGggb2YgZWFjaCBsYXllciBwcm9ncmVzc2l2ZWx5CiAgIC0gUnVsZSBvZiDwn5GNOiB3aGVuIHdlIGFkZCBtb3JlIGxheWVycyB3ZSB0eXBpY2FsbHkgaGF2ZSB0aGUgZm9sbG93aW5nIHBhdHRlcm5zOgogICAgICAtIHR1bm5lbCBzaGFwZWQ6IGVhY2ggaGlkZGVuIGxheWVyIGhhcyB0aGUgc2FtZSBudW1iZXIgb2YgdW5pdHMKICAgICAgLSBmdW5uZWwgc2hhcGVkOiBoaWRkZW4gbGF5ZXJzIHByb2dyZXNzaXZlbHkgZ2V0IHNtYWxsZXIKCmBgYHtyfQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSBfX19fLCBpbnB1dF9zaGFwZSA9IF9fX18sIGFjdGl2YXRpb24gPSBfX19fKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEpCgptb2RlbCAlPiUgY29tcGlsZSgKICBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfc2dkKGxyID0gMC4wMSwgbW9tZW50dW0gPSAuOSksCiAgbG9zcyA9ICJtc2UiCikKCihoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXQoeCwgeSwgZXBvY2hzID0gNTAsIHZlcmJvc2UgPSBGQUxTRSkpCmBgYAoKUnVuIHRoZSBmb2xsb3dpbmcgdG8gc2VlIGhvdyB5b3VyIGFkanVzdGVkIG1vZGVsIGZpdHMgdGhlIGFjdHVhbCBkYXRhOgoKYGBge3J9CmRmICU+JQogIG11dGF0ZShwcmVkID0gcHJlZGljdChtb2RlbCwgeCkgJT4lIGFzLnZlY3RvcigpKSAlPiUKICBnZ3Bsb3QoYWVzKHgsIHkpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMjUpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZCksIGx0eSA9ICJkYXNoZWQiLCBjb2xvciA9ICJyZWQiLCBzaXplID0gMSkKYGBgCgojIyBLZXkgVGFrZWF3YXlzCgoqIEhpZGRlbiBsYXllcnMgYWxtb3N0IGFsd2F5cyB1c2UgdGhlIFJlTFUgYWN0aXZhdGlvbiBmdW5jdGlvbiAodGhpcyBzaG91bGQgYmUKICB5b3VyIGRlZmF1bHQpLgoqIENvbnRyb2wgbW9kZWwgY2FwYWNpdHkgYnkgd2lkdGggYW5kIGRlcHRoIChhZGRpbmcgZGVwdGggdHlwaWNhbGx5IG91dHBlcmZvcm1zCiAgc2ltcGx5IGZvY3VzaW5nIG9uIHdpZHRoKS4KCiMgTXVsdGktcHJlZGljdG9yIE11bHRpLWNsYXNzIENsYXNzaWZpY2F0aW9uCgpMZXQncyBnZXQgYSBsaXR0bGUgbW9yZSBjb21wbGljYXRlZCBub3cgYW5kIGxvb2sgYXQgYSBkYXRhc2V0IHRoYXQgaGFzOgoKLSAzIHByZWRpY3RvciB2YXJpYWJsZXMKLSA0IHJlc3BvbnNlIGNsYXNzZXMKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCmdlbmVyYXRlZCA8LSBtbGJlbmNoOjptbGJlbmNoLnNpbXBsZXgobiA9IG4qMiwgZCA9IDMsIHNkID0gMC4zKQoKcGxvdChnZW5lcmF0ZWQpCmBgYAoKYGBge3J9CiMgMyBmZWF0dXJlcwpoZWFkKGdlbmVyYXRlZCR4KQoKIyA0IHJlc3BvbnNlIGNhdGVnb3JpZXMKaGVhZChnZW5lcmF0ZWQkY2xhc3NlcykKYGBgCgpQcmVwYXJpbmcgb3VyIGRhdGEgdGFrZXMgYSBsaXR0bGUgbW9yZSBlZmZvcnQgaW4gdGhpcyBjYXNlOgoKLSBfX2ZlYXR1cmVzX186IG91ciBmZWF0dXJlcyBhcmUgYWxyZWFkeSBhIG1hdHJpeCBzbyB3ZSdyZSBnb29kCi0gX19yZXNwb25zZV9fOiBvdXIgcmVzcG9uc2UgaXMgYSBmYWN0b3Igd2hpY2ggd2UgYXJlIGdvaW5nIHRvIGNvbnZlcnQgdG8gYSBtYXRyaXg6CiAgIC0gYHRvX2NhdGVnb3JpY2FsYCBkdW1teSBlbmNvZGVzIG91ciBjbGFzc2VzLiBUaGlzIGFsbG93cyB1cyB0byBjb21wdXRlIHRoZQogICAgICBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgZm9yIGVhY2ggY2xhc3MKICAgLSBgdG9fY2F0ZWdvcmljYWxgIGV4cGVjdHMgYSB6ZXJvLWJhc2VkIGlucHV0IGZyb20gMC1uIChQeXRob24g8J+YkikKCmBgYHtyfQp4IDwtIGdlbmVyYXRlZCR4CnkgPC0gZ2VuZXJhdGVkJGNsYXNzZXMgJT4lIGFzLm51bWVyaWMoKQp5IDwtIHRvX2NhdGVnb3JpY2FsKHkgLSAxKQpuX2NsYXNzZXMgPC0gbmNvbCh5KQoKIyBvdXIgcHJlcHJvY2Vzc2VzIHJlc3BvbnNlCmhlYWQoeSkKYGBgCgojIyBGaXQgbW9kZWwgdXNpbmcgdmFsaWRhdGlvbgoKSW4gcHJhY3RpY2Ugd2UgYXJlIHVuYWJsZSB0byB2aXN1YWxpemUgdGhlIGZpdCBvZiBvdXIgZGF0YSB0byB1bmRlcnN0YW5kCnZhcmlhbmNlLWJpYXMgdHJhZGVvZmYgKGkuZS4gYXJlIHdlIG92ZXIgb3IgdW5kZXJmaXR0aW5nIG91ciBkYXRhKS4gQ29uc2VxdWVudGx5LAp3ZSByZWx5IG9uIHVzaW5nIGEgdmFsaWRhdGlvbiBzZXQgYW5kIHdoYXQgd2UgY2FsbCBsZWFybmluZyBjdXJ2ZXMuCgotIF9fdmFsaWRhdGlvbl9zcGxpdF9fOiB3aWxsIHRyYWluIG1vZGVsIG9uIGZpcnN0IDgwJSBvZiBkYXRhIGFuZCB1c2UgdGhlIGxhc3QKICAyMCUgb2YgZGF0YSB0byBzZWUgYXNzZXNzIHBlcmZvcm1hbmNlLgotIF9fbWV0cmljc19fOiBvZnRlbiB3ZSB3YW50IHRvIGFzc2VzcyBhbHRlcm5hdGl2ZSBtZXRyaWNzIGFsb25nIHdpdGggb3VyIGxvc3MKICBzY29yZS4KCmBgYHtyfQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxNiwgaW5wdXRfc2hhcGUgPSBuY29sKHgpLCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IG5fY2xhc3NlcywgYWN0aXZhdGlvbiA9ICJzb2Z0bWF4IikKCm1vZGVsICU+JSBjb21waWxlKAogIG9wdGltaXplciA9IG9wdGltaXplcl9zZ2QobHIgPSAwLjAxLCBtb21lbnR1bSA9IC45KSwKICBsb3NzID0gImNhdGVnb3JpY2FsX2Nyb3NzZW50cm9weSIsCiAgbWV0cmljcyA9ICJhY2N1cmFjeSIKKQoKaGlzdG9yeSA8LSBtb2RlbCAlPiUgZml0KAogIHgsIHksIAogIGJhdGNoX3NpemUgPSAzMiwgCiAgZXBvY2hzID0gMjAsIAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIKICApCmBgYAoKT3VyIGxlYXJuaW5nIGN1cnZlIHNob3dzIHNvbWUgdW5pcXVlIGJlaGF2aW9yLiBXZSBjYW4gbGVhcm4gYSBsb3QgYWJvdXQgb3VyCm1vZGVsIGJ5IHBheWluZyBhdHRlbnRpb24gdG8gbGVhcm5pbmcgY3VydmVzIChzZWUgdGhpcyBleHRyYSBub3RlYm9vazoKaHR0cHM6Ly9yc3R1ZGlvLWNvbmYtMjAyMC5naXRodWIuaW8vZGwta2VyYXMtdGYvbm90ZWJvb2tzL2xlYXJuaW5nLWN1cnZlLWRpYWdub3N0aWNzLm5iLmh0bWwpCgpgYGB7cn0KaGlzdG9yeQoKcGxvdChoaXN0b3J5KQpgYGAKCkluIHRoaXMgY2FzZSwgdGhlIHByb2JsZW0gaXMgdGhhdCBvdXIgZGF0YSBpcyBvcmRlcmVkIHNvIHRoZSBsYXN0IDIwJSBvZiBvdXIKZGF0YSBjb250YWlucyBvbmx5IG9uZSBjbGFzcy4gU28gd2UgYWx3YXlzIHdhbnQgdG8gbWFrZSBzdXJlIHdlIGFyZSByYW5kb21pemluZwpvdXIgZGF0YS4KCmBgYHtyfQpzZXQuc2VlZCgxMjMpCnJhbmRvbWl6ZSA8LSBzYW1wbGUoc2VxX2xlbihuKSwgc2l6ZSA9IG4sIHJlcGxhY2UgPSBGQUxTRSkKeCA8LSB4W3JhbmRvbWl6ZSwgXQp5IDwtIHlbcmFuZG9taXplLCBdCmBgYAoKTm93IGxldCdzIHRyeSB0aGUgc2FtZSBtb2RlbCBhZ2Fpbi4KCmBgYHtyfQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxNiwgaW5wdXRfc2hhcGUgPSBuY29sKHgpLCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IG5fY2xhc3NlcywgYWN0aXZhdGlvbiA9ICJzb2Z0bWF4IikKCm1vZGVsICU+JSBjb21waWxlKAogIG9wdGltaXplciA9IG9wdGltaXplcl9zZ2QobHIgPSAwLjAxLCBtb21lbnR1bSA9IC45KSwKICBsb3NzID0gImNhdGVnb3JpY2FsX2Nyb3NzZW50cm9weSIsCiAgbWV0cmljcyA9ICJhY2N1cmFjeSIKKQoKaGlzdG9yeSA8LSBtb2RlbCAlPiUgZml0KAogIHgsIHksIAogIGJhdGNoX3NpemUgPSAzMiwgCiAgZXBvY2hzID0gMjAsIAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIKICApCmBgYAoKT3VyIHJlc3VsdHMgbG9vayBtdWNoIGJldHRlci4gT3VyIGxvc3MgY3VydmUgc2hvd3MgYSBmZXcgdGhpbmdzIHRoYXQgd2UgYWx3YXlzCndhbnQgdG8gc3RyaXZlIGZvcjoKCiogVGhlIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGxvc3MgY3VydmVzIGFyZSB2ZXJ5IGNsb3NlIHRvIG9uZSBhbm90aGVyLiBUeXBpY2FsbHksCiAgdGhlcmUgd2lsbCBiZSBhIGdhcCBiZXR3ZWVuIHRoZSB0d28gYnV0IG91ciBnb2FsIHNob3VsZCBiZSB0byBtaW5pbWl6ZSB0aGlzCiAgZ2FwLiBUaGlzIGxlYWRzIHRvIGEgbW9yZSBzdGFibGUgbW9kZWwgdGhhdCBnZW5lcmFsaXplcyBiZXR0ZXIKKiBXZSBwcmVmZXIgdGhhdCBvdXIgdmFsaWRhdGlvbiBsb3NzIGlzIGFib3ZlIHRoZSB0cmFpbmluZyBsb3NzICh3aGVuIG91ciBtZXRyaWMKICBpcyBkZXNpZ25lZCBmb3IgImxvd2VyLWlzLWJldHRlciIpLiBJZiBvdXIgdmFsaWRhdGlvbiBsb3NzIGJlbG93IChiZXR0ZXIpCiAgdGhlbiB0aGUgdHJhaW5pbmcgbG9zcyB0aGVuIHRoYXQgdHlwaWNhbGx5IG1lYW5zIHdlIGFyZSB1bmRlcmZpdHRpbmcgYW5kIHdlCiAgc2hvdWxkIGluY3JlYXNlIGNhcGFjaXR5LgoqIE91ciB2YWxpZGF0aW9uIGxvc3MgaGFzIHN0b3BwZWQgaW1wcm92aW5nLCB3aGljaCBtZWFucyB3ZSBoYXZlIHRyYWluZWQgaXQgZm9yCiAgZW5vdWdoIGVwb2Nocy4KKiBXZSB3YW50IG91ciB2YWxpZGF0aW9uIGxvc3MgdG8gYmUgYXMgc21vb3RoIGFzIHBvc3NpYmxlIChpcmF0aWMgYmVoYXZpb3IgbWVhbnMKICB1bnN0YWJsZSBnZW5lcmFsaXphdGlvbikuCgpgYGB7cn0KaGlzdG9yeQoKcGxvdChoaXN0b3J5KQpgYGAKCiMjIEVmZmVjdHMgb2YgYmF0Y2ggc2l6ZQoKU28gZmFyIHdlJ3ZlIGp1c3QgYmVlbiB1c2luZyB0aGUgZGVmYXVsdCBiYXRjaCBzaXplIG9mIDMyLiBIb3dldmVyLCB0aGVyZSBhcmUKb3RoZXIgb3B0aW9uczoKCi0gX19CYXRjaCBncmFkaWVudCBkZXNjZW50X186IGNvbXB1dGVzIHRoZSBkZXJpdmF0aXZlIG9mIHRoZSBncmFkaWVudCBiYXNlZCBvbgogIHRoZSBlbnRpcmUgZGF0YXNldCAoYWxsIG9ic2VydmF0aW9ucykuCiAgICAgLSBwcm92aWRlcyBtb3JlIGFjY3VyYXRlIGFuZCBzbW9vdGggZ3JhZGllbnQgZGVzY2VudCBidXQuLi4KICAgICAtIHNjYWxlcyBob3JyaWJseSB0byBsYXJnZSBkYXRhCgotIF9fU3RvY2hhc3RpYyBncmFkaWVudCBkZXNjZW50X186IHJhbmRvbWx5IHNlbGVjdHMgYW4gaW5kaXZpZHVhbCBvYnNlcnZhdGlvbnMsCiAgY29tcHV0ZXMgZ3JhZGllbnRzIGFuZCB1cGRhdGVzIG1vZGVsIHdlaWdodHMgYWZ0ZXIgdGhpcyBzaW5nbGUgb2JzZXJ2YXRpb24gaGFzCiAgYmVlbiBldmFsdWF0ZWQuCiAgICAgLSBwcm92aWRlcyBxdWljayBmZWVkYmFjayBzbyB0aGUgbW9kZWwgbGVhcm5zIHF1aWNrbHkgYW5kLi4uCiAgICAgLSByZXN1bHRzIGluIG5vaXN5IGdyYWRpZW50IGRlc2NlbnQgd2hpY2ggaGVscHMgYXZvaWQgbG9jYWwgbWluaW11bXMgYnV0Li4uCiAgICAgLSBub2lzeSBncmFkaWVudCBkZXNjZW50IG1ha2VzIGl0IGhhcmQgdG8gY29udmVyZ2Ugb24gZ2xvYmFsIG1pbmltdW0gYW5kLi4uCiAgICAgLSBjYW4gcmVzdWx0IGluIHVuc3RhYmxlIGdlbmVyYWxpemF0aW9uCgotIF9fTWluaS1iYXRjaCBncmFkaWVudCBkZXNjZW50X186IHJhbmRvbWx5IHNlbGVjdHMgYSBzdWJzZXQgb2Ygb2JzZXJ2YXRpb25zLAogIGNvbXB1dGVzIGdyYWRpZW50cyBhbmQgdXBkYXRlcyBtb2RlbCB3ZWlnaHRzIGFmdGVyIHRoaXMgc3Vic2V0IGhhcyBiZWVuCiAgZXZhbHVhdGVkLgogICAgIC0gQmFsYW5jZXMgZWZmaWNpZW5jaWVzIG9mIGJhdGNoIHZzLiBzdG9jaGFzdGljCiAgICAgLSBCYWxhbmNlcyByb2J1c3QgY29udmVyZ2VuY2Ugb2YgYmF0Y2ggd2l0aCBzb21lIHN0b2NoYXN0aWMgbmF0dXJlIHRvCiAgICAgICBtaW5pbWl6ZSBsb2NhbCBtaW5pbWEuCiAgICAgLSBCdXQgb25lIG1vcmUgaHlwZXJwYXJhbWV0ZXIgdG8gdGhpbmsgYWJvdXQuCiAgICAgLSBNb3N0IGNvbW1vbjogJDJecyQ6IDMyLCA2NCwgMTI4LCAyNTYsIDUxMgoKR28gYWhlYWQgYW5kIHRyeToKCjEuIGBiYXRjaF9zaXplID0gMWAgKHN0b2NoYXN0aWMgZ3JhZGllbnQgZGVzY2VudCkKMi4gYGJhdGNoX3NpemUgPSBucm93KHgpYCAoYmF0Y2ggZ3JhZGllbnQgZGVzY2VudCkKMy4gYGJhdGNoX3NpemUgPSBiYCB3aGVyZSBgYmAgZXF1YWxzIDE2LCAzMiwgNjQsIDEyOAoKX19Ob3RlX186IGJhdGNoIHNpemUgYW5kIGxlYXJuaW5nIHJhdGUgb2Z0ZW4gaW50ZXJhY3QgYW5kIHNob3VsZCBiZSB0dW5lZAp0b2dldGhlci4KCmBgYHtyfQptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxNiwgaW5wdXRfc2hhcGUgPSBuY29sKHgpLCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IG5fY2xhc3NlcywgYWN0aXZhdGlvbiA9ICJzb2Z0bWF4IikKCm1vZGVsICU+JSBjb21waWxlKAogIG9wdGltaXplciA9IG9wdGltaXplcl9zZ2QobHIgPSAwLjAxLCBtb21lbnR1bSA9IC45KSwKICBsb3NzID0gImNhdGVnb3JpY2FsX2Nyb3NzZW50cm9weSIsCiAgbWV0cmljcyA9ICJhY2N1cmFjeSIKKQoKaGlzdG9yeSA8LSBtb2RlbCAlPiUgZml0KAogIHgsIHksIAogIGJhdGNoX3NpemUgPSBfX19fLCAKICBlcG9jaHMgPSAyMCwgCiAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMgogICkKYGBgCgojIyBNYWtpbmcgcHJlZGljdGlvbnMKCldoZW4gcHJlZGljdGluZyB3aXRoIGNsYXNzaWZpY2F0aW9uIG1vZGVscyB3ZSBjYW4gZWl0aGVyIHByZWRpY3QgdGhlIGNsYXNzCihiYXNlZCBvbiBwcm9iYWJpbGl0eSA+IDAuNSkgb3IgcHJlZGljdCB0aGUgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzczoKCmBgYHtyfQojIHByZWRpY3RpbmcgcHJvYmFiaWxpdGllcwptb2RlbCAlPiUgcHJlZGljdCh4KSAlPiUgaGVhZCgpCgojIHByZWRpY3RpbmcgY2xhc3Nlcwptb2RlbCAlPiUgcHJlZGljdF9jbGFzc2VzKHgpICU+JSBoZWFkKCkKYGBgCgojIyBLZXkgVGFrZWF3YXlzCgoqIE1vbml0b3IgdGhlIGxlYXJuaW5nIGN1cnZlcyB0byBkaWFnbm9zZSBtb2RlbCBwZXJmb3JtYW5jZS4KKiBCYXRjaCBzaXplIGVmZmVjdHMgb3VyIGxlYXJuaW5nIGN1cnZlLiBNaW5pLWJhdGNoIHNpemVzIG9mIDMyLCA2NCwgMTI4LCAyNTYKICB0ZW5kIHRvIHBlcmZvcm0gYmVzdC4KCgojIE1pbmktUHJvamVjdCAodGltZSBkZXBlbmRlbnQpCgpUaW1lIHRvIHdvcmsgd2l0aCBzb21lIHJlYWwgKGFsdGhvdWdoIHVuZXhjaXRpbmcpIGRhdGEgLSBgaXJpc2AuIFRoaXMgZGF0YQpjb250YWluczoKCiogNCBmZWF0dXJlcyAoYFNlcGFsLkxlbmd0aGAsIGBTZXBhbC5XaWR0aGAsIGBQZXRhbC5MZW5ndGhgLCBgUGV0YWwuV2lkdGhgKQoqIDMgcmVzcG9uc2UgY2xhc3NlcyAoYFNwZWNpZXNgID0gc2V0b3NhLCB2ZXJzaWNvbG9yLCB2ZXJnaW5pY2EpCgpgYGB7cn0KaGVhZChpcmlzKQpgYGAKCiMjIERhdGEgcHJlcAoKRmlyc3Qgc3RlcCBpcyB0byBwcmVwYXJlIG91ciBkYXRhLiBUaGlzIGludm9sdmVzIGNvbnZlcnRpbmcgb3VyIGZlYXR1cmVzIHRvIGEKdGVuc29yIChha2EgbWF0cml4KS4gQWxzbywgc2luY2Ugb3VyIHJlc3BvbnNlIHZhcmlhYmxlIGlzIG11bHRpLWNsYXNzLCB3ZSB3YW50CnRvIGNvbnZlcnQgaXQgdG8gYSAyRCB0ZW5zb3IgKGFrYSBtYXRyaXgpLiBXZSBhbHNvIG5lZWQgdG8gcmFuZG9taXplIHRoZSBkYXRhLgoKVGhlc2Ugc3RlcHMgYXJlIHByb3ZpZGVkIGZvciB5b3U6CgpgYGB7cn0KIyBjb252ZXJ0IGZlYXR1cmVzIHRvIGEgdGVuc29yIChha2EgbWF0cml4KQp4IDwtIGlyaXNbMTo0XSAlPiUgYXMubWF0cml4KCkKCiMgY29udmVydCByZXNwb25zZSB0byBhIG11bHRpLWNsYXNzIHRlbnNvciAoYWthIG1hdHJpeCkKeSA8LSBpcmlzJFNwZWNpZXMgJT4lIGFzLm51bWVyaWMoKQp5IDwtIHRvX2NhdGVnb3JpY2FsKHkgLSAxKQoKIyByYW5kb21pemUgZGF0YQpzZXQuc2VlZCgxMjMpCnRvdGFsX29icyA8LSBucm93KHgpCnJhbmRvbWl6ZSA8LSBzYW1wbGUoc2VxX2xlbih0b3RhbF9vYnMpLCBzaXplID0gIHRvdGFsX29icywgcmVwbGFjZSA9IFRSVUUpCnggPC0geFtyYW5kb21pemUsIF0KeSA8LSB5W3JhbmRvbWl6ZSwgXQpgYGAKCk5vdGUgdGhhdCBvdXIgcmVzcG9uc2UgdGVuc29yIChgeWApIGhhcyAzIGNvbHVtcy4gVGhlc2UgY29sdW1ucyByZWxhdGUKYWxwaGFiZXRpY2FsbHkgdG8gb3VyIHJlc3BvbnNlIGNsYXNzZXM6CgotIGNvbHVtbiAxID0gc2V0b3NhCi0gY29sdW1uIDIgPSB2ZXJzaWNvbG9yCi0gY29sdW1uIDMgPSB2aXJnaW5pY2EKCmBgYHtyfQpoZWFkKHkpCmBgYAoKIyMgTW9kZWxpbmcKClN0YXJ0IHdpdGggdGhlIGZvbGxvd2luZzoKCi0gMSBoaWRkZW4gbGF5ZXIgd2l0aCAxNiB1bml0cwotIGxlYXJuaW5nIHJhdGUgb2YgMC4wMSBhbmQgbm8gbW9tZW50dW0KLSAyMCBlcG9jaHMgd2l0aCBiYXRjaCBzaXplcyBvZiAzMgotIHZhbGlkYXRpb24gc3BsaXQgb2YgMjAlCgpUaGVuIHN0YXJ0IGFkanVzdGluZyB0aGUgZm9sbG93aW5nOgoKLSBsZWFybmluZyByYXRlIChtYXliZSBhZGQgbW9tZW50dW0pCi0gbW9kZWwgY2FwYWNpdHkgKHRyeSB3aWRlciBhbmQvb3IgZGVlcGVyIGNhcGFjaXR5KQotIGJhdGNoIHNpemUgKGRvIGxhcmdlciBvciBzbWFsbGVyIGJhdGNoIHNpemVzIGhlbHAgcGVyZm9ybWFuY2UpCi0gZXBvY2hzIChkbyB5b3UgbmVlZCBtb3JlIG9yIGxlc3MgZXBvY2hzIHRvIHJlYWNoIGEgbWluaW11bSB2YWxpZGF0aW9uIGxvc3MpCgpgYGB7cn0KIyBkZWZpbmUgYXJjaGl0ZWN0dXJlCm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IF9fX18sIGlucHV0X3NoYXBlID0gX19fXywgYWN0aXZhdGlvbiA9IF9fX18pICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gX19fXywgYWN0aXZhdGlvbiA9IF9fX18pCgojIGRlZmluZSBsZWFybmluZyBwcm9jZWR1cmUKbW9kZWwgJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gb3B0aW1pemVyX3NnZChsciA9IF9fX18pLAogIGxvc3MgPSAiY2F0ZWdvcmljYWxfY3Jvc3NlbnRyb3B5IiwKICBtZXRyaWNzID0gImFjY3VyYWN5IgopCgojIHRyYWluIG1vZGVsCmhpc3RvcnkgPC0gbW9kZWwgJT4lIGZpdCgKICB4LCB5LCAKICBiYXRjaF9zaXplID0gX19fXywgCiAgZXBvY2hzID0gX19fXywgCiAgdmFsaWRhdGlvbl9zcGxpdCA9IF9fX18KICApCmBgYAoKIyBTdW1tYXJ5CgpUaGlzIG1vZHVsZSBpcyBtZWFudCB0byBvbmx5IGludHJvZHVjZSBzb21lIG9mIHRoZSBrZXkgaW5ncmVkaWVudHMgaW52b2x2ZWQgaW4KdHJhaW5pbmcgYSBiYXNpYyBNTFAgbW9kZWwuIEhvd2V2ZXIsIGFzIHRoZSBtaW5pLXByb2plY3QgcHJvYmFibHkgZGVtb25zdHJhdGVkLAppdCBkaWRuJ3QgZG8gbXVjaCB0byBoZWxwIHlvdSB1bmRlcnN0YW5kIGhvdyB0byBwdXQgdGhlc2UgaW5ncmVkaWVudHMgdG9nZXRoZXIKaW4gYSBtZXRob2RvbGljYWwgYXBwcm9hY2ggdG8gbWF4aW1pemUgbW9kZWwgcGVyZm9ybWFuY2UuIFRoZSBuZXh0IG1vZHVsZSBhaW1zCnRvIGZpbGwgdGhpcyBnYXAgYW5kIHByb3ZpZGUgc29tZSBiZXN0IHByYWN0aWNlcyBmb3IgdHJhaW5pbmcgYSBtb2RlbC4KClvwn4+gXShodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby1jb25mLTIwMjAvZGwta2VyYXMtdGYp