Einfaches Preprocessing zum Komprimieren (nur eine Spielerei)

  • Moin,

    habe neulich nochmal über die LZ77 Methode nachgedacht und überlegt, dass man einen ähnlichen Effekt vermutlich auch deutlich einfacher bekommen kann. Die Idee ist folgendes:

    - Die Daten sind Binär '010101...' und wir wollen eine "Mustererkennung" ohne wirklich irgendwas zu rechnen.

    - Baue einen Lookuptable auf, der für jede Bitfolge (von z.B. 8 Bit) angibt, wie wahrscheinlich eine "0" oder eine "1" auf diese 8 Bit folgt.

    - Laufe alle Daten durch und ersetze sie durch "0" oder "1", je nachdem ob die Vorhersage gestimmt hat. Aktualisiere den LUT entsprechend nach jedem Schritt.

    - Das Ganze ist trivial reversibel.

    - Für einen Lookuptable mit >8Bit wird eine Map mit Stringkeys verwendet (das ist langsam). Falls man das mal in einer anderen Sprache programmiert müsste die Methode extrem schnell sein (es ist im Prinzip nur triviales Bitgeschiebe und Lookups), sollte auch ASM-freundlich sein.

    - Aktuell wird nichts komprimiert, sondern nur "die Anzahl der Nullen in den Binären Daten maximiert". Vielleicht kann man die Methode aber für irgendwas als Ausgangspunkt verwenden (z.B. Lauflängenkodierung, etc). Daher "Preprocessing", es ist ein Vorverarbeitungsschritt der redundante Daten durch Nullen ersetzt.

    Vermutlich hatten schon hunderte Leute dieselbe Idee, und was besonderes ist es auch nicht (daher in Off-Topic). Vielleicht kann damit ja trotzdem jemand etwas anfangen :)

    Beispiel mit zwei Datensätzen (oben im Skript via "GetSomeData" und "GetSomeData2") abrufbar. In der Konsole kann beobachtet werden die der Algorithmus "warmläuft", zu Beginn ändert sich fast nichts am Input (da jede Vorhersage daneben liegt), später bestehen die Daten aber zum Großteil aus Nullen (im Redundanten Fall bestehen sie quasi exklusiv aus Nullen). Getestet wurde nur rudimentär.

    (Edit: Genau das Gleiche habe ich vor Urzeiten schonmal gebastelt (nur viel umständlicher) und den Code nicht mehr gefunden)

    lg

    M

  • Hi Mars ,

    ich habe ehrlich gesagt keine Verwendung dafür, doch das liegt auch einfach daran das ich mich noch nicht wirklich damit beschäftigt habe und mir nicht ganz klar ist in wie weit ich dies als "Ausgangspunkt" für etwas anderes nutzen kann, dennoch finde ich es spannend, was in so mancher Kopf vorgeht 😅 ... ( nett gemeint 😇 ). Außerdem lerne ich liebend gern dazu, jeden Tag ein wenig.

    encoded Bits: 36816 LUT-size: 16 #0: 29074 (79.0%) #1: 7742 (21.0%)
    decoded Bits: 36816 LUT-size: 16 #0: 20580 (55.9%) #1: 16236 (44.1%)

    (Edit: Genau das Gleiche habe ich vor Urzeiten schonmal gebastelt (nur viel umständlicher) und den Code nicht mehr gefunden)

    Übrigens witzig mal wieder eines deiner Beispiele, die du schon so manche Male erwähnt hast, zu sehen 👍 .

    Viele Grüße
    Sven

  • Mit Ausgangspunkt ist gemeint, dass man das als Vorstufe für einen Kompressionsalgorithmus verwenden kann. Üblicherweise bekommt man eine bessere Kompression, wenn irgendeine Struktur in den Daten vorliegt die man ausnutzen kann. In diesem Fall: Sehr viel mehr Nullen als Einsen, dafür aber etwas unstrukturierter. (Ist also für einen Kodierer der Muster erkennt ungeeignet, die Muster wurden ja quasi in Nullen umgewandelt)

    Mögliche Folgeschritte wären:
    - Lauflängenkodierung (ist intuitiv das erste was mir einfällt)
    - Falls Lauflängenkodierung im Binärformat -> Arithmetische Entropiekodierung als Folgeschritt (huffman geht hier nicht besonders gut, da die Symbole nicht mehr aligned sind).
    - Falls Lauflängenkodierung mit Tokens -> Irgendeine Entropiekodierung (hier hat man die Symbole einzeln -> Jede Methode geht) als Folgeschritt -> Fertig.

    Probleme der aktuellen Methode:
    - Je größer der LUT, desto länger ist die Warmlaufphase, da der LUT erstmal gefüllt werden muss
    - Je größer der LUT, desto besser das Ergebnis nach der Warmlaufphase, aber man ist auf 16 Bit beschränkt.

    Lösungsansatz den ich irgendwann ausprobiere:
    - Baue einen beliebig großen "pseudo" LUT aus mehreren 8Bit Blöcken zusammen.
    - So siehts aus: [Block n-2][Block n-1][Block n][Bit x] -> Akuell wird nur [Block n] verwendet um x vorherzusagen. Man könnte allerdings auch alle anderen Blöcke einbeziehen. Da hier (aus Stochastik-Sicht) keine Unabhängigkeit vorliegt (es ist also ein Unterschied ob ich ausgehend von [Block n-1 (8Bit)] und [Block n (8Bit)] das x vorhersage, oder ob ich einen einzigen 16Bit Block nehme) muss man für halbwegs gute Ergebnisse vermutlich eine Art Interleaving verwenden.

    Trivialbeispiel mit 4 x 8Bit Blöcken -> [aaaaaaaa][bbbbbbbb][cccccccc][dddddddd][x] -> 4 Vorhersagen mit den Blöcken ohne Änderung (habe keine Nummern drangeschrieben, die aaaaaaaa sind eigentlich a1a2a3...usw)
    Zusätzlich mit Interleaving [abcdabcd][abcdabcd][abcdabcd][abcdabcd][x] -> 4 Weitere Vorhersagen für x
    Eine Linearkombination (mit irgendwelchen Koeffizienten die ich nicht kenne) dieser 8 Vorhersagen dient als Approximation der Vorhersage die ein einziger 32Bit Block liefern könnte. Je mehr Vorhersagen mit verschieden Verwobenenen 8 Bit Blöcken man verwendet, desto besser die Approximation. Gleichzeitig sinkt die Warmlaufphase, da man nicht 2^32 Zustände, sondern (im Beispielfall) nur 8 * 2^8 = 2048 Zustände vollmachen muss. Ich schätze damit sind auch bei "normalem Text" 90%+ Nullen möglich (statt 79%). Wäre zumindest lustig, wenn das gehen würde :)

    Danach kommt die "swap Methode" (aus dem Permutationen Komprimieren Thread) zum Einsatz (wobei hier die Nullen und Einsen geswappt werden um sie zu sortieren). Dann sind die Daten in folgender Form: [Bits zur Steuerung der Swaps][alle Einsen][alle Nullen]. Alle Einsen und Alle Nullen können als 2 Integer gespeichert werden, sodass die komprimierten Daten nur noch aus Swap-Positionen + 2 Int bestehen. Hehehe. Damit werde ich die Welt erobern.

    M