【问题标题】:python zlib - size of compressed string vs Shannon entropypython zlib - 压缩字符串的大小与香农熵
【发布时间】:2023-03-27 14:15:02
【问题描述】:

我试图更好地了解压缩算法(例如 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-bits。我不明白这一点,因为压缩的效果似乎比理论预期要好得多。

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

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

任何建议都会非常有用!

【问题讨论】:

  • 我将此作为1 更长声明的注释,正如您正确指出的那样,这将适用于真正的随机序列,这不是常见的情况。
  • zlib 引入了 6 个字节的开销,而 deflate 又增加了几个。双重压缩时,您将获得包含两次的开销字节。
  • 开销微不足道(根据文档,最大为 0.03%)。我报告的问题是我的压缩效率似乎比我应该能够的要高得多——大约 6 倍。作为旁注,在这种情况下,我尝试对np.random.bytes(N)compsize / rawsize 方法1 做同样的事情,也许它为正在发生的事情提供了一些有用的提示。

标签: python string compression zlib information-theory


【解决方案1】:

当调用np.random.randint(0, 2, l, dtype='uint8').tostring()时,你得到的不是0和1的随机序列,而是0和1的8位二进制表示的随机序列:10000000和@987654323 @。几乎每 8 位 1 是随机的,其他 7 位都是 0。我猜最佳比例应该是 1/8 左右,加上一些开销。

确实,如果改用np.random.randint(0, 256, 100000, dtype='uint8').tostring(),comp_ratio 为~1。

【讨论】:

  • 谢谢,当我发现比率大致为log2(k)/8 时,我也得出了结论,其中 k 是字典大小(在这种情况下是最大整数,对于 k=256,我得到 1 )。那么正确的做法是采用长度为 N 的 1 和 0 的序列 s1 并将每个 8 块转换为相应的整数,从而获得长度为 N/8 的序列 s2 吗?然后通过压缩s2,zlib 应该尝试压缩对应于s1 的位数组。
【解决方案2】:

您会发现,当您在输入中添加一位熵时,您会在压缩输出中添加 0.155bytes,即 1.24bits

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-02
    • 2014-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多