Tutorial 4, Advanced Crime Analysis, BSc Security and Crime Science, UCL


Aim of this tutorial

You will use concepts learned in the lectures to:

Task 1: Prepare data for supervised classification - predicting political polarity of news channels

(you’ll need the quanteda package for this one)

For the supervised ML part, you will use the dataset from last tutorial on YouTube transcripts extracted from left-leaning and right-leaning news channels. In the provided dataset, you have the transcripts of 2000 YouTube videos each from FoxNews (a right-leaning US news channel) and from The Young Turks (a left-leaning US news outlet).

Load the original dataframe called media_data from data/media_data.RData.

rr #your code here load(‘data/media_data.RData’)

Build an ngram model that contains unigrams and bigrams and correct for sparsity so that the tokens are contained in at least 10% of the documents. Make sure to remove all punctuation, numbers and symbols.

rr #your code here library(quanteda)

corpus.media_data = corpus(x = media_data$text)

tokens.media_data = tokens(corpus.media_data , remove_numbers = T , remove_punct = T , remove_symbols = T , remove_hyphens = T)

dfm.media_data = dfm(tokens.media_data , ngrams = 1:2 , tolower = T , stem = T , remove = stopwords() )

dfm.media_data = dfm_trim(dfm.media_data, sparsity = 0.90)

convert to dataframe

ngrams.media_data = as.data.frame(dfm.media_data)

add outcome column back in

ngrams.media_data\(outcome = media_data\)pol

remove document id variable

ngrams.media_data = ngrams.media_data[, -1]

Task 2: Predict whether a transcript comes froma right or left-leaning YouTube channel

Step 1: split the data

(you need the caret package for this one)

rr #your code here library(caret) set.seed(2012) in_training = createDataPartition(y = ngrams.media_data$outcome , p = .8 , list = FALSE )

training_set = ngrams.media_data[in_training, ] test_set = ngrams.media_data[-in_training, ]

Have a look at the dimensions of your data. How many features are there?

rr #your code here dim(test_set)

Step 2: set your training controls

Here, you can go for a k-fold with a high number of k (e.g. 20).

Make sure to specify classProbs = T since we need this for later Area Under the Curve calculations.

rr #your code here training_controls = trainControl(method=
, number = 20 , classProbs = T )

Step 3: train the model

You can use a Linear SVM, for example.

rr #your code here svm.model = train(outcome ~. , data = training_set , trControl = training_controls , method =
)

Step 4: fit the model

rr #your code here svm.model.predictions = predict(svm.model, test_set)

Task 3: Assess the model performance

Step 1: calculate the accuracy of your model on the test set

rr #your code here table(test_set$outcome, svm.model.predictions)

Step 2: calculate the precision, recall and F1 scores

Stop and think for a second: why do we need these metrics in addition to the accuracy?

rr #your code here confusionMatrix(table(test_set\(outcome, svm.model.predictions)) precision(table(test_set\)outcome, svm.model.predictions), relevant = ‘l’) #…

Step 3: calculate the area under the curve

To obtain the area under the curve, remember that we need class probabilities. You can obtain these by creating a new variable that uses the predict function with the parameter type = "prob".

rr #your code here svm.model.probs = predict(svm.model, test_set, type = ‘prob’)[, 1]

(Hint: if done correctly, you will obtain a dataframe with each case of the test set in the rows and two columns - one for the class probabilities in class 1 and one for class 2. You will see that they sum to 1, so you can choose one of them for the AUC calculation. Try it out to proof that the results won’t change.)

Now use the pROC library to calculate the area under the curve.

rr #your code here library(pROC) auc.svm_model = roc(response = test_set$outcome , predictor = svm.model.probs , ci=T) auc.svm_model

Plot the area under the curve (using plot.roc:

rr #your code here plot.roc(auc.svm_model, legacy.axes = T)

What is your conclusion re. the model you just built?

Finally: Have a look at the features that drive the classifier using varImp. Note that the variable importance of caret relies on numerical outcomes - therefore: re-run the model but change the training set so that the outcome variable’s levels are numeric (1 and 0) and set classProbs = F in the training controls.

Once you identified the most important features, have a look in which class they are more prevalent.

rr #your code here training_set_2 = training_set training_set_2\(outcome = as.factor(training_set_2\)outcome) levels(training_set_2$outcome) = c(1, 0)

training_controls = trainControl(method=
, number = 20 ) svm.model_2 = train(outcome ~. , data = training_set_2 , trControl = training_controls , method =
)

call varImp

varImp(svm.model_2)

tapply(training_set_2\(gonna, training_set_2\)outcome, mean)

Task 4: Unsupervised learning on tech titles

Load the data.frame tech_titles from the tech_titles.RData object located in the ./data directory. These data are all titles of articles written on the two major tech websites VentureBeat and TechCrunch in 2017 (dataset details on Kaggle).

Your task is to represent these titles as unigrams and find out whether there are clusters in the data.

Step 1: Load the data

rr #your code here load(‘./data/tech_titles.RData’)

Step 2: Create the unigrams

(apply preprocessing where you think this is necessary)

rr #your code here library(quanteda)

corpus.tech_titles = corpus(tech_titles$title) tokens.tech_titles = tokens(corpus.tech_titles , remove_numbers = T , remove_punct = T , remove_symbols = T , remove_hyphens = T ) dfm.tech_titles = dfm(tokens.tech_titles , ngrams = 1 , tolower = T , stem = T , remove = stopwords() )

dfm.tech_titles = dfm_trim(dfm.tech_titles, sparsity = 0.99)

Step 3: Determine the number of clusters

Use the elbow method:

(note: you will get an error here, try to figure out why and solve it!)

rr #your code here df_dfm.tech_titles = as.data.frame(dfm.tech_titles)[, -1]

wss = numeric()

for(i in 1:20){ model_i = kmeans(x = df_dfm.tech_titles, centers = i, nstart = 10, iter.max = 10)
wss[i] = model_i$tot.withinss }

plot(1:20, wss)

Step 4: Build the final model

rr #your code here kmeans_model = kmeans(x = df_dfm.tech_titles , centers = 4 , nstart = 10 , iter.max = 20)

Step 5: Interpret the class membership

Tip:

  • assign the cluster membership to a column in the original dataframe
  • then aggregate the unigram frequencies by cluster
    • this returns the average freq per unigram per cluster
  • now sort the frequencies per cluster separately to see what the clusters are about

rr #your code here df_dfm.tech_titles\(cluster = kmeans_model\)cluster

aggregated_clusters = aggregate(df_dfm.tech_titles , by = list(df_dfm.tech_titles$cluster) , mean)

aggregated_clusters = aggregated_clusters[, -c(1, 120)]

cluster1_ordered = sort(aggregated_clusters[1,], decreasing = T) cluster2_ordered = sort(aggregated_clusters[2,], decreasing = T) cluster3_ordered = sort(aggregated_clusters[3,], decreasing = T) cluster4_ordered = sort(aggregated_clusters[4,], decreasing = T)


END

LS0tCnRpdGxlOiAnU29sdXRpb25zOiBNYWNoaW5lIGxlYXJuaW5nIGluIFInCmF1dGhvcjogQiBLbGVpbmJlcmcKZGF0ZTogMjYgRmViIDIwMTkKc3VidGl0bGU6IERlcHQgb2YgU2VjdXJpdHkgYW5kIENyaW1lIFNjaWVuY2UsIFVDTApvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgotLS0KClR1dG9yaWFsIDQsIEFkdmFuY2VkIENyaW1lIEFuYWx5c2lzLCBCU2MgU2VjdXJpdHkgYW5kIENyaW1lIFNjaWVuY2UsIFVDTAoKLS0tCgojIyBBaW0gb2YgdGhpcyB0dXRvcmlhbAoKWW91IHdpbGwgdXNlIGNvbmNlcHRzIGxlYXJuZWQgaW4gdGhlIGxlY3R1cmVzIHRvOgoKLSBwcmVwYXJlIGRhdGEgZm9yIHN1cGVydmlzZWQgY2xhc3NpZmljYXRpb24gaW4gUgotIHJ1biBzdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGluIFIKLSBhc3Nlc3MgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBtb2RlbHMKLSBhcHBseSB1bnN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZyBtb2RlbHMgaW4gUgoKCiMjIFRhc2sgMTogUHJlcGFyZSBkYXRhIGZvciBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uIC0gcHJlZGljdGluZyBwb2xpdGljYWwgcG9sYXJpdHkgb2YgbmV3cyBjaGFubmVscwoKKHlvdSdsbCBuZWVkIHRoZSBgcXVhbnRlZGFgIHBhY2thZ2UgZm9yIHRoaXMgb25lKQoKRm9yIHRoZSBzdXBlcnZpc2VkIE1MIHBhcnQsIHlvdSB3aWxsIHVzZSB0aGUgZGF0YXNldCBmcm9tIGxhc3QgdHV0b3JpYWwgb24gWW91VHViZSB0cmFuc2NyaXB0cyBleHRyYWN0ZWQgZnJvbSBsZWZ0LWxlYW5pbmcgYW5kIHJpZ2h0LWxlYW5pbmcgbmV3cyBjaGFubmVscy4gSW4gdGhlIHByb3ZpZGVkIGRhdGFzZXQsIHlvdSBoYXZlIHRoZSB0cmFuc2NyaXB0cyBvZiAyMDAwIFlvdVR1YmUgdmlkZW9zIGVhY2ggZnJvbSBGb3hOZXdzIChhIHJpZ2h0LWxlYW5pbmcgVVMgbmV3cyBjaGFubmVsKSBhbmQgZnJvbSBUaGUgWW91bmcgVHVya3MgKGEgbGVmdC1sZWFuaW5nIFVTIG5ld3Mgb3V0bGV0KS4KCkxvYWQgdGhlIG9yaWdpbmFsIGRhdGFmcmFtZSBjYWxsZWQgYG1lZGlhX2RhdGFgIGZyb20gYGRhdGEvbWVkaWFfZGF0YS5SRGF0YWAuCgpgYGB7cn0KI3lvdXIgY29kZSBoZXJlCmxvYWQoJ2RhdGEvbWVkaWFfZGF0YS5SRGF0YScpCmBgYAoKQnVpbGQgYW4gbmdyYW0gbW9kZWwgdGhhdCBjb250YWlucyB1bmlncmFtcyBhbmQgYmlncmFtcyBhbmQgY29ycmVjdCBmb3Igc3BhcnNpdHkgc28gdGhhdCB0aGUgdG9rZW5zIGFyZSBjb250YWluZWQgaW4gYXQgbGVhc3QgMTAlIG9mIHRoZSBkb2N1bWVudHMuIE1ha2Ugc3VyZSB0byByZW1vdmUgW2FsbCBwdW5jdHVhdGlvbiwgbnVtYmVycyBhbmQgc3ltYm9sc10oaHR0cHM6Ly9kYXRhLmxpYnJhcnkudmlyZ2luaWEuZWR1L2EtYmVnaW5uZXJzLWd1aWRlLXRvLXRleHQtYW5hbHlzaXMtd2l0aC1xdWFudGVkYS8pLgoKYGBge3J9CiN5b3VyIGNvZGUgaGVyZQpsaWJyYXJ5KHF1YW50ZWRhKQoKY29ycHVzLm1lZGlhX2RhdGEgPSBjb3JwdXMoeCA9IG1lZGlhX2RhdGEkdGV4dCkKCnRva2Vucy5tZWRpYV9kYXRhID0gdG9rZW5zKGNvcnB1cy5tZWRpYV9kYXRhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICwgcmVtb3ZlX251bWJlcnMgPSBUCiAgICAgICAgICAgICAgICAgICAgICAgICAgICwgcmVtb3ZlX3B1bmN0ID0gVAogICAgICAgICAgICAgICAgICAgICAgICAgICAsIHJlbW92ZV9zeW1ib2xzID0gVAogICAgICAgICAgICAgICAgICAgICAgICAgICAsIHJlbW92ZV9oeXBoZW5zID0gVCkKCmRmbS5tZWRpYV9kYXRhID0gZGZtKHRva2Vucy5tZWRpYV9kYXRhCiAgICAgICAgICAgICAgICAgICAgICAsIG5ncmFtcyA9IDE6MgogICAgICAgICAgICAgICAgICAgICAgLCB0b2xvd2VyID0gVAogICAgICAgICAgICAgICAgICAgICAgLCBzdGVtID0gVAogICAgICAgICAgICAgICAgICAgICAgLCByZW1vdmUgPSBzdG9wd29yZHMoKQogICAgICAgICAgICAgICAgICAgICAgKQoKZGZtLm1lZGlhX2RhdGEgPSBkZm1fdHJpbShkZm0ubWVkaWFfZGF0YSwgc3BhcnNpdHkgPSAwLjkwKQoKI2NvbnZlcnQgdG8gZGF0YWZyYW1lCm5ncmFtcy5tZWRpYV9kYXRhID0gYXMuZGF0YS5mcmFtZShkZm0ubWVkaWFfZGF0YSkKCiNhZGQgb3V0Y29tZSBjb2x1bW4gYmFjayBpbgpuZ3JhbXMubWVkaWFfZGF0YSRvdXRjb21lID0gbWVkaWFfZGF0YSRwb2wKCiNyZW1vdmUgZG9jdW1lbnQgaWQgdmFyaWFibGUKbmdyYW1zLm1lZGlhX2RhdGEgPSBuZ3JhbXMubWVkaWFfZGF0YVssIC0xXQpgYGAKCiMjIFRhc2sgMjogUHJlZGljdCB3aGV0aGVyIGEgdHJhbnNjcmlwdCBjb21lcyBmcm9tYSByaWdodCBvciBsZWZ0LWxlYW5pbmcgWW91VHViZSBjaGFubmVsCgpTdGVwIDE6IHNwbGl0IHRoZSBkYXRhCgooeW91IG5lZWQgdGhlIGBjYXJldGAgcGFja2FnZSBmb3IgdGhpcyBvbmUpCgpgYGB7cn0KI3lvdXIgY29kZSBoZXJlCmxpYnJhcnkoY2FyZXQpCnNldC5zZWVkKDIwMTIpCmluX3RyYWluaW5nID0gY3JlYXRlRGF0YVBhcnRpdGlvbih5ID0gbmdyYW1zLm1lZGlhX2RhdGEkb3V0Y29tZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBwID0gLjgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgbGlzdCA9IEZBTFNFCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCgp0cmFpbmluZ19zZXQgPSBuZ3JhbXMubWVkaWFfZGF0YVtpbl90cmFpbmluZywgXQp0ZXN0X3NldCA9IG5ncmFtcy5tZWRpYV9kYXRhWy1pbl90cmFpbmluZywgXQpgYGAKCkhhdmUgYSBsb29rIGF0IHRoZSBkaW1lbnNpb25zIG9mIHlvdXIgZGF0YS4gSG93IG1hbnkgZmVhdHVyZXMgYXJlIHRoZXJlPwoKYGBge3J9CiN5b3VyIGNvZGUgaGVyZQpkaW0odGVzdF9zZXQpCmBgYAoKClN0ZXAgMjogc2V0IHlvdXIgdHJhaW5pbmcgY29udHJvbHMKCkhlcmUsIHlvdSBjYW4gZ28gZm9yIGEgay1mb2xkIHdpdGggYSBoaWdoIG51bWJlciBvZiBrIChlLmcuIDIwKS4KCk1ha2Ugc3VyZSB0byBzcGVjaWZ5IGBjbGFzc1Byb2JzID0gVGAgc2luY2Ugd2UgbmVlZCB0aGlzIGZvciBsYXRlciBBcmVhIFVuZGVyIHRoZSBDdXJ2ZSBjYWxjdWxhdGlvbnMuCgpgYGB7cn0KI3lvdXIgY29kZSBoZXJlCnRyYWluaW5nX2NvbnRyb2xzID0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgbnVtYmVyID0gMjAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBjbGFzc1Byb2JzID0gVAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCmBgYAoKClN0ZXAgMzogdHJhaW4gdGhlIG1vZGVsCgpZb3UgY2FuIHVzZSBhIExpbmVhciBTVk0sIGZvciBleGFtcGxlLgoKYGBge3J9CiN5b3VyIGNvZGUgaGVyZQpzdm0ubW9kZWwgPSB0cmFpbihvdXRjb21lIH4uCiAgICAgICAgICAgICAgICAgICwgZGF0YSA9IHRyYWluaW5nX3NldAogICAgICAgICAgICAgICAgICAsIHRyQ29udHJvbCA9IHRyYWluaW5nX2NvbnRyb2xzCiAgICAgICAgICAgICAgICAgICwgbWV0aG9kID0gInN2bUxpbmVhciIKICAgICAgICAgICAgICAgICAgKQpgYGAKClN0ZXAgNDogZml0IHRoZSBtb2RlbAoKYGBge3J9CiN5b3VyIGNvZGUgaGVyZQpzdm0ubW9kZWwucHJlZGljdGlvbnMgPSBwcmVkaWN0KHN2bS5tb2RlbCwgdGVzdF9zZXQpCmBgYAoKCiMjIFRhc2sgMzogQXNzZXNzIHRoZSBtb2RlbCBwZXJmb3JtYW5jZQoKU3RlcCAxOiBjYWxjdWxhdGUgdGhlIGFjY3VyYWN5IG9mIHlvdXIgbW9kZWwgb24gdGhlIHRlc3Qgc2V0CgpgYGB7cn0KI3lvdXIgY29kZSBoZXJlCnRhYmxlKHRlc3Rfc2V0JG91dGNvbWUsIHN2bS5tb2RlbC5wcmVkaWN0aW9ucykKYGBgCgpTdGVwIDI6IGNhbGN1bGF0ZSB0aGUgcHJlY2lzaW9uLCByZWNhbGwgYW5kIEYxIHNjb3JlcwoKU3RvcCBhbmQgdGhpbmsgZm9yIGEgc2Vjb25kOiB3aHkgZG8gd2UgbmVlZCB0aGVzZSBtZXRyaWNzIGluIGFkZGl0aW9uIHRvIHRoZSBhY2N1cmFjeT8KCmBgYHtyfQojeW91ciBjb2RlIGhlcmUKY29uZnVzaW9uTWF0cml4KHRhYmxlKHRlc3Rfc2V0JG91dGNvbWUsIHN2bS5tb2RlbC5wcmVkaWN0aW9ucykpCnByZWNpc2lvbih0YWJsZSh0ZXN0X3NldCRvdXRjb21lLCBzdm0ubW9kZWwucHJlZGljdGlvbnMpLCByZWxldmFudCA9ICdsJykKIy4uLgpgYGAKClN0ZXAgMzogY2FsY3VsYXRlIHRoZSBhcmVhIHVuZGVyIHRoZSBjdXJ2ZQoKVG8gb2J0YWluIHRoZSBhcmVhIHVuZGVyIHRoZSBjdXJ2ZSwgcmVtZW1iZXIgdGhhdCB3ZSBuZWVkIGNsYXNzIHByb2JhYmlsaXRpZXMuIFlvdSBjYW4gb2J0YWluIHRoZXNlIGJ5IGNyZWF0aW5nIGEgbmV3IHZhcmlhYmxlIHRoYXQgdXNlcyB0aGUgYHByZWRpY3RgIGZ1bmN0aW9uIHdpdGggdGhlIHBhcmFtZXRlciBgdHlwZSA9ICJwcm9iImAuCgpgYGB7cn0KI3lvdXIgY29kZSBoZXJlCnN2bS5tb2RlbC5wcm9icyA9IHByZWRpY3Qoc3ZtLm1vZGVsLCB0ZXN0X3NldCwgdHlwZSA9ICdwcm9iJylbLCAxXQpgYGAKCihIaW50OiBpZiBkb25lIGNvcnJlY3RseSwgeW91IHdpbGwgb2J0YWluIGEgZGF0YWZyYW1lIHdpdGggZWFjaCBjYXNlIG9mIHRoZSB0ZXN0IHNldCBpbiB0aGUgcm93cyBhbmQgdHdvIGNvbHVtbnMgLSBvbmUgZm9yIHRoZSBjbGFzcyBwcm9iYWJpbGl0aWVzIGluIGNsYXNzIDEgYW5kIG9uZSBmb3IgY2xhc3MgMi4gWW91IHdpbGwgc2VlIHRoYXQgdGhleSBzdW0gdG8gMSwgc28geW91IGNhbiBjaG9vc2Ugb25lIG9mIHRoZW0gZm9yIHRoZSBBVUMgY2FsY3VsYXRpb24uIFRyeSBpdCBvdXQgdG8gcHJvb2YgdGhhdCB0aGUgcmVzdWx0cyB3b24ndCBjaGFuZ2UuKQoKCk5vdyB1c2UgdGhlIGBwUk9DYCBsaWJyYXJ5IHRvIGNhbGN1bGF0ZSB0aGUgYXJlYSB1bmRlciB0aGUgY3VydmUuCgpgYGB7cn0KI3lvdXIgY29kZSBoZXJlCmxpYnJhcnkocFJPQykKYXVjLnN2bV9tb2RlbCA9IHJvYyhyZXNwb25zZSA9IHRlc3Rfc2V0JG91dGNvbWUKICAgICAgICAgICAgICAgICAgICAsIHByZWRpY3RvciA9IHN2bS5tb2RlbC5wcm9icwogICAgICAgICAgICAgICAgICAgICwgY2k9VCkKYXVjLnN2bV9tb2RlbApgYGAKClBsb3QgdGhlIGFyZWEgdW5kZXIgdGhlIGN1cnZlICh1c2luZyBgcGxvdC5yb2NgOgoKYGBge3J9CiN5b3VyIGNvZGUgaGVyZQpwbG90LnJvYyhhdWMuc3ZtX21vZGVsLCBsZWdhY3kuYXhlcyA9IFQpCmBgYAoKCldoYXQgaXMgeW91ciBjb25jbHVzaW9uIHJlLiB0aGUgbW9kZWwgeW91IGp1c3QgYnVpbHQ/CgpGaW5hbGx5OiBIYXZlIGEgbG9vayBhdCB0aGUgZmVhdHVyZXMgdGhhdCBkcml2ZSB0aGUgY2xhc3NpZmllciB1c2luZyBgdmFySW1wYC4gTm90ZSB0aGF0IHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlIG9mIGNhcmV0IHJlbGllcyBvbiBudW1lcmljYWwgb3V0Y29tZXMgLSB0aGVyZWZvcmU6IHJlLXJ1biB0aGUgbW9kZWwgYnV0IGNoYW5nZSB0aGUgdHJhaW5pbmcgc2V0IHNvIHRoYXQgdGhlIG91dGNvbWUgdmFyaWFibGUncyBsZXZlbHMgYXJlIG51bWVyaWMgKDEgYW5kIDApIGFuZCBzZXQgYGNsYXNzUHJvYnMgPSBGYCBpbiB0aGUgdHJhaW5pbmcgY29udHJvbHMuCgpPbmNlIHlvdSBpZGVudGlmaWVkIHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlcywgaGF2ZSBhIGxvb2sgaW4gd2hpY2ggY2xhc3MgdGhleSBhcmUgbW9yZSBwcmV2YWxlbnQuCgpgYGB7cn0KI3lvdXIgY29kZSBoZXJlCnRyYWluaW5nX3NldF8yID0gdHJhaW5pbmdfc2V0CnRyYWluaW5nX3NldF8yJG91dGNvbWUgPSBhcy5mYWN0b3IodHJhaW5pbmdfc2V0XzIkb3V0Y29tZSkKbGV2ZWxzKHRyYWluaW5nX3NldF8yJG91dGNvbWUpID0gYygxLCAwKQoKdHJhaW5pbmdfY29udHJvbHMgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBudW1iZXIgPSAyMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCnN2bS5tb2RlbF8yID0gdHJhaW4ob3V0Y29tZSB+LgogICAgICAgICAgICAgICAgICAsIGRhdGEgPSB0cmFpbmluZ19zZXRfMgogICAgICAgICAgICAgICAgICAsIHRyQ29udHJvbCA9IHRyYWluaW5nX2NvbnRyb2xzCiAgICAgICAgICAgICAgICAgICwgbWV0aG9kID0gInN2bUxpbmVhciIKICAgICAgICAgICAgICAgICAgKQoKI2NhbGwgdmFySW1wCnZhckltcChzdm0ubW9kZWxfMikKCnRhcHBseSh0cmFpbmluZ19zZXRfMiRnb25uYSwgdHJhaW5pbmdfc2V0XzIkb3V0Y29tZSwgbWVhbikKYGBgCgoKIyMgVGFzayA0OiBVbnN1cGVydmlzZWQgbGVhcm5pbmcgb24gdGVjaCB0aXRsZXMKCkxvYWQgdGhlIGRhdGEuZnJhbWUgYHRlY2hfdGl0bGVzYCBmcm9tIHRoZSBgdGVjaF90aXRsZXMuUkRhdGFgIG9iamVjdCBsb2NhdGVkIGluIHRoZSBgLi9kYXRhYCBkaXJlY3RvcnkuIFRoZXNlIGRhdGEgYXJlIGFsbCB0aXRsZXMgb2YgYXJ0aWNsZXMgd3JpdHRlbiBvbiB0aGUgdHdvIG1ham9yIHRlY2ggd2Vic2l0ZXMgVmVudHVyZUJlYXQgYW5kIFRlY2hDcnVuY2ggaW4gMjAxNyBbKGRhdGFzZXQgZGV0YWlscyBvbiBLYWdnbGUpXShodHRwczovL3d3dy5rYWdnbGUuY29tL1Byb21wdENsb3VkSFEvdGl0bGVzLWJ5LXRlY2hjcnVuY2gtYW5kLXZlbnR1cmViZWF0LWluLTIwMTcpLgoKWW91ciB0YXNrIGlzIHRvIHJlcHJlc2VudCB0aGVzZSB0aXRsZXMgYXMgdW5pZ3JhbXMgYW5kIGZpbmQgb3V0IHdoZXRoZXIgdGhlcmUgYXJlIGNsdXN0ZXJzIGluIHRoZSBkYXRhLgoKU3RlcCAxOiBMb2FkIHRoZSBkYXRhCgpgYGB7cn0KI3lvdXIgY29kZSBoZXJlCmxvYWQoJy4vZGF0YS90ZWNoX3RpdGxlcy5SRGF0YScpCmBgYAoKU3RlcCAyOiBDcmVhdGUgdGhlIHVuaWdyYW1zCgooYXBwbHkgcHJlcHJvY2Vzc2luZyB3aGVyZSB5b3UgdGhpbmsgdGhpcyBpcyBuZWNlc3NhcnkpCgpgYGB7cn0KI3lvdXIgY29kZSBoZXJlCmxpYnJhcnkocXVhbnRlZGEpCgpjb3JwdXMudGVjaF90aXRsZXMgPSBjb3JwdXModGVjaF90aXRsZXMkdGl0bGUpCnRva2Vucy50ZWNoX3RpdGxlcyA9IHRva2Vucyhjb3JwdXMudGVjaF90aXRsZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgcmVtb3ZlX251bWJlcnMgPSBUCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIHJlbW92ZV9wdW5jdCA9IFQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgcmVtb3ZlX3N5bWJvbHMgPSBUCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIHJlbW92ZV9oeXBoZW5zID0gVAogICAgICAgICAgICAgICAgICAgICAgICAgICAgKQpkZm0udGVjaF90aXRsZXMgPSBkZm0odG9rZW5zLnRlY2hfdGl0bGVzCiAgICAgICAgICAgICAgICAgICAgICAsIG5ncmFtcyA9IDEKICAgICAgICAgICAgICAgICAgICAgICwgdG9sb3dlciA9IFQKICAgICAgICAgICAgICAgICAgICAgICwgc3RlbSA9IFQKICAgICAgICAgICAgICAgICAgICAgICwgcmVtb3ZlID0gc3RvcHdvcmRzKCkKICAgICAgICAgICAgICAgICAgICAgICkKCmRmbS50ZWNoX3RpdGxlcyA9IGRmbV90cmltKGRmbS50ZWNoX3RpdGxlcywgc3BhcnNpdHkgPSAwLjk5KQpgYGAKClN0ZXAgMzogRGV0ZXJtaW5lIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMKClVzZSB0aGUgZWxib3cgbWV0aG9kOgoKKG5vdGU6IHlvdSB3aWxsIGdldCBhbiBlcnJvciBoZXJlLCB0cnkgdG8gZmlndXJlIG91dCB3aHkgYW5kIHNvbHZlIGl0ISkKCmBgYHtyfQojeW91ciBjb2RlIGhlcmUKZGZfZGZtLnRlY2hfdGl0bGVzID0gYXMuZGF0YS5mcmFtZShkZm0udGVjaF90aXRsZXMpWywgLTFdCgp3c3MgPSBudW1lcmljKCkKCmZvcihpIGluIDE6MjApewogIG1vZGVsX2kgPSBrbWVhbnMoeCA9IGRmX2RmbS50ZWNoX3RpdGxlcywgY2VudGVycyA9IGksIG5zdGFydCA9IDEwLCBpdGVyLm1heCA9IDEwKSAgCiAgd3NzW2ldID0gbW9kZWxfaSR0b3Qud2l0aGluc3MKfQoKcGxvdCgxOjIwLCB3c3MpCmBgYAoKClN0ZXAgNDogQnVpbGQgdGhlIGZpbmFsIG1vZGVsCgpgYGB7cn0KI3lvdXIgY29kZSBoZXJlCmttZWFuc19tb2RlbCA9IGttZWFucyh4ID0gZGZfZGZtLnRlY2hfdGl0bGVzCiAgICAgICAgICAgICAgICAgICAgICAsIGNlbnRlcnMgPSA0CiAgICAgICAgICAgICAgICAgICAgICAsIG5zdGFydCA9IDEwCiAgICAgICAgICAgICAgICAgICAgICAsIGl0ZXIubWF4ID0gMjApCmBgYAoKU3RlcCA1OiBJbnRlcnByZXQgdGhlIGNsYXNzIG1lbWJlcnNoaXAKClRpcDogCgotIGFzc2lnbiB0aGUgY2x1c3RlciBtZW1iZXJzaGlwIHRvIGEgY29sdW1uIGluIHRoZSBvcmlnaW5hbCBkYXRhZnJhbWUKLSB0aGVuIGFnZ3JlZ2F0ZSB0aGUgdW5pZ3JhbSBmcmVxdWVuY2llcyBieSBjbHVzdGVyCiAgICAtIHRoaXMgcmV0dXJucyB0aGUgYXZlcmFnZSBmcmVxIHBlciB1bmlncmFtIHBlciBjbHVzdGVyCi0gbm93IHNvcnQgdGhlIGZyZXF1ZW5jaWVzIHBlciBjbHVzdGVyIHNlcGFyYXRlbHkgdG8gc2VlIHdoYXQgdGhlIGNsdXN0ZXJzIGFyZSBhYm91dAoKYGBge3J9CiN5b3VyIGNvZGUgaGVyZQpkZl9kZm0udGVjaF90aXRsZXMkY2x1c3RlciA9IGttZWFuc19tb2RlbCRjbHVzdGVyCgphZ2dyZWdhdGVkX2NsdXN0ZXJzID0gYWdncmVnYXRlKGRmX2RmbS50ZWNoX3RpdGxlcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgYnkgPSBsaXN0KGRmX2RmbS50ZWNoX3RpdGxlcyRjbHVzdGVyKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgbWVhbikKCmFnZ3JlZ2F0ZWRfY2x1c3RlcnMgPSBhZ2dyZWdhdGVkX2NsdXN0ZXJzWywgLWMoMSwgMTIwKV0KCmNsdXN0ZXIxX29yZGVyZWQgPSBzb3J0KGFnZ3JlZ2F0ZWRfY2x1c3RlcnNbMSxdLCBkZWNyZWFzaW5nID0gVCkKY2x1c3RlcjJfb3JkZXJlZCA9IHNvcnQoYWdncmVnYXRlZF9jbHVzdGVyc1syLF0sIGRlY3JlYXNpbmcgPSBUKQpjbHVzdGVyM19vcmRlcmVkID0gc29ydChhZ2dyZWdhdGVkX2NsdXN0ZXJzWzMsXSwgZGVjcmVhc2luZyA9IFQpCmNsdXN0ZXI0X29yZGVyZWQgPSBzb3J0KGFnZ3JlZ2F0ZWRfY2x1c3RlcnNbNCxdLCBkZWNyZWFzaW5nID0gVCkKYGBgCgoKCi0tLQoKIyMgRU5E