Compressione efficiente di semplici dati binari


27

Ho un file contenente numeri binari ordinati da a :2 n - 102n1

0000000000
0000000001
0000000010
0000000011
0000000100
...
1111111111

7z non ha compresso questo file in modo molto efficiente (per n = 20, 22 MB sono stati compressi a 300 kB).

Esistono algoritmi in grado di riconoscere una struttura di dati molto semplice e comprimere il file in diversi byte? Voglio anche sapere quale area di CS o teoria dell'informazione studia tali algoritmi intelligenti. "AI" sarebbe troppo ampio, ti preghiamo di suggerire parole chiave più concrete.
La nozione di simmetria dovrebbe svolgere un ruolo fondamentale nella compressione dei dati, ma le query di ricerca "simmetria nella compressione dei dati" e "teoria dei gruppi nella compressione dei dati" sorprendentemente non restituiscono quasi nulla di rilevante.


11
Scopri la complessità di Kolmogorov, che è in qualche modo la compressione ottimale (fino a una costante additiva). Sfortunatamente, la complessità di Kolmogorov non è calcolabile ...
Yuval Filmus

12
Perché è necessario comprimere quei dati? Non puoi semplicemente rigenerarlo ogni volta che ne hai bisogno? (Che è strettamente correlato all'approccio della complessità di Kolmogorov menzionato da @YuvalFilmus: la complessità di Kolmogorov è essenzialmente la lunghezza del programma più breve che genera l'output).
David Richerby,

7
Ho scritto un tale algoritmo al liceo, 20 anni fa. Dato il tuo input, sarebbe stato compresso in "pochi byte" (circa 3.500.000: 1 in uno scenario ottimale). Tuttavia, i dati raramente sembrano così, quindi non è pratico avere un algoritmo come questo. Gli algoritmi di compressione generale hanno a che fare con schemi complessi, non semplici. Chiunque può scrivere un algoritmo per archiviare dati lineari, ma la memorizzazione di dati interessanti è la sfida.
phyrfox

3
In che modo n = 20 ti dà 22 MB? Ottengo 4,2 MB se utilizzo 4 byte interi
njzk2,

3
@JiK oh, ok. bene sarebbe una prima nozione di compressione, non usare 8 bit per rappresentare un singolo bit.
njzk2,

Risposte:


27

Questo sembra essere un chiaro caso d'uso per la compressione delta . Se è noto a priori questo è banale: memorizzare il primo numero alla lettera e per ogni numero successivo memorizzare solo la differenza rispetto al precedente. Nel tuo caso, questo daràn

0
1
1
1
1
...

Questo può quindi essere archiviato con una semplice codifica run-length nello spazio , in quanto vi sono solo gruppi (ovvero due) di delta diversi.O(n)O(1)

Se non è noto, la cosa più semplice sarebbe una ricerca della forza bruta per la dimensione della parola per la quale questa rappresentazione delta / lunghezza di esecuzione risulta più breve. Forse fai solo questa ricerca per blocchi scelti in modo casuale, , per ammortizzare il sovraccarico di trovare mantenendo una buona affidabilità.nNn

A differenza della proposta "tutto o niente" di DW, la compressione delta con codifica run-length può effettivamente fornire rapporti di compressione sensati per alcuni tipi di contenuti del mondo reale, come l'audio a bassa risoluzione. (È quindi adatto per la compressione audio di bassa qualità, bassissima latenza e bassa potenza.)


44

Certo, ovviamente ci sono algoritmi. Ecco il mio algoritmo:

  1. Innanzitutto, controlla se il file contiene numeri binari ordinati da a 2 n - 1 , per alcuni n . In tal caso, scrivere uno 0 bit seguito da n uno bit seguito da uno 0 bit.02n1nn

  2. In caso contrario, scrivere 1 bit, quindi scrivere la compressione 7z del file.

Questo è estremamente efficiente per i file di quella particolare struttura.

Il punto è: non c'è pranzo libero nella compressione dei dati. Potresti essere in grado di creare un algoritmo di compressione che comprime bene un tipo di file, a costo di comprimerne altri peggio. Ma, se conosci a priori qualcosa sulla natura dei file che comprimerai, puoi ottimizzare il tuo algoritmo per quel particolare tipo di file.

L'area è "compressione dati". Vedi il nostro tag di e leggi i libri di testo sulla compressione dei dati.


5
Il compito di un compressore è riconoscere schemi comuni e sfruttarli. Non è che questo schema sia raro o oscuro. Quindi è una domanda naturale chiedersi perché non sia sfruttato. Dirgli che c'è un compromesso o dargli un algoritmo che fallisce su tutto tranne che quel modello è un cop-out totale.
Mehrdad,

17
Certo mi sembra insolito. Ciò si verificherebbe molto raramente nei dati della vita reale, rispetto ai tipi di schemi che i buoni compressori cercano.
amalloy

17
@Mehrdad Non è un poliziotto snarky: è l'intero punto . Per ogni modello X che viene semplicemente generato e semplicemente verificato, esiste un algoritmo di compressione che cerca quel modello e lo gestisce. Quindi questa è la risposta a qualsiasi domanda sulla falsariga di "Esiste un algoritmo di compressione che si occupa di tale X?" Certo, c'è sempre un algoritmo che cerca modelli leggermente più complessi. E ce n'è uno che cerca modelli leggermente più complessi di quello, anche all'infinito . La tua obiezione non è fondata.
David Richerby,

I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Gilles 'SO- smetti di essere malvagio'

Un'applicazione estrema di questo principio sono i collegamenti magnetici bittorrenti in cui qualsiasi file o raccolta di file di qualsiasi dimensione sono semplicemente rappresentati (compressi) in 160 bit fissi di dati. Esiste ovviamente il rischio che si verifichino collisioni di hash.
slebetman,

17

Tutto ciò che utilizza una BWT (trasformata di Burrows – Wheeler) dovrebbe essere in grado di comprimerlo abbastanza bene.

Il mio rapido test 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

(I numeri qui sono 'first_compressor second_compressor time_taken bytes_out')

(BWT preso da qui )

Questo è ancora "non solo pochi byte", ma è comunque molto meglio del solo gzip da solo. BWT + bz2 scende a 237 byte da 1114111 per un input a 16 bit, ad esempio.

Purtroppo, i BWT sono troppo lenti e affamati di memoria per molte applicazioni. Soprattutto dato che questa è un'implementazione ingenua in Python - sulla mia macchina ho esaurito la RAM prima di 2 ** 20.

Con Pypy sono stato in grado di eseguire l'intero 2 ** 20 input e lo comprime a 2611 byte con un BWT seguito da bz2. Ma impiegando oltre 3 minuti e raggiungendo un picco di oltre 4 GB di RAM utilizzati ...

Anche sfortunatamente, questo approccio è ancora O (2 ^ n) spazio di output, sembrerebbe - almeno dall'adattamento alla curva 1..20.


Puoi sbarazzartene evalfacendo: for first in (bwt_c, nothing, lzma, zlib, gzip, bz2):e fOut = first.compress(inputData).
Kasperd,

@kasperd - come avrei stampato i nomi in quel caso? Personalmente, è più semplice (e meno soggetto a errori) fare una valutazione piuttosto che cercare di mantenere sincronizzati i nomi + i riferimenti.
TLW

5
Prima bwt e poi bz2 lo comprime estremamente bene. Questo è un comportamento estremamente strano e probabilmente dovuto a questo schema esatto. Nota che stai facendo il bwt due volte (bz2 si basa sul BWT) che di solito provoca una compressione peggiore . Nota anche che oggi il bwt funziona normalmente in 4 times block sizememoria (ad esempio ~ 4 MB per questo) e alla velocità di >10 MB/s(sono l'autore di un tale algoritmo di libreria / compressione bwt) che è abbastanza utilizzabile per molte applicazioni. Si noti che anche gzip produce risultati comprimibili molto buoni. Grazie per la condivisione Non sono a conoscenza di alcuna ricerca sull'uso di bwt due volte.
Christoph,

3
@Christoph - So che bz2 è basato su BWT ... In realtà avevo iniziato a scrivere una risposta all'effetto di "usa solo bz2", ma ho scoperto che non si è compresso quasi come mi aspettavo, è andato 'huh ', e ho deciso di vedere se il mio BWT avrebbe fatto meglio. Avevo solo bisogno di un compressore per l'output e sono andato "potrei anche provare diversi compressori per vedere cosa succede".
TLW

1
@Christoph - Ho dato un'altra occhiata. 2 bwts di questi dati generano qualcosa che è estremamente suscettibile alla codifica RLE. Come in, se si conta il numero di coppie RLE richieste per 0, 1, 2, ... BWT nidificati su un input a 16 bit, si ottiene 622591 1081343 83 ...
TLW

10

La codifica PNG fa esattamente quello che vuoi. Funziona anche con dati di vita reale, non solo con dati estremamente organizzati.

In PNG, ogni riga è codificata con un filtro, di cui 4 specificati. Uno di questi è "codifica questo pixel come differenza tra il suo valore e il valore del pixel sopra di esso". Dopo il filtraggio, i dati vengono quindi compressi mediante DEFLATE.

Questo filtro è un esempio specifico della codifica Delta menzionata da leftaroundabout nella sua risposta, tranne che invece di seguirla con la codifica della lunghezza di esecuzione, la si segue con il più potente algoritmo DEFLATE. Raggiunge lo stesso obiettivo, solo DEFLATE gestirà una maggiore varietà di input pur fornendo i rapporti di compressione desiderabili.

Un altro strumento che viene spesso utilizzato nei dati scientifici in cui il filtro semplice + DEFLATE non è altrettanto efficace è la codifica RICE. In RICE, prendi un blocco di valori e produci prima tutti i bit più significativi, quindi tutti i bit 2 più significativi, fino ai bit meno significativi. Quindi comprimere il risultato. Per i tuoi dati che non saranno altrettanto efficaci del filtro in stile PNG (perché i tuoi dati sono perfetti per il filtro PNG), ma per molti dati scientifici tende a portare a buoni risultati. In molti dati scientifici, vediamo che il bit più significativo tende a cambiare lentamente, mentre il meno significativo è quasi casuale. Ciò distingue i dati altamente prevedibili dai dati altamente entropici.

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

5

Qualsiasi algoritmo pratico alla ricerca di strutture specifiche sarebbe limitato solo alle strutture codificate in esso. Potresti correggere 7z per riconoscere questa specifica sequenza, ma con quale frequenza si verificherà questa struttura specifica nella vita reale? Non abbastanza spesso da garantire il tempo necessario per controllare gli input per questo input.

Pratiche a parte, si può concepire il compressore perfetto come un algoritmo che cerca di costruire il programma più breve che produce un dato output. Inutile dire che non ci sono modi pratici per farlo. Anche se hai provato una enumerazione della forza bruta di tutti i programmi possibili e verificato se hanno prodotto l'output desiderato ( non un'idea del tutto folle ), incontrerai il problema di Halting , il che significa che dovrai interrompere le esecuzioni di prova dopo un certo numero delle fasi di esecuzione, prima di sapere se questo programma sicuramente non può produrre l'output desiderato.

L'albero di ricerca per un tale approccio a forza bruta cresce esponenzialmente con la lunghezza del programma e non è pratico per tutti ma per i programmi più semplici (qualcosa come 5-7 istruzioni lunghe).


n

1
nnn+1n1

Bene, strumenti come Mathematica trovano funzioni per molte sequenze ...
Raffaello

3

I rapporti di compressione dipendono interamente dal decompressore target. Se il decompressore non riesce a decodificare i numeri sequenziali di 4 byte in modo più compatto di 4 byte per numero, allora sei SOL.

Esistono varie cose che consentirebbero la codifica di numeri sequenziali. Ad esempio una codifica differenziale. Prendi n byte alla volta e quindi prendi la differenza o lo xor dei bit e quindi comprimi il risultato. Questo aggiunge 4 opzioni qui per provare per ogni conteggio di byte: identità a'[i] = a[i]; differenza a'[i] = a[i-1]-a[i]; differenza inversa a'[i] = a[i]-a[i-1]; e lo xor a'[i] = a[i]^a[i-1]. Ciò significa aggiungere 2 bit per selezionare i metodi e un conteggio byte per 3 opzioni su 4.

Tuttavia, non tutti i dati sono una sequenza di record a lunghezza fissa. La codifica differenziale non ha senso per questo (a meno che il compressore non possa empiricamente dimostrare che funziona per un po 'di dati).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.