如何获取NumPy随机数生成器的当前种子?

97

以下代码导入了NumPy并设置了种子。

import numpy as np
np.random.seed(42)

然而,我不想设置随机种子,而是更关心如何读取它。random.get_state() 似乎并不包含种子。文档 中没有明显的答案。

如果我没有手动设置随机种子,如何检索由 numpy.random 使用的当前种子?

我想使用当前种子来在进程的下一次迭代中继续使用。


1
你能解释一下你所说的“使用当前种子来传递到下一个进程迭代”的意思吗?你为什么不能简单地使用np.random.get_statenp.random.set_state的组合,或者传递一个np.random.RandomState实例来跟踪RNG的内部状态呢? - ali_m
1
@ali_m 如果我固定了种子,我知道要使用哪个种子来重现结果。但是,如果我没有固定种子,我怎么知道使用了哪个种子? - Mast
1
为什么你的问题的答案说“简短的回答是你根本不能这样做(至少在一般情况下不行)”,但是你却接受了这个答案。他是否成功回答了你的问题?我感到困惑。 - Charlie Parker
@CharlieParker,我接受了回答,因为没有更好的选择。如果你有一个能够解决这个问题并说明如何做到的答案,请发表出来。接受标记是可以移动的。 - Mast
为什么不先通过 seed = np.random.randint(0, 100000) 设置一个种子? - Fangda Han
6个回答

109

简短的回答是你不能这样做(至少不是通常情况下)。

numpy使用的 Mersenne Twister 随机数生成器 (RNG) 有219937-1个可能的内部状态,而单个64位整数只有264个可能的值。因此,无法将每个RNG状态映射到唯一的整数种子。

你可以使用 np.random.get_statenp.random.set_state 直接获取和设置RNG的内部状态。 get_state的输出是一个元组,其第二个元素是一个 (624,) 的32位整数数组。这个数组的位数足以表示RNG的每个可能的内部状态 (2624 * 32 > 219937-1)。

get_state返回的元组可以像种子一样用于创建可重复的随机数序列。例如:
import numpy as np

# randomly initialize the RNG from some platform-dependent source of entropy
np.random.seed(None)

# get the initial state of the RNG
st0 = np.random.get_state()

# draw some random numbers
print(np.random.randint(0, 100, 10))
# [ 8 76 76 33 77 26  3  1 68 21]

# set the state back to what it was originally
np.random.set_state(st0)

# draw again
print(np.random.randint(0, 100, 10))
# [ 8 76 76 33 77 26  3  1 68 21]

22
为什么你说你不能做到“简短回答是你根本做不到(至少在一般情况下是这样的)。”从我读到的回答来看,好像您可以做到。我感到困惑。 - Charlie Parker
3
不确定是否有严格的定义规定种子在任何情况下需要是什么。如果元组或其他结构将您带回所需的相同随机状态,那么这不就是一个种子吗?我不知道种子必须是整数。但是你的答案似乎有效,除了你提到的整数表示之外,是否还有什么需要注意的地方? - Charlie Parker
21
我不是特别想辩论“种子”的定义。只要你愿意将get_state的输出称为“种子”,那么我回答中展示的代码对你就有效。我理解OP的问题是“np.random.seed的反函数是什么?”但出于我上面讨论过的原因,这是不可能的。 - ali_m
10
我没有在辩论,也不想辩论。我真的只是想确保我理解了你的回答,并且没有意外的奇怪限制。据我所知,一切都好,你的回答对我来说更有意义了(这就是为什么我点了赞的原因 ;) )。谢谢 :) - Charlie Parker
3
由于 RandomState 中的 seed 参数需要是介于0和2**32-1之间的整数或这些整数数组,因此@bukzor提出的问题并不重要。原则上,您可以通过解包本机Python整数中的位来生成uint32s数组,但这不是 RandomState 本身支持的功能。 - ali_m
显示剩余5条评论

36

这篇文章旨在澄清ali_m的正确答案并更正Dong Justin的建议。


以下是我的研究发现:

  1. 使用np.random.seed(X)设置随机种子后,您可以使用np.random.get_state()[1][0]找到它。
  2. 然而,这对您来说几乎没有什么用处。

下面的代码段输出将向您展示为什么两个语句都是正确的。


语句1 - 您可以使用np.random.get_state()[1][0]找到随机种子。

如果您使用np.random.seed(123)设置了随机种子,则可以使用state=np.random.get_state()以元组形式检索随机状态。以下是state的详细信息(我在Spyder中使用变量资源管理器)。我正在使用屏幕截图,因为使用print(state)会因为元组第二个元素中包含的数组大小而使控制台溢出。

enter image description here

您可以轻松地看到在第二个元素包含的数组中,123是数组的第一个数字。使用seed = np.random.get_state()[1][0] 给您123。 完美吗?不完全是,因为:

语句2 - 然而,这对您来说几乎没有什么用处:

虽然一开始可能不会这样,但是您可以使用np.random.seed(123),使用seed = np.random.get_state()[1][0]检索相同的数字,使用np.random.seed(444)重置种子,然后(表面上)使用np.random.seed(seed)将其设置回123的情况。 但是,您在此之前已经知道了您的随机种子,因此不需要以这种方式进行操作。下一个代码部分还将显示,您不能使用np.random.get_state()[1][0]获取任何随机状态的第一个数字并期望重新创建该确切情况。请注意,您可能需要完全关闭和重新启动内核(或调用np.random.seed(None)才能看到这一点。

以下代码段使用np.random.randint()生成5个介于-10和10之间的随机整数,并存储有关该过程的一些信息:

代码段1

# 1. Imports
import pandas as pd
import numpy as np

# 2. set random seed
#seedSet = None
seedSet = 123
np.random.seed(seedSet)

# 3. describe random state
state = np.random.get_state()
state5 = np.random.get_state()[1][:5]
seedState = np.random.get_state()[1][0]

# 4. generate random numbers
random = np.random.randint(-10, 10, size = 5)

# 5. organize and present findings
df = pd.DataFrame.from_dict({'seedSet':seedSet, 'seedState':seedState, 'state':state, 'random':random})
print(df)
注意到名为seedState的列与state下的第一个数字相同。我本可以将其作为独立的数字打印出来,但我想把它们放在同一个地方。另请注意,seedSet = 123np.random.seed(seedSet)目前已被注释掉了。因为没有设置随机种子,所以你的数字会与我的不同。但重要的不是这个,而是你的结果内部一致性。
   random seedSet   seedState       state
0       2    None  1558056443  1558056443
1      -1    None  1558056443  1808451632
2       4    None  1558056443   730968006
3      -4    None  1558056443  3568749506
4      -6    None  1558056443  3809593045
在这个特定的情况下,seed = np.random.get_state()[1][0]等于1558056443。按照Dong Justin回答的逻辑(以及我在编辑之前的回答),您可以使用np.random.seed(1558056443)设置随机数种子并获得相同的随机状态。下面的代码片段将说明您不能

代码片段2

# 1. Imports
import pandas as pd
import numpy as np

# 2. set random seed
#seedSet = None
seedSet = 1558056443
np.random.seed(seedSet)

# 3. describe random state
#state = np.random.get_state()
state = np.random.get_state()[1][:5]
seedState = np.random.get_state()[1][0]

# 4. generate random numbers
random = np.random.randint(-10, 10, size = 5)

# 5. organize and present findings
df = pd.DataFrame.from_dict({'seedSet':seedSet, 'seedState':seedState, 'state':state, 'random':random})
print(df)

输出2:

   random     seedSet   seedState       state
0       8  1558056443  1558056443  1558056443
1       3  1558056443  1558056443  1391218083
2       7  1558056443  1558056443  2754892524
3      -8  1558056443  1558056443  1971852777
4       4  1558056443  1558056443  2881604748

看到区别了吗?np.random.get_state() [1] [0]在输出1和输出2中是相同的,但其余的输出不同(最重要的是随机数不同)。因此,正如ali_m已经清楚地指出:

因此,将每个RNG状态映射到唯一整数种子是不可能的。


5
TL;DR:中间随机状态无法恢复(例如生成 5 个数字后的状态)。写得很好。 - OverLordGoldDragon
请检查代码和输出。在片段1中,seedSet 应该是 None,而 state5 应该是 state。数据框的列顺序与输出不一致。文本中说“seed=123”已被注释掉……实际上没有,并且无论如何也没有使用。在片段2中,为了清晰起见,应该删除注释语句(语句 state = np.random.get_state() 不应该出现在代码中)。 - marsipan
1
实际上,我得到了完全相反的结果;通过 np.random.seed 设置的原始随机状态在生成数字后无法恢复,但是中间状态(_当前状态_)可以。 - OverLordGoldDragon

5

了解随机种子的一个简单解决方案是随机生成一个,然后将其作为随机数生成器的种子。可以使用以下代码:

import numpy as np
seed = int(np.random.rand() * (2**32 - 1))
np.random.seed(seed)

1
正是我所需要的,可以保存一个出现意外行为的随机状态。 - Hunaphu

2
这个答案补充了其他人忽略的重要细节。首先,为了换句话说结论:

原始随机种子(通过np.random.seed设置)不能在生成数字后被恢复,但中间状态(当前状态)可以。

请参考@vestland的回答;然而,它可能会误导:生成的数字之所以不同,并非由于无法映射状态,而是使用了不完整的编码get_state()[1]。完整的表示包括pos = get_state()[2]。为了说明这一点:
import numpy as np

state0 = np.random.get_state()
rand0  = np.random.randint(0, 10, 1)
state1 = np.random.get_state()
rand1  = np.random.randint(0, 10, 1)

assert all(s0 == s1 for s0, s1 in zip(state0[1], state1[1]))

我们生成了一个数字,但是 get_state()[1] 保持不变。然而:

np.random.set_state(state0)
assert np.random.randint(0, 10, 1) == rand0

对于state1rand1同样如此。因此,@vestland的数字不同,因为当没有设置种子时,pos = 623 - 而如果我们使用np.random.seedpos = 624。为什么存在这种不方便的差异?没有头绪。


总之,关于np.random.seed(s)

  • 在设置后立即使用get_state()[1][0]:检索精确重现状态的s
  • 在生成数字后使用get_state()[1][0]:可能会检索到s,但它将无法重新创建当前状态(在get_state()处)
  • 在生成许多数字后使用get_state()[1][0]:将无法检索到s。这是因为pos耗尽了其表示。
  • 在任何时候使用get_state():将精确地重新创建那个点。

最后,行为也可能因get_state()[3:](当然还有[0])而有所不同。


2

检查np.random.get_state()返回的数组的第一个元素,它看起来就像是随机种子。


3
是的,尽管没有明确说明,但已经提供的答案很可能就是通过这种方式实现其所能达到的效果。 - Mast

1
虽然顶部答案通常是正确的,即一般情况下不可能,但实际上是可能的。我建议您访问此人的博客:https://kamila.akagi.moe/posts/mersenne-twister/。该个人开发了一个Mersenne Twister破解算法以恢复初始种子,并提供了详细的算法细节。我并非作者,并且不完全理解该材料,但任何有兴趣尝试此操作的人都应该查看此内容。

3
请提供更多细节以扩展您的答案,例如工作代码或文档引用。 - Community

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