Арифметическое кодирование — различия между версиями
(Отмена правки 60036 участника 92.255.113.52 (обсуждение)) |
|||
Строка 1: | Строка 1: | ||
− | + | '''Арифметическое кодирование''' (англ. ''Arithmetic coding'') {{---}} алгоритм сжатия информации без потерь, который при кодировании ставит в соответствие тексту вещественное число из отрезка <tex>[0; 1)</tex>. | |
− | + | Данный метод, как и [[Алгоритм Хаффмана|алгоритм Хаффмана]], является [[Энтропия случайного источника|энтропийным]], т.е. длина кода конкретного символа зависит от частоты встречаемости этого символа в тексте. Арифметическое кодирование показывает более высокие результаты сжатия, чем алгоритм Хаффмана, для данных с неравномерными распределениями вероятностей кодируемых символов. Кроме того, при арифметическом кодировании каждый символ кодируется нецелым числом бит, что эффективнее кода Хаффмана (теоретически, символу <tex>a</tex> с вероятностью появления <tex>p(a)</tex> допустимо ставить в соответствие код длины <tex>-\log_2 p(a)</tex>, следовательно, при кодировании алгоритмом Хаффмана это достигается только с вероятностями, равными обратным степеням двойки). | |
− | + | ||
− | + | == Принцип действия == | |
− | + | === Кодирование === | |
− | + | На вход алгоритму передаются текст для кодирования и список частот встречаемости символов. | |
− | + | # Рассмотрим отрезок <tex>[0; 1)</tex> на координатной прямой. | |
− | + | # Поставим каждому символу текста в соответствие отрезок, длина которого равна частоте его появления. | |
− | + | # Считаем символ из входного потока и рассмотрим отрезок, соответствующий этому символу. Разделим этот отрезок на части, пропорциональные частотам встречаемости символов. | |
− | + | # Повторим пункт (3) до конца входного потока. | |
− | + | # Выберем любое число из получившегося отрезка, которое и будет результатом арифметического кодирования. | |
− | + | ||
− | + | === Псевдокод === | |
− | + | ||
− | + | *<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> {{---}} границы отрезка, содержащего возможный результат арифметического кодирования. | |
− | + | ||
− | + | <code> | |
− | + | '''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 | |
− | + | </code> | |
− | + | ||
− | + | '''Замечание:''' для оптимизации размера кода можно выбрать из полученного на последнем шаге диапазона <tex>[left; right]</tex> число, содержащее наименьшее количество знаков в двоичной записи. | |
− | + | ||
− | + | === Декодирование === | |
− | + | Алгоритм по вещественному числу восстанавливает исходный текст. | |
− | + | # Выберем на отрезке <tex>[0; 1)</tex>, разделенном на части, длины которых равны вероятностям появления символов в тексте, подотрезок, содержащий входное вещественное число. Символ, соответствующий этому подотрезку, дописываем в ответ. | |
− | + | # Нормируем подотрезок и вещественное число. | |
− | + | # Повторим пункты 1{{---}}2 до тех пор, пока не получим ответ. | |
− | + | ||
− | + | === Псевдокод === | |
− | + | ||
− | + | *<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> {{---}} значение символа. | |
− | + | ||
− | + | <code> | |
− | + | '''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 >= 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 | |
− | + | </code> | |
− | + | ||
− | + | '''Замечание:''' кодировщику и декодировщику должно быть известно, когда завершать работу. Для этого можно передавать в качестве аргумента длину текста или символ конца файла, после которого процесс должен быть остановлен. | |
− | + | ||
− | + | '''Замечание:''' Несмотря на преимущества арифметического кодирования, существует проблема при его практическом применении из-за несовершенства представления чисел с плавающей точкой в памяти компьютера {{---}} поскольку некоторые дробные числа не могут быть точно представлены в двоичном коде, используемом современными процессорами (например, <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> можно найти по формуле: | ||
+ | <div style="text-align: center;"><tex> L = \prod\limits_{i=1}^l p_{fi} = \prod\limits_{i=1}^n p_{i}^{f_{i}}</tex></div> | ||
+ | |||
+ | Число бит в закодированном тексте: | ||
+ | <div style="text-align: center;"><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...p_n)</tex></div> | ||
+ | }} | ||
+ | |||
+ | == См. также == | ||
+ | * [[Алгоритм_Хаффмана | Алгоритм Хаффмана]] | ||
+ | * [[Алгоритмы_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 Визуализатор арифметического кодирования] | ||
+ | |||
+ | [[Категория: Дискретная математика и алгоритмы]] | ||
+ | [[Категория: Теория вероятности]] | ||
+ | [[Категория: Алгоритмы сжатия]] |
Версия 21:20, 18 января 2017
Арифметическое кодирование (англ. Arithmetic coding) — алгоритм сжатия информации без потерь, который при кодировании ставит в соответствие тексту вещественное число из отрезка алгоритм Хаффмана, является энтропийным, т.е. длина кода конкретного символа зависит от частоты встречаемости этого символа в тексте. Арифметическое кодирование показывает более высокие результаты сжатия, чем алгоритм Хаффмана, для данных с неравномерными распределениями вероятностей кодируемых символов. Кроме того, при арифметическом кодировании каждый символ кодируется нецелым числом бит, что эффективнее кода Хаффмана (теоретически, символу с вероятностью появления допустимо ставить в соответствие код длины , следовательно, при кодировании алгоритмом Хаффмана это достигается только с вероятностями, равными обратным степеням двойки).
. Данный метод, как иСодержание
Принцип действия
Кодирование
На вход алгоритму передаются текст для кодирования и список частот встречаемости символов.
- Рассмотрим отрезок на координатной прямой.
- Поставим каждому символу текста в соответствие отрезок, длина которого равна частоте его появления.
- Считаем символ из входного потока и рассмотрим отрезок, соответствующий этому символу. Разделим этот отрезок на части, пропорциональные частотам встречаемости символов.
- Повторим пункт (3) до конца входного потока.
- Выберем любое число из получившегося отрезка, которое и будет результатом арифметического кодирования.
Псевдокод
- — текст, подаваемый на вход;
- — длина исходного текста;
- — мощность алфавита исходного текста;
- — массив символов, составляющих алфавит исходного текста;
- — массив вероятностей обнаружения символа в тексте;
- — левая граница подотрезка;
- — правая граница подотрезка;
— структура, задающая подотрезок отрезка , соответствующего конкретному символу на основе частотного анализа. Имеет поля:
- , — границы отрезка, содержащего возможный результат арифметического кодирования.
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
Замечание: для оптимизации размера кода можно выбрать из полученного на последнем шаге диапазона
число, содержащее наименьшее количество знаков в двоичной записи.Декодирование
Алгоритм по вещественному числу восстанавливает исходный текст.
- Выберем на отрезке , разделенном на части, длины которых равны вероятностям появления символов в тексте, подотрезок, содержащий входное вещественное число. Символ, соответствующий этому подотрезку, дописываем в ответ.
- Нормируем подотрезок и вещественное число.
- Повторим пункты 1—2 до тех пор, пока не получим ответ.
Псевдокод
- — вещественное число, подаваемое на вход;
- — длина восстанавливаемого текста;
- — мощность алфавита исходного текста;
- — массив символов, составляющих алфавит исходного текста;
- — массив вероятностей обнаружения символа в тексте;
- — левая граница подотрезка;
- — правая граница подотрезка;
- — значение символа.
— структура, задающая подотрезок отрезка , соответствующего конкретному символу на основе частотного анализа. Имеет поля:
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 >= 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
Замечание: кодировщику и декодировщику должно быть известно, когда завершать работу. Для этого можно передавать в качестве аргумента длину текста или символ конца файла, после которого процесс должен быть остановлен.
Замечание: Несмотря на преимущества арифметического кодирования, существует проблема при его практическом применении из-за несовершенства представления чисел с плавающей точкой в памяти компьютера — поскольку некоторые дробные числа не могут быть точно представлены в двоичном коде, используемом современными процессорами (например,
), границы символов будут округлены, что может повлечь за собой неверную работу алгоритма при больших объёмах данных. В общем случае, алгоритм можно модифицировать так, чтобы результатом было дробное число. В такой реализации вероятность встречи символа представляется в виде рационального числа. Поскольку в каждой итерации будет переход из текущего отрезка в один из его подотрезков, кратных по длине , а всего итераций , в конечном результате знаменатель дроби не превысит , а поскольку сумма всех вероятностей встречи символов равна , полученная дробь будет находиться в промежутке .Пример работы
Рассмотрим в качестве примера строку
:Кодирование
Символ | Частота появления |
---|---|
| |
| |
|
Считанный символ | Левая граница отрезка | Правая граница отрезка |
---|---|---|
| ||
| ||
| ||
| ||
| ||
| ||
| ||
|
Код:
Декодирование
Код:
Декодируемый символ | Код |
---|---|
| |
| |
| |
| |
| |
| |
|
Замечание: при декодировании текста можно не только нормализовывать рабочий отрезок и текущий код, но и уменьшать рабочий отрезок (аналогично кодированию), не изменяя значение кода.
Декодирование (второй способ)
Код:
Декодируемый символ | Границы отрезка | |||
---|---|---|---|---|
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
|
Оценка длины кодового слова
Теорема: |
При арифметическом кодировании длина кодового слова не превышает энтропии исходного текста. |
Доказательство: |
Введём следующие обозначения:
Размер сообщения можно найти по формуле:Число бит в закодированном тексте: |