私のファイルには400万を超える行があり、ベイズ分類器に渡すことができるように、データをコーパスとドキュメントの用語マトリックスに変換するより効率的な方法が必要です。
次のコードについて考えてみます。
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
パッケージも確認しましたが、パッケージを読み込もうとするとエラーが発生し、動作するかどうかさえわかりません。
もっと正規表現に焦点を当てた解決策を検討したいかもしれません。これらは私が開発者として取り組んでいる問題/考えのいくつかです。私は現在、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
_data.table
_は間違いなく正しい方法です。正規表現の操作は遅いですが、stringi
の操作ははるかに高速です(はるかに優れていることに加えて)。と何でも
quantedaパッケージのquanteda::dfm()
を作成する際に、問題を解決するために何度も繰り返しました( GitHubリポジトリはこちら)を参照してください )。最速の解決策は、_data.table
_およびMatrix
パッケージを使用してドキュメントとトークン化された機能にインデックスを付け、ドキュメント内の機能をカウントし、結果をスパース行列に直接プラグインすることです。
以下のコードでは、quantedaパッケージで見つかったテキストの例を取り上げました。これは、CRANまたは開発バージョンからインストールできます(インストールする必要があります)。
_devtools::install_github("kbenoit/quanteda")
_
4mのドキュメントでどのように機能するかを知りたいと思います。そのサイズのコーパスでの私の経験に基づくと、それはかなりうまく機能します(十分なメモリがある場合)。
すべてのプロファイリングで、C++での記述方法が原因で、あらゆる種類の並列化によってdata.table操作の速度を向上させることができなかったことに注意してください。
dfm()
関数のコア_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
_)で簡単に見つけることができます。
簡単にするためにこれはどうですか?
_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
_
いくつかの選択肢があります。 @TylerRinkerはqdap
についてコメントしました。これは確かに 方法 行くべきです。
あるいは(またはさらに)、並列処理の健全な実行から利益を得ることができます。 RのHPCリソースの詳細を示すNiceCRANページがあります。少し古くなっており、multicore
パッケージの機能がparallel
に含まれるようになりました。
apply
パッケージのマルチコアparallel
関数を使用するか、クラスターコンピューティングを使用してテキストマイニングをスケールアップできます( そのパッケージ 、およびsnowfall
とbiopara
でもサポートされます)。
もう1つの方法は、MapReduce
アプローチを採用することです。ビッグデータのtm
とMapReduce
の組み合わせに関する優れたプレゼンテーションが利用可能です ここ 。そのプレゼンテーションは数年前のものですが、すべての情報は最新で、有効で、関連性があります。同じ著者が 新しい学術記事 トピックについて持っており、tm.plugin.dc
プラグインに焦点を当てています。 DirSource
の代わりにVectorSourceを使用することを回避するには、強制を使用できます。
data("crude")
as.DistributedCorpus(crude)
これらのソリューションのいずれもあなたの好みに合わない場合、または単に冒険心を感じている場合は、GPUが問題にどれだけうまく対処できるかもわかります。 GPUのパフォーマンスはCPUと比較してさまざまであり、これはユースケースになる可能性があります。試してみたい場合は、 gputools
またはCRANHPCタスクビューに記載されている他の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
これは私の以前の答えよりも優れています。
quantedaパッケージは大幅に進化し、この種の問題に組み込まれたツールを使用すると、より高速で簡単に使用できるようになりました。私たちはそれを設計しました。 OPの一部は、ベイズ分類器のテキストを準備する方法を尋ねました。 quantedaのtextmodel_nb()
は、汗をかくことなく300kのドキュメントを処理するため、この例も追加しました。多項NBモデル(テキストカウントマトリックスに最も適しています。 https://stackoverflow.com/a/54431055/4158274 も参照)を正しく実装します。 )。
ここでは、組み込みの最初のコーパスオブジェクトについて説明しますが、以下の関数は、単純な文字ベクトル入力でも機能します。私はこれと同じワークフローを使用して、ラップトップでモデルを処理し、数千万のツイートに数分で適合させたので、高速です。
_library("quanteda", warn.conflicts = FALSE)
## Package version: 1.4.1
## Parallel computing: 2 of 12 threads used.
## See https://quanteda.io for tutorials and examples.
# use a built-in data object
data <- data_corpus_inaugural
data
## Corpus consisting of 58 documents and 3 docvars.
# here we input a corpus, but plain text input works fine too
dtm <- dfm(data, tolower = TRUE, remove_numbers = TRUE, remove_punct = TRUE) %>%
dfm_wordstem(language = "english") %>%
dfm_remove(stopwords("english"))
dtm
## Document-feature matrix of: 58 documents, 5,346 features (89.0% sparse).
tail(dtm, nf = 5)
## Document-feature matrix of: 6 documents, 5 features (83.3% sparse).
## 6 x 5 sparse Matrix of class "dfm"
## features
## docs bleed urban sprawl windswept nebraska
## 1997-Clinton 0 0 0 0 0
## 2001-Bush 0 0 0 0 0
## 2005-Bush 0 0 0 0 0
## 2009-Obama 0 0 0 0 0
## 2013-Obama 0 0 0 0 0
## 2017-Trump 1 1 1 1 1
_
これはかなり些細な例ですが、説明のために、トランプのドキュメントを差し出して、ナイーブベイズモデルを適合させましょう。これは、この投稿の時点での最後の就任演説( "2017-Trump")であり、ndoc()
番目の文書と同じ位置にあります。
_# fit a Bayesian classifier
postwar <- ifelse(docvars(data, "Year") > 1945, "post-war", "pre-war")
textmod <- textmodel_nb(dtm[-ndoc(dtm), ], y = postwar[-ndoc(dtm)], prior = "docfreq")
_
他の適合モデルオブジェクト(たとえば、lm()
、glm()
など)で機能する同じ種類のコマンドは、適合したNaiveBayesテキストモデルオブジェクトで機能します。そう:
_summary(textmod)
##
## Call:
## textmodel_nb.dfm(x = dtm[-ndoc(dtm), ], y = postwar[-ndoc(dtm)],
## prior = "docfreq")
##
## Class Priors:
## (showing first 2 elements)
## post-war pre-war
## 0.2982 0.7018
##
## Estimated Feature Scores:
## fellow-citizen senat hous repres among vicissitud incid
## post-war 0.02495 0.4701 0.2965 0.06968 0.213 0.1276 0.08514
## pre-war 0.97505 0.5299 0.7035 0.93032 0.787 0.8724 0.91486
## life event fill greater anxieti notif transmit order
## post-war 0.3941 0.1587 0.3945 0.3625 0.1201 0.3385 0.1021 0.1864
## pre-war 0.6059 0.8413 0.6055 0.6375 0.8799 0.6615 0.8979 0.8136
## receiv 14th day present month one hand summon countri
## post-war 0.1317 0.3385 0.5107 0.06946 0.4603 0.3242 0.307 0.6524 0.1891
## pre-war 0.8683 0.6615 0.4893 0.93054 0.5397 0.6758 0.693 0.3476 0.8109
## whose voic can never hear vener
## post-war 0.2097 0.482 0.3464 0.2767 0.6418 0.1021
## pre-war 0.7903 0.518 0.6536 0.7233 0.3582 0.8979
predict(textmod, newdata = dtm[ndoc(dtm), ])
## 2017-Trump
## post-war
## Levels: post-war pre-war
predict(textmod, newdata = dtm[ndoc(dtm), ], type = "probability")
## post-war pre-war
## 2017-Trump 1 1.828083e-157
_