散列为numpy数组的最有效属性

sap*_*api 51 python numpy

我需要能够存储numpy array一个dict用于缓存的目的.哈希速度很重要.

array代表indicies,所以在对象的真实身份并不重要,值.可变性不是一个问题,因为我只对当前价值感兴趣.

我应该散列什么才能将其存储在一个dict

我目前的方法是使用str(arr.data),这比md5我的测试更快.


我已经从答案中加入了一些例子来了解相对时间:

In [121]: %timeit hash(str(y))
10000 loops, best of 3: 68.7 us per loop

In [122]: %timeit hash(y.tostring())
1000000 loops, best of 3: 383 ns per loop

In [123]: %timeit hash(str(y.data))
1000000 loops, best of 3: 543 ns per loop

In [124]: %timeit y.flags.writeable = False ; hash(y.data)
1000000 loops, best of 3: 1.15 us per loop

In [125]: %timeit hash((b*y).sum())
100000 loops, best of 3: 8.12 us per loop
Run Code Online (Sandbox Code Playgroud)

对于这个特定的用例(小型标记阵列),似乎可以arr.tostring提供最佳性能.

虽然对只读缓冲区进行散列本身很快,但设置可写入标志的开销实际上会使其变慢.

Fre*_*Foo 41

如果将其设置为只读,则可以简单地对底层缓冲区进行哈希:

>>> a = random.randint(10, 100, 100000)
>>> a.flags.writeable = False
>>> %timeit hash(a.data)
100 loops, best of 3: 2.01 ms per loop
>>> %timeit hash(a.tostring())
100 loops, best of 3: 2.28 ms per loop
Run Code Online (Sandbox Code Playgroud)

对于非常大的阵列,hash(str(a))速度要快得多,但是它只需要考虑阵列的一小部分.

>>> %timeit hash(str(a))
10000 loops, best of 3: 55.5 us per loop
>>> str(a)
'[63 30 33 ..., 96 25 60]'
Run Code Online (Sandbox Code Playgroud)

  • 在Python 3.4中,我发现我必须使用``hash(a.data.tobytes())`` (17认同)
  • 注意:使用此方法计算的哈希值在进程之间发生更改.即`hash(np.array([0,1]).data.tobytes())`在不同的python进程中有不同的值.它不是从数组内容计算的确定性值.要获得确定性行为,您需要设置`PYTHONHASHSEED`环境变量.更多信息:/sf/ask/1926583851/ (6认同)
  • 使用 `hash(str(a))` 是危险的,会导致长数组出现错误的结果,因为只有数组的 **缩短** 字符串表示形式,即 `'[63 30 33 ..., 96 25 60] `, 被散列(这可能也是该方法更快的原因?)。特别是,具有相同起始值和结束值的所有数组“a”最终都会得到相同的哈希值。您能否在我们的回答中添加一个简短的注释? (4认同)
  • 如果您使用 hashlib,您 1. 将获得确定性值,2. 不必将数组设置为只读,3. 操作不限于任何格式:`hashlib.sha256( a.data )`。 (4认同)
  • 很抱歉迟到了,但使用 `hash(a.data.tobytes())` 作为 @ariddell 建议意味着我不必设置 `a.flags.writeable = false`。这样做的任何原因以及这样做的任何潜在问题? (3认同)
  • @SCB `tobytes()` 制作底层数据的完整副本(这对于大型数组来说可能很耗时),并返回一个字节数组,然后可以对其进行散列。所以如果你之后修改`a`,你将需要再次调用a.data.tobytes(),浪费更多的时间/内存。在 python 2 中,您使用 hash(a.data) ,它不获取数据的副本,并且它的散列被缓存,以便将来对 hash(a.data) 的调用立即返回。但这就是问题所在:更改 `a` 不会清除缓存,因此如果更改 `a`,哈希值是不正确的。我不确定在 Python 3 中是否可以使用无复制哈希。 (3认同)

Con*_* Ma 19

您可以尝试xxhash通过其Python绑定.对于大型阵列,这比它快得多hash(x.tostring()).

示例IPython会话:

>>> import xxhash
>>> import numpy
>>> x = numpy.random.rand(1024 * 1024 * 16)
>>> h = xxhash.xxh64()
>>> %timeit hash(x.tostring())
1 loops, best of 3: 208 ms per loop
>>> %timeit h.update(x); h.intdigest(); h.reset()
100 loops, best of 3: 10.2 ms per loop
Run Code Online (Sandbox Code Playgroud)

顺便说一句,在发布到Stack Overflow的各种博客和答案中,您会看到人们使用sha1md5作为哈希函数.出于性能方面的原因,这通常是接受的,因为那些"安全"的散列函数是相当缓慢的.只有当哈希冲突是最受关注的问题之一时,它们才有用.

然而,哈希冲突一直在发生.如果您只需要实现__hash__数据数组对象以便它们可以用作Python字典或集合中的键,我认为最好专注于__hash__自身的速度并让Python处理哈希冲突[1].

[1]您可能还需要覆盖__eq__,以帮助Python管理哈希冲突.你会想要__eq__返回一个布尔值,而不是一个布尔数组numpy.

  • 请注意,“xxhash”是确定性的,并且对于不同的 python 进程会产生相同的结果。“hash”的情况并非如此(默认情况下),它为每个新进程使用随机种子。请参阅 Harald Thomson 的评论或此 [SO 线程](/sf/ask/1926583851/)。 (2认同)

Jam*_*gan 12

如果你的np.array()代码很小并且处于紧密循环中,那么一种选择是完全跳过hash()np.array().data.tobytes()直接用作你的字典键:

grid  = np.array([[True, False, True],[False, False, True]])
hash  = grid.data.tobytes()
cache = cache or {}
if hash not in cache:
    cache[hash] = function(grid)
return cache[hash]
Run Code Online (Sandbox Code Playgroud)


hun*_*nse 8

来晚了,但对于大型数组,我认为一个不错的方法是随机对矩阵进行二次采样并对样本进行哈希处理:

def subsample_hash(a):
    rng = np.random.RandomState(89)
    inds = rng.randint(low=0, high=a.size, size=1000)
    b = a.flat[inds]
    b.flags.writeable = False
    return hash(b.data)
Run Code Online (Sandbox Code Playgroud)

我认为这比这样做更好hash(str(a)),因为后者可能会混淆中间有唯一数据但边缘有零的数组。

  • 进行 one-hot 编码的人会感到悲伤。 (3认同)