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==