数据压缩和信息熵.

压缩的原理及有限性

在计算机导论中简要介绍过几种压缩的算法,原理十分简单,便是利用更短、熵更高的字符串来代替一些重复率高,熵较低的字符串,从而实现缩短字节量,减小文件的体积的目的。步骤大致如下:

  1. 得到文件的概率分布,统计频次高的以及频次低的部分;
  2. 结合统计结果对源文件重新编码,用短字符代替重复的长字符。

举个例子:

AAAAAAAAAA => 10A  // 用 3 个字符代表 10 个字符,压缩比 30%
ABCABCABCABCABCABCABCABC => 8ABC  // 用 4 个字符代表 24 个字符,压缩比 16.7%

显而易见,越是重复率高的文件,就意味着能够压缩的量就越大,体积也自然能够越小;反之,如果一个文件的重复率低,也就越难压缩。所以每一个文件的熵不同,压缩比率也会不尽相同。

那么,压缩有一定的限度吗?

当然,在信息论中我们学到过,无损压缩实现的前提是输出和输入必须要严格一一对应,也就是不能够出现多对一的映射。我们可以假设任意文件都能够压缩到 $n$ bits,那么就能够产生 $2^n$ 种可能的压缩结果。这便意味着如果有 $2^n+m$ 个不同的文件,就会导致有 $m+1$ 个文件出现了重复,所以压缩一定存在着极限

压缩的极限

一个文件的压缩极限可以通过计算文件的平均信息熵大小,从而推导出来。当然这个最小值仅仅是理论最小值,并不一定能够达到,下面我将用浅显的方式做一些推导。

在不同的文件格式中可能存在着不同的计算方法,比如文本文件可能就会根据文本中的字符计算信息熵,图片会使用 RGB 排列来计算信息熵等。我这里使用了一个通用的方法——将任意文件按字节的形式进行读取,统计不同字节的信息熵

计算平均信息熵的公式为:

$$H(X)=\sum P_n * \log(\frac{1}{P_n})$$

$P_n$ 为每一个字节在文件中出现的概率,计算方法如下:

$$P_n=\frac{N_{freq}}{N_{all}}$$

$N_{freq}$ 为该字节在文件中出现的次数,$N_{all}$ 文件的总字节量。

其中的 $log$ 在信息论中是默认以 2 为底的(在通信原理中是默认以 10 为底),通常会做省略处理。

最后求出来的 $H(X)$ 是文件每一个字节的平均信息熵,如果乘以总字节数就可以得到理论的压缩极限大小

$$Size_{min}=H(X) * N_{all}$$

信息熵的含义

  1. 信息熵只反映内容的随机性,与内容本身无关。不管是什么文件,服从同样的概率分布就会得到同样的信息熵。
  2. 信息熵越大,表示占用的二进制位越长,可以表达更多的符号。即信息熵越大,信息量就越大,但这并不代表所获得的的信息越大。

代码实现 (Python)

import sys
from math import log
from argparse import ArgumentParser
from os.path import dirname, abspath, realpath


def main():
    # Initialization
    real_path = dirname(realpath(sys.executable))
    parser = ArgumentParser()
    parser.add_argument('address')
    args = parser.parse_args()
    path = abspath(real_path + '\\' + args.address)
    file_format = path.split('.')[-1]

    # Read the file
    try:
        f = open(path, "rb")
        byteArr = list(f.read())
        fileSize = len(byteArr)
        f.close()
    except IOError:
        path = args.address
        f = open(path, "rb")
        byteArr = list(f.read())
        fileSize = len(byteArr)
        f.close()

    # core algorithm for shannon entropy
    ent = 0
    for b in range(256):
        ctr = 0
        for byte in byteArr:
            if byte == b:
                ctr += 1
        freq = ctr / fileSize
        if freq > 0:
            ent = ent - freq * log(freq, 2)

    # print the information
    print('\n==================== File Information ====================')
    print('Location: ', path)
    print('  Format: ', file_format)
    print('    Size: ', fileSize, 'Bytes')
    print('\n================= Amount of Information =================')
    print('Average: ', round(ent, 2))
    print('  Total: ', round(ent * fileSize, 2))


if __name__ == '__main__':
    main()

代码直接使用命令行运行即可,格式为 python main.py test.txt。其中 test.txt 为你需要计算信息熵的文件。 代码对地址作了处理,使用绝对路径和相对路径皆可。