NXEP 4-采用 numpy.random.Generator 作为默认随机接口#

作者

罗斯·巴诺斯基(rossbar@berkeley.edu)

状态

草稿

类型

标准轨道

创建

2022-02-24

摘要#

伪随机数在NetworkX中的许多图和网络分析算法中扮演着重要的角色。NetworkX提供了 standard interface to random number generators 这包括支持 numpy.random and the Python built-in random module. numpy.random is used extensively within NetworkX and in several cases is the preferred package for random number generation. NumPy introduced a new interface in the numpy.random package in NumPy version 1.17. According to NEP19, the new interface based on numpy.random.Generator is recommended over the legacy numpy.random.RandomState as the former has better statistical propertiesmore features ,以及 improved performance 。该NXEP提出了采用以下策略的策略 numpy.random.Generator 作为 默认设置 用于在NetworkX内生成随机数的接口。

动机和范围#

采用的主要动机是 numpy.random.Generator 因为NetworkX中的默认随机数生成引擎允许用户受益于 numpy.random.Generator ,包括:-现代pRNG的统计质量的进步--改进的性能--其他特征

这个 numpy.random.Generator API非常类似于 numpy.random.RandomState API,因此用户无需任何额外更改即可从这些改进中受益 1 添加到他们现有的网络X代码中。

原则上,此更改将影响使用由装饰的任何功能的NetworkX用户 np_random_statepy_random_state (当 random_state 争论涉及到 numpy )。有关详细信息,请参阅下一部分。

1

中有关兼容层的说明。 Implementation section

使用和影响#

在NetworkX中,随机数生成器通常通过装饰符::

from networkx.utils import np_random_state

@np_random_state("seed")  # Or could be the arg position, i.e. 0
def foo(seed=None):
    return seed

修饰器负责将各种不同的输入映射到函数内的随机数生成器的实例中。目前,返回的随机数生成器实例是 numpy.random.RandomState 对象::

>>> type(foo(None))
numpy.random.mtrand.RandomState
>>> type(foo(12345))
numpy.random.mtrand.RandomState

想要获得一份 numpy.random.Generator 来自随机状态修饰符的实例是直接传入实例::

>>> import numpy as np
>>> rng = np.random.default_rng()
>>> type(foo(rng))
numpy.random._generator.Generator

此NXEP建议更改行为,以便在例如AND INTEGER或 None 是为 seed 参数,则为 numpy.random.Generator 实例,即::

>>> type(foo(None))
numpy.random._generator.Generator
>>> type(foo(12345))
numpy.random._generator.Generator

numpy.random.RandomState instances can still be used as seed, but they must be explicitly passed in:

>>> rs = np.random.RandomState(12345)
>>> type(foo(rs))
numpy.random.mtrand.RandomState

向后兼容性#

主要有三个方面的担忧:

  1. 这个 Generator 接口与流不兼容 RandomState ,因此, Generator 方法将不会与相应的 RandomState 方法。

  2. 在方法名称和可用性方面, RandomStateGenerator API接口。

  3. 没有全球性的 Generator 实例内部到 numpy.random 就像情况一样 numpy.random.RandomState

这个 numpy.random.Generator 接口打破了流兼容性保证 numpy.random.RandomState 坚持价值的精确再现性。将默认随机数生成器从 RandomStateGenerator 将意味着用 np_random_state 会产生不同的结果 不是实例化的RNG 被用作种子。例如,让我们采用以下函数:

@np_random_state("seed")
def bar(num, seed=None):
    """Return an array of `num` uniform random numbers."""
    return seed.random(num)

使用当前实现的 np_random_state ,用户可以将一个整数值传递给 seed 它将被用来播种新的 RandomState 举个例子。使用相同的种子值可确保输出始终完全可重现:

>>> bar(10, seed=12345)
array([0.92961609, 0.31637555, 0.18391881, 0.20456028, 0.56772503,
       0.5955447 , 0.96451452, 0.6531771 , 0.74890664, 0.65356987])
>>> bar(10, seed=12345)
array([0.92961609, 0.31637555, 0.18391881, 0.20456028, 0.56772503,
       0.5955447 , 0.96451452, 0.6531771 , 0.74890664, 0.65356987])

但是,在更改由返回的默认RNG之后 np_random_state 发送到 Generator 实例中,由修饰的 bar 整数种子的函数将不再相同::

>>> bar(10, seed=12345)
array([0.22733602, 0.31675834, 0.79736546, 0.67625467, 0.39110955,
       0.33281393, 0.59830875, 0.18673419, 0.67275604, 0.94180287])

为了恢复原始结果的精确重现性,种子 RandomState 实例将需要显式创建并通过 seed ::

>>> import numpy as np
>>> rng = np.random.RandomState(12345)
>>> bar(10, seed=rng)
array([0.92961609, 0.31637555, 0.18391881, 0.20456028, 0.56772503,
       0.5955447 , 0.96451452, 0.6531771 , 0.74890664, 0.65356987])

由于流将不再兼容,因此在此NXEP中建议仅在主要版本中考虑切换默认随机数生成器,例如从NetworkX 2.x到NetworkX 3.0的过渡。

第二点仅与正在使用 create_random_state 和相应的装饰者 np_random_state 在他们自己的图书馆里。例如, numpy.random.RandomState.randint 方法已被替换为 numpy.random.Generator.integers 。因此,任何使用 create_random_statecreate_py_random_state 并依赖于 randint 方法返回的RNG将导致 AttributeError 。这可以通过一个兼容性类来解决,类似于 networkx.utils.misc.PythonRandomInterface 类,该类在 randomnumpy.random.RandomState

create_random_state currently returns the global numpy.random.mtrand._rand RandomState instance when the input is None or the numpy.random module. By switching to numpy.random.Generator, this will no longer be possible as there is no global, internal Generator instance in the numpy.random module. This should have no effect on users, as seed=None currently does not guarantee reproducible results.

详细描述#

此NXEP建议更改由 create_random_state 函数(和相关的装饰符 np_random_state )从一个 numpy.random.RandomState 实例转换为 numpy.random.Generator 当函数的输入为整数或 None

实施#

实现本身非常简单。确定如何将输入映射到随机数生成器的逻辑封装在 create_random_state 函数(和相关的 create_py_random_state )。目前(即NetworkX<=2.x),此函数映射如下输入 Nonenumpy.random ,和整数到 RandomState 实例::

def create_random_state(random_state=None):
    if random_state is None or random_state is np.random:
        return np.random.mtrand._rand
    if isinstance(random_state, np.random.RandomState):
        return random_state
    if isinstance(random_state, int):
        return np.random.RandomState(random_state)
    if isinstance(random_state, np.random.Generator):
        return random_state
    msg = (
        f"{random_state} cannot be used to create a numpy.random.RandomState or\n"
        "numpy.random.Generator instance"
    )
    raise ValueError(msg)

该NXEP建议修改函数以产生 Generator 这些输入的实例。示例实现可能如下所示:

def create_random_state(random_state=None):
    if random_state is None or random_state is np.random:
        return np.random.default_rng()
    if isinstance(random_state, (np.random.RandomState, np.random.Generator)):
        return random_state
    if isinstance(random_state, int):
        return np.random.default_rng(random_state)
    msg = (
        f"{random_state} cannot be used to create a numpy.random.RandomState or\n"
        "numpy.random.Generator instance"
    )
    raise ValueError(msg)

尽管实现细节可能有所不同,但上述内容捕捉到了逻辑上的基本变化。与实施这一变化相关的大部分工作将与改进/重新组织的测试相关;包括增加测试RNG-Stream重现性。

选择#

现状,即使用 RandomState 默认情况下,是完全可接受的替代方案。 RandomState 不会被弃用,并有望永久保持其流兼容性保证。

另一种可能的替代方法是提供包级切换,用户可以使用该切换将行为切换到 seed 装饰的所有功能的Kwarg np_random_statepy_random_state 。要说明(忽略实施细节):

>>> import networkx as nx
>>> from networkx.utils.misc import create_random_state

# NetworkX 2.X behavior: RandomState by default

>>> type(create_random_state(12345))
numpy.random.mtrand.RandomState

# Change random backend by setting pkg attr

>>> nx._random_backend = "Generator"

>>> type(create_random_state(12345))
numpy.random._generator.Generator

讨论#

本节可能只是一个项目符号列表,其中包含有关NXEP的任何讨论的链接:

  • 这包括到邮件列表线程或相关GitHub问题的链接。