Непоследовательное поведение с функциями преобразования tm_map при использовании нескольких ядер
Еще один потенциальный заголовок для этого поста: "При параллельной обработке в 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)
А вот ссылка на образец данных. Мне нужно вставить достаточно большую выборку из 2 тыс. Документов, чтобы воссоздать, и SO не позволит мне вставить так много, поэтому, пожалуйста, смотрите связанные документы для данных.
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 Doc и экспериментировал с разными значениями размера корпуса, количества ядер и размеров чанка. В каждом случае все было успешно. Итак, я попытался запустить на полных данных, и все работало. Тем не менее, для здравого смысла я попытался снова запустить на полных данных, и это не удалось. Теперь я вернулся туда, где был вчера. Похоже, что запуск функции на большом наборе данных что-то изменил... Я не знаю, что. Возможно переменная сеанса некоторого вида? Итак, новая информация состоит в том, что эта ошибка возникает только после запуска функции на очень большом наборе данных. Перезапуск сеанса не решил проблему, но возобновил сеансы после нескольких часов отсутствия.
Новая информация. Возможно, будет легче воспроизвести проблему на большом корпусе, поскольку это то, что, кажется, вызывает проблему corpus <- do.call(c, replicate(250, corpus, simplify = F))
создаст 500 тыс. корпусов документов на основе предоставленного мною образца. Эта функция может сработать при первом вызове, но для меня она, похоже, дает сбой во второй раз.
Эта проблема сложна, потому что, если бы я мог воспроизвести проблему, я бы, вероятно, смог ее выявить и устранить
Новая информация. Поскольку с этой функцией происходит несколько вещей, было трудно понять, на чем сосредоточить усилия по отладке. Я смотрел как на то, что я использую несколько временных файлов RDS для экономии памяти, так и на то, что я выполняю параллельную обработку. Я написал две альтернативные версии скрипта, одна из которых по-прежнему использует файлы rds и разбивает корпус, но не выполняет параллельную обработку (заменила%dopar% на просто%do%, а также удалила строку registerDoParallel), а другая использует параллельную обработку, но не использует временные файлы RDS, чтобы разбить небольшой образец корпуса. Я не смог выдать ошибку с одноядерной версией скрипта, только с версией, которая использует%dopar%, я смог воссоздать проблему (хотя проблема с перебоями, она не всегда терпит неудачу с допаром). Таким образом, эта проблема появляется только при использовании %dopar%
, Тот факт, что я использую временные файлы RDS, не является частью проблемы.
1 ответ
Если вы попытаетесь перезаписать память программой, использующей параллельную обработку, вам следует сначала убедиться, что это того стоит.
Например, проверьте, находится ли ваш диск на скорости записи 80%-100%; если это так, то ваша программа также может использовать только одно ядро, потому что оно все равно блокируется скоростью записи на диск.
Если это не так, я рекомендую вам использовать выходные данные отладчика или рекламной консоли/графического интерфейса для вашей программы, чтобы убедиться, что все выполняется в правильном порядке.
Если это не поможет, то рекомендую проверить, не накосячили ли вы программу (например одна стрелка указывает не в ту сторону).