Kompresi data biner sederhana yang efisien

27

Saya memiliki file yang berisi nomor biner yang dipesan dari hingga :02n1

0000000000
0000000001
0000000010
0000000011
0000000100
...
1111111111

7z tidak mengkompres file ini dengan sangat efisien (untuk n = 20, 22 MB dikompresi hingga 300 kB).

Apakah ada algoritma yang dapat mengenali struktur data yang sangat sederhana dan mengkompres file ke beberapa byte? Saya juga ingin tahu apa bidang CS atau teori informasi yang mempelajari algoritma pintar tersebut. "AI" akan terlalu luas, harap sarankan kata kunci yang lebih konkret.
Gagasan simetri harus memainkan peran mendasar dalam kompresi data, tetapi kueri pencarian "simetri dalam kompresi data" dan "teori grup dalam kompresi data" secara mengejutkan mengembalikan hampir tidak ada yang relevan.

DSblizzard
sumber
11
Lihat kompleksitas Kolmogorov, yang dalam beberapa hal adalah kompresi optimal (hingga konstanta aditif). Sayangnya, kompleksitas Kolmogorov tidak dapat dihitung ...
Yuval Filmus
12
Mengapa Anda perlu mengompres data itu? Tidak bisakah Anda membuat ulang kapan saja Anda membutuhkannya? (Yang terkait erat dengan pendekatan kompleksitas Kolmogorov yang disebutkan oleh @YuvalFilmus: kompleksitas Kolmogorov pada dasarnya adalah panjang dari program terpendek yang menghasilkan output).
David Richerby
7
Saya memang menulis algoritma seperti itu di sekolah menengah, 20 tahun yang lalu. Diberikan masukan Anda, itu akan dikompresi menjadi "beberapa byte" (sekitar 3.500.000: 1 dalam skenario optimal). Namun, data jarang terlihat seperti ini, jadi tidak praktis untuk memiliki algoritma seperti ini. Algoritma kompresi umum harus berurusan dengan pola yang kompleks, bukan yang sederhana. Siapa pun dapat menulis algoritme untuk menyimpan data linier, tetapi menyimpan data yang menarik adalah tantangannya.
phyrfox
3
Bagaimana n = 20 memberi Anda 22MB? Saya mendapatkan 4,2 MB jika menggunakan integer 4 byte
njzk2
3
@ Jim Oh, ok. baik itu akan menjadi gagasan kompresi pertama, tidak menggunakan 8 bit untuk mewakili bit tunggal.
njzk2

Jawaban:

27

Ini tampaknya menjadi kasus penggunaan yang jelas untuk kompresi delta . Jika diketahui apriori, ini sepele: simpan angka pertama kata demi kata, dan untuk setiap angka berikutnya simpan hanya perbedaan dengan yang sebelumnya. Dalam kasus Anda, ini akan memberin

0
1
1
1
1
...

Ini kemudian dapat dengan pengkodean run-length sederhana disimpan dalam ruang , karena hanya ada O ( 1 ) kelompok (yaitu, dua) dari delta yang berbeda.O(n)O(1)

Jika tidak diketahui, hal yang paling sederhana adalah pencarian brute force untuk ukuran kata yang mewakili representasi delta / run-length tersingkat. Mungkin hanya pencarian ini untuk dipilih secara acak, nPotongan berukuran N , untuk mengamortisasi overhead temuannsambil mempertahankan keandalan yang baik.Nn

Tidak seperti proposal DW "all or nothing", kompresi delta dengan enkode run-length sebenarnya dapat memberikan rasio kompresi yang masuk akal untuk beberapa jenis konten dunia nyata yang sederhana, seperti audio resolusi rendah. (Dengan demikian cocok untuk kompresi audio berkualitas rendah, latensi rendah, dan daya rendah.)

leftaroundabout
sumber
44

Tentu, tentu saja ada algoritma. Ini algoritma saya:

  1. Pertama, periksa apakah file tersebut berisi angka biner yang terurut dari hingga 2 n - 1 , untuk beberapa n . Jika demikian, tulis 0 bit diikuti oleh n satu bit diikuti oleh 0 bit.02n-1nn

  2. Jika tidak, tulis 1 bit, lalu tulis 7z-kompresi file.

Ini sangat efisien untuk file dari struktur tertentu.

Intinya adalah: tidak ada makan siang gratis dalam kompresi data. Anda mungkin dapat membangun algoritma kompresi yang memampatkan satu jenis file dengan baik, dengan mengorbankan lebih banyak orang. Tetapi, jika Anda mengetahui sesuatu tentang sifat file yang akan Anda kompres, Anda dapat mengoptimalkan algoritme untuk jenis file tertentu.

Area ini adalah "kompresi data". Lihat tag kami , dan baca buku teks tentang kompresi data.

DW
sumber
5
Tugas kompresor adalah mengenali pola umum dan mengeksploitasinya. Ini tidak seperti pola ini tidak umum atau tidak jelas. Jadi pertanyaan wajar untuk bertanya mengapa itu tidak dieksploitasi. Memberitahunya ada trade-off atau memberinya algoritma yang gagal dalam segala hal kecuali pola itu adalah solusi total.
Mehrdad
17
Tentu terlihat tidak biasa bagi saya. Ini akan muncul dalam data kehidupan nyata yang sangat jarang, dibandingkan dengan jenis pola yang dicari oleh kompresor.
amalloy
17
@Mehrdad Ini bukan jalan keluar yang sulit: ini intinya . Untuk setiap pola X yang hanya dihasilkan dan diperiksa, ada algoritma kompresi yang mencari pola itu dan mengatasinya. Jadi ini adalah jawaban untuk setiap pertanyaan di sepanjang baris "Apakah ada algoritma kompresi yang berhubungan dengan X seperti itu?" Tentu, selalu ada algoritma yang mencari pola yang sedikit lebih kompleks. Dan ada satu yang mencari pola yang sedikit lebih kompleks daripada yang itu juga, ad infinitum . Keberatan Anda tidak beralasan.
David Richerby
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Gilles 'SO- stop being evil'
Aplikasi ekstrem dari prinsip ini adalah tautan magnet bittorrent di mana setiap file atau koleksi file dalam ukuran apa pun dengan mudah diwakili (dikompresi) ke data 160 bit yang tetap. Tentu saja ada risiko bahwa tabrakan hash dapat terjadi.
slebetman
17

Apa pun yang menggunakan BWT (Burrows-Wheeler transform) harus dapat mengompres dengan cukup baik.

Tes Python cepat saya:

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

(Angka di sini adalah 'first_compressor second_compressor time_taken bytes_out')

(BWT diambil dari sini )

Ini masih 'bukan hanya beberapa byte', tetapi masih jauh lebih baik daripada hanya gzip saja. BWT + bz2 turun ke 237 byte dari 1114111 untuk input 16-bit, misalnya.

Sayangnya, BWT terlalu lambat dan haus akan memori untuk banyak aplikasi. Terutama mengingat ini adalah implementasi naif dalam Python - pada mesin saya, saya kehabisan RAM sebelum 2 ** 20.

Dengan Pypy saya dapat menjalankan input 2 ** 20 penuh, dan memampatkannya menjadi 2611 byte dengan BWT diikuti oleh bz2. Tetapi mengambil lebih dari 3 menit dan memuncak pada lebih dari 4GB RAM yang digunakan ...

Sayangnya, pendekatan ini masih O (2 ^ n) ruang output, akan muncul - setidaknya dari kurva-pas 1..20.

TLW
sumber
Anda dapat dihilangkan evaldengan melakukan: for first in (bwt_c, nothing, lzma, zlib, gzip, bz2):dan fOut = first.compress(inputData).
kasperd
@kasperd - bagaimana saya mencetak nama-nama dalam hal itu? Secara pribadi, itu lebih sederhana (dan lebih sedikit rawan kesalahan) untuk melakukan eval daripada mencoba untuk menjaga nama + referensi disinkronkan.
TLW
5
Bwt pertama dan kemudian bz2 kompres dengan baik. Ini adalah perilaku yang sangat aneh dan mungkin karena pola yang tepat ini. Perhatikan bahwa Anda melakukan bwt dua kali (bz2 didasarkan pada BWT) yang biasanya menghasilkan kompresi yang lebih buruk . Juga perhatikan bahwa bwt saat ini biasanya berjalan pada 4 times block sizememori (mis ~ ~ 4MB untuk ini) dan pada kecepatan >10 MB/s(saya penulis perpustakaan bwt / algoritma kompresi) yang cukup dapat digunakan untuk banyak aplikasi. Perhatikan bahwa bahkan gzip menghasilkan hasil kompresi yang sangat baik. Terima kasih telah berbagi Saya tidak mengetahui adanya penelitian tentang penggunaan bwt dua kali.
Christoph
3
@Christoph - Saya tahu bz2 didasarkan pada BWT ... Saya sebenarnya sudah mulai menulis jawaban atas efek 'gunakan bz2', tetapi ternyata bz2 tidak kompres sebaik yang saya harapkan, lanjut 'huh ', dan memutuskan untuk melihat apakah BWT saya sendiri akan lebih baik. Hanya saya membutuhkan kompresor untuk output, dan pergi 'mungkin juga mencoba berbagai kompresor untuk melihat apa yang terjadi'.
TLW
1
@Christoph - Saya melihat lagi. 2 bwts data ini menghasilkan sesuatu yang sangat sesuai untuk penyandian RLE. Seperti pada, jika Anda menghitung jumlah pasangan RLE yang diperlukan untuk 0, 1, 2, ... BWT bersarang pada input 16-bit, Anda mendapatkan 622591 1081343 83 ...
TLW
10

Pengkodean PNG melakukan apa yang Anda inginkan. Ini bekerja pada data kehidupan nyata juga, bukan hanya data yang sangat terorganisir.

Dalam PNG, setiap baris dikodekan dengan filter, yang 4 ditentukan. Salah satunya adalah "menyandikan piksel ini sebagai perbedaan antara nilainya dan nilai piksel yang di atasnya." Setelah pemfilteran, data kemudian di-zip menggunakan DEFLATE.

Penyaringan ini adalah contoh spesifik dari Delta Encoding yang disebutkan oleh leftaroundabout dalam jawabannya, kecuali alih-alih menindaklanjutinya dengan Run Length Encoding Anda menindaklanjutinya dengan algoritma DEFLATE yang lebih kuat. Ini mencapai tujuan yang sama, hanya DEFLATE akan menangani berbagai input yang lebih besar sambil tetap memberikan rasio kompresi yang diinginkan.

Alat lain yang sering digunakan dalam data ilmiah di mana filter + DEFLATE tidak cukup efektif adalah pengkodean BERAS. Dalam RICE, Anda mengambil satu blok nilai dan mengeluarkan semua bit paling signifikan terlebih dahulu, lalu semua bit paling signifikan ke-2, sampai ke bit paling tidak signifikan. Anda kemudian mengompres hasilnya. Untuk data Anda yang tidak akan seefektif pemfilteran gaya PNG (karena data Anda sempurna untuk pemfilteran PNG), tetapi untuk banyak data ilmiah cenderung menghasilkan hasil yang baik. Dalam banyak data ilmiah, kita melihat bit yang paling signifikan cenderung berubah perlahan, sedangkan yang paling signifikan hampir acak. Ini menggoda data yang sangat mudah diprediksi dari data yang sangat entropis.

0000000000       00000  most significant bits
0000000001       00000
0000000010  =>   00000
0000000011       00000
0000000100       00000
                 00000
                 00000
                 00001
                 00110
                 01010 least significant bits
Cort Ammon - Pulihkan Monica
sumber
5

Algoritma praktis apa pun yang mencari struktur tertentu akan dibatasi hanya pada struktur yang dikodekan secara keras. Anda dapat menambal 7z untuk mengenali urutan spesifik ini, tetapi seberapa sering struktur spesifik ini akan terjadi dalam kehidupan nyata? Tidak sering cukup untuk menjamin waktu yang diperlukan untuk memeriksa input untuk input ini.

Selain kepraktisan, orang dapat menganggap kompresor sempurna sebagai algoritma yang mencoba untuk membangun program terpendek yang menghasilkan output yang diberikan. Tidak perlu dikatakan, tidak ada cara praktis untuk melakukan ini. Bahkan jika Anda mencoba enumerasi brute-force dari semua program yang mungkin dan memeriksa apakah mereka menghasilkan output yang diinginkan ( bukan ide yang sepenuhnya gila ), Anda akan mengalami masalah Menghentikan , yang berarti bahwa Anda harus membatalkan uji coba berjalan setelah sejumlah tertentu langkah-langkah eksekusi, sebelum Anda tahu apakah program ini pasti tidak dapat menghasilkan output yang diinginkan.

Pohon pencarian untuk pendekatan brute force tumbuh secara eksponensial dengan panjang program dan tidak praktis untuk semua kecuali program yang paling sederhana (sekitar 5-7 instruksi panjang).

Roman Starkov
sumber
n
1
nnn+1n1
Nah, alat-alat seperti Mathematica menemukan fungsi untuk banyak urutan ...
Raphael
3

Rasio kompresi sepenuhnya tergantung pada dekompresor yang ditargetkan. Jika decompressor tidak dapat mendekode angka 4 byte berurutan lebih kompak dari 4 byte per angka maka Anda SOL.

Ada berbagai hal yang memungkinkan pengkodean angka berurutan. Misalnya penyandian diferensial. Anda mengambil n byte pada suatu waktu dan kemudian mengambil perbedaan atau xor bit dan kemudian kompres hasilnya. Ini menambahkan 4 opsi di sini untuk mencoba setiap hitungan byte: identitas a'[i] = a[i]; perbedaan a'[i] = a[i-1]-a[i]; membalikkan perbedaan a'[i] = a[i]-a[i-1]; dan xor a'[i] = a[i]^a[i-1]. Itu berarti menambahkan 2 bit untuk memilih metode dan jumlah byte untuk 3 dari 4 opsi.

Namun tidak semua data merupakan urutan catatan dengan panjang tetap. Pengkodean diferensial tidak masuk akal untuk itu (kecuali kompresor dapat secara empiris membuktikan bahwa ia bekerja untuk sedikit data).

ratchet freak
sumber