1 - Description
In this project we will be re-implementing the recommender system built in project three using Spark and the sparklyr package. This recommender system uses Singular Value Decomposition to recommend beers to users. We will be using the data set from Beer Advocate which can be found on Data Wrold with a login required. After numerous attempts we were unable to implement a SVD based recommender, we chose to use the Alternating Least Squares model that was implemented in sparklyr. We have included the setup work for Spark and the steps needed to prepare the data for use in a Spark based system.
2 - Installing sparklyr and Spark and Loading the Data
In this section we include the code to install the latest version of saprklyr, install the version of Spark that we want to work with along with Hadoop. The ultimate goal is to be able to move this type of project to an Amazon Web Services platform based on Elastic MapReduce so we will go with the currently supported version of spark which is version 2.1.
2.1 - Installation
We start by loading the most up-to-date version of sparklyr using the devtools package and installing our version of Spark. We also include the code we used to install the latest version of H2O through rsparkling. This path ultimately was a dead end as we were not able to implement the recommender using SVD and elected to move to implementing the recommender using ALS.
# Installing sparklyr
devtools::install_github("rstudio/sparklyr")
library(sparklyr)
# Installing our version of spark
spark_install(version = "2.1.0", hadoop_version = "2.7")
# Used in an attempt to implement SVD
install.packages("rsparkling")
options(rsparkling.sparklingwater.version = "2.1.0")
2.2 - Connecting to Spark
We now create a connection to our local spark cluster and load in our data. We will also use some of the built in dplyr functionality to subset our data to only include beers that have been reviewed more than 100 times and all reviewers that have reviewed more than 50 beers. This reduces our number of reviews to just under a million and should help to reduce the effects of reviewers and beers that are not active on the process of building a recommender system. We also found that we needed to reduce the data set for memory issues on the local version of Spark. We were able to crash the R session with the full data set.
sc <- spark_connect(master = "local") # Generatng the spark connection
Re-using existing Spark connection to local
2.3 - Loading Data
We also learned from our first implementation that our best model seemed to come from simply applying SVD to the data and then adding the user’s average rating. We found that the model preformed slightly worse when we added in measures such as user bias and item bias. For our Alternating Least Squares model we will also only pass the overall reviews to Spark. One of the issues that we ran into was that spark needs the reviewer’s and beer’s to be represented by numeric values. Much of the code below is focused on reducing the data set and preparing it for use in Spark. We also create a user-item matrix in this step. Originally this was used to attempt to use the SVD function in H2O. Unfortunately the matrix was to large to pass to a local copy of Spark and we also ran into issues with getting the data into a format that H2O would recognize. We were able o use this object later in the recommender.
3 - Building a Recommender using Alternating Least Squares on Spark
The recommender that we ended up building for this project was based on what we could get to work. We tried to recreate the SVD system from Project 3 but ran into issues getting a form of SVD to work in the Spark system. After many days of debugging the system we ended up moving forward with the ALS method that is supported in the sparklyr package.
3.1 Creating the Model
We start by building the model using the ml_als_factorization() and using the technique shared by Professor Stern to create a dataframe from the model and calculate the RMSE. One issue that we ran into here was that given the size of our data we were only able to use 5 iterations of the algorithm before the R session ran into memory issues. This is one area that would be improved by moving the code to a web based system.
We take a quick look at the available information from the model.
summary(model)
Length Class Mode
item.factors 11 data.frame list
user.factors 11 data.frame list
data 2 spark_jobj environment
ml.options 6 ml_options list
model.parameters 2 -none- list
.call 9 -none- call
.model 2 spark_jobj environment
Generating a dataframe of the predicted values for the beers that each of the reviewers have actually reviewed. We will use this information to further measure the performance of the model.
3.2 Calculating the RMSE
We note that the RMSE is very good for this application we have calculated and error of 0.51. On average our recommendations are within half of a point from the actual predictions.
sqrt(mean(with(predictions, prediction-review_overall)^2))
[1] 0.5098854
3.3 Generating the Prediction Matrix
One of the aspects of the model generated by the ml_als_factorization() function is the matrix of user factors and the matrix of item factors that can b recomposed into a matrix of predictions by multiplying the User matrix by the Item matrix transposed. We do this and add back in the user and beer names to facilitate look-up for the recommender.
ratings_pred[1:6, 1:6]
Amber Turbodog Purple Haze Golden Allagash Dubbel Ale Witkap Pater Singel / Stimulo
2.991730 3.513958 3.150859 2.911830 3.551045 3.059690
0110x011 4.104052 3.888289 3.343372 4.854568 4.119103 4.805647
05Harley 3.544364 3.696008 3.440849 3.212972 4.020933 3.724362
100floods 3.428924 3.950155 3.247723 3.396405 3.816144 4.105002
11osixBrew 3.229600 3.360413 2.592963 2.802360 4.128837 3.564293
1759Girl 3.277639 3.291416 2.865247 3.074601 3.823492 3.796192
Now that we have our information we can shut down the spark connection.
4 - Building the Recommender
We will build the recommender in a similar way to the method that we used in project 3. We use the previously rated value if the reviewer has already rated the beer and predicted rating if the user has not rated the beer yet.
getBeer("BeerLover99", "Alaskan Smoked Porter")
[1] "Predicted Rating: 4.1"
getBeer("2xHops", "#9")
[1] "Previously Rated: 3"
4 - Evaluating the Recommender
Now that we have the recommender built and a set of recommendations for our previously rated values we can start to evaluate the recommender. We will look at the root mean square error, create the confusion matrix, and calculate the precision, true-positive rate, and false-positive rate.
4.1 Evaluating the RMSE
We start off by comparing the RMSE with the value we computed last week. Using Spark and the ALS algorithm we were able to achieve a RMSE = 0.51. When we used the SVD algorithm was a RMSE = 1.38. There is the possibility that the ALS method is overfiting the data and we will need more testing to determine this.
4.2 Creating a Confusion Matrix
We next want to look at the total number of true positives and true negatives that we got from the reocmmender. We do this by counting any rating of a 3 or above as a positive and any review of a 3 or below as a negative. We see that the recommender seems to do a good job of predicting the true positives but we may have some issues with more poorly rates beers.
cf_table
predicted
actual 0 1
0 25192 33961
1 17623 865347
Calculating the Precision, Recall, and False Positive Rates
We finish up our evaluation of the system by calculating the precision, recall, and false positive rate. For our recommender. We see that the recommender system has both a high precision and recall but it also has a high false negative rate. We wonder if this might not be related to a class imbalance problem or where we set up our cut score for positive and negative scores.
paste("Percision =", precision, " Recall =", recall, " False Negative =", falseneg)
[1] "Percision = 0.971711514038127 Recall = 0.980041224503664 False Negative = 0.574121346339154"
5 - Conclusion
The project provided a number of interesting challenges for us. The installation and setup of Spark proved to be more complicated then first expected. Once we were able to get a local instance of Spark running we then ran into some classic big data issues. The first attempt to load the user-item matrix into the spark cluster caused the R session to crash. We also ran into the same issue when we attempted to run to many iterations in the ALS model. This would be improved by moving to an AWS installation of Spark but it did limit our testing for this project. We also see from evaluation of the recommender system that we have an issue with any recommendations that are not true positives.
Finally we ran into a number of issues with attempting to perform SVD on the data. The H2O package include with sparklyr should have this functionality but we were unable to debug the issues with our system and get any type of result with this method. We were able to work around this issue but had to implement a different recommendation algorithm to get this to work.
This method of building the recommender had a number pros and cons compared to building the system in local memory. The ALS algorithm was able to run very quickly and the having the data offloaded to Spark seemed to provide a large boost in speed. We also were able, with a few lines of code, generate a very accurate recommender using a more advanced mathematical technique. The trade off was that we needed to spend a lot of time on the front end setting up the system and preparing the data to match a format that algorithm could work with. Ultimately I think that this method is a better and faster way to build the recommender once all a data preparation workflow has been figured out.
LS0tDQp0aXRsZTogJ0RBVEE2NDMgLSBQcm9qZWN0IDQ6IEltcGxlbWVudGluZyBhIFJlY29tbWVuZGVyIFN5c3RlbSBpbiBTcGFyaycNCmF1dGhvcjogIkVyaWsgTnlsYW5kZXIiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQojIyAxIC0gRGVzY3JpcHRpb24NCioqKg0KSW4gdGhpcyBwcm9qZWN0IHdlIHdpbGwgYmUgcmUtaW1wbGVtZW50aW5nIHRoZSByZWNvbW1lbmRlciBzeXN0ZW0gYnVpbHQgaW4gcHJvamVjdCB0aHJlZSB1c2luZyBTcGFyayBhbmQgdGhlICpzcGFya2x5ciogcGFja2FnZS4gVGhpcyByZWNvbW1lbmRlciBzeXN0ZW0gdXNlcyBTaW5ndWxhciBWYWx1ZSBEZWNvbXBvc2l0aW9uIHRvIHJlY29tbWVuZCBiZWVycyB0byB1c2Vycy4gV2Ugd2lsbCBiZSB1c2luZyB0aGUgZGF0YSBzZXQgZnJvbSBbQmVlciBBZHZvY2F0ZV0oaHR0cHM6Ly93d3cuYmVlcmFkdm9jYXRlLmNvbS8pIHdoaWNoIGNhbiBiZSBmb3VuZCBvbiBbRGF0YSBXcm9sZF0oaHR0cHM6Ly9kYXRhLndvcmxkL3NvY2lhbG1lZGlhZGF0YS9iZWVyYWR2b2NhdGUpIHdpdGggYSBsb2dpbiByZXF1aXJlZC4gQWZ0ZXIgbnVtZXJvdXMgYXR0ZW1wdHMgd2Ugd2VyZSB1bmFibGUgdG8gaW1wbGVtZW50IGEgU1ZEIGJhc2VkIHJlY29tbWVuZGVyLCB3ZSBjaG9zZSB0byB1c2UgdGhlIEFsdGVybmF0aW5nIExlYXN0IFNxdWFyZXMgbW9kZWwgdGhhdCB3YXMgaW1wbGVtZW50ZWQgaW4gKnNwYXJrbHlyKi4gV2UgaGF2ZSBpbmNsdWRlZCB0aGUgc2V0dXAgd29yayBmb3IgU3BhcmsgYW5kIHRoZSBzdGVwcyBuZWVkZWQgdG8gcHJlcGFyZSB0aGUgZGF0YSBmb3IgdXNlIGluIGEgU3BhcmsgYmFzZWQgc3lzdGVtLiANCg0KDQojIyAyIC0gSW5zdGFsbGluZyBzcGFya2x5ciBhbmQgU3BhcmsgYW5kIExvYWRpbmcgdGhlIERhdGENCioqKg0KSW4gdGhpcyBzZWN0aW9uIHdlIGluY2x1ZGUgdGhlIGNvZGUgdG8gaW5zdGFsbCB0aGUgbGF0ZXN0IHZlcnNpb24gb2YgKnNhcHJrbHlyKiwgaW5zdGFsbCB0aGUgdmVyc2lvbiBvZiBTcGFyayB0aGF0IHdlIHdhbnQgdG8gd29yayB3aXRoIGFsb25nIHdpdGggSGFkb29wLiBUaGUgdWx0aW1hdGUgZ29hbCBpcyB0byBiZSBhYmxlIHRvIG1vdmUgdGhpcyB0eXBlIG9mIHByb2plY3QgdG8gYW4gQW1hem9uIFdlYiBTZXJ2aWNlcyBwbGF0Zm9ybSBiYXNlZCBvbiBFbGFzdGljIE1hcFJlZHVjZSBzbyB3ZSB3aWxsIGdvIHdpdGggdGhlIGN1cnJlbnRseSBzdXBwb3J0ZWQgdmVyc2lvbiBvZiBzcGFyayB3aGljaCBpcyB2ZXJzaW9uIDIuMS4NCg0KIyMjIDIuMSAtIEluc3RhbGxhdGlvbiAgDQpXZSBzdGFydCBieSBsb2FkaW5nIHRoZSBtb3N0IHVwLXRvLWRhdGUgdmVyc2lvbiBvZiAqc3BhcmtseXIqIHVzaW5nIHRoZSBkZXZ0b29scyBwYWNrYWdlIGFuZCBpbnN0YWxsaW5nIG91ciB2ZXJzaW9uIG9mIFNwYXJrLiBXZSBhbHNvIGluY2x1ZGUgdGhlIGNvZGUgd2UgdXNlZCB0byBpbnN0YWxsIHRoZSBsYXRlc3QgdmVyc2lvbiBvZiBIMk8gdGhyb3VnaCAqcnNwYXJrbGluZyouIFRoaXMgcGF0aCB1bHRpbWF0ZWx5IHdhcyBhIGRlYWQgZW5kIGFzIHdlIHdlcmUgbm90IGFibGUgdG8gaW1wbGVtZW50IHRoZSByZWNvbW1lbmRlciB1c2luZyBTVkQgYW5kIGVsZWN0ZWQgdG8gbW92ZSB0byBpbXBsZW1lbnRpbmcgdGhlIHJlY29tbWVuZGVyIHVzaW5nIEFMUy4NCmBgYHtSIGV2YWw9RkFMU0V9DQojIEluc3RhbGxpbmcgc3BhcmtseXINCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigicnN0dWRpby9zcGFya2x5ciIpDQoNCmxpYnJhcnkoc3BhcmtseXIpDQojIEluc3RhbGxpbmcgb3VyIHZlcnNpb24gb2Ygc3BhcmsNCnNwYXJrX2luc3RhbGwodmVyc2lvbiA9ICIyLjEuMCIsIGhhZG9vcF92ZXJzaW9uID0gIjIuNyIpDQoNCiMgVXNlZCBpbiBhbiBhdHRlbXB0IHRvIGltcGxlbWVudCBTVkQNCmluc3RhbGwucGFja2FnZXMoInJzcGFya2xpbmciKQ0Kb3B0aW9ucyhyc3BhcmtsaW5nLnNwYXJrbGluZ3dhdGVyLnZlcnNpb24gPSAiMi4xLjAiKQ0KYGBgDQoNCiMjIyAyLjIgLSBDb25uZWN0aW5nIHRvIFNwYXJrDQpXZSBub3cgY3JlYXRlIGEgY29ubmVjdGlvbiB0byBvdXIgbG9jYWwgc3BhcmsgY2x1c3RlciBhbmQgbG9hZCBpbiBvdXIgZGF0YS4gV2Ugd2lsbCBhbHNvIHVzZSBzb21lIG9mIHRoZSBidWlsdCBpbiAqZHBseXIqIGZ1bmN0aW9uYWxpdHkgdG8gc3Vic2V0IG91ciBkYXRhIHRvIG9ubHkgaW5jbHVkZSBiZWVycyB0aGF0IGhhdmUgYmVlbiByZXZpZXdlZCBtb3JlIHRoYW4gMTAwIHRpbWVzIGFuZCBhbGwgcmV2aWV3ZXJzIHRoYXQgaGF2ZSByZXZpZXdlZCBtb3JlIHRoYW4gNTAgYmVlcnMuIFRoaXMgcmVkdWNlcyBvdXIgbnVtYmVyIG9mIHJldmlld3MgdG8ganVzdCB1bmRlciBhIG1pbGxpb24gYW5kIHNob3VsZCBoZWxwIHRvIHJlZHVjZSB0aGUgZWZmZWN0cyBvZiByZXZpZXdlcnMgYW5kIGJlZXJzIHRoYXQgYXJlIG5vdCBhY3RpdmUgb24gdGhlIHByb2Nlc3Mgb2YgYnVpbGRpbmcgYSByZWNvbW1lbmRlciBzeXN0ZW0uIFdlIGFsc28gZm91bmQgdGhhdCB3ZSBuZWVkZWQgdG8gcmVkdWNlIHRoZSBkYXRhIHNldCBmb3IgbWVtb3J5IGlzc3VlcyBvbiB0aGUgbG9jYWwgdmVyc2lvbiBvZiBTcGFyay4gV2Ugd2VyZSBhYmxlIHRvIGNyYXNoIHRoZSBSIHNlc3Npb24gd2l0aCB0aGUgZnVsbCBkYXRhIHNldC4gIA0KDQpgYGB7Un0NCmxpYnJhcnkoc3BhcmtseXIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShyZXNoYXBlMikNCg0KIyBHZW5lcmF0aW5nIHRoZSBzcGFyayBjb25uZWN0aW9uDQpzYyA8LSBzcGFya19jb25uZWN0KG1hc3RlciA9ICJsb2NhbCIpIA0KDQojIFJlYWRpbmcgaW4gdGhlIGRhdGENCnJhdGluZ3MgPC0gcmVhZC5jc3YoIn4vR2l0SHViL0RBVEE2NDMvZGF0YS9iZWVyYWR2b2NhdGUvYmVlcl9yZXZpZXdzLmNzdiIsIGhlYWRlciA9IFRSVUUsIHNlcCA9IiwiLA0KICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQpgYGANCg0KIyMjIDIuMyAtIExvYWRpbmcgRGF0YQ0KV2UgYWxzbyBsZWFybmVkIGZyb20gb3VyIGZpcnN0IGltcGxlbWVudGF0aW9uIHRoYXQgb3VyIGJlc3QgbW9kZWwgc2VlbWVkIHRvIGNvbWUgZnJvbSBzaW1wbHkgYXBwbHlpbmcgU1ZEIHRvIHRoZSBkYXRhIGFuZCB0aGVuIGFkZGluZyB0aGUgdXNlcidzIGF2ZXJhZ2UgcmF0aW5nLiBXZSBmb3VuZCB0aGF0IHRoZSBtb2RlbCBwcmVmb3JtZWQgc2xpZ2h0bHkgd29yc2Ugd2hlbiB3ZSBhZGRlZCBpbiBtZWFzdXJlcyBzdWNoIGFzIHVzZXIgYmlhcyBhbmQgaXRlbSBiaWFzLiBGb3Igb3VyIEFsdGVybmF0aW5nIExlYXN0IFNxdWFyZXMgbW9kZWwgd2Ugd2lsbCBhbHNvIG9ubHkgcGFzcyB0aGUgb3ZlcmFsbCByZXZpZXdzIHRvIFNwYXJrLiBPbmUgb2YgdGhlIGlzc3VlcyB0aGF0IHdlIHJhbiBpbnRvIHdhcyB0aGF0IHNwYXJrIG5lZWRzIHRoZSByZXZpZXdlcidzIGFuZCBiZWVyJ3MgdG8gYmUgcmVwcmVzZW50ZWQgYnkgbnVtZXJpYyB2YWx1ZXMuIE11Y2ggb2YgdGhlIGNvZGUgYmVsb3cgaXMgZm9jdXNlZCBvbiByZWR1Y2luZyB0aGUgZGF0YSBzZXQgYW5kIHByZXBhcmluZyBpdCBmb3IgdXNlIGluIFNwYXJrLiBXZSBhbHNvIGNyZWF0ZSBhIHVzZXItaXRlbSBtYXRyaXggaW4gdGhpcyBzdGVwLiBPcmlnaW5hbGx5IHRoaXMgd2FzIHVzZWQgdG8gYXR0ZW1wdCB0byB1c2UgdGhlIFNWRCBmdW5jdGlvbiBpbiBIMk8uIFVuZm9ydHVuYXRlbHkgdGhlIG1hdHJpeCB3YXMgdG8gbGFyZ2UgdG8gcGFzcyB0byBhIGxvY2FsIGNvcHkgb2YgU3BhcmsgYW5kIHdlIGFsc28gcmFuIGludG8gaXNzdWVzIHdpdGggZ2V0dGluZyB0aGUgZGF0YSBpbnRvIGEgZm9ybWF0IHRoYXQgSDJPIHdvdWxkIHJlY29nbml6ZS4gV2Ugd2VyZSBhYmxlIG8gdXNlIHRoaXMgb2JqZWN0IGxhdGVyIGluIHRoZSByZWNvbW1lbmRlci4NCmBgYHtSfQ0KIyBTdWJzZXR0aW5nIHRoZSByYXRpbmdzDQpyYXRpbmdzIDwtIHJhdGluZ3MgJT4lDQogIGdyb3VwX2J5KGJlZXJfYmVlcmlkKSAlPiUNCiAgZmlsdGVyKG4oKT4xMDApICU+JQ0KICBncm91cF9ieShyZXZpZXdfcHJvZmlsZW5hbWUpICU+JQ0KICBmaWx0ZXIobigpPjUwKSAlPiUNCiAgc2VsZWN0KC1icmV3ZXJ5X2lkLCAtYnJld2VyeV9uYW1lLCAtcmV2aWV3X3RpbWUpDQoNCiMgR2VuZXJhdGluZyB0aGUgdXNlci1pdGVtIG1hdHJpeCBmb3IgdGhlIHByZWRpY3Rvcg0KdXNlcl9iZWVyIDwtIGRjYXN0KHJhdGluZ3MsIHJldmlld19wcm9maWxlbmFtZX5iZWVyX25hbWUsIA0KICAgICAgICAgICAgICAgICAgIHZhbHVlLnZhciA9ICJyZXZpZXdfb3ZlcmFsbCIsIGZpbGw9MCwgZnVuLmFnZ3JlZ2F0ZSA9IG1lYW4pDQoNCiMgRmlsbGluZyBpbiByb3duYW1lcw0Kcm93bmFtZXModXNlcl9iZWVyKSA9IHVzZXJfYmVlciRyZXZpZXdfcHJvZmlsZW5hbWUNCg0KIyBSZW1vdmluZyB0aGUgZmlyc3QgY29sdW1uDQp1c2VyX2JlZXIgPC0gdXNlcl9iZWVyWywtMV0NCg0KIyBDb252ZXJ0aW5nIHRvIGEgbWF0cml4DQp1c2VyX2JlZXIgPC0gYXMubWF0cml4KHVzZXJfYmVlcikNCg0KIyBBZGRpbmcgdW5pcXVlIHVzZXIgaWQncyB1c2luZyB0aGUgbnVtZXJpYyB2YWx1ZSBvZiB0aGUgZmFjdG9yIHZhbHVlIG9mIHRoZSBwcm9maWxlIG5hbWUNCnJhdGluZ3MgPC0gdHJhbnNmb3JtKHJhdGluZ3MsdXNlcl9pZD1hcy5udW1lcmljKGZhY3RvcihyZXZpZXdfcHJvZmlsZW5hbWUpKSkNCg0KIyBDcmVhdGluZyBhIGRhdGFmcmFtZSBmb3IgdXNlciBuYW1lcyBhbmQgdXNlciBpZCdzDQp1X25hbWVzIDwtIHJhdGluZ3MgJT4lDQogIGRpc3RpbmN0KHJldmlld19wcm9maWxlbmFtZSwgdXNlcl9pZCkgJT4lDQogIGFycmFuZ2UodXNlcl9pZCkNCg0KIyBjcmVhdGluZyBhIGRhdGFmcmFtZSBmb3IgYmVlciBuYW1lcyBhbmQgYmVlciBpZCdzDQpiZWVyX25hbWVzIDwtIHJhdGluZ3MgJT4lDQogIGRpc3RpbmN0KGJlZXJfbmFtZSwgYmVlcl9iZWVyaWQpICU+JQ0KICBhcnJhbmdlKGJlZXJfYmVlcmlkKQ0KDQojIGNyZWF0aW5nIGEgZGF0YSBmcmFtZSBvZiB1c2VyIGlkJ3MsIGJlZXIgaWQncywgYW5kIG92ZXJhbGwgcmV2aWV3cy4NCnJhdGluZ3MgPC0gcmF0aW5ncyAlPiUNCiAgc2VsZWN0KHVzZXJfaWQsIGJlZXJfYmVlcmlkLCByZXZpZXdfb3ZlcmFsbCkNCg0KIyBMb2FkaW5nIHRoZSBkYXRhIGludG8gU3BhcmsNCmJlZXJfdGJsIDwtIHNkZl9jb3B5X3RvKHNjLCByYXRpbmdzLCBvdmVyd3JpdGUgPSBUUlVFKQ0KYmVlcl90YmwNCmBgYA0KDQojIyAzIC0gQnVpbGRpbmcgYSBSZWNvbW1lbmRlciB1c2luZyBBbHRlcm5hdGluZyBMZWFzdCBTcXVhcmVzIG9uIFNwYXJrDQoqKioNClRoZSByZWNvbW1lbmRlciB0aGF0IHdlIGVuZGVkIHVwIGJ1aWxkaW5nIGZvciB0aGlzIHByb2plY3Qgd2FzIGJhc2VkIG9uIHdoYXQgd2UgY291bGQgZ2V0IHRvIHdvcmsuIFdlIHRyaWVkIHRvIHJlY3JlYXRlIHRoZSBTVkQgc3lzdGVtIGZyb20gUHJvamVjdCAzIGJ1dCByYW4gaW50byBpc3N1ZXMgZ2V0dGluZyBhIGZvcm0gb2YgU1ZEIHRvIHdvcmsgaW4gdGhlIFNwYXJrIHN5c3RlbS4gQWZ0ZXIgbWFueSBkYXlzIG9mIGRlYnVnZ2luZyB0aGUgc3lzdGVtIHdlIGVuZGVkIHVwIG1vdmluZyBmb3J3YXJkIHdpdGggdGhlIEFMUyBtZXRob2QgdGhhdCBpcyBzdXBwb3J0ZWQgaW4gdGhlICpzcGFya2x5ciogcGFja2FnZS4gDQoNCiMjIyMgMy4xIENyZWF0aW5nIHRoZSBNb2RlbA0KV2Ugc3RhcnQgYnkgYnVpbGRpbmcgdGhlIG1vZGVsIHVzaW5nIHRoZSAqbWxfYWxzX2ZhY3Rvcml6YXRpb24oKSogYW5kIHVzaW5nIHRoZSB0ZWNobmlxdWUgc2hhcmVkIGJ5IFByb2Zlc3NvciBTdGVybiB0byBjcmVhdGUgYSBkYXRhZnJhbWUgZnJvbSB0aGUgbW9kZWwgYW5kIGNhbGN1bGF0ZSB0aGUgUk1TRS4gT25lIGlzc3VlIHRoYXQgd2UgcmFuIGludG8gaGVyZSB3YXMgdGhhdCBnaXZlbiB0aGUgc2l6ZSBvZiBvdXIgZGF0YSB3ZSB3ZXJlIG9ubHkgYWJsZSB0byB1c2UgNSBpdGVyYXRpb25zIG9mIHRoZSBhbGdvcml0aG0gYmVmb3JlIHRoZSBSIHNlc3Npb24gcmFuIGludG8gbWVtb3J5IGlzc3Vlcy4gVGhpcyBpcyBvbmUgYXJlYSB0aGF0IHdvdWxkIGJlIGltcHJvdmVkIGJ5IG1vdmluZyB0aGUgY29kZSB0byBhIHdlYiBiYXNlZCBzeXN0ZW0uDQoNCmBgYHtSfQ0KbW9kZWwgPC0gbWxfYWxzX2ZhY3Rvcml6YXRpb24oYmVlcl90YmwsIHJhdGluZy5jb2x1bW4gPSAicmV2aWV3X292ZXJhbGwiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZXIuY29sdW1uID0gInVzZXJfaWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXRlbS5jb2x1bW4gPSAiYmVlcl9iZWVyaWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXRlci5tYXggPSA1LCByZWd1bGFyaXphdGlvbi5wYXJhbWV0ZXIgPSAwLjAxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltcGxpY2l0LnByZWZlcmVuY2VzID0gVFJVRSwgYWxwaGEgPSAxLjApDQpgYGANCg0KV2UgdGFrZSBhIHF1aWNrIGxvb2sgYXQgdGhlIGF2YWlsYWJsZSBpbmZvcm1hdGlvbiBmcm9tIHRoZSBtb2RlbC4NCmBgYHtSfQ0Kc3VtbWFyeShtb2RlbCkNCmBgYA0KDQpHZW5lcmF0aW5nIGEgZGF0YWZyYW1lIG9mIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGZvciB0aGUgYmVlcnMgdGhhdCBlYWNoIG9mIHRoZSByZXZpZXdlcnMgaGF2ZSBhY3R1YWxseSByZXZpZXdlZC4gV2Ugd2lsbCB1c2UgdGhpcyBpbmZvcm1hdGlvbiB0byBmdXJ0aGVyIG1lYXN1cmUgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBtb2RlbC4NCmBgYHtSfQ0KcHJlZGljdGlvbnMgPC0gbW9kZWwkLm1vZGVsICU+JQ0KICBpbnZva2UoInRyYW5zZm9ybSIsIHNwYXJrX2RhdGFmcmFtZShiZWVyX3RibCkpICU+JQ0KICBjb2xsZWN0KCkNCg0KcHJlZGljdGlvbnNbcHJlZGljdGlvbnMkdXNlcl9pZCA9PSAxLF0NCmBgYA0KDQojIyMjIDMuMiBDYWxjdWxhdGluZyB0aGUgUk1TRQ0KV2Ugbm90ZSB0aGF0IHRoZSBSTVNFIGlzIHZlcnkgZ29vZCBmb3IgdGhpcyBhcHBsaWNhdGlvbiB3ZSBoYXZlIGNhbGN1bGF0ZWQgYW5kIGVycm9yIG9mIDAuNTEuIE9uIGF2ZXJhZ2Ugb3VyIHJlY29tbWVuZGF0aW9ucyBhcmUgd2l0aGluIGhhbGYgb2YgYSBwb2ludCBmcm9tIHRoZSBhY3R1YWwgcHJlZGljdGlvbnMuIA0KYGBge1J9DQpzcXJ0KG1lYW4od2l0aChwcmVkaWN0aW9ucywgcHJlZGljdGlvbi1yZXZpZXdfb3ZlcmFsbCleMikpDQpgYGANCg0KIyMjIyAzLjMgR2VuZXJhdGluZyB0aGUgUHJlZGljdGlvbiBNYXRyaXggIA0KT25lIG9mIHRoZSBhc3BlY3RzIG9mIHRoZSBtb2RlbCBnZW5lcmF0ZWQgYnkgdGhlICptbF9hbHNfZmFjdG9yaXphdGlvbigpKiBmdW5jdGlvbiBpcyB0aGUgbWF0cml4IG9mIHVzZXIgZmFjdG9ycyBhbmQgdGhlIG1hdHJpeCBvZiBpdGVtIGZhY3RvcnMgdGhhdCBjYW4gYiByZWNvbXBvc2VkIGludG8gYSBtYXRyaXggb2YgcHJlZGljdGlvbnMgYnkgbXVsdGlwbHlpbmcgdGhlIFVzZXIgbWF0cml4IGJ5IHRoZSBJdGVtIG1hdHJpeCB0cmFuc3Bvc2VkLiBXZSBkbyB0aGlzIGFuZCBhZGQgYmFjayBpbiB0aGUgdXNlciBhbmQgYmVlciBuYW1lcyB0byBmYWNpbGl0YXRlIGxvb2stdXAgZm9yIHRoZSByZWNvbW1lbmRlci4NCmBgYHtSfQ0KIyBFeHRyYWN0aW5nIHRoZSBVc2VyIGFuZCBJdGVtIGZhY3RvciBtYXRyaWNlcw0KdXNlcl9tYXRyaXggPC0gYXMubWF0cml4KG1vZGVsJHVzZXIuZmFjdG9yc1ssLTFdKQ0KaXRlbV9tYXRyaXggPC0gYXMubWF0cml4KG1vZGVsJGl0ZW0uZmFjdG9yc1ssLTFdKQ0KDQojIENhbGN1bGF0aW5nIHRoZSBwcmVkaWN0ZWQgcmF0aW5ncyBtYXRyaXgNCnJhdGluZ3NfcHJlZCA8LSB1c2VyX21hdHJpeCAlKiUgdChpdGVtX21hdHJpeCkNCg0KIyBBZGRpbmcgaW4gdGhlIHVzZXIocm93KSBhbmQgYmVlcihjb2x1bW4pIG5hbWVzIA0Kcm93bmFtZXMocmF0aW5nc19wcmVkKSA9IHVfbmFtZXMkcmV2aWV3X3Byb2ZpbGVuYW1lDQpjb2xuYW1lcyhyYXRpbmdzX3ByZWQpID0gYmVlcl9uYW1lcyRiZWVyX25hbWUNCg0KIyBMb29raW5nIGF0IHRoZSB1cHBlciBsZWZ0IGNvcm5lcg0KcmF0aW5nc19wcmVkWzE6NiwgMTo2XQ0KYGBgDQoNCk5vdyB0aGF0IHdlIGhhdmUgb3VyIGluZm9ybWF0aW9uIHdlIGNhbiBzaHV0IGRvd24gdGhlIHNwYXJrIGNvbm5lY3Rpb24uDQpgYGB7Un0NCnNwYXJrX2Rpc2Nvbm5lY3Qoc2MpDQpgYGANCg0KIyMgNCAtIEJ1aWxkaW5nIHRoZSBSZWNvbW1lbmRlcg0KKioqDQpXZSB3aWxsIGJ1aWxkIHRoZSByZWNvbW1lbmRlciBpbiBhIHNpbWlsYXIgd2F5IHRvIHRoZSBtZXRob2QgdGhhdCB3ZSB1c2VkIGluIHByb2plY3QgMy4gV2UgdXNlIHRoZSBwcmV2aW91c2x5IHJhdGVkIHZhbHVlIGlmIHRoZSByZXZpZXdlciBoYXMgYWxyZWFkeSByYXRlZCB0aGUgYmVlciBhbmQgcHJlZGljdGVkIHJhdGluZyBpZiB0aGUgdXNlciBoYXMgbm90IHJhdGVkIHRoZSBiZWVyIHlldC4NCmBgYHtSfQ0KZ2V0QmVlciA8LSBmdW5jdGlvbih1c2VyLCBiZWVyKXsNCiAgaWYodXNlcl9iZWVyW3VzZXIsIGJlZXJdICE9IDApew0KICAgIHBhc3RlKCJQcmV2aW91c2x5IFJhdGVkOiIsIHVzZXJfYmVlclt1c2VyLGJlZXJdKQ0KICB9DQogIGVsc2V7DQogICAgcGFzdGUoIlByZWRpY3RlZCBSYXRpbmc6Iiwgcm91bmQocmF0aW5nc19wcmVkW3VzZXIsYmVlcl0sMSkpDQogIH0NCn0NCmBgYA0KDQpgYGB7Un0NCmdldEJlZXIoIkJlZXJMb3Zlcjk5IiwgIkFsYXNrYW4gU21va2VkIFBvcnRlciIpDQpgYGANCg0KYGBge1J9DQpnZXRCZWVyKCIyeEhvcHMiLCAiIzkiKQ0KYGBgDQoNCiMjIDQgLSBFdmFsdWF0aW5nIHRoZSBSZWNvbW1lbmRlcg0KKioqDQpOb3cgdGhhdCB3ZSBoYXZlIHRoZSByZWNvbW1lbmRlciBidWlsdCBhbmQgYSBzZXQgb2YgcmVjb21tZW5kYXRpb25zIGZvciBvdXIgcHJldmlvdXNseSByYXRlZCB2YWx1ZXMgd2UgY2FuIHN0YXJ0IHRvIGV2YWx1YXRlIHRoZSByZWNvbW1lbmRlci4gV2Ugd2lsbCBsb29rIGF0IHRoZSByb290IG1lYW4gc3F1YXJlIGVycm9yLCBjcmVhdGUgdGhlIGNvbmZ1c2lvbiBtYXRyaXgsIGFuZCBjYWxjdWxhdGUgdGhlIHByZWNpc2lvbiwgdHJ1ZS1wb3NpdGl2ZSByYXRlLCBhbmQgZmFsc2UtcG9zaXRpdmUgcmF0ZS4NCg0KIyMjIyA0LjEgRXZhbHVhdGluZyB0aGUgUk1TRQ0KV2Ugc3RhcnQgb2ZmIGJ5IGNvbXBhcmluZyB0aGUgUk1TRSB3aXRoIHRoZSB2YWx1ZSB3ZSBjb21wdXRlZCBsYXN0IHdlZWsuIFVzaW5nIFNwYXJrIGFuZCB0aGUgQUxTIGFsZ29yaXRobSB3ZSB3ZXJlIGFibGUgdG8gYWNoaWV2ZSBhIFJNU0UgPSAwLjUxLiBXaGVuIHdlIHVzZWQgdGhlIFNWRCBhbGdvcml0aG0gd2FzIGEgUk1TRSA9IDEuMzguIFRoZXJlIGlzIHRoZSBwb3NzaWJpbGl0eSB0aGF0IHRoZSBBTFMgbWV0aG9kIGlzIG92ZXJmaXRpbmcgdGhlIGRhdGEgYW5kIHdlIHdpbGwgbmVlZCBtb3JlIHRlc3RpbmcgdG8gZGV0ZXJtaW5lIHRoaXMuDQoNCiMjIyMgNC4yIENyZWF0aW5nIGEgQ29uZnVzaW9uIE1hdHJpeA0KV2UgbmV4dCB3YW50IHRvIGxvb2sgYXQgdGhlIHRvdGFsIG51bWJlciBvZiB0cnVlIHBvc2l0aXZlcyBhbmQgdHJ1ZSBuZWdhdGl2ZXMgdGhhdCB3ZSBnb3QgZnJvbSB0aGUgcmVvY21tZW5kZXIuIFdlIGRvIHRoaXMgYnkgY291bnRpbmcgYW55IHJhdGluZyBvZiBhIDMgb3IgYWJvdmUgYXMgYSBwb3NpdGl2ZSBhbmQgYW55IHJldmlldyBvZiBhIDMgb3IgYmVsb3cgYXMgYSBuZWdhdGl2ZS4gV2Ugc2VlIHRoYXQgdGhlIHJlY29tbWVuZGVyIHNlZW1zIHRvIGRvIGEgZ29vZCBqb2Igb2YgcHJlZGljdGluZyB0aGUgdHJ1ZSBwb3NpdGl2ZXMgYnV0IHdlIG1heSBoYXZlIHNvbWUgaXNzdWVzIHdpdGggbW9yZSBwb29ybHkgcmF0ZXMgYmVlcnMuDQpgYGB7Un0NCmNvbmZ1c2lvbiA8LSBiZWVyX3ByZWQgJT4lIA0KICBtdXRhdGUoYWN0dWFsID0gaWZfZWxzZShyZXZpZXdfb3ZlcmFsbCA+PSAzLCAxLCAwKSwNCiAgICAgICAgIHByZWRpY3RlZCA9IGlmX2Vsc2UocHJlZGljdGlvbiA+PSAzLCAxLCAwKSkgJT4lDQogIHNlbGVjdChhY3R1YWwsIHByZWRpY3RlZCkNCg0KY2ZfdGFibGUgPC0gdGFibGUoY29uZnVzaW9uKQ0KY2ZfdGFibGUNCmBgYA0KDQojIyMjIENhbGN1bGF0aW5nIHRoZSBQcmVjaXNpb24sIFJlY2FsbCwgYW5kIEZhbHNlIFBvc2l0aXZlIFJhdGVzDQpXZSBmaW5pc2ggdXAgb3VyIGV2YWx1YXRpb24gb2YgdGhlIHN5c3RlbSBieSBjYWxjdWxhdGluZyB0aGUgcHJlY2lzaW9uLCByZWNhbGwsIGFuZCBmYWxzZSBwb3NpdGl2ZSByYXRlLiBGb3Igb3VyIHJlY29tbWVuZGVyLiBXZSBzZWUgdGhhdCB0aGUgcmVjb21tZW5kZXIgc3lzdGVtIGhhcyBib3RoIGEgaGlnaCBwcmVjaXNpb24gYW5kIHJlY2FsbCBidXQgaXQgYWxzbyBoYXMgYSBoaWdoIGZhbHNlIG5lZ2F0aXZlIHJhdGUuIFdlIHdvbmRlciBpZiB0aGlzIG1pZ2h0IG5vdCBiZSByZWxhdGVkIHRvIGEgY2xhc3MgaW1iYWxhbmNlIHByb2JsZW0gb3Igd2hlcmUgd2Ugc2V0IHVwIG91ciBjdXQgc2NvcmUgZm9yIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBzY29yZXMuIA0KYGBge1J9DQpwcmVjaXNpb24gPSAoY2ZfdGFibGVbMiwyXSkvKGNmX3RhYmxlWzIsMl0gKyBjZl90YWJsZVsxLDFdKQ0KcmVjYWxsID0gKGNmX3RhYmxlWzIsMl0pLyhjZl90YWJsZVsyLDJdICsgY2ZfdGFibGVbMiwxXSkNCmZhbHNlbmVnID0gKGNmX3RhYmxlWzEsMl0pLyhjZl90YWJsZVsxLDJdICsgY2ZfdGFibGVbMSwxXSkNCg0KcGFzdGUoIlBlcmNpc2lvbiA9IiwgcHJlY2lzaW9uLCAiIFJlY2FsbCA9IiwgcmVjYWxsLCAiIEZhbHNlIE5lZ2F0aXZlID0iLCBmYWxzZW5lZykNCmBgYA0KDQojIyA1IC0gQ29uY2x1c2lvbg0KVGhlIHByb2plY3QgcHJvdmlkZWQgYSBudW1iZXIgb2YgaW50ZXJlc3RpbmcgY2hhbGxlbmdlcyBmb3IgdXMuIFRoZSBpbnN0YWxsYXRpb24gYW5kIHNldHVwIG9mIFNwYXJrIHByb3ZlZCB0byBiZSBtb3JlIGNvbXBsaWNhdGVkIHRoZW4gZmlyc3QgZXhwZWN0ZWQuIE9uY2Ugd2Ugd2VyZSBhYmxlIHRvIGdldCBhIGxvY2FsIGluc3RhbmNlIG9mIFNwYXJrIHJ1bm5pbmcgd2UgdGhlbiByYW4gaW50byBzb21lIGNsYXNzaWMgYmlnIGRhdGEgaXNzdWVzLiBUaGUgZmlyc3QgYXR0ZW1wdCB0byBsb2FkIHRoZSB1c2VyLWl0ZW0gbWF0cml4IGludG8gdGhlIHNwYXJrIGNsdXN0ZXIgY2F1c2VkIHRoZSBSIHNlc3Npb24gdG8gY3Jhc2guIFdlIGFsc28gcmFuIGludG8gdGhlIHNhbWUgaXNzdWUgd2hlbiB3ZSBhdHRlbXB0ZWQgdG8gcnVuIHRvIG1hbnkgaXRlcmF0aW9ucyBpbiB0aGUgQUxTIG1vZGVsLiBUaGlzIHdvdWxkIGJlIGltcHJvdmVkIGJ5IG1vdmluZyB0byBhbiBBV1MgaW5zdGFsbGF0aW9uIG9mIFNwYXJrIGJ1dCBpdCBkaWQgbGltaXQgb3VyIHRlc3RpbmcgZm9yIHRoaXMgcHJvamVjdC4gV2UgYWxzbyBzZWUgZnJvbSBldmFsdWF0aW9uIG9mIHRoZSByZWNvbW1lbmRlciBzeXN0ZW0gdGhhdCB3ZSBoYXZlIGFuIGlzc3VlIHdpdGggYW55IHJlY29tbWVuZGF0aW9ucyB0aGF0IGFyZSBub3QgdHJ1ZSBwb3NpdGl2ZXMuICANCg0KRmluYWxseSB3ZSByYW4gaW50byBhIG51bWJlciBvZiBpc3N1ZXMgd2l0aCBhdHRlbXB0aW5nIHRvIHBlcmZvcm0gU1ZEIG9uIHRoZSBkYXRhLiBUaGUgSDJPIHBhY2thZ2UgaW5jbHVkZSB3aXRoICpzcGFya2x5ciogc2hvdWxkIGhhdmUgdGhpcyBmdW5jdGlvbmFsaXR5IGJ1dCB3ZSB3ZXJlIHVuYWJsZSB0byBkZWJ1ZyB0aGUgaXNzdWVzIHdpdGggb3VyIHN5c3RlbSBhbmQgZ2V0IGFueSB0eXBlIG9mIHJlc3VsdCB3aXRoIHRoaXMgbWV0aG9kLiBXZSB3ZXJlIGFibGUgdG8gd29yayBhcm91bmQgdGhpcyBpc3N1ZSBidXQgaGFkIHRvIGltcGxlbWVudCBhIGRpZmZlcmVudCByZWNvbW1lbmRhdGlvbiBhbGdvcml0aG0gdG8gZ2V0IHRoaXMgdG8gd29yay4gIA0KDQpUaGlzIG1ldGhvZCBvZiBidWlsZGluZyB0aGUgcmVjb21tZW5kZXIgaGFkIGEgbnVtYmVyIHByb3MgYW5kIGNvbnMgY29tcGFyZWQgdG8gYnVpbGRpbmcgdGhlIHN5c3RlbSBpbiBsb2NhbCBtZW1vcnkuIFRoZSBBTFMgYWxnb3JpdGhtIHdhcyBhYmxlIHRvIHJ1biB2ZXJ5IHF1aWNrbHkgYW5kIHRoZSBoYXZpbmcgdGhlIGRhdGEgb2ZmbG9hZGVkIHRvIFNwYXJrIHNlZW1lZCB0byBwcm92aWRlIGEgbGFyZ2UgYm9vc3QgaW4gc3BlZWQuIFdlIGFsc28gd2VyZSBhYmxlLCB3aXRoIGEgZmV3IGxpbmVzIG9mIGNvZGUsIGdlbmVyYXRlIGEgdmVyeSBhY2N1cmF0ZSByZWNvbW1lbmRlciB1c2luZyBhIG1vcmUgYWR2YW5jZWQgbWF0aGVtYXRpY2FsIHRlY2huaXF1ZS4gVGhlIHRyYWRlIG9mZiB3YXMgdGhhdCB3ZSBuZWVkZWQgdG8gc3BlbmQgYSBsb3Qgb2YgdGltZSBvbiB0aGUgZnJvbnQgZW5kIHNldHRpbmcgdXAgdGhlIHN5c3RlbSBhbmQgcHJlcGFyaW5nIHRoZSBkYXRhIHRvIG1hdGNoIGEgZm9ybWF0IHRoYXQgYWxnb3JpdGhtIGNvdWxkIHdvcmsgd2l0aC4gVWx0aW1hdGVseSBJIHRoaW5rIHRoYXQgdGhpcyBtZXRob2QgaXMgYSBiZXR0ZXIgYW5kIGZhc3RlciB3YXkgdG8gYnVpbGQgdGhlIHJlY29tbWVuZGVyIG9uY2UgYWxsIGEgZGF0YSBwcmVwYXJhdGlvbiB3b3JrZmxvdyBoYXMgYmVlbiBmaWd1cmVkIG91dC4NCg==