如何与子进程共享父进程的numpy随机状态?

时间:2018-03-19 21:28:21

标签: python python-3.x numpy scipy multiprocessing

我在程序开头设置了numpy随机种子。在程序执行期间,我使用multiprocessing.Process多次运行一个函数。该函数使用numpy随机函数绘制随机数。问题是Process获取当前环境的副本。因此,每个进程都独立运行,它们都以与父环境相同的随机种子开始。

所以我的问题是如何在父环境中与子进程环境共享numpy的随机状态?请注意,我希望将Process用于我的工作,并且需要使用单独的类并分别在该类中执行import numpy。我尝试使用multiprocessing.Manager来共享随机状态,但似乎事情没有按预期工作,我总是得到相同的结果。此外,如果我在drawNumpySamples内移动for循环或将其保留在main.py中,则无关紧要;我仍然无法得到不同的数字,随机状态总是一样的。这是我的代码的简化版本:

# randomClass.py
import numpy as np
class myClass(self):
    def __init__(self, randomSt):
        print ('setup the object')
        np.random.set_state(randomSt)
    def drawNumpySamples(self, idx)
        np.random.uniform()

在主文件中:

    # main.py
    import numpy as np
    from multiprocessing import Process, Manager
    from randomClass import myClass

    np.random.seed(1) # set random seed
    mng = Manager()
    randomState = mng.list(np.random.get_state())
    myC = myClass(randomSt = randomState)

    for i in range(10):
        myC.drawNumpySamples() # this will always return the same results

注意:我使用的是Python 3.5。我还在Numpy的GitHub页面上发布了一个问题。只需发送问题链接here以供将来参考。

4 个答案:

答案 0 :(得分:5)

即使你设法使这个工作,我认为它不会做你想要的。一旦你有多个进程并行地从相同的随机状态拉出,它们就不再确定它们每个进入状态的顺序,这意味着你的运行实际上不可重复。可能有办法,但这似乎是一个非常重要的问题。

同时,有一个解决方案可以解决你想要的问题和非确定性问题:

在产生子进程之前,请向RNG索取一个随机数,然后将其传递给孩子。然后孩子可以用这个数字播种。然后,每个孩子将具有与其他孩子不同的随机序列,但是如果您使用固定种子重新运行整个应用程序,则该子项具有相同的随机序列。

如果您的主要流程执行任何其他可能非确定性地依赖于子女执行的RNG工作,您需要按顺序为所有子流程预先生成种子,然后再拉动任何子流程。其他随机数。

正如发送者在评论中所指出的:如果你不需要多次不同的运行,只需要一次固定的运行,你甚至不需要从种子RNG中提取种子;只需使用从1开始的计数器并为每个新进程递增计数器,并将其用作种子。我不知道这是否可以接受,但如果可以接受,那就很难比这简单。

正如Amir在评论中指出的:更好的方法是每次生成一个新进程时绘制一个随机整数,并将该随机整数传递给新进程,以便用该整数设置numpy的随机种子。这个整数确实来自np.random.randint()

答案 1 :(得分:1)

每次获得随机数时,您需要更新Manager的状态:

import numpy as np
from multiprocessing import Manager, Pool, Lock

lock = Lock()
mng = Manager()
state = mng.list(np.random.get_state())

def get_random(_):
    with lock:
        np.random.set_state(state)
        result = np.random.uniform()
        state[:] = np.random.get_state()
        return result

np.random.seed(1)
result1 = Pool(10).map(get_random, range(10))

# Compare with non-parallel version
np.random.seed(1)
result2 = [np.random.uniform() for _ in range(10)]

# result of Pool.map may be in different order
assert sorted(result1) == sorted(result2)

答案 2 :(得分:1)

幸运的是,根据the documentation,您可以访问the complete state of the numpy random number generator using get_state并使用set_state重新设置它。生成器本身使用Mersenne Twister算法(请参阅the RandomState part of the documentation)。

这意味着你可以做任何你想做的事情,不管它是否效率完全是另一个问题。作为abarnert points out,无论你如何分享父母的状态 - 这可以使用Alex Hall's method,这看起来是正确的 - 你在每个孩子中的排序将取决于每个孩子从MT状态中抽取随机数的顺序机。

为每个孩子构建一个大的伪随机数池可能会更好,在开始时保存整个生成器的开始状态。然后每个孩子都可以绘制一个PRNG值,直到它的特定池用完为止,之后你将孩子与下一个池的父级坐标。父母列举哪些孩子得到哪个“池”号。代码看起来像这样(请注意,使用next方法将其转换为无限生成器是有意义的):

class PrngPool(object):
    def __init__(self, child_id, shared_state):
        self._child_id = child_id
        self._shared_state = shared_state
        self._numbers = []

    def next_number(self):
        if not self.numbers:
            self._refill()
        return self.numbers.pop(0)  # XXX inefficient

    def _refill(self):
        # ... something like Alex Hall's lock/gen/unlock,
        # but fill up self._numbers with the next 1000 (or
        # however many) numbers after adding our ID and
        # the index "n" of which n-through-n+999 numbers
        # we took here.  Any other child also doing a
        # _refill will wait for the lock and get an updated
        # index n -- eg, if we got numbers 3000 to 3999,
        # the next child will get numbers 4000 to 4999.

这样,通过Manager项目(MT状态和我们的ID-and-index添加到“used”列表)的通信几乎没有。在过程结束时,可以查看哪些孩子使用了哪些PRNG值,并在需要时重新生成那些PRNG值(记得记录完整的MT内部开始状态!)。

编辑添加:考虑这个的方式是这样的:MT 实际上是随机的。这是一个很长一段时间的周期性。当您使用任何此类RNG时,您的种子只是该期间的起点。要获得可重复性,您必须使用 - 随机数,例如书中的集合。有一个(虚拟)书,每个数字都来自MT生成器。我们将写下我们用于每组计算的本书的哪个页面,以便我们可以在以后重新打开这些页面并重新进行相同的计算。

答案 3 :(得分:0)

您可以使用 np.random.SeedSequence。见https://numpy.org/doc/stable/reference/random/parallel.html

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]

这样,每个线程/进程都会得到一个统计独立的随机生成器。