Lecture11: 類似度計算, 階層的クラスター分析
Term-Document Matrixの作成
指定ディレクトリのファイル一覧を取得(相対パス)
dirName <-"news_articles"
(files<- list.files(dirName))
[1] "AJ1.txt" "AJ2.txt" "AJ3.txt" "BBC1.txt" "BBC2.txt"
[6] "BBC3.txt" "JT1.txt" "JT2.txt" "JT3.txt"
filesDir <- unlist(lapply(dirName, paste, files, sep = "/"))
filesDir[1]
[1] "news_articles/AJ1.txt"
要素結合用関数の作成
join_Lines <- function(fnaame) {
txt <- readLines(fnaame)
txt <- tolower(txt)
txt<- txt[txt != ""]
paste(txt, collapse = " ")
}
複数テキストファイルへの一括処理
library(magrittr)
lapply(filesDir, join_Lines) %>% unlist -> news_txtset
cleanNLPライブラリの読み込み & 言語モデルの設定
library(cleanNLP)
cnlp_init_udpipe()#言語モデルの設定(デフォルト値=英語)
形態素解析: cnlp_annotate関数
res <- cnlp_annotate(input = news_txtset)
res <- res$token[!res$token$upos %in% c("PUNCT","SYM","NUM"),]#句読点,記号,数字を除外
文書行列(Term-Document Matrix)
docMtx <- as.data.frame.matrix(table(res$lemma, res$doc_id))
#docMtx <- as.data.frame.matrix(table(res$token, res$doc_id))
files %>% strsplit(".txt") %>% unlist -> colnames(docMtx)#列名の変更
docMtxのサイズ確認
dim(docMtx)
[1] 1209 9
部分表示
head(docMtx)
相対頻度行列
colSums(docMtx)#Tokens
AJ1 AJ2 AJ3 BBC1 BBC2 BBC3 JT1 JT2 JT3
428 389 697 658 405 561 464 621 381
#docMtx/colSums(docMtx)#It doesn't work well
relative_docMtx<-sweep(docMtx, MARGIN = 2, colSums(docMtx), FUN = "/")
文書頻度(Dcument Frequency:DF)による条件抽出
DF<-apply(relative_docMtx, 1, function(x) length(x[x>0]))
head(DF)
-escalate -hama -one 's ’re &
1 1 1 9 1 1
文書頻度(Dcument Frequency:DF)による条件抽出
DF値が5-8の行を抽出した例
docMtx_c5 <- relative_docMtx[(DF>4 & DF<9), ] #5-8DF
docMtx_c5のサイズ確認
dim(docMtx_c5)
[1] 51 9
類似度計算
相関係数
ピアソン積率相関係数
\[Corr(x,y)= \frac{\sum
(x_{i}-\overline{x}) (y_{i}-\overline{y})}{\sqrt{\sum
(x_{i}-\overline{x})^2\sum (y_{i}-\overline{y})^2}} \]
相関係数行列(テキスト間)
res <-cor(docMtx_c5)
round(res,2)
AJ1 AJ2 AJ3 BBC1 BBC2 BBC3 JT1 JT2 JT3
AJ1 1.00 0.24 0.22 0.46 0.48 0.12 0.55 0.20 0.05
AJ2 0.24 1.00 0.25 0.38 0.55 0.51 0.19 0.51 -0.03
AJ3 0.22 0.25 1.00 0.30 0.44 0.31 0.11 0.37 0.06
BBC1 0.46 0.38 0.30 1.00 0.45 0.31 0.55 0.10 -0.16
BBC2 0.48 0.55 0.44 0.45 1.00 0.30 0.27 0.52 0.08
BBC3 0.12 0.51 0.31 0.31 0.30 1.00 0.16 0.15 0.00
JT1 0.55 0.19 0.11 0.55 0.27 0.16 1.00 -0.01 0.08
JT2 0.20 0.51 0.37 0.10 0.52 0.15 -0.01 1.00 0.34
JT3 0.05 -0.03 0.06 -0.16 0.08 0.00 0.08 0.34 1.00
相関係数行列(単語間)
res_term <-cor(t(docMtx_c5))
変数間の相関行列
転置(transpose): t関数
testMtx <- matrix(1:6, nrow=3)
testMtx
[,1] [,2]
[1,] 1 4
[2,] 2 5
[3,] 3 6
# transpose
t(testMtx)
[,1] [,2] [,3]
[1,] 1 2 3
[2,] 4 5 6
proxyパッケージのインストール
install.packages("proxy", dependencies = TRUE)
proxyパッケージの読み込み
library(proxy)
simil関数による相関係数行列(テキスト間)
行と列を転置(transpose)する
corr <- simil(t(docMtx_c5))
round(corr, 2)
AJ1 AJ2 AJ3 BBC1 BBC2 BBC3 JT1 JT2
AJ2 0.24
AJ3 0.22 0.25
BBC1 0.46 0.38 0.30
BBC2 0.48 0.55 0.44 0.45
BBC3 0.12 0.51 0.31 0.31 0.30
JT1 0.55 0.19 0.11 0.55 0.27 0.16
JT2 0.20 0.51 0.37 0.10 0.52 0.15 -0.01
JT3 0.05 -0.03 0.06 -0.16 0.08 0.00 0.08 0.34
テキスト間のコサイン類似度
\[Cos(x,y)= \frac{\sum x_{i}
y_{i}}{\sqrt{\sum x_{i}^2\sum y_{i}^2}} \]
res.cos <-simil(t(docMtx_c5), method="cosine")
結果の形式:Distant Object出力
round(res.cos,2)
AJ1 AJ2 AJ3 BBC1 BBC2 BBC3 JT1 JT2
AJ2 0.53
AJ3 0.53 0.55
BBC1 0.68 0.62 0.59
BBC2 0.70 0.73 0.68 0.68
BBC3 0.47 0.70 0.59 0.59 0.59
JT1 0.76 0.57 0.56 0.76 0.65 0.57
JT2 0.53 0.71 0.64 0.48 0.73 0.50 0.51
JT3 0.40 0.35 0.42 0.28 0.44 0.37 0.50 0.59
結果の形式:Matrix出力
res2.cos<-simil(t(docMtx_c5), method="cosine", diag=T)
res2.cos<-as.matrix(res2.cos)
res2.cos[is.na(res2.cos)] <- 1
round(res2.cos,2)
AJ1 AJ2 AJ3 BBC1 BBC2 BBC3 JT1 JT2 JT3
AJ1 1.00 0.53 0.53 0.68 0.70 0.47 0.76 0.53 0.40
AJ2 0.53 1.00 0.55 0.62 0.73 0.70 0.57 0.71 0.35
AJ3 0.53 0.55 1.00 0.59 0.68 0.59 0.56 0.64 0.42
BBC1 0.68 0.62 0.59 1.00 0.68 0.59 0.76 0.48 0.28
BBC2 0.70 0.73 0.68 0.68 1.00 0.59 0.65 0.73 0.44
BBC3 0.47 0.70 0.59 0.59 0.59 1.00 0.57 0.50 0.37
JT1 0.76 0.57 0.56 0.76 0.65 0.57 1.00 0.51 0.50
JT2 0.53 0.71 0.64 0.48 0.73 0.50 0.51 1.00 0.59
JT3 0.40 0.35 0.42 0.28 0.44 0.37 0.50 0.59 1.00
階層的クラスター分析
文書間の距離を測る(euclidean距離)
dist.euclidean <- dist(t(docMtx_c5))
dist.euclidean <- as.matrix(dist.euclidean)
round(dist.euclidean,3)
AJ1 AJ2 AJ3 BBC1 BBC2 BBC3 JT1 JT2 JT3
AJ1 0.000 0.029 0.030 0.028 0.024 0.032 0.020 0.031 0.030
AJ2 0.029 0.000 0.029 0.030 0.023 0.024 0.026 0.025 0.031
AJ3 0.030 0.029 0.000 0.031 0.025 0.028 0.027 0.028 0.030
BBC1 0.028 0.030 0.031 0.000 0.028 0.031 0.024 0.036 0.038
BBC2 0.024 0.023 0.025 0.028 0.000 0.028 0.025 0.024 0.030
BBC3 0.032 0.024 0.028 0.031 0.028 0.000 0.027 0.032 0.032
JT1 0.020 0.026 0.027 0.024 0.025 0.027 0.000 0.030 0.025
JT2 0.031 0.025 0.028 0.036 0.024 0.032 0.030 0.000 0.027
JT3 0.030 0.031 0.030 0.038 0.030 0.032 0.025 0.027 0.000
階層的クラスター分析: euclidean距離 & ward法
hc <- hclust(dist(t(docMtx_c5)), method = "ward.D2")
plot(hc)

#rect.hclust(hc, k=3, border="red")
階層的クラスター分析: canberra距離 & ward法
hc <- hclust(dist(t(docMtx_c5), method = "canberra"), method = "ward.D2")
plot(hc)

#rect.hclust(hc, k=3, border="red")
単語間の階層的クラスター分析: euclidean距離 & ward法
hc <- hclust(dist(docMtx_c5), method = "ward.D2")
plot(hc)

#rect.hclust(hc, k=3, border="red")
単語間の階層的クラスター分析: canberra距離 & ward法
hc <- hclust(dist(docMtx_c5, method = "canberra"), method = "ward.D2")
plot(hc)

#rect.hclust(hc, k=3, border="red")
課題2(締め切り2024年1月25日)
“app_asgnmt2”を編集して、Task1,2に取り組んでください。
Task1: hclustとrect.hclustの表示したTabを追加しなさい。
Task2: 次の2つの変数をスライダー(sliderInput)でインタラクティブに変更できるように拡張しなさい。
(1) Document Frequencyの最低値
(2) rect.hclust関数の引数kの値
スライダーの値の範囲: min=2, max =7, value=4, step=1
LS0tCnRpdGxlOiAiTGVjMTE6IChGYWxsIDIwMjMpIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIExlY3R1cmUxMTog6aGe5Ly85bqm6KiI566XLCDpmo7lsaTnmoTjgq/jg6njgrnjgr/jg7zliIbmnpAKCiMjIOWIhuaekOODhuOCreOCueODiAotIDxhIGhyZWY9Imh0dHBzOi8vd3d3LmFsamF6ZWVyYS5jb20vbmV3cy8yMDI0LzEvOS9kb2ctbWVhdCIgdGFyZ2V0PSJfYmxhbmsiPkFKMTogQWwgSmF6ZWVyYSwgRG9nIG1lYXQ8L2E+Ci0gPGEgaHJlZj0iaHR0cHM6Ly93d3cuYWxqYXplZXJhLmNvbS9uZXdzLzIwMjMvMTEvOC9nNy1uYXRpb25zLXVuYW5pbW91c2x5LWNhbGwtZm9yLWh1bWFuaXRhcmlhbi1wYXVzZXMtaW4taXNyYWVsLWdhemEtd2FyIiB0YXJnZXQ9Il9ibGFuayI+QUoyOiBBbCBKYXplZXJhLCBHNzwvYT4KLSA8YSBocmVmPSJodHRwczovL3d3dy5hbGphemVlcmEuY29tL25ld3MvMjAyNC8xLzIvcGxhbmUtY2F0Y2hlcy1maXJlLW9uLXJ1bndheS1hdC1qYXBhbnMtaGFuZWRhLWFpcnBvcnQiIHRhcmdldD0iX2JsYW5rIj5BSjM6IEFsIEphemVlcmEsIEpBTCBjb2xsaXNpb248L2E+Ci0gPGEgaHJlZj0iaHR0cHM6Ly93d3cuYmJjLmNvbS9uZXdzL3dvcmxkLWFzaWEtNjc5MjAxNjciIHRhcmdldD0iX2JsYW5rIj5CQkMxOiBCQkMsIERvZyBtZWF0PC9hPgotIDxhIGhyZWY9Imh0dHBzOi8vd3d3LmJiYy5jb20vbmV3cy93b3JsZC1ldXJvcGUtNjczNTU0MjMiIHRhcmdldD0iX2JsYW5rIj5CQkMyOiBCQkMsIEc3PC9hPgotIDxhIGhyZWY9Imh0dHBzOi8vd3d3LmJiYy5jb20vbmV3cy93b3JsZC1hc2lhLTY3ODc5MzA4IiB0YXJnZXQ9Il9ibGFuayI+QkJDMjogQkJDLCBKQUwgY29sbGlzaW9uPC9hPgotIDxhIGhyZWY9Imh0dHBzOi8vd3d3LmphcGFudGltZXMuY28uanAvbmV3cy8yMDI0LzAxLzA5L2FzaWEtcGFjaWZpYy9wb2xpdGljcy9zb3V0aC1rb3JlYS1kb2ctbWVhdC1iYW4vIiB0YXJnZXQ9Il9ibGFuayI+SlQxOiBKYXBhbiBUaW1lcywgRG9nIG1lYXQ8L2E+Ci0gPGEgaHJlZj0iaHR0cHM6Ly93d3cuamFwYW50aW1lcy5jby5qcC9uZXdzLzIwMjMvMTEvMDgvamFwYW4vcG9saXRpY3MvZzctZm9yZWlnbi1taW5pc3RlcnMtbWVldGluZy8iIHRhcmdldD0iX2JsYW5rIj5KVDI6IEphcGFuIFRpbWVzLCBHNzwvYT4KLSA8YSBocmVmPSJodHRwczovL3d3dy5qYXBhbnRpbWVzLmNvLmpwL25ld3MvMjAyNC8wMS8wOS9qYXBhbi9oYW5lZGEtc2FmZXR5LXByZXZlbnRpb24tcGxhbi8iIHRhcmdldD0iX2JsYW5rIj5KVDM6IEphcGFuIFRpbWVzLCBKQUwgY29sbGlzaW9uPC9hPgoKIyMgVGVybS1Eb2N1bWVudCBNYXRyaXjjga7kvZzmiJAKIyMjIOaMh+WumuODh+OCo+ODrOOCr+ODiOODquOBruODleOCoeOCpOODq+S4gOimp+OCkuWPluW+lyjnm7jlr77jg5HjgrkpCmBgYHtyfQpkaXJOYW1lIDwtIm5ld3NfYXJ0aWNsZXMiCihmaWxlczwtIGxpc3QuZmlsZXMoZGlyTmFtZSkpCmZpbGVzRGlyIDwtIHVubGlzdChsYXBwbHkoZGlyTmFtZSwgcGFzdGUsIGZpbGVzLCBzZXAgPSAiLyIpKQpmaWxlc0RpclsxXQpgYGAKIyMjIOimgee0oOe1kOWQiOeUqOmWouaVsOOBruS9nOaIkApgYGB7cn0Kam9pbl9MaW5lcyA8LSBmdW5jdGlvbihmbmFhbWUpIHsKICB0eHQgPC0gcmVhZExpbmVzKGZuYWFtZSkKICB0eHQgPC0gdG9sb3dlcih0eHQpCiAgdHh0PC0gdHh0W3R4dCAhPSAiIl0KICBwYXN0ZSh0eHQsIGNvbGxhcHNlID0gIiAiKQp9CmBgYAoKIyMjIOikh+aVsOODhuOCreOCueODiOODleOCoeOCpOODq+OBuOOBruS4gOaLrOWHpueQhgpgYGB7cn0KbGlicmFyeShtYWdyaXR0cikKbGFwcGx5KGZpbGVzRGlyLCBqb2luX0xpbmVzKSAlPiUgdW5saXN0IC0+IG5ld3NfdHh0c2V0CmBgYAoKIyMgY2xlYW5OTFDjg6njgqTjg5bjg6njg6rjga7oqq3jgb/ovrzjgb8gJiDoqIDoqp7jg6Ljg4fjg6vjga7oqK3lrpoKYGBge3J9CmxpYnJhcnkoY2xlYW5OTFApCmNubHBfaW5pdF91ZHBpcGUoKSPoqIDoqp7jg6Ljg4fjg6vjga7oqK3lrprvvIjjg4fjg5Xjgqnjg6vjg4jlgKQ96Iux6Kqe77yJCmBgYAoKIyMjIOW9ouaFi+e0oOino+aekDogY25scF9hbm5vdGF0ZemWouaVsApgYGB7cn0KcmVzIDwtIGNubHBfYW5ub3RhdGUoaW5wdXQgPSBuZXdzX3R4dHNldCkKcmVzIDwtIHJlcyR0b2tlblshcmVzJHRva2VuJHVwb3MgJWluJSBjKCJQVU5DVCIsIlNZTSIsIk5VTSIpLF0j5Y+l6Kqt54K5LOiomOWPtyzmlbDlrZfjgpLpmaTlpJYKYGBgCiMjIyBWaWV3CmBgYHtyLCBldmFsPUZBTFNFfQpWaWV3KHJlcykKYGBgCiMjIOaWh+abuOihjOWIlyhUZXJtLURvY3VtZW50IE1hdHJpeCkKYGBge3J9CmRvY010eCA8LSBhcy5kYXRhLmZyYW1lLm1hdHJpeCh0YWJsZShyZXMkbGVtbWEsIHJlcyRkb2NfaWQpKQojZG9jTXR4IDwtIGFzLmRhdGEuZnJhbWUubWF0cml4KHRhYmxlKHJlcyR0b2tlbiwgcmVzJGRvY19pZCkpCmZpbGVzICU+JSBzdHJzcGxpdCgiLnR4dCIpICU+JSB1bmxpc3QgLT4gY29sbmFtZXMoZG9jTXR4KSPliJflkI3jga7lpInmm7QKYGBgCiMjIyBkb2NNdHjjga7jgrXjgqTjgrrnorroqo0KYGBge3J9CmRpbShkb2NNdHgpCmBgYAoKIyMjIFZpZXcKYGBge3IsIGV2YWw9RkFMU0V9ClZpZXcoZG9jTXR4KQpgYGAKCiMjIyDpg6jliIbooajnpLoKYGBge3J9CmhlYWQoZG9jTXR4KQpgYGAKCiMjIyMg55u45a++6aC75bqm6KGM5YiXCi0gPGEgaHJlZj0iaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vMjAyMi8wNy9ob3ctdG8tdXNlLXRoZS1zd2VlcC1mdW5jdGlvbi1pbi1yLyIgdGFyZ2V0PSJfYmxhbmsiPnN3ZWVw6Zai5pWwPC9hPuOBruWIqeeUqAoKYGBge3J9CmNvbFN1bXMoZG9jTXR4KSNUb2tlbnMKI2RvY010eC9jb2xTdW1zKGRvY010eCkjSXQgZG9lc24ndCB3b3JrIHdlbGwKcmVsYXRpdmVfZG9jTXR4PC1zd2VlcChkb2NNdHgsIE1BUkdJTiA9IDIsIGNvbFN1bXMoZG9jTXR4KSwgRlVOID0gIi8iKQpgYGAKCiMjIyDmlofmm7jpoLvluqYoRGN1bWVudCBGcmVxdWVuY3k6REYp44Gr44KI44KL5p2h5Lu25oq95Ye6CmBgYHtyfQpERjwtYXBwbHkocmVsYXRpdmVfZG9jTXR4LCAxLCBmdW5jdGlvbih4KSBsZW5ndGgoeFt4PjBdKSkKaGVhZChERikKYGBgCiMjIyDmlofmm7jpoLvluqYoRGN1bWVudCBGcmVxdWVuY3k6REYp44Gr44KI44KL5p2h5Lu25oq95Ye6CiMjIyBERuWApOOBjDUtOOOBruihjOOCkuaKveWHuuOBl+OBn+S+iwpgYGB7cn0KZG9jTXR4X2M1IDwtIHJlbGF0aXZlX2RvY010eFsoREY+NCAmIERGPDkpLCBdICM1LThERgpgYGAKCiMjIyBkb2NNdHhfYzXjga7jgrXjgqTjgrrnorroqo0KYGBge3J9CmRpbShkb2NNdHhfYzUpCmBgYAojIyMgVmlldwpgYGB7ciwgZXZhbD1GQUxTRX0KVmlldyhkb2NNdHhfYzUpCmBgYAoKIyMg6aGe5Ly85bqm6KiI566XCiMjIOebuOmWouS/guaVsAojIyDjg5TjgqLjgr3jg7PnqY3njofnm7jplqLkv4LmlbAKJCRDb3JyKHgseSk9IFxmcmFje1xzdW0gKHhfe2l9LVxvdmVybGluZXt4fSkgKHlfe2l9LVxvdmVybGluZXt5fSl9e1xzcXJ0e1xzdW0gKHhfe2l9LVxvdmVybGluZXt4fSleMlxzdW0gKHlfe2l9LVxvdmVybGluZXt5fSleMn19ICQkCgojIyMg55u46Zai5L+C5pWw6KGM5YiX77yI44OG44Kt44K544OI6ZaT77yJCmBgYHtyfQpyZXMgPC1jb3IoZG9jTXR4X2M1KQpyb3VuZChyZXMsMikKYGBgCiMjIyDnm7jplqLkv4LmlbDooYzliJfvvIjljZjoqp7plpPvvIkKYGBge3J9CnJlc190ZXJtIDwtY29yKHQoZG9jTXR4X2M1KSkKYGBgCiMjIyBWaWV3CmBgYHtyLCBldmFsPUZBTFNFfQpWaWV3KHJlc190ZXJtKQpgYGAKIyMjIOWkieaVsOmWk+OBruebuOmWouihjOWIlwojIyMjIOi7oue9ru+8iHRyYW5zcG9zZe+8iTogdOmWouaVsApgYGB7cn0KdGVzdE10eCA8LSBtYXRyaXgoMTo2LCBucm93PTMpCnRlc3RNdHgKIyB0cmFuc3Bvc2UKdCh0ZXN0TXR4KQpgYGAKCiMjIyA8YSBocmVmPSJodHRwczovL3VyaWJvLmdpdGh1Yi5pby9ycGtnX3Nob3djYXNlL2RhdGEtYW5hbHlzaXMvcHJveHkuaHRtbCIgdGFyZ2V0PSJfYmxhbmsiPnByb3h5PC9hPuODkeODg+OCseODvOOCuOOBruOCpOODs+OCueODiOODvOODqwpgYGB7ciwgZXZhbD1GQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygicHJveHkiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQpgYGAKCiMjIyBwcm94eeODkeODg+OCseODvOOCuOOBruiqreOBv+i+vOOBvwpgYGB7cn0KbGlicmFyeShwcm94eSkKYGBgCgojIyMgc2ltaWzplqLmlbDjgavjgojjgovnm7jplqLkv4LmlbDooYzliJfvvIjjg4bjgq3jgrnjg4jplpPvvIkKIyMjIyDooYzjgajliJfjgpLou6Lnva7vvIh0cmFuc3Bvc2XvvInjgZnjgosKYGBge3J9CmNvcnIgPC0gc2ltaWwodChkb2NNdHhfYzUpKQpyb3VuZChjb3JyLCAyKQpgYGAKCiMjIOODhuOCreOCueODiOmWk+OBruOCs+OCteOCpOODs+mhnuS8vOW6pgokJENvcyh4LHkpPSBcZnJhY3tcc3VtIHhfe2l9IHlfe2l9fXtcc3FydHtcc3VtIHhfe2l9XjJcc3VtIHlfe2l9XjJ9fSAkJAoKYGBge3J9CnJlcy5jb3MgPC1zaW1pbCh0KGRvY010eF9jNSksIG1ldGhvZD0iY29zaW5lIikKYGBgCgojIyMg57WQ5p6c44Gu5b2i5byP77yaRGlzdGFudCBPYmplY3Tlh7rlipsKYGBge3J9CnJvdW5kKHJlcy5jb3MsMikKYGBgCgojIyMg57WQ5p6c44Gu5b2i5byP77yaTWF0cml45Ye65YqbCmBgYHtyfQpyZXMyLmNvczwtc2ltaWwodChkb2NNdHhfYzUpLCBtZXRob2Q9ImNvc2luZSIsIGRpYWc9VCkKcmVzMi5jb3M8LWFzLm1hdHJpeChyZXMyLmNvcykKcmVzMi5jb3NbaXMubmEocmVzMi5jb3MpXSA8LSAxCnJvdW5kKHJlczIuY29zLDIpCmBgYAoKIyMg6ZqO5bGk55qE44Kv44Op44K544K/44O85YiG5p6QCiogPGEgaHJlZj0iaHR0cHM6Ly9iZWxsY3VydmUuanAvc3RhdGlzdGljcy9jb3Vyc2UvMjcxNTUuaHRtbCIgdGFyZ2V0PSJfYmxhbmsiPuWPguiAg+OCteOCpOODiDE6IOmajuWxpOeahOOCr+ODqeOCueOCv+ODvOWIhuaekDwvYT4KKiA8YSBocmVmPSJodHRwczovL251bWVyaWNzLm1hdGhkb3RuZXQuY29tL0Rpc3RhbmNlIiB0YXJnZXQ9Il9ibGFuayI+5Y+C6ICD44K144Kk44OIMjog6Led6Zui6KGM5YiXPC9hPgoKIyMjIOaWh+abuOmWk+OBrui3nembouOCkua4rOOCi++8iGV1Y2xpZGVhbui3nembou+8iQpgYGB7cn0KZGlzdC5ldWNsaWRlYW4gPC0gZGlzdCh0KGRvY010eF9jNSkpCmRpc3QuZXVjbGlkZWFuIDwtIGFzLm1hdHJpeChkaXN0LmV1Y2xpZGVhbikKcm91bmQoZGlzdC5ldWNsaWRlYW4sMykKYGBgCgojIyMg6ZqO5bGk55qE44Kv44Op44K544K/44O85YiG5p6QOiBldWNsaWRlYW7ot53pm6IgJiB3YXJk5rOVCmBgYHtyfQpoYyA8LSBoY2x1c3QoZGlzdCh0KGRvY010eF9jNSkpLCBtZXRob2QgPSAid2FyZC5EMiIpCnBsb3QoaGMpCiNyZWN0LmhjbHVzdChoYywgaz0zLCBib3JkZXI9InJlZCIpCmBgYAojIyMg6ZqO5bGk55qE44Kv44Op44K544K/44O85YiG5p6QOiBjYW5iZXJyYei3nemboiAmIHdhcmTms5UKYGBge3J9CmhjIDwtIGhjbHVzdChkaXN0KHQoZG9jTXR4X2M1KSwgbWV0aG9kID0gImNhbmJlcnJhIiksIG1ldGhvZCA9ICJ3YXJkLkQyIikKcGxvdChoYykKI3JlY3QuaGNsdXN0KGhjLCBrPTMsIGJvcmRlcj0icmVkIikKYGBgCiMjIyDljZjoqp7plpPjga7pmo7lsaTnmoTjgq/jg6njgrnjgr/jg7zliIbmnpA6IGV1Y2xpZGVhbui3nemboiAmIHdhcmTms5UKYGBge3J9CmhjIDwtIGhjbHVzdChkaXN0KGRvY010eF9jNSksIG1ldGhvZCA9ICJ3YXJkLkQyIikKcGxvdChoYykKI3JlY3QuaGNsdXN0KGhjLCBrPTMsIGJvcmRlcj0icmVkIikKYGBgCgojIyMg5Y2Y6Kqe6ZaT44Gu6ZqO5bGk55qE44Kv44Op44K544K/44O85YiG5p6QOiBjYW5iZXJyYei3nemboiAmIHdhcmTms5UKYGBge3J9CmhjIDwtIGhjbHVzdChkaXN0KGRvY010eF9jNSwgbWV0aG9kID0gImNhbmJlcnJhIiksIG1ldGhvZCA9ICJ3YXJkLkQyIikKcGxvdChoYykKI3JlY3QuaGNsdXN0KGhjLCBrPTMsIGJvcmRlcj0icmVkIikKYGBgCgojIyAg6Kqy6aGMMu+8iOe3oOOCgeWIh+OCijIwMjTlubQx5pyIMjXml6XvvIkKIyMjICJhcHBfYXNnbm10MiLjgpLnt6jpm4bjgZfjgabjgIFUYXNrMSwy44Gr5Y+W44KK57WE44KT44Gn44GP44Gg44GV44GE44CCCmBgYApUYXNrMTogaGNsdXN044GocmVjdC5oY2x1c3Tjga7ooajnpLrjgZfjgZ9UYWLjgpLov73liqDjgZfjgarjgZXjgYTjgIIKClRhc2syOiDmrKHjga7vvJLjgaTjga7lpInmlbDjgpLjgrnjg6njgqTjg4Djg7woc2xpZGVySW5wdXQp44Gn44Kk44Oz44K/44Op44Kv44OG44Kj44OW44Gr5aSJ5pu044Gn44GN44KL44KI44GG44Gr5ouh5by144GX44Gq44GV44GE44CCCigxKSBEb2N1bWVudCBGcmVxdWVuY3njga7mnIDkvY7lgKQKKDIpIHJlY3QuaGNsdXN06Zai5pWw44Gu5byV5pWwa+OBruWApArjgrnjg6njgqTjg4Djg7zjga7lgKTjga7nr4Tlm7I6IG1pbj0yLCBtYXggPTcsIHZhbHVlPTQsIHN0ZXA9MQpgYGAKCiMjIyDoo5zotrM6IGh0bWx0b29sc+OCouODg+ODl+ODh+ODvOODiOWvvuW/nApgYGAKPiBydW5BcHAoJ2FwcF9hc3NpZ25tdCcpCkVycm9yOiBwYWNrYWdlIG9yIG5hbWVzcGFjZSBsb2FkIGZhaWxlZCBmb3Ig4oCYRFTigJkgaW4gbG9hZE5hbWVzcGFjZShpLCBjKGxpYi5sb2MsIC5saWJQYXRocygpKSwgdmVyc2lvbkNoZWNrID0gdklbW2ldXSk6CiBuYW1lc3BhY2Ug4oCYaHRtbHRvb2xz4oCZIDAuNS42IGlzIGFscmVhZHkgbG9hZGVkLCBidXQgPj0gMC41LjcgaXMgcmVxdWlyZWQKSW4gYWRkaXRpb246IFdhcm5pbmcgbWVzc2FnZToKSW4gdW5sb2FkTmFtZXNwYWNlKHBhY2thZ2UpIDoKICBuYW1lc3BhY2Ug4oCYaHRtbHRvb2xz4oCZIGlzIGltcG9ydGVkIGJ5IOKAmHJtYXJrZG93buKAmSwg4oCYc2hpbnnigJkgc28gY2Fubm90IGJlIHVubG9hZGVkCmBgYApgYGB7ciwgZXZhbD1GQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygiaHRtbHRvb2xzIikKYGBgCgoKCg==