random.sample()每次返回相同的随机序列?

8
我使用Python中的random.sample(population, k)函数从列表中生成一组随机值,以创建该列表的新排列。问题在于,每次循环运行时,它都会生成完全相同的随机序列。为什么会这样?我甚至使用了random.seed(i),使i变量(每次循环时都会改变)每次都会产生不同的值。但是仍然是相同的序列。怎么回事!@
以下是我的使用方式:
def initialBuild(self):
    alphabet = self.alphabet
    for i in range (self.length):
        value = random.sample(alphabet, 1)
        alphabet.remove(value[0])
        self.finalWord.append(value[0])
    print "Final word = ", self.finalWord

这只是从一个Individual类的init方法中调用的。init方法被如下调用...

def buildPopulation(self, alphabet):
    #Initialize empty individuals
    for i in range(POPULATION_SIZE):
        self.population.append(Individual(alphabet))

init方法如下所示...

def __init__(self, alphabet = []):
    self.length = len(alphabet)
    self.alphabet = alphabet
    self.initialBuild()

最后,我打印了一个最终的单词。这是两次运行此方法的输出结果:

最终单词 = [[1150, 1160],[720, 635],[95, 260],[595, 360],[770, 610],[830, 610],[25, 185],[520, 585],[605, 625],[410, 250],[555, 815],[880, 660],[300, 465],[1220, 580],[1215, 245],[1250, 400],[565, 575],[1605, 620],[845, 680],[1170, 65],[795, 645],[525, 1000],[760, 650],[580, 1175],[420, 555],[25, 230],[345, 750],[700, 500],[725, 370],[1530, 5],[1740, 245],[875, 920],[415, 635],[1340, 725],[975, 580],[575, 665],[1465, 200],[830, 485],[660, 180],[475, 960],[685, 595],[145, 665],[510, 875],[845, 655],[650, 1130],[945, 685],[480, 415],[700, 580],[560, 365],[685, 610],[835, 625],[1320, 315]]

最终单词 = [[1150, 1160],[720, 635],[95, 260],[595, 360],[770, 610],[830, 610],[25, 185],[520, 585],[605, 625],[410, 250],[555, 815],[880, 660],[300, 465],[1220, 580],[1215, 245],[1250, 400],[565, 575],[1605, 620],[845, 680],[1170, 65],[795, 645],[525, 1000],[760, 650],[580, 1175],[420, 555],[25, 230],[345, 750],[700, 500],[725, 370],[1530, 5],[1740, 245],[875, 920],[415, 635],[1340, 725],[975, 580],[575, 665],[1465, 200],[830, 485],[660, 180],[475, 960],[685, 595],[145, 665],[510, 875],[845, 655],[650, 1130],[945, 685],[480, 415],[700, 580],[560, 365],[685, 610],[835, 625],[1320, 315]]

请注意,这两个是完全相同的。

编辑:由于我很难挑选出有用而且足够简短的代码放到这篇文章中,所以我已经在pastebin上发布了一大堆代码。 http://pastebin.com/f5f068391 这个希望是更好的选择... 再次感谢


这个“loop around”部分完全不清楚。根据您分享的代码,我无法重现这种行为。 - S.Lott
1
@S.Lott,pastebin条目确实使事情变得可重现(无法运行,因为缺少数据文件,但很容易模拟);请参阅我在A中的最新编辑,以了解我已经诊断出的几个相互作用错误(与“random”无关),以及我的建议修复措施(还有一个建议的重新组织,使用random.shuffle而不是random.sample然后删除循环,这与random提供的功能略有关联;-)。 - Alex Martelli
我很难挑选出我认为有用的代码。其实有一个技巧,就是找到能够产生问题的最小代码片段。如果你把问题归咎于错误的事物,有时候这样做会很困难。但是将问题缩减到最小的能够展示问题的代码片段是一项非常好的调试技巧。 - S.Lott
random.sample(..., 1)[0] is equivalent to random.choice(...) - AChampion
4个回答

17

我不确定你所说的“生成完全相同的随机序列”是什么意思。由于你只提供了一个无法独立运行的片段,很可能你的代码中其他部分存在错误,但是你选择不向我们展示——我已经尝试添加绝对最少量的代码来使你的片段运行,即:

import random

import string
def self(): pass
self.alphabet = list(string.lowercase)
self.finalWord = []
self.length = 4

for x in range(5):
  alphabet = self.alphabet
  for i in range (self.length):
      value = random.sample(alphabet, 1)
      alphabet.remove(value[0])
      self.finalWord.append(value[0])
  print "Final word = ", self.finalWord

当我运行这个自给自足的脚本几次时,我看到的是:

$ python sa.py 
Final word =  ['y', 'm', 'u', 'z']
Final word =  ['y', 'm', 'u', 'z', 'h', 'b', 'c', 's']
Final word =  ['y', 'm', 'u', 'z', 'h', 'b', 'c', 's', 'x', 'l', 'r', 'n']
Final word =  ['y', 'm', 'u', 'z', 'h', 'b', 'c', 's', 'x', 'l', 'r', 'n', 'q', 'a', 'k', 'e']
Final word =  ['y', 'm', 'u', 'z', 'h', 'b', 'c', 's', 'x', 'l', 'r', 'n', 'q', 'a', 'k', 'e', 'p', 'd', 'j', 'w']
$ python sa.py 
Final word =  ['k', 'v', 'o', 'd']
Final word =  ['k', 'v', 'o', 'd', 'q', 'p', 'w', 'l']
Final word =  ['k', 'v', 'o', 'd', 'q', 'p', 'w', 'l', 'n', 'u', 'g', 't']
Final word =  ['k', 'v', 'o', 'd', 'q', 'p', 'w', 'l', 'n', 'u', 'g', 't', 'i', 'r', 'e', 'f']
Final word =  ['k', 'v', 'o', 'd', 'q', 'p', 'w', 'l', 'n', 'u', 'g', 't', 'i', 'r', 'e', 'f', 's', 'c', 'j', 'z']
$ python sa.py 
Final word =  ['o', 'a', 'g', 't']
Final word =  ['o', 'a', 'g', 't', 'k', 'j', 'y', 'w']
Final word =  ['o', 'a', 'g', 't', 'k', 'j', 'y', 'w', 'z', 'l', 'i', 's']
Final word =  ['o', 'a', 'g', 't', 'k', 'j', 'y', 'w', 'z', 'l', 'i', 's', 'u', 'p', 'f', 'm']
Final word =  ['o', 'a', 'g', 't', 'k', 'j', 'y', 'w', 'z', 'l', 'i', 's', 'u', 'p', 'f', 'm', 'h', 'e', 'q', 'v']

正如你所见,这与“完全相同的随机序列”完全不同——它每次运行都会发生变化,就像预期的那样。

我想当我试图使您的代码可执行时,我可能已经错误地阅读了您的想法,并且您的意思与我的小脚本使用方式非常不同——但是读心术是一门不可靠的艺术(这就是为什么如果您发布一个自包含的、可运行的示例,尽可能精简地重现您的问题,而不是迫使我们尝试阅读您的想法!)。-

为什么不通过最少的修改来调整我刚刚发布的独立脚本,使其更接近您的预期用途,并重现您观察到的问题呢?然后,我们将更容易地并且更有效地发现您的代码可能存在的任何问题,并建议修复方法!

编辑:原始帖子在pastebin中贴出的代码有两个错误,这与random没有任何关系,这两个错误结合在一起会产生原始帖子观察到的行为。以下是代码的相关部分:

class Phenotype:
   ...
   chromosome = []

   def __init__(self, alleles = []):
    self.length = len(alleles)
    self.alleles = alleles
    self.initialBuild()

   def initialBuild(self):
    alleleSet = self.alleles
    for i in range (self.length):
        value = random.sample(alleleSet, 1)
        alleleSet.remove(value[0])
        self.chromosome.append(value[0])

好的,这里还有另一个bug(在新代码中使用旧的、传统的类,而不是应该始终使用的闪亮新风格类),但这还没有咬到OP,所以我们只是顺便提一下;-)。

Bug 1:由于__init__方法或任何其他方法都没有对self.chromosome = ...进行赋值,代码中所有对self.chromosome的提及实际上都是指Phenotype.chromosome这个唯一的列表,所有Phenotype类的实例都共享它。因此,无论如何,所有这样的实例将始终具有完全相同的、相同的chromosome,无论什么情况。修复方法:在__init__中添加self.chromosome=[](最好也删除类级变量,因为它们毫无用处,只会混淆问题)。

Bug 2:再看一遍以下代码行,以发现问题:

    self.alleles = alleles
       ...
    alleleSet = self.alleles
       ...
        alleleSet.remove(value[0])

明白了吗?self.alleles和本地名称alleleSet都是对同一个alleles集合(实际上是列表)的引用,这意味着remove调用会改变传入的集合。因此,在第一个Phenotype实例化之后,该集合就为空了(这就是为什么尽管存在Bug 1,染色体不会继续增长的原因:因为永远留下了空的等位基因集合)。

修复方法:创建一个副本,例如alleleSet = list(self.alleles),以避免损坏原始集合。

更好的修复方法:你正在使用一种极其复杂的方式来编写更简单的代码,例如:

self.chromosome = list(self.alleles)
random.shuffle(self.chromosome)

也就是说,只需要得到一个随机排列。采用进行N次独立取样的方式来构建一个随机排列,并在每个样本生成后将其从集合中删除,这是一种非常迂回、缓慢和复杂的方式来解决一个非常简单的问题!-)


我还会添加更多的代码 :) 我已经添加了运行此方法两次的输出,但请稍等,我将编辑更多,谢谢! - Chris
是的,你们的问题完全不同,让我编辑答案来解释一下。 - Alex Martelli
哇,太不可思议了。感谢您提供详细信息。第一次在Python中使用类,让我有点束手无策。感谢您的努力,并告诉我关于random.shuffle的事情! - Chris
@AlexMartelli,你以“心灵阅读是一门不可靠的艺术”成为我名言榜上的佼佼者! - ixe013

6
不要为每个样本更改种子。这会破坏随机数生成器并确保它不是随机的。
只需设置一次种子。在应用程序运行时不再更改该值。
随机数生成器以已知的恒定种子开始。每次运行时,您应该得到相同的序列。不设置种子会产生可预测的序列。设置任何常量种子(例如示例中的i)会产生可预测的序列。
如果您想要一个不可预测的序列,则需要一个不可预测的种子。使用时间或从/dev/random读取的一些字节作为种子值。一次。
请将此视为简化。
word = random.sample( alphabet, length )

对我来说,似乎它产生了不同的序列。

>>> import string, random
>>> alphabet = string.ascii_lowercase
>>> random.sample( alphabet, 5 )
['x', 'p', 's', 'k', 'h']
>>> random.sample( alphabet, 5 )
['y', 'h', 'u', 'n', 'd']

1
随机抽样后添加了种子,但这并没有真正解决问题。问题不在于每次程序运行时它都是相同的,而在于每次循环运行时它都是相同的。这使得应用程序在一次运行中一千次输出完全相同。 - Chris
它为我生成了不同的序列,但当我循环并重新创建一个列表时,我得到了相同的序列。等一下,我会编辑并附上我的输出。 - Chris

1
我曾经也有同样的疑问,后来发现这是因为Python会锁定随机值,直到再次运行随机数代码。换句话说,随机数函数需要在while循环内部而不是循环之前。如果将其放在循环之前会导致相同的随机数一遍又一遍地重复出现。
以下是一个简单的示例,运行20次介于1和10之间的随机整数:
#CORRECT version:
x = 1
y = 1
while x<=20:
    number = random.randint(1,10)
    print("Trial", y, "is", number)
    x = x + 1
    y = y + 1

#INCORRECT version:

number = random.randint(1,10)
x = 1
y = 1
while x<=20:
    print("Trial", y, "is", number)
    x = x + 1
    y = y + 1

1
value = random.sample(alphabet, 1) 

根据我的试验和错误,这个代码从你的人口中迭代一个随机值,并将其赋给“value”。之后,“value”将始终是那个第一个随机值。它不会再次随机迭代,只是保持被赋给value的值。
改为:
self.finalWord.append(value[0])

试试这个:

self.finalWord.append(random.sample(alphabet, 1)[0])

我还没有测试过这个,所以不能保证它可以百分之百地工作。但是无论如何,如果我没有弄错的话,它应该能够阐明你的问题。


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