使用多核时 tm_map 转换函数的不一致行为

这篇文章的另一个潜在标题可能是“当在 R中并行处理时,核心数量、循环块大小和对象大小之间的比例是否重要?”

我有一个语料库,我正在使用 tm 包运行一些转换。因为语料库很大,所以我使用并行处理和双并行包。

有时候转换可以完成任务,但有时候不行。例如,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()中的核心数量。我只是不知道那是什么。

解决办法是什么?这个问题可以通过链接的样本语料库再现吗?我很担心,因为有时候我能重现错误,有时候我不能。改变数据块大小可以查看带有删除数字的错误,但并不总是如此。


更新

今天我继续我的会话,无法复制错误。我创建了一个 谷歌文档,并对语料库大小、核心数量和块大小的不同值进行了实验。在每个案例中,一切都是成功的。所以,我尝试运行完整的数据,一切工作。然而,出于理智考虑,我尝试在完整数据上再次运行,但失败了。现在,我又回到了昨天的状态。

它似乎已经运行了一个更大的数据集的功能已经改变了什么... 我不知道是什么!也许是某种会话变量?

因此,新的信息是这个错误只发生在在一个非常大的数据集上运行函数之后。重新开始我的治疗并没有解决问题,但是在离开几个小时后重新开始治疗确实解决了问题。


新信息:

在一个更大的语料库上重现这个问题可能更容易,因为这似乎触发了问题 corpus <- do.call(c, replicate(250, corpus, simplify = F))将根据我提供的示例创建一个500k 的文档语料库。第一次调用这个函数时,它可能会工作,但对我来说,第二次似乎就失败了。

这个问题很难解决,因为如果我能够重现这个问题,我就很可能能够识别并修复它。


新信息:

由于这个函数发生了许多事情,因此很难知道在调试工作中应该把重点放在哪里。我既考虑了使用多个临时 RDS 文件来节省内存的事实,也考虑了并行处理的事实。我编写了这个脚本的两个替代版本,一个仍然使用 RDS 文件并分解语料库,但是不进行并行处理(用% do% 替换% domar% ,并且删除 registerDoAverage 行) ,另一个使用并行处理,但是不使用 RDS 临时文件来分解小样本语料库。

我无法在脚本的单核版本中产生错误,只有在使用% domar% 的版本中,我才能重现这个问题(虽然这个问题是间歇性的,但是使用多巴并不总是会失败)。

因此,这个问题只有在使用 %dopar%时才会出现。我使用临时 RDS 文件的事实似乎并不是问题的一部分。

1178 次浏览

If you try to overwrite your memory with a program that uses parallel processing, you should first verify that it's worth it.

For instance, check if your disk is at 80%-100% writing speed; if that is the case, then your program could also just use a single core, because it is blocked by disk writing speed anyway.

If this is not the case, I recommend you to use the debugger or ad console/GUI outputs to your program to verify that everything gets executed in the right order.

If this does not help, then I recommend that you verify that you did not mess up the program (for example one arrow points in the wrong direction).