Within construction industry, concrete is used extensively in almost all of construction projects. Thus the strength of concrete must passed certain specification depending on the type of project. However, due to its complex chemical mixture, different concretes with slightly different ingredients can interact in different way, thus it is crucial to be able to predict the strength of different types of concrete.

In this section, the goal is to create and train a neural networks model that will help us in predicting the strength of concrete based on different properties such as water(in kg/m^3), cement (kg/m^3), coarse aggregate (kg/m^3), age (day) and so on…

The dataset is donated by Prof. I-Cheng Yeh, Department of Information Management, Chung-Hua University. It is available to download at https://archive.ics.uci.edu/ml/datasets/Concrete+Compressive+Strength. The dataset contain 1030 examples, each with 8 features input and one output which is the strength of the concrete.

With that being said let’s get to it!

Step 1: Load the dataset

concrete <- read.csv('concrete.csv')


Step 2: Explore and prepare the dataset

concrete
str(concrete)
'data.frame':   1030 obs. of  9 variables:
 $ cement      : num  141 169 250 266 155 ...
 $ slag        : num  212 42.2 0 114 183.4 ...
 $ ash         : num  0 124.3 95.7 0 0 ...
 $ water       : num  204 158 187 228 193 ...
 $ superplastic: num  0 10.8 5.5 0 9.1 0 0 6.4 0 9 ...
 $ coarseagg   : num  972 1081 957 932 1047 ...
 $ fineagg     : num  748 796 861 670 697 ...
 $ age         : int  28 14 28 28 28 90 7 56 28 28 ...
 $ strength    : num  29.9 23.5 29.2 45.9 18.3 ...

=> As we can see, this dataset contains 1030 examples and all of the input variables and output are numeric.

Since we want to use neural network, it’s a good idea to normalize the dataset so that variables that have significantly greater range will not dominate others. If a variables has a larger range compare to the others, it will have more “say” (aka impact) during the training process, thus the model will reply more heavily on this variable instead of considering all variables equally.

# let's define a function to normalize dataset
normalize <- function(x){
  return ((x - min(x)) / (max(x) - min(x)))
}
concrete_norm <- as.data.frame(lapply(concrete, normalize))
summary(concrete_norm)
     cement            slag              ash             water         superplastic      coarseagg         fineagg            age             strength     
 Min.   :0.0000   Min.   :0.00000   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   :0.00000   Min.   :0.0000  
 1st Qu.:0.2063   1st Qu.:0.00000   1st Qu.:0.0000   1st Qu.:0.3442   1st Qu.:0.0000   1st Qu.:0.3808   1st Qu.:0.3436   1st Qu.:0.01648   1st Qu.:0.2664  
 Median :0.3902   Median :0.06121   Median :0.0000   Median :0.5048   Median :0.1988   Median :0.4855   Median :0.4654   Median :0.07418   Median :0.4001  
 Mean   :0.4091   Mean   :0.20561   Mean   :0.2708   Mean   :0.4774   Mean   :0.1927   Mean   :0.4998   Mean   :0.4505   Mean   :0.12270   Mean   :0.4172  
 3rd Qu.:0.5662   3rd Qu.:0.39775   3rd Qu.:0.5912   3rd Qu.:0.5607   3rd Qu.:0.3168   3rd Qu.:0.6640   3rd Qu.:0.5770   3rd Qu.:0.15110   3rd Qu.:0.5457  
 Max.   :1.0000   Max.   :1.00000   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000   Max.   :1.00000   Max.   :1.0000  

==> The dataset has been normalized and variables’ range is now between 0 and 1. The normalized dataset is stored in concrete_norm variable which we will use to train the neural network. With their values normalized, each variable will have equal “say” (or impact) compare to the rest of variables.

With the normalized dataset in hand, we will go ahead and split our dataset into training and testing sub dataset:

# training set 75 percent, testing set 25%
concrete_train <- concrete_norm[1:773, ]
concrete_test <- concrete_norm[774:1030, ]

Step 3: Train the neural network

We will use the neural network model implemented in the neuralnet package. We need to download and load the library.

# install.packages('neuralnet')
library(neuralnet)
# we will train a neural network with 1 hidden layer
concrete_model <- neuralnet(strength ~ cement + slag + ash + water + superplastic + coarseagg + fineagg + age, data = concrete_train, hidden = 1)
# then we plot the topology of the resulted neural network
plot(concrete_model)

==> By plotting the neural network’s topology, we observed that the model has 8 input variables plus a bias unit as expected. The weights that goes with each input variable is also shown next to each each node. For example, the weight for water variable is -1.64942; this makes sense because, intuitively, if we put too much water into the concrete mixture it will “thin” out the mixture, thus leads to weaker bounding concrete. The plot also show us the Sum of Squared Error (SSE) = 5.078 which is the cumulative squared error between predicted and true values of strength; it also include the number of steps = 3725 that the neural network take to train the training dataset.

Step 4: Evaluate the neural network’s performance

Now that we have our neural network trained, we will put it to the test by using it to predict the testing dataset and see how it would perform.

# compute function will return the neurons for each layer and the result; these two field are stored in $neurons and $net.result variables
model_results <- compute(concrete_model, concrete_test[1:8])# syntax: compute(neural_network_model, test_dataset)
predicted_strength <- model_results$net.result
# evaluate the correlation between predicted strength and true strength of the testing dataset
cor(predicted_strength, concrete_test$strength)
             [,1]
[1,] 0.8063207679

==> If two variables have correlation close to 1, then it means these two variables have a close linear relationship to each other. Our predicted_strength and concrete_test$strength has a correlation measurement of 0.81; this indicates that even with our simple neural network model with only one hidden unit, it can predict the concrete strength fairly well!

Step 5: Improve the neural network’s performance

When it come to public safety, we want to improve our ability to predict structure integrity as accurate as we can. Even though our simple neural network can predict concrete strength fairly well, there is room for improvement.

Let’s try to increase the number of hidden unit to 5:

concrete_model2 <- neuralnet(strength ~ cement + slag + ash + water + superplastic + coarseagg + fineagg + age, data = concrete_train, hidden = 5)
plot(concrete_model2)

==> Right off the bat, we can see that the Sum of Squared Error (SSE) has decreased significantly from 5.078 to 1.705 which is about 3 times reduction in SSE value. In addition, the number of steps has also increased from 3725 to 41680 since the neural network is more complex due to the extra hidden units! By doing the same steps to evaluate the new neural network’s performance, we get:

model_results2 <- compute(concrete_model2, concrete_test[1:8])
predicted_strength2 <- model_results2$net.result
cor(predicted_strength2, concrete_test$strength)
             [,1]
[1,] 0.9238489504

==> With the new and a bit more complex neural network, we can see a significant improvement in the correlation measurement between predicted strength values and the true strength values. With correlation measurement = 0.924, the predicted strength is much more closely resemble the true strength compare to the previous neural network model. This is such great improvement for adding just a few hidden neurons!!!

Machine Learning with R by Brett Lantz
LS0tCnRpdGxlOiAiTW9kZWxpbmcgdGhlIFN0cmVuZ3RoIG9mIENvbmNyZXRlIHdpdGggQXJ0aWZpY2lhbCBOZXVyYWwgTmV0d29ya3MiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCldpdGhpbiBjb25zdHJ1Y3Rpb24gaW5kdXN0cnksIGNvbmNyZXRlIGlzIHVzZWQgZXh0ZW5zaXZlbHkgaW4gYWxtb3N0IGFsbCBvZiBjb25zdHJ1Y3Rpb24gcHJvamVjdHMuIFRodXMgdGhlIHN0cmVuZ3RoIG9mIGNvbmNyZXRlIG11c3QgcGFzc2VkIGNlcnRhaW4gc3BlY2lmaWNhdGlvbiBkZXBlbmRpbmcgb24gdGhlIHR5cGUgb2YgcHJvamVjdC4gSG93ZXZlciwgZHVlIHRvIGl0cyBjb21wbGV4IGNoZW1pY2FsIG1peHR1cmUsIGRpZmZlcmVudCBjb25jcmV0ZXMgd2l0aCBzbGlnaHRseSBkaWZmZXJlbnQgaW5ncmVkaWVudHMgY2FuIGludGVyYWN0IGluIGRpZmZlcmVudCB3YXksIHRodXMgaXQgaXMgY3J1Y2lhbCB0byBiZSBhYmxlIHRvIHByZWRpY3QgdGhlIHN0cmVuZ3RoIG9mIGRpZmZlcmVudCB0eXBlcyBvZiBjb25jcmV0ZS48L2JyPgoKSW4gdGhpcyBzZWN0aW9uLCB0aGUgPGVtPjx1PmdvYWwgaXMgdG8gY3JlYXRlIGFuZCB0cmFpbiBhIG5ldXJhbCBuZXR3b3JrcyBtb2RlbCB0aGF0IHdpbGwgaGVscCB1cyBpbiBwcmVkaWN0aW5nIHRoZSBzdHJlbmd0aCBvZiBjb25jcmV0ZTwvdT48L2VtPiBiYXNlZCBvbiBkaWZmZXJlbnQgcHJvcGVydGllcyBzdWNoIGFzIHdhdGVyKGluIGtnL21eMyksIGNlbWVudCAoa2cvbV4zKSwgY29hcnNlIGFnZ3JlZ2F0ZSAoa2cvbV4zKSwgYWdlIChkYXkpIGFuZCBzbyBvbi4uLjwvYnI+CgpUaGUgZGF0YXNldCBpcyBkb25hdGVkIGJ5IDxlbT5Qcm9mLiBJLUNoZW5nIFllaCwgRGVwYXJ0bWVudCBvZiBJbmZvcm1hdGlvbiBNYW5hZ2VtZW50LCBDaHVuZy1IdWEgVW5pdmVyc2l0eTwvZW0+LiBJdCBpcyBhdmFpbGFibGUgdG8gZG93bmxvYWQgYXQgaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL2RhdGFzZXRzL0NvbmNyZXRlK0NvbXByZXNzaXZlK1N0cmVuZ3RoLiBUaGUgZGF0YXNldCBjb250YWluIDEwMzAgZXhhbXBsZXMsIGVhY2ggd2l0aCA4IGZlYXR1cmVzIGlucHV0IGFuZCBvbmUgb3V0cHV0IHdoaWNoIGlzIHRoZSBzdHJlbmd0aCBvZiB0aGUgY29uY3JldGUuPC9icj4KCldpdGggdGhhdCBiZWluZyBzYWlkIGxldCdzIGdldCB0byBpdCE8L2JyPjwvYnI+Cgo8aDQ+PHU+IFN0ZXAgMTogTG9hZCB0aGUgZGF0YXNldDwvdT48L2g0PgoKYGBge3J9CmNvbmNyZXRlIDwtIHJlYWQuY3N2KCdjb25jcmV0ZS5jc3YnKQpgYGAKPC9icj4KCjxoND48dT4gU3RlcCAyOiBFeHBsb3JlIGFuZCBwcmVwYXJlIHRoZSBkYXRhc2V0PC91PjwvaDQ+CmBgYHtyfQpjb25jcmV0ZQpzdHIoY29uY3JldGUpCmBgYAoKPT4gQXMgd2UgY2FuIHNlZSwgdGhpcyBkYXRhc2V0IGNvbnRhaW5zIDEwMzAgZXhhbXBsZXMgYW5kIGFsbCBvZiB0aGUgaW5wdXQgdmFyaWFibGVzIGFuZCBvdXRwdXQgYXJlIG51bWVyaWMuCgpTaW5jZSB3ZSB3YW50IHRvIHVzZSBuZXVyYWwgbmV0d29yaywgaXQncyBhIGdvb2QgaWRlYSB0byBub3JtYWxpemUgdGhlIGRhdGFzZXQgc28gdGhhdCB2YXJpYWJsZXMgdGhhdCBoYXZlIHNpZ25pZmljYW50bHkgZ3JlYXRlciByYW5nZSB3aWxsIG5vdCBkb21pbmF0ZSBvdGhlcnMuIElmIGEgdmFyaWFibGVzIGhhcyBhIGxhcmdlciByYW5nZSBjb21wYXJlIHRvIHRoZSBvdGhlcnMsIGl0IHdpbGwgaGF2ZSBtb3JlICJzYXkiIChha2EgaW1wYWN0KSBkdXJpbmcgdGhlIHRyYWluaW5nIHByb2Nlc3MsIHRodXMgdGhlIG1vZGVsIHdpbGwgcmVwbHkgbW9yZSBoZWF2aWx5IG9uIHRoaXMgdmFyaWFibGUgaW5zdGVhZCBvZiBjb25zaWRlcmluZyBhbGwgdmFyaWFibGVzIGVxdWFsbHkuCmBgYHtyfQojIGxldCdzIGRlZmluZSBhIGZ1bmN0aW9uIHRvIG5vcm1hbGl6ZSBkYXRhc2V0Cm5vcm1hbGl6ZSA8LSBmdW5jdGlvbih4KXsKICByZXR1cm4gKCh4IC0gbWluKHgpKSAvIChtYXgoeCkgLSBtaW4oeCkpKQp9Cgpjb25jcmV0ZV9ub3JtIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KGNvbmNyZXRlLCBub3JtYWxpemUpKQpzdW1tYXJ5KGNvbmNyZXRlX25vcm0pCmBgYAo9PT4gVGhlIGRhdGFzZXQgaGFzIGJlZW4gbm9ybWFsaXplZCBhbmQgdmFyaWFibGVzJyByYW5nZSBpcyBub3cgYmV0d2VlbiAwIGFuZCAxLiBUaGUgbm9ybWFsaXplZCBkYXRhc2V0IGlzIHN0b3JlZCBpbiA8ZW0+Y29uY3JldGVfbm9ybTwvZW0+IHZhcmlhYmxlIHdoaWNoIHdlIHdpbGwgdXNlIHRvIHRyYWluIHRoZSBuZXVyYWwgbmV0d29yay4gV2l0aCB0aGVpciB2YWx1ZXMgbm9ybWFsaXplZCwgZWFjaCB2YXJpYWJsZSB3aWxsIGhhdmUgZXF1YWwgInNheSIgKG9yIGltcGFjdCkgY29tcGFyZSB0byB0aGUgcmVzdCBvZiB2YXJpYWJsZXMuPC9icj4KCldpdGggdGhlIG5vcm1hbGl6ZWQgZGF0YXNldCBpbiBoYW5kLCB3ZSB3aWxsIGdvIGFoZWFkIGFuZCBzcGxpdCBvdXIgZGF0YXNldCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHN1YiBkYXRhc2V0OgpgYGB7cn0KIyB0cmFpbmluZyBzZXQgNzUgcGVyY2VudCwgdGVzdGluZyBzZXQgMjUlCmNvbmNyZXRlX3RyYWluIDwtIGNvbmNyZXRlX25vcm1bMTo3NzMsIF0KY29uY3JldGVfdGVzdCA8LSBjb25jcmV0ZV9ub3JtWzc3NDoxMDMwLCBdCmBgYAo8L2JyPgo8aDQ+PHU+IFN0ZXAgMzogVHJhaW4gdGhlIG5ldXJhbCBuZXR3b3JrPC91PjwvaDQ+CgpXZSB3aWxsIHVzZSB0aGUgbmV1cmFsIG5ldHdvcmsgbW9kZWwgaW1wbGVtZW50ZWQgaW4gdGhlIDxlbT5uZXVyYWxuZXQ8L2VtPiBwYWNrYWdlLiBXZSBuZWVkIHRvIGRvd25sb2FkIGFuZCBsb2FkIHRoZSBsaWJyYXJ5LgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCduZXVyYWxuZXQnKQpsaWJyYXJ5KG5ldXJhbG5ldCkKCiMgd2Ugd2lsbCB0cmFpbiBhIG5ldXJhbCBuZXR3b3JrIHdpdGggMSBuZXVyb24gaW4gaGlkZGVuIGxheWVyCmNvbmNyZXRlX21vZGVsIDwtIG5ldXJhbG5ldChzdHJlbmd0aCB+IGNlbWVudCArIHNsYWcgKyBhc2ggKyB3YXRlciArIHN1cGVycGxhc3RpYyArIGNvYXJzZWFnZyArIGZpbmVhZ2cgKyBhZ2UsIGRhdGEgPSBjb25jcmV0ZV90cmFpbiwgaGlkZGVuID0gMSkKIyB0aGVuIHdlIHBsb3QgdGhlIHRvcG9sb2d5IG9mIHRoZSByZXN1bHRlZCBuZXVyYWwgbmV0d29yawpwbG90KGNvbmNyZXRlX21vZGVsKQpgYGAKPT0+IEJ5IHBsb3R0aW5nIHRoZSBuZXVyYWwgbmV0d29yaydzIHRvcG9sb2d5LCB3ZSBvYnNlcnZlZCB0aGF0IHRoZSBtb2RlbCBoYXMgOCBpbnB1dCB2YXJpYWJsZXMgcGx1cyBhIGJpYXMgdW5pdCBhcyBleHBlY3RlZC4gVGhlIHdlaWdodHMgdGhhdCBnb2VzIHdpdGggZWFjaCBpbnB1dCB2YXJpYWJsZSBpcyBhbHNvIHNob3duIG5leHQgdG8gZWFjaCBlYWNoIG5vZGUuIEZvciBleGFtcGxlLCB0aGUgd2VpZ2h0IGZvciB3YXRlciB2YXJpYWJsZSBpcyAtMS42NDk0MjsgdGhpcyBtYWtlcyBzZW5zZSBiZWNhdXNlLCBpbnR1aXRpdmVseSwgaWYgd2UgcHV0IHRvbyBtdWNoIHdhdGVyIGludG8gdGhlIGNvbmNyZXRlIG1peHR1cmUgaXQgd2lsbCAidGhpbiIgb3V0IHRoZSBtaXh0dXJlLCB0aHVzIGxlYWRzIHRvIHdlYWtlciBib3VuZGluZyBjb25jcmV0ZS4KVGhlIHBsb3QgYWxzbyBzaG93IHVzIHRoZSBTdW0gb2YgU3F1YXJlZCBFcnJvciAoU1NFKSA9IDUuMDc4IHdoaWNoIGlzIHRoZSBjdW11bGF0aXZlIHNxdWFyZWQgZXJyb3IgYmV0d2VlbiBwcmVkaWN0ZWQgYW5kIHRydWUgdmFsdWVzIG9mIHN0cmVuZ3RoOyBpdCBhbHNvIGluY2x1ZGUgdGhlIG51bWJlciBvZiBzdGVwcyA9IDM3MjUgdGhhdCB0aGUgbmV1cmFsIG5ldHdvcmsgdGFrZSB0byB0cmFpbiB0aGUgdHJhaW5pbmcgZGF0YXNldC48L2JyPjwvYnI+Cgo8aDQ+PHU+IFN0ZXAgNDogRXZhbHVhdGUgdGhlIG5ldXJhbCBuZXR3b3JrJ3MgcGVyZm9ybWFuY2U8L3U+PC9oND4KTm93IHRoYXQgd2UgaGF2ZSBvdXIgbmV1cmFsIG5ldHdvcmsgdHJhaW5lZCwgd2Ugd2lsbCBwdXQgaXQgdG8gdGhlIHRlc3QgYnkgdXNpbmcgaXQgdG8gcHJlZGljdCB0aGUgdGVzdGluZyBkYXRhc2V0IGFuZCBzZWUgaG93IGl0IHdvdWxkIHBlcmZvcm0uCmBgYHtyfQojIGNvbXB1dGUgZnVuY3Rpb24gd2lsbCByZXR1cm4gdGhlIG5ldXJvbnMgZm9yIGVhY2ggbGF5ZXIgYW5kIHRoZSByZXN1bHQ7IHRoZXNlIHR3byBmaWVsZCBhcmUgc3RvcmVkIGluICRuZXVyb25zIGFuZCAkbmV0LnJlc3VsdCB2YXJpYWJsZXMKbW9kZWxfcmVzdWx0cyA8LSBjb21wdXRlKGNvbmNyZXRlX21vZGVsLCBjb25jcmV0ZV90ZXN0WzE6OF0pIyBzeW50YXg6IGNvbXB1dGUobmV1cmFsX25ldHdvcmtfbW9kZWwsIHRlc3RfZGF0YXNldCkKcHJlZGljdGVkX3N0cmVuZ3RoIDwtIG1vZGVsX3Jlc3VsdHMkbmV0LnJlc3VsdAojIGV2YWx1YXRlIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHByZWRpY3RlZCBzdHJlbmd0aCBhbmQgdHJ1ZSBzdHJlbmd0aCBvZiB0aGUgdGVzdGluZyBkYXRhc2V0CmNvcihwcmVkaWN0ZWRfc3RyZW5ndGgsIGNvbmNyZXRlX3Rlc3Qkc3RyZW5ndGgpCmBgYAo9PT4gSWYgdHdvIHZhcmlhYmxlcyBoYXZlIGNvcnJlbGF0aW9uIGNsb3NlIHRvIDEsIHRoZW4gaXQgbWVhbnMgdGhlc2UgdHdvIHZhcmlhYmxlcyBoYXZlIGEgY2xvc2UgbGluZWFyIHJlbGF0aW9uc2hpcCB0byBlYWNoIG90aGVyLiBPdXIgPHN0cm9uZz5wcmVkaWN0ZWRfc3RyZW5ndGg8L3N0cm9uZz4gYW5kIDxzdHJvbmc+Y29uY3JldGVfdGVzdCRzdHJlbmd0aDwvc3Ryb25nPiBoYXMgYSBjb3JyZWxhdGlvbiBtZWFzdXJlbWVudCBvZiAwLjgxOyB0aGlzIGluZGljYXRlcyB0aGF0IGV2ZW4gd2l0aCBvdXIgc2ltcGxlIG5ldXJhbCBuZXR3b3JrIG1vZGVsIHdpdGggb25seSBvbmUgaGlkZGVuIHVuaXQsIGl0IGNhbiBwcmVkaWN0IHRoZSBjb25jcmV0ZSBzdHJlbmd0aCBmYWlybHkgd2VsbCE8L2JyPjwvYnI+Cgo8aDQ+PHU+IFN0ZXAgNTogSW1wcm92ZSB0aGUgbmV1cmFsIG5ldHdvcmsncyBwZXJmb3JtYW5jZTwvdT48L2g0PgpXaGVuIGl0IGNvbWUgdG8gcHVibGljIHNhZmV0eSwgd2Ugd2FudCB0byBpbXByb3ZlIG91ciBhYmlsaXR5IHRvIHByZWRpY3Qgc3RydWN0dXJlIGludGVncml0eSBhcyBhY2N1cmF0ZSBhcyB3ZSBjYW4uIEV2ZW4gdGhvdWdoIG91ciBzaW1wbGUgbmV1cmFsIG5ldHdvcmsgY2FuIHByZWRpY3QgY29uY3JldGUgc3RyZW5ndGggZmFpcmx5IHdlbGwsIHRoZXJlIGlzIHJvb20gZm9yIGltcHJvdmVtZW50LgoKTGV0J3MgdHJ5IHRvIGluY3JlYXNlIHRoZSBudW1iZXIgb2YgaGlkZGVuIHVuaXQgdG8gNToKYGBge3J9CmNvbmNyZXRlX21vZGVsMiA8LSBuZXVyYWxuZXQoc3RyZW5ndGggfiBjZW1lbnQgKyBzbGFnICsgYXNoICsgd2F0ZXIgKyBzdXBlcnBsYXN0aWMgKyBjb2Fyc2VhZ2cgKyBmaW5lYWdnICsgYWdlLCBkYXRhID0gY29uY3JldGVfdHJhaW4sIGhpZGRlbiA9IDUpCnBsb3QoY29uY3JldGVfbW9kZWwyKQpgYGAKPT0+IFJpZ2h0IG9mZiB0aGUgYmF0LCB3ZSBjYW4gc2VlIHRoYXQgdGhlIFN1bSBvZiBTcXVhcmVkIEVycm9yIChTU0UpIGhhcyBkZWNyZWFzZWQgc2lnbmlmaWNhbnRseSBmcm9tIDUuMDc4IHRvIDEuNzA1IHdoaWNoIGlzIGFib3V0IDMgdGltZXMgcmVkdWN0aW9uIGluIFNTRSB2YWx1ZS4gSW4gYWRkaXRpb24sIHRoZSBudW1iZXIgb2Ygc3RlcHMgaGFzIGFsc28gaW5jcmVhc2VkIGZyb20gMzcyNSB0byA0MTY4MCBzaW5jZSB0aGUgbmV1cmFsIG5ldHdvcmsgaXMgbW9yZSBjb21wbGV4IGR1ZSB0byB0aGUgZXh0cmEgaGlkZGVuIHVuaXRzIQpCeSBkb2luZyB0aGUgc2FtZSBzdGVwcyB0byBldmFsdWF0ZSB0aGUgbmV3IG5ldXJhbCBuZXR3b3JrJ3MgcGVyZm9ybWFuY2UsIHdlIGdldDoKYGBge3J9Cm1vZGVsX3Jlc3VsdHMyIDwtIGNvbXB1dGUoY29uY3JldGVfbW9kZWwyLCBjb25jcmV0ZV90ZXN0WzE6OF0pCnByZWRpY3RlZF9zdHJlbmd0aDIgPC0gbW9kZWxfcmVzdWx0czIkbmV0LnJlc3VsdApjb3IocHJlZGljdGVkX3N0cmVuZ3RoMiwgY29uY3JldGVfdGVzdCRzdHJlbmd0aCkKYGBgCj09PiBXaXRoIHRoZSBuZXcgYW5kIGEgYml0IG1vcmUgY29tcGxleCBuZXVyYWwgbmV0d29yaywgd2UgY2FuIHNlZSBhIHNpZ25pZmljYW50IGltcHJvdmVtZW50IGluIHRoZSBjb3JyZWxhdGlvbiBtZWFzdXJlbWVudCBiZXR3ZWVuIHByZWRpY3RlZCBzdHJlbmd0aCB2YWx1ZXMgYW5kIHRoZSB0cnVlIHN0cmVuZ3RoIHZhbHVlcy4gV2l0aCBjb3JyZWxhdGlvbiBtZWFzdXJlbWVudCA9IDAuOTI0LCB0aGUgcHJlZGljdGVkIHN0cmVuZ3RoIGlzIG11Y2ggbW9yZSBjbG9zZWx5IHJlc2VtYmxlIHRoZSB0cnVlIHN0cmVuZ3RoIGNvbXBhcmUgdG8gdGhlIHByZXZpb3VzIG5ldXJhbCBuZXR3b3JrIG1vZGVsLiBUaGlzIGlzIHN1Y2ggZ3JlYXQgaW1wcm92ZW1lbnQgZm9yIGFkZGluZyBqdXN0IGEgZmV3IGhpZGRlbiBuZXVyb25zISEhPC9icj48L2JyPgoKPGg0PisgUmVmZXJlbmNlOjwvaDQ+CjxlbT5NYWNoaW5lIExlYXJuaW5nIHdpdGggUjwvZW0+IGJ5IDxlbT5CcmV0dCBMYW50ejwvZW0+Cg==