我希望能够在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() = .548
和rng2.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版本中最终的无意义是什么,最后的2个数字是numpy版本。
答案 0 :(得分:7)
NumPy RandomState
州的形式为documented:
返回:out:tuple(str,ndarray of 624 uints,int,int,float)
返回的元组具有以下项目:
- 字符串'MT19937'。
- 624个无符号整数键的一维数组。
- 整数pos。
- 一个整数has_gauss。
- a float cached_gaussian。
醇>
最后两个条目是指标准正态偏差的生成器状态:NumPy uses Box–Muller transform,它们成对生成这些偏差。因此,第一次调用高斯生成器会产生两个值,返回第一个值,然后将第二个值存储起来供以后使用。然后第二个调用检索第二个值。因此,我们在这里有额外的状态,这是存储和检索所必需的。
Python Random
状态的形式未记录,但很容易从source中提取。从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()
可用。
要了解pos
返回的内容,您需要深入了解C source:它是一个长度为625的元组,其中包含形成Mersenne Twister州的624个单词,以及该词汇集中的当前位置。因此,该元组中的最后一个条目对应于NumPy状态的索引2处的值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))
。
这是一个从Python状态转换为NumPy状态的示例,忽略了高斯信息的细节:
RandomState
从Python Random
状态设置NumPy >>> 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)
状态后,我们看到从两个RNG生成的浮点数重合:
>>> _, 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
和以前一样,我们可以检查两个生成器的输出是否匹配:
has_gauss
Python和NumPy使用不同的算法来生成正常偏差(尽管两种算法都会成对地生成这些偏差),因此即使我们转移高斯相关状态,我们也不能期望生成的正常偏差匹配。但是如果您想要做的只是以某种方式保留NumPy状态对象中的Python状态信息(反之亦然),那么从一个状态转换到另一个状态并再次返回并不会丢失信息,那就是很容易做到:如果NumPy状态中None
为零,则使用has_gauss
作为Python状态的最后一个条目,如果cached_gaussian
非零,则使用{的值{1}}来自Python状态的最后一个条目中的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