Изменения

Перейти к: навигация, поиск

Участник:Mikhirurg/Арифметическое кодирование - дополнение

20 853 байта добавлено, 19:49, 16 декабря 2019
Новая страница: «{{временная статья|Арифметическое кодирование}}{{Определение |definition= '''Арифметическое ко…»
{{временная статья|Арифметическое кодирование}}{{Определение
|definition=
'''Арифметическое кодирование''' (англ. ''Arithmetic coding'') {{---}} алгоритм сжатия информации без потерь, который при кодировании ставит в соответствие тексту вещественное число из отрезка <tex>[0; 1)</tex>.
Данный метод, как и [[Алгоритм Хаффмана|алгоритм Хаффмана]], является [[Энтропия случайного источника|энтропийным]], то есть длина кода конкретного символа зависит от частоты встречаемости этого символа в тексте. Арифметическое кодирование показывает более высокие результаты сжатия, чем алгоритм Хаффмана, для данных с неравномерными распределениями вероятностей кодируемых символов.
}}

== Принцип действия ==
При арифметическом кодировании каждый символ кодируется нецелым числом бит, что эффективнее кода Хаффмана (теоретически, символу <tex>a</tex> с вероятностью появления <tex>p(a)</tex> допустимо ставить в соответствие код длины <tex>-\log_2 p(a)</tex>, следовательно, при кодировании алгоритмом Хаффмана это достигается только с вероятностями, равными обратным степеням двойки).
=== Кодирование ===
На вход алгоритму передаются текст для кодирования и список частот встречаемости символов.
# Рассмотрим отрезок <tex>[0; 1)</tex> на координатной прямой.
# Поставим каждому символу текста в соответствие отрезок, длина которого равна частоте его появления.
# Считаем символ из входного потока и рассмотрим отрезок, соответствующий этому символу. Разделим этот отрезок на части, пропорциональные частотам встречаемости символов.
# Повторим пункт <tex>(3)</tex> до конца входного потока.
# Выберем любое число из получившегося отрезка, которое и будет результатом арифметического кодирования.

=== Псевдокод ===

*<math>\mathtt{s}\,</math> {{---}} текст, подаваемый на вход;
*<math>\mathtt{n}\,</math> {{---}} длина исходного текста;
*<math>\mathtt{m}\,</math> {{---}} мощность алфавита исходного текста;
*<math>\mathtt{letters[m]}\,</math> {{---}} массив символов, составляющих алфавит исходного текста;
*<math>\mathtt{probability[m]}\,</math> {{---}} массив вероятностей обнаружения символа в тексте;
*<math>\mathtt{Segment}\,</math> {{---}} структура, задающая подотрезок отрезка <tex>[0; 1)</tex>, соответствующего конкретному символу на основе частотного анализа. Имеет поля:
**<math>\mathtt{left}\,</math> {{---}} левая граница подотрезка;
**<math>\mathtt{right}\,</math> {{---}} правая граница подотрезка;
*<math>\mathtt{left}\,</math>, <math>\mathtt{right}\,</math> {{---}} границы отрезка, содержащего возможный результат арифметического кодирования.

'''struct''' Segment:
'''double''' left
'''double''' right

'''Segment'''[m] defineSegments(letters: '''char'''[m], probability: '''double'''[m]):
'''Segment'''[m] segment
'''double''' l = 0
'''for''' i = 0 '''to''' m - 1
segment[letters[i]].left = l
segment[letters[i]].right = l + probability[i]
l = segment[letters[i]].right
'''return''' segment

'''double''' arithmeticCoding(letters: '''char'''[m], probability: '''double'''[m], s: '''char'''[n]):
'''Segment'''[m] segment = defineSegments(letters, probability)
'''double''' left = 0
'''double''' right = 1
'''for''' i = 0 '''to''' n - 1
'''char''' symb = s[i]
'''double''' newRight = left + (right - left) * segment[symb].right
'''double''' newLeft = left + (right - left) * segment[symb].left
left = newLeft
right = newRight
'''return''' (left + right) / 2

'''Замечание:''' для оптимизации размера кода можно выбрать из полученного на последнем шаге диапазона <tex>[left; right]</tex> число, содержащее наименьшее количество знаков в двоичной записи.

=== Декодирование ===
Алгоритм по вещественному числу восстанавливает исходный текст.
# Выберем на отрезке <tex>[0; 1)</tex>, разделенном на части, длины которых равны вероятностям появления символов в тексте, подотрезок, содержащий входное вещественное число. Символ, соответствующий этому подотрезку, дописываем в ответ.
# Нормируем подотрезок и вещественное число.
# Повторим пункты <tex>1</tex>{{---}}<tex>2</tex> до тех пор, пока не получим ответ.

=== Псевдокод ===

*<math>\mathtt{code}\,</math> {{---}} вещественное число, подаваемое на вход;
*<math>\mathtt{n}\,</math> {{---}} длина восстанавливаемого текста;
*<math>\mathtt{m}\,</math> {{---}} мощность алфавита исходного текста;
*<math>\mathtt{letters[m]}\,</math> {{---}} массив символов, составляющих алфавит исходного текста;
*<math>\mathtt{probability[m]}\,</math> {{---}} массив вероятностей обнаружения символа в тексте;
*<math>\mathtt{segment}\,</math> {{---}} структура, задающая подотрезок отрезка <tex>[0; 1)</tex>, соответствующего конкретному символу на основе частотного анализа. Имеет поля:
** <math>\mathtt{left}\,</math> {{---}} левая граница подотрезка;
** <math>\mathtt{right}\,</math> {{---}} правая граница подотрезка;
** <math>\mathtt{character}\,</math> {{---}} значение символа.

'''struct''' Segment:
'''double''' left
'''double''' right
'''char''' character

'''Segment'''[m] defineSegments(letters: '''char'''[n], probability: '''double'''[n]):
'''Segment'''[m] segment
'''double''' l = 0
'''for''' i = 0 '''to''' m - 1
segment[i].left = l
segment[i].right = l + probability[i]
segment[i].character = letters[i]
l = segment[i].right
'''return''' segment

'''string''' arithmeticDecoding(letters: '''char'''[m], probability: '''double'''[m], code: '''double''', n: '''int'''):
'''Segment'''[m] segment = defineSegments(letters, probability)
'''string''' s = ""
'''for''' i = 0 '''to''' n - 1
'''for''' j = 0 '''to''' m - 1
'''if''' code<tex>\small{~\geqslant~}</tex>segment[j].left '''and''' code < segment[j].right
s += segment[j].character
code = (code – segment[j].left) / (segment[j].right – segment[j].left)
'''break'''
'''return''' s

'''Замечание:''' кодировщику и декодировщику должно быть известно, когда завершать работу. Для этого можно передавать в качестве аргумента длину текста или символ конца файла, после которого процесс должен быть остановлен.

'''Замечание:''' Несмотря на преимущества арифметического кодирования, существует проблема при его практическом применении из-за несовершенства представления чисел с плавающей точкой в памяти компьютера {{---}} поскольку некоторые дробные числа не могут быть точно представлены в двоичном коде, используемом современными процессорами (например, <tex>\dfrac{1}{3}</tex>), границы символов будут округлены, что может повлечь за собой неверную работу алгоритма при больших объёмах данных. В общем случае, алгоритм можно модифицировать так, чтобы результатом было дробное число. В такой реализации вероятность встречи символа представляется в виде рационального числа. Поскольку в каждой итерации будет переход из текущего отрезка в один из его <tex>m</tex> подотрезков, кратных по длине <tex>n</tex>, а всего итераций <tex>n</tex>, в конечном результате знаменатель дроби не превысит <tex>n^{n}</tex>, а поскольку сумма всех вероятностей встречи символов равна <tex>1</tex>, полученная дробь будет находиться в промежутке <tex>[0; 1)</tex>.

== Пример работы ==
Рассмотрим в качестве примера строку <tex>abacaba</tex>:
=== Кодирование ===
{|class="wikitable"
!Символ||Частота появления
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.571429</tex></p>
|-
|<p style="text-align:center;"><tex>b</tex></p>||<p style="text-align:center;"><tex>0.285714</tex></p>
|-
|<p style="text-align:center;"><tex>c</tex></p>||<p style="text-align:center;"><tex>0.142857</tex></p>
|}
[[Файл:Code_png.png|thumb|right|200px|Пример работы кодировщика ]]
{|class="wikitable"
!Считанный символ||Левая граница отрезка||Правая граница отрезка
|-
|||<p style="text-align:center;"><tex>0</tex></p>||<p style="text-align:center;"><tex>1</tex></p>
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0</tex></p>||<p style="text-align:center;"><tex>0.571429</tex></p>
|-
|<p style="text-align:center;"><tex>b</tex></p>||<p style="text-align:center;"><tex>0.326531</tex></p>||<p style="text-align:center;"><tex>0.489796</tex></p>
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.326531</tex></p>||<p style="text-align:center;"><tex>0.419825</tex></p>
|-
|<p style="text-align:center;"><tex>c</tex></p>||<p style="text-align:center;"><tex>0.406497</tex></p>||<p style="text-align:center;"><tex>0.419825</tex></p>
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.406497</tex></p>||<p style="text-align:center;"><tex>0.414113</tex></p>
|-
|<p style="text-align:center;"><tex>b</tex></p>||<p style="text-align:center;"><tex>0.410849</tex></p>||<p style="text-align:center;"><tex>0.413025</tex></p>
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.410849</tex></p>||<p style="text-align:center;"><tex>0.412093</tex></p>
|}
Код: <tex>0.411471</tex>

=== Декодирование ===
Код: <tex>0.411471</tex>
[[Файл:decode1_png.png|thumb|right|200px|Пример работы декодировщика ]]
{|class="wikitable"
!Декодируемый символ||Код
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.411471</tex></p>
|-
|<p style="text-align:center;"><tex>b</tex></p>||<p style="text-align:center;"><tex>0.720074</tex></p>
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.520259</tex></p>
|-
|<p style="text-align:center;"><tex>c</tex></p>||<p style="text-align:center;"><tex>0.910454</tex></p>
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.373178</tex></p>
|-
|<p style="text-align:center;"><tex>b</tex></p>||<p style="text-align:center;"><tex>0.653061</tex></p>
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.285714</tex></p>
|}

'''Замечание:''' при декодировании текста можно не только нормализовывать рабочий отрезок и текущий код, но и уменьшать рабочий отрезок (аналогично кодированию), не изменяя значение кода.
=== Декодирование (второй способ)===
Код: <tex>0.411471</tex>
[[Файл:decode2_png.png|thumb|right|200px|Пример работы декодировщика (второй способ) ]]
{|class="wikitable"
!Декодируемый символ||colspan="4" |Границы отрезка
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0</tex></p>||<p style="text-align:center;"><tex>0.571429</tex></p>||<p style="text-align:center;"><tex>0.857143</tex></p>||<p style="text-align:center;"><tex>1</tex></p>
|-
|<p style="text-align:center;"><tex>b</tex></p>||<p style="text-align:center;"><tex>0</tex></p>||<p style="text-align:center;"><tex>0.326531</tex></p>||<p style="text-align:center;"><tex>0.489796 </tex></p>||<p style="text-align:center;"><tex>0.571429</tex></p>
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.326531 </tex></p>||<p style="text-align:center;"><tex>0.419825 </tex></p>||<p style="text-align:center;"><tex>0.466472 </tex></p>||<p style="text-align:center;"><tex>0.489796 </tex></p>
|-
|<p style="text-align:center;"><tex>c</tex></p>||<p style="text-align:center;"><tex>0.326531</tex></p>||<p style="text-align:center;"><tex>0.379842</tex></p>||<p style="text-align:center;"><tex>0.406497</tex></p>||<p style="text-align:center;"><tex>0.419825</tex></p>
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.406497</tex></p>||<p style="text-align:center;"><tex>0.414113</tex></p>||<p style="text-align:center;"><tex>0.417921 </tex></p>||<p style="text-align:center;"><tex>0.419825</tex></p>
|-
|<p style="text-align:center;"><tex>b</tex></p>||<p style="text-align:center;"><tex>0.406497</tex></p>||<p style="text-align:center;"><tex>0.410849</tex></p>||<p style="text-align:center;"><tex>0.413025</tex></p>||<p style="text-align:center;"><tex>0.414113</tex></p>
|-
|<p style="text-align:center;"><tex>a</tex></p>||<p style="text-align:center;"><tex>0.410849</tex></p>||<p style="text-align:center;"><tex>0.412093</tex></p>||<p style="text-align:center;"><tex>0.412714</tex></p>||<p style="text-align:center;"><tex>0.413025</tex></p>

|}
== Оценка длины кодового слова ==
{{Теорема
|statement=При арифметическом кодировании длина кодового слова не превышает энтропии исходного текста.


||proof=Введём следующие обозначения:
*<tex>l</tex> {{---}} длина текста,
*<tex>n</tex> {{---}} размер алфавита,
*<tex>f_i</tex> {{---}} частота встречаемости символа,
*<tex>p_i</tex> {{---}} вероятность вхождения символа.

Размер сообщения <tex>L</tex> можно найти по формуле:
<tex> L = \prod\limits_{i=1}^l p_{fi} = \prod\limits_{i=1}^n p_{i}^{f_{i}}</tex>

Число бит в закодированном тексте:
<tex>\log_2 L = -\sum\limits_{i=1}^n f_i\cdot \log_2 p_i = -l \cdot \sum\limits_{i=1}^n p_i\cdot \log_2 p_i = -l \cdot H(p_1 \ldots p_n)</tex>
}}

== Адаптивное арифметическое кодирование ==

Необходимость применения адаптивного алгоритма возникает в том случае, если вероятностные оценки символов сообщения не известны до начала работы алгоритма. Преимущество такого подхода кодирования заключается в том, что декодировщику не нужно передавать вероятностные оценки для символов, он будет строить их по мере декодирования сообщения, что может сильно сократить вес такого сообщения.

=== Алгоритм кодирования ===

На вход алгоритму передаётся последовательность символов и алфавит. Каждому символу алфавита </tex>\alpha \in \sum</tex> сопоставляется его вес <tex>w_\alpha</tex>. В начале работы алгоритма все веса символов равны 1. Вероятность каждого символа <tex>\alpha</tex> - <tex>p(\alpha)</tex> устанавливется равной его весу, делённому на суммарный вес всех символов: <tex>p(\alpha) = \frac{w_\alpha}{\sum_{i=1}^n w_i}</tex>. После получения очередного символа и построения нужного интервала, вес символа увеличивается на 1. Когда все символы последовательности будут обработаны, необходимо либо записать маркер конца последовательности, либо запомнить её длину, чтобы позже передать декодировщику. После получения нужных границ <tex>[l, r]</tex>, в котором будет лежать код, необходмо выбрать число <tex>x</tex>, описывающее кодирование:
<tex>x \in [l, r]</tex>. Выбор числа <tex>x</tex> производится также, как и в неадаптивном алгоритме. Выбирается число вида <tex>\frac{x}{2^p}: x,p \in \mathbb N</tex>.

== См. также ==
* [[Алгоритм_Хаффмана | Алгоритм Хаффмана]]
* [[Алгоритмы_LZ77_и_LZ78 | Алгоритмы LZ77 и LZ78]]
* [[Энтропия_случайного_источника | Энтропия случайного источника]]

== Источники информации ==
* [http://ru.wikipedia.org/wiki/%D0%90%D1%80%D0%B8%D1%84%D0%BC%D0%B5%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BA%D0%BE%D0%B4%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5 Википедия {{---}} Арифметическое кодирование]
* [https://en.wikipedia.org/wiki/Arithmetic_coding Wikipedia {{---}} Arithmetic coding]
* [http://www.sernam.ru/cod_3.php Арифметическое кодирование]
* [http://rain.ifmo.ru/cat/view.php/vis/data-compression/arithmetic-coding-2006 Визуализатор арифметического кодирования]

[[Категория: Дискретная математика и алгоритмы]]
[[Категория: Теория вероятности]]
[[Категория: Алгоритмы сжатия]]
55
правок

Навигация