如何生成可重复的随机数序列?

21

我需要一个函数来生成一个伪随机序列,但要求每次运行都能重复。希望生成的数据在给定区间内有一个合理的随机分布,不一定是完美的。

我想编写一些代码,对基于随机数据的性能测试进行运行。我希望每个测试运行在每台机器上都使用相同的数据,但出于存储原因,我不希望在测试中包含随机数据(可能会占用很多兆字节的空间)。

random 模块的库似乎没有说明在任何机器上使用相同的种子将始终产生相同的序列。

编辑:如果你要建议我使用种子数据(如我上面所说),请提供可以证明该方法在各种机器和实现上有效的文档。

编辑:在 Mac OS X 上,CPython 2.7.1 和 PyPy 1.7 以及 Ubuntu 上的 CPython 2.7.1 和 CPython 2.52=.2 看起来给出了相同的结果。尽管如此,仍然没有明确规定这一点的文档。

有什么好的想法吗?


3
你尝试过用给定的种子生成多个序列吗? - user684934
我只有一台电脑和一个操作系统,所以无法可靠地测试这个。 - Joe
我认为根本问题是“为什么?”如果是密码,那么这是一个非常糟糕的想法,不要这样做。你必须写下“为什么”。 - theWalker
1
@skippy:请仔细阅读他的问题。他明确表示他想要基于随机数据进行性能测试,这是非常合理的要求。 - DSM
只是一个快速的评论,为了避免其他人犯我这个新手的错误:random.seed()可以使随机数可重复,但是只有在输入数据也相同时才会看到相同的结果。听起来很明显,但一定要检查它。 - Stephen
9个回答

25
为了达到这个目的,我使用了重复的MD5哈希,因为哈希函数的意图是跨平台的一对一转换,所以在不同的平台上它始终是相同的。
import md5

def repeatable_random(seed):
    hash = seed
    while True:
        hash = md5.md5(hash).digest()
        for c in hash:
            yield ord(c)

def test():
    for i, v in zip(range(100), repeatable_random("SEED_GOES_HERE")):
        print v

输出:

184 207 76 134 103 171 90 41 12 142 167 107 84 89 149 131 142 43 241 211 224 157 47 59 34 233 41 219 73 37 251 194 15 253 75 145 96 80 39 179 249 202 159 83 209 225 250 7 69 218 6 118 30 4 223 205 91 10 122 203 150 202 99 38 192 105 76 100 117 19 25 131 17 60 251 77 246 242 80 163 13 138 36 213 200 135 216 173 92 32 9 122 53 250 80 128 6 139 49 94

基本上,该代码将使用您的种子(任何有效字符串)进行重复哈希,从而生成从0到255的整数。


1
那是一个绝妙的想法! - Joe
它会在 Python 版本之间保留吗? - Muhammad Yasirroni
1
@MuhammadYasirroni,是的,绝对是因为它依赖于MD5算法。MD5旨在验证文件的完整性,因此我甚至希望这种方法可以在不同的语言中保持一致。 - DrRobotNinja

12

不同平台之间存在差异,因此如果你需要在不同平台之间移植代码,建议使用DrRobotNinja所描述的方法。

请看下面的例子。我的桌面机器上的Python(64位Ubuntu,Core i7,Python 2.7.3)给了我以下结果:

> import random
> r = random.Random()
> r.seed("test")
> r.randint(1,100)
18

但是,如果我在我的树莓派上运行相同的代码(在ARM11上运行的Raspbian),我会得到一个不同的结果(对于相同版本的Python)。

> import random
> r = random.Random()
> r.seed("test")
> r.randint(1,100)
34

我不确定这是否是一种已记录的行为。当 Python 标准库的大部分设计为跨平台工作时,这似乎很奇怪,它应该是与平台相关的。也许我应该向 Python 团队报告一个 bug? - Joppe
这是个好主意(在查看了错误报告后)。如果不是一个错误,那么一定会有一个很好的解释。 - Joe
我在两个Ubuntu副本上遇到了同样的问题。它们都是相同的Linux版本,相同的Python版本(2.7.3),相同的GCC版本。但是,一个是32位的,另一个是64位的。64位机器给出与您的64位版本相同的结果(18),而我的32位机器给出与您的Pi相同的结果(34)。这一定是32/64位的问题。是否曾经创建过错误报告? - Tom17
不好意思,我很遗憾从未提交过任何错误报告。对此感到抱歉。 - Joppe
4
当我回答这个问题时,我没有在回答中添加链接,所以在这里提供链接:https://dev59.com/YWox5IYBdhLWcg3wtmc1#26592047。基本上,问题在于您没有用整数初始化生成器,而是用其他一些值来进行哈希处理(这是平台相关的部分)。 - causa prima

8
如果随机数的质量不像跨平台重复性那样关键,您可以使用传统的线性同余生成器之一: 线性同余生成器
class lcg(object):
    def __init__( self, seed=1 ):
        self.state = seed

    def random(self):
        self.state = (self.state * 1103515245 + 12345) & 0x7FFFFFFF
        return self.state

由于在您的程序中使用了整数算术,因此它应该在任何合理的平台上具有确定性可重复性。


1
很棒,我会看看的。 - Joe

7

同样,解释为什么这个答案的示例在不同的机器上会产生不同的输出:

这是因为当种子随机生成器时,种子必须是整数。如果您使用某些非整数来设置种子,则必须先对其进行哈希处理。哈希函数本身不是平台无关的(显然至少不是全部,如果您知道更多,请纠正我)。

因此,综合起来:Python使用伪随机数生成器。因此,从相同状态开始时,生成的随机数序列将始终相同,与平台无关。它只是一个确定性算法,没有来自外部世界的进一步输入。

这意味着:只要您使用相同的状态初始化随机生成器,它将生成相同的数字序列。可以使用相同的整数种子或保存和重新应用旧状态(random.getstate()和random.setstate())来达到相同的状态。


1
这似乎是一个真实的,尽管不完全的答案,并且肯定增加了信息。如果我是你,我会删除顶部的道歉! - Joe
1
它最初很短,但发展成了一个详细的答案,所以你是正确的,我已经删除/重新表述了它。 - causa prima

7

1
这正是我所想的,但正如我在问题中所说的那样,我找不到任何支持这一点的文档。 - Joe
@Oleksi - 仅限于此操作系统上的Python实现。我的要求是它在不同的实现中表现相同(首先,文档似乎表明随机种子是在C模块中生成的。那PyPy呢?) - Joe
3
@Joe,这是因为这是种子的正式定义的一部分,它没有伪随机算法会在相同的种子下给出不同的结果,这是不可能的。我认为他们可以提到它,但他们可能认为这对于每个人来说都是显而易见的。 - Voo
据我所知,所有的伪随机数生成器都保持这个特性,即相同的种子将生成相同的随机数集合。这似乎是用于生成质数的基础算法的一个特性。 - Oleksi
3
我和其他人一样只有猜测,并且来到这里是因为我无法用事实支持我的假设。 我们能保证总是使用同样的算法吗? - Joe

6
文档没有明确说明提供种子将始终保证相同的结果,但基于Python使用的算法,这在Python的随机实现中是有保证的。
根据文档,Python使用Mersenne Twister作为核心生成器。一旦该算法被种子化,它就不会获得任何会改变后续调用的外部输出,因此给它相同的种子,您获得相同的结果。
当然,您也可以通过设置种子并生成大量的随机数列表来验证它们是否相同,但我理解不想仅凭此来信任它。
我没有检查除CPython以外的其他Python实现,但我非常怀疑它们会使用完全不同的算法来实现随机模块。

1
这正是我所想的。我可能最终会选择这个最不糟糕的解决方案。 - Joe
即使他们使用完全不同的算法,如果您将相同的种子提供给相同的伪随机算法,它将输出相同的数字序列 - 如果您想在使用不同算法的两个不同Python实现上进行测试,那么您可能会遇到问题,但仅此而已。但据我所了解,文档保证了底层算法,所以一切都很好。 - Voo
我想到了一个问题,如果你使用32位的Mersenne Twister和64位的Mersenne Twister,你可能会得到不同的结果。 - DrRobotNinja

5
使用 random.seed(...) 可以生成可重复的序列。以下是一个示例演示:
import random

random.seed(321)
list1 = [random.randint(1,10) for x in range(5)]

random.seed(321)
list2 = [random.randint(1,10) for x in range(5)]

assert(list1==list2)

这是因为 random.seed(...) 不是真正的随机数:它是伪随机数,通过对某个状态机进行排列来产生连续的数字,给定一个初始的起始条件即“种子”。

使用 random.Random 类代替上面的方法,因为前者会改变模块级别的 seed,如果你的代码从其他地方调用 randint,那么你可能会遇到麻烦。 - Murali KG

1
我刚刚尝试了以下内容:
import random
random.seed(1)
random.random()
random.random()
random.random()

random.seed(1)
random.random()
random.random()
random.random()

我在CLI中以不同的速度输入了每一行,多次进行测试。每次都产生相同的值。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接