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

Каждый раз, когда я запускаю следующий код, числа в векторе 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)))

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


person sonicboom    schedule 01.03.2021    source источник


Ответы (1)


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

Вы можете переместить set.seed() в testFun(), чтобы решить эту проблему:

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

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

person Nate    schedule 01.03.2021
comment
Если вы хотите изменить внешний интерфейс распараллеливания, то структура future гарантирует числовые воспроизводимые случайные числа независимо от того, как вы распараллеливаете и сколько параллельных рабочих процессов вы запускаете или запускаете последовательно, например. result_par <- future.apply::future.lapply(1:nsim, testFun, future.seed = TRUE). - person HenrikB; 01.03.2021
comment
Но если заполнение происходит только в родительском сеансе, а затем родительские сеансы порождают дочерние сеансы, не должны ли сгенерированные числа быть одинаковыми при каждом запуске, поскольку родитель всегда заполняется с помощью set.seed(11). Похоже, что дочерний процесс действует так, как будто set.seed(11) вообще не вызывался. - person sonicboom; 02.03.2021
comment
новые сеансы не будут наследовать состояние родителя (возможно, это была плохая аналогия с моей стороны), но это не похоже на объектно-ориентированное наследование. Но вы можете создать новые/дочерние сеансы для репликации исходной/родительской среды. Аргументы ?future.apply::future_lapply() дают вам точный контроль над тем, что нужно перенести в новые сеансы. - person Nate; 02.03.2021
comment
Мое эмпирическое правило: используйте set.seed() только в верхней части вашего скрипта, если вообще используете. Если вы обнаружите, что устанавливаете его в другом месте, это предполагает, что вы делаете что-то специальное, и есть риск, что он вернется и укусит вас позже, например. когда вы забыли об этом, и он сбрасывает ваш поток ГСЧ, на который вы полагаетесь в другом месте - person HenrikB; 02.03.2021
comment
@sonicboom, если вы спрашиваете о future.seed = TRUE из future.apply, то ответ таков: этот аргумент создаст статистически обоснованные подпотоки ГСЧ на основе текущего состояния ГСЧ родительского процесса (= вашего основного RNG сессия). Для получения дополнительной информации см., например, jottr.org/2017/02/19/ будущее-rng и jottr.org /22/09/2020/push-for-static-sound-rng. - person HenrikB; 03.03.2021