获得可清洗的numpy内存视图

时间:2016-07-27 15:17:10

标签: python arrays numpy hash

我想首先对numpy数组进行哈希处理而不将数据复制到bytearray中。

具体来说,我有一个具有唯一行的连续只读二维int64 numpy数组A。具体来说,让我们说:

A = np.array([[1, 2], [3, 4], [5, 6]])
A.setflags(write=False)

我想创建一个常量时间函数,将值相同的任意数组ap映射到A的切片,例如A[i],其索引为i。 e.g。

foo(np.array([1, 2])) == 0
foo(np.array([3, 4])) == 1
foo(np.array([5, 6])) == 2

自然的选择是制作如下字典:

lookup = {a: i for i, a in enumerate(A)}

不幸的是,numpy数组不可清除。有ways来散列numpy数组,但理想情况下我希望保留相等性,这样我就可以在字典中使用它而无需编写手动碰撞检测。

引用的文章确实指出我可以这样做:

lookup = {a.data.tobytes(): i for i, a in enumerate(A)}

def foo(ap):
    return lookup[ap.data.tobytes()]

但是tobytes方法返回a.data指向的数据的副本,因此内存使用量增加了一倍。

我喜欢做的事情是:

lookup = {a.data: i for i, a in enumerate(A)}

def foo(ap):
    return lookup[ap.data]

理想情况下使用指向底层内存的指针而不是数组对象或其字节的副本,但是从a.dtype == int开始,这会失败:

ValueError: memoryview: hashing is restricted to formats 'B', 'b' or 'c'

这没关系,我们可以将它Aview = A.view(np.byte)投射出去,现在我们有了:

>>> Aview.flags
#   C_CONTIGUOUS : True
#   F_CONTIGUOUS : False
#   OWNDATA : False
#   WRITEABLE : False
#   ALIGNED : True
#   UPDATEIFCOPY : False

>>> Aview.data.format
# 'b'

但是,在尝试对此进行哈希处理时,它仍会出错:

TypeError: unhashable type: 'numpy.ndarray'

可能的解决方案(受this启发)将定义:

class _wrapper(object)
    def __init__(self, array):
        self._array = array
        self._hash = hash(array.data.tobytes())

    def __hash__(self):
        return self._hash

    def __eq__(self, other):
        return self._hash == other._hash and np.all(self._array == other._array)

lookup = {_wrapper(a): i for i, a in enumerate(A)}

def foo(ap):
    return lookup[_wrapper(ap)]

但这似乎不够优雅。有没有办法告诉python只是将内存视图解释为bytearray并正常哈希,而不必复制到bytestring或者有numpy intercede并中止哈希?

我尝试过的其他事情:

  1. A的格式允许我将每一行映射到一个不同的整数,但对于非常大的A,可能的数组空间大于np.iinfo(int).max,而我可以使用python的整数类型,比仅散列内存慢约100倍。

  2. 我也尝试过这样的事情:

    Aview = A.view(np.void(A.shape[1] * A.itemsize)).squeeze()

    但是,即使A.flags.writeable == FalseA[0].flags.writeable == True。尝试hash(A[0])时,python会引发TypeError: unhashable type: 'writeable void-scalar'。我不确定是否可以将标量标记为只读,或以其他方式散列虚空标量,即使大多数其他标量似乎可以清除。

1 个答案:

答案 0 :(得分:-1)

我无法理解你想要做什么。

当我创建一个数组时:

In [111]: A=np.array([1,0,1,2,3,0,2])
In [112]: A.__array_interface__
Out[112]: 
{'data': (173599672, False),
 'descr': [('', '<i4')],
 'shape': (7,),
 'strides': None,
 'typestr': '<i4',
 'version': 3}
In [113]: A.nbytes
Out[113]: 28
In [114]: id(A)
Out[114]: 2984144632

我得到一个ndarray个对象,其中包含唯一的id,以及shapestridesdata buffer等属性。此缓冲区为28个字节,从173599672开始。

A[3]中没有A个对象。我必须创建它

In [115]: x=A[3]
In [116]: type(x)
Out[116]: numpy.int32
In [117]: id(x)
Out[117]: 2984723472
In [118]: x.__array_interface__
Out[118]: 
{'__ref': array(2),
 'data': (179546048, False),
 'descr': [('', '<i4')],
 'shape': (),
 'strides': None,
 'typestr': '<i4',
 'version': 3}

这个x在很多方面只是一个0d 1元素数组(它的显示方式不同)。请注意,其数据指针与A的数据指针无关。所以它甚至不能共享内存。

切片确实共享内存

In [119]: y=A[3:4]
In [120]: y.__array_interface__
Out[120]: 
{'data': (173599684, False),   # 173599672+3*4
 'descr': [('', '<i4')],
 'shape': (1,),
 'strides': None,
 'typestr': '<i4',
 'version': 3}

====================

映射arbitrary A[i] to its B[i]到底是什么意思?您使用A[i]处的值作为键,还是使用位置作为键?在我的示例中,A的元素不是唯一的。我可以唯一地访问A[0]A[2](按索引),但在这两种情况下,我都会获得1的值。

但请考虑这种情况。有一种相对快速的方法可以在1d数组中找到一个值 - in1d

In [121]: np.in1d(A,1)
Out[121]: array([ True, False,  True, False, False, False, False], dtype=bool)

制作B数组:

In [122]: B=np.arange(A.shape[0])

B中与1中的A值对应的所有元素:

In [123]: B[np.in1d(A,1)]
Out[123]: array([0, 2])
In [124]: B[np.in1d(A,0)]   # to 0
Out[124]: array([1, 5])
In [125]: B[np.in1d(A,2)]   # to 2
Out[125]: array([3, 6])

A创建的字典提供相同(最后)的值:

In [134]: dict(zip(A,B))
Out[134]: {0: 5, 1: 2, 2: 6, 3: 4}

=====================

Python文档中关于hashable的段落讨论了需要__hash__方法。

所以我查了一个对象:

In [200]: {}.__hash__    # None
In [201]: [].__hash__    # None
In [202]: ().__hash__
Out[202]: <method-wrapper '__hash__' of tuple object at 0xb729302c>

In [204]: class MyClass(object):
     ...:     pass
     ...: 
In [205]: MyClass().__hash__
Out[205]: <method-wrapper '__hash__' of MyClass object at 0xb3008c4c>

numpy整数 - 带有np.int32包装器的int:

In [206]: x
Out[206]: 2
In [207]: x.__hash__
Out[207]: <method-wrapper '__hash__' of numpy.int32 object at 0xb1e748c0>
In [208]: x.__hash__()
Out[208]: 2

一个numpy数组

In [209]: A
Out[209]: 
array(['one', 'two'], 
      dtype='<U3')
In [210]: A.__hash__    # None

In [212]: np.float(12.232).__hash__()
Out[212]: 1219767578

因此,字典的密钥必须至少具有生成hash的方法,即唯一标识符。它可能是实例id(默认情况),也可能是从对象的值派生的东西(某种校验和?)。字典维护是这些哈希的表,可能是指向keyvalue的指针。当我做一个字典get时,它会为我给它的对象生成哈希,查找它,并返回相应的值 - 如果存在。

不可用的课程不具备__hash__方法(或方法为None)。他们无法生成此唯一ID。显然,按设计,类np.ndarray的对象没有__hash__。使用writability标志不会改变它。

尝试散列或创建数组行的字典时遇到的一个大问题是,您对散列数组的特定实例(由切片视图创建的对象)感兴趣,但是哈希基于该行的值。

所以拿你的2d阵列:

In [236]: A
Out[236]: 
array([[1, 2],
       [3, 4],
       [5, 6]])

您希望A[1,:]np.array([3,4])生成相同的__hash__()值。并且A[0,:]+2,也许A.mean(axis=0)(除了那是一个浮点数组)。

由于你担心内存,你必须处理非常大的数组,比如(1000,1000) - 这意味着基于1000个不同数字的哈希值,以及一些独特的数据。