【问题标题】:Portability and reproducibility of RNG techniquesRNG 技术的可移植性和可重复性
【发布时间】:2019-11-20 19:51:58
【问题描述】:

我可以使用两种方法中的一种来创建具有两个重要特征的伪随机数序列 - (1) 它可以在不同的机器上重现,以及 (2) 序列从不重复范围内的数字,直到所有数字都被发出.

我的问题是 - 这些方法中的任何一种在可移植性(操作系统、Python 版本等)方面是否存在潜在问题?例如,有谁知道当 XXX 为真时,我是否会在一个系统上得到一组结果,而在另一个系统上得到不同的结果?

我并不是真的在寻求关于使用哪种方法本身的建议,只有当 Z 为真时我应该注意 Y 系统上的 X。

我尝试了几个版本的 Linux,都是 64 位的,它们看起来是一致的,但我无法轻松访问 Windows 或 32 位版本。

请注意,它们不会产生彼此相同的范围,但这对于我的目的来说是可以的。这些数字在人眼看来是随机的。

方法 1
使用原生 Python 库函数从一个范围内生成随机样本。如果我使用大范围(10m 或更大)会很慢,但对于相对较小的范围来说还可以,而且对于没有数学学位的人来说更容易理解:

import random
random.seed(5)
x = random.sample(range(10000,99999),89999)
for i in range(10):
   print(x[i])

方法 2
使用不是来自 Python 库的算法:
(https://en.wikipedia.org/wiki/Linear_congruential_generator)
即使在大范围内它也非常快,但更难理解并因此发现潜在问题:

def lcg(modulus, a, c, seed):
  while True:
    seed = (a * seed + c) % modulus
    yield seed


m = 10000019
c = int(m/2)
a = 5653
s = a

g = lcg(m,a,c,s)
for _ in range(10):
  print(next(g))

请注意,我对替代方案持开放态度;最初的问题是在这里提出的:https://math.stackexchange.com/questions/3289084/generate-a-pseudo-random-predictable-non-repeating-integer-sequence-purely-math

【问题讨论】:

  • @Dukeling - RNG 只需对人类有益(即看起来像是随机的),只要它不重复并且可以在机器之间复制。我想这个问题是模糊的,我猜可移植性问题可能包括事情是如何在引擎盖下工作的,这可能会使一台机器上的种子与另一台机器上的行为不同。我倾向于发现其他更有经验的人只是“知道”这些事情,所以我认为值得一问。我当然会尽可能多地测试。
  • 只使用特定的RNG。如果您的 LCG 满足您的需求,请使用它,没有理由担心它。 python 和几乎所有其他编程语言都是关于可移植性的,如果它们的行为在机器之间发生变化,它们将没有多大用处。

标签: python algorithm math random


【解决方案1】:

最便携的版本,IMO,将是 LCG,其周期等于机器的自然字长。它对模块使用寄存器溢出,使其更快。你必须使用 NumPy 数据类型来做到这一点,这里是简单的代码,常量 a、c 取自表 4 here

import numpy as np

def LCG(seed: np.uint64, a: np.uint64, c: np.uint64) -> np.uint64:
    with np.errstate(over='ignore'):
        while True:
            seed = (a * seed + c)
            yield seed

a = np.uint64(2862933555777941757)
c = np.uint64(1)

rng64 = LCG(np.uint64(17), a, c)

print(next(rng64))
print(next(rng64))
print(next(rng64))

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总结:

  1. 有了良好的常数(参见 L'Ecuyer 论文的参考资料),它将覆盖整个 [0...264) 范围而不会重复。基本上完美的 [0...264) -> [0...264) 映射,你可以设置 0,1,2,3,...作为输入并获得全范围输出
  2. 它是可逆的,你可以取回以前的种子,所以映射实际上是 双射,[0...264) [0...264)。详情请见Reversible pseudo-random sequence generator
  3. 它有对数向前和向后跳跃,所以找到没有问题 并行计算的合适间隔 - 从单个种子开始,然后下一个线程将被跳过(种子,264/N),下一个线程跳过(种子,264/ N * 2) 等等。保证不重叠
  4. 它简单快速,虽然不是一个非常高质量的 RNG

【讨论】:

  • “我可以为您提供代码来做到这一点” - 是的,请!我发现这整个主题很有趣。有没有办法限制最大值。你的例子中的数字长度?我正在尽可能将其保持在 7 位数。
  • @DavidWylie 请检查更新。不确定way to restrict the max. digit length。如果您在谈论 LCG 中的乘数,它取自论文并且已知具有良好的 FoM。您可以将代码转换为 32 位 LCG,只需将 np.uint64 替换为 np.uint32 即可,然后从 prof.L'Ecuyer 论文中为 ac 选择合适的值,以及 LCG 常量(s) 会更小
【解决方案2】:

算法(尤其是伪随机数生成器)可以通过多种方式在计算机上使用inconsistent results。最值得注意的方式是算法是否依赖浮点数(几乎总是有限精度)和浮点舍入。

某些编程语言比其他语言更容易出现可移植性问题。例如,与 Python 不同,C 或 C++ 中可能会出现不一致的结果,原因是——

  • 有符号整数溢出的未定义行为,或
  • 某些数据类型的长度(尤其是 intlong)在不同编译器中的定义方式不同。

我不知道方法 2 中的 Python 代码会以何种方式在计算机之间提供不一致的结果。另一方面,方法 1 是否可以这样做取决于 random.sample 在您关心的所有 Python 版本和所有计算机上是否以相同的方式实现。

【讨论】:

    【解决方案3】:

    LCG 不错。如果你想让 LCG 更容易理解,你可以递归地而不是迭代地实现它,以突出它所基于的递归公式。仅当您不太担心复杂性时才这样做。

    否则,我认为方法 2 对于 PRNG 来说已经足够清晰了。

    【讨论】:

    • 但是它们都是便携的吗?我将编辑问题以更好地定义,但我的意思是 python 版本、操作系统类型和版本等。
    • 我不知道随机库是如何工作的(它是基于 OS 原语吗?它基于什么 PRNG?)。解决方案 2 适用于任何当前的 python 版本 2.7 和 3,以及任何具有 python 的操作系统。
    猜你喜欢
    • 2015-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-02
    • 1970-01-01
    • 1970-01-01
    • 2013-05-16
    相关资源
    最近更新 更多