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"),]#句読点,記号,数字を除外

View

View(res)

文書行列(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

View

View(docMtx)

部分表示

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

View

View(docMtx_c5)

類似度計算

相関係数

ピアソン積率相関係数

\[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))

View

View(res_term)

変数間の相関行列

転置(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

補足: htmltoolsアップデート対応

> runApp('app_assignmt')
Error: package or namespace load failed for ‘DT’ in loadNamespace(i, c(lib.loc, .libPaths()), versionCheck = vI[[i]]):
 namespace ‘htmltools’ 0.5.6 is already loaded, but >= 0.5.7 is required
In addition: Warning message:
In unloadNamespace(package) :
  namespace ‘htmltools’ is imported by ‘rmarkdown’, ‘shiny’ so cannot be unloaded
install.packages("htmltools")
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==