python zlib - 压缩字符串的大小与香农熵

时间:2017-05-23 21:44:38

标签: python string compression zlib information-theory

我试图更好地理解压缩算法的输出 - 例如zlib - 如何与一个人的理论预期相比较。所以我有几个问题。

(1)首先,我想检查一下我是否正确计算了压缩率。假设我想压缩1000个数组,我可以执行以下操作

# encode the array such that len(s) == 1000 bytes
s = np.ones(1000, dtype='uint8').tostring()

# compress using the python zlib (deflate)
comp_s = zlib.compress(s, 9) 
# giving comp_s = 'x\xdacd\x1c\x05\xa3`\x14\x0cw\x00\x00\xa7e\x03\xe9'

comp_ratio = len(comp_s)/len(s)
# giving 17/1000

因此我的第一个问题是:comp_s编码,使其长度对应于字节数?我无法弄清楚这个字符串是如何编码的。如果我sys.getsizeof(comp_s)我发现它的大小是54字节而不是17字节?由于getsizeof返回python对象的大小,所以它肯定高估了字符串的大小,我是否正确假设sys.getsizeof(s) - sys.getsizeof('')是正确的方法?它似乎至少会产生与len()相同的结果。

(2)压缩序列的大小应大于(或等于)其香农熵。对于以50:50概率发生的1和0的随机二进制序列,每个数字的信息数是1比特(根据定义h = - p log p - (1-p)log(1-p))。由于真正的随机序列是不可压缩的,如果我生成一个长度为n的随机二进制序列,我希望通过添加一个随机数字,得到的n+1长序列平均后会大1位。压缩。

当我执行以下操作时

rawsize = range(1, 100000, 1000)
compsize = []
for l in rawsize:
    s = np.random.randint(0, 2, l, dtype='uint8').tostring() 
    comp_s = zlib.compress(s, 9)
    # note: I compress again to achieve better compression when l is large
    comp_s = zlib.compress(comp_s, 9)
    compsize.append(len(comp_s))

如果我绘制compsize / rawsize,我发现曲线接近0.155附近的常数值,意味着(如果我正确解释),通过添加一位数,信息量增加0.155 - 位。我不明白这一点,因为看起来压缩比理论预期要好得多。

为了进一步理解这一点,我还比较了压缩的字符串大小,用于1和0的二进制序列,其中1的概率为0<p<1。然后,字符串的压缩大小(每个数字)应该跟踪香农熵并且在(=1)处是最大p=0.5。我发现压缩字符串大小(每个数字)的曲线远低于香农熵,如果我将香农熵乘以0.155,它们大致位于彼此之上。

显然有一些我没有考虑的标准化因素,但我无法弄清楚它的基本原理。我还尝试使用163264位无符号整数对原始序列进行编码,发现比率compsize / rawsize大致为0.176,{{ 1}},0.2,所以看起来通过在1和0的表示中添加一个字节,我们贡献了大约0.23位的额外信息,这也很奇怪。

任何建议都非常有用!

2 个答案:

答案 0 :(得分:2)

当调用np.random.randint(0, 2, l, dtype='uint8').tostring()时,你没有获得0和1的随机序列,而是随机序列的 0和1的8位二进制表示:{{1} }和10000000。每8位几乎是1个是随机的,其他7个都是0。我想最佳比例应该是大约1/8,加上一些开销。

实际上,如果改为使用00000000,则comp_ratio为~1。

答案 1 :(得分:1)

您发现当您向输入添加一位熵时,您将0.155 字节添加到压缩输出,即1.24