TF-IDF

apply関数

lapply関数

data <- c("file1", "file2", "file3")
paste(data, ".txt" , sep="")
[1] "file1.txt" "file2.txt" "file3.txt"
paste0(data, ".txt")
[1] "file1.txt" "file2.txt" "file3.txt"
lapply(data, paste, ".txt")
[[1]]
[1] "file1 .txt"

[[2]]
[1] "file2 .txt"

[[3]]
[1] "file3 .txt"

sapply関数()

  • 名前の属性付き
sapply(data, paste, ".txt")
       file1        file2        file3 
"file1 .txt" "file2 .txt" "file3 .txt" 

apply関数

apply(test_matrix, c(1,2), function(x) x*10)
     [,1] [,2]
[1,]   10   40
[2,]   20   50
[3,]   30   60

テキストファイルの読み込み

ディレクトリ内ファイル名の取得

dirName <-"testdata"
files<- list.files(dirName)
files

相対パスの取得

filesDir <- unlist(lapply(dirName, paste, files, sep = "/"))
filesDir

ファイルの読み込み

test_tests
                                   testdata/article1 
"TOKYO An art deco building in the Japanese capital" 
                                   testdata/article2 
                "NAGOYA Kyodo A team of researchers" 
                                   testdata/article3 
  "TOKYO Kyodo Former Japan striker Kazuyoshi Miura" 

cleanNLPパッケージによる文書頻度行列の作成

ライブラリの読み込み

library(cleanNLP)

文書頻度行列

cnlp_init_udpipe()

test_res<-cnlp_annotate(input = test_tests)
View(test_res$token)

test_res$token$lemma
 [1] "Tokyo"      "a"          "art"        "deco"       "building"   "in"        
 [7] "the"        "japanese"   "capital"    "NAGOYA"     "Kyodo"      "a"         
[13] "team"       "of"         "researcher" "Tokyo"      "Kyodo"      "former"    
[19] "Japan"      "striker"    "Kazuyoshi"  "Miura"     
test_docMtx <- as.data.frame.matrix(table(test_res$token$lemma, test_res$token$doc_id))
head(test_docMtx)
colnames(test_docMtx) <- files
head(test_docMtx)

TF-IDF

  • テキスト特有の出現単語に対して、重みづけをする
  • テキストに共通する単語に対しては(低い)重み付け

TF-IDF 1

\[w=tf*log(\frac{N}{df}) \]

TF-IDF 2

\[w=tf*(log(\frac{N}{df})+1) \]

Document Frequency

# the document numbers
N<-ncol(test_docMtx)

docFreq<-apply(test_docMtx, 1, function(x) length(x[x>0]))
head(docFreq)

TF-IDF 1

tf_idf1 <- test_docMtx*log(N/docFreq)
head(tf_idf1)

TF-IDF 2

tf_idf2 <- test_docMtx*(log(N/docFreq)+1)
head(tf_idf2)

オンライン記事から情報を取得 (Ref. Lec02)

ライブラリの読み込み

library(httr)
library(rvest)

自作関数

getArticleContent <- function(url){
  response <- GET(url, user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15"))
  page <- read_html(response)
  article_content <- html_text(html_nodes(page, "p.txt"), trim = TRUE)
  cleaned_content <- trimws(article_content)
  cleaned_content <- paste(cleaned_content, collapse = "")
}

記事データの取得

article_urls <- c()
article_urls <- append(article_urls,"https://mainichi.jp/english/articles/20241107/p2a/00m/0et/015000c")
#article_urls[[1]] <- "https://mainichi.jp/english/articles/20241107/p2a/00m/0et/015000c"

article_urls 
length(article_urls)
article_urls <- append(article_urls,"https://mainichi.jp/english/articles/20241110/p2g/00m/0li/028000c")
article_urls <- append(article_urls,"https://mainichi.jp/english/articles/20241111/p2g/00m/0na/048000c")
article_urls <- append(article_urls,"https://mainichi.jp/english/articles/20241112/p2g/00m/0sp/005000c")
length(article_urls)

substring of a content

content1 <- getArticleContent(article_urls[1])
substring(content1, 1, 100)
contents <- lapply(article_urls, getArticleContent)
for(txt in contents)  print(substring(txt, 1, 80))
length(contents)

Tokenization (形態素解析)

#cnlp_init_udpipe()
res<-cnlp_annotate(input = contents)
dim(res$token)

記号・数字を除去

res <- res$token[!res$token$upos %in% c("PUNCT","SYM","NUM"),]
dim(res)
View(res)

条件一致による列抽出

head(res[,colnames(res)=="lemma"])

条件一致による行抽出

res[res$lemma=="'s",]

文書頻度行列

docMtx <- as.data.frame.matrix(table(res$lemma, res$doc_id))
head(docMtx)

列名の変更

colnames(docMtx) <- sapply(seq(1:4), function(x) paste0("article", x))

Tokens

colSums(docMtx) #Tokens
article1 article2 article3 article4 
     256      248      148      134 

Types

length(docMtx$article1[docMtx$article1>0])
[1] 147

Types

length(docMtx$article1[docMtx$article1>0])
[1] 147

Document Freqency

N<-ncol(docMtx)

docFreq<-apply(docMtx, 1, function(x) length(x[x>0]))
head(docFreq)
   's  40th     a about above   add 
    3     1     4     1     1     1 

tf_idf1

tf_idf1 <- docMtx*log(N/docFreq)
head(tf_idf1)

tf_idf1

tf_idf2 <- docMtx*(log(N/docFreq)+1)
head(tf_idf2)

練習:Apply関数を活用して、上のdocMtxの素頻度文書行列を相対頻度文書行列に変換し、TF-IDF1を計算してください

相対頻度文書行列の一部表示

        article1    article2   article3    article4
's    0.02343750 0.004032258 0.00000000 0.007462687
40th  0.00000000 0.000000000 0.00000000 0.007462687
a     0.03125000 0.040322581 0.02027027 0.014925373
about 0.00390625 0.000000000 0.00000000 0.000000000
above 0.00390625 0.000000000 0.00000000 0.000000000
add   0.00390625 0.000000000 0.00000000 0.000000000

tf_idf1計算結果一部表示

         article1    article2 article3    article4
's    0.006742549 0.001160008        0 0.002146881
40th  0.000000000 0.000000000        0 0.010345480
a     0.000000000 0.000000000        0 0.000000000
about 0.005415212 0.000000000        0 0.000000000
above 0.005415212 0.000000000        0 0.000000000
add   0.005415212 0.000000000        0 0.000000000
LS0tCnRpdGxlOiAiTGVjMDY6IFRGLUlERiIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQojIFRGLUlERgorIDxhIGhyZWY9Imh0dHBzOi8vd3d3LmdlZWtzZm9yZ2Vla3Mub3JnL2FuLWVhc3ktYXBwcm9hY2gtdG8tdGYtaWRmLXVzaW5nLXIvIiB0YXJnZXQ9Il9ibGFuayI+dG0gcGFja2FnZTwvYT4KKyA8YSBocmVmPSJodHRwczovL3NhcmFzd2F0bWtzLmdpdGh1Yi5pby8yMDIwLzAyL3RmaWRmLW1hdHJpeC1zdXBlcm1sLVIuaHRtbCIgdGFyZ2V0PSJfYmxhbmsiPnN1cGVybWwgcGFja2FnZTwvYT4KCiMjIGFwcGx56Zai5pWwCisgIDxhIGhyZWY9Imh0dHBzOi8vc3RhdHMuYmlvcGFweXJ1cy5qcC9yL2Jhc2ljL2FwcGx5Lmh0bWwiIHRhcmdldD0iX2JsYW5rIj7lj4LogIPos4fmlpk8L2E+CgojIyMgbGFwcGx56Zai5pWwCmBgYHtyfQpkYXRhIDwtIGMoImZpbGUxIiwgImZpbGUyIiwgImZpbGUzIikKcGFzdGUoZGF0YSwgIi50eHQiICwgc2VwPSIiKQpwYXN0ZTAoZGF0YSwgIi50eHQiKQpsYXBwbHkoZGF0YSwgcGFzdGUsICIudHh0IikKYGBgCiMjIyBzYXBwbHnplqLmlbAoKQorIOWQjeWJjeOBruWxnuaAp+S7mOOBjQpgYGB7cn0Kc2FwcGx5KGRhdGEsIHBhc3RlLCAiLnR4dCIpCmBgYAojIyMgYXBwbHnplqLmlbAKYGBge3J9CnRlc3RfbWF0cml4IDwtIG1hdHJpeChjKDEsIDIsIDMsIDQsIDUsIDYpLCBucm93ID0gMywgbmNvbCA9IDIpCnRlc3RfbWF0cml4Cgojcm93U3Vtcyh0ZXN0X21hdHJpeCkKYXBwbHkodGVzdF9tYXRyaXgsIDEsIHN1bSkgCgojY293U3Vtcyh0ZXN0X21hdHJpeCkKYXBwbHkodGVzdF9tYXRyaXgsIDIsIHN1bSkKCmFwcGx5KHRlc3RfbWF0cml4LCBjKDEsMiksIGZ1bmN0aW9uKHgpIHgqMTApCmBgYAoKIyMg44OG44Kt44K544OI44OV44Kh44Kk44Or44Gu6Kqt44G/6L6844G/CiMjIyDjg4fjgqPjg6zjgq/jg4jjg6rlhoXjg5XjgqHjgqTjg6vlkI3jga7lj5blvpcKYGBge3J9CmRpck5hbWUgPC0idGVzdGRhdGEiCmZpbGVzPC0gbGlzdC5maWxlcyhkaXJOYW1lKQpmaWxlcwpgYGAKCiMjIyDnm7jlr77jg5Hjgrnjga7lj5blvpcKYGBge3J9CmZpbGVzRGlyIDwtIHVubGlzdChsYXBwbHkoZGlyTmFtZSwgcGFzdGUsIGZpbGVzLCBzZXAgPSAiLyIpKQpmaWxlc0RpcgpgYGAKCiMjIyAg44OV44Kh44Kk44Or44Gu6Kqt44G/6L6844G/CmBgYHtyfQp0ZXN0X3Rlc3RzIDwtIHNhcHBseShmaWxlc0RpciwgcmVhZExpbmVzKQpgYGAKIyMgPGEgaHJlZj0iaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2NsZWFuTkxQL2NsZWFuTkxQLnBkZiIgdGFyZ2V0PSJfYmxhbmsiPmNsZWFuTkxQ44OR44OD44Kx44O844K4PC9hPuOBq+OCiOOCi+aWh+abuOmgu+W6puihjOWIl+OBruS9nOaIkAoKIyMjIOODqeOCpOODluODqeODquOBruiqreOBv+i+vOOBvwpgYGB7cn0KbGlicmFyeShjbGVhbk5MUCkKYGBgCgojIyMg5paH5pu46aC75bqm6KGM5YiXCmBgYHtyfQpjbmxwX2luaXRfdWRwaXBlKCkKCnRlc3RfcmVzPC1jbmxwX2Fubm90YXRlKGlucHV0ID0gdGVzdF90ZXN0cykKI1ZpZXcodGVzdF9yZXMkdG9rZW4pCgp0ZXN0X3JlcyR0b2tlbiRsZW1tYQoKdGVzdF9kb2NNdHggPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgodGFibGUodGVzdF9yZXMkdG9rZW4kbGVtbWEsIHRlc3RfcmVzJHRva2VuJGRvY19pZCkpCmhlYWQodGVzdF9kb2NNdHgpCmNvbG5hbWVzKHRlc3RfZG9jTXR4KSA8LSBmaWxlcwpoZWFkKHRlc3RfZG9jTXR4KQpgYGAKCiMjIFRGLUlERgorIOODhuOCreOCueODiOeJueacieOBruWHuuePvuWNmOiqnuOBq+WvvuOBl+OBpuOAgemHjeOBv+OBpeOBkeOCkuOBmeOCiworIOODhuOCreOCueODiOOBq+WFsemAmuOBmeOCi+WNmOiqnuOBq+WvvuOBl+OBpuOBr++8iOS9juOBhO+8iemHjeOBv+S7mOOBkQoKIyMjIFRGLUlERiAxCiQkdz10Zipsb2coXGZyYWN7Tn17ZGZ9KSAkJAoKIyMjIFRGLUlERiAyCiQkdz10ZioobG9nKFxmcmFje059e2RmfSkrMSkgJCQKCiMjIyBEb2N1bWVudCBGcmVxdWVuY3kKYGBge3J9CiMgdGhlIGRvY3VtZW50IG51bWJlcnMKTjwtbmNvbCh0ZXN0X2RvY010eCkKCmRvY0ZyZXE8LWFwcGx5KHRlc3RfZG9jTXR4LCAxLCBmdW5jdGlvbih4KSBsZW5ndGgoeFt4PjBdKSkKaGVhZChkb2NGcmVxKQpgYGAKCiMjIyBURi1JREYgMQpgYGB7cn0KdGZfaWRmMSA8LSB0ZXN0X2RvY010eCpsb2coTi9kb2NGcmVxKQpoZWFkKHRmX2lkZjEpCmBgYAoKIyMjIFRGLUlERiAyCmBgYHtyfQp0Zl9pZGYyIDwtIHRlc3RfZG9jTXR4Kihsb2coTi9kb2NGcmVxKSsxKQpoZWFkKHRmX2lkZjIpCmBgYAoKIyMg44Kq44Oz44Op44Kk44Oz6KiY5LqL44GL44KJ5oOF5aCx44KS5Y+W5b6XIChSZWYuIExlYzAyKQojIyMg44Op44Kk44OW44Op44Oq44Gu6Kqt44G/6L6844G/CmBgYHtyfQpsaWJyYXJ5KGh0dHIpCmxpYnJhcnkocnZlc3QpCmBgYAoKIyMjIOiHquS9nOmWouaVsApgYGB7cn0KZ2V0QXJ0aWNsZUNvbnRlbnQgPC0gZnVuY3Rpb24odXJsKXsKICByZXNwb25zZSA8LSBHRVQodXJsLCB1c2VyX2FnZW50KCJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxNF83KSBBcHBsZVdlYktpdC82MDUuMS4xNSAoS0hUTUwsIGxpa2UgR2Vja28pIFZlcnNpb24vMTguMCBTYWZhcmkvNjA1LjEuMTUiKSkKICBwYWdlIDwtIHJlYWRfaHRtbChyZXNwb25zZSkKICBhcnRpY2xlX2NvbnRlbnQgPC0gaHRtbF90ZXh0KGh0bWxfbm9kZXMocGFnZSwgInAudHh0IiksIHRyaW0gPSBUUlVFKQogIGNsZWFuZWRfY29udGVudCA8LSB0cmltd3MoYXJ0aWNsZV9jb250ZW50KQogIGNsZWFuZWRfY29udGVudCA8LSBwYXN0ZShjbGVhbmVkX2NvbnRlbnQsIGNvbGxhcHNlID0gIiIpCn0KYGBgCgojIyMg44OL44Ol44O844K56KiY5LqLCiogPGEgaHJlZj0iaHR0cHM6Ly9tYWluaWNoaS5qcC9lbmdsaXNoL2FydGljbGVzLzIwMjQxMTA3L3AyYS8wMG0vMGV0LzAxNTAwMGMiIHRhcmdldD0iX2JsYW5rIj5SZXRybyBKYXBhbjogVG9reW8gdGV4dGJvb2sgbGlicmFyeSBkZXNpZ25lZCBpbiBhcnQgZGVjbyBzdHlsZSBzdG9yZXMgaGlzdG9yaWNhbCBtYXRlcmlhbHM8L2E+CiogPGEgaHJlZj0iaHR0cHM6Ly9tYWluaWNoaS5qcC9lbmdsaXNoL2FydGljbGVzLzIwMjQxMTEwL3AyZy8wMG0vMGxpLzAyODAwMGMiIHRhcmdldD0iX2JsYW5rIj5KYXBhbiByZXNlYXJjaGVycyB0byBzZWUgaWYgc2tpbiB2aWJyYXRpb24gYm9vc3RzIG1lbnRhbCBoZWFsdGg8L2E+CiogPGEgaHJlZj0iaHR0cHM6Ly9tYWluaWNoaS5qcC9lbmdsaXNoL2FydGljbGVzLzIwMjQxMTExL3AyZy8wMG0vMG5hLzA0ODAwMGMiIHRhcmdldD0iX2JsYW5rIj5Jc2hpYmEgc3VzcGVjdGVkIG9mIGZhbGxpbmcgYXNsZWVwIGR1cmluZyBEaWV0IHNlc3Npb24gdG8gc2VsZWN0IFBNPC9hPgoqIDxhIGhyZWY9Imh0dHBzOi8vbWFpbmljaGkuanAvZW5nbGlzaC9hcnRpY2xlcy8yMDI0MTExMi9wMmcvMDBtLzBzcC8wMDUwMDBjIiB0YXJnZXQ9Il9ibGFuayI+Rm9vdGJhbGw6IEthenV5b3NoaSBNaXVyYSwgNTcsIHNldCB0byBwbGF5IDQwdGggc2Vhc29uIGFzIHByb2Zlc3Npb25hbDwvYT4KCiMjIyDoqJjkuovjg4fjg7zjgr/jga7lj5blvpcKYGBge3J9CmFydGljbGVfdXJscyA8LSBjKCkKYXJ0aWNsZV91cmxzIDwtIGFwcGVuZChhcnRpY2xlX3VybHMsImh0dHBzOi8vbWFpbmljaGkuanAvZW5nbGlzaC9hcnRpY2xlcy8yMDI0MTEwNy9wMmEvMDBtLzBldC8wMTUwMDBjIikKI2FydGljbGVfdXJsc1tbMV1dIDwtICJodHRwczovL21haW5pY2hpLmpwL2VuZ2xpc2gvYXJ0aWNsZXMvMjAyNDExMDcvcDJhLzAwbS8wZXQvMDE1MDAwYyIKCmFydGljbGVfdXJscyAKbGVuZ3RoKGFydGljbGVfdXJscykKYXJ0aWNsZV91cmxzIDwtIGFwcGVuZChhcnRpY2xlX3VybHMsImh0dHBzOi8vbWFpbmljaGkuanAvZW5nbGlzaC9hcnRpY2xlcy8yMDI0MTExMC9wMmcvMDBtLzBsaS8wMjgwMDBjIikKYXJ0aWNsZV91cmxzIDwtIGFwcGVuZChhcnRpY2xlX3VybHMsImh0dHBzOi8vbWFpbmljaGkuanAvZW5nbGlzaC9hcnRpY2xlcy8yMDI0MTExMS9wMmcvMDBtLzBuYS8wNDgwMDBjIikKYXJ0aWNsZV91cmxzIDwtIGFwcGVuZChhcnRpY2xlX3VybHMsImh0dHBzOi8vbWFpbmljaGkuanAvZW5nbGlzaC9hcnRpY2xlcy8yMDI0MTExMi9wMmcvMDBtLzBzcC8wMDUwMDBjIikKbGVuZ3RoKGFydGljbGVfdXJscykKYGBgCgojIyMgc3Vic3RyaW5nIG9mIGEgY29udGVudApgYGB7cn0KY29udGVudDEgPC0gZ2V0QXJ0aWNsZUNvbnRlbnQoYXJ0aWNsZV91cmxzWzFdKQpzdWJzdHJpbmcoY29udGVudDEsIDEsIDEwMCkKYGBgCgpgYGB7cn0KY29udGVudHMgPC0gbGFwcGx5KGFydGljbGVfdXJscywgZ2V0QXJ0aWNsZUNvbnRlbnQpCmZvcih0eHQgaW4gY29udGVudHMpICBwcmludChzdWJzdHJpbmcodHh0LCAxLCA4MCkpCmxlbmd0aChjb250ZW50cykKYGBgCgojIyMgVG9rZW5pemF0aW9uICjlvaLmhYvntKDop6PmnpApCmBgYHtyfQojY25scF9pbml0X3VkcGlwZSgpCnJlczwtY25scF9hbm5vdGF0ZShpbnB1dCA9IGNvbnRlbnRzKQpkaW0ocmVzJHRva2VuKQpgYGAKCiMjIyDoqJjlj7fjg7vmlbDlrZfjgpLpmaTljrsKYGBge3J9CnJlcyA8LSByZXMkdG9rZW5bIXJlcyR0b2tlbiR1cG9zICVpbiUgYygiUFVOQ1QiLCJTWU0iLCJOVU0iKSxdCmRpbShyZXMpClZpZXcocmVzKQpgYGAKCiMjIyDmnaHku7bkuIDoh7TjgavjgojjgovliJfmir3lh7oKYGBge3J9CmhlYWQocmVzWyxjb2xuYW1lcyhyZXMpPT0ibGVtbWEiXSkKYGBgCgojIyMg5p2h5Lu25LiA6Ie044Gr44KI44KL6KGM5oq95Ye6CmBgYHtyfQpyZXNbcmVzJGxlbW1hPT0iJ3MiLF0KYGBgCgojIyMg5paH5pu46aC75bqm6KGM5YiXCmBgYHtyfQpkb2NNdHggPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgodGFibGUocmVzJGxlbW1hLCByZXMkZG9jX2lkKSkKaGVhZChkb2NNdHgpCmBgYAoKIyMjIOWIl+WQjeOBruWkieabtApgYGB7cn0KY29sbmFtZXMoZG9jTXR4KSA8LSBzYXBwbHkoc2VxKDE6NCksIGZ1bmN0aW9uKHgpIHBhc3RlMCgiYXJ0aWNsZSIsIHgpKQpgYGAKCiMjIyBUb2tlbnMKYGBge3J9CmNvbFN1bXMoZG9jTXR4KSAjVG9rZW5zCmBgYAoKIyMjIFR5cGVzCmBgYHtyfQpsZW5ndGgoZG9jTXR4JGFydGljbGUxW2RvY010eCRhcnRpY2xlMT4wXSkKYGBgCgojIyMgVHlwZXMKYGBge3J9Cmxlbmd0aChkb2NNdHgkYXJ0aWNsZTFbZG9jTXR4JGFydGljbGUxPjBdKQpgYGAKIyMjIERvY3VtZW50IEZyZXFlbmN5CmBgYHtyfQpOPC1uY29sKGRvY010eCkKCmRvY0ZyZXE8LWFwcGx5KGRvY010eCwgMSwgZnVuY3Rpb24oeCkgbGVuZ3RoKHhbeD4wXSkpCmhlYWQoZG9jRnJlcSkKYGBgCgojIyMgdGZfaWRmMQpgYGB7cn0KdGZfaWRmMSA8LSBkb2NNdHgqbG9nKE4vZG9jRnJlcSkKaGVhZCh0Zl9pZGYxKQpgYGAKCiMjIyB0Zl9pZGYxCmBgYHtyfQp0Zl9pZGYyIDwtIGRvY010eCoobG9nKE4vZG9jRnJlcSkrMSkKaGVhZCh0Zl9pZGYyKQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsgIj7nt7Tnv5I8L3NwYW4+OkFwcGx56Zai5pWw44KS5rS755So44GX44Gm44CB5LiK44GuZG9jTXR444Gu57Sg6aC75bqm5paH5pu46KGM5YiX44KS55u45a++6aC75bqm5paH5pu46KGM5YiX44Gr5aSJ5o+b44GX44CBVEYtSURGMeOCkuioiOeul+OBl+OBpuOBj+OBoOOBleOBhAoKYGBge3IsIGVjaG89RkFMU0V9Ck48LW5jb2woZG9jTXR4KQpkb2NGcmVxPC1hcHBseShkb2NNdHgsIDEsIGZ1bmN0aW9uKHgpIGxlbmd0aCh4W3g+MF0pKQoKcmVsYXRpdmVfZG9jTXR4IDwtIGFwcGx5KGRvY010eCwgMiwgZnVuY3Rpb24oeCkgeC9zdW0oeCkpCnJlbGF0aXZlX3RmX2lkZjEgPC0gcmVsYXRpdmVfZG9jTXR4KmxvZyhOL2RvY0ZyZXEpCmBgYAojIyMg55u45a++6aC75bqm5paH5pu46KGM5YiX44Gu5LiA6YOo6KGo56S6CmBgYHtyLCBlY2hvPUZBTFNFfQpoZWFkKHJlbGF0aXZlX2RvY010eCkKYGBgCgojIyMgdGZfaWRmMeioiOeul+e1kOaenOS4gOmDqOihqOekugpgYGB7ciwgZWNobz1GQUxTRX0KaGVhZChyZWxhdGl2ZV90Zl9pZGYxKQpgYGAKCgo=