#In the field of engineering, it is crucial to have accurate estimates of the performance of building materials. These estimates are required in order to develop safety guidelines governing the materials used in the construction of buildings, bridges, and roadways. 
#Estimating the strength of concrete is a challenge of particular interest. Although it is used in nearly every construction project, concrete performance varies greatly due to the use of a wide variety of ingredients that interact in complex ways. As a result, it is difficult to accurately predict the strength of the final product. A model that could reliably predict concrete strength given a listing of the composition of the input materials could result in safer construction practices.
#Fist let's explore the data
## 'data.frame':    1030 obs. of  9 variables:
##  $ cement      : num  540 540 332 332 199 ...
##  $ slag        : num  0 0 142 142 132 ...
##  $ ash         : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ water       : num  162 162 228 228 192 228 228 228 228 228 ...
##  $ superplastic: num  2.5 2.5 0 0 0 0 0 0 0 0 ...
##  $ coarseagg   : num  1040 1055 932 932 978 ...
##  $ fineagg     : num  676 676 594 594 826 ...
##  $ age         : int  28 28 270 365 360 90 365 28 28 28 ...
##  $ strength    : num  80 61.9 40.3 41 44.3 ...
#The nine variables in the data frame correspond to the eight features and one outcome we expected, although a problem has become apparent. Neural networks work best when the input data are scaled to a narrow range around zero, and here we see values ranging anywhere from zero up to over a thousand.
#Typically, the solution to this problem is to rescale the data with a normalizing or standardization function. Here we are going to use a MinMax normalization and we are going to build a function to rescale the data.
normalize=function(x)
{
  return ((x-min(x))/(max(x)-min(x)))
}
#It's time to apply this function to all the database columns and take a look at our new normalized data.
concrete_norm=as.data.frame(lapply(concrete,normalize))
summary(concrete_norm$strength)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0000  0.2664  0.4001  0.4172  0.5457  1.0000
#We are convinced that the data now varies from 0 to 1.
#It's time to detemine the training and the test dataset. We will divide them 72:25, so 75% of the data will be the training set and 25% of them will be the test dataset.
concrete_train=concrete_norm[1:773, ]
concrete_test = concrete_norm[774:1030, ]
#To model the relationship between the ingredients used in concrete and the strength of the finished product, we will use a multilayer feedforward neural network. The neuralnet package by Stefan Fritsch and Frauke Guenther provides a standard and easyto-use implementation of such networks.
#We'll begin by training the simplest multilayer feedforward network with only a single hidden node:
concrete_model=neuralnet(strength~cement+slag+ash+water+superplastic+coarseagg+fineagg+age,data=concrete_train)
#We can then visualize the network topology
plot(concrete_model,rep="best")

#In this simple model, there is one input node for each of the eight features, followed by a single hidden node and a single output node that predicts the concrete strength. The weights for each of the connections are also depicted, as are the bias terms (indicated by the nodes with a 1). The plot also reports the number of training steps and a measure called, the Sum of Squared Errors (SSE). These metrics will be useful when we are evaluating the model performance.
#Now it's time to estimate our model's performance.
model_results <- compute(concrete_model, concrete_test[1:8])
predicted_strength <- model_results$net.result
cor(predicted_strength, concrete_test$strength)
##           [,1]
## [1,] 0.7202723
#Correlations close to 1 indicate strong linear relationships between two variables. Therefore, the correlation here of about 0.73 indicates a fairly strong relationship. This implies that our model is doing a fairly good job, even with only a single hidden node
#Lets try to improve the model performance by increasing the number of hidden nodes.
concrete_model2=neuralnet(strength~cement+slag+ash+water+superplastic+coarseagg+fineagg+age,data=concrete_train,hidden=5)
plot(concrete_model2,rep="best")

model_results2 =compute(concrete_model2, concrete_test[1:8])
predicted_strength2 = model_results2$net.result
cor(predicted_strength2, concrete_test$strength)
##           [,1]
## [1,] 0.7561785
#It is interesting, because if you see the example in the original book the correlation in this step is calculated about 80% and here is just 79%. This is because the weights are chosen randomly.