Python RNG技术的可移植性和再现性
我可以使用两种方法中的一种来创建一个伪随机数序列,该序列具有两个重要特征:(1)它在不同的机器上是可复制的,(2)该序列在所有数字发出之前不会重复范围内的数字 我的问题是-这两种方法在可移植性(OS、Python版本等)方面是否存在潜在问题?例如,有人知道当XXX为真时,我是否会在一个系统上得到一组结果,但在另一个系统上得到一组不同的结果吗? 我并不是在征求关于使用哪种方法的建议,只是在Z为真时我应该注意Y系统上的X 我试过几个版本的Linux,都是64位的,它们看起来是一致的,但我无法轻松访问Windows或32位版本 请注意,它们彼此产生的范围并不相同,但出于我的目的,这是可以的。这些数字在人眼看来必须是随机的 方法1Python RNG技术的可移植性和再现性,python,algorithm,math,random,Python,Algorithm,Math,Random,我可以使用两种方法中的一种来创建一个伪随机数序列,该序列具有两个重要特征:(1)它在不同的机器上是可复制的,(2)该序列在所有数字发出之前不会重复范围内的数字 我的问题是-这两种方法在可移植性(OS、Python版本等)方面是否存在潜在问题?例如,有人知道当XXX为真时,我是否会在一个系统上得到一组结果,但在另一个系统上得到一组不同的结果吗? 我并不是在征求关于使用哪种方法的建议,只是在Z为真时我应该注意Y系统上的X 我试过几个版本的Linux,都是64位的,它们看起来是一致的,但我无法轻松访问
使用本机Python库函数从一个范围生成一个随机样本。如果我使用较大的范围(10米或更大),速度会很慢,但相对较小的范围也可以,并且对于没有数学学位的人来说更容易理解:
import random
random.seed(5)
x = random.sample(range(10000,99999),89999)
for i in range(10):
print(x[i])
方法2使用的算法不是来自Python库:
()
即使在大范围内,速度也非常快,但更难理解,因此发现以下方面的潜在问题: def lcg(模数、a、c、种子): 尽管如此: 种子=(a*种子+c)%模量 出籽 m=10000019 c=int(m/2) a=5653 s=a g=lcg(m、a、c、s) 对于范围(10)内的uu: 打印(下一页(g))
注:我对其他选择非常开放;最初的问题在这里被问到:LCG很好。如果您想让LCG更容易理解,您可以递归地实现它,而不是迭代地实现它,以突出显示它所基于的递归公式。只有在你不太担心复杂性的情况下才去做
否则,我认为方法2对于PRNG来说已经足够清晰了。一个算法(特别是伪随机数生成器)可以在多台计算机上实现。最值得注意的方法是,如果算法依赖于浮点数(几乎总是有限精度)和浮点舍入 一些编程语言比其他语言更容易出现可移植性问题。例如,不像Python,由于-< /P>,C或C++中可能会出现不一致的结果。
- 有符号整数溢出的未定义行为,或
- 某些数据类型的长度,尤其是
和int
,在不同的编译器中是如何定义的long
我不知道方法2中的Python代码会以任何方式在计算机上传递不一致的结果。另一方面,方法1是否可以这样做取决于
random.sample
在您关心的所有Python版本和所有计算机上的实现方式是否相同。大多数可移植版本IMO将是周期等于机器自然字长的LCG。它对模块使用寄存器溢出,这使它更快。您必须使用NumPy数据类型来实现这一点,这里是简单的代码,常量a,c取自表4
Linux x64和Windows x64以及OS X VM的工作原理完全相同
关于再现性,唯一好的方法是存储前两个数字,并在应用程序初始化阶段将其与LCG输出进行比较-如果它们正常,则继续
我喜欢LCG的另一个特性是它能够在log2(N)时间内跳到前面,其中N是跳过的次数。我可以为您提供代码来实现这一点。使用提前跳转可以确保并行独立随机流的序列不重叠 更新 这里是我的C代码到Python/NumPy的翻译,看起来很有效。它可以在对数时间内向前和向后跳跃
import numpy as np
class LCG(object):
UZERO: np.uint64 = np.uint64(0)
UONE : np.uint64 = np.uint64(1)
def __init__(self, seed: np.uint64, a: np.uint64, c: np.uint64) -> None:
self._seed: np.uint64 = np.uint64(seed)
self._a : np.uint64 = np.uint64(a)
self._c : np.uint64 = np.uint64(c)
def next(self) -> np.uint64:
self._seed = self._a * self._seed + self._c
return self._seed
def seed(self) -> np.uint64:
return self._seed
def set_seed(self, seed: np.uint64) -> np.uint64:
self._seed = seed
def skip(self, ns: np.int64) -> None:
"""
Signed argument - skip forward as well as backward
The algorithm here to determine the parameters used to skip ahead is
described in the paper F. Brown, "Random Number Generation with Arbitrary Stride,"
Trans. Am. Nucl. Soc. (Nov. 1994). This algorithm is able to skip ahead in
O(log2(N)) operations instead of O(N). It computes parameters
A and C which can then be used to find x_N = A*x_0 + C mod 2^M.
"""
nskip: np.uint64 = np.uint64(ns)
a: np.uint64 = self._a
c: np.uint64 = self._c
a_next: np.uint64 = LCG.UONE
c_next: np.uint64 = LCG.UZERO
while nskip > LCG.UZERO:
if (nskip & LCG.UONE) != LCG.UZERO:
a_next = a_next * a
c_next = c_next * a + c
c = (a + LCG.UONE) * c
a = a * a
nskip = nskip >> LCG.UONE
self._seed = a_next * self._seed + c_next
np.seterr(over='ignore')
a = np.uint64(2862933555777941757)
c = np.uint64(1)
seed = np.uint64(1)
rng64 = LCG(seed, a, c) # initialization
print(rng64.next())
print(rng64.next())
print(rng64.next())
rng64.skip(-3) # back by 3
print(rng64.next())
print(rng64.next())
print(rng64.next())
rng64.skip(-3) # back by 3
rng64.skip(2) # forward by 2
print(rng64.next())
无论如何,LCG RNG的总结:
但是它们都是可移植的吗?我会编辑这个问题来更好地定义它,但我指的是python版本、OS类型和版本等。我不知道随机库是如何工作的(它是基于OS原语的吗?它基于什么PRNG?).Solution 2适用于任何当前的python版本2.7和3,以及任何具有python的操作系统。@Dukeling-RNG只需符合人性(即看起来像是随机的)只要它不重复,并且可以在机器之间复制。我想问题是模糊的,我想便携性问题可能包括如何在引擎盖下解决问题,这可能会使一台机器上的种子表现出与另一台不同的行为。我倾向于发现其他更有经验的人只是“知道”这些东西,所以我认为值得一问。我当然会尽可能多地测试。只要使用一个特定的RNG。如果你的LCG满足你的需要,那么就使用它,没有理由为此烦恼。python和几乎所有其他编程语言都是关于可移植性的,如果它们的行为在不同的机器之间发生变化,它们就没有多大用处。“我可以为您提供这样做的代码”-是的,请!我发现整个主题很吸引人。有没有办法限制您的示例中的最大位数长度?我正在尽可能将其限制为7位数。@DavidW
import numpy as np
class LCG(object):
UZERO: np.uint64 = np.uint64(0)
UONE : np.uint64 = np.uint64(1)
def __init__(self, seed: np.uint64, a: np.uint64, c: np.uint64) -> None:
self._seed: np.uint64 = np.uint64(seed)
self._a : np.uint64 = np.uint64(a)
self._c : np.uint64 = np.uint64(c)
def next(self) -> np.uint64:
self._seed = self._a * self._seed + self._c
return self._seed
def seed(self) -> np.uint64:
return self._seed
def set_seed(self, seed: np.uint64) -> np.uint64:
self._seed = seed
def skip(self, ns: np.int64) -> None:
"""
Signed argument - skip forward as well as backward
The algorithm here to determine the parameters used to skip ahead is
described in the paper F. Brown, "Random Number Generation with Arbitrary Stride,"
Trans. Am. Nucl. Soc. (Nov. 1994). This algorithm is able to skip ahead in
O(log2(N)) operations instead of O(N). It computes parameters
A and C which can then be used to find x_N = A*x_0 + C mod 2^M.
"""
nskip: np.uint64 = np.uint64(ns)
a: np.uint64 = self._a
c: np.uint64 = self._c
a_next: np.uint64 = LCG.UONE
c_next: np.uint64 = LCG.UZERO
while nskip > LCG.UZERO:
if (nskip & LCG.UONE) != LCG.UZERO:
a_next = a_next * a
c_next = c_next * a + c
c = (a + LCG.UONE) * c
a = a * a
nskip = nskip >> LCG.UONE
self._seed = a_next * self._seed + c_next
np.seterr(over='ignore')
a = np.uint64(2862933555777941757)
c = np.uint64(1)
seed = np.uint64(1)
rng64 = LCG(seed, a, c) # initialization
print(rng64.next())
print(rng64.next())
print(rng64.next())
rng64.skip(-3) # back by 3
print(rng64.next())
print(rng64.next())
print(rng64.next())
rng64.skip(-3) # back by 3
rng64.skip(2) # forward by 2
print(rng64.next())