主要議題:依字頻表對文章分群
學習重點:
- 依字頻表對文章分群
- 層級式集群分析:Hierarchical Cluster Analysis
- 依據樹狀圖決定要分多少群
- 依據應用決定要分多少群
- K-Means集群分析:K-Means Cluster Analysis
- 從常見字辭推論文集的主題
小組討論:
rm(list=ls(all=T))
Sys.setlocale("LC_ALL","C")
options(digits=4, scipen=12)
library(dplyr)
1. Hierarchical Clustering
1.1 字頻表、距離矩陣、階層式集群分析
Let’s start by building a hierarchical clustering model. First, read the data set into R. Then, compute the distances (using method=“euclidean”), and use hclust to build the model (using method=“ward.D”). You should cluster on all of the variables.
MIT做法
# dailykos = read.csv("data/dailykos.csv")
# kosDist = dist(dailykos, method="euclidean")
# kosHierClust = hclust(kosDist, method="ward.D")
D = read.csv('data/dailykos.csv')
Warning message:
In scan(file = file, what = what, sep = sep, quote = quote, dec = dec, :
EOF within quoted string
dim(D)
[1] 3430 1545
# 字頻表: Document Term Matrix
D[1:20, 1:10]
# 距離矩陣: Distance Matrix
t0 = Sys.time()
d = dist(D, method="euclidean")
Sys.time() - t0
1.1小組討論
用歐幾里德方法method=“euclidean”,計算資料點的距離distance的矩陣,並儲存成d物件。
Running the dist function will probably take you a while. Why? Select all that apply.
We have a lot of observations, so it takes a long time to compute the distance between each pair of observations.
We have a lot of variables, so the distance computation is long.
# 階層式集群分析: Hierarchical Clustering Analysis
t0 = Sys.time()
hc = hclust(d, method='ward.D')
Sys.time() - t0
Plot the dendrogram of your hierarchical clustering model.
plot(hc)
1.2 從樹狀圖判斷群數
Just looking at the dendrogram,
MIT做法
plot(kosHierClust)
which of the following seem like good choices for the number of clusters? Select all that apply.
1.2小組討論
根據樹狀圖,選擇分成2個或3個群落,垂直的距離很長,代表有很大的空間,群與群之間差異很大,分配掉很多的資料量,
1.3 從應用決定群數
In this problem, we are trying to cluster news articles or blog posts into groups. This can be used to show readers categories to choose from when trying to decide what to read. Just thinking about this application,
分成2或3群會太廣泛而沒什麼幫助,分成7或8群比較洽當合理。
what are good choices for the number of clusters? Select all that apply.
1.4 依群組分割資料
MIT做法
rr L[[1]] %>% colMeans %>% sort %>% tail
state republican poll democrat kerry bush
0.7575 0.7591 0.9036 0.9194 1.0624 1.7054
1.4小組討論
用cutree function把資料spilt成七個group 。
Let’s pick 7 clusters. This number is reasonable according to the dendrogram, and also seems reasonable for the application. Use the cutree function to split your data into 7 clusters.
kg = cutree(hc, k=7)
L = split(D, kg)
Now, we don’t really want to run tapply on every single variable when we have over 1,000 different variables. Let’s instead use the subset function to subset our data by cluster. Create 7 new datasets, each containing the observations from one of the clusters.
How many observations are in cluster 3?
nrow(L[[3]])
table(kg) %>% sort
Which cluster has the most observations?
Which cluster has the fewest observations?
1.5 找出第一族群中最常見的字辭
Instead of looking at the average value in each variable individually, we’ll just look at the top 6 words in each cluster. To do this for cluster 1, type the following in your R console (where “HierCluster1” should be replaced with the name of your first cluster subset):
tail(sort(colMeans(HierCluster1)))
This computes the mean frequency values of each of the words in cluster 1, and then outputs the 6 words that occur the most frequently. The colMeans function computes the column (word) means, the sort function orders the words in increasing order of the mean values, and the tail function outputs the last 6 words listed, which are the ones with the largest column means.
MIT做法
tail(sort(colMeans(HierCluster1)))
print("bush")
What is the most frequent word in this cluster, in terms of average value? Enter the word exactly how you see it in the output:
L[[1]] %>% colMeans %>% sort %>% tail
1.6 找出各族群中最常見的字辭
Now repeat the command given in the previous problem for each of the other clusters, and answer the following questions.
MIT
tail(sort(colMeans(HierCluster2)))
tail(sort(colMeans(HierCluster3)))
tail(sort(colMeans(HierCluster4)))
tail(sort(colMeans(HierCluster5)))
tail(sort(colMeans(HierCluster6)))
tail(sort(colMeans(HierCluster7)))
sapply(L, function(x) x %>% colMeans %>% sort %>% tail %>% names) %>% t
Which words best describe cluster 2?
Which cluster could best be described as the cluster related to the Iraq war?
In 2004, one of the candidates for the Democratic nomination for the President of the United States was Howard Dean, John Kerry was the candidate who won the democratic nomination, and John Edwards with the running mate of John Kerry (the Vice President nominee). Given this information,
which cluster best corresponds to the democratic party?
2 K-Means Clustering
2.1 K-Means集群分析
Now, run k-means clustering, setting the seed to 1000 right before you run the kmeans function. Again, pick the number of clusters equal to 7. You don’t need to add the iters.max argument.
MIT
set.seed(1000)
KmeansCluster = kmeans(dailykos, centers=7)
KmeansCluster1 = subset(dailykos, KmeansCluster$cluster == 1)
KmeansCluster2 = subset(dailykos, KmeansCluster$cluster == 2)
KmeansCluster3 = subset(dailykos, KmeansCluster$cluster == 3)
KmeansCluster4 = subset(dailykos, KmeansCluster$cluster == 4)
KmeansCluster5 = subset(dailykos, KmeansCluster$cluster == 5)
KmeansCluster6 = subset(dailykos, KmeansCluster$cluster == 6)
KmeansCluster7 = subset(dailykos, KmeansCluster$cluster == 7)
set.seed(1000)
km = kmeans(D, 7)
kg2 = km$cluster
table(km$cluster) %>% sort
Subset your data into the 7 clusters (7 new datasets) by using the “cluster” variable of your kmeans output.
How many observations are in Cluster 3?
Which cluster has the most observations?
Which cluster has the fewest number of observations?
2.2 找出各族群中最常見的字辭
Now, output the six most frequent words in each cluster, like we did in the previous problem, for each of the k-means clusters.
MIT
tail(sort(colMeans(KmeansCluster1)))
tail(sort(colMeans(KmeansCluster2)))
tail(sort(colMeans(KmeansCluster3)))
tail(sort(colMeans(KmeansCluster4)))
tail(sort(colMeans(KmeansCluster5)))
tail(sort(colMeans(KmeansCluster6)))
tail(sort(colMeans(KmeansCluster7)))
split(D, kg2) %>% sapply(function(x)
x %>% colMeans %>% sort %>% tail %>% names) %>% t
Which k-means cluster best corresponds to the Iraq War?
Which k-means cluster best corresponds to the democratic party? (Remember that we are looking for the names of the key democratic party leaders.)
2.3 ~ 2.6 兩種分群結果之間的對應關係
For the rest of this problem, we’ll ask you to compare how observations were assigned to clusters in the two different methods. Use the table function to compare the cluster assignment of hierarchical clustering to the cluster assignment of k-means clustering.
MIT
table(hierGroups, KmeansCluster$cluster)
table(Hierarchical=kg, KMeans=kg2)
Which Hierarchical Cluster best corresponds to K-Means Cluster 2?
Which Hierarchical Cluster best corresponds to K-Means Cluster 3?
Which Hierarchical Cluster best corresponds to K-Means Cluster 7?
- No Hierarchical Cluster contains at least half of the points in K-Means Cluster 7.
Which Hierarchical Cluster best corresponds to K-Means Cluster 6?
【討論問題】
字頻表是什麼?它的資料格式?
- 字頻表用Document term matrix表示,將所有document的所有字攤開來放在column,row是每個document。row 1的abandon欄位是0意思是documment1沒有abandon 這個字;row 3的abstain欄位是1意思是abstain這個字出現在document3 1次。其他的每個值以此規則類推。
- 他是一個Document term matrix,我們以class函數可以看到在R是用data.frame的資料型態存放。
使用字頻表作集群分析時,區隔變數是什麼?
- 資料裡面colnames(變數)所出現在數量,藉由次數高低,做為是否區隔的門檻。
- 數量為字頻,數字越大表示該字出現在document的次數越高。
從樹狀圖判斷群數和從應用需求決定群數有什麼差別?
- 一般以樹狀圖所出現的推薦的群數並不一定和你最後所做的決策群數完全相符,有時還是得考慮到現實面。比如樹狀圖最好的方式是三群(三群時高度差最大),可是考慮到現實面假設資料庫太大,三群可能沒辦法有效分類,有時得選擇更多群去解釋資料。
- 有時候可能用樹狀圖告訴我們分成10群很棒,但有時候我們做決策可能只是希望依照不同族群的特性制定一些不同的方案。所以我們自己衡量要將群數增加還是減少,以對應我們想要做的決策數量。
小組心得
透過Hierarchical Clustering可以幫助我們將大筆資料分成數個群聚,並從樹狀圖可以觀察到最好的分群數量,但實際上的應用,還是得經由自身經驗判斷適當的分群數,並非一味的相信樹狀圖顯示的結果,此外,還可以透過階層分群來得知該群聚中,最常出現的字詞有哪些,從而知道這些群聚的特性。
LS0tDQp0aXRsZTogIkFTNi0xIERhaWx5IEtvc+aWh+eroOWIhue+pCINCmF1dGhvcjogIkdST1VQIDEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQo8YnI+DQoNCioq5Li76KaB6K2w6aGM77ya5L6d5a2X6aC76KGo5bCN5paH56ug5YiG576kKioNCg0KKirlrbjnv5Lph43pu57vvJoqKg0KDQorIOS+neWtl+mgu+ihqOWwjeaWh+eroOWIhue+pA0KKyDlsaTntJrlvI/pm4bnvqTliIbmnpDvvJpIaWVyYXJjaGljYWwgQ2x1c3RlciBBbmFseXNpcw0KKyDkvp3mk5rmqLnni4DlnJbmsbrlrpropoHliIblpJrlsJHnvqQNCisg5L6d5pOa5oeJ55So5rG65a6a6KaB5YiG5aSa5bCR576kDQorIEstTWVhbnPpm4bnvqTliIbmnpDvvJpLLU1lYW5zIENsdXN0ZXIgQW5hbHlzaXMNCisg5b6e5bi46KaL5a2X6L6t5o6o6KuW5paH6ZuG55qE5Li76aGMDQoNCg0KKirlsI/ntYToqI7oq5bvvJoqKg0KDQorIDxhIGhyZWY9JyNEMS4xJz4xLjE8L2E+DQorIDxhIGhyZWY9JyNEMS4yJz4xLjI8L2E+DQorIDxhIGhyZWY9JyNEMS40Jz4xLjQ8L2E+DQorIDxhIGhyZWY9JyNDTCc+5bCP57WE5b+D5b6XPC9hPg0KYGBge3IgZWNobz1ULCBtZXNzYWdlPUYsIGNhY2hlPUYsIHdhcm5pbmc9Rn0NCnJtKGxpc3Q9bHMoYWxsPVQpKQ0KU3lzLnNldGxvY2FsZSgiTENfQUxMIiwiQyIpDQpvcHRpb25zKGRpZ2l0cz00LCBzY2lwZW49MTIpDQpsaWJyYXJ5KGRwbHlyKQ0KYGBgDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyAxLiBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZw0KDQojIyMjIyAxLjEg5a2X6aC76KGo44CB6Led6Zui55+p6Zmj44CB6ZqO5bGk5byP6ZuG576k5YiG5p6QDQpMZXQncyBzdGFydCBieSBidWlsZGluZyBhIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG1vZGVsLiBGaXJzdCwgcmVhZCB0aGUgZGF0YSBzZXQgaW50byBSLiBUaGVuLCBjb21wdXRlIHRoZSBkaXN0YW5jZXMgKHVzaW5nIG1ldGhvZD0iZXVjbGlkZWFuIiksIGFuZCB1c2UgaGNsdXN0IHRvIGJ1aWxkIHRoZSBtb2RlbCAodXNpbmcgbWV0aG9kPSJ3YXJkLkQiKS4gWW91IHNob3VsZCBjbHVzdGVyIG9uIGFsbCBvZiB0aGUgdmFyaWFibGVzLg0KDQojIyNNSVTlgZrms5UNCmBgYHtyfQ0KIGRhaWx5a29zID0gcmVhZC5jc3YoImRhdGEvZGFpbHlrb3MuY3N2IikNCiBrb3NEaXN0ID0gZGlzdChkYWlseWtvcywgbWV0aG9kPSJldWNsaWRlYW4iKQ0KIGtvc0hpZXJDbHVzdCA9IGhjbHVzdChrb3NEaXN0LCBtZXRob2Q9IndhcmQuRCIpDQpgYGANCg0KDQpgYGB7cn0NCkQgPSByZWFkLmNzdignZGF0YS9kYWlseWtvcy5jc3YnKQ0KZGltKEQpDQpgYGANCg0KYGBge3J9DQojIOWtl+mgu+ihqDogRG9jdW1lbnQgVGVybSBNYXRyaXgNCkRbMToyMCwgMToxMF0NCmBgYA0KDQpgYGB7cn0NCiMg6Led6Zui55+p6ZmjOiBEaXN0YW5jZSBNYXRyaXgNCnQwID0gU3lzLnRpbWUoKQ0KZCA9IGRpc3QoRCwgbWV0aG9kPSJldWNsaWRlYW4iKQ0KU3lzLnRpbWUoKSAtIHQwDQpgYGANCg0KIyMjIyMjICA8c3BhbiBpZD0nRDEuMSc+MS4x5bCP57WE6KiO6KuWPC9zcGFuPg0K55So5q2Q5bm+6YeM5b635pa55rOVbWV0aG9kPSJldWNsaWRlYW4i77yM6KiI566X6LOH5paZ6bue55qE6Led6ZuiZGlzdGFuY2XnmoTnn6npmaPvvIzkuKblhLLlrZjmiJBk54mp5Lu244CCDQoNCl9SdW5uaW5nIHRoZSBkaXN0IGZ1bmN0aW9uIHdpbGwgcHJvYmFibHkgdGFrZSB5b3UgYSB3aGlsZS4gV2h5P18gU2VsZWN0IGFsbCB0aGF0IGFwcGx5Lg0KDQorIFdlIGhhdmUgYSBsb3Qgb2Ygb2JzZXJ2YXRpb25zLCBzbyBpdCB0YWtlcyBhIGxvbmcgdGltZSB0byBjb21wdXRlIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIGVhY2ggcGFpciBvZiBvYnNlcnZhdGlvbnMuDQoNCisgV2UgaGF2ZSBhIGxvdCBvZiB2YXJpYWJsZXMsIHNvIHRoZSBkaXN0YW5jZSBjb21wdXRhdGlvbiBpcyBsb25nLg0KDQoNCmBgYHtyfQ0KIyDpmo7lsaTlvI/pm4bnvqTliIbmnpA6IEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIEFuYWx5c2lzDQp0MCA9IFN5cy50aW1lKCkNCmhjID0gaGNsdXN0KGQsIG1ldGhvZD0nd2FyZC5EJykNClN5cy50aW1lKCkgLSB0MA0KYGBgDQoNClBsb3QgdGhlIGRlbmRyb2dyYW0gb2YgeW91ciBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBtb2RlbC4gDQpgYGB7cn0NCnBsb3QoaGMpDQpgYGANCg0KIyMjIyMgMS4yIOW+nuaoueeLgOWcluWIpOaWt+e+pOaVuA0KSnVzdCBsb29raW5nIGF0IHRoZSBkZW5kcm9ncmFtLCANCg0KIyMjTUlU5YGa5rOVDQpgYGB7cn0NCnBsb3Qoa29zSGllckNsdXN0KQ0KYGBgDQoNCg0KX3doaWNoIG9mIHRoZSBmb2xsb3dpbmcgc2VlbSBsaWtlIGdvb2QgY2hvaWNlcyBmb3IgdGhlIG51bWJlciBvZiBjbHVzdGVycz9fIFNlbGVjdCBhbGwgdGhhdCBhcHBseS4NCg0KKyAyDQorIDMNCisgDQoNCiMjIyMjIyA8c3BhbiBpZD0nRDEuMic+MS4y5bCP57WE6KiO6KuWPC9zcGFuPg0K5qC55pOa5qi554uA5ZyW77yM6YG45pOH5YiG5oiQMuWAi+aIljPlgIvnvqTokL3vvIzlnoLnm7TnmoTot53pm6LlvojplbfvvIzku6PooajmnInlvojlpKfnmoTnqbrplpPvvIznvqToiIfnvqTkuYvplpPlt67nlbDlvojlpKfvvIzliIbphY3mjonlvojlpJrnmoTos4fmlpnph4/vvIwNCg0KIyMjIyMgMS4zIOW+nuaHieeUqOaxuuWumue+pOaVuA0KSW4gdGhpcyBwcm9ibGVtLCB3ZSBhcmUgdHJ5aW5nIHRvIGNsdXN0ZXIgbmV3cyBhcnRpY2xlcyBvciBibG9nIHBvc3RzIGludG8gZ3JvdXBzLiBUaGlzIGNhbiBiZSB1c2VkIHRvIHNob3cgcmVhZGVycyBjYXRlZ29yaWVzIHRvIGNob29zZSBmcm9tIHdoZW4gdHJ5aW5nIHRvIGRlY2lkZSB3aGF0IHRvIHJlYWQuIEp1c3QgdGhpbmtpbmcgYWJvdXQgdGhpcyBhcHBsaWNhdGlvbiwgDQoNCiMjIyMjI+WIhuaIkDLmiJYz576k5pyD5aSq5buj5rOb6ICM5rKS5LuA6bq85bmr5Yqp77yM5YiG5oiQN+aIljjnvqTmr5TovIPmtL3nlbblkIjnkIbjgIINCg0KX3doYXQgYXJlIGdvb2QgY2hvaWNlcyBmb3IgdGhlIG51bWJlciBvZiBjbHVzdGVycz9fIFNlbGVjdCBhbGwgdGhhdCBhcHBseS4NCg0KKyA3DQorIDgNCisgDQoNCiMjIyMjIDEuNCDkvp3nvqTntYTliIblibLos4fmlpkNCg0KIyMjTUlU5YGa5rOVDQpgYGB7cn0NCmhpZXJHcm91cHMgPSBjdXRyZWUoa29zSGllckNsdXN0LCBrID0gNykNCkhpZXJDbHVzdGVyMSA9IHN1YnNldChkYWlseWtvcywgaGllckdyb3VwcyA9PSAxKQ0KSGllckNsdXN0ZXIyID0gc3Vic2V0KGRhaWx5a29zLCBoaWVyR3JvdXBzID09IDIpDQpIaWVyQ2x1c3RlcjMgPSBzdWJzZXQoZGFpbHlrb3MsIGhpZXJHcm91cHMgPT0gMykNCkhpZXJDbHVzdGVyNCA9IHN1YnNldChkYWlseWtvcywgaGllckdyb3VwcyA9PSA0KQ0KSGllckNsdXN0ZXI1ID0gc3Vic2V0KGRhaWx5a29zLCBoaWVyR3JvdXBzID09IDUpDQpIaWVyQ2x1c3RlcjYgPSBzdWJzZXQoZGFpbHlrb3MsIGhpZXJHcm91cHMgPT0gNikNCkhpZXJDbHVzdGVyNyA9IHN1YnNldChkYWlseWtvcywgaGllckdyb3VwcyA9PSA3KQ0KdGFibGUoaGllckdyb3VwcykNCkhpZXJDbHVzdGVyID0gc3BsaXQoZGFpbHlrb3MsIGhpZXJHcm91cHMpDQpgYGANCg0KIyMjIyMjIDxzcGFuIGlkPSdEMS40Jz4xLjTlsI/ntYToqI7oq5Y8L3NwYW4+DQrnlKhjdXRyZWUgZnVuY3Rpb27mioros4fmlplzcGlsdOaIkOS4g+WAi2dyb3VwIOOAgg0KDQpMZXQncyBwaWNrIDcgY2x1c3RlcnMuIFRoaXMgbnVtYmVyIGlzIHJlYXNvbmFibGUgYWNjb3JkaW5nIHRvIHRoZSBkZW5kcm9ncmFtLCBhbmQgYWxzbyBzZWVtcyByZWFzb25hYmxlIGZvciB0aGUgYXBwbGljYXRpb24uIFVzZSB0aGUgY3V0cmVlIGZ1bmN0aW9uIHRvIHNwbGl0IHlvdXIgZGF0YSBpbnRvIDcgY2x1c3RlcnMuDQpgYGB7cn0NCmtnID0gY3V0cmVlKGhjLCBrPTcpDQpMID0gc3BsaXQoRCwga2cpDQpgYGANCk5vdywgd2UgZG9uJ3QgcmVhbGx5IHdhbnQgdG8gcnVuIHRhcHBseSBvbiBldmVyeSBzaW5nbGUgdmFyaWFibGUgd2hlbiB3ZSBoYXZlIG92ZXIgMSwwMDAgZGlmZmVyZW50IHZhcmlhYmxlcy4gTGV0J3MgaW5zdGVhZCB1c2UgdGhlIHN1YnNldCBmdW5jdGlvbiB0byBzdWJzZXQgb3VyIGRhdGEgYnkgY2x1c3Rlci4gQ3JlYXRlIDcgbmV3IGRhdGFzZXRzLCBlYWNoIGNvbnRhaW5pbmcgdGhlIG9ic2VydmF0aW9ucyBmcm9tIG9uZSBvZiB0aGUgY2x1c3RlcnMuDQoNCl9Ib3cgbWFueSBvYnNlcnZhdGlvbnMgYXJlIGluIGNsdXN0ZXIgMz9fDQpgYGB7cn0NCm5yb3coTFtbM11dKQ0KYGBgDQoNCmBgYHtyfQ0KdGFibGUoa2cpICU+JSBzb3J0DQpgYGANCg0KX1doaWNoIGNsdXN0ZXIgaGFzIHRoZSBtb3N0IG9ic2VydmF0aW9ucz9fDQoNCisgMQ0KKw0KDQpfV2hpY2ggY2x1c3RlciBoYXMgdGhlIGZld2VzdCBvYnNlcnZhdGlvbnM/Xw0KDQorIDQNCisNCg0KIyMjIyMgMS41IOaJvuWHuuesrOS4gOaXj+e+pOS4reacgOW4uOimi+eahOWtl+i+rQ0KSW5zdGVhZCBvZiBsb29raW5nIGF0IHRoZSBhdmVyYWdlIHZhbHVlIGluIGVhY2ggdmFyaWFibGUgaW5kaXZpZHVhbGx5LCB3ZSdsbCBqdXN0IGxvb2sgYXQgdGhlIHRvcCA2IHdvcmRzIGluIGVhY2ggY2x1c3Rlci4gVG8gZG8gdGhpcyBmb3IgY2x1c3RlciAxLCB0eXBlIHRoZSBmb2xsb3dpbmcgaW4geW91ciBSIGNvbnNvbGUgKHdoZXJlICJIaWVyQ2x1c3RlcjEiIHNob3VsZCBiZSByZXBsYWNlZCB3aXRoIHRoZSBuYW1lIG9mIHlvdXIgZmlyc3QgY2x1c3RlciBzdWJzZXQpOg0KDQp0YWlsKHNvcnQoY29sTWVhbnMoSGllckNsdXN0ZXIxKSkpDQoNClRoaXMgY29tcHV0ZXMgdGhlIG1lYW4gZnJlcXVlbmN5IHZhbHVlcyBvZiBlYWNoIG9mIHRoZSB3b3JkcyBpbiBjbHVzdGVyIDEsIGFuZCB0aGVuIG91dHB1dHMgdGhlIDYgd29yZHMgdGhhdCBvY2N1ciB0aGUgbW9zdCBmcmVxdWVudGx5LiBUaGUgY29sTWVhbnMgZnVuY3Rpb24gY29tcHV0ZXMgdGhlIGNvbHVtbiAod29yZCkgbWVhbnMsIHRoZSBzb3J0IGZ1bmN0aW9uIG9yZGVycyB0aGUgd29yZHMgaW4gaW5jcmVhc2luZyBvcmRlciBvZiB0aGUgbWVhbiB2YWx1ZXMsIGFuZCB0aGUgdGFpbCBmdW5jdGlvbiBvdXRwdXRzIHRoZSBsYXN0IDYgd29yZHMgbGlzdGVkLCB3aGljaCBhcmUgdGhlIG9uZXMgd2l0aCB0aGUgbGFyZ2VzdCBjb2x1bW4gbWVhbnMuDQoNCiMjI01JVOWBmuazlQ0KYGBge3J9DQp0YWlsKHNvcnQoY29sTWVhbnMoSGllckNsdXN0ZXIxKSkpDQpwcmludCgiYnVzaCIpDQpgYGANCg0KDQpfV2hhdCBpcyB0aGUgbW9zdCBmcmVxdWVudCB3b3JkIGluIHRoaXMgY2x1c3RlciwgaW4gdGVybXMgb2YgYXZlcmFnZSB2YWx1ZT9fIEVudGVyIHRoZSB3b3JkIGV4YWN0bHkgaG93IHlvdSBzZWUgaXQgaW4gdGhlIG91dHB1dDoNCmBgYHtyfQ0KTFtbMV1dICU+JSBjb2xNZWFucyAlPiUgc29ydCAlPiUgdGFpbA0KYGBgDQoNCiMjIyMjIDEuNiDmib7lh7rlkITml4/nvqTkuK3mnIDluLjopovnmoTlrZfovq0NCk5vdyByZXBlYXQgdGhlIGNvbW1hbmQgZ2l2ZW4gaW4gdGhlIHByZXZpb3VzIHByb2JsZW0gZm9yIGVhY2ggb2YgdGhlIG90aGVyIGNsdXN0ZXJzLCBhbmQgYW5zd2VyIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb25zLg0KDQoNCiMjI01JVA0KDQpgYGB7cn0NCnRhaWwoc29ydChjb2xNZWFucyhIaWVyQ2x1c3RlcjIpKSkNCnRhaWwoc29ydChjb2xNZWFucyhIaWVyQ2x1c3RlcjMpKSkNCnRhaWwoc29ydChjb2xNZWFucyhIaWVyQ2x1c3RlcjQpKSkNCnRhaWwoc29ydChjb2xNZWFucyhIaWVyQ2x1c3RlcjUpKSkNCnRhaWwoc29ydChjb2xNZWFucyhIaWVyQ2x1c3RlcjYpKSkNCnRhaWwoc29ydChjb2xNZWFucyhIaWVyQ2x1c3RlcjcpKSkNCmBgYA0KDQoNCmBgYHtyfQ0Kc2FwcGx5KEwsIGZ1bmN0aW9uKHgpIHggJT4lIGNvbE1lYW5zICU+JSBzb3J0ICU+JSB0YWlsICU+JSBuYW1lcykgJT4lIHQNCmBgYA0KDQpfV2hpY2ggd29yZHMgYmVzdCBkZXNjcmliZSBjbHVzdGVyIDI/Xw0KDQorIG5vdmVtYmVyLCBwb2xsLCB2b3RlDQorDQoNCl9XaGljaCBjbHVzdGVyIGNvdWxkIGJlc3QgYmUgZGVzY3JpYmVkIGFzIHRoZSBjbHVzdGVyIHJlbGF0ZWQgdG8gdGhlIElyYXEgd2FyP18NCg0KKyA1DQorDQoNCkluIDIwMDQsIG9uZSBvZiB0aGUgY2FuZGlkYXRlcyBmb3IgdGhlIERlbW9jcmF0aWMgbm9taW5hdGlvbiBmb3IgdGhlIFByZXNpZGVudCBvZiB0aGUgVW5pdGVkIFN0YXRlcyB3YXMgSG93YXJkIERlYW4sIEpvaG4gS2Vycnkgd2FzIHRoZSBjYW5kaWRhdGUgd2hvIHdvbiB0aGUgZGVtb2NyYXRpYyBub21pbmF0aW9uLCBhbmQgSm9obiBFZHdhcmRzIHdpdGggdGhlIHJ1bm5pbmcgbWF0ZSBvZiBKb2huIEtlcnJ5ICh0aGUgVmljZSBQcmVzaWRlbnQgbm9taW5lZSkuIEdpdmVuIHRoaXMgaW5mb3JtYXRpb24sIA0KDQpfd2hpY2ggY2x1c3RlciBiZXN0IGNvcnJlc3BvbmRzIHRvIHRoZSBkZW1vY3JhdGljIHBhcnR5P18NCg0KKyA3DQorDQoNCjxicj4NCg0KLSAtIC0NCg0KIyMjIDIgSy1NZWFucyBDbHVzdGVyaW5nDQoNCiMjIyMjIDIuMSBLLU1lYW5z6ZuG576k5YiG5p6QDQpOb3csIHJ1biBrLW1lYW5zIGNsdXN0ZXJpbmcsIHNldHRpbmcgdGhlIHNlZWQgdG8gMTAwMCByaWdodCBiZWZvcmUgeW91IHJ1biB0aGUga21lYW5zIGZ1bmN0aW9uLiBBZ2FpbiwgcGljayB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGVxdWFsIHRvIDcuIFlvdSBkb24ndCBuZWVkIHRvIGFkZCB0aGUgaXRlcnMubWF4IGFyZ3VtZW50Lg0KDQojIyNNSVQNCmBgYHtyfQ0Kc2V0LnNlZWQoMTAwMCkNCkttZWFuc0NsdXN0ZXIgPSBrbWVhbnMoZGFpbHlrb3MsIGNlbnRlcnM9NykNCkttZWFuc0NsdXN0ZXIxID0gc3Vic2V0KGRhaWx5a29zLCBLbWVhbnNDbHVzdGVyJGNsdXN0ZXIgPT0gMSkNCkttZWFuc0NsdXN0ZXIyID0gc3Vic2V0KGRhaWx5a29zLCBLbWVhbnNDbHVzdGVyJGNsdXN0ZXIgPT0gMikNCkttZWFuc0NsdXN0ZXIzID0gc3Vic2V0KGRhaWx5a29zLCBLbWVhbnNDbHVzdGVyJGNsdXN0ZXIgPT0gMykNCkttZWFuc0NsdXN0ZXI0ID0gc3Vic2V0KGRhaWx5a29zLCBLbWVhbnNDbHVzdGVyJGNsdXN0ZXIgPT0gNCkNCkttZWFuc0NsdXN0ZXI1ID0gc3Vic2V0KGRhaWx5a29zLCBLbWVhbnNDbHVzdGVyJGNsdXN0ZXIgPT0gNSkNCkttZWFuc0NsdXN0ZXI2ID0gc3Vic2V0KGRhaWx5a29zLCBLbWVhbnNDbHVzdGVyJGNsdXN0ZXIgPT0gNikNCkttZWFuc0NsdXN0ZXI3ID0gc3Vic2V0KGRhaWx5a29zLCBLbWVhbnNDbHVzdGVyJGNsdXN0ZXIgPT0gNykNCmBgYA0KDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTAwMCkNCmttID0ga21lYW5zKEQsIDcpDQprZzIgPSBrbSRjbHVzdGVyDQp0YWJsZShrbSRjbHVzdGVyKSAlPiUgc29ydA0KYGBgDQoNClN1YnNldCB5b3VyIGRhdGEgaW50byB0aGUgNyBjbHVzdGVycyAoNyBuZXcgZGF0YXNldHMpIGJ5IHVzaW5nIHRoZSAiY2x1c3RlciIgdmFyaWFibGUgb2YgeW91ciBrbWVhbnMgb3V0cHV0Lg0KDQpfSG93IG1hbnkgb2JzZXJ2YXRpb25zIGFyZSBpbiBDbHVzdGVyIDM/Xw0KDQorIDI3Nw0KKyANCg0KX1doaWNoIGNsdXN0ZXIgaGFzIHRoZSBtb3N0IG9ic2VydmF0aW9ucz9fDQoNCisgNA0KKw0KDQpfV2hpY2ggY2x1c3RlciBoYXMgdGhlIGZld2VzdCBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zP18NCg0KKyAyDQorDQoNCiMjIyMjIDIuMiDmib7lh7rlkITml4/nvqTkuK3mnIDluLjopovnmoTlrZfovq0NCk5vdywgb3V0cHV0IHRoZSBzaXggbW9zdCBmcmVxdWVudCB3b3JkcyBpbiBlYWNoIGNsdXN0ZXIsIGxpa2Ugd2UgZGlkIGluIHRoZSBwcmV2aW91cyBwcm9ibGVtLCBmb3IgZWFjaCBvZiB0aGUgay1tZWFucyBjbHVzdGVycy4NCg0KIyMjTUlUDQpgYGB7cn0NCnRhaWwoc29ydChjb2xNZWFucyhLbWVhbnNDbHVzdGVyMSkpKQ0KdGFpbChzb3J0KGNvbE1lYW5zKEttZWFuc0NsdXN0ZXIyKSkpDQp0YWlsKHNvcnQoY29sTWVhbnMoS21lYW5zQ2x1c3RlcjMpKSkNCnRhaWwoc29ydChjb2xNZWFucyhLbWVhbnNDbHVzdGVyNCkpKQ0KdGFpbChzb3J0KGNvbE1lYW5zKEttZWFuc0NsdXN0ZXI1KSkpDQp0YWlsKHNvcnQoY29sTWVhbnMoS21lYW5zQ2x1c3RlcjYpKSkNCnRhaWwoc29ydChjb2xNZWFucyhLbWVhbnNDbHVzdGVyNykpKQ0KYGBgDQoNCg0KYGBge3J9DQpzcGxpdChELCBrZzIpICU+JSBzYXBwbHkoZnVuY3Rpb24oeCkgDQogIHggJT4lIGNvbE1lYW5zICU+JSBzb3J0ICU+JSB0YWlsICU+JSBuYW1lcykgJT4lIHQNCmBgYA0KDQpfV2hpY2ggay1tZWFucyBjbHVzdGVyIGJlc3QgY29ycmVzcG9uZHMgdG8gdGhlIElyYXEgV2FyP18NCg0KKyAzDQorIA0KDQpfV2hpY2ggay1tZWFucyBjbHVzdGVyIGJlc3QgY29ycmVzcG9uZHMgdG8gdGhlIGRlbW9jcmF0aWMgcGFydHk/XyAoUmVtZW1iZXIgdGhhdCB3ZSBhcmUgbG9va2luZyBmb3IgdGhlIG5hbWVzIG9mIHRoZSBrZXkgZGVtb2NyYXRpYyBwYXJ0eSBsZWFkZXJzLikNCg0KKyAyDQorIA0KDQojIyMjIyAyLjMgfiAyLjYg5YWp56iu5YiG576k57WQ5p6c5LmL6ZaT55qE5bCN5oeJ6Zec5L+CDQpGb3IgdGhlIHJlc3Qgb2YgdGhpcyBwcm9ibGVtLCB3ZSdsbCBhc2sgeW91IHRvIGNvbXBhcmUgaG93IG9ic2VydmF0aW9ucyB3ZXJlIGFzc2lnbmVkIHRvIGNsdXN0ZXJzIGluIHRoZSB0d28gZGlmZmVyZW50IG1ldGhvZHMuIFVzZSB0aGUgdGFibGUgZnVuY3Rpb24gdG8gY29tcGFyZSB0aGUgY2x1c3RlciBhc3NpZ25tZW50IG9mIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIHRvIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnQgb2Ygay1tZWFucyBjbHVzdGVyaW5nLg0KDQojIyNNSVQNCmBgYHtyfQ0KdGFibGUoaGllckdyb3VwcywgS21lYW5zQ2x1c3RlciRjbHVzdGVyKQ0KYGBgDQoNCg0KYGBge3J9DQp0YWJsZShIaWVyYXJjaGljYWw9a2csIEtNZWFucz1rZzIpDQpgYGANCl9XaGljaCBIaWVyYXJjaGljYWwgQ2x1c3RlciBiZXN0IGNvcnJlc3BvbmRzIHRvIEstTWVhbnMgQ2x1c3RlciAyP18NCg0KKyA3DQorDQoNCl9XaGljaCBIaWVyYXJjaGljYWwgQ2x1c3RlciBiZXN0IGNvcnJlc3BvbmRzIHRvIEstTWVhbnMgQ2x1c3RlciAzP18NCg0KKyA1DQorDQoNCl9XaGljaCBIaWVyYXJjaGljYWwgQ2x1c3RlciBiZXN0IGNvcnJlc3BvbmRzIHRvIEstTWVhbnMgQ2x1c3RlciA3P18NCg0KKyBObyBIaWVyYXJjaGljYWwgQ2x1c3RlciBjb250YWlucyBhdCBsZWFzdCBoYWxmIG9mIHRoZSBwb2ludHMgaW4gSy1NZWFucyBDbHVzdGVyIDcuDQorIA0KDQpfV2hpY2ggSGllcmFyY2hpY2FsIENsdXN0ZXIgYmVzdCBjb3JyZXNwb25kcyB0byBLLU1lYW5zIENsdXN0ZXIgNj9fDQoNCisgMg0KKyANCg0KDQojIyMjIyDjgJDoqI7oq5bllY/poYzjgJENCg0K5a2X6aC76KGo5piv5LuA6bq877yf5a6D55qE6LOH5paZ5qC85byP77yfDQoNCisg5a2X6aC76KGo55SoRG9jdW1lbnQgdGVybSBtYXRyaXjooajnpLrvvIzlsIfmiYDmnIlkb2N1bWVudOeahOaJgOacieWtl+aUpOmWi+S+huaUvuWcqGNvbHVtbu+8jHJvd+aYr+avj+WAi2RvY3VtZW5044CCcm93IDHnmoRhYmFuZG9u5qyE5L2N5pivMOaEj+aAneaYr2RvY3VtbWVudDHmspLmnIlhYmFuZG9uDQrpgJnlgIvlrZc7cm93IDPnmoRhYnN0YWlu5qyE5L2N5pivMeaEj+aAneaYr2Fic3RhaW7pgJnlgIvlrZflh7rnj77lnKhkb2N1bWVudDMgMeasoeOAguWFtuS7lueahOavj+WAi+WAvOS7peatpOimj+WJh+mhnuaOqOOAgg0KKyDku5bmmK/kuIDlgItEb2N1bWVudCB0ZXJtIG1hdHJpeO+8jOaIkeWAkeS7pWNsYXNz5Ye95pW45Y+v5Lul55yL5Yiw5ZyoUuaYr+eUqGRhdGEuZnJhbWXnmoTos4fmlpnlnovmhYvlrZjmlL7jgIINCg0KDQrkvb/nlKjlrZfpoLvooajkvZzpm4bnvqTliIbmnpDmmYLvvIzljYDpmpTorormlbjmmK/ku4DpurzvvJ8NCg0KKyDos4fmlpnoo6HpnaJjb2xuYW1lcyjorormlbgp5omA5Ye654++5Zyo5pW46YeP77yM6JeJ55Sx5qyh5pW46auY5L2O77yM5YGa54K65piv5ZCm5Y2A6ZqU55qE6ZaA5qq744CCDQorIOaVuOmHj+eCuuWtl+mgu++8jOaVuOWtl+i2iuWkp+ihqOekuuipsuWtl+WHuuePvuWcqGRvY3VtZW5055qE5qyh5pW46LaK6auY44CCDQoNCuW+nuaoueeLgOWcluWIpOaWt+e+pOaVuOWSjOW+nuaHieeUqOmcgOaxguaxuuWumue+pOaVuOacieS7gOm6vOW3ruWIpe+8nw0KDQorIOS4gOiIrOS7peaoueeLgOWcluaJgOWHuuePvueahOaOqOiWpueahOe+pOaVuOS4puS4jeS4gOWumuWSjOS9oOacgOW+jOaJgOWBmueahOaxuuetlue+pOaVuOWujOWFqOebuOespu+8jOacieaZgumChOaYr+W+l+iAg+aFruWIsOePvuWvpumdouOAguavlOWmguaoueeLgOWcluacgOWlveeahOaWueW8j+aYr+S4iee+pCjkuInnvqTmmYLpq5jluqblt67mnIDlpKcp77yM5Y+v5piv6ICD5oWu5Yiw54++5a+m6Z2i5YGH6Kit6LOH5paZ5bqr5aSq5aSn77yM5LiJ576k5Y+v6IO95rKS6L6m5rOV5pyJ5pWI5YiG6aGe77yM5pyJ5pmC5b6X6YG45pOH5pu05aSa576k5Y676Kej6YeL6LOH5paZ44CCDQorIOacieaZguWAmeWPr+iDveeUqOaoueeLgOWcluWRiuiotOaIkeWAkeWIhuaIkDEw576k5b6I5qOS77yM5L2G5pyJ5pmC5YCZ5oiR5YCR5YGa5rG6562W5Y+v6IO95Y+q5piv5biM5pyb5L6d54Wn5LiN5ZCM5peP576k55qE54m55oCn5Yi25a6a5LiA5Lqb5LiN5ZCM55qE5pa55qGI44CC5omA5Lul5oiR5YCR6Ieq5bex6KGh6YeP6KaB5bCH576k5pW45aKe5Yqg6YKE5piv5rib5bCR77yM5Lul5bCN5oeJ5oiR5YCR5oOz6KaB5YGa55qE5rG6562W5pW46YeP44CCDQoNCiMjIyMjIyAgPHNwYW4gaWQ9J0NMJz7lsI/ntYTlv4Plvpc8L3NwYW4+DQrpgI/pgY5IaWVyYXJjaGljYWwgQ2x1c3RlcmluZ+WPr+S7peW5q+WKqeaIkeWAkeWwh+Wkp+ethuizh+aWmeWIhuaIkOaVuOWAi+e+pOiBmu+8jOS4puW+nuaoueeLgOWcluWPr+S7peingOWvn+WIsOacgOWlveeahOWIhue+pOaVuOmHj++8jOS9huWvpumam+S4iueahOaHieeUqO+8jOmChOaYr+W+l+e2k+eUseiHqui6q+e2k+mpl+WIpOaWt+mBqeeVtueahOWIhue+pOaVuO+8jOS4pumdnuS4gOWRs+eahOebuOS/oeaoueeLgOWclumhr+ekuueahOe1kOaenO+8jOatpOWklu+8jOmChOWPr+S7pemAj+mBjumajuWxpOWIhue+pOS+huW+l+efpeipsue+pOiBmuS4re+8jOacgOW4uOWHuuePvueahOWtl+ipnuacieWTquS6m++8jOW+nuiAjOefpemBk+mAmeS6m+e+pOiBmueahOeJueaAp+OAgg0KDQo8YnI+DQoNCi0gLSAtDQoNCjxicj48YnI+PGJyPjxicj48YnI+DQoNCjxzdHlsZT4NCi5jYXB0aW9uIHsNCiAgY29sb3I6ICM3Nzc7DQogIG1hcmdpbi10b3A6IDEwcHg7DQp9DQpwIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogaW5oZXJpdDsNCn0NCnByZSB7DQogIHdvcmQtYnJlYWs6IG5vcm1hbDsNCiAgd29yZC13cmFwOiBub3JtYWw7DQogIGxpbmUtaGVpZ2h0OiAxOw0KfQ0KcHJlIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogaW5oZXJpdDsNCn0NCnAsbGkgew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KLnJ7DQogIGxpbmUtaGVpZ2h0OiAxLjI7DQp9DQoNCnRpdGxlew0KICBjb2xvcjogI2NjMDAwMDsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCmJvZHl7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpoMSxoMixoMyxoNCxoNXsNCiAgY29sb3I6ICMwMDg4MDA7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpoM3sNCiAgY29sb3I6ICNiMzZiMDA7DQogIGJhY2tncm91bmQ6ICNmZmUwYjM7DQogIGxpbmUtaGVpZ2h0OiAyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KaDV7DQogIGNvbG9yOiAjMDA2MDAwOw0KICBiYWNrZ3JvdW5kOiAjZmZmZmUwOw0KICBsaW5lLWhlaWdodDogMjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQoNCmg2ew0KICBjb2xvcjogIzAwNjAwMDsNCiAgYmFja2dyb3VuZDogIzAwZmZmZjsNCiAgbGluZS1oZWlnaHQ6IDI7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KDQp9DQplbXsNCiAgY29sb3I6ICMwMDAwYzA7DQogIGJhY2tncm91bmQ6ICNmMGYwZjA7DQogIH0NCg0KPC9zdHlsZT4NCg0K