【问题标题】:C++ boost random number generator set seed for multiple instances用于多个实例的 C++ 提升随机数生成器集种子
【发布时间】:2018-06-07 04:29:55
【问题描述】:

我想创建一个类的许多实例,并希望它们使用 boost 随机数生成器来进行具有用户定义平均值的正态分布跳转。我一直在阅读一些消息来源,他们说您不想像here 这样重新设置数字生成器的种子。理想情况下,我想要一个全局生成器,并且单个类的每个实例都能够更改平均值并生成一个对于所有实例都不相同的随机数。我正在努力实现这一点我有一个全局普通类,但是该类的每个实例的种子都是相同的。

// C/C++ standard library
#include <iostream>
#include <cstdlib>
#include <ctime>

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/random/lognormal_distribution.hpp>

/**
 * The mt11213b generator is fast and has a reasonable cycle length
 * See http://www.boost.org/doc/libs/1_60_0/doc/html/boost_random/reference.html#boost_random.reference.generators
 */
struct random_generator : boost::mt11213b {
    random_generator(void){
        seed(static_cast<unsigned int>(std::time(0)));
    }
} random_generator;


template<
    class Type
> struct Distribution {
    Type distribution;
    boost::variate_generator<decltype(random_generator),Type> variate_generator;

    template<class... Args>
    Distribution(Args... args):
        variate_generator(random_generator,Type(args...)) {}

    double random(void) {
        return variate_generator();
    }
};
typedef Distribution< boost::normal_distribution<> > Normal;

using namespace std;
// global normal random number generator
Normal normal_random_generator;

// Class Individual
class Individual {
    public:
        Individual() { } // constructor initialise value
        virtual~Individual() = default;
        // an accessor to pass information back
        void move_bias_random_walk(double mu) {
            normal_random_generator = {mu, sigma_};
            distance_ += normal_random_generator.random();
        }

        // An accessor for the distance object
        double get_distance() {
            return distance_;
        }

    private:
        //containers
        double distance_ = 0.4;
        double sigma_ = 0.4;
};


int main() {
    cout << "!!!Begin!!!" << endl;
    // Initialise two individuals in this case but there could be thousands
    Individual individual_a;
    Individual individual_b;

    cout << "starting values: individual a = " << individual_a.get_distance() << " individual b = " << individual_b.get_distance() << endl;
    // Do 10 jumps with the same mean for each individual and see where they end up each time

    cout << "A\tB" << endl;
    for (auto i = 1; i <= 10; ++i) {
        double mean = rand();
        individual_a.move_bias_random_walk(mean);
        individual_b.move_bias_random_walk(mean);
        cout << individual_a.get_distance() << "\t" << individual_b.get_distance() << endl;
    }
    cout << "finished" << endl;

    system("PAUSE");
    return 0;
}

这是编译上述代码得到的输出。

!!!Begin!!!
starting values: individual a = 0.4 individual b = 0.4
A   B
41.8024 41.8024
18509.2 18509.2
24843.6 24843.6
51344   51344
70513.4 70513.4
86237.8 86237.8
97716.2 97716.2
127075  127075
154037  154037
178501  178501
finished

【问题讨论】:

  • 上面的代码无法编译,因为它在个人类中的normal_random_generator 上缺少下划线。此外,AFAIK variate_generator 将复制您的 random_generator,这就是为什么这两个发行版提供相同的序列。
  • @Christoph 我已将 normal_random_generator 定义为 std:namespace 下面的全局变量,它至少允许我的系统编译。这就是为什么我使用全局变量以便只制作一个副本并且任何平局都会继续种子
  • @Cyrillm_44 和 Cristoph 正确地指出这不是实际发生的情况。我正在查看您的代码,还有很多问题,所以我现在删除了我的答案。 (直到我明白为止)
  • 当我早些时候复制代码时,随机生成器被定义为Normal normal_random_generator_;。我猜你现在修好了。然而,我评论中的重点是这个生成器被复制而不是被引用使用......
  • @Christoph 你可以看到编辑:stackoverflow.com/posts/50732980/revisions(你也可以制作它们,所以请随时修复类似的小错误)

标签: c++ random boost


【解决方案1】:

我已经尝试提出一个示例来实现我认为您想要实现的目标。我希望你不介意我摆脱了 boost,因为我看不到在这里使用它的理由。

希望这会有所帮助:

// C/C++ standard library
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <random>

// Class Individual
class Individual
{
public:
  // an accessor to pass information back
  void move_bias_random_walk( double mu, double sigma = 0.4 )
  {
    distance_ += std::normal_distribution<double>{mu, sigma}( myEngine );
  }

  // An accessor for the distance object
  double get_distance() { return distance_; }

private:
  // containers
  double distance_ = 0.4;
  static std::mt19937 myEngine;
};

// initialize static random engine to be shared by all instances of the Inidvidual class   
auto Individual::myEngine = std::mt19937( std::time( 0 ) );

int main()
{
  std::cout << "!!!Begin!!!" << std::endl;
  // Initialise two individuals in this case but there could be thousands
  Individual individual_a{};
  Individual individual_b{};

  std::cout << "starting values: individual a = " << individual_a.get_distance()
       << " individual b = " << individual_b.get_distance() << std::endl;
  // Do 10 jumps with the same mean for each individual and see where they end up each time

  std::cout << "A\tB" << std::endl;

  // let's not use rand()
  std::default_random_engine eng{1337};
  std::uniform_real_distribution<double> uniformMean{-10,10};

  for ( auto i = 1; i <= 10; ++i ) {
    double mean = uniformMean(eng);
    individual_a.move_bias_random_walk( mean );
    individual_b.move_bias_random_walk( mean );
    std::cout << individual_a.get_distance() << " " << individual_b.get_distance() << std::endl;
  }

  std::cout << "finished" << std::endl;
  return 0;
}

打印出来:

!!!Begin!!!
starting values: individual a = 0.4 individual b = 0.4
A   B
8.01456 7.68829
2.53383 1.19675
7.06496 5.74414
9.60985 9.04333
12.4008 13.4647
11.2468 13.4128
6.02199 8.24547
0.361428 2.85905
-3.28938 -1.59109
-5.99163 -4.37436
finished

【讨论】:

  • 嘿。为什么time(0)static 很聪明,但几乎总是有代码味道。您没有解释为什么 A 和 B 上的数字总是相同。不过,+1 表示“我们不要单独使用 rand()
  • 哦,使用 Boost 的一个原因是它具有随机设备的附加种子选项:pcg-random.org/posts/cpps-random_device.html(它模拟 SeeqSeq)
  • time(0) 刚刚被使用,因为 OP 使用了它。不过,您关于使用随机设备播种的 cmets 肯定是正确的;)也感谢您提供有关播种选项的参考,阅读愉快:)
【解决方案2】:

就像 Christoph 评论的那样,如果您复制生成器引擎的状态,您将拥有两个具有相同状态的引擎。

所以在复制之后播种引擎:

template<class... Args>
Distribution(Args... args):
    variate_generator(random_generator,Type(args...)) {
        boost::random::random_device dev;
        variate_generator.engine().seed(dev);
    }

注意从random_device 播种是多么可取。这样可以确保种子本身是随机的,并且引擎的整个状态都是种子。

如果您不想链接到 Boost Random,可以再次使用单个种子值:

template<class... Args>
Distribution(Args... args):
    variate_generator(random_generator,Type(args...)) {
        std::random_device dev;
        variate_generator.engine().seed(dev());
    }

其他问题

当你这样做时

normal_random_generator = {mu, sigma_};

您正在替换您的全局 Distribution 实例,并将 mu 设置为您从 main 获得的值。由于您在那里(ab)使用rand()mu 将只是一些完全非随机且较大的值。在我的系统上它总是

1804289383
846930886
1681692777
1714636915
1957747793
424238335
719885386
1649760492
596516649
1189641421

相比之下,您的分布的 sigma 非常小,因此您生成的值将接近原始值,并且数字的科学格式将隐藏任何差异:

!!!Begin!!!
starting values: individual a = 0.4 individual b = 0.4
A       B
1.80429e+09     1.80429e+09
2.65122e+09     2.65122e+09
4.33291e+09     4.33291e+09
6.04755e+09     6.04755e+09
8.0053e+09      8.0053e+09
8.42954e+09     8.42954e+09
9.14942e+09     9.14942e+09
1.07992e+10     1.07992e+10
1.13957e+10     1.13957e+10
1.25853e+10     1.25853e+10
finished

看起来好像两列具有相同的值。但是,它们基本上只是rand() 的输出,变化很小。添加

std::cout << std::fixed;

表明存在差异:

!!!Begin!!!
starting values: individual a = 0.4 individual b = 0.4
A       B
1804289383.532134       1804289383.306165
2651220269.054946       2651220269.827112
4332913046.416999       4332913046.791281
6047549960.973747       6047549961.979666
8005297753.938927       8005297755.381466
8429536088.122741       8429536090.737263
9149421474.458202       9149421477.268963
10799181966.514246      10799181969.109875
11395698614.754076      11395698617.892900
12585340035.563337      12585340038.882833
finished

总的来说,我建议

  • 不使用rand() 和/或为mean 选择更合适的范围
  • 另外我建议从不使用全局变量。事实上,您每次都会在这里创建一个 Normal 的新实例:

        normal_random_generator = {mu, sigma_};
    

    我看不出用该实例覆盖全局变量可能有什么价值。它只会降低效率。所以,这是严格等价的并且更高效:

    void move_bias_random_walk(double mu) {
        Normal nrg {mu, sigma_};
        distance_ += nrg.random();
    }
    
  • 了解您的分布的 Sigma,这样您就可以预测预期数字的方差。

固定代码 #1

Live On Coliru

// C/C++ standard library
#include <iostream>
#include <cstdlib>
#include <ctime>

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/random/lognormal_distribution.hpp>
#include <boost/random/random_device.hpp>

/**
 * The mt11213b generator is fast and has a reasonable cycle length
 * See http://www.boost.org/doc/libs/1_60_0/doc/html/boost_random/reference.html#boost_random.reference.generators
 */
typedef boost::mt11213b Engine;
boost::random::random_device random_device;

template<
    class Type
> struct Distribution {
    boost::variate_generator<Engine, Type> variate_generator;

    template<class... Args>
    Distribution(Args... args):
        variate_generator(Engine(random_device()), Type(args...)) {
            //variate_generator.engine().seed(random_device);
            //std::cout << "ctor test: " << variate_generator.engine()() << "\n";
        }

    double random(void) {
        double v = variate_generator();
        //std::cout << "debug: " << v << "\n";
        return v;
    }
};

typedef Distribution< boost::normal_distribution<> > Normal;

// Class Individual
class Individual {
    public:
        Individual() { } // constructor initialise value
        virtual ~Individual() = default;

        // an accessor to pass information back
        void move_bias_random_walk(double mu) {
            Normal nrg {mu, sigma_};
            distance_ += nrg.random();
        }

        // An accessor for the distance object
        double get_distance() {
            return distance_;
        }

    private:
        //containers
        double distance_ = 0.4;
        double sigma_ = 0.4;
};


int main() {
    std::cout << std::fixed;
    std::cout << "!!!Begin!!!" << std::endl;
    // Initialise two individuals in this case but there could be thousands
    Individual individual_a;
    Individual individual_b;

    std::cout << "starting values: individual a = " << individual_a.get_distance() << " individual b = " << individual_b.get_distance() << std::endl;
    // Do 10 jumps with the same mean for each individual and see where they end up each time

    std::cout << "A\tB" << std::endl;
    for (auto i = 1; i <= 10; ++i) {
        double mean = rand()%10;
        //std::cout << "mean: " << mean << "\n";
        individual_a.move_bias_random_walk(mean);
        individual_b.move_bias_random_walk(mean);
        std::cout << individual_a.get_distance() << "\t" << individual_b.get_distance() << std::endl;
    }
    std::cout << "finished" << std::endl;
}

打印

!!!Begin!!!
starting values: individual a = 0.400000 individual b = 0.400000
A   B
3.186589    3.754065
9.341219    8.984621
17.078740   16.054461
21.787808   21.412336
24.896861   24.272279
29.801920   29.090233
36.134987   35.568845
38.228595   37.365732
46.833353   46.410176
47.573564   47.194575
finished

简化:演示 #2

以下内容完全相同,但效率更高:

Live On Coliru

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/random_device.hpp>
#include <iostream>

/**
 * The mt11213b generator is fast and has a reasonable cycle length
 * See http://www.boost.org/doc/libs/1_60_0/doc/html/boost_random/reference.html#boost_random.reference.generators
 */
typedef boost::mt11213b Engine;

template <typename Distribution>
class Individual {
  public:
    Individual(Engine& engine) : engine_(engine) { }

    // an accessor to pass information back
    void move_bias_random_walk(double mu) {
        Distribution dist { mu, sigma_ };
        distance_ += dist(engine_);
    }

    // An accessor for the distance object
    double get_distance() {
        return distance_;
    }

  private:
    Engine& engine_;
    //containers
    double distance_ = 0.4;
    double sigma_ = 0.4;
};

int main() {
    boost::random::random_device device;
    Engine engine(device);

    std::cout << std::fixed;
    std::cout << "!!!Begin!!!" << std::endl;

    // Initialise two individuals in this case but there could be thousands
    Individual<boost::normal_distribution<> > individual_a(engine);
    Individual<boost::normal_distribution<> > individual_b(engine);

    std::cout << "starting values: individual a = " << individual_a.get_distance() << " individual b = " << individual_b.get_distance() << std::endl;
    // Do 10 jumps with the same mean for each individual and see where they end up each time

    std::cout << "A\tB" << std::endl;
    for (auto i = 1; i <= 10; ++i) {
        double mean = rand()%10;
        individual_a.move_bias_random_walk(mean);
        individual_b.move_bias_random_walk(mean);
        std::cout << individual_a.get_distance() << "\t" << individual_b.get_distance() << std::endl;
    }
    std::cout << "finished" << std::endl;
}

注意

  • 它可以根据需要共享 Engine 实例
  • 它不使用全局变量(或者,更糟糕的是,重新分配它们!)
  • 否则行为完全相同,但代码少得多

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-08
    • 2022-01-06
    • 1970-01-01
    • 1970-01-01
    • 2016-07-22
    • 1970-01-01
    • 2013-08-23
    相关资源
    最近更新 更多