种子Python RNG显示集合的非确定性行为

时间:2016-03-30 19:03:48

标签: python python-2.7 random set non-deterministic

当尝试从集合中选择伪随机元素时,我看到了非确定性行为,即使RNG是种子(示例代码如下所示)。为什么会发生这种情况,我是否应该期望其他Python数据类型显示出类似的行为?

注意:我只在Python 2.7上测试了这个,但它在两台不同的Windows计算机上都可以重现。

类似问题:Python random seed not working with Genetic Programming example code的问题可能类似。根据我的测试,我的假设是集合中的逐个运行的内存分配差异导致不同的元素被相同的RNG状态拾取。

到目前为止,我还没有在集合或随机的Python文档中提到过这种警告/问题。

示例代码(randTest生成不同的输出run-to-run):

import random

''' Class contains a large set of pseudo-random numbers. '''
class bigSet:
    def __init__(self):
        self.a = set()
        for n in range(2000):
            self.a.add(random.random())
        return


''' Main test function. '''
def randTest():
    ''' Seed the PRNG. '''
    random.seed(0)

    ''' Create sets of bigSet elements, presumably many memory allocations. ''' 
    b = set()
    for n in range (2000):
        b.add(bigSet())

    ''' Pick a random value from a random bigSet. Would have expected this to be deterministic. '''    
    c = random.sample(b,1)[0]
    print('randVal: ' + str(random.random()))           #This value is always the same
    print('setSample: ' + str(random.sample(c.a,1)[0])) #This value can change run-to-run
    return

3 个答案:

答案 0 :(得分:1)

它与可变对象的对象实例化有关。如果我创建set frozenset,它确实会给出确定性结果;

Python 2.7.11 (default, Jan  9 2016, 15:47:04) 
[GCC 4.2.1 Compatible FreeBSD Clang 3.4.1 (tags/RELEASE_34/dot1-final 208032)] on freebsd10
Type "help", "copyright", "credits" or "license" for more information.
>>> import random
>>> random.seed(0)
>>> set(frozenset(random.random() for i in range(5)) for j in range(5))
set([frozenset([0.7298317482601286, 0.3101475693193326, 0.8988382879679935, 0.47214271545271336, 0.6839839319154413]), frozenset([0.5833820394550312, 0.4765969541523558, 0.4049341374504143, 0.30331272607892745, 0.7837985890347726]), frozenset([0.7558042041572239, 0.5046868558173903, 0.9081128851953352, 0.28183784439970383, 0.6183689966753316]), frozenset([0.420571580830845, 0.25891675029296335, 0.7579544029403025, 0.8444218515250481, 0.5112747213686085]), frozenset([0.9097462559682401, 0.8102172359965896, 0.9021659504395827, 0.9827854760376531, 0.25050634136244054])])
>>> random.seed(0)
>>> set(frozenset(random.random() for i in range(5)) for j in range(5))
set([frozenset([0.7298317482601286, 0.3101475693193326, 0.8988382879679935, 0.47214271545271336, 0.6839839319154413]), frozenset([0.5833820394550312, 0.4765969541523558, 0.4049341374504143, 0.30331272607892745, 0.7837985890347726]), frozenset([0.7558042041572239, 0.5046868558173903, 0.9081128851953352, 0.28183784439970383, 0.6183689966753316]), frozenset([0.420571580830845, 0.25891675029296335, 0.7579544029403025, 0.8444218515250481, 0.5112747213686085]), frozenset([0.9097462559682401, 0.8102172359965896, 0.9021659504395827, 0.9827854760376531, 0.25050634136244054])])
>>> 

如果我没弄错的话,CPython使用(可变)对象的内存位置作为它的id和散列的关键。

所以虽然对象的内容总是相同的,但它的id会有所不同;

In [13]: random.seed(0)

In [14]: k = set()

In [15]: for n in range (20):
    k.add(bigSet())
   ....:     

In [16]: for x in k:
    print(id(x))
   ....:     
34856629808
34856629864
34856631936
34856630424
34856629920
34856631992
34856630480
34856629976
34856632048
34856631040
34856630536
34856632104
34856630032
34856630592
34856630088
34856632160
34856629752
34856629696
34856630760
34856630256

In [17]: random.seed(0)

In [18]: k = set()

In [19]: for n in range (20):
   ....:         k.add(bigSet())
   ....:     

In [20]: for x in k:
   ....:         print(id(x))
   ....:     
34484534800
34856629808
34484534856
34856629864
34856631936
34856630424
34856629920
34856631992
34484534968
34856629976
34856630480
34856632048
34856631040
34484535024
34484535080
34484535136
34856632216
34484534688
34484534912
34484534744

可能的解决方案是对冻结集进行子类化。

答案 1 :(得分:1)

OrderedSet是理想的选择。

此处setfrozenset均不应使用,因为未指定任何位置对其进行排序。另一个答案有效的事实只是实施的偶然。集合是无序的,并且依靠它们的次序导致耦合到Python版本(可能还有机器)。

我在Python 3.8.6中得到了与Roland's answer不同的顺序(尽管两次运行之间的顺序恰好是相同的)。尽管存在这样的事实,即生成的随机数是相同的。

要保留顺序,并因此保留基于random种子的确定性,必须使用诸如OrderedSet之类的有序数据结构。

如果没有OrderedSet可用,或者分析代码显示OrderedSet速度很慢,则可以使用OrderedDict并忽略其值。

如果您的Python> = 3.6,则由于performance optimizations,甚至可以订购常规的dict

答案 2 :(得分:0)

我很确定你是对的,问题是由set的运行到运行内存分配差异引起的。当我将程序更改为使用列表而不是集合时,我得到了确定性行为:

import random

''' Class contains a large list of pseudo-random numbers. '''
class bigList:
    def __init__(self):
        self.a = [random.random() for n in range(2000)]

''' Main test function. '''
def randTest():
    ''' Seed the PRNG. '''
    random.seed(0)

    ''' Create lists of bigList elements, presumably many memory allocations. '''
    b = [bigList() for n in range(2000)]

    ''' Pick a random value from a random bigSet. Would have expected this to be deterministic. '''
    c = random.sample(b, 1)[0]
    print('randVal: ' + str(random.random()))  # This value is always the same
    # and so is this now...
    print('setSample: ' + str(random.sample(c.a, 1)[0]))

randTest()