使numpy.savez确定性

时间:2017-11-07 22:00:01

标签: python numpy

我很惊讶地发现,如果使用numpy.savez将相同的numpy对象保存到文件,则创建的文件不具有确定性。 例如,

import numpy
x = numpy.random.rand(1000, 1000)
numpy.savez('foo.npz', x)
numpy.savez('bar.npz', x)

然后

md5sum foo.npz bar.npz

d1b8b7d2000055b8bf62dddc4a5c77b5  foo.npz
1c6e13bb9efca3ec144e81b88b6cdc75  bar.npz

阅读this它看起来与npz zip文件中的时间戳有关。

出于测试目的,我想验证我的代码创建的数据文件是否相同。我通常使用pickle文件的校验和来执行此操作,例如

import cPickle as pickle
with open('foo.pkl', 'wb') as f:
    pickle.dump(x, f, protocol=2)

with open('bar.pkl', 'wb') as f:
    pickle.dump(x, f, protocol=2)

然后

 md5sum foo.pkl bar.pkl
 3139d9142d57bdde0970013f39b4854f  foo.pkl
 3139d9142d57bdde0970013f39b4854f  bar.pkl

使用numpy.savez做同样的事情是否有任何解决方法?

2 个答案:

答案 0 :(得分:2)

如果您确实没有将关键字参数传递给np.savez(即实际上只是序列化您的数据,而不想以后根据键引用这些项),您可以将多个数组转储到同一个数据库中文件np.save

import numpy as np
import time

def mysavez(outfile,*args):
    with open(outfile,'wb') as outf:
        for arg in args:
            np.save(outf,arg)

x = np.random.rand(1000,1000)

# control group
np.savez('foo.npz', *[x]*5)
time.sleep(2) # make sure there's a difference in timestamp
np.savez('bar.npz', *[x]*5)

# new one
mysavez('foo.nopz', *[x]*5)
time.sleep(2) # make sure there's a difference in timestamp
mysavez('bar.nopz', *[x]*5)

生成的新文件具有相同的哈希值,它们甚至具有与原始文件完全相同的大小:

$ md5sum foo.npz bar.npz
4d21c47903b4ffab945f619ad5b6f471  foo.npz
f9af863c6178765d6dc32a5fa2f63623  bar.npz
$ md5sum foo.nopz bar.nopz
c8504f0d8cc53956100912efb02573b0  foo.nopz
c8504f0d8cc53956100912efb02573b0  bar.nopz
$ du {foo,bar}.n*pz
39064   foo.nopz
39064   foo.npz
39064   bar.nopz
39064   bar.npz

只要您从文件中顺序读取变量,就不会发现功能差异。当然,你需要一个myload来生成保存的数组,直到它们全部消失(或者更加奇特并保存一个初始整数标题,告诉你保存到文件中的数组的数量)。这种方法无疑是愚蠢的,但它可能会根据您的确切用例来削减它。

如果你想要使用键访问你保存的变量,你仍然可以考虑编写一个辅助函数来测试哪个读取“生产”.npz文件,迭代它们的命令键,使用上面的mysavez函数按顺序保存它们,然后计算这些“扁平”pickle文件的哈希值。当然,您可能不需要np.savepickle可以为您做同样的事情(尽管cpickle might not)。

答案 1 :(得分:0)

这可以通过numpy记录/结构来实现,唯一的限制是每个数组的第一维必须相同。

下面是用于使其与numpy记录一起使用的代码段,因此您甚至可以使用kwargs。

import numpy as np
import time
import hashlib

def md5(fname):
    hash_md5 = hashlib.md5()
    with open(fname, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

def mysavez(outfile,**kwargs):
    # sort the keys
    _sorted_keys = list(kwargs.keys())
    _sorted_keys.sort()

    # len of first element ... and check if it is same for all elements
    _len = kwargs[_sorted_keys[0]].shape[0]
    for k, v in kwargs.items():
        if v.shape[0] != _len:
            raise Exception(
                f"While creating numpy struct all arrays must have same length."
                f"invalid shape {v.shape} for item {k}"
            )

    # create numpy record buffer
    npy_record = np.zeros(
        _len,
        dtype=[
            (k, kwargs[k].dtype,  kwargs[k].shape[1:])
            for k in _sorted_keys
        ],
    )

    # fill up the elements
    for k, v in kwargs.items():
        npy_record[k] = v

    # save
    with open(outfile, 'wb') as outf:
        np.save(outf, npy_record)

a = np.random.rand(1000,1000)
b = np.random.rand(1000,1000)

# new one
mysavez('foo.nopz', a=a, b=b)
time.sleep(2) # make sure there's a difference in timestamp
mysavez('bar.nopz', a=a, b=b)

# check hash
print('foo.nopz', md5('foo.nopz'))
print('bar.nopz', md5('bar.nopz'))

输出:

foo.nopz b7759b6a60f135c393954e530fb5604b
bar.nopz b7759b6a60f135c393954e530fb5604b