0.1 Introduction

The goal of this assignment is to provide practice working with Matrix Factorization techniques.

The task is to implement a matrix factorization method—such as singular value decomposition (SVD) or Alternating Least Squares (ALS)—in the context of a recommender system.

You may approach this assignment in a number of ways. You are welcome to start with an existing recommender system written by yourself or someone else. Remember as always to cite your sources, so that you can be graded on what you added, not what you found.

SVD can be thought of as a pre-processing step for feature engineering. You might easily start with thousands or millions of items, and use SVD to create a much smaller set of “k” items (e.g. 20 or 70).

0.1.1 Assignment Highlights

SVD builds features that may or may not map neatly to items (such as movie genres or news topics). As in many areas of machine learning, the lack of explainability can be an issue).

SVD requires that there are no missing values. There are various ways to handle this, including (1) imputation of missing values, (2) mean-centering values around 0, or (3) using a more advance technique, such as stochastic gradient descent to simulate SVD in populating the factored matrices.

Calculating the SVD matrices can be computationally expensive, although calculating ratings once the factorization is completed is very fast. You may need to create a subset of your data for SVD calculations to be successfully performed, especially on a machine with a small RAM footprint.

0.1.2 About the Data

For Project 3, I will build on Project 2 using the same MovieLens dataset, and evaluate the models 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.

0.1.3 Loading the MovieLense Data

## [1] "data"      "normalize"
## [1] "realRatingMatrix"
## attr(,"package")
## [1] "recommenderlab"
## [1]  943 1664

0.1.4 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

0.1.5 Exploring the ratings values

## [1] 5 4 0 3 1 2
## ratingvalues
##       0       1       2       3       4       5 
## 1469760    6059   11307   27002   33947   21077

0.1.6 Excluding the missing values

0.2 Create Training and Testing sets

Use 80% for training the model

Prepare training dataset

Prepare testing set

Prepare evaluation set

0.3 User-User Collaborative Filtering

The User based collaborative filtering algorithms are based on measuring the similarity between users. A “Recommender” object is then given the “UBCF” (User-based collaborative filter), with a center normalization, cosine method, with 25 nearest neighbors.But first, I will compute the similarity matrix.

0.3.1 Similarity Matrix

A similarity matrix is a recommenderlab function that takes the “realRatingMatrix”" and calculates a cosine similarity which aids in the investigation of model development.

0.3.2 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.

0.3.3 Making Predictions with newdata = test

0.4 Model Evaluation - User Based

##      RMSE       MSE       MAE 
## 0.9420550 0.8874676 0.7463825

0.5 item-item Collaborative Filtering

The Item based collaborative filtering algorithms are based on measuring the similarity between items A “Recommender” object is then given the “IBCF” (Item-based collaborative filter), with a center normalization, cosine method, with K = 250.But first, I will compute the similarity matrix.

0.5.1 Similarity Matrix

A similarity matrix is a recommenderlab function that takes the “realRatingMatrix”" and calculates a cosine similarity which aids in the investigation of model development.

The diagonal is yellow because it’s comparing each items with itself.

0.5.2 The Item Based Model

Building the item-item collaborative filtering system to recommend movies to users where their item’s ratings are similar.

0.5.3 Making Predictions with newdata = test

0.6 Model Evaluation - Item Based

##      RMSE       MSE       MAE 
## 1.1347000 1.2875440 0.8856329

0.7 Singular Value Decomposition Model(SVD Model)

Singular Value Decomposition (SVD) will enable us to uncover hiden features in a ratings matrix which are generated from the collective behavior of users. The SVD uses dimensional reduction which can minimize the problem of overfitting to provide robust and compact representations of the data.

0.7.1 SVD Model

I will generate an SVD model with a much smaller set of “k” items (70). It will contain all the required information and I will compare to the Item and User Based Models.

0.7.2 Making Predictions with newdata = test

0.8 Model Evaluation - User Based

##      RMSE       MSE       MAE 
## 0.9648359 0.9309084 0.7649963

0.9 Model Performance

0.9.1 Comparing the Recommender Systems

##         ModelName Model_RMSE Model_MSE Model_MAE
## 1 UserBased Model      0.942     0.887     0.747
## 2 ItemBased Model      1.135     1.288     0.886
## 3       SVD Model      0.965     0.931     0.765

0.10 Conclusion

The RMSE is a measure of the differences between values predicted by a model and the values observed.The RMSE is a good measure to use if we want to estimate the standard deviation of a typical observed value from the model’s prediction, assuming that the observed data can be decomposed.

If the noise is small, as estimated by RMSE, this generally means that the model is good at predicting the observed data, and if RMSE is large, this generally means the model is failing to account for important features underlying the data.

The Metrics shows that the RMSE, MSE and MAE for the User based Recommender system is slightly better in performance compared to the Item Based and the SVD systems. The RMSE for the User based system is lower than the other systems, the MSE and MAE for this system is also lower.

The lower RMSE shows that a collaborative filtering model may be more effective at predicting items based on ratings than the SVD. Although using a larger value of k may improve the performance, it is important identify other possible applications of the SVD.

The disadvantage of SVD is that there is little explanation why an item was recommended since similarity matrix was not calculated. This can be problematic if users are eager to know why a specific item was recommended.

0.10.1 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

LS0tDQp0aXRsZTogIkRhdGEgNjEyIC0gUHJvamVjdCAzIg0KYXV0aG9yOiBFbW1hbnVlbCBIYXlibGUtR29tZXMNCmRhdGU6ICIwNi8yMi8yMDIwIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KLS0tDQoNCiMjIEludHJvZHVjdGlvbg0KDQpUaGUgZ29hbCBvZiB0aGlzIGFzc2lnbm1lbnQgaXMgdG8gcHJvdmlkZSBwcmFjdGljZSB3b3JraW5nIHdpdGggTWF0cml4IEZhY3Rvcml6YXRpb24gdGVjaG5pcXVlcy4NCg0KVGhlIHRhc2sgaXMgdG8gaW1wbGVtZW50IGEgbWF0cml4IGZhY3Rvcml6YXRpb24gbWV0aG9k4oCUc3VjaCBhcyBzaW5ndWxhciB2YWx1ZSBkZWNvbXBvc2l0aW9uIChTVkQpIG9yIEFsdGVybmF0aW5nIExlYXN0IFNxdWFyZXMgKEFMUynigJRpbiB0aGUgY29udGV4dCBvZiBhIHJlY29tbWVuZGVyIHN5c3RlbS4NCg0KWW91IG1heSBhcHByb2FjaCB0aGlzIGFzc2lnbm1lbnQgaW4gYSBudW1iZXIgb2Ygd2F5cy4gWW91IGFyZSB3ZWxjb21lIHRvIHN0YXJ0IHdpdGggYW4gZXhpc3RpbmcgcmVjb21tZW5kZXIgc3lzdGVtIHdyaXR0ZW4gYnkgeW91cnNlbGYgb3Igc29tZW9uZSBlbHNlLiBSZW1lbWJlciBhcyBhbHdheXMgdG8gY2l0ZSB5b3VyIHNvdXJjZXMsIHNvIHRoYXQgeW91IGNhbiBiZSBncmFkZWQgb24gd2hhdCB5b3UgYWRkZWQsIG5vdCB3aGF0IHlvdSBmb3VuZC4NCg0KU1ZEIGNhbiBiZSB0aG91Z2h0IG9mIGFzIGEgcHJlLXByb2Nlc3Npbmcgc3RlcCBmb3IgZmVhdHVyZSBlbmdpbmVlcmluZy4gWW91IG1pZ2h0IGVhc2lseSBzdGFydCB3aXRoIHRob3VzYW5kcyBvciBtaWxsaW9ucyBvZiBpdGVtcywgYW5kIHVzZSBTVkQgdG8gY3JlYXRlIGEgbXVjaCBzbWFsbGVyIHNldCBvZiDigJxr4oCdIGl0ZW1zIChlLmcuIDIwIG9yIDcwKS4NCg0KIyMjIEFzc2lnbm1lbnQgSGlnaGxpZ2h0cw0KDQpTVkQgYnVpbGRzIGZlYXR1cmVzIHRoYXQgbWF5IG9yIG1heSBub3QgbWFwIG5lYXRseSB0byBpdGVtcyAoc3VjaCBhcyBtb3ZpZSBnZW5yZXMgb3IgbmV3cyB0b3BpY3MpLiBBcyBpbiBtYW55IGFyZWFzIG9mIG1hY2hpbmUgbGVhcm5pbmcsIHRoZSBsYWNrIG9mIGV4cGxhaW5hYmlsaXR5IGNhbiBiZSBhbiBpc3N1ZSkuDQoNClNWRCByZXF1aXJlcyB0aGF0IHRoZXJlIGFyZSBubyBtaXNzaW5nIHZhbHVlcy4gVGhlcmUgYXJlIHZhcmlvdXMgd2F5cyB0byBoYW5kbGUgdGhpcywgaW5jbHVkaW5nICgxKSBpbXB1dGF0aW9uIG9mIG1pc3NpbmcgdmFsdWVzLCAoMikgbWVhbi1jZW50ZXJpbmcgdmFsdWVzIGFyb3VuZCAwLCBvciAoMykgPGFkdmFuY2VkPiB1c2luZyBhIG1vcmUgYWR2YW5jZSB0ZWNobmlxdWUsIHN1Y2ggYXMgc3RvY2hhc3RpYyBncmFkaWVudCBkZXNjZW50IHRvIHNpbXVsYXRlIFNWRCBpbiBwb3B1bGF0aW5nIHRoZSBmYWN0b3JlZCBtYXRyaWNlcy4NCg0KQ2FsY3VsYXRpbmcgdGhlIFNWRCBtYXRyaWNlcyBjYW4gYmUgY29tcHV0YXRpb25hbGx5IGV4cGVuc2l2ZSwgYWx0aG91Z2ggY2FsY3VsYXRpbmcgcmF0aW5ncyBvbmNlIHRoZSBmYWN0b3JpemF0aW9uIGlzIGNvbXBsZXRlZCBpcyB2ZXJ5IGZhc3QuIFlvdSBtYXkgbmVlZCB0byBjcmVhdGUgYSBzdWJzZXQgb2YgeW91ciBkYXRhIGZvciBTVkQgY2FsY3VsYXRpb25zIHRvIGJlIHN1Y2Nlc3NmdWxseSBwZXJmb3JtZWQsIGVzcGVjaWFsbHkgb24gYSBtYWNoaW5lIHdpdGggYSBzbWFsbCBSQU0gZm9vdHByaW50Lg0KDQojIyMgQWJvdXQgdGhlIERhdGENCg0KRm9yIFByb2plY3QgMywgSSB3aWxsIGJ1aWxkIG9uIFByb2plY3QgMiB1c2luZyB0aGUgc2FtZSBNb3ZpZUxlbnMgZGF0YXNldCwgYW5kIGV2YWx1YXRlIHRoZSBtb2RlbHMgdXNpbmcgUk1TRSwgTVNFIGFuZCBNQUUuVGhlIE1vdmllTGVucyBMYXRlc3QgU21hbGwgRGF0YXNldHMgY29udGFpbiAxMDAsMDAwIHJhdGluZ3MgYW5kIDMsNjAwIHRhZyBhcHBsaWNhdGlvbnMgYXBwbGllZCB0byA5LDAwMCBtb3ZpZXMgYnkgNjAwIHVzZXJzLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KHJlY29tbWVuZGVybGFiKQ0KYGBgDQoNCiMjIyBMb2FkaW5nIHRoZSBNb3ZpZUxlbnNlIERhdGENCg0KYGBge3J9DQpkYXRhIChNb3ZpZUxlbnNlKQ0KYGBgDQoNCg0KYGBge3J9DQpzbG90TmFtZXMoTW92aWVMZW5zZSkNCmNsYXNzKE1vdmllTGVuc2UpDQpkaW0oTW92aWVMZW5zZSkNCmBgYA0KDQojIyMgRGF0YSBzdHJ1Y3R1cmUNCg0KYGBge3J9DQpzdHIoTW92aWVMZW5zZSkNCmBgYA0KDQojIyMgRXhwbG9yaW5nIHRoZSByYXRpbmdzIHZhbHVlcw0KDQpgYGB7cn0NCnJhdGluZ3ZhbHVlcyA8LSBhcy52ZWN0b3IoTW92aWVMZW5zZUBkYXRhKQ0KdW5pcXVlKHJhdGluZ3ZhbHVlcykgIyBUaGUgcmF0aW5nIGlzIG51bWVyaWMgd2l0aCB0aGUgbGVhc3QgdmFsdWUgYXMgMC41IGFuZCB0aGUgaGlnaGVzdCB2YWx1ZXMgYXMgNS4NCg0KdGFibGVyYXRpbmcgPC0gdGFibGUocmF0aW5ndmFsdWVzKQ0KdGFibGVyYXRpbmcNCmBgYA0KDQojIyMgRXhjbHVkaW5nIHRoZSBtaXNzaW5nIHZhbHVlcw0KDQpgYGB7cn0NCnJhdGluZ3ZhbHVlcyA8LSByYXRpbmd2YWx1ZXNbcmF0aW5ndmFsdWVzICE9MF0NCmBgYA0KDQoNCiMjIyBEaXN0cmlidXRpb24gb2YgdGhlIFJhdGluZ3MNCg0KYGBge3J9DQpoaXN0KHJhdGluZ3ZhbHVlcywgDQogICAgIGJyZWFrcyA9IDYsIA0KICAgICBtYWluPSJEaXN0cmlidXRpb24gb2YgUmF0aW5ncyIsDQogICAgIHhsYWI9IlJhdGluZ3MiLA0KICAgICBjb2w9ImJsdWUiLA0KICAgICBmcmVxPVRSVUUNCiAgICAgKQ0KYGBgDQoNCiMjIyBQcmUtcHJvY2Vzc2luZw0KDQoqKkNvbnZlcnQgZGF0YSB0byBudW1lcmljKioNCg0KYGBge3J9DQptb3ZpZXMgPC0gYXMoTW92aWVMZW5zZSwgJ2RhdGEuZnJhbWUnKQ0KbW92aWVzJHVzZXIgPC0gYXMubnVtZXJpYyhtb3ZpZXMkdXNlcikNCm1vdmllcyRpdGVtIDwtIGFzLm51bWVyaWMobW92aWVzJGl0ZW0pDQpgYGANCg0KIyMjIENyZWF0ZSB0aGUgc3BhcnNlIE1hdHJpeA0KDQpgYGB7cn0NCnJhdGluZ3NNYXQgPC0gc3BhcnNlTWF0cml4KGkgPSBtb3ZpZXMkdXNlciwgaiA9IG1vdmllcyRpdGVtLCB4ID0gbW92aWVzJHJhdGluZywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltcyA9IGMobGVuZ3RoKHVuaXF1ZShtb3ZpZXMkdXNlcikpLCBsZW5ndGgodW5pcXVlKG1vdmllcyRpdGVtKSkpLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KHBhc3RlKCJ1IiwgMTpsZW5ndGgodW5pcXVlKG1vdmllcyR1c2VyKSksIHNlcCA9ICIiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCJtIiwgMTpsZW5ndGgodW5pcXVlKG1vdmllcyRpdGVtKSksIHNlcCA9ICIiKSkpDQoNCnJhdGluZ3NSZWFsIDwtIG5ldygicmVhbFJhdGluZ01hdHJpeCIsIGRhdGEgPSByYXRpbmdzTWF0KQ0KcmF0aW5nc1JlYWwNCmBgYA0KDQoqKkknbSBzZWxlY3RpbmcgdGhlIFVzZXJzIHdobyBoYXZlIHJhdGVkIGF0IGxlYXN0IDEwMCBtb3ZpZXMgYW5kIHRob3NlIG1vdmllcyB0aGF0IGhhdmUgYmVlbiB3YXRjaGVkIGF0IGxlYXN0IDE1MCB0aW1lcyoqDQoNCmBgYHtyfQ0KcmF0aW5ncyA8LSBNb3ZpZUxlbnNlW3Jvd0NvdW50cyhNb3ZpZUxlbnNlKSA+IDEwMCwgY29sQ291bnRzKE1vdmllTGVuc2UpID4gMTUwXQ0KYGBgDQoNCiMjIENyZWF0ZSBUcmFpbmluZyBhbmQgVGVzdGluZyBzZXRzDQoNClVzZSA4MCUgZm9yIHRyYWluaW5nIHRoZSBtb2RlbA0KDQpgYGB7cn0NCnNldC5zZWVkICgxMDApDQpTY2hlbWUgPC0gZXZhbHVhdGlvblNjaGVtZShyYXRpbmdzLCBtZXRob2QgPSAic3BsaXQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluID0gMC44LCBnaXZlbj0gMjAsIGdvb2RSYXRpbmc9NCkNCmBgYA0KDQoqKlByZXBhcmUgdHJhaW5pbmcgZGF0YXNldCoqDQoNCmBgYHtyfQ0KdHJhaW4gPC0gZ2V0RGF0YShTY2hlbWUsICJ0cmFpbiIpDQpgYGANCg0KKipQcmVwYXJlIHRlc3Rpbmcgc2V0KioNCg0KYGBge3J9DQp0ZXN0IDwtIGdldERhdGEoU2NoZW1lLCAia25vd24iKQ0KYGBgDQoNCioqUHJlcGFyZSBldmFsdWF0aW9uIHNldCoqDQoNCmBgYHtyfQ0KZXZhbHVhdGlvbiA8LSBnZXREYXRhKFNjaGVtZSwgInVua25vd24iKQ0KYGBgDQoNCiMjIFVzZXItVXNlciBDb2xsYWJvcmF0aXZlIEZpbHRlcmluZw0KDQpUaGUgVXNlciBiYXNlZCBjb2xsYWJvcmF0aXZlIGZpbHRlcmluZyBhbGdvcml0aG1zIGFyZSBiYXNlZCBvbiBtZWFzdXJpbmcgdGhlIHNpbWlsYXJpdHkgYmV0d2VlbiB1c2Vycy4gQSDigJxSZWNvbW1lbmRlcuKAnSBvYmplY3QgaXMgdGhlbiBnaXZlbiB0aGUg4oCcVUJDRuKAnSAoVXNlci1iYXNlZCBjb2xsYWJvcmF0aXZlIGZpbHRlciksIHdpdGggYSBjZW50ZXIgbm9ybWFsaXphdGlvbiwgY29zaW5lIG1ldGhvZCwgd2l0aCAyNSBuZWFyZXN0IG5laWdoYm9ycy5CdXQgZmlyc3QsIEkgd2lsbCBjb21wdXRlIHRoZSBzaW1pbGFyaXR5IG1hdHJpeC4NCg0KIyMjIFNpbWlsYXJpdHkgTWF0cml4DQoNCkEgc2ltaWxhcml0eSBtYXRyaXggaXMgYSByZWNvbW1lbmRlcmxhYiBmdW5jdGlvbiB0aGF0IHRha2VzIHRoZSDigJxyZWFsUmF0aW5nTWF0cml44oCdIiBhbmQgY2FsY3VsYXRlcyBhIGNvc2luZSBzaW1pbGFyaXR5IHdoaWNoIGFpZHMgaW4gdGhlIGludmVzdGlnYXRpb24gb2YgbW9kZWwgZGV2ZWxvcG1lbnQuIA0KDQpgYGB7cn0NCnNpbWlsYXJpdHlVc2VycyA8LSBzaW1pbGFyaXR5KE1vdmllTGVuc2VbMTo0LCBdLCBtZXRob2QgPSAiY29zaW5lIiwgd2hpY2ggPSAidXNlcnMiKQ0KDQppbWFnZShhcy5tYXRyaXgoc2ltaWxhcml0eVVzZXJzKSwgbWFpbiA9ICJVc2VycyBzaW1pbGFyaXR5IikNCmBgYA0KDQojIyMgVGhlIFVzZXIgQmFzZWQgTW9kZWwNCg0KQnVpbGRpbmcgdGhlIFVzZXIgQmFzZWQgTW9kZWwgdXNpbmcgMjUgbmVhcmVzdCBuZWlnaGJvci4gQnVpbGRpbmcgdGhlIHVzZXIgY29sbGFib3JhdGl2ZSBmaWx0ZXJpbmcgc3lzdGVtIHRvIHJlY29tbWVuZCBtb3ZpZXMgdG8gdXNlcnMgYmFzZWQgb24gaG93IHNpbWlsYXIgdGhleSBhcmUgd2l0aCBvdGhlciB1c2Vycy4NCg0KYGBge3J9DQpVc2VybW9kZWwgPC0gUmVjb21tZW5kZXIodHJhaW4sIG1ldGhvZCA9ICJVQkNGIiwgDQogICAgICAgICAgICAgICAgICAgICBwYXJhbT1saXN0KG5vcm1hbGl6ZSA9ICJjZW50ZXIiLCBtZXRob2Q9IkNvc2luZSIsIG5uPTI1KSkNCmBgYA0KDQojIyMgTWFraW5nIFByZWRpY3Rpb25zIHdpdGggbmV3ZGF0YSA9IHRlc3QNCg0KYGBge3J9DQpVc2VycHJlZCA8LSBwcmVkaWN0KFVzZXJtb2RlbCwgbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAicmF0aW5ncyIpDQpgYGANCg0KIyMgTW9kZWwgRXZhbHVhdGlvbiAtIFVzZXIgQmFzZWQNCg0KYGBge3J9DQpVc2VyYWNjdXJhY3kgPC0gY2FsY1ByZWRpY3Rpb25BY2N1cmFjeShVc2VycHJlZCwgZXZhbHVhdGlvbikNClVzZXJhY2N1cmFjeQ0KYGBgDQoNCiMjIGl0ZW0taXRlbSBDb2xsYWJvcmF0aXZlIEZpbHRlcmluZw0KDQpUaGUgSXRlbSBiYXNlZCBjb2xsYWJvcmF0aXZlIGZpbHRlcmluZyBhbGdvcml0aG1zIGFyZSBiYXNlZCBvbiBtZWFzdXJpbmcgdGhlIHNpbWlsYXJpdHkgYmV0d2VlbiBpdGVtcyBBIOKAnFJlY29tbWVuZGVy4oCdIG9iamVjdCBpcyB0aGVuIGdpdmVuIHRoZSDigJxJQkNG4oCdIChJdGVtLWJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyKSwgd2l0aCBhIGNlbnRlciBub3JtYWxpemF0aW9uLCBjb3NpbmUgbWV0aG9kLCB3aXRoIEsgPSAyNTAuQnV0IGZpcnN0LCBJIHdpbGwgY29tcHV0ZSB0aGUgc2ltaWxhcml0eSBtYXRyaXguDQoNCiMjIyBTaW1pbGFyaXR5IE1hdHJpeA0KDQpBIHNpbWlsYXJpdHkgbWF0cml4IGlzIGEgcmVjb21tZW5kZXJsYWIgZnVuY3Rpb24gdGhhdCB0YWtlcyB0aGUg4oCccmVhbFJhdGluZ01hdHJpeOKAnSIgYW5kIGNhbGN1bGF0ZXMgYSBjb3NpbmUgc2ltaWxhcml0eSB3aGljaCBhaWRzIGluIHRoZSBpbnZlc3RpZ2F0aW9uIG9mIG1vZGVsIGRldmVsb3BtZW50LiANCg0KYGBge3J9DQpzaW1pbGFyaXR5aXRlbXMgPC0gc2ltaWxhcml0eShNb3ZpZUxlbnNlWywgMTo0XSwgbWV0aG9kID0gImNvc2luZSIsIHdoaWNoID0gIml0ZW1zIikNCg0KaW1hZ2UoYXMubWF0cml4KHNpbWlsYXJpdHlpdGVtcyksIG1haW4gPSAiSXRlbXMgc2ltaWxhcml0eSIpDQpgYGANCg0KVGhlIGRpYWdvbmFsIGlzIHllbGxvdyBiZWNhdXNlIGl04oCZcyBjb21wYXJpbmcgZWFjaCBpdGVtcyB3aXRoIGl0c2VsZi4gDQoNCiMjIyBUaGUgSXRlbSBCYXNlZCBNb2RlbA0KDQpCdWlsZGluZyB0aGUgaXRlbS1pdGVtIGNvbGxhYm9yYXRpdmUgZmlsdGVyaW5nIHN5c3RlbSB0byByZWNvbW1lbmQgbW92aWVzIHRvIHVzZXJzIHdoZXJlIHRoZWlyIGl0ZW3igJlzIHJhdGluZ3MgYXJlIHNpbWlsYXIuDQoNCmBgYHtyfQ0KSXRlbW1vZGVsIDwtIFJlY29tbWVuZGVyKHRyYWluLCBtZXRob2QgPSAiSUJDRiIsIA0KICAgICAgICAgICAgICAgICAgICAgcGFyYW09bGlzdChub3JtYWxpemUgPSAiY2VudGVyIiwgbWV0aG9kPSJDb3NpbmUiLCBrPTI1MCkpDQpgYGANCg0KIyMjIE1ha2luZyBQcmVkaWN0aW9ucyB3aXRoIG5ld2RhdGEgPSB0ZXN0DQoNCmBgYHtyfQ0KSXRlbXByZWQgPC0gcHJlZGljdChJdGVtbW9kZWwsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gInJhdGluZ3MiKQ0KYGBgDQoNCiMjIE1vZGVsIEV2YWx1YXRpb24gLSBJdGVtIEJhc2VkDQoNCmBgYHtyfQ0KDQpJdGVtYWNjdXJhY3kgPC0gY2FsY1ByZWRpY3Rpb25BY2N1cmFjeShJdGVtcHJlZCwgZXZhbHVhdGlvbikNCkl0ZW1hY2N1cmFjeQ0KYGBgDQoNCiMjIFNpbmd1bGFyIFZhbHVlIERlY29tcG9zaXRpb24gTW9kZWwoU1ZEIE1vZGVsKQ0KDQpTaW5ndWxhciBWYWx1ZSBEZWNvbXBvc2l0aW9uIChTVkQpIHdpbGwgZW5hYmxlIHVzIHRvIHVuY292ZXIgaGlkZW4gZmVhdHVyZXMgaW4gYSByYXRpbmdzIG1hdHJpeCB3aGljaCBhcmUgZ2VuZXJhdGVkIGZyb20gdGhlIGNvbGxlY3RpdmUgYmVoYXZpb3Igb2YgdXNlcnMuIFRoZSBTVkQgdXNlcyBkaW1lbnNpb25hbCByZWR1Y3Rpb24gd2hpY2ggY2FuIG1pbmltaXplIHRoZSBwcm9ibGVtIG9mIG92ZXJmaXR0aW5nIHRvIHByb3ZpZGUgcm9idXN0IGFuZCBjb21wYWN0IHJlcHJlc2VudGF0aW9ucyBvZiB0aGUgZGF0YS4NCg0KIyMjIFNWRCBNb2RlbA0KDQpJIHdpbGwgZ2VuZXJhdGUgYW4gU1ZEIG1vZGVsIHdpdGggYSBtdWNoIHNtYWxsZXIgc2V0IG9mIOKAnGvigJ0gaXRlbXMgKDcwKS4gSXQgd2lsbCBjb250YWluIGFsbCB0aGUgcmVxdWlyZWQgaW5mb3JtYXRpb24gYW5kIEkgd2lsbCBjb21wYXJlIHRvIHRoZSBJdGVtIGFuZCBVc2VyIEJhc2VkIE1vZGVscy4gDQoNCmBgYHtyfQ0KU1ZEbW9kZWwgPC0gUmVjb21tZW5kZXIodHJhaW4sIG1ldGhvZCA9ICJTVkQiLCBwYXJhbWV0ZXIgPSBsaXN0KGsgPSA3MCkpDQpgYGANCg0KIyMjIE1ha2luZyBQcmVkaWN0aW9ucyB3aXRoIG5ld2RhdGEgPSB0ZXN0DQoNCmBgYHtyfQ0KU1ZEcHJlZCA8LSBwcmVkaWN0KFNWRG1vZGVsLCBuZXdkYXRhID0gdGVzdCwgdHlwZSA9ICJyYXRpbmdzIikNCmBgYA0KDQojIyBNb2RlbCBFdmFsdWF0aW9uIC0gVXNlciBCYXNlZA0KDQpgYGB7cn0NClNWRGFjY3VyYWN5IDwtIGNhbGNQcmVkaWN0aW9uQWNjdXJhY3koU1ZEcHJlZCwgZXZhbHVhdGlvbikNClNWRGFjY3VyYWN5DQpgYGANCg0KIyMgTW9kZWwgUGVyZm9ybWFuY2UNCg0KIyMjICoqQ29tcGFyaW5nIHRoZSBSZWNvbW1lbmRlciBTeXN0ZW1zKioNCg0KYGBge3J9DQpNb2RlbE5hbWUgPC0gYygiVXNlckJhc2VkIE1vZGVsIiwiSXRlbUJhc2VkIE1vZGVsIiwiU1ZEIE1vZGVsIikNCk1vZGVsX1JNU0UgPC0gYygiMC45NDIiLCAiMS4xMzUiLCIwLjk2NSIpDQpNb2RlbF9NU0UgPC0gYygiMC44ODciLCAiMS4yODgiLCIwLjkzMSIpDQpNb2RlbF9NQUUgPC0gYygiMC43NDciLCAiMC44ODYiLCIwLjc2NSIpDQoNCk1vZGVsX1BlcmZvcm1hbmNlIDwtIGRhdGEuZnJhbWUoTW9kZWxOYW1lLE1vZGVsX1JNU0UsTW9kZWxfTVNFLE1vZGVsX01BRSkNCk1vZGVsX1BlcmZvcm1hbmNlDQpgYGANCg0KIyMgQ29uY2x1c2lvbg0KDQpUaGUgUk1TRSBpcyBhIG1lYXN1cmUgb2YgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gdmFsdWVzIHByZWRpY3RlZCBieSBhIG1vZGVsIGFuZCB0aGUgdmFsdWVzIG9ic2VydmVkLlRoZSBSTVNFIGlzIGEgZ29vZCBtZWFzdXJlIHRvIHVzZSBpZiB3ZSB3YW50IHRvIGVzdGltYXRlIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgYSB0eXBpY2FsIG9ic2VydmVkIHZhbHVlIGZyb20gdGhlIG1vZGVs4oCZcyBwcmVkaWN0aW9uLCBhc3N1bWluZyB0aGF0IHRoZSBvYnNlcnZlZCBkYXRhIGNhbiBiZSBkZWNvbXBvc2VkLg0KDQpJZiB0aGUgbm9pc2UgaXMgc21hbGwsIGFzIGVzdGltYXRlZCBieSBSTVNFLCB0aGlzIGdlbmVyYWxseSBtZWFucyB0aGF0IHRoZSBtb2RlbCBpcyBnb29kIGF0IHByZWRpY3RpbmcgdGhlIG9ic2VydmVkIGRhdGEsIGFuZCBpZiBSTVNFIGlzIGxhcmdlLCB0aGlzIGdlbmVyYWxseSBtZWFucyB0aGUgbW9kZWwgaXMgZmFpbGluZyB0byBhY2NvdW50IGZvciBpbXBvcnRhbnQgZmVhdHVyZXMgdW5kZXJseWluZyB0aGUgZGF0YS4NCg0KVGhlIE1ldHJpY3Mgc2hvd3MgdGhhdCB0aGUgUk1TRSwgTVNFIGFuZCBNQUUgZm9yIHRoZSBVc2VyIGJhc2VkIFJlY29tbWVuZGVyIHN5c3RlbSBpcyBzbGlnaHRseSBiZXR0ZXIgaW4gcGVyZm9ybWFuY2UgY29tcGFyZWQgdG8gdGhlIEl0ZW0gQmFzZWQgYW5kIHRoZSBTVkQgc3lzdGVtcy4gVGhlIFJNU0UgZm9yIHRoZSBVc2VyIGJhc2VkIHN5c3RlbSBpcyBsb3dlciB0aGFuIHRoZSBvdGhlciBzeXN0ZW1zLCB0aGUgTVNFIGFuZCBNQUUgZm9yIHRoaXMgc3lzdGVtIGlzIGFsc28gbG93ZXIuDQoNClRoZSBsb3dlciBSTVNFIHNob3dzIHRoYXQgYSBjb2xsYWJvcmF0aXZlIGZpbHRlcmluZyBtb2RlbCBtYXkgYmUgbW9yZSBlZmZlY3RpdmUgYXQgcHJlZGljdGluZyBpdGVtcyBiYXNlZCBvbiByYXRpbmdzIHRoYW4gdGhlIFNWRC4gQWx0aG91Z2ggdXNpbmcgYSBsYXJnZXIgdmFsdWUgb2YgayBtYXkgaW1wcm92ZSB0aGUgcGVyZm9ybWFuY2UsIGl0IGlzIGltcG9ydGFudCBpZGVudGlmeSBvdGhlciBwb3NzaWJsZSBhcHBsaWNhdGlvbnMgb2YgdGhlIFNWRC4NCg0KVGhlIGRpc2FkdmFudGFnZSBvZiBTVkQgaXMgdGhhdCB0aGVyZSBpcyBsaXR0bGUgZXhwbGFuYXRpb24gd2h5IGFuIGl0ZW0gd2FzIHJlY29tbWVuZGVkIHNpbmNlIHNpbWlsYXJpdHkgbWF0cml4IHdhcyBub3QgY2FsY3VsYXRlZC4gVGhpcyBjYW4gYmUgcHJvYmxlbWF0aWMgaWYgdXNlcnMgYXJlIGVhZ2VyIHRvIGtub3cgd2h5IGEgc3BlY2lmaWMgaXRlbSB3YXMgcmVjb21tZW5kZWQuDQoNCg0KIyMjICoqUmVmZXJlbmNlcyoqDQoNCkJyZWVzZSBKUywgSGVja2VybWFuIEQsIEthZGllIEMgKDE5OTgpLiAiRW1waXJpY2FsIEFuYWx5c2lzIG9mIFByZWRpY3RpdmUgQWxnb3JpdGhtcyBmb3IgQ29sbGFib3JhdGl2ZSBGaWx0ZXJpbmcuIiBJbiBVbmNlcnRhaW50eSBpbiBBcnRpZmljaWFsIEludGVsbGlnZW5jZS4gUHJvY2VlZGluZ3Mgb2YgdGhlIEZvdXJ0ZWVudGggQ29uZmVyZW5jZSwgcHAuIDQzLTUyLg0KDQpLb2hhdmksIFJvbiAoMTk5NSkuICJBIHN0dWR5IG9mIGNyb3NzLXZhbGlkYXRpb24gYW5kIGJvb3RzdHJhcCBmb3IgYWNjdXJhY3kgZXN0aW1hdGlvbiBhbmQgbW9kZWwgc2VsZWN0aW9uIi4gUHJvY2VlZGluZ3Mgb2YgdGhlIEZvdXJ0ZWVudGggSW50ZXJuYXRpb25hbCBKb2ludCBDb25mZXJlbmNlIG9uIEFydGlmaWNpYWwgSW50ZWxsaWdlbmNlLCBwcC4gMTEzNy0xMTQzLg0KDQpLb3JlbiwgWS4sIEJlbGwsIFIuLCAmIFZvbGluc2t5LCBDLiAoMjAwOSkuIE1hdHJpeCBGYWN0b3JpemF0aW9uIFRlY2huaXF1ZXMgZm9yIFJlY29tbWVuZGVyIFN5c3RlbXMuIENvbXB1dGVyLCA0Mig4KSwgMzDigJMzNy4gaHR0cHM6Ly9kb2kub3JnLzEwLjExMDkvTUMuMjAwOS4yNjMNCg==