Kompresja danych Instytut Informatyki UWr Studia dzienne Wykład nr 2: rozszerzone i dynamiczne Huffmana
Kod Huffmana - niemiłe przypadki... Niech alfabet składa się z 2 liter: P(a)=1/16P(b)=15/16 Mamy H(1/16, 15/16) = -1/16*log(1/16)-15/16*log(15/16) 0.34 Natomiast algorytm Huffmana daje kod K: K(a)=0K(b)=1 Czyli S(K) = 1/16*1+15/16*1 = 1... żadnej kompresji, prawie 3 razy gorzej od entropii...
Kod Huffmana - rozszerzamy... Dla rozkładu prawdopodobieńtw jak poprzednio: P(A)=1/16P(B)=15/16 Wprowadźmy rozszerzony alfabet {AA, AB, BB, BA} średnia długość powyższego kodu Huffmana: S(H) = 1/256 * /256 * 3 +15/256 * /256*1 1.18 a entropia: H(1/256, 15/256, 15/256, 225/256) 0.68 Czyli już „tylko” niecałe 2 razy gorzej od entropii. ParaP(para)Kod Huffm AA 1 / AB 15 / BB 225 / BA 15 /
Uogólnijmy rozszerzanie... Uogólniamy (dla ciągów niezależnych): Dany rozkład prawdopodobieństw P = { p 1, ,p n } odpowiadający symbolom a 1, ,a n k-tym rozszerzeniem P k rozkładu P nazywamy rozkład odpowiadający wszystkim k-elementowym ciągom symboli ze zbioru { a 1, ,a n } prawdopodobieństwo ciągu a i1 a ik w rozkładzie P k to p i1 *p i2 * *p ik Jak zmieni się entropia? rozkład prawdopodobieństwa oryginalnych symboli nie zmienił się! A zatem „zawartość informacyjna” danych również powinna ulec zmianie!!!
Entropia dla rozszerzonego alfabetu Twierdzenie Niech P k będzie rozkładem prawdopodobieństw k-tego rozszerzenia alfabetu z rozkładem P. Wówczas: H(P k ) = k H(P) Dowód: k = 1: oczywiste Krok indukcyjny: Załóżmy, że H(P k-1 ) = (k-1) H(P). Wówczas:
Dowód c.d.
Rozszerzony alfabet c.d. Skoro H(P k ) = k H(P) to znaczy, że zgodnie z intuicją liczba „bitów informacji” przypadających na jeden symbol rozszerzonego alfabetu jest k-krotnie większa od liczby bitów „informacji” na symbol oryginalnego alfabetu ale jeden symbol w P k odpowiada k symbolom w P czyli liczba bitów na „oryginalny” symbol nie zmienia się. A jak z jakością kodów Huffmana dla rozszerzonego alfabetu?
Jakość Huffmana dla rozszerzonego... Wniosek Średnia długość kodu Huffmana dla rozszerzonego alfabetu z rozkładem P k odpowiadająca przypadająca na jeden symbol alfabetu oryginalnego wynosi co najwyżej H(P)+1/k. Dowód: Optymalność kodu Huffmana gwarantuje, że S( Huffman k ) H(P k ) + 1 gdzie Huffmank to kod Huffmana dla P k. A zatem na jeden symbol alfabetu oryginalnego przypada co najwyżej: S( Huffman k ) / k (H(P k ) + 1) / k = H(P) + 1/k bitów.
Kompresja a wydajność Wniosek Używając rozszerzonych kodów Huffmana dla coraz większych k osiągamy kompresję coraz bliższą entropii. Ale związane są z tym koszty: W k-tym rozszerzeniu alfabetu o rozmiarze n uzyskujemy alfabet rozmiaru n k (wzrost wykładniczy!) Oznacza to wykładniczy wzrost czasu tworzenia kodu...oraz wykładniczy wzrost pamięci potrzebnej na przechowywanie (drzewa) kodu Ale czas kompresji/dekompresji pozostaje liniowy! W praktyce: Trzeba wybrać kompromis między kompresją a czasem/pamięcią Problem techniczny: tekst musi mieć długość podzielną przez k.
Skąd brać prawdopodobieństwa? Prawdopodobieństwa ustalone z góry, w oparciu o specyfikę danych: –z góry znane koderowi i dekoderowi (np. standard wideo H.263) –ale przestaje działać gdy zmieni się charakterystyka danych Wyznaczamy prawdopodobieństwa w oparciu o częstość występowania symboli w kodowanym tekście: –konieczne 2 przebiegi: najpierw zliczanie częstości, potem kodowanie –konieczne dołączenie kodu lub częstości do zakodowanych danych (dekoder ich nie zna!) Kodowanie dynamiczne: –w każdym kroku korzystamy z częstości w dotychczas zakodowanej części tekstu (znać ją będzie też dekoder) –wystarczy jeden przebieg –nie trzeba dołączać kodu ani prawdopodobieństw (pbb) do danych.
Dynamiczne kody Huffmana... czyli wystarczy tylko raz policzyć do nieskończoności Idea: Przy kodowaniu każdej litery stosujemy kod Huffmana dla pbb opartych na częstościach już zakodowanej części Prawdopodobieństwa te znane są również dekoderowi: –Przy odkodowywaniu p-tej litery znane są już litery od pierwszej do (p-1)-szej Po każdej literze konieczne modyfikowanie (drzewa) kodu ALE wystarczy jeden przebieg kodowanego pliku! CEL: Przebudowa kodu po każdym kroku nie powinna być kosztowna!
Dynamiczne kody Huffmana Ważne przy kodowaniu modyfikujemy kod po zakodowaniu symbolu przy dekodowaniu modyfikujemy kod przed odkodowaniem symbolu W ten sposób koder i dekoder przy każdym symbolu używają tego samego drzewa kodu!
Dynamiczne kody Huffmana Numerowanie wierzchołków drzewa: od dołu do góry od lewej do prawej A B CD
Dynamiczne kody Huffmana c.d. Wagi wierzchołków: waga liścia = liczba wystąpień odpowiadającego mu symbolu waga wierzchołka wewnętrznego = suma wag liści w jego poddrzewie
Niezmiennik W optymalnym drzewie kodu dla n symboli istnieje numerowanie wszystkich wierzchołków v 1, ,v 2n-1 spełniające warunki: w(v 1 ) w(v 2 ) w(v 2n-1 ), gdzie w(x) to waga wierzchołka x wierzchołki mające wspólnego rodzica mają sąsiednie numery I na odwrót: Jeśli drzewo kodu ma numerowanie spełniające powyższe warunki, kod jest optymalny Obserwacja: W kodzie Huffmana taką numerację można uzyskać poprzez numerowanie (od końca) w kolejności usuwania elementów (poprzez zsumowanie ich prawdopodobieństw) CEL: zachowywać tę własność w kodowaniu dynamicznym, bez przebudowywania całego drzewa.
Niezmiennik silniejszy Przypomnijmy W optymalnym drzewie kodu dla n symboli istnieje numerowanie wszystkich wierzchołków v 1, ,v 2n-1 spełniające warunki: w(v 1 ) w(v 2 ) w(v 2n-1 ), gdzie w(x) to waga wierzchołka x wierzchołki mające wspólnego rodzica mają sąsiednie numery I na odwrót: Jeśli drzewo kodu ma numerowanie spełniające powyższe warunki, kod jest optymalny Dla nas interesujące jest tylko to, że powyższe własności zachodzą dla numerowania, które sobie zdefiniowaliśmy: od dołu do góry od lewej do prawej.
Inicjalizacja Na początku (alfabet a 1,…,a m ): drzewo kodu: złożone z jednego wierzchołka NP (od „nie przesłany”) o wadze 0 i numerze 2m-1; UWAGI: –wierzchołek NP będzie w drzewie symbolizować wszystkie symbole, które jeszcze nie pojawiły się w tekście –numer 2m-1 dlatego, że będzie 2m-1 wierzchołków docelowo (m liści) Wszystkich literom przyporządkowujemy kody stałe, wykorzystywane tylko przy pierwszym pojawieniu się danej litery w tekście:
Kody stałe Niech e i r takie, że m = 2 e + r i 0 r < 2 e. Literze a i przyporządkowujemy kod stały: (e+1) -bitowej reprezentacji liczby i-1 gdy 1 i 2r e-bitowej reprezentacji liczby i-r-1 w przeciwnym przypadku. Czyli Kod stały równy kodowi o stałej długości równej log m, gdy m jest potęgą dwójki Mała optymalizacja kodu o stałej długości, gdy m nie jest potęgą dwójki: –2r symboli ma kod o długości log m –m - 2r symbol ma kod o długości log m
Kody stałe - przykład Niech m = 10 (alfabet ma 10 symboli). Wtedy : 10 = , czyli e=3 r=2 Inaczej: rysujemy drzewo o głębokości e+1 i staramy się wykorzystać „wolne” liście (dwa liście na poziomie e+1 dpowiadają jednemu wierzchołkowi na poziomie e) FAKT: kod stały jest kodem prefiksowym (ćw.) LiteraKod A10000 A20001 A30010 A40011 A5010 A6011 A7100 A8101 A9110 A10111
Kodowanie Dla kolejnego symbolu tekstu b: jeśli w drzewie kodu nie ma liścia o etykiecie b, kodujemy b jako: –kod wierzchołka NP –a za nim kod stały odpowiadający symbolowi b Dodaj 2 dzieci wierzchołka NP (o numerze p) –lewe dziecko to nowy NP (numerze p-2, waga 0) –prawe dziecko ma etykietę b (numer p-1, waga 1) Jeśli w drzewie kodu jest liść o etykiecie b: –kodujemy b za pomocą odpowiadającego mu w drzewie kodu słowa kodowego wykonaj aktualizację drzewa kodu
Dekodowanie Dopóki nie ma końca zakodowanego pliku: odkoduj słowo kodowe odpowiadające liściowi aktualnego drzewa kodu jeśli odkodowane słowo kodowe odpowiada literze alfabetu: zapisz ją. jeśli odkodowane słowo kodowe odpowiada wierzchołkowi NP: –odkoduj kolejną literę według kodu stałego (e lub e+1 bitów według drzewa kodu stałego): zapisz ją. Następnie, dodaj 2 dzieci wierzchołka NP (o numerze p) –lewe dziecko to nowy NP (numerze p-2, waga 0) –prawe dziecko ma etykietę b (numer p-1, waga 1) wykonaj aktualizację drzewa kodu.
Aktualizacja drzewa kodu CEL - zachowanie niezmiennika: numerowanie wszystkich wierzchołków v 1, ,v 2n-1 (od dołu do góry, od lewej do prawej) ma spełniać warunek: w(v 1 ) w(v 2 ) w(v 2n-1 ), gdzie w(x) to waga wierzchołka x Idea rozwiązania: przechodzimy ścieżkę od liścia odpowiadającego ostatniemu symbolowi i zwiększamy wagi wszystkich wierzchołków o 1 gdy zwiększenie wagi zaburza powyższy niezmiennik, zamieniamy aktualny wierzchołek z najwyżej położonym wierzchołkiem o takiej samej wadze. Efekt: koszt proporcjonalny do długości słowa kodowego a nie n log n....
Aktualizacja drzewa kodu c.d. Blok: zbiór wierzchołków o tej samej wadze. UWAGI: Jeśli numeracja v 1, ,v 2n-1 spełnia warunek w(v 1 ) w(v 2 ) w(v 2n-1 ), to wszystkie wierzchołki z jednego bloku tworzą spójny obszar w tej numeracji Jak reprezentujemy bloki: lista dwustronna w kolejności odpowiadającej numeracji wszystkich wierzchołków dodatkowo wskaźniki na początki bloków
Aktualizacja drzewa kodu c.d. Niech v to wierzchołek odpowiadający ostatnio zakodowanemu bądź odkodowanemu symbolowi: Dopóki v jest różny od korzenia: jeśli numer v nie jest największy w bloku do którego v należy: zamień v z wierzchołkiem w o największym numerze w bloku (o ile w nie jest rodzicem v). UWAGI: –zamieniamy całe poddrzewa –v i w zamieniają się numerami –ale numery pozostałych wierzchołków nie zmieniają się zwiększ wagę v o jeden: w(v) w(v)+1 v rodzic(v)
Przykład: dyn. Huffman Alfabet {A, B, C, D,..., J} – 10 elementów. Tekst do zakodowania: A A B C D A D Kody stałe: Drzewo kodu: LiteraKod A0000 B0001 C0010 D0011 E010 F011 G100 H101 I110 J111 0 NP
Przykład c.d. A A B C D A D Drzewo kodu: OUTPUT: 0000 kod stały A UWAGA: kod wierzchołka NP jest pusty! 0 NP 21
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: 0000 kod stały A UWAGA: kod wierzchołka NP jest pusty! 1 A 01 NP
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A 01 NP
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A 02 NP
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: kod NP kod stały B 2 A 02 NP
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NPB
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NPB
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: kod NP kod stały C 3 A NPB
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NP B c 1516
Przykład c.d.: A A B C D A D Drzewo kodu: Popraw. śc. OUTPUT: A NP B c 1516
Przykład c.d.: A A B C D A D Drzewo kodu: Popraw. śc. OUTPUT: kod NP kod stały D 4 A NP B c 1516
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NP B c D 1314
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NP B c D 1314 ZAMIANA!
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NP B c D 1314 ZAMIANA!
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NP B c D 1314
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NP B c D 1314
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: Uwaga: A był kodowany jako 0000, 1 a na końcu jako 0. 5 A NP B c D 1314
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NP B c D 1314
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NP B c D 1314
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NP B c D 1314 ZAMIANA!
Przykład c.d.: A A B C D A D Drzewo kodu: OUTPUT: A NP D c B 1314
Dynamiczny Huffman: struktury danych Tabela kodu stałego Binarne drzewo kodu H. Wskaźniki na liście dla każdej litery Lista dwustronna wg numeracji (oraz wskaźniki na początki bloków) CZAS: liniowy względem rozmiaru kodu 7 A NP D c B 1314 A B C D
Dynamiczny Huffman: niezmiennik? Chcemy pokazać, że algorytm modyfikacji drzewa kodu zachowuje własności: Numeracja w algorytmie v 1, ,v 2n-1 jest numeracją od dołu do góry i od lewej do prawej Wagi wierzchołków spełniają warunek: w(v 1 ) w(v 2 ) w(v 2n-1 ) Szkic dowodu: Zamiana z największym w bloku gwarantuje, że zanim zwiększymy wagę wierzchołka, „wypchniemy” go przed wszystkie wierzchołki, których waga stanie się mniejsza (czyli „na początek” jego bloku) Ale razem z wierzchołkiem „przestawiamy” całe jego poddrzewo... co może zaburzyć numerację Jednak: wszystkie wierzchołki pomiędzy dwoma zamienianymi są liśćmi (poza jednym przypadkiem...)
Kodowanie Huffmana: podsumowanie Własności Optymalny wśród prefiksowych Kodowanie i dekodowanie w czasie liniowym! Kody rozszerzone: kompromis między zasobami a kompresją Możliwość implementacji jednoprzebiegowej, dynamicznej: kompresja zbliżona do kodu statycznego, dodatkowy czas liniowy Zastosowania: pkZIP, lha, gz, zoo, arj. formaty JPEG i MPEG (jako jeden z etapów, czasem zmodyfikowany) Eksperymenty Bezstratna kompresja obrazów: współczynnik 1,5 Kompresja tekstów w języku naturalnym: wsp. 2 Kompresja dźwięku: wsp. 1,5 (kodowanie różnic)
Jak robiono to dawniej... Kody Shannona... czyli nierówność Krafta jest konstruktywna: p 1 … p n to prawdopodobieństwa symboli F i = p 1 + p p i-1 Kod: Słowo kodowe symbolu a i to pierwszych l i = log (1/p i ) bitów (``po przecinku'') w binarnej reprezentacji liczby F i. Ale: Pokazaliśmy wcześniej, że kod o takich długościach ma średnią długość co najwyżej H(p 1,…, p n )+1
Jak robiono to dawniej... Trzeba tylko pokazać, że: Kod Shannona jest kodem prefiksowym. Dowód (szkic): F i+1 – F i = p i 1 / 2 l_i oraz l i+1 l i gwarantuje, że słowa kodowe coraz dłuższe a różnica między kodem a i i kodem a j dla j > i musi wystąpić wśród pierwszych l i pozycji. Do tego tematu wrócimy...
Jak robiono to dawniej... dziel i zwyciężaj Kod Shannon-Fano v1.0.0: Niech p 1 … p n to prawdopodobieństwa symboli Jeśli n = 1, to kod(a 1 )=0. Jeśli n=2, to kod(a 1 )=0, kod(a 2 )=1. Jeśli n > 2 : –podziel ciąg {p 1,..., p n } na dwa podciągi R = {p 1,...,p n1 } i S={p n1+1,..., p n } takie, że różnica |(p p n1 ) – (p n p n )| jest minimalna. Rekurencyjnie, znajdź kody Shannon-Fano dla prawdopodobieństw p 1,...,p n1 oraz p n1+1,...,p n Poprzedź kody symboli z S bitem 0, kody symboli z R, bitem 1.
Jak robiono to dawniej... dziel i zwyciężaj Kod Shannon-Fano v1.0.1 Niech p 1 … p n to prawdopodobieństwa symboli –Wybieramy nie punkt podziału w uporządkowanym ciągu p 1,..., p n lecz podział zbioru {p 1,..., p n } na dwa podzbiory dające minimalną różnicę sum prawdopodobieństw