-
Notifications
You must be signed in to change notification settings - Fork 0
/
weighting.qmd
111 lines (86 loc) · 5.44 KB
/
weighting.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 単語頻度の重みづけ {#weighting}
```{r}
#| label: setup
#| code-fold: true
dat_txt <-
tibble::tibble(
doc_id = seq_along(audubon::polano) |> as.character(),
text = audubon::polano
) |>
dplyr::mutate(text = audubon::strj_normalize(text))
dat <- gibasa::tokenize(dat_txt, text, doc_id)
dat_count <- dat |>
gibasa::prettify(col_select = c("POS1", "Original")) |>
dplyr::filter(POS1 %in% c("名詞", "動詞", "形容詞")) |>
dplyr::mutate(
doc_id = forcats::fct_drop(doc_id),
token = dplyr::if_else(is.na(Original), token, Original)
) |>
dplyr::count(doc_id, token)
```
## tidytextによる重みづけ
`tidytext::bind_tf_idf`を使うと単語頻度からTF-IDFを算出することができます。
```{r}
dat_count |>
tidytext::bind_tf_idf(token, doc_id, n) |>
dplyr::slice_max(tf_idf, n = 5L)
```
tidytextにおけるTFとIDFは、RMeCabにおけるそれとは採用している計算式が異なるため、計算結果が異なります。TFはRMeCabでは生の索引語頻度(tfの場合)ですが、tidytextでは文書内での相対頻度になります。また、IDFはRMeCabでは対数の底が2であるのに対して、tidytextでは底が`exp(1)`であるなどの違いがあります。
## gibasaによる重みづけ
gibasaはRMeCabにおける単語頻度の重みづけを`tidytext::bind_tf_idf`と同様のスタイルでおこなうことができる関数`gibasa::bind_tf_idf2`を提供しています。
RMeCabは以下の単語頻度の重みづけをサポートしています。
- 局所的重み(TF)
- tf(索引語頻度)
- tf2(対数化索引語頻度)
- tf3(2進重み)
- 大域的重み(IDF)
- idf(文書頻度の逆数)
- idf2(大域的IDF)
- idf3(確率的IDF)
- idf4(エントロピー)
- 正規化
- norm(コサイン正規化)
gibasaはこれらの重みづけを再実装しています。ただし、`tf="tf"`はgibasaでは相対頻度になるため、RMeCabの`weight="tf*idf"`に相当する出力を得るには、たとえば次のように計算します。
```{r}
dat_count |>
gibasa::bind_tf_idf2(token, doc_id, n) |>
dplyr::mutate(
tf_idf = n * idf
) |>
dplyr::slice_max(tf_idf, n = 5L)
```
なお、注意点として、RMeCabの単語を数える機能は、品詞情報(POS1とPOS2まで)を確認しながら単語を数えているようなので、ここでのように原形だけを見て数えた結果とは必ずしも一致しません。
## udpipeによる重みづけ
[udpipe](https://bnosac.github.io/udpipe/en/)を使っても単語頻度とTF-IDFを算出できます。また、`udpipe::document_term_frequencies_statistics`では、TF、IDFとTF-IDFにくわえて、[Okapi BM25](https://en.wikipedia.org/wiki/Okapi_BM25)を計算することができます。
`udpipe::document_term_frequencies_statistics`には、パラメータとして`k`と`b`を渡すことができます。デフォルト値はそれぞれ`k=1.2`、`b=0.5`です。`k`の値を大きくすると、単語の出現数の増加に対してBM25の値もより大きくなりやすくなります。
`k=1.2`というのは、Elasticsearchでもデフォルト値として採用されている値です。Wikipediaや[Elasticsearchの技術記事](https://www.elastic.co/jp/blog/practical-bm25-part-3-considerations-for-picking-b-and-k1-in-elasticsearch)によると、`k`は`[1.2, 2.0]`、`b=.75`とした場合に、多くのケースでよい結果が得られるとされています。
dplyrを使っていればあまり意識する必要はないと思いますが、udpipeのこのあたりの関数の戻り値はdata.tableである点に注意してください。
```{r}
suppressPackageStartupMessages(require(dplyr))
dat |>
gibasa::prettify(col_select = c("POS1", "Original")) |>
dplyr::filter(POS1 %in% c("名詞", "動詞", "形容詞")) |>
dplyr::mutate(
doc_id = forcats::fct_drop(doc_id),
token = dplyr::if_else(is.na(Original), token, Original)
) |>
udpipe::document_term_frequencies(document = "doc_id", term = "token") |>
udpipe::document_term_frequencies_statistics(b = .75) |>
dplyr::slice_max(tf_bm25, n = 5L)
```
## tidyloによる重みづけ
TF-IDFによる単語頻度の重みづけのモチベーションは、索引語のなかでも特定の文書だけに多く出現していて、ほかの文書ではそれほど出現しないような「注目に値する語」を調べることにあります。
こうしたことを実現するための値として、[tidylo](https://github.com/juliasilge/tidylo)パッケージでは「重み付きログオッズ(weighted log odds)」を計算することができます。
```{r}
dat_count |>
tidylo::bind_log_odds(set = doc_id, feature = token, n = n) |>
dplyr::filter(is.finite(log_odds_weighted)) |>
dplyr::slice_max(log_odds_weighted, n = 5L)
```
ここで用いているデータは小説を改行ごとに一つの文書と見なしていたため、中には次のような極端に短い文書が含まれています。こうした文書では、直観的にはそれほどレアには思われない単語についてもオッズが極端に高くなってしまっているように見えます。
```{r}
dat_txt |>
dplyr::filter(doc_id %in% c(430, 536, 577, 824)) |>
dplyr::pull(text)
```
weighted log oddsについては[この資料](https://bookdown.org/Maxine/tidy-text-mining/weighted-log-odds-ratio.html)などを参照してください。