我读过许多伪随机数生成器需要许多样本才能“预热”。使用 std::random_device 播种 std::mt19937 是这种情况,还是我们可以期望它在构造后准备好?有问题的代码:
#include <random>
std::random_device rd;
std::mt19937 gen(rd());
我读过许多伪随机数生成器需要许多样本才能“预热”。使用 std::random_device 播种 std::mt19937 是这种情况,还是我们可以期望它在构造后准备好?有问题的代码:
#include <random>
std::random_device rd;
std::mt19937 gen(rd());
Mersenne Twister 是一个基于移位寄存器的 pRNG(伪随机数生成器),因此会受到长期运行 0 或 1 的不良种子的影响,这会导致相对可预测的结果,直到内部状态充分混合。
然而,采用单个值的构造函数对该种子值使用了一个复杂的函数,该函数旨在最大限度地减少产生这种“坏”状态的可能性。还有第二种方法可以mt19937
通过符合 SeedSequence 概念的对象来初始化直接设置内部状态的位置。这是第二种初始化方法,您可能需要关注选择“良好”状态或进行热身。
该标准包括一个符合 SeedSequence 概念的对象,称为seed_seq
. seed_seq
获取任意数量的输入种子值,然后对这些值执行某些操作,以生成适合直接设置 pRNG 内部状态的不同值序列。
这是一个使用足够随机数据加载种子序列以填充整个std::mt19937
状态的示例:
std::array<int, 624> seed_data;
std::random_device r;
std::generate_n(seed_data.data(), seed_data.size(), std::ref(r));
std::seed_seq seq(std::begin(seed_data), std::end(seed_data));
std::mt19937 eng(seq);
这确保了整个状态是随机的。此外,每个引擎都指定了它从 seed_sequence 读取的数据量,因此您可能需要阅读文档以找到适用于您使用的任何引擎的信息。
std::random_device
尽管在这里我完全从,加载了 seed_seq ,但是seed_seq
指定了一些不是特别随机的数字应该可以正常工作。例如:
std::seed_seq seq{1, 2, 3, 4, 5};
std::mt19937 eng(seq);
在下面的评论中,Cubbi 表示seed_seq
通过为您执行热身序列来工作。
以下是播种的“默认值”:
std::random_device r;
std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
std::mt19937 rng(seed);
如果您只使用一个 32 位值作为种子,那么您将得到的只是通过状态空间的相同 2^32 轨迹之一。如果您使用带有 KiB 状态的 PRNG,那么您可能应该将所有这些作为种子。如@bames63'答案的评论中所述,std::seed_seq
如果您想用随机数初始化整个状态,使用可能不是一个好主意。可悲的是,std::random_device
不符合这个SeedSequence
概念,但您可以编写一个包装器:
#include <random>
#include <iostream>
#include <algorithm>
#include <functional>
class random_device_wrapper {
std::random_device *m_dev;
public:
using result_type = std::random_device::result_type;
explicit random_device_wrapper(std::random_device &dev) : m_dev(&dev) {}
template <typename RandomAccessIterator>
void generate(RandomAccessIterator first, RandomAccessIterator last) {
std::generate(first, last, std::ref(*m_dev));
}
};
int main() {
auto rd = std::random_device{};
auto seedseq = random_device_wrapper{rd};
auto mt = std::mt19937{seedseq};
for (auto i = 100; i; --i)
std::cout << mt() << std::endl;
}
这至少在您启用概念之前有效。根据您的编译器是否知道SeedSequence
C++20 concept
,它可能无法工作,因为我们只提供缺少的generate()
方法,没有别的。但是,在鸭式模板编程中,该代码就足够了,因为 PRNG 不存储种子序列对象。
我相信在某些情况下,MT 可以“较差”地播种,从而导致非最佳序列。如果我没记错的话,全零播种就是这样一种情况。如果这对您来说是一个严重的问题,我建议您尝试使用 WELL 生成器。我相信它们更灵活——种子的质量并不重要。(也许更直接地回答您的问题:专注于播种可能更有效,而不是播种不佳然后尝试生成一堆样本以使生成器达到最佳状态。)