高效压缩numpy数组
我尝试了各种方法来压缩一些 numpy 数组
,以便将它们保存到磁盘上。
这些一维数组包含以特定采样率采样的数据(可以是用麦克风录制的声音,或者任何传感器的测量数据):这些数据本质上是 连续的(从数学的角度来看;当然,经过采样后现在变成了离散数据)。
我尝试了 HDF5
(使用 h5py 库):
f.create_dataset("myarray1", myarray, compression="gzip", compression_opts=9)
但是这个方法相当慢,而且压缩效果也不是我们期望的最好。
我还尝试了
numpy.savez_compressed()
但再一次,这可能不是处理这种数据(前面提到的)的最佳压缩算法。
对于这样的 numpy 数组
,你会选择什么方法来获得更好的压缩效果呢?
(我考虑过无损的 FLAC 格式(最初是为音频设计的),但有没有简单的方法可以将这样的算法应用到 numpy 数据上呢?)
6 个回答
什么是最佳压缩方式(如果有的话)其实很大程度上取决于数据的类型。很多测量数据在需要无损压缩的情况下,几乎是完全无法压缩的。
pytables的文档里有很多关于数据压缩的实用指南。它还详细说明了速度的权衡等问题;结果发现,压缩等级越高通常是浪费时间。
http://pytables.github.io/usersguide/optimization.html
需要注意的是,这可能就是压缩效果最好的情况了。对于整数测量数据,使用一种叫做“洗牌过滤器”的方法配合简单的zip类型压缩通常效果不错。这个过滤器非常有效地利用了一个常见的情况:最高位的字节通常是0,只有在防止溢出时才会被包含在内。
使用压缩功能保存HDF5文件可以非常快速和高效,这主要取决于你选择的压缩算法,以及你希望保存时快,读取时快,还是两者都快。还有,数据本身的特性也会影响速度,之前已经解释过了。
GZIP的速度一般,压缩比也不高。BZIP2在保存和读取时都比较慢,但压缩效果更好。BLOSC是我发现的一个压缩算法,它在压缩效果和速度上都表现不错。不过,BLOSC并不是所有HDF5的实现都支持,所以你的程序可能无法在不同环境中使用。
因此,你总是需要进行一些测试,以选择最适合你需求的配置。
我现在做的事情是:
import gzip
import numpy
f = gzip.GzipFile("my_array.npy.gz", "w")
numpy.save(file=f, arr=my_array)
f.close()
噪声是无法压缩的。因此,你的数据中如果有噪声,不管使用什么压缩算法,这部分噪声都会以1:1的比例进入压缩后的数据,除非你通过某种方式把它丢掉(比如有损压缩)。假设你有一个每个样本24位的数据,但有效位数(ENOB)只有16位,那么剩下的24-16=8位噪声会限制你最大无损压缩比为3:1,即使你的(无噪声的)数据是完美可压缩的。非均匀噪声可以根据它的非均匀程度进行压缩;你可能需要查看噪声的有效熵来判断它的可压缩性。
压缩数据的过程是基于对数据的建模(部分是为了去除冗余,部分是为了把噪声分离出来并丢掉噪声)。举个例子,如果你知道你的数据带宽限制在10MHz,而你以200MHz的频率进行采样,你可以进行快速傅里叶变换(FFT),把高频部分置零,只存储低频部分的系数(在这个例子中:10:1的压缩)。这方面有一个专门的领域叫做“压缩感知”。
一个实用的建议,适合许多种合理连续的数据:去噪 -> 带宽限制 -> 差分压缩 -> gzip(或xz等)。去噪可以和带宽限制是同一个过程,或者使用像移动中位数这样的非线性滤波器。带宽限制可以用FIR/IIR来实现。差分压缩就是y[n] = x[n] - x[n-1]。
编辑 一个示例:
from pylab import *
import numpy
import numpy.random
import os.path
import subprocess
# create 1M data points of a 24-bit sine wave with 8 bits of gaussian noise (ENOB=16)
N = 1000000
data = (sin( 2 * pi * linspace(0,N,N) / 100 ) * (1<<23) + \
numpy.random.randn(N) * (1<<7)).astype(int32)
numpy.save('data.npy', data)
print os.path.getsize('data.npy')
# 4000080 uncompressed size
subprocess.call('xz -9 data.npy', shell=True)
print os.path.getsize('data.npy.xz')
# 1484192 compressed size
# 11.87 bits per sample, ~8 bits of that is noise
data_quantized = data / (1<<8)
numpy.save('data_quantized.npy', data_quantized)
subprocess.call('xz -9 data_quantized.npy', shell=True)
print os.path.getsize('data_quantized.npy.xz')
# 318380
# still have 16 bits of signal, but only takes 2.55 bits per sample to store it