[翻訳] text2vec vignette: 発展的な話題

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

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

**関連文書**

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

----

<!-- ## Working with files -->
# ファイルの取り扱い

<!-- It this paragraph I will show how to use `text2vec` for vectorization of large collections of text stored in files. -->
この段落では，`text2vec`を使ってファイルに保存された巨大なテキストの集合をベクトル化する方法を示します．

<!-- Imagine we want to build a topic model with [lda](http://cran.r-project.org/package=lda) package. We have a collection of movie reviews stored in multiple text files on disk.  -->
[lda](http://cran.r-project.org/package=lda)パッケージでトピックモデルを構築したいとしましょう．映画レビューの集まりを，複数のテキストファイルでディスクに保存して持っているとします．

<!-- For this vignette we will create files from embedded `movie_review` dataset: -->
このビネットのために，組み込みの`movie_review`データセットからそのようなファイルを作成します．


```r
library(text2vec)
library(magrittr)
data("movie_review")
# 読み込みを簡単にするために，全ての改行を除いておく
movie_review$review <- gsub(pattern = '\n', replacement = ' ', 
                            x = movie_review$review, fixed = TRUE)
N_FILES <- 10
CHUNK_LEN <- nrow(movie_review) / N_FILES
files <- sapply(1:N_FILES, function(x) tempfile())
chunks <- split(movie_review, rep(1:N_FILES, each = nrow(movie_review) / N_FILES ))
for (i in 1:N_FILES ) {
  write.table(chunks[[i]], files[[i]], quote = T, row.names = F, col.names = T, sep = '|')
}
# どんなデータか見ておく
str(movie_review, strict.width = 'cut')
```

```
## 'data.frame':	5000 obs. of  3 variables:
##  $ id       : chr  "5814_8" "2381_9" "7759_3" "3630_4" ...
##  $ sentiment: int  1 1 0 0 1 1 0 0 0 1 ...
##  $ review   : chr  "With all this stuff going down at the moment with MJ"..
```

<!-- `text2vec` provides functions to **easily** work with files. -->
`text2vec`はファイルを**容易に**扱うための関数群を提供します．

<!-- User need to perform only a few things:  -->
ユーザはほんのいくつかの作業を行う必要があるだけです．

<!-- 1. Construct iterator over the files with `ifiles` function. -->
1. `ifiles`関数でファイルのイテレータを構築する．

<!-- 1. Provide reader function to `ifiles`. `text2vec` doesn't know anything about underlying files. They can be in plain text or some binary format. -->
2. `ifiles`にファイル読込関数を与える．`text2vec`は背後にあるファイルについては何も知りません．プレーンテキストでも何らかのバイナリ形式でも構いません．

<!-- 1. Construct tokens iterator from files iterator via `itoken` function. -->
3. `itoken`関数によってファイルからトークンのイテレータを構築する．

<!-- Lets see how it works: -->
どうやるのか見てみましょう．


```r
library(data.table)
reader <- function(x, ...) {
  # 読み込み
  chunk <- fread(x, header = T, sep = '|')
  # review列を選択
  res <- chunk$review
  # レビューにidを振る
  names(res) <- chunk$id
  res
}
# ファイルのイテレータを作成
it_files  <- ifiles(files, reader_function = reader)
# ファイルのイテレータからトークンのイテレータを作成
it_tokens <- itoken(it_files, preprocess_function = tolower, 
                    tokenizer = word_tokenizer, progessbar = FALSE)

vocab <- create_vocabulary(it_tokens)
```

<!-- Now are able to construct DTM in `lda_c` format (as required by `lda` package): -->
これでDTMを（`lda`パッケージで必要な）`lda_c`形式で構築することができます．


```r
# イテレータは再初期化が必要！
# イテレータは変更可能（mutable）で既に空になっている！
# try(it_files$nextElem())
it_files  <- ifiles(files, reader_function = reader)
it_tokens <- itoken(it_files, preprocess_function = tolower, 
                    tokenizer = word_tokenizer, progessbar = FALSE)

dtm <- create_dtm(it_tokens, vectorizer = vocab_vectorizer(vocab), type = 'lda_c')
str(dtm, list.len = 5)
```

```
## List of 5000
##  $ 5814_8  : int [1:2, 1:228] 7489 1 7835 1 8174 3 8365 1 8816 4 ...
##  $ 2381_9  : int [1:2, 1:109] 7835 1 8174 2 8816 2 9560 1 9562 1 ...
##  $ 7759_3  : int [1:2, 1:253] 7593 1 7790 1 8174 1 8634 1 8708 1 ...
##  $ 3630_4  : int [1:2, 1:218] 7410 1 7841 1 8174 1 8258 1 8572 1 ...
##  $ 9495_8  : int [1:2, 1:256] 7489 1 7659 1 7835 1 7878 1 8174 1 ...
##   [list output truncated]
```

<!-- Note that DTM has document ids. They are inhereted from document names we assigned in `reader` function. This is a convenient way to assign document ids when working with files. -->
DTMに文書IDが付いていることに注目してください．これらのIDは`reader`関数で割り当てた名前から引き継がれています．ファイルを扱うときに文書IDを割り当てる便利な方法です．

<!-- Now we can fit `LDA` model using `lda::lda.collapsed.gibbs.sampler()` function: -->
これで`lda::lda.collapsed.gibbs.sampler()`関数を使って`LDA`モデルをフィットさせることができます．


```r
library(lda)
# トピックの事前分布
alpha = 0.1
# 単語の事前分布
eta = 0.001
# トピック30個のモデルをフィットさせる．Gibbsサンプリングは30回
lda_fit <- lda.collapsed.gibbs.sampler(documents = dtm, K = 30, 
                                       vocab = vocab$vocab$terms, 
                                       alpha = alpha, 
                                       eta = eta,
                                       num.iterations = 30, 
                                       trace = 2L)
```

<!-- # Parallel mode - using multiple cores -->
# マルチコアを使った並列モード

<!-- `create_dtm`, `create_tcm`, `create_vocabulary` are albe to take advantage of multicore machines and do it in transparent manner. In contrast to GloVe fitting which uses low-level thread parallelism via `RcppParallel`, these functions use standart R high-level parallelism on top of `foreach` package. They are flexible and can use diffrent parallel backends - `doParallel`, `doRedis`, etc. But user should remember that such high-level parallelism **can** involve significant overhead. -->
`create_dtm`, `create_tcm`, `create_vocabulary`はマルチコアのマシンを透過的なやり方で利用することができます．GloVeのフィッティングでは`RcppParallel`による低レベルスレッドの並列処理を使用するのとは対照的に，これらの関数は`foreach`パッケージを通じて標準的なRの高レベル並列処理を使用します．これらの関数は柔軟であり，`doParallel`や`doRedis`等，いろいろな並列処理バックエンドを使うことができます．ただし，このような高レベルの並列処理は大きなオーバーヘッドをもたらす**可能性がある**ということをユーザは覚えておくべきです．

<!-- Only two things user should perform manually to take advantage of multicore machine:  -->
マルチコアのマシンを利用するのにユーザが手動で行うべき操作は2つだけです．

<!-- 1. register parallel backend -->
1. 並列処理のバックエンドを登録する．

<!-- 1. prepare splits of input data in a form of `list` of `itoken` iterators. -->
2. `itoken`イテレータのリストの形式で分割された入力データを用意する．

<!-- Here is simple example: -->
以下は簡単な例です[^win]．


```r
N_WORKERS <- 4
library(doParallel)
```

```
## Loading required package: foreach
```

```
## Loading required package: iterators
```

```
## Loading required package: parallel
```

```r
# 並列処理バックエンドを登録
registerDoParallel(N_WORKERS)

# 分割を用意する
# "jobs"はitokenイテレータのリスト！
N_SPLITS <- 4

jobs <- files %>% 
  split_into(N_SPLITS) %>% 
  lapply(ifiles, reader_function = reader) %>% 
  # 既に入力データを分割してあるので，chunks_numberは1にするのがよい
  lapply(itoken, chunks_number = 1, preprocess_function = tolower, 
         tokenizer = word_tokenizer, progessbar = FALSE)

# あるいは，データがメモリ上にある時は以下のようにして分割を実行することが可能
#
# review_chunks <- split_into(movie_review$review, N_SPLITS)
# review_ids <- split_into(movie_review$id, N_SPLITS)
#
# jobs <- Map(function(doc, ids) {
#  itoken(iterable = doc, ids = ids, preprocess_function = tolower, 
#         tokenizer = word_tokenizer, chunks_number = 1, progessbar = FALSE) 
# }, review_chunks, review_ids)

# 以降の関数呼び出しは全てマルチコアの恩恵を受ける
# それぞれのjobが別々のプロセスで評価される

# 語彙の作成
vocab <- create_vocabulary(jobs)

# 語彙でベクトル化したDTM
v_vectorizer <- vocab_vectorizer(vocab)
vocab_dtm_parallel <- create_dtm(jobs, vectorizer = v_vectorizer)

# 素性ハッシングでベクトル化したDTM
h_vectorizer <- hash_vectorizer()
hash_dtm_parallel <- create_dtm(jobs, vectorizer = h_vectorizer)

# 共起回数の統計
tcm_vectorizer <- vocab_vectorizer(vocab, grow_dtm = FALSE, skip_grams_window = 5)
tcm_parallel <- create_tcm(jobs, vectorizer = tcm_vectorizer)
```



----

[^translater_env]: 訳者の環境を示しておく．
    
    ```r
    devtools::session_info()
    ```
    
    ```
    ## Session info --------------------------------------------------------------
    ```
    
    ```
    ##  setting  value                       
    ##  version  R version 3.3.1 (2016-06-21)
    ##  system   x86_64, linux-gnu           
    ##  ui       X11                         
    ##  language (EN)                        
    ##  collate  ja_JP.UTF-8                 
    ##  tz       <NA>                        
    ##  date     2016-09-04
    ```
    
    ```
    ## Packages ------------------------------------------------------------------
    ```
    
    ```
    ##  package      * version date       source        
    ##  Matrix         1.2-7.1 2016-09-01 CRAN (R 3.3.1)
    ##  Rcpp           0.12.3  2016-01-10 CRAN (R 3.2.3)
    ##  RcppParallel   4.3.20  2016-08-16 CRAN (R 3.3.1)
    ##  chron          2.3-47  2015-06-24 CRAN (R 3.2.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.9   2016-01-08 CRAN (R 3.2.3)
    ##  doParallel   * 1.0.10  2015-10-14 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.3.1)
    ##  formatR        1.2.1   2015-09-18 CRAN (R 3.2.3)
    ##  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.2.1)
    ##  lda          * 1.4.2   2015-11-22 CRAN (R 3.3.1)
    ##  magrittr     * 1.5     2014-11-22 CRAN (R 3.3.1)
    ##  memoise        1.0.0   2016-01-29 CRAN (R 3.3.1)
    ##  rmarkdown      1.0     2016-07-08 CRAN (R 3.3.1)
    ##  stringi        1.0-1   2015-10-22 CRAN (R 3.2.2)
    ##  stringr        1.0.0   2015-04-30 CRAN (R 3.2.2)
    ##  text2vec     * 0.3.0   2016-03-31 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.2.3)
    ```

[^win]: `doParallel`の仕様により，以下のコードはWindowsでは動作しない．Windowsで動かすには，訳者は未検証だが[このあたり](http://stackoverflow.com/questions/34653567/doparallel-foreach-inconsistently-inherits-objects-from-parent-environment-e)が参考になるかもしれない．