MT19937生成器在C++和NumPy中生成不同的数字。

3
我正在尝试用Python复现一些涉及随机数生成的C++代码。C++代码使用MT19937生成器,如下所示:
#include <random>
#include <iostream>

int main() {
    std::mt19937 generator(1234);
    std::uniform_real_distribution<double> distribution(0.0, 1.0);

    for (int i = 0; i < 10; ++i) {
        std::cout << distribution(generator) << std::endl;
    }

    return 0;
}

Python版本为(带有NumPy 1.23.3)
import numpy as np

rng = np.random.Generator(np.random.MT19937(1234))
for _ in range(10):
    print(rng.random())

在这两种情况下,随机种子都设置为1234。但是在我的机器上(macOS 14.0 ARM),这两个产生了不同的输出。C++代码的输出为:
0.497664
0.817838
0.612112
0.77136
0.86067
0.150637
0.198519
0.815163
0.158815
0.116138

Python代码输出时
0.12038356302504949
0.4037014194964441
0.8777026256367374
0.9565788014497463
0.42646002242298486
0.28304326113156464
0.9009410688498408
0.830833142531224
0.6752899264264728
0.3977176012599666

为什么两个MT19937生成器尽管使用相同的种子却产生不同的序列?如果可能的话,我该如何使它们生成相同的序列?

2
发电机产生整数。整数如何映射到指定范围内的实数取决于具体的实现方式。 - undefined
你的问题在于你只看到了分布,它们有自己的状态。你真正只能比较你的旋转器的直接输出。 - undefined
2个回答

5
C++标准库版本的mersenne twister引擎在从整数种子初始化状态方面与所有其他版本不同。
您可以选择在C++中使用另一个与numpy方法匹配的mt19937库(我认为这是大多数人实现mt19937的方式),或者您可以更改Python引擎的种子方式以匹配:
import numpy as np
import numpy.random

WORD_SIZE = 32  # The template argument after the type
STATE_SIZE = 624  # The next template argument (Also `len(np.random.MT19937().state['state']['key'])`)
INITIALIZATION_MULTIPLIER = 1812433253  # The last template argument
DEFAULT_SEED = 5489  # A constant

def cpp_seed_mt19937(seed = DEFAULT_SEED):
    state = np.zeros(STATE_SIZE, dtype=np.uint32)
    state[0] = seed
    for j in range(1, STATE_SIZE):
        state[j] = INITIALIZATION_MULTIPLIER * (state[j-1] ^ (state[j-1] >> (WORD_SIZE - 2))) + j
    result = np.random.MT19937()
    result.state = {'bit_generator': 'MT19937', 'state': {'key': state, 'pos': STATE_SIZE - 1}}
    result.random_raw(1)  # Start at index "STATE_SIZE-1" and advance by 1 to advance past the generated state
    return result
    
engine = cpp_seed_mt19937(2)
print(*engine.random_raw(10), sep='\n')

#include <random>
#include <iostream>

int main() {
    std::mt19937 e(2);
    for (int i = 0; i < 10; ++i)
        std::cout << e() << '\n';
}

这两个应该产生相同的输出:
1872583848
794921487
111352301
4000937544
2360782358
4070471979
1869695442
2081981515
1805465960
1376693511

现在将这些32位数字转换为0到1之间的浮点数,结果将取决于算法的实现方式。您必须使用一种标准化的方法,以便在mersenne twister中给出相同的随机数时得到相同的结果。
(另外,std::uniform_real_distribution<double> distribution(0.0, 1.0);在不同平台上不会给出相同的数字序列)
或者,您可以有一小部分C++代码,您可以使用ctypes或其他方式调用该代码,该代码仅为您生成随机数,而其余代码可以使用Python编写。

在我的情况下,只需要使用numpy中的传统随机生成器来使其与c++匹配就足够了。官方文档建议使用RandomState对象来间接调用传统的种子生成器。 - undefined

4
Mersenne Twister生成器对于任何给定的种子都有一个定义好的序列。同时,还有一些测试值可以用来验证您所使用的生成器是否符合规范。
然而,分布是标准化的,可能在不同的实现中产生不同的值。如果要比较生成器,请移除分布。
请注意,std::mt19937是一个32位生成器,而numpy版本的生成器是32位还是64位并不明显(对我来说)。您可能希望将std::mt19937_64与numpy的实现进行比较 - 当然,不涉及分布。

OP应该只检查扭曲器的输出,而不是分布的输出(分布有自己的状态)。 - undefined
@PepijnKramer 是的,那就是我在回答中试图传达的信息。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接