[翻訳] text2vec vignette: GloVeによる単語埋め込み

この文書は Dmitriy Selivanov によるRパッケージ[`text2vec`](https://cran.r-project.org/web/packages/text2vec/) (version 0.3.0) のビネット "[GloVe Word Embeddings](https://cran.r-project.org/web/packages/text2vec/vignettes/glove.html)" の日本語訳です．
ただし文中の注は全て訳者によるものです[^translater_env]．

License: [MIT](https://opensource.org/licenses/MIT)

**関連文書**

+ [text2vec vignette: text2vecパッケージによるテキスト分析](http://qiita.com/nakamichi/items/1bf2ee393cb407d9fb74)
+ text2vec vignette: GloVeによる単語埋め込み
+ [text2vec vignette: 発展的な話題](http://qiita.com/nakamichi/items/9805cd3831f674d81dcd)

----

<!-- # Word embeddings -->
# 単語埋め込み

<!-- After Tomas Mikolov et al. released the [word2vec](https://code.google.com/p/word2vec/) tool, there was a boom of articles about word vector representations. One of the best of these articles is Stanford's [GloVe: Global Vectors for Word Representation](http://nlp.stanford.edu/projects/glove/), which did explained why such algorithms work and reformulating word2vec optimizations as a special kind of factorization for word co-occurence matrices.  -->
Tomas Mikolovらが[word2vec](https://code.google.com/archive/p/word2vec/)のツールを公開して以来，単語のベクトル表現に関する論文がブームとなりました．それらの論文の中で最良のもののひとつはStanford大学の[GloVe: Global Vectors for Word Representation](http://nlp.stanford.edu/projects/glove/)です．この論文は，なぜこのようなアルゴリズムがうまくいくのか説明し，word2vecで使われた最適化を単語共起行列の因子分解の特別な場合として定式化し直しています．

<!-- Here I will briefly introduce the GloVe algorithm and show how to use its text2vec implementation. -->
ここではGloVeアルゴリズムを簡単に紹介し，text2vecにおける実装の使い方を示します．

<!-- # Introduction to the GloVe algorithm -->
# GloVeアルゴリズム入門

<!-- GloVe algorithm consists of following steps: -->
GloVeアルゴリズムは以下のステップからなります．

<!-- 1. Collect word cooccurence statistics in a form of word coocurence matrix $X$. Each element $X_{ij}$ of such matrix represents measure of how often word *i* appears in context of word *j*. Usually we scan our corpus in the following manner: for each term we look for context terms withing some area, a *window_size* before and a *window_size* after. Also we give less weight for more distant words, usually using the formula $$decay = 1/offset$$. -->
1. 単語の共起回数を数えて，単語共起行列$X$の形にまとめます．行列の各要素$X_{ij}$は，単語$i$が単語$j$の文脈でどのくらい頻繁に出現するかの程度を表しています．普通，次のようにしてコーパスを走査します．各単語に対して，あるウィンドウサイズの分だけ前後の範囲内で文脈語を探します．また，遠くの単語ほど小さな重みを割り当てるようにします．普通，重み付けには以下の式を使います．
$$\text{decay} = 1 / \text{offset} .$$

<!-- 2. Define soft constraints for each word pair:  -->
<!-- $$w_i^Tw_j + b_i + b_j = log(X_{ij})$$ -->
<!-- Here $w_i$ - vector for the main word, $w_j$ - vector for the context word, $b_i$, $b_j$ are scalar biases for the main and context words. -->

2. それぞれの単語対に対してソフトな制約を入れます．
$$w_i^T w_j + b_i + b_j = log(X_{ij}) .$$
ここで$w_i$は注目している単語のベクトルで，$w_j$は文脈語のベクトルです．$b_i$，$b_j$は注目している単語と文脈語のスカラーバイアスです．

<!-- 3. Define a cost function  -->
<!-- $$J = \sum_{i=1}^V \sum_{j=1}^V \; f(X_{ij}) ( w_i^T w_j + b_i + b_j - \log X_{ij})^2$$ -->
<!-- Here $f$ is a weighting function which help us to prevent learning only from extremely common word pairs. The GloVe authors choose the following fucntion: -->
3. 損失関数を
$$J = \sum_{i=1}^V \sum_{j=1}^V \; f(X_{ij}) ( w_i^T w_j + b_i + b_j - \log X_{ij})^2$$
で定義します．ここで$f$は重み付け関数であり，極端に出現頻度が高い単語対だけから学習してしまうのを防ぎます．GloVe論文の著者は以下の関数を選んでいます．

$$f(X_{ij}) =
\begin{cases}
(\frac{X_{ij}}{x_{max}})^\alpha & \text{if } X_{ij} < x_{max} \\
1 & \text{otherwise}
\end{cases}
$$

<!-- # Canonical example: linguistic regularities -->
# 標準的な例：言語の規則性（linguistic regularities）

<!-- Now let's examine how GloVe embeddings works. As commonly known, word2vec word vectors capture many linguistic regularities. To give the canonical example, if we take word vectors for the words "paris," "france," and "italy" and perform the following operation:  -->
さて，GloVeによる埋め込みがどのように機能するか調べてみましょう．周知の通り，word2vecの単語ベクトルは言語のいろいろな規則性をうまく捉えています．標準的な例として，もし"paris"，france"，"italy"の単語ベクトルに
$$vector('paris') - vector('france') + vector('italy')$$

<!-- the resulting vector will be close to the vector for "rome." -->

という操作を行えば，結果のベクトルは"rome"のベクトルに近いものになるでしょう．

<!-- Let's download the same Wikipedia data used as a demo by word2vec: -->
word2vecでデモに使われたのと同じWikipediaのデータをダウンロードしましょう．


```r
library(text2vec)
library(readr)
temp <- tempfile()
download.file('http://mattmahoney.net/dc/text8.zip', temp)
wiki <- read_lines(unz(temp, "text8"))
unlink(temp)
```

<!-- In the next step we will create a vocabulary, a set of words for which we want to learn word vectors. Note, that all of text2vec's functions which operate on raw text data (`create_vocabulary`, `create_corpus`, `create_dtm`, `create_tcm`) have a streaming API and you should iterate over tokens as the first argument for these functions. -->
次のステップでは語彙，すなわち単語ベクトルを学習する対象となる単語の集合を作成します．text2vecの関数のうち，生のテキストデータに作用するもの（`create_vocabulary`, `create_corpus`, `create_dtm`, `create_tcm`）は全てストリーミングAPIを持っており，これらの関数の第一引数にトークンのイテレータを渡すべきです．


```r
# トークンのイテレータを作成
tokens <- strsplit(wiki, split = " ", fixed = T)
# 語彙を作成．タームはユニグラム（単語）
vocab <- create_vocabulary(itoken(tokens))
```

<!-- These words should not be too rare. Fot example we cannot calculate a meaningful word vector for a word which we saw only once in the entire corpus. Here we will take only words which appear at least 5 times. *text2vec* provides more options to filter vocabulary (see `?prune_vocabulary`). -->
語彙に含まれる単語は出現頻度が小さすぎてはいけません．たとえば，コーパス全体の中でたった一度しか出現しない単語に対しては，意味のある単語ベクトルを計算することはできません．ここでは最低5回は出現する単語のみ使うことにします．text2vecは余分な単語を語彙から除くためのオプションを他にも提供しています（`?prune_vocabulary`を見てください）．


```r
vocab <- prune_vocabulary(vocab, term_count_min = 5L)
```

<!-- Now we have 71,290 terms in the vocabulary and are ready to construct term-co-occurence matrix (TCM). -->
こうして語彙に含まれるタームは71,290個になり，ターム共起行列（term-co-occurence matrix, TCM）を構築する準備ができました．


```r
# create_vocab_corpus関数にイテレータを渡す
it <- itoken(tokens)
# 余分な単語を除いた語彙を使う
vectorizer <- vocab_vectorizer(vocab,
                               # 入力文書のベクトル化はしない
                               grow_dtm = FALSE,
                               # 文脈語のウィンドウサイズは5
                               skip_grams_window = 5L)
tcm <- create_tcm(it, vectorizer)
```

<!-- Now we have a TCM matrix and can factorize it via the GloVe algorithm.   -->
<!-- text2vec uses a parallel stochastic gradient descend algorithm. By default it use all cores on your machine, but you can specify the number of cores if you wish. For example, to use 4 threads, call `RcppParallel::setThreadOptions(numThreads = 4)`.  -->
TCMができたので，これをGloVeアルゴリズムで因子分解することができます．text2vecでは並列化された確率的勾配降下法を使用します．デフォルトではマシンの全てのコアを使いますが，必要ならコア数を指定することもできます．たとえば4スレッド使うには`RcppParallel::setThreadOptions(numThreads = 4)`を呼びます．

<!-- Let's fit our model. (It can take several of minutes to fit!) -->
モデルをフィットさせてみましょう．（フィッティングには数分かかるかもしれません！）[^exp]


```r
fit <- glove(tcm = tcm,
             word_vectors_size = 50,
             x_max = 10, learning_rate = 0.2,
             num_iters = 15)
```

```
## 2016-01-10 14:12:37 - epoch 1, expected cost 0.0662
## 2016-01-10 14:12:51 - epoch 2, expected cost 0.0472
## 2016-01-10 14:13:06 - epoch 3, expected cost 0.0429
## 2016-01-10 14:13:21 - epoch 4, expected cost 0.0406
## 2016-01-10 14:13:36 - epoch 5, expected cost 0.0391
## 2016-01-10 14:13:50 - epoch 6, expected cost 0.0381
## 2016-01-10 14:14:05 - epoch 7, expected cost 0.0373
## 2016-01-10 14:14:19 - epoch 8, expected cost 0.0366
## 2016-01-10 14:14:33 - epoch 9, expected cost 0.0362
## 2016-01-10 14:14:47 - epoch 10, expected cost 0.0358
## 2016-01-10 14:15:01 - epoch 11, expected cost 0.0355
## 2016-01-10 14:15:16 - epoch 12, expected cost 0.0351
## 2016-01-10 14:15:30 - epoch 13, expected cost 0.0349
## 2016-01-10 14:15:44 - epoch 14, expected cost 0.0347
## 2016-01-10 14:15:59 - epoch 15, expected cost 0.0345
```
<!--
## 2016-09-04 18:26:47 - epoch 1, expected cost 0.0877
## 2016-09-04 18:27:19 - epoch 2, expected cost 0.0590
## 2016-09-04 18:27:50 - epoch 3, expected cost 0.0521
## 2016-09-04 18:28:21 - epoch 4, expected cost 0.0484
## 2016-09-04 18:28:55 - epoch 5, expected cost 0.0460
## 2016-09-04 18:29:29 - epoch 6, expected cost 0.0443
## 2016-09-04 18:30:02 - epoch 7, expected cost 0.0430
## 2016-09-04 18:30:33 - epoch 8, expected cost 0.0420
## 2016-09-04 18:31:05 - epoch 9, expected cost 0.0412
## 2016-09-04 18:31:38 - epoch 10, expected cost 0.0405
## 2016-09-04 18:32:11 - epoch 11, expected cost 0.0399
## 2016-09-04 18:32:44 - epoch 12, expected cost 0.0394
## 2016-09-04 18:33:16 - epoch 13, expected cost 0.0390
## 2016-09-04 18:33:49 - epoch 14, expected cost 0.0387
## 2016-09-04 18:34:23 - epoch 15, expected cost 0.0384
 -->

<!-- And now we get the word vectors: -->
こうして単語ベクトルが得られます．


```r
word_vectors <- fit$word_vectors[[1]] + fit$word_vectors[[2]]
rownames(word_vectors) <- rownames(tcm)
```

<!-- We can find the closest word vectors for our *paris - france + italy* example: -->
例に挙げた`paris - france + italy`に最も近いベクトルを見つけることができます[^exp_result]．


```r
word_vectors_norm <- sqrt(rowSums(word_vectors ^ 2))

rome <- word_vectors['paris', , drop = FALSE] -
  word_vectors['france', , drop = FALSE] +
  word_vectors['italy', , drop = FALSE]

cos_dist <- text2vec:::cosine(rome,
                              word_vectors,
                              word_vectors_norm)
head(sort(cos_dist[1,], decreasing = T), 10)
```

```
##     paris    venice     genoa      rome  florence
## 0.7811252 0.7763088 0.7048109 0.6696540 0.6580989
```
<!--
##     paris    vienna  florence    venice   bologna  carthage    geneva
## 0.7443674 0.6555516 0.6537769 0.6472170 0.6267782 0.5964526 0.5952593
##     genoa     italy    madrid
## 0.5766377 0.5739754 0.5665218
-->

<!-- You can achieve **much** better results by experimenting with `skip_grams_window` and the parameters of the `glove()` function (including word vectors size and the number of iterations). For more details and large-scale experiments on wikipedia data see this [post](http://dsnotes.com/blog/text2vec/2015/12/01/glove-enwiki/) on my blog. -->
`skip_grams_window`と`glove()`関数のパラメータ（単語ベクトルのサイズや反復回数を含みます）を変えて実験すれば，**はるかに**良い結果を得ることができます．さらなる詳細とWikipediaデータによる大規模実験については私のブログの[記事](http://dsnotes.com/blog/text2vec/2015/12/01/glove-enwiki/)を見てください．



----

[^translater_env]: 訳者の環境を示しておく．

    ```r
    devtools::session_info()
    ```

    ```
    ## Session info --------------------------------------------------------------
    ```

    ```
    ##  setting  value                       
    ##  version  R version 3.3.1 (2016-06-21)
    ##  system   x86_64, mingw32             
    ##  ui       RTerm                       
    ##  language (EN)                        
    ##  collate  Japanese_Japan.932          
    ##  tz       Asia/Tokyo                  
    ##  date     2016-09-04
    ```

    ```
    ## Packages ------------------------------------------------------------------
    ```

    ```
    ##  package       * version date       source        
    ##  assertthat      0.1     2013-12-06 CRAN (R 3.3.1)
    ##  chron           2.3-47  2015-06-24 CRAN (R 3.3.1)
    ##  codetools       0.2-14  2015-07-15 CRAN (R 3.3.1)
    ##  data.table      1.9.6   2015-09-19 CRAN (R 3.3.1)
    ##  devtools        1.12.0  2016-06-24 CRAN (R 3.3.1)
    ##  digest          0.6.10  2016-08-02 CRAN (R 3.3.1)
    ##  evaluate        0.9     2016-04-29 CRAN (R 3.3.1)
    ##  foreach         1.4.3   2015-10-13 CRAN (R 3.2.2)
    ##  formatR         1.4     2016-05-09 CRAN (R 3.3.1)
    ##  htmltools       0.3.5   2016-03-21 CRAN (R 3.3.1)
    ##  iterators       1.0.8   2015-10-13 CRAN (R 3.2.2)
    ##  knitr           1.14    2016-08-13 CRAN (R 3.3.1)
    ##  lattice         0.20-33 2015-07-14 CRAN (R 3.3.1)
    ##  magrittr        1.5     2014-11-22 CRAN (R 3.3.1)
    ##  Matrix          1.2-6   2016-05-02 CRAN (R 3.3.1)
    ##  memoise         1.0.0   2016-01-29 CRAN (R 3.3.1)
    ##  Rcpp            0.12.6  2016-07-19 CRAN (R 3.3.1)
    ##  RcppParallel    4.3.20  2016-08-16 CRAN (R 3.3.1)
    ##  readr         * 1.0.0   2016-08-03 CRAN (R 3.3.1)
    ##  RevoUtils       10.0.1  2016-08-24 local         
    ##  RevoUtilsMath * 8.0.3   2016-04-13 local         
    ##  rmarkdown       1.0     2016-07-08 CRAN (R 3.3.1)
    ##  stringi         1.1.1   2016-05-27 CRAN (R 3.3.0)
    ##  stringr         1.1.0   2016-08-19 CRAN (R 3.3.1)
    ##  text2vec      * 0.3.0   2016-03-31 CRAN (R 3.3.1)
    ##  tibble          1.2     2016-08-26 CRAN (R 3.3.1)
    ##  withr           1.0.2   2016-06-20 CRAN (R 3.3.1)
    ##  yaml            2.1.13  2014-06-12 CRAN (R 3.3.1)
    ```

[^exp]: 以下の実行結果は原文におけるもの．

[^exp_result]: 実験の結果は乱数に依存する．ここでの結果は原文に記載されているもの（訳者の実験ではromeが出てこなかったので……）．