Hiệu quả nén dữ liệu nhị phân đơn giản


27

Tôi có một tệp chứa các số nhị phân được đặt hàng từ đến 2 n - 1 :02n1

0000000000
0000000001
0000000010
0000000011
0000000100
...
1111111111

7z không nén tệp này rất hiệu quả (với n = 20, 22 MB được nén thành 300 kB).

Có thuật toán nào có thể nhận ra cấu trúc dữ liệu rất đơn giản và nén tệp thành nhiều byte không? Ngoài ra tôi muốn biết khu vực nào của CS hoặc lý thuyết thông tin nghiên cứu các thuật toán thông minh như vậy. "AI" sẽ quá rộng, vui lòng đề xuất các từ khóa cụ thể hơn.
Cảm giác đối xứng sẽ đóng vai trò cơ bản trong nén dữ liệu, nhưng các truy vấn tìm kiếm "đối xứng trong nén dữ liệu" và "lý thuyết nhóm trong nén dữ liệu" đáng ngạc nhiên trả về hầu như không có gì liên quan.


11
Kiểm tra độ phức tạp Kolmogorov, trong một số ý nghĩa là nén tối ưu (lên đến hằng số phụ gia). Thật không may, độ phức tạp Kolmogorov không thể tính toán được ...
Yuval Filmus

12
Tại sao bạn cần nén dữ liệu đó? Bạn có thể tái tạo nó bất cứ lúc nào bạn cần không? (Liên quan chặt chẽ đến phương pháp phức tạp Kolmogorov được đề cập bởi @YuvalFilmus: độ phức tạp Kolmogorov về cơ bản là độ dài của chương trình ngắn nhất tạo ra đầu ra).
David Richerby

7
Tôi đã viết một thuật toán như vậy ở trường trung học, 20 năm trước. Với đầu vào của bạn, nó sẽ nén thành "vài byte" (khoảng 3.500.000: 1 trong một kịch bản tối ưu). Tuy nhiên, dữ liệu hiếm khi giống như thế này, vì vậy không thực tế khi có một thuật toán như thế này. Các thuật toán nén chung phải xử lý các mẫu phức tạp, không đơn giản. Bất cứ ai cũng có thể viết một thuật toán để lưu trữ dữ liệu tuyến tính, nhưng lưu trữ dữ liệu thú vị là thách thức.
phyrfox

3
Làm thế nào để n = 20 cung cấp cho bạn 22MB? Tôi nhận được 4.2 MB nếu sử dụng số nguyên 4 byte
njzk2

3
@JKK oh, ok. tốt, đó sẽ là một khái niệm đầu tiên về nén, không sử dụng 8 bit để biểu diễn một bit đơn.
njzk2

Câu trả lời:


27

Đây có vẻ là một trường hợp sử dụng rõ ràng để nén delta . Nếu được biết là một tiên nghiệm thì đây là chuyện nhỏ: lưu trữ nguyên văn số đầu tiên và đối với mỗi số tiếp theo chỉ lưu trữ sự khác biệt so với trước. Trong trường hợp của bạn, điều này sẽ cung cấp chon

0
1
1
1
1
...

Điều này sau đó có thể với mã hóa độ dài chạy đơn giản được lưu trữ trong không gian , vì chỉ có O ( 1 )O(n)O(1) các nhóm (cụ thể là hai) của các vùng đồng bằng khác nhau.

Nếu không được biết, điều đơn giản nhất sẽ là tìm kiếm mạnh mẽ cho kích thước từ mà biểu diễn delta / run-length này xuất hiện ngắn nhất. Có lẽ chỉ thực hiện tìm kiếm này cho lựa chọn ngẫu nhiên,n khối N -sized, để khấu hao chi phí tìm kiếmnNn trong khi vẫn giữ được độ tin cậy tốt.

Không giống như tất cả hoặc không có gì đề xuất của DW, nén delta với mã hóa độ dài thực sự có thể đưa ra tỷ lệ nén hợp lý cho một số loại nội dung trong thế giới thực đơn giản, như âm thanh độ phân giải thấp. (Do đó, nó phù hợp để nén âm thanh chất lượng thấp, độ trễ rất thấp và công suất thấp.)


44

Chắc chắn, tất nhiên có thuật toán. Đây là thuật toán của tôi:

  1. Trước tiên, hãy kiểm tra xem tệp có chứa các số nhị phân được đặt hàng từ đến 2 n - 1 không , đối với một số n . Nếu vậy, hãy viết ra một bit 0 theo sau là n một bit theo sau là 0 bit.02n1nn

  2. Nếu không, hãy viết ra 1 bit, sau đó viết ra nén 7z của tệp.

Điều này là cực kỳ hiệu quả cho các tập tin của cấu trúc cụ thể đó.

Vấn đề là: không có bữa ăn trưa miễn phí trong việc nén dữ liệu. Bạn có thể xây dựng một thuật toán nén giúp nén một loại tệp tốt, với chi phí nén các loại khác tệ hơn. Nhưng, nếu bạn biết một tiên nghiệm gì đó về bản chất của các tệp bạn sẽ nén, bạn có thể tối ưu hóa thuật toán của mình cho loại tệp cụ thể đó.

Khu vực này là "nén dữ liệu". Xem thẻ của chúng tôi và đọc sách giáo khoa về nén dữ liệu.


5
Công việc của máy nén là nhận ra các mẫu phổ biến và khai thác chúng. Nó không giống như mô hình này là không phổ biến hoặc tối nghĩa. Vì vậy, đó là một câu hỏi tự nhiên để hỏi tại sao nó không được khai thác. Nói với anh ta rằng có một sự đánh đổi hoặc đưa cho anh ta một thuật toán thất bại trên tất cả mọi thứ ngoại trừ mô hình đó là một sự đồng thanh toán hoàn toàn.
Mehrdad

17
Chắc chắn có vẻ không phổ biến với tôi. Điều này sẽ xuất hiện trong dữ liệu thực tế rất hiếm khi, so với các kiểu máy nén tốt tìm kiếm.
amalloy

17
@Mehrdad Đó không phải là một cảnh sát lén lút: đó là toàn bộ vấn đề . Đối với bất kỳ mẫu X nào được tạo đơn giản và được kiểm tra đơn giản, có một thuật toán nén tìm mẫu đó và xử lý nó. Vì vậy, đây là câu trả lời cho bất kỳ câu hỏi nào dọc theo dòng "Có thuật toán nén nào xử lý X như vậy không?" Chắc chắn, luôn có một thuật toán tìm kiếm các mẫu phức tạp hơn một chút. Và có một mẫu tìm kiếm các mẫu phức tạp hơn một chút so với mẫu đó, ad infinitum . Phản đối của bạn là không sáng lập.
David Richerby

Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Gilles 'SO- ngừng trở nên xấu xa'

Một ứng dụng cực đoan của nguyên tắc này là các liên kết nam châm bittorrent trong đó bất kỳ tệp hoặc tập hợp tệp nào có kích thước bất kỳ được biểu diễn đơn giản (nén) thành 160 bit dữ liệu cố định. Tất nhiên có nguy cơ va chạm băm có thể xảy ra.
slebetman

17

Bất cứ điều gì bằng cách sử dụng một BWT (biến đổi Burrows của Wheeler) phải có khả năng nén khá tốt.

Bài kiểm tra Python nhanh của tôi:

>>> 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

(Các số ở đây là 'first_compressor second_compressor time_taken byte tựa')

(BWT lấy từ đây )

Đây vẫn là "không chỉ một vài byte", nhưng dù sao cũng tốt hơn nhiều so với chỉ một mình gzip. Chẳng hạn, BWT + bz2 giảm xuống còn 237 byte từ 1114111 cho đầu vào 16 bit.

Than ôi, BWT quá chậm và ngốn bộ nhớ cho nhiều ứng dụng. Đặc biệt, đây là một triển khai ngây thơ trong Python - trên máy của tôi, tôi hết RAM trước 2 ** 20.

Với Pypy, tôi có thể chạy đầy đủ 2 ** 20 đầu vào và nó nén nó thành 2611 byte với một BWT theo sau là bz2. Nhưng mất hơn 3 phút và đạt đỉnh 4GB RAM đã sử dụng ...

Thật không may, cách tiếp cận này vẫn là không gian đầu ra O (2 ^ n), nó sẽ xuất hiện - ít nhất là từ đường cong 1..20.


Bạn có thể thoát khỏi evalbằng cách làm: for first in (bwt_c, nothing, lzma, zlib, gzip, bz2):fOut = first.compress(inputData).
kasperd

@kasperd - Làm thế nào tôi có thể in tên trong trường hợp đó? Cá nhân, việc thực hiện một eval đơn giản hơn (và ít bị lỗi hơn) là cố gắng giữ cho tên + tham chiếu được đồng bộ hóa.
TLW

5
Đầu tiên bwt và sau đó bz2 nén điều này cực kỳ tốt. Đây là hành vi cực kỳ kỳ lạ và có lẽ là do mô hình chính xác này. Lưu ý rằng bạn đang thực hiện bwt hai lần (bz2 dựa trên BWT), điều này dẫn đến việc nén kém hơn . Cũng lưu ý rằng bwt ngày nay thường chạy ở 4 times block sizebộ nhớ (ví dụ ~ 4 MB cho điều này) và ở tốc độ >10 MB/s(Tôi là tác giả của một thuật toán nén / thư viện bwt như vậy) có thể sử dụng được cho nhiều ứng dụng. Lưu ý rằng ngay cả gzip cũng tạo ra kết quả nén rất tốt. Cảm ơn đã chia sẻ Tôi không biết về bất kỳ nghiên cứu nào về việc sử dụng bwt hai lần.
Christoph

3
@Christoph - Tôi biết bz2 dựa trên BWT ... Tôi thực sự đã bắt đầu viết một câu trả lời cho tác dụng của 'chỉ sử dụng bz2', nhưng thấy rằng nó không nén gần như tôi mong đợi, đã đi 'huh ', và quyết định xem liệu BWT của riêng tôi sẽ làm tốt hơn. Chỉ có tôi cần một máy nén cho đầu ra, và 'cũng có thể thử các máy nén khác nhau để xem điều gì xảy ra'.
TLW

1
@Christoph - Tôi đã xem xét khác. 2 bwts của dữ liệu này tạo ra thứ gì đó cực kỳ phù hợp với mã hóa RLE. Như trong, nếu bạn đếm số lượng cặp RLE cần thiết cho 0, 1, 2, ... các BWT lồng nhau trên đầu vào 16 bit, bạn nhận được 622591 1081343 83 ...
TLW

10

Mã hóa PNG thực hiện chính xác những gì bạn muốn. Nó cũng hoạt động trên dữ liệu thực tế, không chỉ là dữ liệu cực kỳ có tổ chức.

Trong PNG, mỗi hàng được mã hóa bằng một bộ lọc, trong đó 4 hàng được chỉ định. Một trong số đó là "mã hóa pixel này là sự khác biệt giữa giá trị của nó và giá trị của pixel phía trên nó." Sau khi lọc, dữ liệu sẽ được nén bằng DEFLATE.

Bộ lọc này là một ví dụ cụ thể về Mã hóa Delta được đề cập bởi leftaroundabout trong câu trả lời của anh ta, ngoại trừ thay vì theo dõi với Mã hóa độ dài chạy, bạn theo dõi nó bằng thuật toán DEFLATE mạnh hơn. Nó hoàn thành cùng một mục tiêu, chỉ DEFLATE sẽ xử lý nhiều loại đầu vào lớn hơn trong khi vẫn cung cấp các tỷ lệ nén mong muốn.

Một công cụ khác thường được sử dụng trong dữ liệu khoa học trong đó bộ lọc đơn giản + DEFLATE không hiệu quả lắm là mã hóa RICE. Trong RICE, trước tiên, bạn lấy một khối các giá trị và xuất ra tất cả các bit có ý nghĩa nhất, sau đó tất cả các bit có ý nghĩa thứ 2, tất cả đều giảm xuống các bit có trọng số thấp nhất. Sau đó, bạn nén kết quả. Đối với dữ liệu của bạn sẽ không hiệu quả bằng lọc kiểu PNG (vì dữ liệu của bạn là hoàn hảo cho lọc PNG), nhưng đối với nhiều dữ liệu khoa học, nó có xu hướng dẫn đến kết quả tốt. Trong rất nhiều dữ liệu khoa học, chúng ta thấy bit đáng kể nhất có xu hướng thay đổi chậm, trong khi đáng kể nhất là gần như ngẫu nhiên. Điều này trêu chọc dữ liệu rất dễ đoán từ dữ liệu entropic cao.

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

5

Bất kỳ thuật toán thực tế nào tìm kiếm các cấu trúc cụ thể sẽ chỉ giới hạn ở các cấu trúc được mã hóa cứng vào nó. Bạn có thể vá 7z để nhận ra chuỗi cụ thể này, nhưng tần suất cấu trúc cụ thể này sẽ xảy ra trong cuộc sống thực như thế nào? Không thường xuyên đủ để đảm bảo thời gian cần thiết để kiểm tra đầu vào cho đầu vào này.

Về mặt thực tiễn, người ta có thể hình dung máy nén hoàn hảo như một thuật toán cố gắng xây dựng chương trình ngắn nhất tạo ra một đầu ra nhất định. Không cần phải nói, không có cách thực tế để làm điều này. Ngay cả khi bạn đã thử liệt kê tất cả các chương trình có thể và kiểm tra xem chúng có tạo ra đầu ra mong muốn không ( không không phải là một ý tưởng hoàn toàn điên rồ ), bạn sẽ gặp phải vấn đề Dừng , có nghĩa là bạn sẽ phải hủy bỏ chạy thử sau một số lượng nhất định của các bước thực hiện, trước khi bạn biết liệu chương trình này chắc chắn không thể tạo ra đầu ra mong muốn.

Cây tìm kiếm cho cách tiếp cận mạnh mẽ như vậy phát triển theo cấp số nhân với thời lượng chương trình và không thực tế cho tất cả, ngoại trừ các chương trình đơn giản nhất (tương tự như 5-7 hướng dẫn dài).


Đây không chỉ là thời gian cần thiết để kiểm tra loại đầu vào này: đó còn là không gian được chiếm bởi bất kỳ sơ đồ mã hóa nào bạn sử dụng cho "Và sau đó đếm đến ntrong nhị phân "và, đặc biệt, hiệu ứng kích thích mà nó có trên mọi phần nén khác, phải được chú thích bằng" Điều này không được tính vào một số nào đó trong nhị phân. "
David Richerby

1
Ngoài ra, thay vì hủy bỏ, bạn có thể sử dụng kỹ thuật "khớp nối" để đối phó với các chương trình không tạm dừng. Để làm điều này, bạn chạy đầu tiênn các chương trình cho n từng bước, sau đó là đầu tiên n+1 các chương trình cho n1từng bước, và như vậy. Đây có nghĩa là bạn chạy mỗi chương trình cho một số bước mà không phải là một tiên nghiệm bị chặn, nhưng mà không cần phải chờ đợi cho một chương trình để dừng lại trước khi cố gắng tiếp theo.
David Richerby

Chà, các công cụ như Mathicala tìm các hàm cho nhiều chuỗi ...
Raphael

3

Tỷ số nén phụ thuộc hoàn toàn vào bộ giải nén mục tiêu. Nếu bộ giải nén không thể giải mã các số 4 byte liên tiếp gọn hơn 4 byte cho mỗi số thì bạn là SOL.

Có nhiều thứ khác nhau sẽ cho phép mã hóa các số liên tiếp. Ví dụ mã hóa vi sai. Bạn lấy n byte tại một thời điểm và sau đó lấy chênh lệch hoặc xor của các bit và sau đó nén kết quả. Điều này thêm 4 tùy chọn ở đây để thử cho mỗi số byte: danh tính a'[i] = a[i]; sự khác biệt a'[i] = a[i-1]-a[i]; chênh lệch ngược a'[i] = a[i]-a[i-1]; và xora'[i] = a[i]^a[i-1] . Điều đó có nghĩa là thêm 2 bit để chọn các phương thức và số byte cho 3 trên 4 tùy chọn.

Tuy nhiên, không phải tất cả dữ liệu là một chuỗi các bản ghi có độ dài cố định. Mã hóa vi sai không có ý nghĩa cho điều đó (trừ khi máy nén có thể chứng minh bằng thực nghiệm rằng nó hoạt động với một chút dữ liệu).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.