The data for this problem is based on the revision history of the page Language. Wikipedia provides a history for each page that consists of the state of the page at each revision. Rather than manually considering each revision, a script was run that checked whether edits stayed or were reverted. If a change was eventually reverted then that revision is marked as vandalism. This may result in some misclassifications, but the script performs well enough for our needs.

As a result of this preprocessing, some common processing tasks have already been done, including lower-casing and punctuation removal. The columns in the dataset are:



packages = c(
  "dplyr","ggplot2","caTools","tm","SnowballC","ROCR","rpart","rpart.plot","randomForest")
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
rm(list=ls(all=TRUE))
Sys.setlocale("LC_ALL","C")
[1] "C/C/C/C/C/zh_TW.UTF-8"
options(digits=5, scipen=10)
library(dplyr)
library(tm)
library(SnowballC)
library(ROCR)
library(caTools)
library(rpart)
library(rpart.plot)
library(randomForest)


Problem 1 - Bags of Words

1.1 The data set
wiki = read.csv("data/wiki.csv", stringsAsFactors = F)
wiki$Vandal = factor(wiki$Vandal)
table(wiki$Vandal)

   0    1 
2061 1815 

【P1.1】How many cases of vandalism were detected in the history of this page?

  • 1815
1.2 DTM, The Added Words
library(tm)
library(SnowballC)
# Create corpus for Added Words
txt = iconv(wiki$Added, to = "utf-8", sub="")
corpus = Corpus(VectorSource(txt))
corpus = tm_map(corpus, removeWords, stopwords("english"))
transformation drops documents
corpus = tm_map(corpus, stemDocument)
transformation drops documents
dtm = DocumentTermMatrix(corpus)
dtm
<<DocumentTermMatrix (documents: 3876, terms: 6674)>>
Non-/sparse entries: 15367/25853057
Sparsity           : 100%
Maximal term length: 784
Weighting          : term frequency (tf)

【P1.2】How many terms appear in dtmAdded?

  • 6675
1.3 Handle Sparsity

Filter out sparse terms by keeping only terms that appear in 0.3% or more of the revisions, and call the new matrix sparseAdded.

nwAdded = rowSums(as.matrix(dtm))     # no. word added in each edit
dtm = removeSparseTerms(dtm, 0.997)
dtm
<<DocumentTermMatrix (documents: 3876, terms: 166)>>
Non-/sparse entries: 2681/640735
Sparsity           : 100%
Maximal term length: 28
Weighting          : term frequency (tf)

【P1.3】How many terms appear in sparseAdded?

  • 166
1.4 Create Data Frames, wordAdded & wordRemoved

Convert sparseAdded to a data frame called wordsAdded, and then prepend all the words with the letter A, by using the command:

wordsAdded = as.data.frame(as.matrix(dtm))
colnames(wordsAdded) = paste("A", colnames(wordsAdded))  # for proper column names

Now repeat all of the steps we’ve done so far to create a Removed bag-of-words dataframe, called wordsRemoved, except this time, prepend all of the words with the letter R:

# Create corpus
txt = iconv(wiki$Removed, to = "utf-8", sub="")
corpus = Corpus(VectorSource(txt))
corpus = tm_map(corpus, removeWords, stopwords("english"))
transformation drops documents
corpus = tm_map(corpus, stemDocument)
transformation drops documents
dtm = DocumentTermMatrix(corpus)
dtm
<<DocumentTermMatrix (documents: 3876, terms: 5403)>>
Non-/sparse entries: 13293/20928735
Sparsity           : 100%
Maximal term length: 784
Weighting          : term frequency (tf)
nwRemoved = rowSums(as.matrix(dtm))
dtm = removeSparseTerms(dtm, 0.997)
dtm
<<DocumentTermMatrix (documents: 3876, terms: 162)>>
Non-/sparse entries: 2552/625360
Sparsity           : 100%
Maximal term length: 28
Weighting          : term frequency (tf)
wordsRemoved = as.data.frame(as.matrix(dtm))
colnames(wordsRemoved) = paste("R", colnames(wordsRemoved))

【P1.4】How many words are in the wordsRemoved data frame?

  • 162
1.5 Prepare the Data Frame

Combine the Data Frames wordsAdded & wordsRemoved with the Target Variable wiki$Vandal

wikiWords = cbind(wordsAdded, wordsRemoved)
wikiWords$Vandal = wiki$Vandal

Split the data frame for train and test data

library(caTools)
set.seed(123)
spl = sample.split(wikiWords$Vandal, 0.7)
train = subset(wikiWords, spl == TRUE)
test = subset(wikiWords, spl == FALSE)
table(test$Vandal) %>% prop.table

      0       1 
0.53138 0.46862 

【P1.5】What is the accuracy on the test set of a baseline method that always predicts “not vandalism”?

  • 0.53138
1.6 CART Model
library(rpart)
library(rpart.plot)
cart = rpart(Vandal~., train, method="class")
pred = predict(cart,test,type='class')
table(test$Vandal, pred) %>% {sum(diag(.)) / sum(.)} # 0.54428
[1] 0.54428

【P1.6】What is the accuracy of the model on the test set, using a threshold of 0.5?

  • 0.54428
1.7 Plot the Decision Tree
prp(cart)

【P1.7】How many word stems does the CART model use?

  • 3
1.8 Predictability of the CART model

【P1.8】Given the performance of the CART model relative to the baseline, what is the best explanation of these results?

  • Although it beats the baseline, bag of words is not very predictive for this problem.


Problem 2 - Add Features with Problem-specific Knowledge

2.1 Add HTTP column

Add a new column based on whether "http" is added

wiki2 = wikiWords
wiki2$HTTP = ifelse( grepl("http",wiki$Added,fixed=TRUE) , 1, 0)
table(wiki2$HTTP) # 217

   0    1 
3659  217 

【P2.1】Based on this new column, how many revisions added a link?

  • 217
2.2 Check accuracy again
train2 = subset(wiki2, spl==T)
test2 = subset(wiki2, spl==F)
cart2 = rpart(Vandal~., train2, method="class")
pred2 = predict(cart2,test2,type='class')
table(test2$Vandal, pred2) %>% {sum(diag(.)) / sum(.)} # 0.57524
[1] 0.57524

【P2.2】What is the new accuracy of the CART model on the test set, using a threshold of 0.5?

  • 0.57524
2.3 Total numbers of words added and removed
wiki2$nwAdded = nwAdded
wiki2$nwRemoved = nwRemoved
mean(nwAdded) # 4.0501
[1] 4.0498

【P2.3】What is the average number of words added?

  • 4.0501
2.4 Check accuracy again
train = subset(wiki2, spl)
test = subset(wiki2, !spl)
cart = rpart(Vandal~., train, method="class")
pred = predict(cart,test,type='class')
table(test$Vandal, pred) %>% {sum(diag(.)) / sum(.)} # 0.6552
[1] 0.6552

【P2.4】What is the new accuracy of the CART model on the test set?



Problem 3 - Using Non-Textual Data

原始資料之中還有一些之前沒有用到的欄位,我們把它們也加進來

wiki3 = wiki2
wiki3$Minor = wiki$Minor
wiki3$Loggedin = wiki$Loggedin
3.1 Check accuracy again
train = subset(wiki3, spl=T)
test = subset(wiki3, spl=F)
cart = rpart(Vandal~., train, method="class")
pred = predict(cart,test,type='class')
table(test$Vandal, pred) %>% {sum(diag(.)) / sum(.)} # .72472
[1] 0.72472

【P3.1】What is the accuracy of the model on the test set?

  • 0.72472
3.2 The Decision Tree
prp(cart)

【P3.2】How many splits are there in the tree?

  • 3


討論議題:
  ■ 請舉出一些可以繼續提高模型準確率的方法,方法越多越好:
    ● 利用原始資料去計算出新的變數。
    ● 將多個模型混合成混合模型。
    ●
    ●
    ●





LS0tCnRpdGxlOiAiQVMxMC0x77ya6Kqw5piv5L6G5LqC55qE77yfIgphdXRob3I6ICLmnpflmInnvr0gTTA2NDExMTAzOCIKZGF0ZTogImByIFN5cy50aW1lKClgIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2sKLS0tCgpUaGUgZGF0YSBmb3IgdGhpcyBwcm9ibGVtIGlzIGJhc2VkIG9uIHRoZSByZXZpc2lvbiBoaXN0b3J5IG9mIHRoZSBwYWdlIExhbmd1YWdlLiBXaWtpcGVkaWEgcHJvdmlkZXMgYSBoaXN0b3J5IGZvciBlYWNoIHBhZ2UgdGhhdCBjb25zaXN0cyBvZiB0aGUgc3RhdGUgb2YgdGhlIHBhZ2UgYXQgZWFjaCByZXZpc2lvbi4gUmF0aGVyIHRoYW4gbWFudWFsbHkgY29uc2lkZXJpbmcgZWFjaCByZXZpc2lvbiwgYSBzY3JpcHQgd2FzIHJ1biB0aGF0IGNoZWNrZWQgd2hldGhlciBlZGl0cyBzdGF5ZWQgb3Igd2VyZSByZXZlcnRlZC4gSWYgYSBjaGFuZ2Ugd2FzIGV2ZW50dWFsbHkgcmV2ZXJ0ZWQgdGhlbiB0aGF0IHJldmlzaW9uIGlzIG1hcmtlZCBhcyB2YW5kYWxpc20uIFRoaXMgbWF5IHJlc3VsdCBpbiBzb21lIG1pc2NsYXNzaWZpY2F0aW9ucywgYnV0IHRoZSBzY3JpcHQgcGVyZm9ybXMgd2VsbCBlbm91Z2ggZm9yIG91ciBuZWVkcy4KCkFzIGEgcmVzdWx0IG9mIHRoaXMgcHJlcHJvY2Vzc2luZywgc29tZSBjb21tb24gcHJvY2Vzc2luZyB0YXNrcyBoYXZlIGFscmVhZHkgYmVlbiBkb25lLCBpbmNsdWRpbmcgbG93ZXItY2FzaW5nIGFuZCBwdW5jdHVhdGlvbiByZW1vdmFsLiBUaGUgY29sdW1ucyBpbiB0aGUgZGF0YXNldCBhcmU6CgorIFZhbmRhbCA9IDEgaWYgdGhpcyBlZGl0IHdhcyB2YW5kYWxpc20sIDAgaWYgbm90LgorIE1pbm9yID0gMSBpZiB0aGUgdXNlciBtYXJrZWQgdGhpcyBlZGl0IGFzIGEgIm1pbm9yIGVkaXQiLCAwIGlmIG5vdC4KKyBMb2dnZWRpbiA9IDEgaWYgdGhlIHVzZXIgbWFkZSB0aGlzIGVkaXQgd2hpbGUgdXNpbmcgYSBXaWtpcGVkaWEgYWNjb3VudCwgMCBpZiB0aGV5IGRpZCBub3QuCisgQWRkZWQgPSBUaGUgdW5pcXVlIHdvcmRzIGFkZGVkLgorIFJlbW92ZWQgPSBUaGUgdW5pcXVlIHdvcmRzIHJlbW92ZWQuCgo8YnI+PGhyPgoKYGBge3J9CnBhY2thZ2VzID0gYygKICAiZHBseXIiLCJnZ3Bsb3QyIiwiY2FUb29scyIsInRtIiwiU25vd2JhbGxDIiwiUk9DUiIsInJwYXJ0IiwicnBhcnQucGxvdCIsInJhbmRvbUZvcmVzdCIpCmV4aXN0aW5nID0gYXMuY2hhcmFjdGVyKGluc3RhbGxlZC5wYWNrYWdlcygpWywxXSkKZm9yKHBrZyBpbiBwYWNrYWdlc1shKHBhY2thZ2VzICVpbiUgZXhpc3RpbmcpXSkgaW5zdGFsbC5wYWNrYWdlcyhwa2cpCmBgYAoKYGBge3Igd2FybmluZz1GLCBtZXNzYWdlPUYsIGNhY2hlPUYsIGVycm9yPUZ9CnJtKGxpc3Q9bHMoYWxsPVRSVUUpKQpTeXMuc2V0bG9jYWxlKCJMQ19BTEwiLCJDIikKb3B0aW9ucyhkaWdpdHM9NSwgc2NpcGVuPTEwKQoKbGlicmFyeShkcGx5cikKbGlicmFyeSh0bSkKbGlicmFyeShTbm93YmFsbEMpCmxpYnJhcnkoUk9DUikKbGlicmFyeShjYVRvb2xzKQpsaWJyYXJ5KHJwYXJ0KQpsaWJyYXJ5KHJwYXJ0LnBsb3QpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpgYGAKPGJyPgoKIyMjIFByb2JsZW0gMSAtIEJhZ3Mgb2YgV29yZHMKCiMjIyMjIDEuMSBUaGUgZGF0YSBzZXQKCmBgYHtyfQp3aWtpID0gcmVhZC5jc3YoImRhdGEvd2lraS5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRikKd2lraSRWYW5kYWwgPSBmYWN0b3Iod2lraSRWYW5kYWwpCnRhYmxlKHdpa2kkVmFuZGFsKQpgYGAKCuOAkFAxLjHjgJFfX0hvdyBtYW55IGNhc2VzIG9mIHZhbmRhbGlzbSB3ZXJlIGRldGVjdGVkIGluIHRoZSBoaXN0b3J5IG9mIHRoaXMgcGFnZT9fXwoKKyAxODE1CisKCiMjIyMjIDEuMiBEVE0sIFRoZSBBZGRlZCBXb3JkcwpgYGB7cn0KbGlicmFyeSh0bSkKbGlicmFyeShTbm93YmFsbEMpCgojIENyZWF0ZSBjb3JwdXMgZm9yIEFkZGVkIFdvcmRzCnR4dCA9IGljb252KHdpa2kkQWRkZWQsIHRvID0gInV0Zi04Iiwgc3ViPSIiKQpjb3JwdXMgPSBDb3JwdXMoVmVjdG9yU291cmNlKHR4dCkpCmNvcnB1cyA9IHRtX21hcChjb3JwdXMsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImVuZ2xpc2giKSkKY29ycHVzID0gdG1fbWFwKGNvcnB1cywgc3RlbURvY3VtZW50KQpkdG0gPSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzKQpkdG0KYGBgCuOAkFAxLjLjgJFfX0hvdyBtYW55IHRlcm1zIGFwcGVhciBpbiBgZHRtQWRkZWRgP19fCgorIDY2NzUKKwoKIyMjIyMgMS4zIEhhbmRsZSBTcGFyc2l0eQpGaWx0ZXIgb3V0IHNwYXJzZSB0ZXJtcyBieSBrZWVwaW5nIG9ubHkgdGVybXMgdGhhdCBhcHBlYXIgaW4gMC4zJSBvciBtb3JlIG9mIHRoZSByZXZpc2lvbnMsIGFuZCBjYWxsIHRoZSBuZXcgbWF0cml4IHNwYXJzZUFkZGVkLiAKYGBge3J9Cm53QWRkZWQgPSByb3dTdW1zKGFzLm1hdHJpeChkdG0pKSAgICAgIyBuby4gd29yZCBhZGRlZCBpbiBlYWNoIGVkaXQKZHRtID0gcmVtb3ZlU3BhcnNlVGVybXMoZHRtLCAwLjk5NykKZHRtCmBgYArjgJBQMS4z44CRX19Ib3cgbWFueSB0ZXJtcyBhcHBlYXIgaW4gYHNwYXJzZUFkZGVkYD9fXwoKKyAxNjYKKwoKIyMjIyMgMS40IENyZWF0ZSBEYXRhIEZyYW1lcywgYHdvcmRBZGRlZGAgJiBgd29yZFJlbW92ZWRgCkNvbnZlcnQgc3BhcnNlQWRkZWQgdG8gYSBkYXRhIGZyYW1lIGNhbGxlZCBgd29yZHNBZGRlZGAsIGFuZCB0aGVuIHByZXBlbmQgYWxsIHRoZSB3b3JkcyB3aXRoIHRoZSBsZXR0ZXIgQSwgYnkgdXNpbmcgdGhlIGNvbW1hbmQ6CmBgYHtyfQp3b3Jkc0FkZGVkID0gYXMuZGF0YS5mcmFtZShhcy5tYXRyaXgoZHRtKSkKY29sbmFtZXMod29yZHNBZGRlZCkgPSBwYXN0ZSgiQSIsIGNvbG5hbWVzKHdvcmRzQWRkZWQpKSAgIyBmb3IgcHJvcGVyIGNvbHVtbiBuYW1lcwpgYGAKCk5vdyByZXBlYXQgYWxsIG9mIHRoZSBzdGVwcyB3ZSd2ZSBkb25lIHNvIGZhciB0byBjcmVhdGUgYSBSZW1vdmVkIGJhZy1vZi13b3JkcyBkYXRhZnJhbWUsIGNhbGxlZCBgd29yZHNSZW1vdmVkYCwgZXhjZXB0IHRoaXMgdGltZSwgcHJlcGVuZCBhbGwgb2YgdGhlIHdvcmRzIHdpdGggdGhlIGxldHRlciBSOgoKYGBge3J9CiMgQ3JlYXRlIGNvcnB1cwp0eHQgPSBpY29udih3aWtpJFJlbW92ZWQsIHRvID0gInV0Zi04Iiwgc3ViPSIiKQpjb3JwdXMgPSBDb3JwdXMoVmVjdG9yU291cmNlKHR4dCkpCmNvcnB1cyA9IHRtX21hcChjb3JwdXMsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImVuZ2xpc2giKSkKY29ycHVzID0gdG1fbWFwKGNvcnB1cywgc3RlbURvY3VtZW50KQpkdG0gPSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzKQpkdG0KbndSZW1vdmVkID0gcm93U3Vtcyhhcy5tYXRyaXgoZHRtKSkKZHRtID0gcmVtb3ZlU3BhcnNlVGVybXMoZHRtLCAwLjk5NykKZHRtCndvcmRzUmVtb3ZlZCA9IGFzLmRhdGEuZnJhbWUoYXMubWF0cml4KGR0bSkpCmNvbG5hbWVzKHdvcmRzUmVtb3ZlZCkgPSBwYXN0ZSgiUiIsIGNvbG5hbWVzKHdvcmRzUmVtb3ZlZCkpCmBgYArjgJBQMS4044CRX19Ib3cgbWFueSB3b3JkcyBhcmUgaW4gdGhlIGB3b3Jkc1JlbW92ZWRgIGRhdGEgZnJhbWU/X18KCisgMTYyCisKCiMjIyMjIDEuNSBQcmVwYXJlIHRoZSBEYXRhIEZyYW1lCkNvbWJpbmUgdGhlIERhdGEgRnJhbWVzIGB3b3Jkc0FkZGVkYCAmIGB3b3Jkc1JlbW92ZWRgIHdpdGggdGhlIFRhcmdldCBWYXJpYWJsZSBgd2lraSRWYW5kYWxgCmBgYHtyfQp3aWtpV29yZHMgPSBjYmluZCh3b3Jkc0FkZGVkLCB3b3Jkc1JlbW92ZWQpCndpa2lXb3JkcyRWYW5kYWwgPSB3aWtpJFZhbmRhbApgYGAKClNwbGl0IHRoZSBkYXRhIGZyYW1lIGZvciB0cmFpbiBhbmQgdGVzdCBkYXRhCmBgYHtyfQpsaWJyYXJ5KGNhVG9vbHMpCnNldC5zZWVkKDEyMykKc3BsID0gc2FtcGxlLnNwbGl0KHdpa2lXb3JkcyRWYW5kYWwsIDAuNykKdHJhaW4gPSBzdWJzZXQod2lraVdvcmRzLCBzcGwgPT0gVFJVRSkKdGVzdCA9IHN1YnNldCh3aWtpV29yZHMsIHNwbCA9PSBGQUxTRSkKdGFibGUodGVzdCRWYW5kYWwpICU+JSBwcm9wLnRhYmxlCmBgYArjgJBQMS4144CRX19XaGF0IGlzIHRoZSBhY2N1cmFjeSBvbiB0aGUgdGVzdCBzZXQgb2YgYSBiYXNlbGluZSBtZXRob2QgdGhhdCBhbHdheXMgcHJlZGljdHMgIm5vdCB2YW5kYWxpc20iP19fCgorIDAuNTMxMzgKKwoKIyMjIyMgMS42IENBUlQgTW9kZWwKYGBge3J9CmxpYnJhcnkocnBhcnQpCmxpYnJhcnkocnBhcnQucGxvdCkKY2FydCA9IHJwYXJ0KFZhbmRhbH4uLCB0cmFpbiwgbWV0aG9kPSJjbGFzcyIpCnByZWQgPSBwcmVkaWN0KGNhcnQsdGVzdCx0eXBlPSdjbGFzcycpCnRhYmxlKHRlc3QkVmFuZGFsLCBwcmVkKSAlPiUge3N1bShkaWFnKC4pKSAvIHN1bSguKX0gIyAwLjU0NDI4CmBgYArjgJBQMS4244CRX19XaGF0IGlzIHRoZSBhY2N1cmFjeSBvZiB0aGUgbW9kZWwgb24gdGhlIHRlc3Qgc2V0LCB1c2luZyBhIHRocmVzaG9sZCBvZiAwLjU/X18KCisgMC41NDQyOAorCgoKIyMjIyMgMS43IFBsb3QgdGhlIERlY2lzaW9uIFRyZWUKYGBge3IgZmlnLmhlaWdodD0zLjZ9CnBycChjYXJ0KQpgYGAK44CQUDEuN+OAkV9fSG93IG1hbnkgd29yZCBzdGVtcyBkb2VzIHRoZSBDQVJUIG1vZGVsIHVzZT9fXwoKKyAzCisKCiMjIyMjIDEuOCBQcmVkaWN0YWJpbGl0eSBvZiB0aGUgQ0FSVCBtb2RlbArjgJBQMS4444CRX19HaXZlbiB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIENBUlQgbW9kZWwgcmVsYXRpdmUgdG8gdGhlIGJhc2VsaW5lLCB3aGF0IGlzIHRoZSBiZXN0IGV4cGxhbmF0aW9uIG9mIHRoZXNlIHJlc3VsdHM/X18KCisgQWx0aG91Z2ggaXQgYmVhdHMgdGhlIGJhc2VsaW5lLCBiYWcgb2Ygd29yZHMgaXMgbm90IHZlcnkgcHJlZGljdGl2ZSBmb3IgdGhpcyBwcm9ibGVtLgorCgo8YnI+PGhyPgoKIyMjIFByb2JsZW0gMiAtIEFkZCBGZWF0dXJlcyB3aXRoIFByb2JsZW0tc3BlY2lmaWMgS25vd2xlZGdlCgojIyMjIyAyLjEgQWRkIGBIVFRQYCBjb2x1bW4KQWRkIGEgbmV3IGNvbHVtbiBiYXNlZCBvbiB3aGV0aGVyIGAiaHR0cCJgIGlzIGFkZGVkIApgYGB7cn0Kd2lraTIgPSB3aWtpV29yZHMKd2lraTIkSFRUUCA9IGlmZWxzZSggZ3JlcGwoImh0dHAiLHdpa2kkQWRkZWQsZml4ZWQ9VFJVRSkgLCAxLCAwKQp0YWJsZSh3aWtpMiRIVFRQKSAjIDIxNwpgYGAK44CQUDIuMeOAkV9fQmFzZWQgb24gdGhpcyBuZXcgY29sdW1uLCBob3cgbWFueSByZXZpc2lvbnMgYWRkZWQgYSBsaW5rP19fCgorIDIxNworCgojIyMjIyAyLjIgQ2hlY2sgYWNjdXJhY3kgYWdhaW4KYGBge3J9CnRyYWluMiA9IHN1YnNldCh3aWtpMiwgc3BsPT1UKQp0ZXN0MiA9IHN1YnNldCh3aWtpMiwgc3BsPT1GKQpjYXJ0MiA9IHJwYXJ0KFZhbmRhbH4uLCB0cmFpbjIsIG1ldGhvZD0iY2xhc3MiKQpwcmVkMiA9IHByZWRpY3QoY2FydDIsdGVzdDIsdHlwZT0nY2xhc3MnKQp0YWJsZSh0ZXN0MiRWYW5kYWwsIHByZWQyKSAlPiUge3N1bShkaWFnKC4pKSAvIHN1bSguKX0gIyAwLjU3NTI0CmBgYArjgJBQMi4y44CRX19XaGF0IGlzIHRoZSBuZXcgYWNjdXJhY3kgb2YgdGhlIENBUlQgbW9kZWwgb24gdGhlIHRlc3Qgc2V0LCB1c2luZyBhIHRocmVzaG9sZCBvZiAwLjU/X18KCisgMC41NzUyNAorCgojIyMjIyAyLjMgVG90YWwgbnVtYmVycyBvZiB3b3JkcyBhZGRlZCBhbmQgcmVtb3ZlZApgYGB7cn0Kd2lraTIkbndBZGRlZCA9IG53QWRkZWQKd2lraTIkbndSZW1vdmVkID0gbndSZW1vdmVkCm1lYW4obndBZGRlZCkgIyA0LjA1MDEKYGBgCuOAkFAyLjPjgJFfX1doYXQgaXMgdGhlIGF2ZXJhZ2UgbnVtYmVyIG9mIHdvcmRzIGFkZGVkP19fCgorIDQuMDUwMQorCgojIyMjIyAyLjQgQ2hlY2sgYWNjdXJhY3kgYWdhaW4KYGBge3J9CnRyYWluID0gc3Vic2V0KHdpa2kyLCBzcGwpCnRlc3QgPSBzdWJzZXQod2lraTIsICFzcGwpCmNhcnQgPSBycGFydChWYW5kYWx+LiwgdHJhaW4sIG1ldGhvZD0iY2xhc3MiKQpwcmVkID0gcHJlZGljdChjYXJ0LHRlc3QsdHlwZT0nY2xhc3MnKQp0YWJsZSh0ZXN0JFZhbmRhbCwgcHJlZCkgJT4lIHtzdW0oZGlhZyguKSkgLyBzdW0oLil9ICMgMC42NTUyCmBgYArjgJBQMi4044CRX19XaGF0IGlzIHRoZSBuZXcgYWNjdXJhY3kgb2YgdGhlIENBUlQgbW9kZWwgb24gdGhlIHRlc3Qgc2V0P19fCgorIAorCgo8YnI+PGhyPgoKIyMjIFByb2JsZW0gMyAtIFVzaW5nIE5vbi1UZXh0dWFsIERhdGEKCuWOn+Wni+izh+aWmeS5i+S4remChOacieS4gOS6m+S5i+WJjeaykuacieeUqOWIsOeahOashOS9je+8jOaIkeWAkeaKiuWug+WAkeS5n+WKoOmAsuS+hgpgYGB7cn0Kd2lraTMgPSB3aWtpMgp3aWtpMyRNaW5vciA9IHdpa2kkTWlub3IKd2lraTMkTG9nZ2VkaW4gPSB3aWtpJExvZ2dlZGluCmBgYAoKIyMjIyMgMy4xIENoZWNrIGFjY3VyYWN5IGFnYWluCmBgYHtyfQp0cmFpbiA9IHN1YnNldCh3aWtpMywgc3BsPVQpCnRlc3QgPSBzdWJzZXQod2lraTMsIHNwbD1GKQpjYXJ0ID0gcnBhcnQoVmFuZGFsfi4sIHRyYWluLCBtZXRob2Q9ImNsYXNzIikKcHJlZCA9IHByZWRpY3QoY2FydCx0ZXN0LHR5cGU9J2NsYXNzJykKdGFibGUodGVzdCRWYW5kYWwsIHByZWQpICU+JSB7c3VtKGRpYWcoLikpIC8gc3VtKC4pfSAjIC43MjQ3MgpgYGAK44CQUDMuMeOAkV9fV2hhdCBpcyB0aGUgYWNjdXJhY3kgb2YgdGhlIG1vZGVsIG9uIHRoZSB0ZXN0IHNldD9fXwoKKyAwLjcyNDcyCisKCiMjIyMjIDMuMiBUaGUgRGVjaXNpb24gVHJlZQpgYGB7ciAgZmlnLmhlaWdodD0zLjZ9CnBycChjYXJ0KQpgYGAKCuOAkFAzLjLjgJFfX0hvdyBtYW55IHNwbGl0cyBhcmUgdGhlcmUgaW4gdGhlIHRyZWU/X18KCisgMworCiAgCjxicj48aHI+Cgo8cCBjbGFzcz0icWl6Ij4K6KiO6KuW6K2w6aGM77yaPGJyPgomZW1zcDsg4pagIOiri+iIieWHuuS4gOS6m+WPr+S7pee5vOe6jOaPkOmrmOaooeWei+a6lueiuueOh+eahOaWueazle+8jOaWueazlei2iuWkmui2iuWlve+8miA8YnI+CiZlbXNwOyAmZW1zcDsg4pePICDliKnnlKjljp/lp4vos4fmlpnljrvoqIjnrpflh7rmlrDnmoTorormlbjjgII8YnI+CiZlbXNwOyAmZW1zcDsg4pePICDlsIflpJrlgIvmqKHlnovmt7flkIjmiJDmt7flkIjmqKHlnovjgII8YnI+CiZlbXNwOyAmZW1zcDsg4pePICA8YnI+CiZlbXNwOyAmZW1zcDsg4pePICA8YnI+CiZlbXNwOyAmZW1zcDsg4pePICA8YnI+Cjxicj4KPC9wPgoKPGJyPjxicj48YnI+PGJyPgoKCgoKCg==