Homework: Predicting Price of Used Car Sales

Consider the data on used cars (ToyotaCorolla.csv) with 1436 records and details on 38 attributes, including Price, Age, KM, HP, and other specifications. The goal is to predict the price of a used Toyota Corolla based on its specifications.

# import ToyotaCorolla.csv and
cars <- read.csv("ToyotaCorolla.csv")

Exercise 1

  • Preprocess data appropriately for neural network modelling.
    • Select following variables: Price, Age_08_04, KM, Fuel_Type, HP, Automatic, Doors, Quarterly_Tax, Mfr_Guarantee, Guarantee_Period, Airco, Automatic_airco, CD_Player, Powered_Windows, Sport_Model, and Tow_Bar.
    • Remember to first scale the numerical predictor and outcome variables to a 0–1 scale.
    • Convert categorical predictors to dummies.
  • Use 70% of the data for training and 30% for test.
# Type your code here

set.seed(54321)

selected_columns <- c('Price', 'Age_08_04', 'KM', 'Fuel_Type', 'HP','Automatic','Doors','Quarterly_Tax','Mfr_Guarantee', 'Guarantee_Period','Airco','Automatic_airco','CD_Player','Powered_Windows','Sport_Model','Tow_Bar')

cars_selected <- cars[,selected_columns]

names(cars)
##  [1] "Id"                "Model"             "Price"            
##  [4] "Age_08_04"         "Mfg_Month"         "Mfg_Year"         
##  [7] "KM"                "Fuel_Type"         "HP"               
## [10] "Met_Color"         "Color"             "Automatic"        
## [13] "CC"                "Doors"             "Cylinders"        
## [16] "Gears"             "Quarterly_Tax"     "Weight"           
## [19] "Mfr_Guarantee"     "BOVAG_Guarantee"   "Guarantee_Period" 
## [22] "ABS"               "Airbag_1"          "Airbag_2"         
## [25] "Airco"             "Automatic_airco"   "Boardcomputer"    
## [28] "CD_Player"         "Central_Lock"      "Powered_Windows"  
## [31] "Power_Steering"    "Radio"             "Mistlamps"        
## [34] "Sport_Model"       "Backseat_Divider"  "Metallic_Rim"     
## [37] "Radio_cassette"    "Parking_Assistant" "Tow_Bar"
target <- cars_selected$Price 
cars_Selected <- cars_selected[,-which(names(cars_selected) == "Price")]

cars_selected_no_cat <- cars_selected[,!names(cars_selected) %in% c('Fuel_Type', 'Automatic', 'Mfr_Guarantee','Guarantee_Period', 'Airco', 'Automatic_airco','CD_Player', 'Powered_Windows', 'Sport_Model', 'Tow_Bar')]

cars_dummies <- model.matrix(~ Fuel_Type + Automatic + Mfr_Guarantee + Guarantee_Period + Airco + Automatic_airco + CD_Player + Powered_Windows + Sport_Model + Tow_Bar - 1, data = cars_selected)

cars_processed <- cbind(Price = target, cars_dummies, cars_selected_no_cat)

set.seed(54321)

train_size <-floor(0.7 * nrow(cars_processed))

train_data <- cars_processed[1:train_size,]

test_data <- cars_processed[(train_size + 1):nrow(cars_processed),]

dim(train_data)
## [1] 1005   19
dim(test_data)
## [1] 431  19

Exercise 2

Fit a neural network model to the data. Use a single hidden layer with 2 nodes.
- Use predictors Age_08_04, KM, Fuel_Type, HP, Automatic, Doors, Quarterly_Tax, Mfr_Guarantee, Guarantee_Period, Airco, Automatic_airco, CD_Player, Powered_Windows, Sport_Model, and Tow_Bar.
- Record the RMSE for the training data and the test data.

# Type your code here
library(neuralnet)
## Warning: package 'neuralnet' was built under R version 4.3.3
## 
## Attaching package: 'neuralnet'
## The following object is masked from 'package:dplyr':
## 
##     compute
nn_model <- neuralnet(Price ~ ., data = train_data, hidden = 2, linear.output = TRUE)

summary(nn_model)
##                     Length Class      Mode    
## call                    5  -none-     call    
## response             1005  -none-     numeric 
## covariate           17085  -none-     numeric 
## model.list              2  -none-     list    
## err.fct                 1  -none-     function
## act.fct                 1  -none-     function
## linear.output           1  -none-     logical 
## data                   19  data.frame list    
## exclude                 0  -none-     NULL    
## net.result              1  -none-     list    
## weights                 1  -none-     list    
## generalized.weights     1  -none-     list    
## startweights            1  -none-     list    
## result.matrix          42  -none-     numeric
train_predictions <- predict(nn_model, train_data)

test_predictions <- predict(nn_model, test_data)

train_rmse <- sqrt(mean((train_data$Price - train_predictions)^2))

test_rmse <- sqrt(mean((test_data$Price - test_predictions)^2))

Exercise 3

Repeat the process, changing the number of hidden layers and nodes to {single layer with 5 nodes}, {two layers, 5 nodes in each layer}
i. What happens to the RMS error for the training data as the number of layers and nodes increases?
ii. What happens to the RMS error for the validation data?
iii. Comment on the appropriate number of layers and nodes for this application.

# Type your code here

nn_model_single_layer <- neuralnet(Price ~ ., data = train_data, hidden = 5, linear.output = TRUE)

train_predictions_single_layer <- predict(nn_model_single_layer, train_data)

test_predictions_single_layer <- predict(nn_model_single_layer, test_data)

train_rmse_single_layer <- sqrt(mean((train_data$Price - train_predictions_single_layer)^2)) 

test_rmse_single_layer <- sqrt(mean((test_data$Price - test_predictions_single_layer)^2))

cat("Single Hidden Layer (5 nodes) - Training RMSE:", train_rmse_single_layer, "\n")
## Single Hidden Layer (5 nodes) - Training RMSE: 3743.216
cat("Single Hidden Layer (5 nodes) - Testing RMSE:", test_rmse_single_layer, "\n")
## Single Hidden Layer (5 nodes) - Testing RMSE: 3904.122
nn_model_two_layers <- neuralnet(Price ~ ., data = train_data, hidden = c(5, 5), linear.output = TRUE)

train_predictions_two_layers <- predict(nn_model_two_layers, train_data)

test_predictions_two_layers <- predict(nn_model_two_layers, test_data)

train_rmse_two_layers <-sqrt(mean((train_data$Price - train_predictions_two_layers)^2))

test_rmse_two_layers <-sqrt(mean((test_data$Price - test_predictions_two_layers)^2))

cat("Two Hidden Layer (5 nodes each) - Training RMSE:", train_rmse_two_layers, "\n")
## Two Hidden Layer (5 nodes each) - Training RMSE: 3743.216
cat("Two Hidden Layer (5 nodes each) - Testing RMSE:", test_rmse_two_layers, "\n")
## Two Hidden Layer (5 nodes each) - Testing RMSE: 3908.356

Summary

  1. In terms of what happened to the RMSE for the training data as the number of layers and nodes increased was the neural network became more complex. This can potentially lower the training RMSE. When models become too complex it may start overfitting the training data, meaning it could perform “too well” and not generalize well to the whole test set

  2. In terms of what happens to the RMSE for the validation data if the model becomes overfitted and complicated with too many layers and nodes the test RMSE may increase. This means that the model has a poor generalization of the data. If the network on the other hand is not complex enough then the test RMSE will remain high because the model overgeneralizes and does not capture the patterns of the data. In terms of an actual comparison between the test RMSE for both models there is a leveling off at a certain point in which adding more layers and nodes will reduce the test RMSE. Further layers and nodes will off balance this benefit and lead to an increased test RMSE.

  3. The appropriate number of layers and nodes for this application is single layer with 5 nodes. We have seen a common theme throughout these responses that we are ultimately looking for a sweet spot in maximizing layers and nodes while also not overfitting the data and performing poorly in not seeing patterns. We don’t want generalizations but we also don’t want overcomplications. For these reasons with every data set we need to understand a good number to use and understand the trade-offs between training RMSE and test RMSE.

LS0tDQp0aXRsZTogIkVDT04gMzIwMDogSG9tZXdvcmsgNSINCmF1dGhvcjogIkphZGVuIFNhbXBzb24iDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6IG9wZW5pbnRybzo6bGFiX3JlcG9ydA0KLS0tDQoNCmBgYHtyIGxvYWQtcGFja2FnZXMsIG1lc3NhZ2U9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZXZhbCA9IFRSVUUpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkob3BlbmludHJvKQ0KYGBgDQoNCiMgSG9tZXdvcms6IFByZWRpY3RpbmcgUHJpY2Ugb2YgVXNlZCBDYXIgU2FsZXMNCg0KQ29uc2lkZXIgdGhlIGRhdGEgb24gdXNlZCBjYXJzIChgVG95b3RhQ29yb2xsYS5jc3ZgKSB3aXRoIDE0MzYgcmVjb3JkcyBhbmQgZGV0YWlscyBvbiAzOCBhdHRyaWJ1dGVzLCBpbmNsdWRpbmcgUHJpY2UsIEFnZSwgS00sIEhQLCBhbmQgb3RoZXIgc3BlY2lmaWNhdGlvbnMuIFRoZSBnb2FsIGlzIHRvIHByZWRpY3QgdGhlIHByaWNlIG9mIGEgdXNlZCBUb3lvdGEgQ29yb2xsYSBiYXNlZCBvbiBpdHMgc3BlY2lmaWNhdGlvbnMuDQoNCmBgYHtyfQ0KIyBpbXBvcnQgVG95b3RhQ29yb2xsYS5jc3YgYW5kDQpjYXJzIDwtIHJlYWQuY3N2KCJUb3lvdGFDb3JvbGxhLmNzdiIpDQpgYGANCg0KIyMgRXhlcmNpc2UgMQ0KLSBQcmVwcm9jZXNzIGRhdGEgYXBwcm9wcmlhdGVseSBmb3IgbmV1cmFsIG5ldHdvcmsgbW9kZWxsaW5nLiAgDQogIC0gU2VsZWN0IGZvbGxvd2luZyB2YXJpYWJsZXM6IFByaWNlLCBBZ2VfMDhfMDQsIEtNLCBGdWVsX1R5cGUsIEhQLCBBdXRvbWF0aWMsIERvb3JzLCBRdWFydGVybHlfVGF4LCBNZnJfR3VhcmFudGVlLCBHdWFyYW50ZWVfUGVyaW9kLCBBaXJjbywgQXV0b21hdGljX2FpcmNvLCBDRF9QbGF5ZXIsIFBvd2VyZWRfV2luZG93cywgU3BvcnRfTW9kZWwsIGFuZCBUb3dfQmFyLiAgDQogIC0gUmVtZW1iZXIgdG8gZmlyc3Qgc2NhbGUgdGhlIG51bWVyaWNhbCBwcmVkaWN0b3IgYW5kIG91dGNvbWUgdmFyaWFibGVzIHRvIGEgMOKAkzEgc2NhbGUuICANCiAgLSBDb252ZXJ0IGNhdGVnb3JpY2FsIHByZWRpY3RvcnMgdG8gZHVtbWllcy4gICAgDQotIFVzZSA3MCUgb2YgdGhlIGRhdGEgZm9yIHRyYWluaW5nIGFuZCAzMCUgZm9yIHRlc3QuICANCg0KDQpgYGB7cn0NCiMgVHlwZSB5b3VyIGNvZGUgaGVyZQ0KDQpzZXQuc2VlZCg1NDMyMSkNCg0Kc2VsZWN0ZWRfY29sdW1ucyA8LSBjKCdQcmljZScsICdBZ2VfMDhfMDQnLCAnS00nLCAnRnVlbF9UeXBlJywgJ0hQJywnQXV0b21hdGljJywnRG9vcnMnLCdRdWFydGVybHlfVGF4JywnTWZyX0d1YXJhbnRlZScsICdHdWFyYW50ZWVfUGVyaW9kJywnQWlyY28nLCdBdXRvbWF0aWNfYWlyY28nLCdDRF9QbGF5ZXInLCdQb3dlcmVkX1dpbmRvd3MnLCdTcG9ydF9Nb2RlbCcsJ1Rvd19CYXInKQ0KDQpjYXJzX3NlbGVjdGVkIDwtIGNhcnNbLHNlbGVjdGVkX2NvbHVtbnNdDQoNCm5hbWVzKGNhcnMpDQoNCnRhcmdldCA8LSBjYXJzX3NlbGVjdGVkJFByaWNlIA0KY2Fyc19TZWxlY3RlZCA8LSBjYXJzX3NlbGVjdGVkWywtd2hpY2gobmFtZXMoY2Fyc19zZWxlY3RlZCkgPT0gIlByaWNlIildDQoNCmNhcnNfc2VsZWN0ZWRfbm9fY2F0IDwtIGNhcnNfc2VsZWN0ZWRbLCFuYW1lcyhjYXJzX3NlbGVjdGVkKSAlaW4lIGMoJ0Z1ZWxfVHlwZScsICdBdXRvbWF0aWMnLCAnTWZyX0d1YXJhbnRlZScsJ0d1YXJhbnRlZV9QZXJpb2QnLCAnQWlyY28nLCAnQXV0b21hdGljX2FpcmNvJywnQ0RfUGxheWVyJywgJ1Bvd2VyZWRfV2luZG93cycsICdTcG9ydF9Nb2RlbCcsICdUb3dfQmFyJyldDQoNCmNhcnNfZHVtbWllcyA8LSBtb2RlbC5tYXRyaXgofiBGdWVsX1R5cGUgKyBBdXRvbWF0aWMgKyBNZnJfR3VhcmFudGVlICsgR3VhcmFudGVlX1BlcmlvZCArIEFpcmNvICsgQXV0b21hdGljX2FpcmNvICsgQ0RfUGxheWVyICsgUG93ZXJlZF9XaW5kb3dzICsgU3BvcnRfTW9kZWwgKyBUb3dfQmFyIC0gMSwgZGF0YSA9IGNhcnNfc2VsZWN0ZWQpDQoNCmNhcnNfcHJvY2Vzc2VkIDwtIGNiaW5kKFByaWNlID0gdGFyZ2V0LCBjYXJzX2R1bW1pZXMsIGNhcnNfc2VsZWN0ZWRfbm9fY2F0KQ0KDQpzZXQuc2VlZCg1NDMyMSkNCg0KdHJhaW5fc2l6ZSA8LWZsb29yKDAuNyAqIG5yb3coY2Fyc19wcm9jZXNzZWQpKQ0KDQp0cmFpbl9kYXRhIDwtIGNhcnNfcHJvY2Vzc2VkWzE6dHJhaW5fc2l6ZSxdDQoNCnRlc3RfZGF0YSA8LSBjYXJzX3Byb2Nlc3NlZFsodHJhaW5fc2l6ZSArIDEpOm5yb3coY2Fyc19wcm9jZXNzZWQpLF0NCg0KZGltKHRyYWluX2RhdGEpDQpkaW0odGVzdF9kYXRhKQ0KDQpgYGANCg0KIyMgRXhlcmNpc2UgMg0KRml0IGEgbmV1cmFsIG5ldHdvcmsgbW9kZWwgdG8gdGhlIGRhdGEuIFVzZSBhIHNpbmdsZSBoaWRkZW4gbGF5ZXIgd2l0aCAyIG5vZGVzLiAgDQotIFVzZSBwcmVkaWN0b3JzIEFnZV8wOF8wNCwgS00sIEZ1ZWxfVHlwZSwgSFAsIEF1dG9tYXRpYywgRG9vcnMsIFF1YXJ0ZXJseV9UYXgsIE1mcl9HdWFyYW50ZWUsIEd1YXJhbnRlZV9QZXJpb2QsIEFpcmNvLCBBdXRvbWF0aWNfYWlyY28sIENEX1BsYXllciwgUG93ZXJlZF9XaW5kb3dzLCBTcG9ydF9Nb2RlbCwgYW5kIFRvd19CYXIuICANCi0gUmVjb3JkIHRoZSBSTVNFIGZvciB0aGUgdHJhaW5pbmcgZGF0YSBhbmQgdGhlIHRlc3QgZGF0YS4gIA0KDQpgYGB7cn0NCiMgVHlwZSB5b3VyIGNvZGUgaGVyZQ0KbGlicmFyeShuZXVyYWxuZXQpDQoNCm5uX21vZGVsIDwtIG5ldXJhbG5ldChQcmljZSB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhLCBoaWRkZW4gPSAyLCBsaW5lYXIub3V0cHV0ID0gVFJVRSkNCg0Kc3VtbWFyeShubl9tb2RlbCkNCg0KdHJhaW5fcHJlZGljdGlvbnMgPC0gcHJlZGljdChubl9tb2RlbCwgdHJhaW5fZGF0YSkNCg0KdGVzdF9wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG5uX21vZGVsLCB0ZXN0X2RhdGEpDQoNCnRyYWluX3Jtc2UgPC0gc3FydChtZWFuKCh0cmFpbl9kYXRhJFByaWNlIC0gdHJhaW5fcHJlZGljdGlvbnMpXjIpKQ0KDQp0ZXN0X3Jtc2UgPC0gc3FydChtZWFuKCh0ZXN0X2RhdGEkUHJpY2UgLSB0ZXN0X3ByZWRpY3Rpb25zKV4yKSkNCmBgYA0KDQojIyBFeGVyY2lzZSAzDQpSZXBlYXQgdGhlIHByb2Nlc3MsIGNoYW5naW5nIHRoZSBudW1iZXIgb2YgaGlkZGVuIGxheWVycyBhbmQgbm9kZXMgdG8ge3NpbmdsZSBsYXllciB3aXRoIDUgbm9kZXN9LCB7dHdvIGxheWVycywgNSBub2RlcyBpbiBlYWNoIGxheWVyfSAgDQoJaS4gV2hhdCBoYXBwZW5zIHRvIHRoZSBSTVMgZXJyb3IgZm9yIHRoZSB0cmFpbmluZyBkYXRhIGFzIHRoZSBudW1iZXIgb2YgbGF5ZXJzIGFuZCBub2RlcyBpbmNyZWFzZXM/ICANCglpaS4gV2hhdCBoYXBwZW5zIHRvIHRoZSBSTVMgZXJyb3IgZm9yIHRoZSB2YWxpZGF0aW9uIGRhdGE/ICANCglpaWkuIENvbW1lbnQgb24gdGhlIGFwcHJvcHJpYXRlIG51bWJlciBvZiBsYXllcnMgYW5kIG5vZGVzIGZvciB0aGlzIGFwcGxpY2F0aW9uLiAgDQoNCmBgYHtyfQ0KIyBUeXBlIHlvdXIgY29kZSBoZXJlDQoNCm5uX21vZGVsX3NpbmdsZV9sYXllciA8LSBuZXVyYWxuZXQoUHJpY2UgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSwgaGlkZGVuID0gNSwgbGluZWFyLm91dHB1dCA9IFRSVUUpDQoNCnRyYWluX3ByZWRpY3Rpb25zX3NpbmdsZV9sYXllciA8LSBwcmVkaWN0KG5uX21vZGVsX3NpbmdsZV9sYXllciwgdHJhaW5fZGF0YSkNCg0KdGVzdF9wcmVkaWN0aW9uc19zaW5nbGVfbGF5ZXIgPC0gcHJlZGljdChubl9tb2RlbF9zaW5nbGVfbGF5ZXIsIHRlc3RfZGF0YSkNCg0KdHJhaW5fcm1zZV9zaW5nbGVfbGF5ZXIgPC0gc3FydChtZWFuKCh0cmFpbl9kYXRhJFByaWNlIC0gdHJhaW5fcHJlZGljdGlvbnNfc2luZ2xlX2xheWVyKV4yKSkgDQoNCnRlc3Rfcm1zZV9zaW5nbGVfbGF5ZXIgPC0gc3FydChtZWFuKCh0ZXN0X2RhdGEkUHJpY2UgLSB0ZXN0X3ByZWRpY3Rpb25zX3NpbmdsZV9sYXllcileMikpDQoNCmNhdCgiU2luZ2xlIEhpZGRlbiBMYXllciAoNSBub2RlcykgLSBUcmFpbmluZyBSTVNFOiIsIHRyYWluX3Jtc2Vfc2luZ2xlX2xheWVyLCAiXG4iKQ0KDQpjYXQoIlNpbmdsZSBIaWRkZW4gTGF5ZXIgKDUgbm9kZXMpIC0gVGVzdGluZyBSTVNFOiIsIHRlc3Rfcm1zZV9zaW5nbGVfbGF5ZXIsICJcbiIpDQoNCm5uX21vZGVsX3R3b19sYXllcnMgPC0gbmV1cmFsbmV0KFByaWNlIH4gLiwgZGF0YSA9IHRyYWluX2RhdGEsIGhpZGRlbiA9IGMoNSwgNSksIGxpbmVhci5vdXRwdXQgPSBUUlVFKQ0KDQp0cmFpbl9wcmVkaWN0aW9uc190d29fbGF5ZXJzIDwtIHByZWRpY3Qobm5fbW9kZWxfdHdvX2xheWVycywgdHJhaW5fZGF0YSkNCg0KdGVzdF9wcmVkaWN0aW9uc190d29fbGF5ZXJzIDwtIHByZWRpY3Qobm5fbW9kZWxfdHdvX2xheWVycywgdGVzdF9kYXRhKQ0KDQp0cmFpbl9ybXNlX3R3b19sYXllcnMgPC1zcXJ0KG1lYW4oKHRyYWluX2RhdGEkUHJpY2UgLSB0cmFpbl9wcmVkaWN0aW9uc190d29fbGF5ZXJzKV4yKSkNCg0KdGVzdF9ybXNlX3R3b19sYXllcnMgPC1zcXJ0KG1lYW4oKHRlc3RfZGF0YSRQcmljZSAtIHRlc3RfcHJlZGljdGlvbnNfdHdvX2xheWVycyleMikpDQoNCmNhdCgiVHdvIEhpZGRlbiBMYXllciAoNSBub2RlcyBlYWNoKSAtIFRyYWluaW5nIFJNU0U6IiwgdHJhaW5fcm1zZV90d29fbGF5ZXJzLCAiXG4iKQ0KDQpjYXQoIlR3byBIaWRkZW4gTGF5ZXIgKDUgbm9kZXMgZWFjaCkgLSBUZXN0aW5nIFJNU0U6IiwgdGVzdF9ybXNlX3R3b19sYXllcnMsICJcbiIpDQoNCg0KDQpgYGANCg0KIyMjIFN1bW1hcnkgDQoNCmkuIEluIHRlcm1zIG9mIHdoYXQgaGFwcGVuZWQgdG8gdGhlIFJNU0UgZm9yIHRoZSB0cmFpbmluZyBkYXRhIGFzIHRoZSBudW1iZXIgb2YgbGF5ZXJzIGFuZCBub2RlcyBpbmNyZWFzZWQgd2FzIHRoZSBuZXVyYWwNCm5ldHdvcmsgYmVjYW1lIG1vcmUgY29tcGxleC4gVGhpcyBjYW4gcG90ZW50aWFsbHkgbG93ZXIgdGhlIHRyYWluaW5nIFJNU0UuIFdoZW4gbW9kZWxzIGJlY29tZSB0b28gY29tcGxleCBpdCBtYXkgc3RhcnQgDQpvdmVyZml0dGluZyB0aGUgdHJhaW5pbmcgZGF0YSwgbWVhbmluZyBpdCBjb3VsZCBwZXJmb3JtICJ0b28gd2VsbCIgYW5kIG5vdCBnZW5lcmFsaXplIHdlbGwgdG8gdGhlIHdob2xlIHRlc3Qgc2V0IA0KDQppaS4gSW4gdGVybXMgb2Ygd2hhdCBoYXBwZW5zIHRvIHRoZSBSTVNFIGZvciB0aGUgdmFsaWRhdGlvbiBkYXRhIGlmIHRoZSBtb2RlbCBiZWNvbWVzIG92ZXJmaXR0ZWQgYW5kIGNvbXBsaWNhdGVkIHdpdGggdG9vDQptYW55IGxheWVycyBhbmQgbm9kZXMgdGhlIHRlc3QgUk1TRSBtYXkgaW5jcmVhc2UuIFRoaXMgbWVhbnMgdGhhdCB0aGUgbW9kZWwgaGFzIGEgcG9vciBnZW5lcmFsaXphdGlvbiBvZiB0aGUgZGF0YS4gSWYgdGhlDQpuZXR3b3JrIG9uIHRoZSBvdGhlciBoYW5kIGlzIG5vdCBjb21wbGV4IGVub3VnaCB0aGVuIHRoZSB0ZXN0IFJNU0Ugd2lsbCByZW1haW4gaGlnaCBiZWNhdXNlIHRoZSBtb2RlbCBvdmVyZ2VuZXJhbGl6ZXMgYW5kDQpkb2VzIG5vdCBjYXB0dXJlIHRoZSBwYXR0ZXJucyBvZiB0aGUgZGF0YS4gSW4gdGVybXMgb2YgYW4gYWN0dWFsIGNvbXBhcmlzb24gYmV0d2VlbiB0aGUgdGVzdCBSTVNFIGZvciBib3RoIG1vZGVscyB0aGVyZSANCmlzIGEgbGV2ZWxpbmcgb2ZmIGF0IGEgY2VydGFpbiBwb2ludCBpbiB3aGljaCBhZGRpbmcgbW9yZSBsYXllcnMgYW5kIG5vZGVzIHdpbGwgcmVkdWNlIHRoZSB0ZXN0IFJNU0UuIEZ1cnRoZXIgbGF5ZXJzIGFuZCANCm5vZGVzIHdpbGwgb2ZmIGJhbGFuY2UgdGhpcyBiZW5lZml0IGFuZCBsZWFkIHRvIGFuIGluY3JlYXNlZCB0ZXN0IFJNU0UuDQoNCmlpaS4gVGhlIGFwcHJvcHJpYXRlIG51bWJlciBvZiBsYXllcnMgYW5kIG5vZGVzIGZvciB0aGlzIGFwcGxpY2F0aW9uIGlzIHNpbmdsZSBsYXllciB3aXRoIDUgbm9kZXMuIFdlIGhhdmUgc2VlbiBhIGNvbW1vbg0KdGhlbWUgdGhyb3VnaG91dCB0aGVzZSByZXNwb25zZXMgdGhhdCB3ZSBhcmUgdWx0aW1hdGVseSBsb29raW5nIGZvciBhIHN3ZWV0IHNwb3QgaW4gbWF4aW1pemluZyBsYXllcnMgYW5kIG5vZGVzIHdoaWxlDQphbHNvIG5vdCBvdmVyZml0dGluZyB0aGUgZGF0YSBhbmQgcGVyZm9ybWluZyBwb29ybHkgaW4gbm90IHNlZWluZyBwYXR0ZXJucy4gV2UgZG9uJ3Qgd2FudCBnZW5lcmFsaXphdGlvbnMgYnV0IHdlIGFsc28NCmRvbid0IHdhbnQgb3ZlcmNvbXBsaWNhdGlvbnMuIEZvciB0aGVzZSByZWFzb25zIHdpdGggZXZlcnkgZGF0YSBzZXQgd2UgbmVlZCB0byB1bmRlcnN0YW5kIGEgZ29vZCBudW1iZXIgdG8gdXNlIGFuZA0KdW5kZXJzdGFuZCB0aGUgdHJhZGUtb2ZmcyBiZXR3ZWVuIHRyYWluaW5nIFJNU0UgYW5kIHRlc3QgUk1TRS4NCg==