Algorytm kodowania
arytmetycznego
Algorytmy Kompresji Danych
wykład 4
Roman Starosolski
Plan wykładu
Historia kodowania arytmetycznego
Idea kodowania arytmetycznego
Koncepcja implementacji dla liczb o ograniczonej
precyzji
Wybrane algorytmy
MQ-Coder
Range-Coder
Szybki model dla kodera arytmetycznego
Historia kodowania arytmetycznego
Historia
Shannon (1948) ― „Podstawowe twierdzenie ...”, wzmianka o
możliwości kodowania arytmetycznego
Elias (196x) ― dalsze prace
Jelinek (1968) ― dalsze prace
Rissanen i Pascoe (1976, niezależnie) ― idea implementacji w
arytmetyce stałopozycyjnej
IBM (198x) ― algorytmy zorientowane na sprzętowe kodowanie
alfabetu binarnego (w większości opatentowane)
Witten (1987) i inni ― implementacje programowe dla alfabetów
binarnych i nie-binarnych
„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%
Idea kodowania arytmetycznego
Cały ciąg zostanie zakodowany za pomocą jednej liczby rzeczywistej z
lewostronnie domkniętego przedziału zawartego w przedziale [0, 1)
(Jeżeli długość ciągu nie jest znana dekoderowi, to należy ją przetransmitować,
bądź uzupełnić alfabet źródła o symbol oznaczający koniec ciągu.)
Zaczynamy od przedziału [0, 1)
Czytając kolejne symbole ciągu zawężamy stopniowo początkowy
przedział;
dla kolejnego symbolu pobranego z ciągu
przedział dzieli się na lewostronnie domknięte podprzedziały o długościach
wprost proporcjonalnych do prawdopodobieństw poszczególnych symboli
alfabetu źródła
wybiera się przedział odpowiadający odczytanemu symbolowi.
Wyprowadzamy dowolną liczbę z przedziału wyznaczonego dla ciągu
Przykład
komdujemy ciąg abaca,
Używamy stałego modelu danych
alfabet źródła to {a, b, c}
P(a)=0.6, P(b)=0.2, P(c)=0.2
Przyjmujemy, iż długość ciągu jest znana dekoderowi.
Zaczynamy od przedziału [0, 1). Dla kolejnego symbolu z ciągu
odczytaliśmy a
... przedział dzieli się na lewostronnie domknięte podprzedziały o długościach wprost
proporcjonalnych do prawdopodobieństw poszczególnych symboli alfabetu źródła,
a: [0, 0.6)
b: [0.6, 0.8)
c: [0.8, 1)
... wybiera się przedział odpowiadający temu symbolowi.
przedział dla ciągu a: [0, 0.6)
1
0.6
c
0.8
b
0.6
a
0
0
przedział dla ciągu a: [0, 0.6). Dla kolejnego symbolu z ciągu
odczytaliśmy b
... przedział dzieli się na lewostronnie domknięte podprzedziały o długościach wprost
proporcjonalnych do prawdopodobieństw poszczególnych symboli alfabetu źródła,
aa: [0, 0.36)
ab: [0.36, 0.48)
ac: [0.48, 0.6)
... wybiera się przedział odpowiadający temu symbolowi.
przedział dla ciągu ab: [0.36, 0.48)
1
0.6
c
c
0.8
0.48
b
b
0.6
0.36
a
a
0
0
0.48
0.36
przedział dla ciągu ab: [0.36, 0.48). Dla kolejnego symbolu z ciągu
odczytaliśmy a
... przedział dzieli się na lewostronnie domknięte podprzedziały o długościach wprost
proporcjonalnych do prawdopodobieństw poszczególnych symboli alfabetu źródła,
aba: [0.36, 0.432)
abb: [0.432, 0.456)
abc: [0.456, 0.48)
... wybiera się przedział odpowiadający temu symbolowi.
przedział dla ciągu aba: [0.36, 0.432)
1
0.6
0.48
c
c
c
0.8
0.48
b
b
0.6
0.36
a
a
0
0
0.432
0.456
b
0.432
a
0.36
0.36
... po przetworzeniu całego ciągu abaca
otrzymujemy przedział dla ciągu abaca: [0.4176, 0.432)
Wyprowadzamy dowolną liczbę z przedziału wyznaczonego dla ciągu
1
np. 0.42
(a wiedząc, że liczba jest z [0, 1), wystarczy wyprowadzić znaczące cyfry
mantysy: 42)
0.6
0.48
c
c
c
0.8
0.48
b
b
0.6
0.36
a
a
0
0
0.456
b
0.432
0.432
c
0.4176
b
0.4032
a
a
0.36
0.36
0.432
0.42624
c
0.42912
b
0.42624
a
0.4176
0.4176
Przedziału uzyskany po wykonaniu algorytmu
długość
przedziału równa jest prawdopodobieństwu
wygenerowania odczytanego ciągu
dla
każdego możliwego ciągu o długości n symboli
otrzymamy inny przedział
przedziały te nie pokrywają się
ich suma jest przedziałem [0, 1)
dla
podprzedziału o długości p można znaleźć taką
liczbę, że binarne zakodowanie jej mantysy wymagać
będzie nie więcej niż
–log(p) + 1 bitów
Dekodowanie
Znając rozkład prawdopodobieństwa symboli alfabetu i długość
ciągu,
pobieramy zakodowany ciąg, czyli kod 42 → liczba 0.42
zaczynamy od przedziału [0, 1)
cyklicznie, aż nie zdekodujemy ciągu o danej długości:
1
analogicznie do kodowania, dzielimy przedział na podprzedziały dla
wszystkich symboli alfabetu
wybieramy podprzedział który zawiera liczbę 0.42
wyprowadzamy symbol odpowiadający temu podprzedziałowi
0.6
0.48
c
c
c
0.8
0.48
b
b
0.6
0.36
0.432
c
0.456
0.42
0.4176
b
b
0.432
0.4032
0.432
0.42
0.42624
c
0.42912
b
0.42624
0.42
a
a
a
a
a
0.42
0.42
0
0
0.36
0.36
0.4176
0.4176
Cechy kodowania arytmetycznego
efektywność bliska 100%, asymptotycznie 100%
bez ograniczeń co do rozkładu prawdopodobieństwa symboli
(ani co do rozmiaru alfabetu)
niezależność kodera od modelu
można stosować różne modele probabilistyczne
można stosować wiele modeli
nadaje się do algorytmów adaptacyjnych
(trzeba uzupełnić alfabet o symbol EOF)
nie da się go zaimplementować wprost dla liczb o ograniczonej
precyzji
Koncepcja implementacji dla liczb
o ograniczonej precyzji
Część cyfr liczby z wnętrza przedziału można wyprowadzać już w trakcie kodowania
1
gdy początkowe cyfry górnego i dolnego kresu przedziału są takie same,
to takie będą również cyfry każdej liczby wewnątrz przedziału
można więc te cyfry wyprowadzić
(i odpowiednio przeskalować przedział aby korzystać z pełnej dostępnej dokładności)
0.6
0.48
c
c
c
0.8
0.48
b
b
0.6
0.36
a
a
0
0
0.456
b
0.432
0.432
c
0.4176
b
0.4032
a
a
0.36
0.36
0.432
0.42624
c
0.42912
b
0.42624
a
0.4176
0.4176
Koncepcja implementacji dla liczb o
ograniczonej precyzji
Model danych
model oparty o liczby całkowite zamiast prawdopodobieństw
zlicza liczby wystąpień symboli
do wyznaczenia podprzedziału dla kolejnego symbolu wystarczy
znać jego prawdopodobieństwo oraz łączne
prawdopodobieństwo wszystkich symboli poprzedzających go w
alfabecie
ponieważ łatwiej to robić w modelu, zazwyczaj to model
wyznacza skumulowane prawdopodobieństwa
Pi = ∑ki= 1 pi ,
gdzie pi to prawdopodobieństwo symbolu si , tj. i-tego symbolu
alfabetu; tu symbole numerujemy od 1
Koncepcja implementacji dla liczb o
ograniczonej precyzji
Reprezentacja przedziału
kresy pamiętamy na m-bitowych liczbach całkowitych, M = 2 m –1
przedziały traktujemy jako obustronnie domknięte,
kres górny danego podprzedziału jest o 1 mniejszy od kresu
dolnego następnego podprzedziału
w przypadku skalowania przedziału trzeba zadbać aby powyższa
własność została zachowana (po pomnożeniu kresów przez 2
odległość między nimi wzrośnie do 2)
zaczynamy od przedziału [0, M], tj. [0000...0 i 1111...1]
prawdopodobieństwo symbolu wyznaczone przez model nie może
spowodować wyznaczenia przedziału o długości 0
Normalizacja podprzedziału zawartego całkowicie w
górnej lub dolnej połówce przedziału [0, M]
wyprowadź 1 (górna), lub 0 (dolna) i przeskaluj
co zrobić gdy podprzedział zawiera środek przedziału [0, M]?
Normalizacja podprzedziału zawierającego środek
przedziału [0, M]
koduj alfabet binarny, to nie będzie takich problemów (przy
odpowiedniej normalizacji), albo
gdy długość podprzedziału będzie mniejsza od M/2
przeskaluj
zliczaj przeskalowania, ale nie wyprowadzaj bitów
(na podstawie: A. Drozdek, Wprowadzenie do kompresji danych, WNT, Warszawa, 1999)
Kodujemy symbol si (i-ty symbolu alfabetu), aktualny przedział to [L, R]
bieżącyPrzedział = [
L + Pi-1(R – L + 1) , L + Pi (R – L + 1)
]
Normalizacja przedziału (gdy przedział jest mały wykonywana kilkakrotnie)
while(1)
if bieżącyPrzedział [0, M/2]
zwróc 0 i licznikBitów jedynek
licznikBitów = 0
elseif bieżącyPrzedział [M/2, M]
zwróc 1 i licznikBitów zer
licznikBitów = 0
odejmij M/2 od obu kresów bieżącegoPrzedziału
elseif bieżącyPrzedział [M/4, 3M/4]
licznikBitów ++ // tylko tu możliwe przepełnienie
odejmij M/4 od obu kresów bieżącegoPrzedziału
else
break
endif
pomnóż przez 2 oba kresy bieżącegoPrzedziału
dodaj 1 do górnego kresu bieżącegoPrzedziału
endwhile
(na podstawie: A. Drozdek, Wprowadzenie do kompresji danych, WNT, Warszawa, 1999)
Zakończenie kodowania
(wyprowadź resztę „stanu” kodera arytmetycznego,
czyli końcówkę rozwinięcia binarnego liczby z bieżącego przedeziału)
licznikBitów ++
if kres dolny bieżącegoPrzedziału < M/4
zwróc 0 i licznikBitów jedynek
else
zwróc 1 i licznikBitów zer
endif
Wybrane Implementacje
Witten, Neal, Cleary (1987)
(tzw. CACM, zasada działania pokazana na poprzednich slajdach)
ftp://ftp.cpsc.ucalgary.ca/pub/projects/ar.cod/cacm-87.shar
Moffat, Neal, Witten (1998)
(udoskonalony CACM, bez dzielenia, mniej mnożeń, shift, +, –)
http://www.cs.mu.oz.au/~alistair/arith_coder/
MQ Coder
(binarny, opatentowany /IBM i in./, użyty w JBIG2, JPEG2000 i innych)
Range Coder
(szybki, prosty, alfabety wielosymbolowe)
http://www.compressconsult.com/rangecoder/
Binarny koder arytmetyczny
MQ Coder
Kodujemy symbole alfabetu binarnego
bardzo proste modelowanie,
nie trzeba liczyć prawdopodobieństwa kumulatywnego
wystarczy szacować prawdopodobieństwo tylko jednego symbolu
bardzo proste kodowanie
nowy symbol to zmiana tylko jednego kresu przedziału
podprzedział o długości M/2 zawsze jest całkowicie zawarty w górnej lub
dolnej połówce przedziału [0, M] (przy odpowiedniej normalizacji)
MQ Coder – dalsze uproszczenia
normalizacja utrzymuje długość podprzedziału między 0.75 a 1.5, średnio 1.0
faktycznie kodujemy alfabet {MPS, LPS} – czyli bardziej i mniej prawdopodobny
symbol; to, czy MPS to 0 czy 1 jest określa flaga uaktualniana po każdym symbolu
unikamy mnożenia; nowa długość podprzedziału dla prawdopodobieństwa p,
to nie p * stara_długość, a po prostu p
pojawienie się LPS to zawsze skalowanie i wyprowadzenie bitu
MPS może, ale nie musi powodować skalowania i wyprowadzania bitów
model szacuje tylko prawdopodobieństwo LPS
jest ściśle powiązany z koderem i jest aktualizowany tylko w razie normalizacji
prawdopodobieństwo LPS pamiętane z niewielką precyzją (np. 7 bitów)
precyzja jednocześnie odpowiada za adaptacyjność modelu (szybkość zapominania)
aktualizacja modelu również bez mnożeń,
stablicowane prawdopodobieństwa po normalizacji wywołanej przez LPS/MPS
(2 tablice indeksowane prawdopodobieństwem LPS)
MQ Coder
bardzo szybki
dla większych alfabetów symbol kodowany jest jako ciąg bitów
niewielkie wymagania pamięciowe
szczególnie istotne dla modelu; model dla kontekstu pamięta jedynie:
prawdopodobieństwo LPS (np. 7 bitów)
który bit jest LPS (1 bit)
można stosować złożone modele z wielką liczbą kontekstów
co nadal jest szybkie
mimo uproszczeń, efektywność kodowania bliska 100%
Range Coder – koncepcja
Martin (1979)
Koncepcja opracowana niezależnie od klasycznego kodowania
arytmetycznego
podział pewnego przedziału liczb analogicznie jak w kodowaniu
arytmetycznym, ale
przedział liczb całkowitych
odpowiednio duży przedział, np. [0, 1000000]
po przetworzeniu ciągu wyprowadzamy najbardziej znaczące cyfry pewnej
liczby z wnętrza przedziału
(nie wszystkie, wystarczające do jednoznacznego określenia przedziału)
Range Coder – realizacja
Zakodowanie ciągów dłuższych niż bardzo krótkie, wymagałoby przedziału zbyt
wielkiego by jego krańce reprezentować wprost jako liczby
używamy przedziału reprezentowalnego jako liczba o stałej precyzji w systemie narnym; gdy aktualna długość przedziału spada
Otrzymujemy koder podobny do kodera CACM
inna (szybsza) operacja normalizacji
nadal nie trywialna, mogą wystąpić niedomiary, zliczmy je (analogia do licznikaBitów)
alfabet kodu to cyfra w systemie n-arnym
wyprowadzamy pokrywające się najbardziej znaczące cyfry kresów
odpowiednio skalujemy przedział mnożąc jego kresy przez podstawę systemu liczbowego
wygląda znajomo?
n=256, a więc we/wy bajtowe, prostsze i szybsze niż bitowe w CACM
precyzja podziału przedziału na podprzedziały typowo mniejsza niż np. w CACM
Shindler: http://www.compressconsult.com/rangecoder
(koder, dekoder, prosty model bezpamięciowy, przykłady)
Szybki model dla kodera
arytmetycznego
Potrzebne operacje
dla si wyznacz Pi-1
dla si wyznacz pi
dla Pi-1 wyznacz si (dekodowanie)
powyższe łatwe do zrealizowania, gdy potrafimy szybko wyznaczyć
Ci – łączną liczbę wystąpień symboli sx, x ≤ i
uaktualnij model (po przetworzeniu si)
podziel liczniki w modelu (zwykle przez 2)
Struktura Fenwicka
dla alfabetu n symboli, tablica T [0..n-1]
o indeksie i zawiera sumę liczb wystąpień
symboli od i – 2j + 1 do i
gdzie j to pozycja najmniej znaczącej jedynki w
binarnie zakodowanym i
komórka
np. i = 6 = 110b, j = 1
2j = i & – i
Struktura Fenwicka
i
i binarnie
j
2j
suma liczników w T [i]
0
0000
0
1
0–0
1
0001
0
1
1–1
2
0010
1
2
1–2
3
0011
0
1
3–3
4
0100
2
4
1–4
5
0101
0
1
5–5
6
0110
1
2
5–6
7
0111
0
1
7–7
8
1000
3
8
1–8
9
1001
0
1
9–9
10
1010
1
2
09 – 10
11
1011
0
1
11 – 11
12
1100
2
4
09 – 12
Struktura Fenwicka
Fenwick
złożoność
pamięciowa: n
złożoność czasowa
dla si wyznacz Pi-1
O(log2n)
dla si wyznacz pi
O(1)
dla Pi-1 wyznacz si
O(log2n)
uaktualnij model
O(log2n)
podziel liczniki w modelu O(log2n)
Struktura Moffata
W tablicy sumowanie „w przód”
złożoność
pamięciowa: n (z sortowaniem 3n)
złożoność czasowa
dla si wyznacz Pi-1
O(log2i)
dla si wyznacz pi
O(1)
dla Pi-1 wyznacz si
O(log2i)
uaktualnij model
O(log2i)
podziel liczniki w modelu O(log2n)
(w praktyce struktury podobnie szybkie, Moffata będzie szybsza gdy alfabet będzie
naturalnie posortowany wg rosnących częstości występowania symboli)