为蒙特卡罗模拟种子mt19937_64的最佳方法

时间:2014-06-20 19:02:24

标签: c++ c++11 random random-seed mersenne-twister

我正在开发一个运行蒙特卡罗模拟的程序;具体来说,我正在使用Metropolis算法。该程序需要产生数十亿的“随机”数字。我知道梅森捻线机非常适合蒙特卡罗模拟,但我想确保以最佳方式播种发电机。

目前我正在使用以下方法计算32位种子:

mt19937_64 prng; //pseudo random number generator
unsigned long seed; //store seed so that every run can follow the same sequence
unsigned char seed_count; //to help keep seeds from repeating because of temporal proximity

unsigned long genSeed() {
    return (  static_cast<unsigned long>(time(NULL))      << 16 )
         | ( (static_cast<unsigned long>(clock()) & 0xFF) << 8  )
         | ( (static_cast<unsigned long>(seed_count++) & 0xFF) );
}

//...

seed = genSeed();
prng.seed(seed);

我觉得有更好的方法来确保不重复的新种子,我很确定mt19937_64可以播种超过32位。有没有人有任何建议?

6 个答案:

答案 0 :(得分:25)

使用std::random_device生成种子。它提供非确定性随机数,只要您的实现支持它。否则,它允许使用其他随机数引擎。

std::mt19937_64 prng;
seed = std::random_device{}();
prng.seed(seed);
operator()的{​​p> std::random_device会返回unsigned int,因此如果您的平台有32位int,并且您需要64位种子,那么您需要std::mt19937_64 prng; std::random_device device; seed = (static_cast<uint64_t>(device()) << 32) | device(); prng.seed(seed); 。我需要打两次电话。

seed_seq::generate

另一个可用选项是使用std::seed_seq为PRNG播种。这允许PRNG调用std::mt19937_64 prng; std::random_device device; std::seed_seq seq{device(), device(), device(), device()}; prng.seed(seq); ,其在 [0≤i<1]的范围内产生非偏置序列。 2 32 ,输出范围足以填满整个状态。

random_device

我调用seed_seq 4次为{{1}}创建一个4元素的初始序列。但是,就初始序列中元素的长度或来源而言,我不确定最佳做法是什么。

答案 1 :(得分:5)

让我们回顾一下(评论),我们想要生成不同的种子,以便在以下每个事件中获得独立的随机数序列:

  1. 该程序稍后将在同一台机器上重新启动,
  2. 同时在同一台机器上启动两个线程,
  3. 该程序同时在两台不同的机器上启动。
  4. 1使用时间解决,因为epoch,2用全局原子计数器求解,3用平台相关id求解(见How to obtain (almost) unique system identifier in a cross platform way?

    现在重点是将它们组合起来以获得uint_fast64_t(种子类型std::mt19937_64)的最佳方法是什么?我在这里假设我们不知道每个参数的范围或者它们太大,所以我们不能只是用位移来获得一个独特的种子。

    std::seed_seq是最简单的方法,但其返回类型uint_least32_t不是我们的最佳选择。

    一个好的64位哈希是一个更好的选择。 STL在std::hash标题下提供functional,可能是将上面的三个数字连接成一个字符串,然后将其传递给hasher。返回类型是size_t,它在64台机器上很可能符合我们的要求。

    冲突是不可能的,但当然可能,如果你想确保不再建立包含序列的统计数据,你只能存储种子并丢弃重复的运行。

    也可以使用std::random_device来生成种子(碰撞可能仍然会发生,很难说或多或少),但是由于实现是依赖于库的,可能会归结为伪随机生成器,必须检查设备的熵并避免使用零熵设备用于此目的,因为您可能会破坏上述点(尤其是第3点)。遗憾的是,只有将程序带到特定计算机并使用已安装的库进行测试时,才能发现熵。

答案 2 :(得分:4)

就我的评论而言,似乎你感兴趣的是确保如果一个过程在几个同时开始你的几个模拟,它们将得到不同的种子。

我能用当前方法看到的唯一重要问题是竞争条件:如果要同时启动多个模拟,则必须从单独的线程完成。如果是从单独的线程完成的,则需要以线程安全的方式更新seed_count,否则多个模拟可能会以相同的seed_count结束。您只需将其设为std::atomic<int>即可解决此问题。

除此之外,它似乎比它必须更复杂。使用两个独立的计时器可以获得什么?你可以做一些简单的事情:

  1. 在程序启动时,抓取当前系统时间(使用高分辨率计时器)一次,然后存储它。
  2. 为每个模拟分配一个唯一的ID(这可能只是一个初始化为0的整数(应该在没有任何竞争条件的情况下生成,如上所述),每次模拟开始时都会增加,有效地类似于{{1} }。
  3. 播种模拟时,只需使用最初生成的时间戳+唯一ID。如果这样做,过程中的每个模拟都会确保一个独特的种子。

答案 3 :(得分:1)

怎么样......

有一些主要代码启动线程,并且在这些线程中运行了一个函数的副本,每个副本都有自己的Marsenne Twister。我对么?如果是这样,为什么不在主代码中使用另一个随机生成器?它将被加上时间戳,并将其连续的伪随机数作为其种子发送给函数实例。

答案 4 :(得分:0)

POSIX函数gettimeofday(2)以微秒精度给出时间。

POSIX线程函数gettid(2)返回当前线程的ID号。

您应该能够合并自纪元(您已经使用)以来的秒数,以微秒为单位的时间以及获取在一台计算机上始终唯一的种子的线程ID。

如果您还需要它在多台计算机上是唯一的,您还可以考虑获取主机名,IP地址或MAC地址。

我猜这32位可能就足够了,因为有超过40亿个独特的种子可用。除非你运行数十亿的进程,这似乎不太可能,否则你应该没有64位种子。

答案 5 :(得分:0)

从我理解的评论中你想要运行算法的几个实例,每个线程一个实例。并且考虑到每个实例的种子将同时生成,您需要确保这些种子不同。如果这确实是你要解决的问题,那么你的genSeed函数不一定能保证。

在我看来,你需要的是一个可并行的随机数发生器(RNG)。这意味着,您只需要 一个 RNG,您只需使用 一个 种子进行实例化(您可以使用使用genSeed生成,然后通常在顺序环境中生成的随机数序列被分成X个非重叠序列;其中X是线程数。有一个非常好的库,用C ++提供这些类型的RNG,遵循RNG的C ++标准,称为TRNG(http://numbercrunch.de/trng)。

这是更多信息。每种线程有两种方法可以实现非重叠序列。假设来自 RNG的随机数序列是r = {r(1),r(2),r(3),...}和你只有两个主题。如果您事先知道每个线程需要多少随机数,比如M,您可以将r序列的前M个给第一个线程,即{r(1),r(2),...,r (M)},第二个M到第二个线程,即{r(M + 1),r(M + 2),... r(2M)}。这种技术称为blocksplitting,因为您将序列拆分为两个连续的块。

第二种方法是将第一个线程的序列创建为{r(1),r(3),r(5),...},将第二个线程创建为{r(2),r( 4),r(6),...},其优点是您不需要事先知道每个线程需要多少个随机数。这叫做越级跳跃。

请注意,两种方法 保证 每个线程的序列确实不重叠。我上面发布的链接有很多例子,库本身非常容易使用。我希望我的帖子有所帮助。