Numpy:翻译元素会大大增加文件的大小(8 倍)

Numpy : Translating elements increases size of file by a lot (factor of 8)

我有一个仅包含值 0、1 和 2 的 3D 数组,我想将这些值分别转换为 0,128 和 255。我环顾四周,这个线程 ( Translate every element in numpy array according to key ) 似乎是要走的路。

所以我尝试实现它并且成功了,代码的相关部分可以在下面看到(我从 h5 文件读取数据和向 h5 文件写入数据,但我怀疑这是否重要,我只是提到它以防万一)

#fetch dataset from disk
f = h5py.File('input/A.h5','r') #size = 572kB

#read and transform array
array = f['data'].value  #type = numpy.ndarray
my_dict = {1:128, 2:255, 0:0}
array=np.vectorize(my_dict.get)(array)

#write translated dataset to disk
h5 = h5py.File('output/B.h5', driver=None) #final size = 4.5MB
h5.create_dataset('data', data=array)  
h5.close()

问题是,输入文件 (A.h5) 的大小为 572kB,输出文件 (B.h5) 的大小是它的 8 倍 (4.5MB)。

这是怎么回事?我有另一个具有相同维度的数组,其中充满了从 0 到 255 的值,它的大小也是 572kB,因此数字越大无关紧要。我的第一个猜测是 python 可能正在创建对象而不是整数,我尝试转换为 int 但大小保持不变。

旁注:如果我使用 3 个缩进 for 循环转换数据,则大小保持为 572kB(但代码要慢得多)

通过将数组写回为 int64(其中原始数组存储为 uint8),您可能会得到 8 倍。你可以试试:

array=np.vectorize(my_dict.get)(array).astype(np.uint8)

然后保存到 h5...

正如@Jaime 指出的那样,您可以通过直接告诉 vectorize 您想要的数据类型来保存数组副本:

array=np.vectorize(my_dict.get, otypes=[np.uint8])(array)

虽然链接的 SO 接受的答案使用 np.vectorize,但它并不是最快的选择,尤其是在这种情况下,您只需替换 3 个小数字 0,1,2。

SO 问题中的新答案提供了一种简单快速的索引替代方法:

In [508]: x=np.random.randint(0,3,(100,100,100))
In [509]: x.size
Out[509]: 1000000
In [510]: x1=np.vectorize(my_dict.get, otypes=['uint8'])(x)
In [511]: arr=np.array([0,128,255],np.uint8)
In [512]: x2=arr[x]
In [513]: np.allclose(x1,x2)
Out[513]: True

比较他们的时间:

In [514]: timeit x1=np.vectorize(my_dict.get, otypes=['uint8'])(x)
10 loops, best of 3: 161 ms per loop
In [515]: timeit x2=arr[x]
100 loops, best of 3: 3.48 ms per loop

索引方法要快得多。

关于 np.vectorize 有几件事用户经常会错过。

  • 速度免责声明;与显式迭代相比,它并不保证显着的速度。不过,它确实使多维数组的迭代更容易。

  • 没有otypes,它通过测试计算确定return数组的类型。有时,默认设置会导致问题。这里指定 otypes 只是为了方便,立即为您提供正确的数据类型。

出于好奇,下面是列表理解方法的时间:

In [518]: timeit x3=np.array([my_dict[i] for i in x.ravel()]).reshape(x.shape)
1 loop, best of 3: 556 ms per loop

h5py 允许您在保存数据集时指定 dtype。当我以不同的方式保存数组时,请注意 type

In [529]: h5.create_dataset('data1',data=x1, dtype=np.uint8)
Out[529]: <HDF5 dataset "data1": shape (100, 100, 100), type "|u1">
In [530]: h5.create_dataset('data2',data=x1, dtype=np.uint16)
Out[530]: <HDF5 dataset "data2": shape (100, 100, 100), type "<u2">
In [531]: h5.create_dataset('data3',data=x1)
Out[531]: <HDF5 dataset "data3": shape (100, 100, 100), type "|u1">
In [532]: x.dtype
Out[532]: dtype('int32')
In [533]: h5.create_dataset('data4',data=x)
Out[533]: <HDF5 dataset "data4": shape (100, 100, 100), type "<i4">
In [534]: h5.create_dataset('data5',data=x, dtype=np.uint8)
Out[534]: <HDF5 dataset "data5": shape (100, 100, 100), type "|u1">

因此,即使您没有在 vectorize 中指定 uint8,您仍然可以使用该类型保存它。