13

私のファイルには 4M を超える行があり、ベイジアン分類器に渡すことができるように、データをコーパスおよびドキュメント用語マトリックスに変換するより効率的な方法が必要です。

次のコードを検討してください。

library(tm)

GetCorpus <-function(textVector)
{
  doc.corpus <- Corpus(VectorSource(textVector))
  doc.corpus <- tm_map(doc.corpus, tolower)
  doc.corpus <- tm_map(doc.corpus, removeNumbers)
  doc.corpus <- tm_map(doc.corpus, removePunctuation)
  doc.corpus <- tm_map(doc.corpus, removeWords, stopwords("english"))
  doc.corpus <- tm_map(doc.corpus, stemDocument, "english")
  doc.corpus <- tm_map(doc.corpus, stripWhitespace)
  doc.corpus <- tm_map(doc.corpus, PlainTextDocument)
  return(doc.corpus)
}

data <- data.frame(
  c("Let the big dogs hunt","No holds barred","My child is an honor student"), stringsAsFactors = F)

corp <- GetCorpus(data[,1])

inspect(corp)

dtm <- DocumentTermMatrix(corp)

inspect(dtm)

出力:

> inspect(corp)
<<VCorpus (documents: 3, metadata (corpus/indexed): 0/0)>>

[[1]]
<<PlainTextDocument (metadata: 7)>>
let big dogs hunt

[[2]]
<<PlainTextDocument (metadata: 7)>>
 holds bar

[[3]]
<<PlainTextDocument (metadata: 7)>>
 child honor stud
> inspect(dtm)
<<DocumentTermMatrix (documents: 3, terms: 9)>>
Non-/sparse entries: 9/18
Sparsity           : 67%
Maximal term length: 5
Weighting          : term frequency (tf)

              Terms
Docs           bar big child dogs holds honor hunt let stud
  character(0)   0   1     0    1     0     0    1   1    0
  character(0)   1   0     0    0     1     0    0   0    0
  character(0)   0   0     1    0     0     1    0   0    1

私の質問は、コーパスと DTM をより速く作成するために何を使用できるかということです。30万行を超えると非常に遅くなるようです。

使えると聞いたことがありますdata.tableが、よくわかりません。

パッケージも見ましたが、qdapパッケージをロードしようとするとエラーが発生し、さらにそれが機能するかどうかもわかりません。

参考文献 http://cran.r-project.org/web/packages/qdap/qdap.pdf

4

4 に答える 4

16

どのアプローチ?

data.table間違いなく正しい方法です。正規表現操作は遅いですが、 の操作はstringiはるかに高速です (はるかに優れていることに加えて)。何でも

quanteda::dfm()私はquantedaパッケージを作成する際に問題を解決するために何度も繰り返しました (こちらのGitHub リポジトリを参照してください)。最速のソリューションは、data.tableandMatrixパッケージを使用してドキュメントとトークン化された機能をインデックス化し、ドキュメント内の機能を数え、結果をスパース マトリックスに直接挿入することです。

以下のコードでは、quanteda パッケージで見つかったテキストの例を取り上げました。これは、CRAN からインストールすることも、開発バージョンを からインストールすることもできます。

devtools::install_github("kbenoit/quanteda")

4m のドキュメントでどのように機能するか非常に興味があります。そのサイズのコーパスを扱った私の経験に基づいて、それはかなりうまく機能します(十分なメモリがあれば)。

すべてのプロファイリングで、C++ で記述されているため、どのような種類の並列化によっても data.table 操作の速度を向上させることができなかったことに注意してください。

quantedadfm()関数のコア

これは、誰かがdata.tableそれを改善したい場合に備えて、ベースのソースコードの骨組みです。トークン化されたテキストを表す文字ベクトルのリストを入力として取ります。quanteda パッケージでは、フル機能dfm()がドキュメントの文字ベクトルまたはコーパス オブジェクトに直接作用し、デフォルトで小文字化、数字の削除、スペースの削除を実装します (ただし、必要に応じてこれらはすべて変更できます)。

require(data.table)
require(Matrix)

dfm_quanteda <- function(x) {
    docIndex <- 1:length(x)
    if (is.null(names(x))) 
        names(docIndex) <- factor(paste("text", 1:length(x), sep="")) else
            names(docIndex) <- names(x)

    alltokens <- data.table(docIndex = rep(docIndex, sapply(x, length)),
                            features = unlist(x, use.names = FALSE))
    alltokens <- alltokens[features != ""]  # if there are any "blank" features
    alltokens[, "n":=1L]
    alltokens <- alltokens[, by=list(docIndex,features), sum(n)]

    uniqueFeatures <- unique(alltokens$features)
    uniqueFeatures <- sort(uniqueFeatures)

    featureTable <- data.table(featureIndex = 1:length(uniqueFeatures),
                               features = uniqueFeatures)
    setkey(alltokens, features)
    setkey(featureTable, features)

    alltokens <- alltokens[featureTable, allow.cartesian = TRUE]
    alltokens[is.na(docIndex), c("docIndex", "V1") := list(1, 0)]

    sparseMatrix(i = alltokens$docIndex, 
                 j = alltokens$featureIndex, 
                 x = alltokens$V1, 
                 dimnames=list(docs=names(docIndex), features=uniqueFeatures))
}

require(quanteda)
str(inaugTexts)
## Named chr [1:57] "Fellow-Citizens of the Senate and of the House of Representatives:\n\nAmong the vicissitudes incident to life no event could ha"| __truncated__ ...
## - attr(*, "names")= chr [1:57] "1789-Washington" "1793-Washington" "1797-Adams" "1801-Jefferson" ...
tokenizedTexts <- tokenize(toLower(inaugTexts), removePunct = TRUE, removeNumbers = TRUE)
system.time(dfm_quanteda(tokenizedTexts))
##  user  system elapsed 
## 0.060   0.005   0.064 

もちろん、これはほんの一部ですが、完全なソース コードは GitHub リポジトリ ( dfm-main.R) で簡単に見つけることができます。

あなたの例のquanteda

簡単にするためにこれはどうですか?

require(quanteda)
mytext <- c("Let the big dogs hunt",
            "No holds barred",
            "My child is an honor student")
dfm(mytext, ignoredFeatures = stopwords("english"), stem = TRUE)
# Creating a dfm from a character vector ...
# ... lowercasing
# ... tokenizing
# ... indexing 3 documents
# ... shaping tokens into data.table, found 14 total tokens
# ... stemming the tokens (english)
# ... ignoring 174 feature types, discarding 5 total features (35.7%)
# ... summing tokens by document
# ... indexing 9 feature types
# ... building sparse matrix
# ... created a 3 x 9 sparse dfm
# ... complete. Elapsed time: 0.023 seconds.

# Document-feature matrix of: 3 documents, 9 features.
# 3 x 9 sparse Matrix of class "dfmSparse"
# features
# docs    bar big child dog hold honor hunt let student
# text1   0   1     0   1    0     0    1   1       0
# text2   1   0     0   0    1     0    0   0       0
# text3   0   0     1   0    0     1    0   0       1
于 2015-07-09T05:42:42.387 に答える
12

より正規表現に焦点を当てたソリューションを検討したいと思うかもしれません。これらは、私が開発者として取り組んでいる問題/考えの一部です。私は現在stringi、開発のためにパッケージを重点的に検討しています。これは、文字列操作が非常に高速な一貫した名前の関数がいくつかあるためです。

この応答では、より便利な方法tmが提供するよりも高速であることがわかっているツールを使用しようとしています (そして確かに よりもはるかに高速ですqdap)。ここでは、並列処理や data.table/dplyr についても調べていません。代わりに、文字列操作とstringiマトリックス内のデータの保持、およびその形式を処理するための特定のパッケージでの操作に焦点を当てています。私はあなたの例を取り、それを100000倍にします。ステミングを使用しても、私のマシンでは 17 秒かかります。

data <- data.frame(
    text=c("Let the big dogs hunt",
        "No holds barred",
        "My child is an honor student"
    ), stringsAsFactors = F)

## eliminate this step to work as a MWE
data <- data[rep(1:nrow(data), 100000), , drop=FALSE]

library(stringi)
library(SnowballC)
out <- stri_extract_all_words(stri_trans_tolower(SnowballC::wordStem(data[[1]], "english"))) #in old package versions it was named 'stri_extract_words'
names(out) <- paste0("doc", 1:length(out))

lev <- sort(unique(unlist(out)))
dat <- do.call(cbind, lapply(out, function(x, lev) {
    tabulate(factor(x, levels = lev, ordered = TRUE), nbins = length(lev))
}, lev = lev))
rownames(dat) <- sort(lev)

library(tm)
dat <- dat[!rownames(dat) %in% tm::stopwords("english"), ] 

library(slam)
dat2 <- slam::as.simple_triplet_matrix(dat)

tdm <- tm::as.TermDocumentMatrix(dat2, weighting=weightTf)
tdm

## or...
dtm <- tm::as.DocumentTermMatrix(dat2, weighting=weightTf)
dtm
于 2014-08-15T20:37:02.443 に答える
2

いくつかの選択肢があります。@TylerRinker は についてコメントしましたがqdap、これは確かに進むべきです。

代わりに (または追加で)、健全な並列処理の恩恵を受けることもできます。R の HPC リソースについて詳しく説明している CRAN ページがあります。少し古くなっていますが、multicoreパッケージの機能は現在 .xml に含まれていますparallel

applyパッケージのマルチコア関数またはクラスター コンピューティングを使用して、テキスト マイニングをスケールアップできます(このパッケージでも および でサポートparallelされています)。snowfallbiopara

別の方法は、MapReduceアプローチを採用することです。tm結合とMapReduceビッグデータに関する優れたプレゼンテーションは、こちらから入手できます。そのプレゼンテーションは数年前のものですが、すべての情報は現在も有効で関連性があります。同じ著者が、プラグインに焦点を当てたトピックに関する新しい学術記事を作成しています。強制を使用するtm.plugin.dc代わりにベクター ソースを使用することを回避するには、次のようにします。DirSource

data("crude")
as.DistributedCorpus(crude)

これらの解決策のどれもあなたの好みに合わない場合、または単に冒険したいだけの場合は、GPU が問題にどれだけうまく対処できるかを確認することもできます。CPU に比べて GPU のパフォーマンスには多くのばらつきがあり、これは使用例になる可能性があります。試してみたい場合は、gputoolsまたは CRAN HPC タスク ビューに記載されている他の GPU パッケージを使用できます。

例:

library(tm)
install.packages("tm.plugin.dc")
library(tm.plugin.dc)

GetDCorpus <-function(textVector)
{
  doc.corpus <- as.DistributedCorpus(VCorpus(VectorSource(textVector)))
  doc.corpus <- tm_map(doc.corpus, content_transformer(tolower))
  doc.corpus <- tm_map(doc.corpus, content_transformer(removeNumbers))
  doc.corpus <- tm_map(doc.corpus, content_transformer(removePunctuation))
  # <- tm_map(doc.corpus, removeWords, stopwords("english")) # won't accept this for some reason...
  return(doc.corpus)
}

data <- data.frame(
  c("Let the big dogs hunt","No holds barred","My child is an honor student"), stringsAsFactors = F)

dcorp <- GetDCorpus(data[,1])

tdm <- TermDocumentMatrix(dcorp)

inspect(tdm)

出力:

> inspect(tdm)
<<TermDocumentMatrix (terms: 10, documents: 3)>>
Non-/sparse entries: 10/20
Sparsity           : 67%
Maximal term length: 7
Weighting          : term frequency (tf)

         Docs
Terms     1 2 3
  barred  0 1 0
  big     1 0 0
  child   0 0 1
  dogs    1 0 0
  holds   0 1 0
  honor   0 0 1
  hunt    1 0 0
  let     1 0 0
  student 0 0 1
  the     1 0 0
于 2014-08-15T17:40:19.897 に答える