有效压缩简单的二进制数据


27

我有一个文件,其中包含从到有序二进制数字:2 n102n1

0000000000
0000000001
0000000010
0000000011
0000000100
...
1111111111

7z不能非常有效地压缩该文件(对于n = 20,22 MB被压缩为300 kB)。

是否有可以识别非常简单的数据结构并将文件压缩到几个字节的算法?我也想知道CS或信息理论的哪个领域研究这种智能算法。“ AI”的范围太广,请提出更具体的关键字。
对称性概念应该在数据压缩中起基本作用,但是搜索查询“数据压缩中的对称性”和“数据压缩中的群论”令人惊讶地几乎没有任何相关性。


11
检查一下Kolmogorov的复杂度,从某种意义上说,这是最佳压缩率(最高累加常数)。不幸的是,Kolmogorov的复杂性无法计算……
Yuval Filmus

12
为什么需要压缩这些数据?您能否在需要时随时重新生成它?(这与@YuvalFilmus提到的Kolmogorov复杂度方法密切相关:Kolmogorov复杂度本质上是生成输出的最短程序的长度)。
大卫·里希比

7
我20年前在高中时确实写过这样的算法。给定您的输入,它将被压缩为“几个字节”(最佳情况下约为3,500,000:1)。但是,数据很少像这样,因此拥有这样的算法是不切实际的。通用压缩算法必须处理复杂的模式,而不是简单的模式。任何人都可以编写一种算法来存储线性数据,但是存储有趣的数据是一个挑战。
phyrfox

3
n = 20如何给您22MB?如果使用4个字节的整数,我将获得4.2 MB的
内存

3
@JiK哦,好的。那将是第一个压缩概念,而不是使用8位来表示单个位。
njzk2

Answers:


27

这似乎是增量压缩的明确用例。如果先验已知则这是微不足道的:逐字存储第一个数字,对于每个下一个数字,仅存储与前一个数字的。就您而言,这将给n

0
1
1
1
1
...

然后,可以使用简单的游程长度编码将其存储在空间中,因为只有组(即两个)不同的增量。O1 O(n)O(1)

如果不知道,最简单的事情就是用暴力搜索单词大小,而该单词大小/游程长度表示最短。也许只搜索随机选择的大小的块,以分摊找到的开销,同时保持良好的可靠性。n ñNn

与DW的“全有还是全无”提议不同,采用行程编码的增量压缩实际上可以为某些简单的现实世界中的内容(例如低分辨率音频)提供合理的压缩率。(因此,它适用于低质量,极低延迟和低功耗的音频压缩。)


44

当然,当然有算法。这是我的算法:

  1. 首先,检查文件中是否包含从到有序二进制数(对于。如果是这样,则写出一个0位,后跟 1位,再写一个0位。2 n1 n n02n1nn

  2. 如果不是,写出一位,然后写出文件的7z压缩。

这对于具有特定结构的文件非常有效。

关键是:在数据压缩方面没有免费的午餐。您也许可以构建一种压缩算法,以很好地压缩一种类型的文件,但代价是压缩其他类型的文件更糟。但是,如果您事先了解将要压缩的文件的性质,则可以针对该特定类型的文件优化算法。

该区域是“数据压缩”。请参阅我们的标签,并阅读有关数据压缩的教科书。


5
压缩器的工作是识别常见的模式并加以利用。并非这种模式并不罕见或晦涩。因此,问为什么不加以利用是一个自然的问题。告诉他需要权衡取舍,或者给他一个算法,该算法会使所有事情都失败,除了这种模式是彻底的失败。
Mehrdad

17
当然,这对我来说并不常见。与好的压缩器确实会寻找的模式相比,这种情况很少会出现在现实生活的数据中。
合金38年

17
@Mehrdad这不是一个笨拙的解决方案:这是重点。对于任何简单生成和检查的模式X,都有一种压缩算法来查找该模式并进行处理。因此,这就是“是否存在处理此类X的压缩算法?”这一问题的答案。当然,总有一种算法会寻找稍微更复杂的模式。而且,还有一种模式比无穷的模式还要复杂一些。您的反对是没有根据的。
大卫·里希比

评论不作进一步讨论;此对话已转移至聊天
吉尔斯(Gillles)'所以

该原理的一个极端应用是bittorrent磁链,其中任何大小的文件或文件集合都可以简单地表示(压缩)为固定的160位数据。当然存在散列冲突可能发生的风险。
slebetman'5

17

任何使用BWT(Burrows-Wheeler变换)的东西都应该能够很好地压缩它。

我的快速Python测试:

>>> import gzip
>>> import lzma
>>> import zlib
>>> import bz2
>>> import time
>>> dLen = 16
>>> inputData = '\n'.join('{:0{}b}'.format(x, dLen) for x in range(2**dLen))
>>> inputData[:100]
'0000000000000000\n0000000000000001\n0000000000000010\n0000000000000011\n0000000000000100\n000000000000010'
>>> inputData[-100:]
'111111111111010\n1111111111111011\n1111111111111100\n1111111111111101\n1111111111111110\n1111111111111111'
>>> def bwt(text):
    N = len(text)
    text2 = text * 2
    class K:
        def __init__(self, i):
            self.i = i
        def __lt__(a, b):
            i, j = a.i, b.i
            for k in range(N): # use `range()` in Python 3
                if text2[i+k] < text2[j+k]:
                    return True
                elif text2[i+k] > text2[j+k]:
                    return False
            return False # they're equal

    inorder = sorted(range(N), key=K)
    return "".join(text2[i+N-1] for i in inorder)

>>> class nothing:
    def compress(x):
        return x

>>> class bwt_c:
    def compress(x):
        return bwt(x.decode('latin_1')).encode('latin_1')

>>> for first in ('bwt_c', 'nothing', 'lzma', 'zlib', 'gzip', 'bz2'):
    fTime = -time.process_time()
    fOut = eval(first).compress(inputData)
    fTime += time.process_time()
    print(first, fTime)
    for second in ('nothing', 'lzma', 'zlib', 'gzip', 'bz2'):
        print(first, second, end=' ')
        sTime = -time.process_time()
        sOut = eval(second).compress(fOut)
        sTime += time.process_time()
        print(fTime + sTime, len(sOut))

bwt_c 53.76768319200005
bwt_c nothing 53.767727423000224 1114111
bwt_c lzma 53.83853460699993 2344
bwt_c zlib 53.7767307470001 5930
bwt_c gzip 53.782549449000044 4024
bwt_c bz2 54.15730512699997 237
nothing 6.357100005516259e-05
nothing nothing 0.0001084830000763759 1114111
nothing lzma 0.6671195740000258 27264
nothing zlib 0.05987233699988792 118206
nothing gzip 2.307255977000068 147743
nothing bz2 0.07741139000017938 187906
lzma 0.6767229399999906
lzma nothing 0.6767684639999061 27264
lzma lzma 0.6843232409999018 27324
lzma zlib 0.6774435929999072 27280
lzma gzip 0.6774431810001715 27292
lzma bz2 0.6821310499999527 27741
zlib 0.05984937799985346
zlib nothing 0.05989508399989063 118206
zlib lzma 0.09543156799986718 22800
zlib zlib 0.06264000899977873 24854
zlib gzip 0.0639041649999399 24611
zlib bz2 0.07275534999985211 21283
gzip 2.303239570000187
gzip nothing 2.303286251000145 147743
gzip lzma 2.309592880000082 1312
gzip zlib 2.3042639900002087 2088
gzip gzip 2.304663197000309 1996
gzip bz2 2.344431411000187 1670
bz2 0.07537686600016968
bz2 nothing 0.07542737000017041 187906
bz2 lzma 0.11371452700018381 22940
bz2 zlib 0.0785322910001014 24719
bz2 gzip 0.07945505000020603 24605
bz2 bz2 0.09332576600013454 27138

(这里的数字是“ first_compressor second_compressor time_taken bytes_out”)

(BWT从这里拍摄)

这仍然是“不仅是几个字节”,但比仅使用gzip更好。例如,对于16位输入,BWT + bz2从1114111减少到237个字节。

遗憾的是,对于许多应用程序而言,BWT太慢且占用大量内存。尤其是考虑到这是Python的幼稚实现-在我的机器上,我在2 ** 20之前用完RAM。

使用Pypy,我可以运行完整的2 ** 20输入,并使用BWT和bz2将其压缩为2611字节。但是要花费3分钟以上的时间,并使用超过4GB的RAM。

同样不幸的是,这种方法仍然是O(2 ^ n)输出空间,至少会出现在曲线拟合1..20中。


您可以eval这样做:for first in (bwt_c, nothing, lzma, zlib, gzip, bz2):和摆脱fOut = first.compress(inputData)
卡巴斯德(Kasperd)'17年

@kasperd-在这种情况下,我将如何打印名称?就个人而言,与评估名称和引用保持同步相比,进行评估更简单(并且更容易出错)。
TLW

5
首先bwt,然后bz2将其压缩得非常好。这是非常奇怪的行为,可能是由于这种确切的模式。请注意,您要执行两次 bwt (bz2基于BWT),这通常会导致压缩效果更差。还要注意,今天bwt通常以4 times block size内存(例如〜4MB)和>10 MB/s(我是这种bwt库/压缩算法的作者)的速度运行,这在许多应用程序中都非常有用。请注意,即使gzip也会产生很好的可压缩结果。感谢您的分享,我不知道有两次使用bwt的研究。
克里斯多夫(Christoph)

3
@Christoph-我知道bz2是基于BWT的...我实际上已经开始写一个关于“只使用bz2”效果的答案,但是发现它的压缩程度不如我预期的高,所以”,并决定看看我自己的BWT是否会做得更好。只有我需要输出的压缩器,然后说“可以尝试使用不同的压缩器以查看会发生什么”。
TLW

1
@Christoph-我又看了一眼。2 bwts的数据会生成一些非常适合RLE编码的东西。如图所示,如果您在16位输入上计算0、1、2,...嵌套BWT所需的RLE对的数量,则会得到622591 1081343 83 ...
TLW

10

PNG编码正是您想要的。它也适用于现实生活中的数据,而不仅仅是非常有组织的数据。

在PNG中,每行都使用一个过滤器进行编码,其中指定了4个。其中之一是“将该像素编码为其值与其上方像素之间的差值”。过滤后,然后使用DEFLATE压缩数据。

此过滤是他的答案中leftaroundabout提到的Delta编码的一个特定示例,除了可以使用功能更强大的DEFLATE算法来跟踪,而不是使用运行长度编码来跟踪它。它实现了相同的目标,只有DEFLATE可以处理多种输入,同时仍提供所需的压缩比。

在简单的filter + DEFLATE效果不甚理想的科学数据中,经常使用的另一种工具是RICE编码。在RICE中,您要获取一个值块,然后首先输出所有最高有效位,然后输出所有所有第二高有效位,一直到最低有效位。然后,您压缩结果。对于您的数据,其效果不如PNG样式过滤(因为您的数据非常适合PNG过滤),但是对于许多科学数据,它往往会产生良好的结果。在许多科学数据中,我们看到最高有效位倾向于缓慢变化,而最低有效位几乎是随机的。这将高度可预测的数据与高度熵的数据分开。

0000000000       00000  most significant bits
0000000001       00000
0000000010  =>   00000
0000000011       00000
0000000100       00000
                 00000
                 00000
                 00001
                 00110
                 01010 least significant bits

5

任何搜索特定结构的实用算法都将仅限于硬编码到其中的结构。您可以打补丁7z来识别此特定序列,但是这种特定结构在现实生活中会发生多少次?经常不足以保证检查此输入的时间。

除了实用性,人们还可以将理想的压缩器构想成一种算法,它试图构建产生给定输出的最短程序。不用说,没有实际的方法可以做到这一点。即使您尝试了所有可能程序的强力枚举并检查它们是否产生了所需的输出(不是完全疯狂的想法),您仍会遇到Halting问题,这意味着您必须在一定数量后中止试运行执行步骤,然后才知道该程序是否绝对不能产生所需的输出。

这种暴力破解方法的搜索树随着程序的长度呈指数增长,除了最简单的程序(大约长5到7条指令)之外,并不适合所有应用。


这不仅是检查这种输入所花费的时间:它还是您用于“然后以二进制形式计数为 ”的任何编码方案所占用的空间,尤其是这种具有的连锁效应在压缩的其他每个部分上,都必须加上“这不算二进制数”。n
大卫·里奇比

1
此外,您可以使用“燕尾式”技术处理非暂停程序,而不是中止程序。为此,您需要对前程序运行步骤,然后对前程序运行步骤,依此类推。这意味着您可以对每个程序执行多个步骤,而这些步骤没有先验的界限,但是不必等待一个程序暂停就可以尝试下一个程序。n n + 1 n 1nnn+1n1
David Richerby '17

好吧,像Mathematica这样的工具可以找到许多序列的函数……
Raphael

3

压缩率完全取决于目标解压缩器。如果解压缩器无法比每个数字4个字节更紧凑地解码连续4个字节的数字,那么您就是SOL。

有各种各样的事情可以允许对序号进行编码。例如差分编码。一次取n个字节,然后取这些位的差或异或,然后压缩结果。在这里,这增加了4个选项,试图为每个字节计数:身份a'[i] = a[i]; 差异a'[i] = a[i-1]-a[i]; 反向差a'[i] = a[i]-a[i-1]; 和xor a'[i] = a[i]^a[i-1]。这意味着要添加2位来选择方法,并要为4个选项中的3个添加一个字节数。

但是,并非所有数据都是固定长度记录的序列。差分编码对此没有任何意义(除非压缩器可以凭经验证明它适用于少量数据)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.