2

像许多其他人一样,我的情况是我有一个收集大量数据的类,并提供了一种将数据作为 numpy 数组返回的方法。(额外的数据可以继续流入,即使在返回一个数组之后)。由于创建数组是一项昂贵的操作,因此我只想在必要时创建它,并尽可能高效地创建它(特别是在可能的情况下就地附加数据)。

为此,我一直在阅读有关 ndarray.resize() 方法和 refcheck 参数的内容。我知道只有在“您确定没有与另一个 Python 对象共享此数组的内存”时,才应将 refcheck 设置为 False。

问题是我不确定。有时我有,有时我没有。如果 refcehck 失败(我可以捕获它然后创建一个新副本),我会引发错误,但我希望它只有在存在“真实”外部引用时才会失败,而忽略那些我知道是安全的。

这是一个简化的插图:

import numpy as np

def array_append(arr, values, refcheck = True):
    added_len = len(values)
    if added_len == 0:
        return arr
    old_len = len(arr)
    new_len = old_len + added_len
    arr.resize(new_len, refcheck = refcheck)
    arr[old_len:] = values
    return arr

class DataCollector(object):

    def __init__(self):
        self._new_data = []
        self._arr = np.array([])

    def add_data(self, data):
        self._new_data.append(data)

    def get_data_as_array(self):
        self._flush()
        return self._arr

    def _flush(self):
        if not self._new_data:
            return
#        self._arr = self._append1()
#        self._arr = self._append2()
        self._arr = self._append3()
        self._new_data = []

    def _append1(self):
        # always raises an error, because there are at least 2 refs:
        # self._arr and local variable 'arr' in array_append()
        return array_append(self._arr, self._new_data, refcheck = True)

    def _append2(self):
        # Does not raise an error, but unsafe in case there are other
        # references to self._arr
        return array_append(self._arr, self._new_data, refcheck = False)

    def _append3(self):
        # "inline" version: works if there are no other references
        # to self._arr, but raises an error if there are.
        added_len = len(self._new_data)
        old_len = len(self._arr)
        self._arr.resize(old_len + added_len, refcheck = True)
        self._arr[old_len:] = self._new_data
        return self._arr

dc = DataCollector()
dc.add_data(0)
dc.add_data(1)
print dc.get_data_as_array()
dc.add_data(2)
print dc.get_data_as_array()
x = dc.get_data_as_array()  # create an external reference
print x.shape
for i in xrange(5000):
    dc.add_data(999)
print dc.get_data_as_array()
print x.shape

问题:

  1. 有没有更好(快速)的方法来做我想做的事情(增量创建 numpy 数组)?
  2. 有没有办法告诉 resize() 方法:“执行 refcheck,但忽略我知道是安全的一个引用(或 n 个引用)”?(这将解决 _append1() 总是失败的问题)
4

2 回答 2

1

resize方法有两个主要问题。第一个是您在用户调用时返回对 self._arr 的引用get_data_as_array。现在调整大小将根据您的实现做两件事之一。它会修改您给您的用户的数组,即用户将采用a.shape并且形状会发生不可预测的变化。或者它会破坏该数组,让它指向坏内存。get_data_as_array您可以通过始终使用return来解决该问题self._arr.copy(),但这使我想到了第二个问题。resize实际上效率不是很高。我相信一般来说,调整大小必须分配新内存并在每次调用它来增长数组时进行复制。另外,现在您每次想要将数组返回给用户时都需要复制该数组。

另一种方法是设计自己的动态数组,看起来像:

class DynamicArray(object):

    _data = np.empty(1)
    data = _data[:0]
    len = 0
    scale_factor = 2

    def append(self, values):
        old_data = len(self.data)
        total_data = len(values) + old_data
        total_storage = len(self._data)
        if total_storage < total_data:
            while total_storage < total_data:
                total_storage = np.ceil(total_storage * self.scale_factor)
            self._data = np.empty(total_storage)
            self._data[:old_data] = self.data

        self._data[old_data:total_data] = values
        self.data = self._data[:total_data]

这应该非常快,因为您只需要增加数组 log(N) 次,并且最多使用 2*N-1 存储,其中 N 是数组的最大大小。除了增加数组之外,您只是制作_data不涉及任何复制并且应该是恒定时间的视图。

希望这是有用的。

于 2013-03-06T01:09:40.127 回答
1

我将array.array()用来做数据收集:

import array
a = array.array("d")
for i in xrange(100):
    a.append(i*2)

每次当您想对收集的数据进行一些计算时,将其转换为numpy.ndarrayby numpy.frombuffer

b = np.frombuffer(a, dtype=float)
print np.mean(b)

b将与 共享数据内存a,因此转换非常快。

于 2013-03-05T13:44:58.887 回答