【发布时间】:2018-01-31 20:11:17
【问题描述】:
我有一个大型语料库,我正在使用 tm::tm_map() 进行转换。由于我使用的是托管 R Studio,因此我有 15 个内核,并希望利用并行处理来加快速度。
如果不共享一个非常大的语料库,我根本无法使用虚拟数据进行复制。
我的代码如下。问题的简短描述是,在控制台中手动循环片段是可行的,但在我的函数中这样做却不行。
函数“clean_corpus”将语料库作为输入,将其分解为多个片段并保存到临时文件以帮助解决 ram 问题。然后该函数使用%dopar% 块遍历每个部分。该功能在对语料库的一小部分进行测试时起作用,例如10k 文件。但在更大的语料库中,该函数返回 NULL。为了调试,我将函数设置为返回已循环的各个部分,而不是整个重建的语料库。我发现在较小的语料库样本上,代码会按预期返回所有迷你语料库的列表,但是当我在较大的语料库样本上测试时,该函数会返回一些 NULL。
这就是为什么这让我感到困惑:
cleaned.corpus <- clean_corpus(corpus.regular[1:10000], n = 1000) # works
cleaned.corpus <- clean_corpus(corpus.regular[10001:20000], n = 1000) # also works
cleaned.corpus <- clean_corpus(corpus.regular[1:50000], n = 1000) # NULL
如果我在 10k 块中执行此操作,例如通过 5 次迭代 50k 一切正常。如果我在例如运行该功能完整的 50k 个文档返回 NULL。
所以,也许我只需要通过更多地分解我的语料库来循环更小的片段。我试过这个。在下面的 clean_corpus 函数中,参数 n 是每个片段的长度。该函数仍然返回 NULL。
所以,如果我这样迭代:
# iterate over 10k docs in 10 chunks of one thousand at a time
cleaned.corpus <- clean_corpus(corpus.regular[1:10000], n = 1000)
如果我手动执行 5 次,最高 50K 一切正常。我的函数在一次调用中执行此操作的等价物是:
# iterate over 50K docs in 50 chunks of one thousand at a time
cleaned.corpus <- clean_corpus(corpus.regular[1:50000], n = 1000)
返回 NULL。
This SO 帖子和唯一答案中链接的帖子暗示这可能与我在 linux 上托管的 RStudio 实例有关,其中 linux“内存不足杀手 oom”可能正在停止工作人员。这就是为什么我尝试将我的语料库分解成碎片以解决内存问题。
关于为什么在 10 个 1k 的块中迭代超过 10k 个文档有效,而 50 个 1k 的块不可行的任何理论或建议?
这里是 clean_corpus 函数:
clean_corpus <- function(corpus, n = 500000) { # n is length of each peice in parallel processing
# split the corpus into pieces for looping to get around memory issues with transformation
nr <- length(corpus)
pieces <- split(corpus, rep(1:ceiling(nr/n), each=n, length.out=nr))
lenp <- length(pieces)
rm(corpus) # save memory
# save pieces to rds files since not enough RAM
tmpfile <- tempfile()
for (i in seq_len(lenp)) {
saveRDS(pieces[[i]],
paste0(tmpfile, i, ".rds"))
}
rm(pieces) # save memory
# doparallel
registerDoParallel(cores = 14) # I've experimented with 2:14 cores
pieces <- foreach(i = seq_len(lenp)) %dopar% {
piece <- readRDS(paste0(tmpfile, i, ".rds"))
# transformations
piece <- tm_map(piece, content_transformer(replace_abbreviation))
piece <- tm_map(piece, content_transformer(removeNumbers))
piece <- tm_map(piece, content_transformer(function(x, ...)
qdap::rm_stopwords(x, stopwords = tm::stopwords("en"), separate = F, strip = T, char.keep = c("-", ":", "/"))))
}
# combine the pieces back into one corpus
corpus <- do.call(function(...) c(..., recursive = TRUE), pieces)
return(corpus)
} # end clean_corpus function
上面的代码块再次只是为了在键入函数后的可读性:
# iterate over 10k docs in 10 chunks of one thousand at a time
cleaned.corpus <- clean_corpus(corpus.regular[1:10000], n = 1000) # works
# iterate over 50K docs in 50 chunks of one thousand at a time
cleaned.corpus <- clean_corpus(corpus.regular[1:50000], n = 1000) # does not work
但是通过调用每个函数在控制台中迭代
corpus.regular[1:10000], corpus.regular[10001:20000], corpus.regular[20001:30000], corpus.regular[30001:40000], corpus.regular[40001:50000] # does work on each run
请注意,我尝试使用 library tm 功能进行并行处理(请参阅 here),但我一直遇到“无法分配内存”错误,这就是为什么我尝试使用 doparallel %dopar%“自行”完成它。
【问题讨论】:
-
您好,感谢您的评论。我知道这是一个内存问题.. 但这正是我去循环路线的原因。循环不是通过分块计算而不是整体计算来帮助缓解这种情况吗?
-
另外,我确实看到他的脚本在 1 + core via shell > top > 1 下运行。在每种情况下,似乎都丢失了可用的内存。
-
啊,我从来没有考虑过。事情是我能够将整个结构加载到 R 中。50k 样本对于完整的 10M 文档语料库来说很小,所以即使是块也不应该导致内存问题。我想知道我是否应该像在函数顶部附近那样尝试将所有部分保存到临时文件中
-
您好,您可以扩展这部分“.packages="tm"吗?是的,我也可以保存 RDS OK。
-
哦,我明白了。我对 r 中的并行处理非常陌生,但我认为在使用 doparallel 包时,任何对象都会自动导出给工作人员,而不是使用例如并行::parLapply。但我真的不确定。无论如何我可以问一下。是否可以在 dopar 块的末尾将每个部分保存到 RDS,然后在调用函数后将它们全部读取?
标签: r parallel-processing tm doparallel