我在程序开头设置了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以供将来参考。
答案 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]
这样,每个线程/进程都会得到一个统计独立的随机生成器。