doparallel 在一个循环中嵌套一个循环可行但逻辑上没有意义?
doparallel nesting a loop in a loop works but logically doesn't make sense?
我有一个大型语料库,我正在使用 tm::tm_map()
进行转换。因为我使用的是托管 R Studio,所以我有 15 个内核,想利用并行处理来加快速度。
如果不共享一个非常大的语料库,我根本无法用虚拟数据进行重现。
我的代码如下。问题的简短描述是,在控制台中手动循环片段是有效的,但在我的函数中这样做却不行。
函数 "clean_corpus" 将语料库作为输入,将其分解成多个部分并保存到临时文件以帮助解决 ram 问题。然后函数使用 %dopar
% 块迭代每个片段。该函数在对语料库的一小部分进行测试时起作用,例如10k 文档。但是在更大的语料库上,函数是 returning NULL。为了调试,我将函数设置为 return 已经循环的各个部分,而不是作为一个整体重建的语料库。我发现在较小的语料库样本上,代码会 return 一个所有迷你语料库的列表,正如预期的那样,但是当我在较大的语料库样本上测试时,该函数会 return 一些 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 个块中执行此操作,例如50k 通过 5 次迭代一切正常。如果我 运行 例如上的功能完整的 50k 文档 returns NULL。
所以,也许我只需要通过更多地分解我的语料库来遍历更小的片段。我试过了。在下面的clean_corpus 函数中,参数n 是每个片段的长度。该函数仍然 return 为 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)
Returns 空。
This 所以 post 和唯一答案中链接的那个表明它可能与我在 linux 上的 RStudio 托管实例有关,其中 linux "out of memory killer 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
注意我尝试使用库 tm 功能进行并行处理(参见 ),但我一直遇到 "cannot allocate memory" 错误,这就是为什么我尝试使用 [=18] "on my own" =].
来自评论的解决方案总结
您的内存问题可能与 corpus <- do.call(function(...) c(..., recursive = TRUE), pieces)
有关,因为它仍然将您的所有(输出)数据存储在内存中
我建议将每个工作人员的输出导出到一个文件,例如 RDS
或 csv
文件,而不是最后将其收集到一个数据结构中
另一个问题(如您所指出的)是 foreach
将使用隐含的 return
语句保存每个工人的输出({}
中的代码块在 dopar
被视为函数)。我建议在结束 }
之前添加一个明确的 return(1)
以不将预期的输出保存到内存中(您已经明确地将其保存为文件)。
我有一个大型语料库,我正在使用 tm::tm_map()
进行转换。因为我使用的是托管 R Studio,所以我有 15 个内核,想利用并行处理来加快速度。
如果不共享一个非常大的语料库,我根本无法用虚拟数据进行重现。
我的代码如下。问题的简短描述是,在控制台中手动循环片段是有效的,但在我的函数中这样做却不行。
函数 "clean_corpus" 将语料库作为输入,将其分解成多个部分并保存到临时文件以帮助解决 ram 问题。然后函数使用 %dopar
% 块迭代每个片段。该函数在对语料库的一小部分进行测试时起作用,例如10k 文档。但是在更大的语料库上,函数是 returning NULL。为了调试,我将函数设置为 return 已经循环的各个部分,而不是作为一个整体重建的语料库。我发现在较小的语料库样本上,代码会 return 一个所有迷你语料库的列表,正如预期的那样,但是当我在较大的语料库样本上测试时,该函数会 return 一些 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 个块中执行此操作,例如50k 通过 5 次迭代一切正常。如果我 运行 例如上的功能完整的 50k 文档 returns NULL。
所以,也许我只需要通过更多地分解我的语料库来遍历更小的片段。我试过了。在下面的clean_corpus 函数中,参数n 是每个片段的长度。该函数仍然 return 为 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)
Returns 空。
This 所以 post 和唯一答案中链接的那个表明它可能与我在 linux 上的 RStudio 托管实例有关,其中 linux "out of memory killer 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
注意我尝试使用库 tm 功能进行并行处理(参见
来自评论的解决方案总结
您的内存问题可能与 corpus <- do.call(function(...) c(..., recursive = TRUE), pieces)
有关,因为它仍然将您的所有(输出)数据存储在内存中
我建议将每个工作人员的输出导出到一个文件,例如 RDS
或 csv
文件,而不是最后将其收集到一个数据结构中
另一个问题(如您所指出的)是 foreach
将使用隐含的 return
语句保存每个工人的输出({}
中的代码块在 dopar
被视为函数)。我建议在结束 }
之前添加一个明确的 return(1)
以不将预期的输出保存到内存中(您已经明确地将其保存为文件)。