在并行程序中设置随机数生成器的种子

27
我正在学习Python的multiprocessing模块。 我有两个情况:
Ex. 1
def Foo(nbr_iter):
    for step in xrange(int(nbr_iter)) :
        print random.uniform(0,1)
...

from multiprocessing import Pool

if __name__ == "__main__":
    ...
    pool = Pool(processes=nmr_parallel_block)
    pool.map(Foo, nbr_trial_per_process)

例2(使用numpy)

 def Foo_np(nbr_iter):
     np.random.seed()
     print np.random.uniform(0,1,nbr_iter)

在这两种情况下,随机数生成器都是在它们的分叉进程中被初始化的。

为什么我在numpy示例中必须显式地初始化种子,而在Python示例中不需要?


请解释一下是什么让你认为你必须这样做。 - shx2
1
因为如果我不这样做,那么每个分叉的进程将生成相同的随机数序列(仅适用于Ex.2)。 - overcomer
shX2 操作系统是 Mac OS。 - overcomer
1
请参考这个出色的答案,它与此问题类似但并非重复:https://dev59.com/IW025IYBdhLWcg3wtofX#5837352 - Mike McKerns
1
@overcomer - numpy 1.17 刚刚推出了新选项(我在下面添加了一个答案),用于“实现可用于在多个进程之间生成可重复伪随机数的策略”。 - mork
显示剩余2条评论
3个回答

31
如果没有显式提供种子,numpy.random会使用操作系统相关的随机源来自动初始化种子。通常在基于Unix的系统上它会使用/dev/urandom(或一些Windows等价物),但如果由于某些原因该选项不可用,则它将从墙上时钟进行初始化。由于自我初始化发生在新子进程fork的时间,因此如果多个子进程在同一时间fork,则可能继承相同的种子,导致不同子进程生成相同的随机变量。
通常这会与您运行的并发线程数量相关联。例如:
import numpy as np
import random
from multiprocessing import Pool

def Foo_np(seed=None):
    # np.random.seed(seed)
    return np.random.uniform(0, 1, 5)

pool = Pool(processes=8)
print np.array(pool.map(Foo_np, xrange(20)))

# [[ 0.14463001  0.80273208  0.5559258   0.55629762  0.78814652] <-
#  [ 0.14463001  0.80273208  0.5559258   0.55629762  0.78814652] <-
#  [ 0.14463001  0.80273208  0.5559258   0.55629762  0.78814652] <-
#  [ 0.14463001  0.80273208  0.5559258   0.55629762  0.78814652] <-
#  [ 0.14463001  0.80273208  0.5559258   0.55629762  0.78814652] <-
#  [ 0.14463001  0.80273208  0.5559258   0.55629762  0.78814652] <-
#  [ 0.14463001  0.80273208  0.5559258   0.55629762  0.78814652] <-
#  [ 0.64672339  0.99851749  0.8873984   0.42734339  0.67158796]
#  [ 0.64672339  0.99851749  0.8873984   0.42734339  0.67158796]
#  [ 0.64672339  0.99851749  0.8873984   0.42734339  0.67158796]
#  [ 0.64672339  0.99851749  0.8873984   0.42734339  0.67158796]
#  [ 0.64672339  0.99851749  0.8873984   0.42734339  0.67158796]
#  [ 0.11283279  0.28180632  0.28365286  0.51190168  0.62864241]
#  [ 0.11283279  0.28180632  0.28365286  0.51190168  0.62864241]
#  [ 0.28917586  0.40997875  0.06308188  0.71512199  0.47386047]
#  [ 0.11283279  0.28180632  0.28365286  0.51190168  0.62864241]
#  [ 0.64672339  0.99851749  0.8873984   0.42734339  0.67158796]
#  [ 0.11283279  0.28180632  0.28365286  0.51190168  0.62864241]
#  [ 0.14463001  0.80273208  0.5559258   0.55629762  0.78814652] <-
#  [ 0.11283279  0.28180632  0.28365286  0.51190168  0.62864241]]

你可以看到,最多8个线程同时使用相同的种子派生出随机序列(我用箭头标记了第一组)。

在子进程中调用np.random.seed()会强制线程本地的RNG实例从/dev/urandom或墙钟重新获取种子,这将(可能)防止你从多个子进程中看到相同的输出。最佳实践是向每个子进程显式传递不同的种子(或numpy.random.RandomState实例),例如:

def Foo_np(seed=None):
    local_state = np.random.RandomState(seed)
    print local_state.uniform(0, 1, 5)

pool.map(Foo_np, range(20))

我不确定在这方面 randomnumpy.random 的差异背后的原因(也许在选择用于自行播种的随机源时有稍微不同的规则,与 numpy.random 相比?)。我仍然建议明确地向每个子进程传递一个种子或 random.Random 实例,以保险起见。您还可以使用 random.Random.jumpahead() 方法,该方法专门用于在多线程程序中洗牌 Random 实例的状态。


我想在子进程中共享父进程的numpy随机状态。我尝试使用Manager,但仍然没有成功。您能否请看一下我的问题[here](https://dev59.com/banka4cB1Zd3GeqPV-_8),并查看是否可以提供解决方案?如果每次生成随机数时都执行np.random.seed(None),我仍然可以获得不同的随机数,但这不允许我使用父进程的随机状态,这不是我想要的。非常感谢您的帮助。 - Amir
1
是的,这是一个很好的解释,对我非常有帮助。感谢@overcomer提出这个问题。 - max29

4

numpy 1.17 刚刚推出了 [quoting] "..三种策略可以用于在多个进程(本地或分布式)之间生成可重复的伪随机数.."。

第一种策略是使用SeedSequence对象。这里有许多父/子选项,但对于我们的情况,如果您希望在每次运行时获得相同的生成随机数,但每个运行都不同:

(python3,从4个进程中打印3个随机数)

from numpy.random import SeedSequence, default_rng
from multiprocessing import Pool

def rng_mp(rng):
    return [ rng.random() for i in range(3) ]

seed_sequence = SeedSequence()
n_proc = 4
pool = Pool(processes=n_proc)
pool.map(rng_mp, [ default_rng(seed_sequence) for i in range(n_proc) ])

# 2 different runs
[[0.2825724770857644, 0.6465318335272593, 0.4620869345284885],
 [0.2825724770857644, 0.6465318335272593, 0.4620869345284885],
 [0.2825724770857644, 0.6465318335272593, 0.4620869345284885],
 [0.2825724770857644, 0.6465318335272593, 0.4620869345284885]]

[[0.04503760429109904, 0.2137916986051025, 0.8947678672387492],
 [0.04503760429109904, 0.2137916986051025, 0.8947678672387492],
 [0.04503760429109904, 0.2137916986051025, 0.8947678672387492],
 [0.04503760429109904, 0.2137916986051025, 0.8947678672387492]]

如果您想要以复制目的获得相同结果,您可以使用相同的种子(17)重新生成numpy:
import numpy as np
from multiprocessing import Pool

def rng_mp(seed):
    np.random.seed(seed)
    return [ np.random.rand() for i in range(3) ]

n_proc = 4
pool = Pool(processes=n_proc)
pool.map(rng_mp, [17] * n_proc)

# same results each run:
[[0.2946650026871097, 0.5305867556052941, 0.19152078694749486],
 [0.2946650026871097, 0.5305867556052941, 0.19152078694749486],
 [0.2946650026871097, 0.5305867556052941, 0.19152078694749486],
 [0.2946650026871097, 0.5305867556052941, 0.19152078694749486]]

2
这里有一篇不错的博客文章,它将解释numpy.random的工作原理。
如果您使用np.random.rand(),它会采用导入np.random模块时创建的种子。因此,您需要在每个线程中手动创建一个新种子(例如,请参见博客文章中的示例)。
Python随机模块没有这个问题,并自动为每个线程生成不同的种子。

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