转移numpy数据的所有权
在我之前的一个问题中,我学会了如何在原地调整一个子类的 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
”数组被“视图转换”成了我的 Foo
类型,尽管文档在这一点上并不是特别清楚或准确。无论如何,在视图转换之后,输出数组不再拥有数据,这使得我无法在原地调整它的形状(就我所知是这样)。
有没有什么办法,通过某种 numpy
的技巧(比如 __array_prepare__
,__array__
等等)把数据的所有权转移给我的子类实例呢?
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
在某个时候,似乎“输出”数组被转换成了我的 Foo 类型。
没错,ndarray.__array_prepare__
会调用 output.view
,这个方法返回一个不拥有自己数据的数组。
我试了一些方法,没找到简单的解决办法。
虽然我同意这种行为并不是最理想的,但在你的使用场景中,我认为 d
不拥有自己的数据也是可以接受的。Numpy 广泛使用视图,如果你坚持在使用 Numpy 数组时避免创建任何视图,那你会让自己变得很麻烦。
根据我的经验,我还认为一般情况下应该避免使用 resize
。如果你不使用 resize
,处理创建的视图应该不会有问题。使用 resize
有点像是在走捷径,而且很难处理(你可能已经开始理解,使用它时会遇到两个经典错误之一:它不拥有自己的数据
。另一个是 不能调整已被引用的数组的大小
)。还有一个问题在这个问题中描述过。
由于你决定使用 resize
是基于你另一个问题的回答,我会把我的剩余回答放在那里。
这可能不是一个很满意的答案,但也不适合放在评论里……你可以通过使用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
可能会节省一些临时内存,并且让你拥有自己的数据。虽然这样做可能会让代码的“可读性”下降,但……