这篇文章的另一个潜在标题可能是“当使用R进行并行处理时,核心数量、循环块大小和对象大小的比例是否重要?"
我正在使用tm包对一个语料库进行一些转换。由于语料库很大,我正在使用doparallel包进行并行处理。
有时候转换可以完成任务,但有时候却不能。例如,tm::removeNumbers()
。语料库中的第一个文档内容为“n417”。因此,如果预处理成功,那么该文档将被转换为只有“n”。
下面是一个示例语料库以供复制。以下是代码块:
library(tidyverse)
library(qdap)
library(stringr)
library(tm)
library(textstem)
library(stringi)
library(foreach)
library(doParallel)
library(SnowballC)
corpus <- (see below)
n <- 100 # This is the size of each chunk in the loop
# 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 = 12)
pieces <- foreach(i = seq_len(lenp)) %dopar% {
piece <- readRDS(paste0(tmpfile, i, ".rds"))
# Regular transformations
piece <- tm_map(piece, content_transformer(removePunctuation), preserve_intra_word_dashes = T)
piece <- tm_map(piece, content_transformer(function(x, ...)
qdap::rm_stopwords(x, stopwords = tm::stopwords("english"), separate = F)))
piece <- tm_map(piece, removeNumbers)
saveRDS(piece, paste0(tmpfile, i, ".rds"))
return(1) # Hack to get dopar to forget the piece to save memory since now saved to rds
}
stopImplicitCluster()
# Combine the pieces back into one corpus
corpus <- list()
corpus <- foreach(i = seq_len(lenp)) %do% {
corpus[[i]] <- readRDS(paste0(tmpfile, i, ".rds"))
}
corpus_done <- do.call(function(...) c(..., recursive = TRUE), corpus)
这里是样本数据的链接(点击跳转)。我需要复制粘贴2k个文档的足够大的样本,但是这里不允许我粘贴这么多,所以请查看链接的文档获取数据。
corpus <- VCorpus(VectorSource([paste the chr vector from link above]))
如果我按照上面的方式运行我的代码块,并将n设置为200,然后查看结果。
我可以看到数字仍然存在,而它们应该被 tm::removeNumbers()
移除:
> lapply(1:10, function(i) print(corpus_done[[i]]$content)) %>% unlist
[1] "n417"
[1] "disturbance"
[1] "grand theft auto"
然而,如果我将块大小(“n”变量的值)更改为100:
> lapply(1:10, function(i) print(corpus_done[[i]]$content)) %>% unlist
[1] "n"
[1] "disturbance"
[1] "grand theft auto"
数字已被删除。
但是这是不一致的。我试图通过在150、125上测试来缩小范围...发现在120和125个块大小之间它会/不会工作。然后在120:125之间迭代函数后,它有时会工作,有时又不能为相同的块大小。
我认为也许存在三个变量之间的关系:语料库的大小、块大小和
registerdoparallel()
中核心的数量。我只是不知道是什么关系。解决方案是什么?可以使用链接的示例语料库重现此问题吗?我很担心,因为有时我可以复制错误,有时则不能。更改块大小可以看到删除数字的错误,但并非总是如此。
更新
今天我恢复了我的会话,无法复制该错误。我创建了一个Google文档,并尝试使用不同的语料库大小、核心数和块大小进行实验。在每种情况下,一切都是成功的。所以,我尝试在完整数据上运行,一切都正常。然而,出于我的理智考虑,我尝试再次在完整数据上运行,但失败了。现在,我又回到了昨天的状态。
看起来似乎运行函数在更大的数据集上已经改变了一些东西...我不知道是什么!也许是某种会话变量?
因此,新信息是这个错误只发生在在非常大的数据集上运行函数之后。重新启动我的会话并没有解决问题,但是几个小时后恢复会话后却可以。
新信息:
在更大的语料库上可能更容易重现该问题,因为这似乎是触发该问题的原因
corpus <- do.call(c, replicate(250, corpus, simplify = F))
将基于我提供的示例创建一个50万个文档的语料库。该函数第一次调用时可能会工作,但对我来说,第二次似乎会失败。这个问题很难,因为如果我能够复制这个问题,我可能就能够确定和修复它。
新的信息:
由于这个函数有几个事情正在发生,所以很难知道在调试方面应该关注哪些内容。我同时关注使用多个临时RDS文件保存内存和使用并行处理两个方面。我编写了脚本的两个替代版本,一个仍然使用rds文件并分割语料库,但不进行并行处理(用%do%替换了%dopar%并删除了registerDoParallel行),另一个使用并行处理,但不使用RDS临时文件来分割小样本语料库。
我无法使用单核版本的脚本来重现错误,只有使用 %dopar% 的版本才能重新创建问题(尽管该问题是间歇性的,不使用 dopar 并不总是失败)。
因此,在使用 %dopar% 时才会出现此问题。使用临时 RDS 文件似乎并不是问题的一部分。
text2vec
一个机会吧(我是作者 :-) )。请在 http://text2vec.org 上查看教程。 - Dmitriy Selivanovtm_map
很难调试,有许多文档不完善的特点,使用自己的自定义函数与另一个包的apply
方法相比,短期和长期都可能更好。 - Gary Weissman