NumPy参考 >例行程序 >Random sampling (numpy.random) > Parallel Applications
实施了三种策略,可用于在多个进程(本地或分布式)之间生成可重复的伪随机数。
SeedSequence
产卵¶SeedSequence
实现了一种算法,用于处理用户提供的种子(通常为某种大小的整数),并将其转换为的初始状态BitGenerator
。它使用散列技术来确保将低质量的种子转换为高质量的初始状态(至少具有很高的概率)。
例如,MT19937
具有由624个uint32整数组成的状态
。一种简单的获取32位整数种子的方法是将状态的最后一个元素设置为32位种子,其余的保留为0。这是的有效状态MT19937
,但不是好状态。如果0太多,则 Mersenne Twister算法会受苦。同样,两个相邻的32位整数种子(即12345
和12346
)将产生非常相似的流。
SeedSequence
通过使用具有良好雪崩特性的连续整数散列来避免这些问题,以确保翻转输入输入中的任何位都有大约50%的机会翻转输出中的任何位。彼此非常接近的两个输入种子将产生彼此非常远离的初始状态(可能性非常高)。它还以可以提供任意大小的整数或整数列表的方式构造。
SeedSequence
将使用您提供的所有位并将它们混合在一起,以产生许多消耗BitGenerator
自身初始化所需的位。
这些属性一起意味着我们可以将用户提供的常规种子与简单的递增计数器安全地混合在一起,以获得BitGenerator
(非常有可能)彼此独立的状态。我们可以将它们包装到一个易于使用且难以滥用的API中。
from numpy.random import SeedSequence, default_rng
ss = SeedSequence(12345)
# Spawn off 10 child SeedSequences to pass to child processes.
child_seeds = ss.spawn(10)
streams = [default_rng(s) for s in child_seeds]
子SeedSequence
对象也可以生成孙子,等等。每个
对象在SeedSequence
生成SeedSequence
对象树中的位置都与用户提供的种子混合在一起,以生成独立的(很有可能)流。
grandchildren = child_seeds[0].spawn(4)
grand_streams = [default_rng(s) for s in grandchildren]
此功能使您可以在何时以及如何拆分流的情况下做出本地决策,而无需在流程之间进行协调。您不必预先分配空间来避免重叠或从通用全局服务请求流。这种通用的“树哈希”方案不是numpy独有的,但尚未普及。Python具有越来越灵活的并行化机制,该方案非常适合这种使用。
使用这种方案,如果知道您派生的流的数量,就可以估算出发生冲突的可能性的上限。SeedSequence
默认情况下,将其输入(种子和spawn-tree-path)散列到一个128位池中。的概率,有在该池中的碰撞,悲观估计(1),将在约其中
Ñ是衍生的流的数目。如果一个程序使用激进的一百万个流,大约为,则它们中至少一对是相同的概率为,大约在可忽略的范围内(2)。
该算法经过精心设计,消除了多种可能的碰撞方式。例如,如果仅执行一个级别的生成,则可以确保所有状态都是唯一的。但是,更容易估计餐巾纸上的天真上限,并知道该概率实际上较低,这会让您感到宽慰。
在此计算中,我们可以忽略从每个流中抽取的数字数量。我们提供的每个PRNG都有一些内置的额外保护,如果SeedSequence
池的差别很小,就可以避免重叠。除了每个周期的长周期位置外,种子PCG64
还具有单独的
周期,因此必须同时进入或接近同一周期,并在该周期附近播种一个位置。
Philox
具有完全独立的循环,取决于种子。
SFC64
包含一个64位计数器,因此每个唯一种子至少与其他任何种子都没有迭代。最后,MT19937
只有一段难以想象的巨大时期。内部发生碰撞SeedSequence
是观察故障的方式。
Philox
是基于计数器的RNG,它通过使用弱密码基元对增量计数器进行加密来生成值。种子确定用于加密的密钥。唯一键创建唯一,独立的流。Philox
使您可以绕过播种算法来直接设置128位密钥。类似但不同的键仍将创建独立的流。
import secrets
from numpy.random import Philox
# 128-bit number as a seed
root_seed = secrets.getrandbits(128)
streams = [Philox(key=root_seed + stream_id) for stream_id in range(10)]
此方案确实要求您避免重用流ID。这可能需要并行进程之间的协调。
jumped
如果已绘制了大量随机数,则推进BitGenerator的状态,并返回具有此状态的新实例。具体的抽奖次数因BitGenerator而异,范围从
到。此外,按条件抽奖还取决于特定BitGenerator产生的默认随机数的大小。下面列出了支持的BitGenerator jumped
,以及BitGenerator 的周期,跳转的大小和默认无符号随机数中的位。
比特生成器 |
期 |
跳跃大小 |
位 |
---|---|---|---|
MT19937 |
32 |
||
PCG64 |
(3) |
64 |
|
菲洛克斯 |
64 |
跳跃大小是其中是黄金比例。随着周期周围的跃迁,相邻流之间的实际距离将逐渐变得小于跃迁大小,但是使用黄金比率这种方法是构造低差异序列的经典方法,该序列可以最佳地分散周期周围的状态。您将无法跳得足够远,无法使这些距离变得很小以至于在您的一生中重叠。
jumped
可用于生产长块,该长块应足够长而不会重叠。
import secrets
from numpy.random import PCG64
seed = secrets.getrandbits(128)
blocked_rng = []
rng = PCG64(seed)
for i in range(10):
blocked_rng.append(rng.jumped(i))
使用时jumped
,必须注意不要跳转到已使用的流。在上述示例中,以后无法使用
blocked_rng[0].jumped()
,因为它将与重叠blocked_rng[1]
。像独立流一样,如果此处的主进程希望通过跳转来分离10个以上的流,则它需要以开始,否则它将重新创建相同的流。另一方面,如果您精心构造流,那么可以保证流不会重叠。range(10, 20)