简介 - 旧版 NumPy
初始化 MT19937 实例的遗留 NumPy 代码(与Wikipedia上的相同)确保不同的种子值导致不同的初始状态(或者至少在提供单个 int 的情况下)。让我们检查一下 prng 状态的前 3 个数字:
np.random.seed(3)
np.random.get_state()[1][:3]
# gives array([ 3, 1142332464, 3889748055], dtype=uint32)
np.random.seed(7)
np.random.get_state()[1][:3]
array([ 7, 4097098180, 3572822661, 1142383841], dtype=uint32)
# gives array([ 7, 4097098180, 3572822661], dtype=uint32)
然而,这种方法受到批评有两个原因:
- 种子大小受基础类型 uint32 的限制
- 相似的种子可能会产生相似的随机数
如果可以提供一个 int 序列(确实实现了,但是如何实现?),则前者可以解决,但后者更难解决。编写遗留代码的实现时牢记此属性 [^1]。
介绍 - 新的 NumPy 随机
在新的实现中,提供的种子值首先被散列,然后用于提供 MT19937 的初始状态。这种散列确保
- 种子值的相似性无关紧要,2 个相似的种子值以与不相似的种子值相同的概率产生不同的初始状态。之前我们已经看到,对于相邻的种子值,第一个状态变量(600+)是相似的。而在新的实现中,由于某种原因,除了第一个值之外,找不到一个类似的值(很有可能):
prng = np.random.Generator(np.random.MT19937(3)) prng.bit_generator.state["state"]["key"][:3] # gives array([2147483648, 2902887791, 607385081], dtype=uint32) prng = np.random.Generator(np.random.MT19937(7)) prng.bit_generator.state["state"]["key"][:3] # gives array([2147483648, 3939563265, 4185785210], dtype=uint32)
- 两个不同的种子值(任何长度的整数)可能会导致相似的初始状态,概率为 $2^{-128}$(默认情况下)。
如果Matsumoto等人[^1]已经解决了相似种子的问题,那么就不需要使用哈希函数,这会引入状态冲突问题。
问题
鉴于 NumPy 中的新实现,是否有一种良好的做法可以确保 MT19937 实例的不同初始状态并在涉及类似种子值时通过质量要求?我正在寻找一种至少消耗 64 位的初始化方法。
如何修改generate_state
SeedSequence 类的输出:如果给定两个整数,则将前 2 个状态(可能除了第一个状态)替换为给定的种子值本身:
class secure_SeedSequence(np.random.SeedSequence):
def __init__(self, seed1: np.uint32, seed2: np.uint32):
self.seed1 = seed1
self.seed2 = seed2
def generate_state(self, n_words, dtype):
ss = np.random.SeedSequence([self.seed1, self.seed2])
states = ss.generate_state(n_words, dtype)
states[1] = self.seed1
states[2] = self.seed2
return states
ss_a = secure_SeedSequence(3, 1)
prng_a = np.random.Generator(np.random.MT19937(ss_a))
# gives [2147483648 3 1 354512857 3507208499 1943065218]
ss_b = secure_SeedSequence(3, 2)
prng_b = np.random.Generator(np.random.MT19937(ss_b))
# gives [2147483648 3 2 2744275888 1746192816 3474647608]
这里secure_SeedSequence
消耗2*32=64位,prng_a
并且prng_b
处于不同的状态,除了前3个状态变量,所有的状态变量都不一样。根据维基百科,前 2 个数字可能与前 2 个状态变量有一些相关性,但是在生成 624 个随机数之后,下一个内部状态将不再反映初始种子。为了避免这个问题,可以通过跳过前 2 个随机数来改进代码。
解决方法
可以声称,两个 MT19937 实例在为其 SeedSequence 提供不同熵后具有相同状态的机会是任意低的,默认为 $2^{-128}$。但我正在寻找一种解决方案,以确保 100% 的概率初始状态不同,而不仅仅是 $1-2^{-32\cdot N}$ 概率。
此外,我对这个计算的担忧是,虽然获得垃圾流的机会很低,但一旦我们拥有它们,它们就会永远产生垃圾输出,因此,如果生成长度为 $M$ 的流,并且 $N$ 个流/prngs被使用,然后通过从这个 $M \times N$ 2D 数组中选择 $M$ 个数字,一个数字是垃圾的概率趋于 1。
为什么我在这里问?
- 这与 NumPy 中的给定实现密切相关
- 我得到答案的机会在这里最高
- 我认为这是一个普遍的问题,我希望其他人已经深入研究过这个话题
[^1]:伪随机数生成器初始化中的常见缺陷,MAKOTO MATSUMOTO 等人,围绕方程 30。