【问题标题】:c++ Gaussian random number generator keeps generating same sequencec ++高斯随机数生成器不断生成相同的序列
【发布时间】:2021-09-07 23:57:03
【问题描述】:

我正在尝试使用类似于 Python 的 Numpy random number generator 的 API 来实现生成高斯(又名正常)随机浮点​​数的 C++ 类:

numpy.random.normal(loc, scale)

其中loc 是平均值,scale 是标准差。

以下是我的尝试。

#include <cstdio>
#include <random>
#include <ctime>


class Gaussian
{
    std::default_random_engine gen;

    public:
        Gaussian()
        {
            srand((unsigned int)time(NULL));
        }

        double get(double mean, double std)
        {
            std::normal_distribution<double> nd(mean, std);
            return nd(gen);
        }
};

问题在于,在我的main() 函数中,我生成了 10 个随机双精度数,并且顺序始终相同。我在 Mac 上使用 g++

int main(int argc, char**argv)
{
    srand((unsigned int)time(NULL));

    Gaussian g;
    int N = 10;
    double mean = 50.0;
    double std = 2.0;

    for (int i = 0; i < N; i++) {
        double value = g.get(mean, std);
        printf("%f\n", value);
    }
}

// Compile: g++ main.cpp

一致地产生多次调用:

47.520528
53.224019
52.765603
48.191707
46.679143
50.151444
50.194442
49.542437
51.169795
51.069510

发生了什么事?

【问题讨论】:

  • 我认为你不会通过调用srand 来播种std::default_random_engine(假设一个是C++,另一个是C),但我可能弄错了。我认为这只影响rand。您可能想要使用std::random_device r;std::default_random_engine gen(r()),或者您可以使用gen.seed(time(NULL)) 进行C 风格播种

标签: c++ random


【解决方案1】:

如果您想要不同的输出,std::default_random_engine gen 需要以不同的方式播种。默认构造函数(即在您的示例中构造的构造函数)将始终以 default_seed 为种子,这将始终相同。

您可以使用任何标准 C++ 方法提供种子来构造类成员,例如构造函数中的初始化列表:

public:
    Gaussian() : gen{ time(NULL) } 
{ }

或者您可以在构造函数中调用gen.seed(),这样效率会降低,因为生成器实际上被播种了两次,一次是在构造时,另一次是在调用seed() 时。

std::default_random_engine 使用的确切引擎是实现定义的,但我们知道srand() 对其没有影响。* 这确实是示例代码的根本缺陷:尝试使用srand() 播种随机生成器无效。

还值得注意的是time(NULL) 以秒为单位返回时间。如果您在同一秒内多次初始化生成器,您将获得相同的输出。 std::chrono::high_resolution_clock::now().time_since_epoch().count() 可能是基于时钟的简单播种的更好选择。

另请注意,为每个单独的随机数重新创建分布对象是低效的。如果有许多具有相同均值和标准的随机数,最好继续使用相同的对象。开发。需要。

* 这来自 C++11 §[rand.req.eng] 表 117:默认构造函数“[c]创建一个具有与所有其他默认构造函数相同的初始状态的引擎E型发动机。”并且函数调用运算符 "[a] 将 e 的状态 ei 推进到 ei+1 = TA(ei) ..." 这使srand() 无法更改默认构造引擎的状态,也无法影响构造后的引擎状态(即重新播种) .

【讨论】:

  • 谢谢。不应该将大括号更改为构造函数的初始化列表中的括号吗?如:Gaussian() : gen(time(NULL) ) { ... }
  • 在这种情况下您可以使用其中任何一种。 {} 是 C++11 大括号初始化器样式。规则略有不同,但在这里并不是特别重要。
  • 按照您的建议,我想出了一个完整的课程。你能提供意见吗? stackoverflow.com/a/69096024/4561314
【解决方案2】:

感谢 TrentP 的回答,我想出了下面的课程。

我还发现这些资源很有用:

https://en.cppreference.com/w/cpp/numeric/random/normal_distribution

Can I change a distribution parameters?

Should I use std::default_random_engine or should I use std::mt19937?

#include <random>

// Class for generating Gaussian random numbers.
class Gaussian
{
    std::random_device _rd{}; // For random seeding.
    std::mt19937 _gen{_rd()};
    std::normal_distribution<double> _normal_dist;

    public:
        Gaussian(double mean, double stdev)
        {
            set(mean, stdev);
        }

        // Update the Gaussian distribution parameters.
        void set(double mean, double stdev)
        {
            std::normal_distribution<double>::param_type new_params(mean, stdev);
            _normal_dist.param(new_params);
        }

        double get()
        {
            return _normal_dist(_gen);
        }
};

【讨论】:

  • 问题是为什么类返回相同的值,而不是编写一个更好的新类(这真的不是一个很好的问题)。您可以使用std::*_distribution::param() 更改参数,而不是构建新的分布并复制它。虽然基于时钟的播种没问题,但使用std::random_device 通常会更好。
  • 谢谢。这个问题比我原先想象的要复杂。
猜你喜欢
  • 2014-06-09
  • 1970-01-01
  • 2021-12-31
  • 1970-01-01
  • 1970-01-01
  • 2014-04-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多