我们可以使用set.seed()
在 R 中设置随机种子,这具有全局效果。这是一个最小的例子来说明我的目标:
set.seed(0)
runif(1)
# [1] 0.8966972
set.seed(0)
f <- function() {
# I do not want this random number to be affected by the global seed
runif(1)
}
f()
# [1] 0.8966972
基本上我希望能够避免全局随机种子(即.Random.seed
)在本地环境中的影响,例如 R 函数,这样我就可以实现某种用户无法控制的随机性。例如,即使用户有set.seed()
,他每次调用这个函数时仍然会得到不同的输出。
现在有两种实现方式。第一个依赖于set.seed(NULL)
每次我想获得一些随机数时让 R 重新初始化随机种子:
createUniqueId <- function(bytes) {
withPrivateSeed(
paste(as.hexmode(sample(256, bytes, replace = TRUE) - 1), collapse = "")
)
}
withPrivateSeed <- function(expr, seed = NULL) {
oldSeed <- if (exists('.Random.seed', envir = .GlobalEnv, inherits = FALSE)) {
get('.Random.seed', envir = .GlobalEnv, inherits = FALSE)
}
if (!is.null(oldSeed)) {
on.exit(assign('.Random.seed', oldSeed, envir = .GlobalEnv), add = TRUE)
}
set.seed(seed)
expr
}
即使我将种子设置为 0,您也可以看到我得到了不同的 id 字符串,并且全局随机数流仍然是可重现的:
> set.seed(0)
> runif(3)
[1] 0.8966972 0.2655087 0.3721239
> createUniqueId(4)
[1] "83a18600"
> runif(3)
[1] 0.5728534 0.9082078 0.2016819
> set.seed(0)
> runif(3) # same
[1] 0.8966972 0.2655087 0.3721239
> createUniqueId(4) # different
[1] "77cb3d91"
> runif(3)
[1] 0.5728534 0.9082078 0.2016819
> set.seed(0)
> runif(3)
[1] 0.8966972 0.2655087 0.3721239
> createUniqueId(4)
[1] "c41d61d8"
> runif(3)
[1] 0.5728534 0.9082078 0.2016819
第二个实现可以在Github 上找到。比较复杂,基本思路是:
set.seed(NULL)
使用(in.onLoad()
)在包启动期间初始化随机种子- 将随机种子存储在单独的环境中 (
.globals$ownSeed
) - 每次我们想要生成随机数时:
- 将本地种子分配给全局随机种子
- 生成随机数
- 将新的全局种子(由于步骤 2 已更改)分配给本地种子
- 将全局种子恢复到其原始值
现在我的问题是这两种方法在理论上是否等效。第一种方法的随机性依赖于createUniqueId()
调用时的当前时间和进程ID,而第二种方法似乎依赖于加载包时的时间和进程ID。对于第一种方法,是否有可能createUniqueId()
在同一个 R 进程中同时发生两个调用,以便它们返回相同的 id 字符串?
更新
在下面的答案中,Robert Krzyzanowski 提供了一些set.seed(NULL)
可能导致严重 ID 冲突的经验证据。我为它做了一个简单的可视化:
createGlobalUniqueId <- function(bytes) {
paste(as.hexmode(sample(256, bytes, replace = TRUE) - 1), collapse = "")
}
n <- 10000
length(unique(replicate(n, createGlobalUniqueId(5))))
length(unique(x <- replicate(n, createUniqueId(5))))
# denote duplicated values by 1, and unique ones by 0
png('rng-time.png', width = 4000, height = 400)
par(mar = c(4, 4, .1, .1), xaxs = 'i')
plot(1:n, duplicated(x), type = 'l')
dev.off()
当线到达图的顶部时,这意味着生成了一个重复值。但是,请注意这些重复不是连续出现的,即any(x[-1] == x[-n])
通常是FALSE
。可能存在与系统时间相关的重复模式。由于我对基于时间的随机种子的工作原理缺乏了解,我无法进一步调查,但您可以在此处和此处查看相关的 C 源代码。