3

我希望能够在 Python 的标准 Random 和 numpy 的 np.random.RandomState 之间来回转换。这两个都使用 Mersenne Twister 算法,所以应该是可能的(除非他们使用该算法的不同版本)。

我开始研究这些对象的 getstate/setstate 和 get_state/set_state 方法。但我不确定如何转换它们的细节。

import numpy as np
import random

rng1 = np.random.RandomState(seed=0)
rng2 = random.Random(seed=0)

state1 = rng1.get_state()
state2 = rng2.getstate()

检查我看到的每个状态:

>>> print(state1) 
('MT19937', array([0, 1, 1812433255, ..., 1796872496], dtype=uint32), 624, 0, 0.0)
>>> print(state2) 
(3, (2147483648, 766982754, ..., 1057334138, 2902720905, 624), None)

第一个状态是一个大小为 5 的元组,带有len(state1[1]) = 624.

第二个状态是一个大小为 3 的元组len(state2[1]) = 625。看起来 state2 中的最后一项实际上是 state1 中的 624,这意味着数组实际上是相同的大小。到目前为止,一切都很好。这些似乎相当兼容。

不幸的是,内部数字没有明显的对应关系,因此种子 0 会导致不同的状态,这是有道理的,因为rng1.rand() = .548rng2.random() = .844。所以,算法似乎略有不同。

但是,我不需要它们完美对应。我只需要能够确定地设置一个 rng 的状态而不影响第一个的状态。

理想情况下,一旦我使用第一个的状态设置第二个的状态,而不调用任何随机方法,然后使用第二个设置第一个的状态,第一个状态将保持不变,但这不是要求.

目前我有一个破解方法,它只是交换我可以从两个 rng 中提取的 624 长度列表。但是,我不确定这种方法是否有任何问题。任何对这个主题更了解的人都可以提供一些启示吗?

这是我的方法,但我不确定它是否正常工作。

np_rng = np.random.RandomState(seed=0)
py_rng = random.Random(0)

# Convert python to numpy random state (incomplete)
py_state = py_rng.getstate()
np_rng = np.random.RandomState(seed=0)
np_state = np_rng.get_state()
new_np_state = (
    np_state[0],
    np.array(py_state[1][0:-1], dtype=np.uint32),
    np_state[2], np_state[3], np_state[4])
np_rng.set_state(new_np_state)

# Convert numpy to python random state (incomplete)
np_state = np_rng.get_state()
py_rng = random.Random(0)
py_state = py_rng.getstate()
new_py_state = (
    py_state[0], tuple(np_state[1].tolist() + [len(np_state[1])]),
    py_state[1]
)
py_rng.setstate(new_py_state)

编辑:

做一些调查,我检查了 10 次调用随机函数时状态的变化。

np_rng = np.random.RandomState(seed=0)
py_rng = random.Random(0)

for i in range(10):
    np_rng.rand()
    npstate = np_rng.get_state()
    print([npstate[0], npstate[1][[0, 1, 2, -2, -1]], npstate[2], npstate[3], npstate[4]])

for i in range(10):
    py_rng.random()
    pystate = py_rng.getstate()
    print([pystate[0], pystate[1][0:3] + pystate[1][-2:], pystate[2]])


['MT19937', array([2443250962, 1093594115, 1878467924, 2648828502, 1678096082], dtype=uint32), 2, 0, 0.0]
['MT19937', array([2443250962, 1093594115, 1878467924, 2648828502, 1678096082], dtype=uint32), 4, 0, 0.0]
['MT19937', array([2443250962, 1093594115, 1878467924, 2648828502, 1678096082], dtype=uint32), 6, 0, 0.0]
['MT19937', array([2443250962, 1093594115, 1878467924, 2648828502, 1678096082], dtype=uint32), 8, 0, 0.0]
['MT19937', array([2443250962, 1093594115, 1878467924, 2648828502, 1678096082], dtype=uint32), 10, 0, 0.0]
['MT19937', array([2443250962, 1093594115, 1878467924, 2648828502, 1678096082], dtype=uint32), 12, 0, 0.0]
['MT19937', array([2443250962, 1093594115, 1878467924, 2648828502, 1678096082], dtype=uint32), 14, 0, 0.0]
['MT19937', array([2443250962, 1093594115, 1878467924, 2648828502, 1678096082], dtype=uint32), 16, 0, 0.0]
['MT19937', array([2443250962, 1093594115, 1878467924, 2648828502, 1678096082], dtype=uint32), 18, 0, 0.0]
['MT19937', array([2443250962, 1093594115, 1878467924, 2648828502, 1678096082], dtype=uint32), 20, 0, 0.0]
[3, (1372342863, 3221959423, 4180954279, 418789356, 2), None]
[3, (1372342863, 3221959423, 4180954279, 418789356, 4), None]
[3, (1372342863, 3221959423, 4180954279, 418789356, 6), None]
[3, (1372342863, 3221959423, 4180954279, 418789356, 8), None]
[3, (1372342863, 3221959423, 4180954279, 418789356, 10), None]
[3, (1372342863, 3221959423, 4180954279, 418789356, 12), None]
[3, (1372342863, 3221959423, 4180954279, 418789356, 14), None]
[3, (1372342863, 3221959423, 4180954279, 418789356, 16), None]
[3, (1372342863, 3221959423, 4180954279, 418789356, 18), None]
[3, (1372342863, 3221959423, 4180954279, 418789356, 20), None]

我希望每个元组中的第一项只是他们使用的算法的版本。

有趣的是,624 个整数似乎没有变化。总是这样吗?

但是,我仍然不确定 Python 版本中最后的 None 是什么意思,而最后的 2 个数字在 numpy 版本中是什么意思。

4

1 回答 1

9

NumPyRandomState状态的形式记录在案

返回: out : tuple(str, ndarray of 624 uints, int, int, float)

返回的元组有以下项目:

  1. 字符串“MT19937”。
  2. 由 624 个无符号整数键组成的一维数组。
  3. 一个整数 pos。
  4. 一个整数 has_gauss。
  5. 一个浮动缓存高斯。

那里的最后两个条目指的是标准正态偏差生成器的状态:NumPy 使用Box –Muller 变换,它成对生成这些偏差。所以对高斯生成器的第一次调用会产生两个值,返回第一个值,然后将第二个值存储起来以备后用。然后第二个调用检索第二个值。因此,我们在这里有额外的状态,需要存储和检索。

PythonRandom状态的形式没有记录,但很容易从源代码中提取。从 CPython 3.6.1 开始,它看起来像这样:

def getstate(self):
    """Return internal state; can be passed to setstate() later."""
    return self.VERSION, super().getstate(), self.gauss_next

同样,Python 会成对生成法线偏差,self.gauss_next如果None没有存储额外的法线偏差,则存储的偏差值(如果有可用的值)。

要找出super().getstate()返回的内容,您需要深入研究C 源代码:它是一个长度为 625 的元组,包含形成 Mersenne Twister 状态的 624 个单词,以及该单词集合中的当前位置。因此,该元组中的最后一个条目对应于posNumPy 状态的索引 2 处的值。

这是一个从 Python 状态转换为 NumPy 状态的示例,忽略高斯信息的细节:

Python 3.6.1 (default, May 23 2017, 18:09:41) 
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> import random
>>> np_rng = np.random.RandomState(seed=0)
>>> py_rng = random.Random(0)
>>> version, (*mt_state, pos), gauss_next = py_rng.getstate() 
>>> np_rng.set_state(('MT19937', mt_state, pos))

RandomState从 Python状态设置 NumPy状态后Random,我们看到两个 RNG 生成的浮点数重合:

>>> py_rng.random(), np_rng.uniform()
(0.8444218515250481, 0.8444218515250481)
>>> py_rng.random(), np_rng.uniform()
(0.7579544029403025, 0.7579544029403025)
>>> py_rng.random(), np_rng.uniform()
(0.420571580830845, 0.420571580830845)

这是反向转换:

>>> _, words, pos, _, _ = np_rng.get_state()
>>> py_rng.setstate((3, tuple(map(int, words)) + (pos,), None))

和以前一样,我们可以检查两个生成器的输出是否匹配:

>>> py_rng.random(), np_rng.uniform()
(0.5488135039273248, 0.5488135039273248)
>>> py_rng.random(), np_rng.uniform()
(0.7151893663724195, 0.7151893663724195)
>>> py_rng.random(), np_rng.uniform()
(0.6027633760716439, 0.6027633760716439)
>>> all(py_rng.random() == np_rng.uniform() for _ in range(1000000))
True

Python 和 NumPy 使用不同的算法来生成法线偏差(尽管使用的两种算法都成对地生成这些偏差),所以即使我们转移高斯相关状态,我们也不能指望生成的法线偏差会匹配。但是,如果您只想以某种方式将 Python 状态信息保存在 NumPy 状态对象中(反之亦然),以便从一种状态转换到另一种状态并再次转换回来不会丢失信息,那么这很容易做到:如果has_gauss在 NumPy 状态中为零,None用于 Python 状态的最后一个条目,如果has_gauss为非零,则在 Python 状态的最后一个条目中使用cached_gaussian来自 NumPy 状态的值。这是实现这些转换的一对函数:

PY_VERSION = 3
NP_VERSION = 'MT19937'

def npstate_to_pystate(npstate):
    """
    Convert state of a NumPy RandomState object to a state
    that can be used by Python's Random.
    """
    version, keys, pos, has_gauss, cached_gaussian = npstate
    pystate = (
        PY_VERSION,
        tuple(map(int, keys)) + (int(pos),),
        cached_gaussian if has_gauss else None,
    )
    return pystate


def pystate_to_npstate(pystate):
    """
    Convert state of a Python Random object to state usable
    by NumPy RandomState.
    """
    version, (*keys, pos), cached_gaussian = pystate
    has_gauss = cached_gaussian is not None
    npstate = (
        NP_VERSION,
        keys,
        pos,
        has_gauss,
        cached_gaussian if has_gauss else 0.0
    )
    return npstate
于 2017-06-01T17:57:44.453 回答