Подтвердить что ты не робот

Более эффективные средства создания корпуса и DTM с 4-мя рядами

Мой файл имеет более 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 быстрее? Это кажется очень медленным, если я использую более 300 тыс. Строк.

Я слышал, что могу использовать data.table, но я не уверен, как это сделать.

Я также посмотрел на пакет qdap, но при попытке загрузить пакет мне пришла ошибка, плюс я даже не знаю, будет ли это работать.

Ref. http://cran.r-project.org/web/packages/qdap/qdap.pdf

4b9b3361

Ответ 1

Я думаю, вы можете захотеть рассмотреть более ориентированное на регулярное выражение решение. Это некоторые из проблем/мышления, с которыми я борюсь как разработчик. В настоящее время я рассматриваю пакет stringi для разработки, так как он имеет некоторые последовательно названные функции, которые быстро становятся злодейскими для манипулирования строками.

В этом ответе я пытаюсь использовать любой инструмент, который я знаю об этом быстрее, чем более удобные методы tm могут дать нам (и, конечно, намного быстрее, чем qdap). Здесь я даже не изучал параллельную обработку или data.table/dplyr и вместо этого сосредоточился на обработке строк с помощью stringi и сохранял данные в матрице и манипулировал определенными пакетами, предназначенными для обработки этого формата. Я беру ваш пример и умножаю его на 100000x. Даже при остановке, это занимает 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

Ответ 2

Какой подход?

data.table определенно правильный путь. Операции с регулярными выражениями медленны, хотя те, что в stringi, намного быстрее (в дополнение к тому, чтобы быть намного лучше). Что-нибудь с

Я рассмотрел множество итераций решения проблемы при создании quanteda::dfm() для моего пакета quanteda (см. GitHub repo здесь). Самое быстрое решение, безусловно, предполагает использование пакетов data.table и Matrix для индексации документов и токенизированных функций, подсчета функций внутри документов и включения результата прямо в разреженную матрицу.

В приведенном ниже коде я взял для примера текст, найденный с пакетом quanteda, который вы можете (и должен!) установить из CRAN или версии разработки из

devtools::install_github("kbenoit/quanteda")

Мне было бы очень интересно посмотреть, как это работает с вашими 4-м документами. Основываясь на моем опыте работы с corpus такого размера, он будет работать очень хорошо (если у вас достаточно памяти).

Обратите внимание, что во всех моих профилированиях я не мог улучшить скорость операций data.table с помощью какой-либо параллелизации из-за того, как они написаны на С++.

Ядро функции квантования dfm()

Вот основные кости исходного кода data.table, если кто-то хочет улучшить его. Он вводит список символьных векторов, представляющих токенизированные тексты. Полнофункциональный dfm() в пакете quanteda работает непосредственно на символьных векторах документов или корпусных объектов и реализует нижнюю шкалу, удаление номеров и удаление интервала по умолчанию (но все они могут быть изменены по желанию).

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

Ответ 3

У вас есть несколько вариантов. @TylerRinker прокомментировал qdap, что, безусловно, путь.

В качестве альтернативы (или дополнительно) вы также можете воспользоваться здоровым способом parallelism. Там хорошая страница CRAN, в которой подробно описаны ресурсы HPC в R. Это немного устарело, и функциональность пакета multicore теперь содержится в parallel.

Вы можете масштабировать интеллектуальный анализ текста с помощью функций multicore apply пакета parallel или с помощью кластерных вычислений (также поддерживаемых этот пакет, а также snowfall и biopara).

Еще один способ - использовать подход MapReduce. Хорошая презентация по объединению tm и MapReduce для больших данных доступна здесь. Хотя этой презентации несколько лет, вся информация по-прежнему актуальна, актуальна и актуальна. Те же авторы новую академическую статью по теме, в которой основное внимание уделяется плагину tm.plugin.dc. Чтобы обойти, имея векторный источник вместо DirSource, вы можете использовать принуждение:

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

Если ни одно из этих решений не соответствует вашему вкусу, или если вы просто чувствуете себя авантюрно, вы также можете увидеть, насколько ваш GPU сможет решить проблему. Там много различий в том, насколько хорошо графические процессоры работают относительно процессоров, и это может быть прецедентом. Если вы хотите попробовать, вы можете использовать gputools или другие пакеты GPU, упомянутые в представлении задач CRAN HPC.

Пример:

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