【问题标题】:Behavior of discard for boost random number generators提升随机数生成器的丢弃行为
【发布时间】:2016-08-17 14:10:06
【问题描述】:

我正在用一个适配器类包装 boost 随机数生成器来实现蒙特卡洛例程。在对类的成员函数编写单元测试时,我假设 .discard(unsigned int N) 的行为是抽取 N 个随机数而不存储它们,从而推进 rng 的状态。升压代码是:

void discard(boost::uintmax_t z)
{
    if(z > BOOST_RANDOM_MERSENNE_TWISTER_DISCARD_THRESHOLD) {
        discard_many(z);
    } else {
        for(boost::uintmax_t j = 0; j < z; ++j) {
            (*this)();
        }
    }
}

这支持了我的假设。但是,我发现 .discard(1) 产生的序列与没有丢弃的相同序列不是一个数字。代码:

#include <iostream>
#include <iomanip>
#include <random>
#include <boost/random.hpp>

int main()
{
    boost::mt19937 uGenOne(1);
    boost::variate_generator<boost::mt19937&, boost::normal_distribution<> > distOne(uGenOne, boost::normal_distribution<>());

    boost::mt19937 uGenTwo(1);
    boost::variate_generator<boost::mt19937&, boost::normal_distribution<> > distTwo(uGenTwo, boost::normal_distribution<>());
    distTwo.engine().discard(1);

    unsigned int M = 10;
    std::vector<double> variatesOne(M);
    std::vector<double> variatesTwo(M);

    for (unsigned int m = 0; m < M; ++m) {
        variatesOne[m] = distOne();
        variatesTwo[m] = distTwo();
    }

    for (unsigned int m = 0; m < M; ++m)
        std::cout << std::left << std::setw(15) << variatesOne[m] << variatesTwo[m] << std::endl;

    return 0;
}

输出

2.28493        0.538758  
-0.668627      -0.0017866
0.00680682     0.619191  
0.26211        0.26211   
-0.806832      -0.806832 
0.751338       0.751338  
1.50612        1.50612   
-0.0631903     -0.0631903
0.785654       0.785654  
-0.923125      -0.923125

我对 .discard 运作方式的解释是否不正确?为什么前三个输出中的两个序列不同,然后是相同的?

(此代码在 cygwin 上的 msvc 19.00.23918 和 g++ 4.9.2 上编译,结果相同)。

【问题讨论】:

    标签: c++ boost-random


    【解决方案1】:

    这里的问题似乎是引擎没有被正确修改,或者发行版正在添加一些额外的工作。如果我们直接用引擎就好了

    int main()
    {
        boost::mt19937 uGenOne(1);
    
        boost::mt19937 uGenTwo(1);
        uGenTwo.discard(1);
    
        unsigned int M = 10;
        std::vector<double> variatesOne(M);
        std::vector<double> variatesTwo(M);
    
        for (unsigned int m = 0; m < M; ++m) {
            variatesOne[m] = uGenOne();
            variatesTwo[m] = uGenTwo();
        }
    
        for (unsigned int m = 0; m < M; ++m)
            std::cout << std::left << std::setw(15) << variatesOne[m] << variatesTwo[m] << std::endl;
    
        return 0;
    }
    

    它产生

    1.7911e+09     4.28288e+09
    4.28288e+09    3.09377e+09
    3.09377e+09    4.0053e+09
    4.0053e+09     491263
    491263         5.5029e+08
    5.5029e+08     1.29851e+09
    1.29851e+09    4.29085e+09
    4.29085e+09    6.30312e+08
    6.30312e+08    1.01399e+09
    1.01399e+09    3.96591e+08
    

    正如我们预期的那样,这是一个 1 移位序列,因为我们丢弃了第一个输出。

    所以你对丢弃的工作方式是正确的。我不确定为什么通过boost::variate_generator 进行操作时会出现差异。我不明白为什么前三个数字不同,但所有其余的输出都匹配。

    【讨论】:

    • 谢谢@NathanOliver。用于默认 boost::normal_distribution 的算法是 Ziggurat 算法,这是一种接受/拒绝算法Wiki article on Ziggurat。这意味着它可以进行未知数量的平局。然而,让我感到好奇的是,在 3 次正常抽签后序列开始一致......
    • “为什么序列开始一致”这个问题的答案有点微妙。假设底层生成器给出 (u1, u2, u3, ...),移位后给出 (u2, u3, u4, ...)。我们不能说Ziggurat抽了多少次,但是如果在N次正常抽签后,统一抽签的总数相等,那么之后两个系列将相同。在上述情况下,这发生在 3 之后。
    • @tompdavis 但是每个 Ziggurat 的抽奖次数不一样吗?或者生成的随机数是否可能会影响抽奖次数,因此即使它们开始不同,它们也可以根据产生的值同步备份?
    • Ziggurat 绘制一个数字,并进行一些操作,如果结果数字不在分布中,它将绘制一个新数字。假设 u1 产生拒绝,那么它将使用 u2,假设产生接受。但是,由于第二个流以 u2 开头,它将产生一个接受。所以在第一种情况下,底层生成器增加了两次,而第二种情况只增加了一次。在这个例子中,两个分布会产生相同的序列。
    • @tompdavis 太棒了。我不知道它是这样工作的。该信息应该在答案中。不确定我的地方是否适合它。我总能把它做成 CW,你可以把它加进去。
    【解决方案2】:

    只是添加上一个答案的 cmets 中的重要细节。正如@NathanOliver 提到的, .discard 增加了生成器,它将制服发送到正态分布,将制服转换为正态分布。 boost::normal_distribution 使用Ziggurat algorithm,这是一种“接受/拒绝”类型的算法。它绘制一个随机制服,对其进行操作,然后检查它是否在所需的分布中。如果不是,它被拒绝并且一个新的随机制服。

    for(;;) {
            std::pair<RealType, int> vals = generate_int_float_pair<RealType, 8>(eng);
            int i = vals.second;
            int sign = (i & 1) * 2 - 1;
            i = i >> 1;
            RealType x = vals.first * RealType(table_x[i]);
            if(x < table_x[i + 1]) return x * sign;
            if(i == 0) return generate_tail(eng) * sign;
            RealType y = RealType(table_y[i]) + uniform_01<RealType>()(eng) * RealType(table_y[i + 1] - table_y[i]);
            if (y < f(x)) return x * sign;
        }
    

    关键是,如果最后一个if 失败,那么for 循环将再次启动,并再次触发对generate_int_float_pair 的调用。这意味着底层生成器增加的次数是未知的。

    因此,正常序列将具有不同的数字,直到子序列中的拒绝总和相同,此时剩余的统一序列相同。这发生在问题中发布的示例中的第三个位置。 (它实际上有点微妙,因为在 Ziggurat 算法中可以调用底层生成器一次或两次,但本质是相同的——一旦序列同步,它们就永远不会产生不同的变量)。

    【讨论】:

      猜你喜欢
      • 2021-06-19
      • 2010-12-10
      • 2020-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多