Introduction
The goal of this assignment is give you practice working with accuracy and other recommender system metrics.
In this assignment you’re asked to do at least one or (if you like) both of the following:
• Work in a small group, and/or • Choose a different dataset to work with from your previous projects.
Assignment Highlights
As in your previous assignments, compare the accuracy of at least two recommender system algorithms against your offline data.
Implement support for at least one business or user experience goal such as increased serendipity, novelty, or diversity.
Compare and report on any change in accuracy before and after you’ve made the change in #2.
As part of your textual conclusion, discuss one or more additional experiments that could be performed and/or metrics that could be evaluated only if online evaluation was possible. Also, briefly propose how you would design a reasonable online evaluation environment.
About the Data
For Project 4, I will build on Project 3 using the same MovieLens dataset, and evaluate the models accuracy using RMSE, MSE and MAE.
The MovieLens Latest Small Datasets contain 100,000 ratings and 3,600 tag applications applied to 9,000 movies by 600 users.
Loading the MovieLense Data
## 943 x 1664 rating matrix of class 'realRatingMatrix' with 99392 ratings.
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 19.0 32.0 64.0 105.4 147.5 735.0
## [1] "data" "normalize"
## [1] "realRatingMatrix"
## attr(,"package")
## [1] "recommenderlab"
## [1] "Toy Story (1995)"
## [2] "GoldenEye (1995)"
## [3] "Four Rooms (1995)"
## [4] "Get Shorty (1995)"
## [5] "Copycat (1995)"
## [6] "Shanghai Triad (Yao a yao yao dao waipo qiao) (1995)"
## [7] "Twelve Monkeys (1995)"
## [8] "Babe (1995)"
## [9] "Dead Man Walking (1995)"
## [10] "Richard III (1995)"
Data structure
## Formal class 'realRatingMatrix' [package "recommenderlab"] with 2 slots
## ..@ data :Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
## .. .. ..@ i : int [1:99392] 0 1 4 5 9 12 14 15 16 17 ...
## .. .. ..@ p : int [1:1665] 0 452 583 673 882 968 994 1386 1605 1904 ...
## .. .. ..@ Dim : int [1:2] 943 1664
## .. .. ..@ Dimnames:List of 2
## .. .. .. ..$ : chr [1:943] "1" "2" "3" "4" ...
## .. .. .. ..$ : chr [1:1664] "Toy Story (1995)" "GoldenEye (1995)" "Four Rooms (1995)" "Get Shorty (1995)" ...
## .. .. ..@ x : num [1:99392] 5 4 4 4 4 3 1 5 4 5 ...
## .. .. ..@ factors : list()
## ..@ normalize: NULL
Distribution of ratings

Exploring the ratings values
## [1] 5 4 0 3 1 2
## ratingvalues
## 0 1 2 3 4 5
## 1469760 6059 11307 27002 33947 21077
Excluding the missing values
Distribution of the Ratings

Pre-processing
Convert data to numeric
Create the sparse Matrix
ratingsMat <- sparseMatrix(i = movies$user, j = movies$item, x = movies$rating,
dims = c(length(unique(movies$user)), length(unique(movies$item))),
dimnames = list(paste("u", 1:length(unique(movies$user)), sep = ""),
paste("m", 1:length(unique(movies$item)), sep = "")))
ratingsReal <- new("realRatingMatrix", data = ratingsMat)
ratingsReal
## 943 x 1664 rating matrix of class 'realRatingMatrix' with 99392 ratings.
I’m selecting the Users who have rated at least 100 movies and those movies that have been watched at least 150 times
Modified Distributions


I’m selecting the Users who have rated at least 100 movies and those movies that have been watched at least 150 times
Spliting the Data Set
Use 90% for training the model
## Evaluation scheme with 10 items given
## Method: 'split' with 1 run(s).
## Training set proportion: 0.900
## Good ratings: >=4.000000
## Data set: 358 x 200 rating matrix of class 'realRatingMatrix' with 32713 ratings.
ModelAlgorithms <- list(
"Random" = list(name="RANDOM", param=list(normalize = "Z-score")),
"Popular" = list(name="POPULAR", param=list(normalize = "Z-score")),
"UserBased" = list(name="UBCF", param=list(normalize = "Z-score",
method="Cosine",
nn=50, minRating=3)),
"ItemBased" = list(name="IBCF2", param=list(normalize = "Z-score"
))
)
Algorithms to Predict Ratings
## RANDOM run fold/sample [model time/prediction time]
## 1 [0sec/0.08sec]
## POPULAR run fold/sample [model time/prediction time]
## 1 [0.06sec/0.02sec]
## UBCF run fold/sample [model time/prediction time]
## 1
## Warning: Unknown parameters: minRating
## Available parameter (with default values):
## method = cosine
## nn = 25
## sample = FALSE
## normalize = center
## verbose = FALSE
## [0.01sec/0.07sec]
## IBCF2 run fold/sample [model time/prediction time]
## 1
## Timing stopped at: 0 0 0
## Error in .local(data, ...) :
## Recommender method IBCF2 not implemented for data type realRatingMatrix .
## Warning in .local(x, method, ...):
## Recommender 'ItemBased' has failed and has been removed from the results!
Comparing the Recommender Systems

Popular appears to be the best performing Model with the lowest RMSE, MSE and MAE.
Let’s predict next movies
## RANDOM run fold/sample [model time/prediction time]
## 1 [0.02sec/0.01sec]
## POPULAR run fold/sample [model time/prediction time]
## 1 [0.02sec/0.09sec]
## UBCF run fold/sample [model time/prediction time]
## 1
## Warning: Unknown parameters: minRating
## Available parameter (with default values):
## method = cosine
## nn = 25
## sample = FALSE
## normalize = center
## verbose = FALSE
## [0.01sec/0.16sec]
## IBCF2 run fold/sample [model time/prediction time]
## 1
## Timing stopped at: 0 0 0
## Error in .local(data, ...) :
## Recommender method IBCF2 not implemented for data type realRatingMatrix .
## Warning in .local(x, method, ...):
## Recommender 'ItemBased' has failed and has been removed from the results!
Implement Support for Business/User Experiance Goal
The Popular and UserBased models outperforms the Random, I will create a hybrid model consisting of Popular and UserBased models.
It may not always be desirable to recommend products that are likely to be most highly rated by a user. Recommending somewhat unexpected products may improve user experience, expand user preferences, provide additional knowledge about a user.
Training, Testing and Evaluation Data sets
Prepare training dataset
Prepare testing set
Prepare evaluation set
Recommender Systems
The Popular Based Model
Building the User Based Model using 25 nearest neighbor. Building the user collaborative filtering system to recommend movies to users based on how similar they are with other users.
Making Predictions with newdata = test
Model Evaluation - Popular Model
## RMSE MSE MAE
## 0.9076623 0.8238508 0.7153872
The User Based Model
Building the User Based Model using 25 nearest neighbor. Building the user collaborative filtering system to recommend movies to users based on how similar they are with other users.
Making Predictions with newdata = test
Model Evaluation - User Based
## RMSE MSE MAE
## 0.9604391 0.9224433 0.7627016
The Hybrid Model
Making Predictions with newdata = test
Model Evaluation - Hybrid Model
## RMSE MSE MAE
## 0.9077973 0.8240959 0.7154864
Comprison of the Model Accuracy
|
|
RMSE
|
MSE
|
MAE
|
|
Popular
|
0.908
|
0.824
|
0.715
|
|
UserBased
|
0.960
|
0.922
|
0.763
|
|
HYBRID
|
0.908
|
0.824
|
0.715
|
The prediction accuracy of the Hybrid Model is similar to the accuracy of the Popular Model. Based on a blend of the two models, the RMSE, MSE and MAE of the Hybrid model decreased slightly in accuracy.
Conclusion
The purpose of this exercise was to build a recommender system and to practice working with accuracy and other recommender system metrics.
For the given data set, POPULAR and the Userbased Models within recommenderlab provided the best results. It is worth remembering that the choice of the recommender algorithm depends on the context. A Hybrid of both models provided performance accuarcy similar to the Popular Model.
In addition to the methods provided within the recommenderlab library, there are other supervised and unsupervised machine learning algorithm as well as neutral network-driven deep learning algorithms that could be used to build recommendation systems.
References
Breese JS, Heckerman D, Kadie C (1998). “Empirical Analysis of Predictive Algorithms for Collaborative Filtering.” In Uncertainty in Artificial Intelligence. Proceedings of the Fourteenth Conference, pp. 43-52.
Kohavi, Ron (1995). “A study of cross-validation and bootstrap for accuracy estimation and model selection”. Proceedings of the Fourteenth International Joint Conference on Artificial Intelligence, pp. 1137-1143.
Koren, Y., Bell, R., & Volinsky, C. (2009). Matrix Factorization Techniques for Recommender Systems. Computer, 42(8), 30–37. https://doi.org/10.1109/MC.2009.263
Michael Hahsler (2016). recommenderlab: A Framework for Developing and Testing Recommendation Algorithms, R package. https://CRAN.R-project.org/package=recommenderlab
LS0tDQp0aXRsZTogIkRhdGEgNjEyIC0gUHJvamVjdCA0Ig0KYXV0aG9yOiBFbW1hbnVlbCBIYXlibGUtR29tZXMNCmRhdGU6ICIwNi8zMC8yMDIwIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KLS0tDQoNCiMgSW50cm9kdWN0aW9uDQoNClRoZSBnb2FsIG9mIHRoaXMgYXNzaWdubWVudCBpcyBnaXZlIHlvdSBwcmFjdGljZSB3b3JraW5nIHdpdGggYWNjdXJhY3kgYW5kIG90aGVyIHJlY29tbWVuZGVyIHN5c3RlbSBtZXRyaWNzLg0KDQpJbiB0aGlzIGFzc2lnbm1lbnQgeW914oCZcmUgYXNrZWQgdG8gZG8gYXQgbGVhc3Qgb25lIG9yIChpZiB5b3UgbGlrZSkgYm90aCBvZiB0aGUgZm9sbG93aW5nOg0KDQogICDigKIgV29yayBpbiBhIHNtYWxsIGdyb3VwLCBhbmQvb3INCiAgIOKAoiBDaG9vc2UgYSBkaWZmZXJlbnQgZGF0YXNldCB0byB3b3JrIHdpdGggZnJvbSB5b3VyIHByZXZpb3VzIHByb2plY3RzLg0KDQojIEFzc2lnbm1lbnQgSGlnaGxpZ2h0cw0KDQoxLiBBcyBpbiB5b3VyIHByZXZpb3VzIGFzc2lnbm1lbnRzLCBjb21wYXJlIHRoZSBhY2N1cmFjeSBvZiBhdCBsZWFzdCB0d28gcmVjb21tZW5kZXIgc3lzdGVtIGFsZ29yaXRobXMgYWdhaW5zdCAgICB5b3VyIG9mZmxpbmUgZGF0YS4NCg0KMi4gSW1wbGVtZW50IHN1cHBvcnQgZm9yIGF0IGxlYXN0IG9uZSBidXNpbmVzcyBvciB1c2VyIGV4cGVyaWVuY2UgZ29hbCBzdWNoIGFzIGluY3JlYXNlZCBzZXJlbmRpcGl0eSwgbm92ZWx0eSwgICAgb3IgZGl2ZXJzaXR5Lg0KDQozLiBDb21wYXJlIGFuZCByZXBvcnQgb24gYW55IGNoYW5nZSBpbiBhY2N1cmFjeSBiZWZvcmUgYW5kIGFmdGVyIHlvdeKAmXZlIG1hZGUgdGhlIGNoYW5nZSBpbiAjMi4NCg0KNC4gQXMgcGFydCBvZiB5b3VyIHRleHR1YWwgY29uY2x1c2lvbiwgZGlzY3VzcyBvbmUgb3IgbW9yZSBhZGRpdGlvbmFsIGV4cGVyaW1lbnRzIHRoYXQgY291bGQgYmUgcGVyZm9ybWVkICAgICAgICBhbmQvb3IgbWV0cmljcyB0aGF0IGNvdWxkIGJlIGV2YWx1YXRlZCBvbmx5IGlmIG9ubGluZSBldmFsdWF0aW9uIHdhcyBwb3NzaWJsZS4gQWxzbywgYnJpZWZseSBwcm9wb3NlIGhvdyAgICAgIHlvdSB3b3VsZCBkZXNpZ24gYSByZWFzb25hYmxlIG9ubGluZSBldmFsdWF0aW9uIGVudmlyb25tZW50Lg0KDQojIEFib3V0IHRoZSBEYXRhDQoNCkZvciBQcm9qZWN0IDQsIEkgd2lsbCBidWlsZCBvbiBQcm9qZWN0IDMgdXNpbmcgdGhlIHNhbWUgTW92aWVMZW5zIGRhdGFzZXQsIGFuZCBldmFsdWF0ZSB0aGUgbW9kZWxzIGFjY3VyYWN5IHVzaW5nIFJNU0UsIE1TRSBhbmQgTUFFLg0KDQpUaGUgTW92aWVMZW5zIExhdGVzdCBTbWFsbCBEYXRhc2V0cyBjb250YWluIDEwMCwwMDAgcmF0aW5ncyBhbmQgMyw2MDAgdGFnIGFwcGxpY2F0aW9ucyBhcHBsaWVkIHRvIDksMDAwIG1vdmllcyBieSA2MDAgdXNlcnMuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkocmVjb21tZW5kZXJsYWIpDQpgYGANCg0KIyMgTG9hZGluZyB0aGUgTW92aWVMZW5zZSBEYXRhDQoNCmBgYHtyfQ0KZGF0YSAoTW92aWVMZW5zZSkNCk1vdmllTGVuc2UNCmBgYA0KDQoNCmBgYHtyfQ0Kc3VtbWFyeShyb3dDb3VudHMoTW92aWVMZW5zZSkpDQpgYGANCg0KYGBge3J9DQpzbG90TmFtZXMoTW92aWVMZW5zZSkNCmNsYXNzKE1vdmllTGVuc2UpDQpoZWFkKGNvbG5hbWVzKE1vdmllTGVuc2UpLDEwKQ0KYGBgDQoNCiMjIERhdGEgc3RydWN0dXJlDQoNCmBgYHtyfQ0Kc3RyKE1vdmllTGVuc2UpDQpgYGANCg0KIyMgRGlzdHJpYnV0aW9uIG9mIHJhdGluZ3MNCg0KYGBge3J9DQpoaXN0KHJvd0NvdW50cyhNb3ZpZUxlbnNlKSwgDQogICAgIGJyZWFrcyA9IDIwLCANCiAgICAgbWFpbiA9ICJSYXcgRGlzdHJpYnV0aW9uIG9mIFVzZXIncyBSYXRpbmciDQogICAgICkNCmBgYA0KDQojIyBFeHBsb3JpbmcgdGhlIHJhdGluZ3MgdmFsdWVzDQoNCmBgYHtyfQ0KcmF0aW5ndmFsdWVzIDwtIGFzLnZlY3RvcihNb3ZpZUxlbnNlQGRhdGEpDQp1bmlxdWUocmF0aW5ndmFsdWVzKSAjIFRoZSByYXRpbmcgaXMgbnVtZXJpYyB3aXRoIHRoZSBsZWFzdCB2YWx1ZSBhcyAwLjUgYW5kIHRoZSBoaWdoZXN0IHZhbHVlcyBhcyA1Lg0KDQp0YWJsZXJhdGluZyA8LSB0YWJsZShyYXRpbmd2YWx1ZXMpDQp0YWJsZXJhdGluZw0KYGBgDQoNCiMjIEV4Y2x1ZGluZyB0aGUgbWlzc2luZyB2YWx1ZXMNCg0KYGBge3J9DQpyYXRpbmd2YWx1ZXMgPC0gcmF0aW5ndmFsdWVzW3JhdGluZ3ZhbHVlcyAhPTBdDQpgYGANCg0KDQojIyBEaXN0cmlidXRpb24gb2YgdGhlIFJhdGluZ3MNCg0KYGBge3J9DQpoaXN0KHJhdGluZ3ZhbHVlcywgDQogICAgIGJyZWFrcyA9IDUsIA0KICAgICBtYWluPSJEaXN0cmlidXRpb24gb2YgUmF0aW5ncyIsDQogICAgIHhsYWI9IlJhdGluZ3MiLA0KICAgICBjb2w9ImdyZXkiLA0KICAgICBmcmVxPVRSVUUNCiAgICAgKQ0KYGBgDQoNCiMgUHJlLXByb2Nlc3NpbmcNCg0KKipDb252ZXJ0IGRhdGEgdG8gbnVtZXJpYyoqDQoNCmBgYHtyfQ0KbW92aWVzIDwtIGFzKE1vdmllTGVuc2UsICdkYXRhLmZyYW1lJykNCm1vdmllcyR1c2VyIDwtIGFzLm51bWVyaWMobW92aWVzJHVzZXIpDQptb3ZpZXMkaXRlbSA8LSBhcy5udW1lcmljKG1vdmllcyRpdGVtKQ0KYGBgDQoNCiMjIENyZWF0ZSB0aGUgc3BhcnNlIE1hdHJpeA0KDQpgYGB7cn0NCnJhdGluZ3NNYXQgPC0gc3BhcnNlTWF0cml4KGkgPSBtb3ZpZXMkdXNlciwgaiA9IG1vdmllcyRpdGVtLCB4ID0gbW92aWVzJHJhdGluZywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltcyA9IGMobGVuZ3RoKHVuaXF1ZShtb3ZpZXMkdXNlcikpLCBsZW5ndGgodW5pcXVlKG1vdmllcyRpdGVtKSkpLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KHBhc3RlKCJ1IiwgMTpsZW5ndGgodW5pcXVlKG1vdmllcyR1c2VyKSksIHNlcCA9ICIiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCJtIiwgMTpsZW5ndGgodW5pcXVlKG1vdmllcyRpdGVtKSksIHNlcCA9ICIiKSkpDQoNCnJhdGluZ3NSZWFsIDwtIG5ldygicmVhbFJhdGluZ01hdHJpeCIsIGRhdGEgPSByYXRpbmdzTWF0KQ0KcmF0aW5nc1JlYWwNCmBgYA0KDQoqKkknbSBzZWxlY3RpbmcgdGhlIFVzZXJzIHdobyBoYXZlIHJhdGVkIGF0IGxlYXN0IDEwMCBtb3ZpZXMgYW5kIHRob3NlIG1vdmllcyB0aGF0IGhhdmUgYmVlbiB3YXRjaGVkIGF0IGxlYXN0IDE1MCB0aW1lcyoqDQoNCmBgYHtyfQ0KcmF0aW5ncyA8LSBNb3ZpZUxlbnNlW3Jvd0NvdW50cyhNb3ZpZUxlbnNlKSA+IDEwMCwgY29sQ291bnRzKE1vdmllTGVuc2UpID4gMTUwXQ0KYGBgDQoNCiMjIE1vZGlmaWVkIERpc3RyaWJ1dGlvbnMNCg0KYGBge3J9DQpoaXN0KHJvd0NvdW50cyhyYXRpbmdzKSwgDQogICAgIGJyZWFrcyA9IDIwLCANCiAgICAgbWFpbiA9ICJNb2RpZmllZCBEaXN0cmlidXRpb24gb2YgVXNlciBSYXRpbmciDQogICAgICkNCg0KaGlzdChjb2xDb3VudHMocmF0aW5ncyksIA0KICAgICBicmVha3MgPSAyMCwgDQogICAgIG1haW4gPSAiTW9kaWZpZWQgRGlzdHJpYnV0aW9uIG9mIE1vdmllIFJhdGluZyINCiAgICAgKQ0KYGBgDQoNCioqSSdtIHNlbGVjdGluZyB0aGUgVXNlcnMgd2hvIGhhdmUgcmF0ZWQgYXQgbGVhc3QgMTAwIG1vdmllcyBhbmQgdGhvc2UgbW92aWVzIHRoYXQgaGF2ZSBiZWVuIHdhdGNoZWQgYXQgbGVhc3QgMTUwIHRpbWVzKioNCg0KYGBge3J9DQpyYXRpbmdzIDwtIE1vdmllTGVuc2Vbcm93Q291bnRzKE1vdmllTGVuc2UpID4gMTAwLCBjb2xDb3VudHMoTW92aWVMZW5zZSkgPiAxNTBdDQpgYGANCg0KDQojIFNwbGl0aW5nIHRoZSBEYXRhIFNldA0KDQpVc2UgOTAlIGZvciB0cmFpbmluZyB0aGUgbW9kZWwNCg0KYGBge3J9DQpTY2hlbWUgPC0gZXZhbHVhdGlvblNjaGVtZShyYXRpbmdzLCBtZXRob2QgPSAic3BsaXQiLCBrID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluID0gMC45LCBnaXZlbj0gMTAsIGdvb2RSYXRpbmc9NCkNCmBgYA0KDQoNCmBgYHtyfQ0KU2NoZW1lDQpgYGANCg0KDQpgYGB7cn0NCk1vZGVsQWxnb3JpdGhtcyA8LSBsaXN0KA0KICAiUmFuZG9tIiA9IGxpc3QobmFtZT0iUkFORE9NIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiWi1zY29yZSIpKSwNCiAgIlBvcHVsYXIiID0gbGlzdChuYW1lPSJQT1BVTEFSIiwgcGFyYW09bGlzdChub3JtYWxpemUgPSAiWi1zY29yZSIpKSwNCiAgIlVzZXJCYXNlZCIgPSBsaXN0KG5hbWU9IlVCQ0YiLCBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJaLXNjb3JlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2Q9IkNvc2luZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm49NTAsIG1pblJhdGluZz0zKSksDQogICJJdGVtQmFzZWQiID0gbGlzdChuYW1lPSJJQkNGMiIsIHBhcmFtPWxpc3Qobm9ybWFsaXplID0gIlotc2NvcmUiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkNCiAgDQogICkNCmBgYA0KDQojIEFsZ29yaXRobXMgdG8gUHJlZGljdCBSYXRpbmdzDQoNCmBgYHtyfQ0KTW9kZWxSZXN1bHRzIDwtIGV2YWx1YXRlKFNjaGVtZSwgTW9kZWxBbGdvcml0aG1zLCB0eXBlID0gInJhdGluZ3MiKQ0KYGBgDQoNCiMjICoqQ29tcGFyaW5nIHRoZSBSZWNvbW1lbmRlciBTeXN0ZW1zKioNCg0KYGBge3J9DQpwbG90KE1vZGVsUmVzdWx0cykNCnRpdGxlKG1haW4gPSAiTW9kZWwgRXJyb3IgQ29tcGFyaXNvbiIpDQpgYGANCg0KUG9wdWxhciBhcHBlYXJzIHRvIGJlIHRoZSBiZXN0IHBlcmZvcm1pbmcgTW9kZWwgd2l0aCB0aGUgbG93ZXN0IFJNU0UsIE1TRSBhbmQgTUFFLg0KDQojIyBMZXQncyBwcmVkaWN0IG5leHQgbW92aWVzDQoNCmBgYHtyfQ0KTW9kZWxSZXN1bHRzIDwtIGV2YWx1YXRlKFNjaGVtZSwgTW9kZWxBbGdvcml0aG1zLCBuPWMoMSwgNSwgMTAsIDE1LCAyMCwgMjUpKQ0KYGBgDQoNCiMgTW9kZWwgTWV0cmljcy9QZXJmb3JtYW5jZQ0KDQojIyBST0MgY3VydmUNCg0KYGBge3J9DQpwbG90KE1vZGVsUmVzdWx0cywgYW5ub3RhdGUgPSBUUlVFLCBsZWdlbmQgPSAidG9wbGVmdCIpDQp0aXRsZSgiUk9DIEN1cnZlIikNCmBgYA0KDQojIyBQcmVjaXNpb24gYW5kIFJlY2FsbA0KDQpgYGB7cn0NCnBsb3QoTW9kZWxSZXN1bHRzLCAicHJlYy9yZWMiLCBhbm5vdGF0ZSA9IFRSVUUpICMgcHJlY2lzaW9uIGFuZCByZWNhbGwNCnRpdGxlKCJQcmVjaXNpb24gYW5kIFJlY2FsbCIpDQpgYGANCg0KDQpUaGUgUG9wdWxhciBNb2RlbCBwZXJmb3JtcyBiZXR0ZXIgdGhhbiBSYW5kb20gYW5kIHRoZSBVc2VyIEJhc2VkIG1vZGVsLg0KDQoNCiMgSW1wbGVtZW50IFN1cHBvcnQgZm9yIEJ1c2luZXNzL1VzZXIgRXhwZXJpYW5jZSBHb2FsDQoNClRoZSBQb3B1bGFyIGFuZCBVc2VyQmFzZWQgbW9kZWxzIG91dHBlcmZvcm1zIHRoZSBSYW5kb20sIEkgd2lsbCBjcmVhdGUgYSBoeWJyaWQgbW9kZWwgY29uc2lzdGluZyBvZiBQb3B1bGFyIGFuZCBVc2VyQmFzZWQgbW9kZWxzLg0KDQpJdCBtYXkgbm90IGFsd2F5cyBiZSBkZXNpcmFibGUgdG8gcmVjb21tZW5kIHByb2R1Y3RzIHRoYXQgYXJlIGxpa2VseSB0byBiZSBtb3N0IGhpZ2hseSByYXRlZCBieSBhIHVzZXIuIFJlY29tbWVuZGluZyBzb21ld2hhdCB1bmV4cGVjdGVkIHByb2R1Y3RzIG1heSBpbXByb3ZlIHVzZXIgZXhwZXJpZW5jZSwgZXhwYW5kIHVzZXIgcHJlZmVyZW5jZXMsIHByb3ZpZGUgYWRkaXRpb25hbCBrbm93bGVkZ2UgYWJvdXQgYSB1c2VyLg0KDQojIyBUcmFpbmluZywgVGVzdGluZyBhbmQgRXZhbHVhdGlvbiBEYXRhIHNldHMNCg0KKipQcmVwYXJlIHRyYWluaW5nIGRhdGFzZXQqKg0KDQpgYGB7cn0NCnRyYWluIDwtIGdldERhdGEoU2NoZW1lLCAidHJhaW4iKQ0KYGBgDQoNCioqUHJlcGFyZSB0ZXN0aW5nIHNldCoqDQoNCmBgYHtyfQ0KdGVzdCA8LSBnZXREYXRhKFNjaGVtZSwgImtub3duIikNCmBgYA0KDQoqKlByZXBhcmUgZXZhbHVhdGlvbiBzZXQqKg0KDQpgYGB7cn0NCmV2YWx1YXRpb24gPC0gZ2V0RGF0YShTY2hlbWUsICJ1bmtub3duIikNCmBgYA0KDQojIFJlY29tbWVuZGVyIFN5c3RlbXMNCg0KIyMgVGhlIFBvcHVsYXIgQmFzZWQgTW9kZWwNCg0KQnVpbGRpbmcgdGhlIFVzZXIgQmFzZWQgTW9kZWwgdXNpbmcgMjUgbmVhcmVzdCBuZWlnaGJvci4gQnVpbGRpbmcgdGhlIHVzZXIgY29sbGFib3JhdGl2ZSBmaWx0ZXJpbmcgc3lzdGVtIHRvIHJlY29tbWVuZCBtb3ZpZXMgdG8gdXNlcnMgYmFzZWQgb24gaG93IHNpbWlsYXIgdGhleSBhcmUgd2l0aCBvdGhlciB1c2Vycy4NCg0KYGBge3J9DQpQb3B1bGFybW9kZWwgPC0gUmVjb21tZW5kZXIodHJhaW4sIG1ldGhvZCA9ICJQT1BVTEFSIikNCmBgYA0KDQojIyMgTWFraW5nIFByZWRpY3Rpb25zIHdpdGggbmV3ZGF0YSA9IHRlc3QNCg0KYGBge3J9DQpQb3B1bGFycHJlZCA8LSBwcmVkaWN0KFBvcHVsYXJtb2RlbCwgbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAicmF0aW5ncyIpDQpgYGANCg0KIyMjIE1vZGVsIEV2YWx1YXRpb24gLSBQb3B1bGFyIE1vZGVsDQoNCmBgYHtyfQ0KUG9wdWxhcmFjY3VyYWN5IDwtIGNhbGNQcmVkaWN0aW9uQWNjdXJhY3koUG9wdWxhcnByZWQsIGV2YWx1YXRpb24pDQpQb3B1bGFyYWNjdXJhY3kNCmBgYA0KDQoNCiMjIFRoZSBVc2VyIEJhc2VkIE1vZGVsDQoNCkJ1aWxkaW5nIHRoZSBVc2VyIEJhc2VkIE1vZGVsIHVzaW5nIDI1IG5lYXJlc3QgbmVpZ2hib3IuIEJ1aWxkaW5nIHRoZSB1c2VyIGNvbGxhYm9yYXRpdmUgZmlsdGVyaW5nIHN5c3RlbSB0byByZWNvbW1lbmQgbW92aWVzIHRvIHVzZXJzIGJhc2VkIG9uIGhvdyBzaW1pbGFyIHRoZXkgYXJlIHdpdGggb3RoZXIgdXNlcnMuDQoNCmBgYHtyfQ0KVXNlcm1vZGVsIDwtIFJlY29tbWVuZGVyKHRyYWluLCBtZXRob2QgPSAiVUJDRiIsIA0KICAgICAgICAgICAgICAgICAgICAgcGFyYW09bGlzdChub3JtYWxpemUgPSAiY2VudGVyIiwgbWV0aG9kPSJDb3NpbmUiLCBubj0yNSkpDQpgYGANCg0KIyMjIE1ha2luZyBQcmVkaWN0aW9ucyB3aXRoIG5ld2RhdGEgPSB0ZXN0DQoNCmBgYHtyfQ0KVXNlcnByZWQgPC0gcHJlZGljdChVc2VybW9kZWwsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gInJhdGluZ3MiKQ0KYGBgDQoNCiMjIyBNb2RlbCBFdmFsdWF0aW9uIC0gVXNlciBCYXNlZA0KDQpgYGB7cn0NClVzZXJhY2N1cmFjeSA8LSBjYWxjUHJlZGljdGlvbkFjY3VyYWN5KFVzZXJwcmVkLCBldmFsdWF0aW9uKQ0KVXNlcmFjY3VyYWN5DQpgYGANCg0KIyMgVGhlIEh5YnJpZCBNb2RlbA0KDQpgYGB7cn0NCkh5YnJpZE1vZGVsIDwtIEh5YnJpZFJlY29tbWVuZGVyKFBvcHVsYXJtb2RlbCwgVXNlcm1vZGVsLCB3ZWlnaHRzID0gYygwLjk5LCANCiAgICAwLjAxKSkNCmBgYA0KDQojIyMgTWFraW5nIFByZWRpY3Rpb25zIHdpdGggbmV3ZGF0YSA9IHRlc3QNCg0KYGBge3J9DQpwcmVkSHlicmlkIDwtIHByZWRpY3QoSHlicmlkTW9kZWwsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gInJhdGluZ3MiKQ0KYGBgDQoNCiMjIyBNb2RlbCBFdmFsdWF0aW9uIC0gSHlicmlkIE1vZGVsDQoNCmBgYHtyfQ0KYWNjdXJhY3lIeWJyaWQgPC0gY2FsY1ByZWRpY3Rpb25BY2N1cmFjeShwcmVkSHlicmlkLCBldmFsdWF0aW9uKQ0KYWNjdXJhY3lIeWJyaWQNCmBgYA0KDQojIENvbXByaXNvbiBvZiB0aGUgTW9kZWwgQWNjdXJhY3kNCg0KYGBge3J9DQpNb2RlbEVycm9yIDwtIHJiaW5kKA0KICAgIFBvcHVsYXIgPSBjYWxjUHJlZGljdGlvbkFjY3VyYWN5KFBvcHVsYXJwcmVkLCBldmFsdWF0aW9uKSwNCiAgICBVc2VyQmFzZWQgPSBjYWxjUHJlZGljdGlvbkFjY3VyYWN5KFVzZXJwcmVkLCBldmFsdWF0aW9uKSwNCiAgICBIWUJSSUQgPSBjYWxjUHJlZGljdGlvbkFjY3VyYWN5KHByZWRIeWJyaWQsIGV2YWx1YXRpb24pDQogICAgKQ0KTW9kZWxFcnJvciAlPiUgcm91bmQoMykgJT4lIA0Ka2FibGUoKSU+JQ0Ka2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiKSkNCmBgYA0KDQpUaGUgcHJlZGljdGlvbiBhY2N1cmFjeSBvZiB0aGUgSHlicmlkIE1vZGVsIGlzIHNpbWlsYXIgdG8gdGhlIGFjY3VyYWN5IG9mIHRoZSBQb3B1bGFyIE1vZGVsLiBCYXNlZCBvbiBhIGJsZW5kIG9mIHRoZSB0d28gbW9kZWxzLCB0aGUgUk1TRSwgTVNFIGFuZCBNQUUgb2YgdGhlIEh5YnJpZCBtb2RlbCBkZWNyZWFzZWQgc2xpZ2h0bHkgaW4gYWNjdXJhY3kuDQoNCiMgQ29uY2x1c2lvbg0KDQpUaGUgcHVycG9zZSBvZiB0aGlzIGV4ZXJjaXNlIHdhcyB0byBidWlsZCBhIHJlY29tbWVuZGVyIHN5c3RlbSBhbmQgdG8gcHJhY3RpY2Ugd29ya2luZyB3aXRoIGFjY3VyYWN5IGFuZCBvdGhlciByZWNvbW1lbmRlciBzeXN0ZW0gbWV0cmljcy4NCg0KRm9yIHRoZSBnaXZlbiBkYXRhIHNldCwgUE9QVUxBUiBhbmQgdGhlIFVzZXJiYXNlZCBNb2RlbHMgd2l0aGluIHJlY29tbWVuZGVybGFiIHByb3ZpZGVkIHRoZSBiZXN0IHJlc3VsdHMuIEl0IGlzIHdvcnRoIHJlbWVtYmVyaW5nIHRoYXQgdGhlIGNob2ljZSBvZiB0aGUgcmVjb21tZW5kZXIgYWxnb3JpdGhtIGRlcGVuZHMgb24gdGhlIGNvbnRleHQuIEEgSHlicmlkIG9mIGJvdGggbW9kZWxzIHByb3ZpZGVkIHBlcmZvcm1hbmNlIGFjY3VhcmN5IHNpbWlsYXIgdG8gdGhlIFBvcHVsYXIgTW9kZWwuDQoNCkluIGFkZGl0aW9uIHRvIHRoZSBtZXRob2RzIHByb3ZpZGVkIHdpdGhpbiB0aGUgcmVjb21tZW5kZXJsYWIgbGlicmFyeSwgdGhlcmUgYXJlIG90aGVyIHN1cGVydmlzZWQgYW5kIHVuc3VwZXJ2aXNlZCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSBhcyB3ZWxsIGFzIG5ldXRyYWwgbmV0d29yay1kcml2ZW4gZGVlcCBsZWFybmluZyBhbGdvcml0aG1zIHRoYXQgY291bGQgYmUgdXNlZCB0byBidWlsZCByZWNvbW1lbmRhdGlvbiBzeXN0ZW1zLg0KDQoNCiMjIyAqKlJlZmVyZW5jZXMqKg0KDQpCcmVlc2UgSlMsIEhlY2tlcm1hbiBELCBLYWRpZSBDICgxOTk4KS4gIkVtcGlyaWNhbCBBbmFseXNpcyBvZiBQcmVkaWN0aXZlIEFsZ29yaXRobXMgZm9yIENvbGxhYm9yYXRpdmUgRmlsdGVyaW5nLiIgSW4gVW5jZXJ0YWludHkgaW4gQXJ0aWZpY2lhbCBJbnRlbGxpZ2VuY2UuIFByb2NlZWRpbmdzIG9mIHRoZSBGb3VydGVlbnRoIENvbmZlcmVuY2UsIHBwLiA0My01Mi4NCg0KS29oYXZpLCBSb24gKDE5OTUpLiAiQSBzdHVkeSBvZiBjcm9zcy12YWxpZGF0aW9uIGFuZCBib290c3RyYXAgZm9yIGFjY3VyYWN5IGVzdGltYXRpb24gYW5kIG1vZGVsIHNlbGVjdGlvbiIuIFByb2NlZWRpbmdzIG9mIHRoZSBGb3VydGVlbnRoIEludGVybmF0aW9uYWwgSm9pbnQgQ29uZmVyZW5jZSBvbiBBcnRpZmljaWFsIEludGVsbGlnZW5jZSwgcHAuIDExMzctMTE0My4NCg0KS29yZW4sIFkuLCBCZWxsLCBSLiwgJiBWb2xpbnNreSwgQy4gKDIwMDkpLiBNYXRyaXggRmFjdG9yaXphdGlvbiBUZWNobmlxdWVzIGZvciBSZWNvbW1lbmRlciBTeXN0ZW1zLiBDb21wdXRlciwgNDIoOCksIDMw4oCTMzcuIGh0dHBzOi8vZG9pLm9yZy8xMC4xMTA5L01DLjIwMDkuMjYzDQoNCk1pY2hhZWwgSGFoc2xlciAoMjAxNikuIHJlY29tbWVuZGVybGFiOiBBIEZyYW1ld29yayBmb3IgRGV2ZWxvcGluZyBhbmQgVGVzdGluZyBSZWNvbW1lbmRhdGlvbiBBbGdvcml0aG1zLCBSIHBhY2thZ2UuIGh0dHBzOi8vQ1JBTi5SLXByb2plY3Qub3JnL3BhY2thZ2U9cmVjb21tZW5kZXJsYWINCg==