R
NLP
text2vec

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

この文書は Dmitriy Selivanov によるRパッケージtext2vec (version 0.3.0) のビネット "Advanced topics" の日本語訳です.
ただし文中の注は全て訳者によるものです1

License: MIT

関連文書


ファイルの取り扱い

この段落では,text2vecを使ってファイルに保存された巨大なテキストの集合をベクトル化する方法を示します.

ldaパッケージでトピックモデルを構築したいとしましょう.映画レビューの集まりを,複数のテキストファイルでディスクに保存して持っているとします.

このビネットのために,組み込みのmovie_reviewデータセットからそのようなファイルを作成します.

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はファイルを容易に扱うための関数群を提供します.

ユーザはほんのいくつかの作業を行う必要があるだけです.

  1. ifiles関数でファイルのイテレータを構築する.
  1. ifilesにファイル読込関数を与える.text2vecは背後にあるファイルについては何も知りません.プレーンテキストでも何らかのバイナリ形式でも構いません.
  1. itoken関数によってファイルからトークンのイテレータを構築する.

どうやるのか見てみましょう.

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)

これでDTMを(ldaパッケージで必要な)lda_c形式で構築することができます.

# イテレータは再初期化が必要!
# イテレータは変更可能(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]

DTMに文書IDが付いていることに注目してください.これらのIDはreader関数で割り当てた名前から引き継がれています.ファイルを扱うときに文書IDを割り当てる便利な方法です.

これでlda::lda.collapsed.gibbs.sampler()関数を使ってLDAモデルをフィットさせることができます.

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)

マルチコアを使った並列モード

create_dtm, create_tcm, create_vocabularyはマルチコアのマシンを透過的なやり方で利用することができます.GloVeのフィッティングではRcppParallelによる低レベルスレッドの並列処理を使用するのとは対照的に,これらの関数はforeachパッケージを通じて標準的なRの高レベル並列処理を使用します.これらの関数は柔軟であり,doParalleldoRedis等,いろいろな並列処理バックエンドを使うことができます.ただし,このような高レベルの並列処理は大きなオーバーヘッドをもたらす可能性があるということをユーザは覚えておくべきです.

マルチコアのマシンを利用するのにユーザが手動で行うべき操作は2つだけです.

  1. 並列処理のバックエンドを登録する.
  1. itokenイテレータのリストの形式で分割された入力データを用意する.

以下は簡単な例です2

N_WORKERS <- 4
library(doParallel)
## Loading required package: foreach
## Loading required package: iterators
## Loading required package: parallel
# 並列処理バックエンドを登録
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)


  1. 訳者の環境を示しておく. 

    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)
    
  2. doParallelの仕様により,以下のコードはWindowsでは動作しない.Windowsで動かすには,訳者は未検証だがこのあたりが参考になるかもしれない.