【问题标题】:Making functions that set the random seed independent使设置随机种子的函数独立
【发布时间】:2015-09-12 19:57:25
【问题描述】:

有时我想编写一个随机函数,它总是为特定输入返回相同的输出。我总是通过在函数顶部设置随机种子然后继续来实现这一点。考虑以这种方式定义的两个函数:

sample.12 <- function(size) {
  set.seed(144)
  sample(1:2, size, replace=TRUE)
}
rand.prod <- function(x) {
  set.seed(144)
  runif(length(x)) * x
}

sample.12 返回从集合{1, 2} 中随机采样的指定大小的向量,rand.prod 将指定向量的每个元素乘以从[0, 1] 中统一选择的随机值。通常我希望x &lt;- sample.12(10000) ; rand.prod(x) 有一个“阶梯”分布,pdf 3/4 在[0, 1] 范围内,1/4 在[1, 2] 范围内,但由于我不幸选择了上面相同的随机种子,我看到了不同的结果:

x <- sample.12(10000)
hist(rand.prod(x))

在这种情况下,我可以通过将其中一个函数中的随机种子更改为其他值来解决此问题。例如,在rand.prod 中使用set.seed(10000),我得到了预期的分布:

Previously on SO 这种使用不同种子的解决方案已被认为是生成独立随机数流的最佳方法。但是,我发现该解决方案并不令人满意,因为具有不同种子的流可能彼此相关(甚至可能是highly related to one another);事实上,根据?set.seed,它们甚至可能产生相同的流:

无法保证不同的种子值会以不同的方式播种 RNG,尽管任何例外情况都极为罕见。

有没有办法在 R 中实现一对随机函数:

  1. 始终为特定输入返回相同的输出,并且
  2. 不只是使用不同的随机种子来强制它们的随机源之间的独立性?

【问题讨论】:

    标签: r random random-sample random-seed


    【解决方案1】:

    我对此进行了深入研究,看起来rlecuyer 包提供了独立的随机流:

    为具有多个独立流的随机数生成器的 C 实现提供接口,由 L'Ecuyer 等人 (2002) 开发。这个包的主要目的是使这个随机数生成器能够在并行 R 应用程序中使用。

    第一步是独立流的全局初始化:

    library(rlecuyer)
    .lec.CreateStream(c("stream.12", "stream.prod"))
    

    然后需要修改每个函数以将适当的流重置为其开始状态(.lec.RestartStartStream),将 R 随机数生成器设置为适当的流(.lec.CurrentStream),然后将 R 随机数生成器设置回恢复到调用函数之前的状态 (.lec.CurrentStreamEnd)。

    sample.12 <- function(size) {
      .lec.ResetStartStream("stream.12")
      .lec.CurrentStream("stream.12")
      x <- sample(1:2, size, replace=TRUE)
      .lec.CurrentStreamEnd()
      x
    }
    rand.prod <- function(x) {
      .lec.ResetStartStream("stream.prod")
      .lec.CurrentStream("stream.prod")
      y <- runif(length(x)) * x
      .lec.CurrentStreamEnd()
      y
    }
    

    这满足了“在相同的输入下总是返回相同的输出”的要求:

    all.equal(rand.prod(sample.12(10000)), rand.prod(sample.12(10000)))
    # [1] TRUE
    

    在我们的示例中,流似乎也独立运行:

    x <- sample.12(10000)
    hist(rand.prod(x))
    

    请注意,这不会在我们的脚本运行中给出一致的值,因为每次调用 .lec.CreateStream 都会给出不同的初始状态。为了解决这个问题,我们可以记录每个流的初始状态:

    .lec.GetState("stream.12")
    # [1] 3161578179 1307260052 2724279262 1101690876 1009565594  836476762
    .lec.GetState("stream.prod")
    # [1]  596094074 2279636413 3050913596 1739649456 2368706608 3058697049
    

    然后我们可以将脚本开头的流初始化更改为:

    library(rlecuyer)
    .lec.CreateStream(c("stream.12", "stream.prod"))
    .lec.SetSeed("stream.12", c(3161578179, 1307260052, 2724279262, 1101690876, 1009565594, 836476762))
    .lec.SetSeed("stream.prod", c(596094074, 2279636413, 3050913596, 1739649456, 2368706608, 3058697049))
    

    现在对 sample.12rand.prod 的调用将在脚本调用之间匹配。

    【讨论】:

    • 很棒的发现。为了完整起见,rlecuyer 使用MRG32k3athis paper 的第 1.1 章),因此它也可能有其局限性(就像 Mersenne-Twister 所做的那样)。不过,在 99% 的情况下应该不是什么大问题。
    猜你喜欢
    • 2021-03-23
    • 2019-04-06
    • 2016-01-14
    • 1970-01-01
    • 2014-11-21
    • 2011-06-14
    • 2014-09-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多