690 likes | 1.19k Views
Algorytmy kodowania Shannona-Fano i Huffmana. Algorytmy Kompresji Danych wykład 3 Roman Starosolski. Plan wykładu. Algorytm kompresji statystycznej Algorytm Shannona-Fano Algorytm Huffmana Rozszerzone kody Huffmana Dynamiczne kodowanie Huffmana
E N D
Algorytmy kodowania Shannona-Fano i Huffmana Algorytmy Kompresji Danychwykład 3Roman Starosolski
Plan wykładu • Algorytm kompresji statystycznej • Algorytm Shannona-Fano • Algorytm Huffmana • Rozszerzone kody Huffmana • Dynamiczne kodowanie Huffmana • Adaptacyjny algorytm kompresji z wykorzystaniem rodziny kodów (Golomb, Golomb-Rice)
Algorytm kompresji statystycznej Algorytm kompresji statystycznej = = probabilistyczny model danych + + koder entropijny (+ dodatkowe przetwarzanie) (+ dekoder)
Koder entropijny • Koder entropijny to algorytm, którego zadaniem jest, na podstawie znajomości prawdopodobieństw występowania symboli, binarne zakodowanie ciągu symboli tak, aby zminimalizować średnią długość ciągu po zakodowaniu. Z podstawowego twierdzenia Shannona o kodowaniu bezszumowym: LŚr ≥ H(S) gdzie LŚr to średnia długość słowa kodowego kodu przedrostkowego. Zatem dążymy do uzyskania średniej długości słowa kodowego możliwie bliskiej entropii (stąd nazwa „koder entropijny”). • Budowa kodera entropijnego jest banalnie prosta, gdy dysponujemy kodem optymalnym dla rozkładu prawdopodobieństwa symboli źródła
Algorytmy adaptacyjne i inne • algorytm (semi-)statyczny • stosowany w algorytmach dwuprzebiegowych • przebieg 1: analiza stałego ciągu, budowa modelu • przebieg 2: kodowanie • problemy: • dwuprzebiegowy • wymaga transmisji i modelu i zakodowanego ciągu • algorytm adaptacyjny • algorytm jednoprzebiegowy, on-line • aktualizacja modelu każdorazowo po zakodowaniu symbolu (taki model nazywamy adaptacyjnym modelem danych) • problemy • przyrostowa aktualizacja modelu nie może być złożona • Zero Frequency Problem
Algorytmy adaptacyjne i inne • algorytm stały • budowa dla całej klasy danych • kodowanie przy założeniu, że dane należą do tej klasy • problemy • dane „nie pasujące” do założeń • inne • kilka (stałych) modeli przełączanych adaptacyjnie podczas kodowania • podział ciągu na podciągi i zastosowanie algorytmu statycznego do każdego podciągu z osobna • ...
„Podstawowe twierdzenie Shannona o kodowaniu bezszumowym” (jedna z postaci twierdzenia, wg.: Drozdek: „Wprowadzenie do kompresji danych”) Dla bezpamięciowego źródła S o entropii H(S) możliwe jest przypisanie ciągom k symboli źródła, słów kodu przedrostkowego tak, że spełnione jest H(S) ≤ Lk / k < H(S) + 1/k • asymptotycznie, możliwe jest uzyskanie średniej długości kodu (w przeliczeniu na pojedynczy symbol) równej entropii źródła • optymalna długość słowa kodowego dla symbolu o prawdopodobieństwie p równa jest –log(p) (czyli autoinformacji dla tego symbolu) • można zbudować koder entropijny o efektywności bliskiej 100%
Jak wygenerować kod przedrostkowy? • Problem: • Mamy wyznaczony przez model rozkład prawdopodobieństwa symboli źródła p1, p2, p3, p4 ... pN, :∑pi = 1 • Znamy optymalne długości słów kodowych (tj. przybliżone długości słów optymalnego kodu przedrostkowego) l1, l2, l3, l4 ... lN: li = –log(pi) • Wiemy jaki warunek muszą spełniać długości słów kodowych aby istniał kod jednoznacznie dekodowalny (nierówność Krafta) ∑2-li ≤ 1 • Chcemy wyznaczyć • Kod przedrostkowy o minimalnej średniej długości kodu • szukamy: dokładnych długości słów • szukamy: postaci (kolejnych bitów) słów
Algorytm Shannona-Fano • Algorytm Shannona-Fano generuje kod przedrostkowy dla zadanego rozkładu prawdopodobieństwa symboli alfabetu.
(na podstawie: A. Drozdek, Wprowadzenie do kompresji danych, WNT, Warszawa, 1999) Krok 1: Ustaw symbole alfabetu źródła w ciąg s uporządkowane zgodnie z prawdopodobieństwem ich użycia; Krok 2: AlgorytmShannonaFano(ciąg s) if s zawiera dwa symbole dołącz 0 do słowa kodowego jednego z symboli, 1 do słowa drugiego symbolu; elseif s zawiera więcej niż dwa symbole podziel s na dwa podciągi s1 i s2 tak, by różnica między sumą prawdopodobieństw symboli w podciągach była najmniejsza; dołącz 0 do słów kodowych symboli w s1 i 1 do słów symboli w s2; AlgorytmShannonaFano(s1); AlgorytmShannonaFano(s2); endif;
Przykład: kodujemy ciąg abracadabra w tabeli mamy ciąg symboli alfabetu źródła i kolejne kroki budowania słów kodowych
Przykład: kodujemy ciąg abracadabra można wygenerować kod o innych długościach słów
Algorytm Huffmana • Algorytm Huffmana generuje kod przedrostkowy dla zadanego rozkładu prawdopodobieństwa symboli alfabetu.
W algorytmie Huffmana buduje się drzewo binarne, zwane drzewem Huffmana. • Każdemu z liści odpowiada pojedynczy symbol alfabetu źródła. • Z każdym węzłem skojarzona jest waga równa łącznemu prawdopodobieństwu liści w poddrzewie dla którego ten węzeł jest korzeniem • Utwórz n drzew, gdzie n jest rozmiarem alfabetu źródła. Każdemu z symboli alfabetu źródła odpowiada pojedyncze drzewo składające się wyłącznie z korzenia i mające wagę równą prawdopodobieństwu wystąpienia danego symbolu. • Wyznacz dwa drzewa o najmniejszych wagach i utwórz z nich nowe drzewo, w którym dwa właśnie wyznaczone drzewa te są synami korzenia o wadze równej sumie ich wag. Powtarzaj krok 2 aż pozostanie tylko jedno drzewo (n – 1 razy). • Słowo kodowe kodu Huffmana dla danego symbolu znajduje się przechodząc ścieżką od korzenia drzewa Huffmana do liścia odpowiadającego temu symbolowi (i-ty bit słowa kodowego ma wartość 0, jeżeli i-ta krawędź ścieżki prowadzi do lewego syna i-tego węzła, a 1 — jeżeli do prawego).
a r d c b 2/11 1/11 1/11 5/11 2/11 Przykład: kodujemy ciąg abracadabra • Utwórz n drzew, gdzie n jest rozmiarem alfabetu źródła. Każdemu z symboli alfabetu źródła odpowiada pojedyncze drzewo składające się wyłącznie z korzenia i mające wagę równą prawdopodobieństwu wystąpienia danego symbolu.
a r d c b 2/11 1/11 1/11 5/11 2/11 2/11 Przykład: kodujemy ciąg abracadabra • Wyznacz dwa drzewa o najmniejszych wagach i utwórz z nich nowe drzewo, w którym dwa właśnie wyznaczone drzewa te są synami korzenia o wadze równej sumie ich wag. Powtarzaj krok 2 aż pozostanie tylko jedno drzewo (n – 1 razy). 0 1
a r d c b 2/11 2/11 4/11 1/11 1/11 2/11 5/11 Przykład: kodujemy ciąg abracadabra • Wyznacz dwa drzewa o najmniejszych wagach i utwórz z nich nowe drzewo, w którym dwa właśnie wyznaczone drzewa te są synami korzenia o wadze równej sumie ich wag. Powtarzaj krok 2 aż pozostanie tylko jedno drzewo (n – 1 razy). 0 1 0 1
a b r d c 4/11 2/11 2/11 6/11 1/11 1/11 2/11 5/11 Przykład: kodujemy ciąg abracadabra • Wyznacz dwa drzewa o najmniejszych wagach i utwórz z nich nowe drzewo, w którym dwa właśnie wyznaczone drzewa te są synami korzenia o wadze równej sumie ich wag. Powtarzaj krok 2 aż pozostanie tylko jedno drzewo (n – 1 razy). 0 1 0 1 0 1
a r d c b 6/11 4/11 2/11 5/11 1 1/11 2/11 2/11 1/11 Przykład: kodujemy ciąg abracadabra 0 1 0 1 0 1 0 1
0 1 0 1 0 1 0 1 a b c r d 1 2/11 4/11 5/11 1/11 1/11 2/11 6/11 2/11 Przykład: kodujemy ciąg abracadabra • Słowo kodowe kodu Huffmana dla danego symbolu znajduje się przechodząc ścieżką od korzenia drzewa Huffmana do liścia odpowiadającego temu symbolowi (i-ty bit słowa kodowego ma wartość 0, jeżeli i-ta krawędź ścieżki prowadzi do lewego syna i-tego węzła, a 1 — jeżeli do prawego).
Własności kodów Huffmana • Podobnie, jak algorytm Shannona-Fano, przedstawiony algorytm jest niedeterministyczny • niedeterminizm można łatwo usunąć –kanoniczne kodowanie Huffmana • Efektywnośc kodów Huffmana jest typowo nieznacznie większa niż Shannona-Fano • (dla przykładu „abracadabra” jest taka sama) • algorytm Huffmana jest prostszy
Własności kodów Huffmana • Kod wygenerowany algorytmem Huffmana jest optymalny w klasie kodów przedrostkowych (gdy prawdopodobieństwa symboli są 2 -N, gdzie N jest nieujemną liczbą całkowitą, to kod jest optymalny, gdyż dla symbolu o prawdopodobieństwie p optymalna długość słowa kodowego to –log(p) bitów) • Nieefektywność kodu Huffmana • Jednocześnie nieefektywność kodów przedrostkowych pmaxto prawdopodobieństwo najbardziej prawdopodobnego symbolu.
Rozszerzone kody Huffmana • Kod Huffmana jest nieefektywny, gdy prawdopodobieństwo wystąpienia jednego z symboli jest duże, najbardziej gdy bliskie 1. • długość słowa kodowego tego symbolu wynosi 1 bit • optymalna długość słowa kodowego jest bliska 0 • zatem średnia długość kodu jest wielokrotnie większa od entropii rozkładu prawdopodobieństwa symboli.
„Podstawowe twierdzenie Shannona o kodowaniu bezszumowym” (jedna z postaci twierdzenia, wg.: Drozdek: „Wprowadzenie do kompresji danych”) Dla bezpamięciowego źródła S o entropii H(S) możliwe jest przypisanie ciągom k symboli źródła, słów kodu przedrostkowego tak, że spełnione jest H(S) ≤ Lk / k < H(S) + 1/k • asymptotycznie, możliwe jest uzyskanie średniej długości kodu (w przeliczeniu na pojedynczy symbol) równej entropii źródła • optymalna długość słowa kodowego dla symbolu o prawdopodobieństwie p równa jest –log(p) (czyli autoinformacji dla tego symbolu) • można zbudować koder entropijny o efektywności bliskiej 100%
Rozszerzone kody Huffmana • Kod Huffmana jest nieefektywny, gdy prawdopodobieństwo wystąpienia jednego z symboli jest duże, najbardziej gdy bliskie 1. • długość słowa kodowego tego symbolu wynosi 1 bit • optymalna długość słowa kodowego jest bliska 0 • zatem średnia długość kodu jest wielokrotnie większa od entropii rozkładu prawdopodobieństwa symboli. • Zamiast konstruować kod dla symboli alfabetu źródła możemy zinterpretować kodowany ciąg jako ciąg nad rozszerzonym alfabetem zawierającym ciągi symboli alfabetu źródła
Przykład: Rozszerzone kody Huffmana Kodujemy ciąg binarny (alfabet źródła to {0, 1}) Prawdopodobieństwo wygenerowania zera przez źródło wynosi 0.9 Entropia rozkładu prawdopodobieństwa H(S) wynosi 0.467. Efektywność kodu Huffmana dla tego źródła wynosi EHuffBin=( H(S)/LHuffBin )•100%=47% Jeżeli kodować będziemy pary symboli ... ... to średnia długość słowa kodowego (w przeliczeniu na symbol alfabetu źródła) wyniesie 0.649, czyli efektywność kodu wzrośnie do około 72 %.
Algorytm kompresji z kodowaniem Huffmana • Typowe zastosowanie algorytmu Huffmana (i Shannona-Fano) to statyczny algorytm kompresji. • zbudowanie modelu wymaga znajomości rozkładu prawdopodobieństwa symboli źródła, praktycznie przesyła się tablicę częstości wystąpień symboli • koszt transmisji modelu np. dla alfabetu źródła ASCII jest niewielki • kodowanie jest bardzo szybkie • po zbudowaniu modelu wyliczamy słowa kodowe wszystkich symboli alfabetu źródła • kodowanie polega na wyprowadzeniu już wyliczonych słów kodowych dla kolejnych symboli kodowanego ciąg
Algorytm kompresji z kodowaniem Huffmana • Zalety • prosty • szybki • wady • nieefektywny gdy prawdopodobieństwo wystąpienia jednego z symboli alfabetu źródła jest duże (ale można kodować ciągi symboli) • dwuprzebiegowy • koszt transmisji modelu może być duży • nie do zastosowania wprost do kodowania on-line
Dynamiczne kodowanie Huffmana • Użycie algorytmu Huffmana w adaptacyjnym modelu jest możliwe Metoda brute-force – każdorazowo po zakodowniu symbolu buduj od nowa drzewo Huffmana ( Uwaga na Zero Frequency Problem ) ... ale w praktyce zbyt kosztowne
Dynamiczne kodowanie Huffmana • Algorytmy Dynamicznego kodowania Huffmana • (Adaptacyjne kodowanie Huffmana) • To algorytm generujący kod zbliżony do kodu Huffmana, nadający się do zastosowania w algorytmie adaptacyjnym • Drzewo Huffmana budowane jest przyrostowo – możliwa jest „aktualizacja modelu” • Ww. algorytm został wynaleziony niezależnie przez Fallera i Gallagera • ... udoskonalony przez Cormacka i Horspoola oraz (niezależnie) przez Knutha • ... następnie udoskonalony przez Vittera
Algorytm Fallera-Gallagera • Budujemy przyrostowo drzewo binarne, którego węzły zawierają liczniki częstości, a liście są dodatkowo skojarzone z symbolami alfabetu źródła • Drzewo ma własność rodzeństwa gdy: • każdy węzeł nie będący liściem ma 2 potomków • przechodząc węzły w kolejności z góry do dołu, a na danym poziomie od prawej do lewej, otrzymamy ciąg węzłów o nierosnących licznikach • Drzewo mające własność rodzeństwa jest drzewem Huffmana (tw. Fallera-Gallagera)
a r d c b 2 1 1 5 2 2 4 6 11 Przykład: drzewo mające własność rodzeństwa (wygląda znajomo?)
Algorytm Fallera-Gallagera • Budowane drzewo zawiera liść (0-węzeł) reprezentujący wszystkie symbole, które jeszcze nie wystąpiły w kodowanym ciągu • Kodowanie rozpoczynamy od drzewa składającego się wyłącznie z 0-węzła • Używamy pomocniczej struktury węzły, listy dwukierunkowej zawierającej węzły drzewa uporządkowane w kolejności przeglądania drzewa z góry do dołu, a na danym poziomie od prawej do lewej • Podlistę listy węzły składającą się z wszystkich węzłów o wartości licznika i nazywamy blokiem-i , a pierwszy węzeł takiego bloku liderem
(na podstawie: A. Drozdek, Wprowadzenie do kompresji danych, WNT, Warszawa, 1999) DynamiczneKodowanieHuffmanaFGK(symbol s) p = liść zawierający symbol s; wyprowadź słowo kodowe dla s (*); if p jest 0-węzłem utwórz nowy węzeł q dla symolu s; q.licznik = 1; p = nowy węzeł w miejscu 0-węzła będący rodzicem 0-węzła i węzła q; p.licznik = 1; else p.licznik++; endif while p nie jest korzeniem if p narusza własność rodzeństwa if liderbloku-i zawierającego p nie jest rodzicem p zamień p z liderem; endif endif p = rodzic(p); p.licznik++; endwhile
a q p a 0 1 1 0 Przykład: kodujemy ciąg abrr, inicjalizacja i wstawienie pierwszego symbolu ( a )
a b q q p p b a a 0 1 0 1 1 1 1 0 1 Przykład: kodujemy ciąg abrr, wstawienie symbolu b
r a b q q p p a b a a b 1 0 1 1 0 1 1 0 1 1 1 2 0 1 Przykład: kodujemy ciąg abrr, wstawienie symbolu b
r a b 2 1 1 1 0 wstawienie symbolu r
r p a a b b r 2 1 1 1 0 1 0 2 1 2 1 1 wstawienie symbolu r
r p p b a a b r r b a 0 2 1 1 1 0 1 1 2 1 1 2 1 2 1 0 1 2 1 wstawienie symbolu r (przywróć własność rodzeństwa)
p r a b 1 1 0 2 1 2 1 wstawienie symbolu r (przywróć własność rodzeństwa)
p p b r a r b a 1 1 0 2 1 2 1 1 1 2 1 0 2 1 wstawienie symbolu r (przywróć własność rodzeństwa)
r p p r r b a a a r b b 2 1 2 1 1 1 1 0 2 1 1 3 1 1 1 2 0 2 1 1 0 wstawienie symbolu r
r a r b 3 1 1 1 0 2 1 ponowne wstawienie symbolu r (ciąg abrr)
r p a b r b r a 1 1 1 0 2 1 0 1 2 1 2 1 3 3 ponowne wstawienie symbolu r (ciąg abrr)
r p p a r b b b a r r a 1 1 1 1 0 2 1 3 1 0 1 2 3 2 3 1 1 1 2 0 2 ponowne wstawienie symbolu r (przywróć własność rodzeństwa)
p a r b 3 1 1 2 0 2 1 ponowne wstawienie symbolu r (przywróć własność rodzeństwa)
p p a b r b a r 1 1 2 0 2 1 0 1 1 2 2 1 3 4 ponowne wstawienie symbolu r (przywróć własność rodzeństwa)
r a b 4 2 1 1 0 2 1 postać drzewa po przetworzeniu ciągu abrr
Algorytm Vittera • Dodatkowe założenie: w bloku-i węzłów najpierw znajdują się węzły wewnętrzne, później liście • minimalizujemy głębokość drzewa • bardziej złożone staje się przywracanie własności rodzeństwa • ciąg o długości s zakodujemy na nie więcej niż h+s bitach, gdzie h to liczba bitów dla kodowania statycznego Huffmana • ten algorytm radzi sobie z ZFP lepiej, niż algorytm Fallera-Gallagera • (dla niektórych ciągów wynik dynamicznego kodowania Huffmana może by lepszy, niż w statycznym algorytmie, bo tu nie przesyłamy modelu)