15

在我当前的 C++11 项目中,我需要执行 M 模拟。对于每个模拟m = 1, ..., M,我使用一个std::mt19937对象随机生成一个数据集,构造如下:

std::mt19937 generator(m);
DatasetFactory dsf(generator);

根据https://stackoverflow.com/a/15509942/1849221https://stackoverflow.com/a/14924350/1849221,Mersenne Twister PRNG 受益于热身阶段,目前我的代码中没有这个阶段。为方便起见,我报告了建议的代码片段:

#include <random>

std::mt19937 get_prng() {
    std::uint_least32_t seed_data[std::mt19937::state_size];
    std::random_device r;
    std::generate_n(seed_data, std::mt19937::state_size, std::ref(r));
    std::seed_seq q(std::begin(seed_data), std::end(seed_data));
    return std::mt19937{q};
}

我的问题是我需要结果的可重复性,即在不同的执行中,对于每个模拟,数据集必须相同。这就是为什么在我当前的解决方案中我使用当前模拟来播种 Mersenne Twister PRNG 的原因。在我看来,使用std::random_device阻止数据相同(AFAIK,这是 的确切目的std::random_device)。

编辑:通过不同的执行,我的意思是重新启动可执行文件。

如何在我的代码中引入上述预热阶段而不影响可重复性?谢谢。

可能的解决方案#1

这是基于@SteveJessop 的第二个提案的暂定实现

#include <random>

std::mt19937 get_generator(unsigned int seed) {
        std::minstd_rand0 lc_generator(seed);
        std::uint_least32_t seed_data[std::mt19937::state_size];

        std::generate_n(seed_data, std::mt19937::state_size, std::ref(lc_generator));
        std::seed_seq q(std::begin(seed_data), std::end(seed_data));
        return std::mt19937{q};
    }

可能的解决方案#2

这是基于@SteveJassop 和@AndréNeve 共同贡献的暂定实现。该sha256函数改编自https://stackoverflow.com/a/10632725/1849221

#include <openssl/sha.h>
#include <sstream>
#include <iomanip>
#include <random>

 std::string sha256(const std::string str) {
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, str.c_str(), str.size());
    SHA256_Final(hash, &sha256);

    std::stringstream ss;
    for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) 
        ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];

    return ss.str();
}

std::mt19937 get_generator(unsigned int seed) {
    std::string seed_str = sha256(std::to_string(seed));
    std::seed_seq q(seed_str.begin(), seed_str.end());
    return std::mt19937{q};
}

编译:-I/opt/ssl/include/ -L/opt/ssl/lib/ -lcrypto

4

3 回答 3

5

两种选择:

  1. 遵循您的建议,但不要使用std::random_device r;为 MT 生成种子序列,而是使用不同的 PRNG 种子序列m。选择一个不会像 MT 那样在处理小种子数据时需要预热的问题:我怀疑 LCG 可能会这样做。对于大量的过度杀伤,您甚至可以使用基于安全哈希的 PRNG。这很像密码学中的“密钥拉伸”,如果你听说过的话。实际上,您可以使用标准密钥拉伸算法,但您使用它来生成长种子序列而不是大密钥材料。

  2. 继续使用m为您的 MT 播种,但discard在开始模拟之前需要大量恒定的数据。也就是说,忽略使用强种子的建议,而是运行 MT 足够长的时间以使其达到良好的内部状态。我不知道您需要丢弃多少数据,但我希望互联网会这样做。

于 2013-04-18T11:02:38.600 回答
4

我认为您只需要为您希望重现的每次运行/模拟存储初始种子(在您的情况下为std::uint_least32_t seed_data[std::mt19937::state_size]数组)和n您所做的预热步骤数(例如discard(n),如上所述使用)。

有了这些信息,您总是可以创建一个新的 MT 实例,用之前的实例播种它,seed_data然后运行它以进行相同的n预热步骤。这将产生相同的值序列,因为 MT 实例在热身结束时将具有相同的内部状态。

当您提到std::random_device影响再现性时,我相信在您的代码中它只是用于生成种子数据。如果您将其用作随机数本身的来源,那么您将无法获得可重现的结果。由于您仅使用它来生成种子,因此应该没有任何问题。如果你想重现价值,你就不能每次都生成一个新的种子!

从 的定义std::random_device

“std::random_device 是一个均匀分布的整数随机数生成器,它产生非确定性随机数。”

因此,如果它不是确定性的,您就无法重现它产生的值序列。话虽如此,仅使用它生成良好的随机种子,然后再存储它们以供重新运行。

希望这可以帮助

编辑 :

在与@SteveJessop 讨论之后,我们得出的结论是,数据集(或其中的一部分)的简单哈希足以用作您需要的合适的种子。这允许在每次运行模拟时以确定性方式生成相同的种子。正如@Steve 所提到的,您必须保证哈希的大小与std::mt19937::state_size. 如果它太小,那么您可以按照他的建议连接 m、m+M、m+2M、... 的哈希值,直到您有足够的数据。

我在这里发布更新的答案,因为使用哈希的想法是我的,但我会赞成@SteveJessop 的答案,因为他对此做出了贡献。

于 2013-04-18T10:30:37.697 回答
1

对您链接到的答案之一的评论表明:

巧合的是,默认的 C++11 seed_seq 是 Mersenne Twister 预热序列(尽管现有的实现,例如 libc++ 的 mt19937,在提供单值种子时使用更简单的预热)

因此,您可以使用您当前的固定种子std::seed_seq为您做热身。

std::mt19937 get_prng(int seed) {
    std::seed_seq q{seed, maybe, some, extra, fixed, values};
    return std::mt19937{q};
}
于 2013-04-18T19:25:30.667 回答