Результаты распараллеливания с библиотекой snowfall не воспроизводимы?

#r #parallel-processing #snow #snowfall

#r #параллельная обработка #снег #снегопад

Вопрос:

Каждый раз, когда я запускаю следующий код, числа в векторе result_seq остаются неизменными, поскольку я использовал set.seed(11) их до генерации вектора.

Однако, похоже, что даже несмотря на то, что я использую set.seed(11) его снова, прежде чем сгенерировать числа result_par , цифры меняются каждый раз, когда я запускаю код.

 library(snowfall)
snowfall::sfInit(parallel = TRUE, cpus = 4)

testFun = function(i) {
  result <- rnorm(1,10,3)
}

nsim <- 10

set.seed(11)
result_seq <- sapply(1:nsim, testFun)
print(mean(result_seq))

set.seed(11)
result_par <- sfLapply(1:nsim, testFun)
print(mean(as.numeric(result_par)))
 

Почему это происходит? Что я могу сделать, чтобы гарантировать, что случайные числа, сгенерированные во время распараллеливания snowfall, воспроизводимы?

Ответ №1:

Поскольку R является однопоточным, любое распараллеливание кода фактически приводит к запуску нескольких сеансов. Итак, здесь вы фактически запускаете 4 отдельных «дочерних» сеанса, sfLapply() и начальная настройка выполняется только один раз в вашем «родительском» сеансе. «Дочерние» сеансы не знают о других и, следовательно, не знают, что вы хотите повторно установить начальное значение в каждом из них.

Вы можете set.seed() перейти testFun() к решению этой проблемы:

 testFun = function(i) {
  set.seed(11)
  result <- rnorm(1,10,3)
}
 

sfExport может быть, стоит изучить, поскольку он предназначен для распределения параметров по «дочерним» сеансам для подобных контекстов.

Комментарии:

1. Если вы готовы переключить интерфейс распараллеливания, то фреймворк future гарантирует числовые воспроизводимые случайные числа независимо от того, как вы выполняете распараллеливание и сколько параллельных рабочих процессов вы запускаете, или выполняете последовательно, например result_par <- future.apply::future.lapply(1:nsim, testFun, future.seed = TRUE) .

2. Но если заполнение происходит только в родительском сеансе, а затем родители порождают дочерние сеансы, разве сгенерированные числа не должны быть одинаковыми при каждом запуске, поскольку родительский файл всегда заполняется с помощью set.seed(11) . Похоже, что дочерний процесс действует так, как если бы set.seed(11) вообще не вызывался.

3. новые сеансы не будут наследовать состояние «родительского» (возможно, это была плохая аналогия с моей стороны), но это не похоже на объектно-ориентированное наследование. Но вы можете создать новые / дочерние сеансы для репликации исходной / родительской среды. Аргументы для ?future.apply::future_lapply() дают вам точный контроль над тем, что переносить в новые сеансы

4. Мое эмпирическое правило: используйте set.seed() только в верхней части вашего скрипта, если вообще используете. Если вы обнаружите, что устанавливаете его в другом месте, это означает, что вы делаете что-то специальное, и есть риск, что оно вернется и укусит вас позже, например, когда вы забыли об этом, и он сбрасывает ваш поток RNG, на который вы полагаетесь в другом месте

5. @sonicboom, если вы спрашиваете о future.seed = TRUE of future.apply , то ответ таков: этот аргумент будет генерировать статистически достоверные подпотоки RNG на основе текущего состояния RNG родительского процесса (= вашего основного сеанса R). Для получения дополнительной информации см., Например jottr.org/2017/02/19/future-rng и jottr.org/2020/09/22/push-for-statical-sound-rng .