#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 .