Арифметическое кодирование — различия между версиями
Migan (обсуждение | вклад) (Pseudocode1 bugfix) |
Migan (обсуждение | вклад) (Реализация для дробей) |
||
Строка 1: | Строка 1: | ||
'''Арифметическое кодирование''' (англ. ''Arithmetic coding'') {{---}} алгоритм сжатия информации без потерь, который при кодировании ставит в соответствие тексту вещественное число из отрезка <tex>[0; 1)</tex>. | '''Арифметическое кодирование''' (англ. ''Arithmetic coding'') {{---}} алгоритм сжатия информации без потерь, который при кодировании ставит в соответствие тексту вещественное число из отрезка <tex>[0; 1)</tex>. | ||
− | Данный метод, как и [[Алгоритм Хаффмана|алгоритм Хаффмана]], является [[Энтропия случайного источника|энтропийным]], т.е. длина кода конкретного символа зависит от частоты встречаемости этого символа в тексте. Арифметическое кодирование показывает более высокие результаты сжатия, чем алгоритм Хаффмана, для данных с неравномерными распределениями вероятностей кодируемых символов. Кроме того, при арифметическом кодировании каждый символ кодируется нецелым числом бит, что эффективнее кода Хаффмана (теоретически, символу <tex>a</tex> с вероятностью появления <tex>p(a)</tex> допустимо ставить в соответствие код длины <tex>-\log_2 p(a)</tex>, следовательно, при кодировании алгоритмом Хаффмана это достигается только с вероятностями, равными обратным степеням двойки) | + | Данный метод, как и [[Алгоритм Хаффмана|алгоритм Хаффмана]], является [[Энтропия случайного источника|энтропийным]], т.е. длина кода конкретного символа зависит от частоты встречаемости этого символа в тексте. Арифметическое кодирование показывает более высокие результаты сжатия, чем алгоритм Хаффмана, для данных с неравномерными распределениями вероятностей кодируемых символов. Кроме того, при арифметическом кодировании каждый символ кодируется нецелым числом бит, что эффективнее кода Хаффмана (теоретически, символу <tex>a</tex> с вероятностью появления <tex>p(a)</tex> допустимо ставить в соответствие код длины <tex>-\log_2 p(a)</tex>, следовательно, при кодировании алгоритмом Хаффмана это достигается только с вероятностями, равными обратным степеням двойки). |
== Принцип действия == | == Принцип действия == | ||
Строка 59: | Строка 59: | ||
*<math>\mathtt{code}\,</math> {{---}} вещественное число, подаваемое на вход; | *<math>\mathtt{code}\,</math> {{---}} вещественное число, подаваемое на вход; | ||
− | *<math>\mathtt{ | + | *<math>\mathtt{n}\,</math> {{---}} длина восстанавливаемого текста; |
*<math>\mathtt{m}\,</math> {{---}} мощность алфавита исходного текста; | *<math>\mathtt{m}\,</math> {{---}} мощность алфавита исходного текста; | ||
*<math>\mathtt{letters[m]}\,</math> {{---}} массив символов, составляющих алфавит исходного текста; | *<math>\mathtt{letters[m]}\,</math> {{---}} массив символов, составляющих алфавит исходного текста; | ||
Строка 82: | Строка 82: | ||
segment[i].character = letters[i] | segment[i].character = letters[i] | ||
− | '''string''' arithmeticDecoding(code: '''double''', | + | '''string''' arithmeticDecoding(code: '''double''', n: '''int'''): |
defineSegments(letters, probability) | defineSegments(letters, probability) | ||
'''string''' s = "" | '''string''' s = "" | ||
− | '''for''' i = 0 '''to''' | + | '''for''' i = 0 '''to''' n - 1 |
'''for''' j = 0 '''to''' m - 1 | '''for''' j = 0 '''to''' m - 1 | ||
'''if''' code >= segment[j].left '''and''' code < segment[j].right ''<font color=green>// segment {{---}} массив, заполненный в результате выполнения метода defineSegments</font>'' | '''if''' code >= segment[j].left '''and''' code < segment[j].right ''<font color=green>// segment {{---}} массив, заполненный в результате выполнения метода defineSegments</font>'' | ||
Строка 96: | Строка 96: | ||
'''Замечание:''' кодировщику и декодировщику должно быть известно, когда завершать работу. Для этого можно передавать в качестве аргумента длину текста или символ конца файла, после которого процесс должен быть остановлен. | '''Замечание:''' кодировщику и декодировщику должно быть известно, когда завершать работу. Для этого можно передавать в качестве аргумента длину текста или символ конца файла, после которого процесс должен быть остановлен. | ||
+ | Несмотря на преимущества арифметического кодирования, существует проблема при его практическом применении из-за несовершенства представления чисел с плавающей точкой в памяти компьютера {{---}} поскольку некоторые дробные числа не могут быть точно представлены в двоичном коде, используемом современными процессорами (например, <tex>\dfrac{1}{3}</tex>), границы символов будут округлены, что может повлечь за собой неверную работу алгоритма при больших объёмах данных. В общем случае, алгоритм можно модифицировать так, чтобы результатом было дробное число. В такой реализации вероятность встречи символа представляется в виде дроби, числитель которой представляет количество использований символа в тексте, знаменатель {{---}} длину <tex>n</tex> исходного текста. Поскольку в каждой итерации будет переход из текущего отрезка в один из его <tex>m</tex> подотрезков, кратных по длине <tex>n</tex>, а всего итераций <tex>n</tex>, в конечном результате в качестве знаменателя дроби будет <tex>n^{n}</tex>, а числитель можно найти, рассматривая отрезок <tex>[0; n^{n})</tex> на первой итерации. | ||
== Пример работы == | == Пример работы == | ||
Рассмотрим в качестве примера строку <tex>abacaba</tex>: | Рассмотрим в качестве примера строку <tex>abacaba</tex>: |
Версия 21:59, 17 июня 2016
Арифметическое кодирование (англ. Arithmetic coding) — алгоритм сжатия информации без потерь, который при кодировании ставит в соответствие тексту вещественное число из отрезка алгоритм Хаффмана, является энтропийным, т.е. длина кода конкретного символа зависит от частоты встречаемости этого символа в тексте. Арифметическое кодирование показывает более высокие результаты сжатия, чем алгоритм Хаффмана, для данных с неравномерными распределениями вероятностей кодируемых символов. Кроме того, при арифметическом кодировании каждый символ кодируется нецелым числом бит, что эффективнее кода Хаффмана (теоретически, символу с вероятностью появления допустимо ставить в соответствие код длины , следовательно, при кодировании алгоритмом Хаффмана это достигается только с вероятностями, равными обратным степеням двойки).
. Данный метод, как иСодержание
Принцип действия
Кодирование
На вход алгоритму передаются текст для кодирования и список частот встречаемости символов.
- Рассмотрим отрезок на координатной прямой.
- Поставим каждому символу текста в соответствие отрезок, длина которого равна частоте его появления.
- Считаем символ из входного потока и рассмотрим отрезок, соответствующий этому символу. Разделим этот отрезок на части, пропорциональные частотам встречаемости символов.
- Повторим пункт (3) до конца входного потока.
- Выберем любое число из получившегося отрезка, которое и будет результатом арифметического кодирования.
Псевдокод
- — текст, подаваемый на вход;
- — длина исходного текста;
- — мощность алфавита исходного текста;
- — массив символов, составляющих алфавит исходного текста;
- — массив вероятностей обнаружения символа в тексте;
- — левая граница подотрезка;
- — правая граница подотрезка;
— структура, задающая подотрезок отрезка , соответствующего конкретному символу на основе частотного анализа. Имеет поля:
- , — границы отрезка, содержащего возможный результат арифметического кодирования.
struct Segment: double left double right
void 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]
double arithmeticCoding(s: char[n]): 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 // segment — массив, заполненный в результате выполнения метода defineSegments 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
void defineSegments(letters: char[n], probability: double[n]): Segment segment[m] double l = 0 for i = 0 to m - 1 segment[i].left = l segment[i].right = l + probability[i] segment[i].character = letters[i]
string arithmeticDecoding(code: double, n: int): 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 // segment — массив, заполненный в результате выполнения метода defineSegments s += segment[j].character code = (code – segment[j].left) / (segment[j].right – segment[j].left) break return s
Замечание: кодировщику и декодировщику должно быть известно, когда завершать работу. Для этого можно передавать в качестве аргумента длину текста или символ конца файла, после которого процесс должен быть остановлен.
Несмотря на преимущества арифметического кодирования, существует проблема при его практическом применении из-за несовершенства представления чисел с плавающей точкой в памяти компьютера — поскольку некоторые дробные числа не могут быть точно представлены в двоичном коде, используемом современными процессорами (например,
), границы символов будут округлены, что может повлечь за собой неверную работу алгоритма при больших объёмах данных. В общем случае, алгоритм можно модифицировать так, чтобы результатом было дробное число. В такой реализации вероятность встречи символа представляется в виде дроби, числитель которой представляет количество использований символа в тексте, знаменатель — длину исходного текста. Поскольку в каждой итерации будет переход из текущего отрезка в один из его подотрезков, кратных по длине , а всего итераций , в конечном результате в качестве знаменателя дроби будет , а числитель можно найти, рассматривая отрезок на первой итерации.Пример работы
Рассмотрим в качестве примера строку
:Кодирование
Символ | Частота появления |
---|---|
| |
| |
|
Считанный символ | Левая граница отрезка | Правая граница отрезка |
---|---|---|
| ||
| ||
| ||
| ||
| ||
| ||
| ||
|
Код:
Декодирование
Код:
Декодируемый символ | Код |
---|---|
| |
| |
| |
| |
| |
| |
|
Замечание: при декодировании текста можно не только нормализовывать рабочий отрезок и текущий код, но и уменьшать рабочий отрезок (аналогично кодированию), не изменяя значение кода.
Декодирование (второй способ)
Код:
Декодируемый символ | Границы отрезка | |||
---|---|---|---|---|
| ||||
| ||||
| ||||
| ||||
| ||||
| ||||
|
Оценка длины кодового слова
Теорема: |
При арифметическом кодировании длина кодового слова не превышает энтропии исходного текста. |
Доказательство: |
Введём следующие обозначения:
Размер сообщения можно найти по формуле:Число бит в закодированном тексте: |