内存高效随机数迭代器无需替换

时间:2012-05-23 19:25:43

标签: python random numpy itertools

我觉得这个应该很容易,但经过无数次搜索和尝试后,我无法找到答案。基本上我有很多项目,我想以随机顺序采样而无需替换。在这种情况下,它们是2D阵列中的单元格。我将用于较小数组的解决方案不会转换,因为它需要改组内存数组。如果我必须采样的数量很小,我也可以随机抽样物品并保留我尝试过的值列表。不幸的是,我经常需要对所有细胞中的很大一部分进行采样,尽可能多。

我想创建的是迭代器,它使用itertools,numpy和/或random的某种组合产生下一个随机单元格(x和y索引)。另一种可能的解决方案是创建一个迭代器,它将产生0和(x_count * y_count)之间的下一个随机数(无替换),我可以将其映射回单元格位置。这些都不容易实现。

感谢任何消息!

这是我目前的解决方案。

import numpy as np
import itertools as itr
import random as rdm

#works great
x_count = 10
y_count = 5

#good luck!
#x_count = 10000
#y_count = 20000

x_indices = np.arange(x_count)
y_indices = np.arange(y_count)

cell_indices = itr.product(x_indices, y_indices)
list_cell_indices = list(cell_indices)
rdm.shuffle(list_cell_indices)

for i in range(25):
    print list_cell_indices[i]

基于当前的反应以及我翻译perl的尝试,我对此一无所知,我理解我能做的最好的事情如下:

import numpy as np
import itertools as itr
import random as rdm

x_count = 10000
y_count = 5000

sample_count = 10000
keep_probability = 0.01


tried_cells = set()
kept_cells = set()

while len(kept_cells) < sample_count:
    x = rdm.randint(0, x_count)
    y = rdm.randint(0, y_count)

    if (x, y) in tried_cells:
        pass
    else:
        tried_cells.add((x, y))
        keep = rdm.random() < keep_probability
        if keep:
            kept_cells.add((x,y))


print "worked"

在大多数情况下,使用的处理时间和内存并不是那么糟糕。也许我可以检查一般的单元格keep_probability和sample_count,并为困难的情况抛出错误。

5 个答案:

答案 0 :(得分:3)

我认为,对于接近R * C的样本大小而言,如果没有使用大量辅助存储空间,就无法对没有替换的序列进行采样。虽然有一些聪明的方法可以减少小样本量的存储量,但如果您希望采样的数据量超过数据集的三分之一,那么最好只创建一个单独的列表。 random.sample是此目的的自然选择;坦率地说,我只是将你的2-d numpy阵列的扁平版本直接传递给它。 (除非你也需要索引,在这种情况下,随机抽样整数并将它们转换成坐标,la hexparrot的解决方案,是一种合理的方法。)

>>> a = numpy.arange(25).reshape((5, 5))
>>> random.sample(a.ravel(), 5)
[0, 13, 8, 18, 4]

如果你看一下random.sample的实现,你会发现对于较小的样本大小,它已经大致与上面的perl代码相同 - 跟踪集合中先前选择的项目并丢弃选择已经在集合中。对于较大的样本大小,它会创建输入的副本 - 这比较大的值的集合更有效,因为集合比每个存储项目的列表占用更多空间 - 并且稍微修改了Fisher-Yates shuffle,当它有sample_size项时停止(即它不会对整个列表进行洗牌,因此它比自己改变整个事物更有效。)

基本上,我的赌注是你不会比random.sample更好,滚动你自己,除非你在c中编写代码。

然而 - 我确实找到了这个,你可能会觉得有趣:numpy.random.choice。这似乎是在c速度下进行或不进行替换的随机采样。抓到了吗?它是Numpy 1.7的新功能!

答案 1 :(得分:2)

这种方法怎么样?我首先创建x * y数组并将其重塑为2-D。然后,知道每个单元格可以由单个整数唯一标识,从0到(x * y)获取样本。

import numpy

x_count = 10000
y_count = 20000

x_indices = numpy.arange(x_count)
y_indices = numpy.arange(y_count)

large_table = numpy.arange(y_count * x_count).reshape(y_count, x_count)
print large_table

def get_random_item(sample_size):
    from random import sample
    for i in sample(xrange(y_count * x_count), sample_size):
        y,x = divmod(i, y_count)
        yield (x,y)

for x,y in get_random_item(10):
    print '%12i   x: %5i y: %5i' % (large_table[x][y],  x,y)

返回:

(首先模拟您通过产品创建的现有二维阵列)

[[        0         1         2 ...,      9997      9998      9999]
 [    10000     10001     10002 ...,     19997     19998     19999]
 [    20000     20001     20002 ...,     29997     29998     29999]
 ..., 
 [199970000 199970001 199970002 ..., 199979997 199979998 199979999]
 [199980000 199980001 199980002 ..., 199989997 199989998 199989999]
 [199990000 199990001 199990002 ..., 199999997 199999998 199999999]]

然后,它返回2-dim坐标,只需通过数组[x] [y]

即可将其转换为单元格内容
   154080675   x: 15408 y:   675
   186978188   x: 18697 y:  8188
   157506087   x: 15750 y:  6087
   168859259   x: 16885 y:  9259
    29775768   x:  2977 y:  5768
    94167866   x:  9416 y:  7866
    15978144   x:  1597 y:  8144
    91964007   x:  9196 y:  4007
   163462830   x: 16346 y:  2830
    62613129   x:  6261 y:  3129

sample()表示它是用于随机抽样而无需替换的。这种方法坚持建议&#39;对于从大量人群中抽样,这种方法特别快速且节省空间:样本(xrange(10000000),60)。&#39;在python random页面上找到。

我注意到虽然我使用get_random_item()作为生成器,但底层sample()仍然生成一个完整列表,所以内存使用仍然是y * x + sample_size,但它运行得非常快。< / p>

答案 2 :(得分:1)

您已经在使用O(N = R * C)内存,因此您也可以使用O(N)内存作为迭代器。复制所有元素并随意对它们进行排序,就像对单维案例一样。如果你要访问每个元素,这是一个合理的事情,你说这是你的情况。

(对于记录:否则,内存不是一个很糟糕的问题,因为你只需要“记住”你以前的位置。因此你可以保留一个已经访问过的索引列表。如果您计划访问每个元素,那就太糟糕了,因为如果实施不断增长的黑名单,拒绝抽样可能需要很长时间。(您也可以使用递减的白名单来实现它,这相当于第一个解决方案。)

答案 3 :(得分:1)

您已经说过,对于网格中的大多数单元格,您的测试可能会失败。如果是这种情况,随机抽样细胞可能不是一个好主意,因为你很难跟踪你已经检查过的细胞,而不需要使用大量的内存。

相反,您可能最好将测试应用于整个网格,并从通过它的那些中选择一个随机元素。

此函数返回一个传递测试的随机元素(如果全部失败则返回None)。它使用的内存非常少。

def findRandomElementThatPasses(iterable, testFunc):
    value = None
    passed = 0

    for element in iterable:
        if testFunc(element):
            passed += 1
            if random.random() > 1.0/passed:
                value = element

    return value

你可以用以下内容来称呼它:

e = findRandomElementThatPasses((x,y) for x in xrange(X_SIZE)
                                      for y in xrange(Y_SIZE),
                                someFunctionTakingAnXYTuple)

如果您使用的是Python 3,请使用range而不是xrange。

答案 4 :(得分:0)

在perl中你可以这样做:

# how many you got and need
$xmax = 10000000;
$ymax = 10000000;
$sampleSize = 10000;

# build hash, dictionary in python
$cells = {};
$count = 0;
while($count < $sampleSize) {
  $x = rand($xmax);
  $y = rand($ymax);
  if(! $cells->{$x}->{$y}) {
    $cells->{$x}->{$y}++;
    $count++;
  }
}
# now grab the stuff
foreach ($x keys %{$cells}) {
  foreach ($y keys %{$cells->{$x}}) {
    getSample($x, $y);
  }
}

没有重复,相当随意,内存不太难。