【问题标题】:Transfer ownership of numpy data转移 numpy 数据的所有权
【发布时间】:2013-03-16 04:45:52
【问题描述】:

在我的previous question 中,我学会了调整子类ndarray 的大小。整洁的。不幸的是,当我尝试调整大小的数组是计算的结果时,这不再有效:

import numpy as np

class Foo(np.ndarray):
    def __new__(cls,shape,dtype=np.float32,buffer=None,offset=0,
                strides=None,order=None):
        return np.ndarray.__new__(cls,shape,dtype,buffer,offset,strides,order)

    def __array_prepare__(self,output,context):
        print output.flags['OWNDATA'],"PREPARE",type(output)
        return np.ndarray.__array_prepare__(self,output,context)

    def __array_wrap__(self,output,context=None):
        print output.flags['OWNDATA'],"WRAP",type(output)

        return np.ndarray.__array_wrap__(self,output,context)

a = Foo((32,))
#resizing a is no problem
a.resize((24,),refcheck=False)

b = Foo((32,))
c = Foo((32,))

d = b+c
#Cannot resize `d`
d.resize((24,),refcheck=False)

确切的输出(包括回溯)是:

True PREPARE <type 'numpy.ndarray'>
False WRAP <class '__main__.Foo'>
Traceback (most recent call last):
  File "test.py", line 26, in <module>
    d.resize((24,),refcheck=False)
ValueError: cannot resize this array: it does not own its data

我认为这是因为numpy 创建了一个新的ndarray 并将其传递给__array_prepare__。不过,在某些时候,似乎“output” 数组得到view-casted to my Foo type,尽管在这一点上文档似乎不是 100% 清晰/准确。无论如何,在视图转换之后,输出不再拥有数据,因此无法就地重塑(据我所知)。

有什么方法可以通过某种 numpy voodoo(__array_prepare____array__)等将数据的所有权转移到我的子类的实例?

【问题讨论】:

  • 在进一步搜索中,该用户似乎是asking the same thing,尽管从不同的角度来看......可能有一个适用于我的情况的答案,但在那里不起作用.. .
  • 这当然是一个视图。基础ndarrayd.base,它拥有数据。作为一个组合,您可以调整 d.base 的大小,然后重新分配 d = d.base.view(Foo)
  • eryksun -- 听起来很有希望,但我可能会失去同时添加到我的领域的其他属性。无论如何,将其发布为答案。明天它会给我一些实验的东西。在这一点上,除了盲目地评论我的代码之外,我有点太累了。
  • 它可能会有所帮助,但它不能回答问题。我可以通过调整标志和基数来强制使用 ctypes,但这既丑陋又脆弱。
  • 要调整大小,您需要使用refcheck=False。这会使当前的Foo 视图无效,因此您需要一个新视图。

标签: python numpy


【解决方案1】:

这不是一个令人满意的答案,但也不适合评论...您可以通过使用 ufunc 的 out 参数来解决数据的所有权问题。一个愚蠢的例子:

>>> a = Foo((5,))
>>> b = Foo((5,))
>>> c = a + b # BAD
True PREPARE <type 'numpy.ndarray'>
False WRAP <class '__main__.Foo'>
>>> c.flags.owndata
False

>>> c = Foo((5,))
>>> c[:] = a + b # BETTER
True PREPARE <type 'numpy.ndarray'>
False WRAP <class '__main__.Foo'>
>>> c.flags.owndata
True

>>> np.add(a, b, out=c) # BEST
True PREPARE <class '__main__.Foo'>
True WRAP <class '__main__.Foo'>
Foo([  1.37754085e-38,   1.68450356e-20,   6.91042737e-37,
         1.74735556e-04,   1.48018885e+29], dtype=float32)
>>> c.flags.owndata
True

我认为上面的输出与c[:] = a + b 以将数据从临时数组复制到c 为代价来拥有数据是一致的。但是,当您使用 out 参数时,不应该发生这种情况。

由于您已经担心数学表达式中的中间存储,因此对其处理方式进行微观管理可能并不是一件坏事。也就是替换

g = a + b + np.sqrt(d*d + e*e + f*f)

g = foo_like(d) # you'll need to write this function!
np.multiply(d, d, out=g)
g += e * e
g += f * f
np.sqrt(g, out=g)
g += b
g += a

可能会为您节省一些中间内存,并让您拥有自己的数据。它确实抛出了“可读性计数”的口头禅,但是......

【讨论】:

  • 是的,我意识到你可以使用out。就我而言,如果你想做a + b * sqrt(c) **4 或其他什么,那并不是最好的。 (把这些都写成函数调用并不好玩)。但是,我从未想过使用切片分配来确保 LHS 拥有数据。这是一个很好的接触。
  • @mgilson 不过,这只是一个隐藏的副本。
【解决方案2】:

不过,在某些时候,“输出”数组似乎 将视图投射到我的 Foo 类型

是的,ndarray.__array_prepare__ 调用 output.view,它返回一个不拥有其数据的数组。

我做了一些实验,但找不到简单的方法。

虽然我同意这种行为并不理想,但至少在您的用例中,我认为d 不拥有其数据是可以接受的。 Numpy 广泛使用视图,如果您坚持避免在使用 numpy 数组时创建任何视图,那么您的生活将变得非常艰难。

我还声称,根据我的经验,resize 通常应该避免使用。如果您避免使用resizeing,那么使用创建的视图应该不会有任何问题。它有一种 hacky 的感觉,而且很难使用(正如您可能开始理解的那样,在使用它时遇到了两个经典错误之一:it does not own its data。另一个是cannot resize an array that has been referenced)。 (quesion 中描述了另一个问题。)

由于您使用resize 的决定来自对您其他问题的回答,我将发布我的其余回答there

【讨论】:

  • 我完全同意,仅仅有一个refcheck=False 就是在玩火。似乎有一个神话,认为复制很慢,但“慢”是相对的,除非你仔细计时它是一个真正的区别并且代码非常具体......不要......
  • @seberg -- 目前,我在操作结束时复制数据 -- 但是为每个ufunc 复制似乎是个坏主意。
  • @mgilson,为什么你首先需要它,为什么你必须在 ufunc 之后缩小数据,以至于值得考虑调整大小?如果你真的只需要一小部分,你可以复制那一小部分而不会严重影响性能。
  • @seberg -- 我有类似a + b + sqrt(d*d + e*e + f*f) ... 这样的操作已经生成了一堆临时数组,我不想再做更多了。我制作的每一个副本都让我更接近于交换(这对于我正在使用的大型阵列来说似乎是可行的)。每个字段约为 512 Mb。在我什至尝试对它们进行数学运算之前,我明确存储了至少 6 个内存,这使我处于 3Gb 内存。如果我可以在我的数据文件阅读器中调整数组的大小(这是我的计划),那么无论如何我都不应该让数组的其他视图浮动成为一个大问题。
  • @seberg 或其他人如果你有兴趣讨论我的程序设计(我很想得到意见),我可以经常在 python 聊天室里闲逛:chat.stackoverflow.com/rooms/6/python(对不起 - - 现在这是正确的链接)
【解决方案3】:

怎么样:

def resize(arr, shape):
    np.require(arr, requirements=['OWNDATA'])
    arr.resize(shape, refcheck=False)

它似乎成功地调整了大小(并减少了内存消耗):

import array
import numpy as np
import time

class Foo(np.ndarray):
    def __new__(cls, shape, dtype=np.float32, buffer=None, offset=0,
                strides=None, order=None):
        return np.ndarray.__new__(cls, shape, dtype, buffer, offset, strides, order)

    def __array_prepare__(self, output, context):
        print(output.flags['OWNDATA'], "PREPARE", type(output))
        return np.ndarray.__array_prepare__(self, output, context)

    def __array_wrap__(self, output, context=None):
        print(output.flags['OWNDATA'], "WRAP", type(output))
        output = np.ndarray.__array_wrap__(self, output, context)
        return output

def free_memory():
    """
    Return free memory available, including buffer and cached memory
    """
    total = 0
    with open('/proc/meminfo', 'r') as f:
        for line in f:
            line = line.strip()
            if any(line.startswith(field) for field in ('MemFree', 'Buffers', 'Cached')):
                field, amount, unit = line.split()
                amount = int(amount)
                if unit != 'kB':
                    raise ValueError(
                        'Unknown unit {u!r} in /proc/meminfo'.format(u=unit))
                total += amount
    return total


def gen_change_in_memory():
    """
    http://stackoverflow.com/a/14446011/190597 (unutbu)
    """
    f = free_memory()
    diff = 0
    while True:
        yield diff
        f2 = free_memory()
        diff = f - f2
        f = f2
change_in_memory = gen_change_in_memory().next

def resize(arr, shape):
    print(change_in_memory())
    # 0
    np.require(arr, requirements=['OWNDATA'])

    time.sleep(1)
    print(change_in_memory())
    # 200

    arr.resize(shape, refcheck=False)

N = 10000000
b = Foo((N,), buffer = array.array('f',range(N)))
c = Foo((N,), buffer = array.array('f',range(N)))

产量

print(change_in_memory())
# 0

d = b+c
d = np.require(d, requirements=['OWNDATA'])

print(change_in_memory())
# 39136

resize(d, (24,))   # Increases memory by 200 KiB
time.sleep(1)
print(change_in_memory())
# -39116

【讨论】:

  • np.require 的文档中的注释说:“返回的数组将保证满足列出的要求如果需要,通过复制。”你怎么看正在发生,除了副本,使这项工作?我倾向于认为最终这与d = d.copy() 相同,尽管它也不完全有意义。
  • @Jaime:在上述情况下,d = b+c,我不明白为什么d 不拥有它的数据。如果你明白为什么,我真的很感激解释。至少在这种情况下,np.require 在没有复制数据的情况下成功,可用内存的增加证明了这一点。
  • @unutbu,不,它确实复制了数据,但它删除了对原始数据的引用,因此它也再次释放了相同的数量。它不拥有自己的数据,因为它基于基类 ndarray,这可能是一个缺陷,但是...
  • @seberg:感谢您提供的信息。然而我很困惑。我在np.require(...) 周围添加了一些print(change_in_memory()) 语句,它似乎只消耗了200 KiB 的内存。如果是复制数组,内存消耗会不会多很多?
  • 仅当您保留对原始 d 的引用时。 (这反过来又保留了对保存数据的d.base 的引用)
猜你喜欢
  • 1970-01-01
  • 2019-09-23
  • 2014-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-12
  • 1970-01-01
  • 2015-05-16
相关资源
最近更新 更多