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

87

这篇文章的另一个潜在标题可能是“当使用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 文件似乎并不是问题的一部分。


4
尽管我非常尊重“tm”,但我建议你做两件事:1)创建自己的预处理函数(学习一些正则表达式)2)转到另一个软件包——“text2vec”或“quanteda”——它们会更快,更容易。 - Dmitriy Selivanov
@DmitriySelivanov刚刚查看了quanteda的文档,实际上看起来非常有趣,我可能会尝试一下。如果我使用qunateda,如果它处理速度更快,那么可能意味着我不必使用并行处理,这似乎是一个doparallel问题,当与tm一起使用时。 - Doug Fir
1
@DougFir 也给 text2vec 一个机会吧(我是作者 :-) )。请在 http://text2vec.org 上查看教程。 - Dmitriy Selivanov
1
同意上面的评论,tm_map 很难调试,有许多文档不完善的特点,使用自己的自定义函数与另一个包的 apply 方法相比,短期和长期都可能更好。 - Gary Weissman
4
我投票关闭此问题,因为该问题已经超过3年没有答案,并且作者在评论中已经得到了明确的替代方案,显然不再对获得答案感兴趣。 - jamesc
显示剩余11条评论
1个回答

1
如果您尝试使用并行处理程序来覆盖内存,您应该首先确认其是否值得这样做。例如,检查您的磁盘是否以80%-100%的写入速度运行;如果是这种情况,那么您的程序也可以只使用单个核心,因为它无论如何都会受到磁盘写入速度的阻塞。
如果不是这种情况,我建议您使用调试器或将控制台/GUI输出添加到程序中,以验证所有内容按正确顺序执行。
如果这没有帮助,那么我建议您验证您没有弄乱程序(例如,一个箭头指向错误方向)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接