Основные тенденции статистического метода арифметического кодирования текстовой информации и последовательностей дискретных данных

УДК 681.518

ОСНОВНЫЕ ТЕНДЕНЦИИ СТАТИСТИЧЕСКОГО МЕТОДА АРИФМЕТИЧЕСКОГО КОДИРОВАНИЯ ТЕКСТОВОЙ ИНФОРМАЦИИ И ПОСЛЕДОВАТЕЛЬНОСТЕЙ ДИСКРЕТНЫХ ДАННЫХ

ЖУРАВКА А. В, ШЕВЧЕНКО Л. П.___________

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

1. Введение

Актуальность. Сжатие — это форма кодирования. Другими целями кодирования является поиск и исправление ошибок, а также шифрование. Удаляя из текста избыточность, сжатие способствует шифрованию, что затрудняет поиск шифра доступным для взломщика статистическим методом. Таким образом, в настоящее время, когда уделяется все больше внимания защите авторских прав разработчиков и владельцев программных продуктов, сжатие информации, как метод и средство защиты программного обеспечения является актуальным.

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

Решаемые задачи. Допустим, необходимо закодировать методом арифметического сжатия такую последовательность дискретных данных: ‘sail!’ алфавита (a,

Таблица 1

Символ Вероятность Интервал

a 0,2 [0,0;0,2)

s 0,3 [0,2;0,5)

l 0,1 [0,5;0,6)

o 0,2 [0,6;0,8)

У 0,1 [0,8;0,9)

! 0,1 [0,9;1,0)

s, l, o, y, !) модель с постоянными вероятностями, заданными в табл. 1.

Описание алгоритма и его реализация. Кодировщику и декодировщику известно, что в самом начале интервал соответствует [0; 1). После просмотра первого символа ‘s’ кодировщик сужает интервал до [0,2; 0,5), который модель выделяет этому символу. Второй символ ‘а’ сузит этот новый интервал до первой его пятой части, поскольку для ‘а’ выделен фиксированный интервал [0,0; 0,2). В результате получим рабочий интервал [0,2;

0,26), так как предыдущий интервал имел ширину в 0,3 единицы

— одна пятая от него 0,06. Следующему символу ‘l’ соответствует фиксированный интервал [0,5; 0,6), что применительно к рабочему интервалу [0,2; 0,26) суживает его до интервала [0,23; 0,236). Следуя алгоритму, получим табл. 2.

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

Для фиксированной модели, задаваемой алфавитом, приведенным в табл. 1, энтропия исследуемого текста вычисляется по известной формуле:

- lg 0.3 - lg 0.2 - lg 0.1 - lg 0.1 - ig 0.1= - ig 0.00006 ~ 4.22.

Программа для арифметического кодирования. Показан фрагмент псевдокода, объединяющего процедуры кодирования и декодирования, излагаемые в предыдущем разделе. Частотный интервал для i-го символа задается от cum_freq[i] до cum_freq[i-1]. При убывании i cum_freq[i] возрастает так, что cum_fTeq[0] = 1. (Причина такого “обратного” соглашения состоит в том, что cum_freq[0] будет потом содержать нормализующий множитель, который удобно хранить в начале массива). Текущий рабочий интервал задается в [low; high] и будет в самом начале равен [0; 1) и для кодировщика, и для раскодировщика.

К сожалению, этот псевдокод очень упрощен, когда как на практике существует несколько факторов, осложняющих и кодирование, и декодирование.

Программа 1. Псевдокод арифметического кодирования и декодирования.

/* АЛГОРИТМ АРИФМЕТИЧЕСКОГО

КОДИРОВАНИЯ */

/* С каждым символом текста обращаться к процедуре encode_symbol() */

/* Проверить, что “завершающий” символ закодирован последним */

Таблица 2

Символы Интервал

"s" [0,2;0,5)

"a" [0,2;0,26)

"l" [0,23;0,236)

"l" [0,233;0,2336)

"!" [0,23354;0,2336)

РИ, 2000, № 1

133

/* Вывести полученное значение интервала [low; high) */

encode_symbol(symbol,cum_freq) range = high - low

high = low + range*cum_freq[symbol-1] low = low + range*cum_freq[symbol]

/* АЛГОРИТМ АРИФМЕТИЧЕСКОГО

ДЕКОДИРОВАНИЯ */

/* Value - это поступившее на вход число */

/* Обращение к процедуре decode_symbol() пока она не возвратит */

/* “завершающий” символ */

decode_symbol(cum_freq) поиск такого символа, что

cum_freq[symbol] <= (value - low)/(high - low) < cum_freq[symbol-1]

/* Это обеспечивает размещение value внутри нового интервала */

/* [low; high), что отражено в оставшейся части программы */

range = high - low

high = low + range*cum_freq[symbol-1] low = low + range*cum_freq[symbol] return symbol

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

Желательно использование целочисленной арифметики. Требуемая для представления интервала [low; high) точность возрастает вместе с длиной текста. Постепенное выполнение помогает преодолеть данную проблему, требуя при этом внимательного учета возможностей переполнения и отрицательного переполнения.

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

Программа 1 содержит рабочий код процедур арифметического кодирования и декодирования. Реализация двух различных моделей дана в Программе 2, при этом Программа 1 может использовать любую из них.

134

В оставшейся части раздела более подробно рассматривается Программа 1 и приводится доказательство правильности раскодирования в целочисленном исполнении, а также делается обзор ограничений на длину слов в программе.

Вероятности представляются в модели как целочисленные счетчики частот, а накапливаемые частоты хранятся в массиве cum_freq[]. Как и в предыдущем случае, этот массив - “обратный”, и счетчик общей частоты, применяемый для нормализации всех частот, размещается в cum_freq[0]. Накапливаемые частоты не должны превышать установленный в Max_frequency максимум, а реализация модели должна предотвращать переполнение соответствующим масштабированием. Необходимо также хотя бы на 1 обеспечить различие между двумя соседними значениями cum_freq[], иначе рассматриваемый символ не сможет быть передан.

Приращаемая передача и получение. В отличие от псеводокода, программа 1 представляет low и high целыми числами. Для них и для других полезных констант определен специальный тип данных code_value. Это - Top_value, определяющий максимально возможный code_value, First_qtr и Third_qtr, представляющие части интервала (строки 6-16). В псевдокоде текущий интервал представлен через [low; high), а в программе 1 это [low; high] - интервал, включающий в себя значение high. На самом деле более правильно, хотя и более непонятно, утверждать, что в программе 1 представляемый интервал есть [low; high + 0.1111...) по той причине, что при масштабировании границ для увеличения точности нули смещаются к младшим битам low, а единицы смещаются в high. Хотя можно писать программу на основе разных договоренностей, данная имеет некоторые преимущества в упрощении кода программы.

По мере сужения кодового интервала старшие биты low и high становятся одинаковыми, поэтому могут быть переданы немедленно, так как на них будущие сужения интервала все равно уже не будут влиять. Поскольку мы знаем, что low<=high, это воплотится в следующую Программу 2:

for (;;) {

if (high < Half) { output_bit(0); low = 2 * low; high = 2 * high + 1;

}

else if (low >= Half) {

output_bit(1);

low = 2 * (low - Half);

high = 2 * (high - Half) + 1;

РИ, 2000, № 1

}

else break;

}

гарантирующую, что после ее завершения будет спреведливо неравенство: low<Half<=high. Это можно найти в процедуре encode_symbol(). Кроме того, есть несколько дополнительных сложностей, связанных с возможностями потери значимости (см. следующий подраздел). Как отмечено выше, нужно внимание при сдвиге единиц к началу high.

Приращаемый ввод исходных данных выполняется с помощью числа, названного value. В Программе 1 обработанные биты перемещаются в верхнюю часть, а заново получаемые поступают в нижнюю. Вначале start_decoding() заполняет value полученными битами. После определения следующего входного символа процедурой decode_symbol() происходит вынос ненужных, одинаковых в low и в high, битов старшего порядка сдвигом value на это количество разрядов (выведенные биты восполняются введением новых с нижнего конца):

for (;;) {

if (high < Half) {

value = 2 * value + шр^_Ьй();

low = 2 * low;

high = 2 * high + 1;

}

else if (low > Half) {

value = 2 * (value - Half) + шр^^кО;

low = 2 * (low - Half);

high = 2 * (high - Half) + 1;

}

else break;

}

Доказательство правильности декодирования

Проверим верность определения процедурой decode_symbol() следующего символа. Из псевдокода в программе 1 видно, что decode_symbol() должна использовать value для поиска символа, сократившего при кодировании рабочий интервал так, что он продолжает включать в себя value. Строки в decode_symbol() определяют символ, таким образом, что value лежит внутри нового интервала, вычисляемого процедурой decode_symbol() в строках. Это определенно гарантирует корректность определения каждого символа операцией декодирования.

Отрицательное переполнение. Как показано в псевдокоде, арифметическое кодирование работает при помощи масштабирования накопленных вероятностей, поставляемых моделью в интервале [low; high] для каждого передаваемого символа. Предположим, что low и high настолько близки друг к другу, что

операция масштабирования приводит полученные от модели разные символы к одному целому числу, входящему в [low; high]. В этом случае дальнейшее кодирование продолжать невозможно. Следовательно, кодировщик должен следить за тем, чтобы интервал [low; high] всегда был достаточно широк. Простейшим способом для этого является обеспечение ширины интервала не меньшей Max_frequency— максимального значения суммы всех накапливаемых частот.

Как можно сделать это условие менее строгим? Объясненная выше операция битового сдвига гарантирует, что low и high могут только тогда становиться опасно близкими, когда заключают между собой Half. Предположим, они становятся настолько близки, что

First_qtr <= low < Half <= high < Third_qtr.

Тогда следующие два бита вывода будут иметь взаимообратные значения: 01 или 10. Шпример, если следующий бит будет нулем (т.е. high опускается ниже Halfn [0; Half] становится рабочим интервалом), то следующий за ним — единицей, так как интервал должен располагаться выше средней точки рабочего интервала. Поборот, если следующий бит оказался 1, то за ним будет следовать 0. Поэтому теперь интервал можно безопасно расширить вправо, если только мы запомним, что какой бы бит не был следующим, вслед за ним необходимо также передать в выходной поток его обратное значение. Таким образом, [First_qtr;Third_qtr] преобразуют в целый интервал, запоминая в bits_to_follow значение бита, за которым надо посылать обратный ему. Это объясняет, почему весь вывод совершается через процедуру bit_plus_follow(), а не непосредственно через output_bit().

Программа 2 показывает такой случай, когда отмеченный жирной линией рабочий интервал [low; high] расширяется 3 раза подряд. Пусть очередной бит ниже средней точки первоначального интервала, оказался нулем. Тогда следующие 3 бита будут единицами, поскольку стрелка находится не просто во второй его четверти, а в верхней четверти, даже в верхней восьмой части нижней половины первоначельного интервала - вот почему расширение можно произвести 3 раза. То же самое показывается для случая, когда очередной бит оказался единицей, и за ним будут следовать нули. Значит в общем случае необходимо сначала сосчитать количество расширений, а затем вслед за очередным битом послать в выходной поток найденное количество обратных ему битов.

Следуя этим рекомендациям, кодировщик гарантирует, что после операций сдвига будет или

low < First_qtr < Half <= high

или

low < Half < Third_qtr <= high

Значит, пока целочисленный интервал, охватываемый накопленными частотами, помещается в ее

135

РИ, 2000, № 1

четверть, представленную в code_value, проблема отрицательного переполнения не возникнет. Это соответствует условию, которое удовлетворяет Программе 1, так как Max_frequency = 2Л14 - 1 и Тор_уа1ие = 2Л16 - 1. Нельзя без увеличения количества битов, выделяемых для code_va1ues, использовать для представления счетчиков накопленных частот больше 14 битов.

Мы рассмотрели проблему отрицательного переполнения только относительно кодировщика, поскольку при декодировании каждого символа процесс следует за операцией кодирования, и отрицательное переполнение не произойдет, если выполняется такое же масштабирование с теми же условиями.

Переполнение. Теперь рассмотрим возможность переполнения при целочисленном умножении. Переполнения не произойдет, если произведение range*Max_frequency вмещается в целое слово, так как накопленные частоты не могут превышать Max_fTequency. Range имеет наибольшее значение в Top_va1ue + 1, поэтому максимально возможное произведение в Программе 1 есть 2Л16*(2Л14 - 1), которое меньше 2Л3 0. Для определения code_va1ue и range использован тип long, чтобы обеспечить 32битовую точность арифметических вычислений.

Ограниченность реализации. Ограничения, связанные с длиной слова и вызванные возможностью переполнения, можно обобщить, полагая, что счетчики частот представляются f битами, а code_va1ues - c битами. Программа будет работать корректно при f <= c - 2 и f + c <= р, где р есть точность арифметики.

В большинстве реализаций на Си р=31, если используются целые числа типа long, и р=32 - при unsigned long. В Программе 1 f=14 и c=16. Присоответствующих изменениях в объявлениях на unsigned long можно применять f=15 и c=17. На языке ассемблера c=16 является естественным выбором, поскольку он ускоряет некоторые операции сравнения и манипулирования битами.

Если ограничить разрядность 16 битами, то лучшие из возможных значений c и f есть соответственно 9 и 7, что не позволяет кодировать полный алфавит из 256 символов, поскольку каждый из них будет иметь значение счетчика не меньше единицы. С меньшим алфавитом (например, из 26 букв или 4-битовых величин) справиться еще можно.

Завершение. При завершении процесса кодирования необходимо послать уникальный завершающий символ (EOF-символ), а затем послать вслед достаточное количество битов для гарантии того, что закодированная строка попадет в итоговый рабочий интервал. Так как процедура done_encoding() может быть уверена, что low и high ограничены выражением либо (1a), либо (1б), ему нужно только передать 01 или 10 соответственно, для удаления оставшейся неопределенности. Удобно это делать с помощью ранее рассмотренной процедуры bit_p1us_fo11ow().

Процедура шр^^Ы^) на самом деле будет читать немного больше битов из тех, что вывела output_bit(), потому что ей нужно сохранять заполнение нижнего конца буфера. Неважно, какое значение имеют эти биты, поскольку EOF уникально определяется последними переданными битами.

Модели для арифметического кодирования Программа 1 должна работать с моделью, которая предоставляет пару перекодировочных таблиц index_to_char[] и char_to_index[] и массив накопленных частот cum_freq[], причем к последнему предъявляются следующие требования:

. cum_freq[i-1] >= cum_freq[i];

. никогда не делается попытка кодировать символ i, для которого

. cum_freq[i-1] = cum_freq[i];

. cum_freq[0] <= Max_frequency.

Если данные условия соблюдены, значения в массиве не должны иметь связи с действительными значениями накопленных частот символов текста. И кодирование, и декодирование будут работать корректно, причем первому понадобится меньше места, если частоты точные. (Вспомним успешное кодирование “sall!” в соответствии с моделью из табл. 1, не отражающей, однако, подлинной частоты в тексте).

Фиксированные модели. Простейшей моделью является та, в которой частоты символов постоянны. Первая модель из Программы 2 задает частоты символов, приближенные к общим для английского текста (взятым из части Свода Брауна). Накопленным частотам байтов, не появлявшимся в этом образце, даются значения, равные 1 (поэтому модель будет работать и для двоичных файлов, где есть все 256 байтов). Все частоты были нормализованы в целом до 8000. Процедура инициализации start_model() просто подсчитывает накопленную версию этих частот, сначала инициализируя таблицы перекодировки. Скорость выполнения будет увеличена, если эти таблицы переупорядочить так, чтобы наиболее частые символы располагались в начале массива cum_freq[]. Так как модель фиксированная, то процедура update_mode1(), вызываемая из encode.c и decode.c, будет просто заглушкой.

Строгой моделью является та, где частоты символов текста в точности соответствуют предписаниям модели. Например, фиксированная модель из Программы 2 близка к строгой модели для некоторого фрагмента из Свода Брауна, откуда она была взята. Однако, для того, чтобы быть истинно строгой, ее, не появлявшиеся в этом фрагменте символы должны иметь счетчики, равные нулю, а не 1 (при этом нужно жертвовать возможностями исходных текстов, которые содержат эти символы). Кроме того, счетчики частот не должны масштабироваться к заданной накопленной частоте, как это было в Программе 2. Строгая

136

РИ, 2000, № 1

модель может быть вычислена и передана перед пересылкой текста. Клири и Уиттен показали, что при общих условиях это не даст общего лучшего сжатия по сравнению с описываемым ниже адаптивным кодированием.

Адаптивная модель. Адаптивная модель изменяет частоты уже найденных в тексте символов. В начале все счетчики могут быть равны, что отражает отсутствие начальных данных, но по мере просмотра каждого входного символа они изменяются, приближаясь к наблюдаемым частотам. И кодировщик, и декодировщик используют одинаковые начальные значения (например, равные счетчики) и один и тот же алгоритм обновления, что позволяет их моделям всегда оставаться на одном уровне. Кодировщик получает очередной символ, кодирует его и изменяет модель. Декодировщик определяет очередной символ на основании своей текущей модели, а затем обновляет ее.

Вторая часть Программы 2 демонстрирует такую адаптивную модель, рекомендуемую для использования в Программе 1, поскольку на практике она превосходит фиксированную модель по эффективности сжатия. Инициализация проводится так же, как для фиксированной модели, за исключением того, что все частоты устанавливаются в 0. Процедура ^date_mode1 (symbol) вызывается из encode_symbol() и decode_symbo1() (Программа 1) после обработки каждого символа.

Обновление модели довольно дорого по причине необходимости поддержания накопленных сумм. В Программе 2 используемые счетчики частот оптимально размещены в массиве в порядке убывания своих значений, что является эффективным видом самоорганизуемого линейного поиска. Процедура update_mode1() сначала проверяет новую модель на предмет превышения ею ограничений по величине накопленной частоты, и если оно имеет место, то уменьшает все частоты делением на 2, заботясь при этом, чтобы счетчики не превратились в 0, и перевычисляет накопленные значения (Программа

2). Затем, если необходимо, update_mode1() переупорядочивает символы для того, чтобы разместить текущий в его правильной категории относительно частотного порядка, чередуя для отражения изменений перекодировочные таблицы. В итоге процедура увеличивает значение соответствующего счетчика частоты и приводит в порядок соответствующие накопленные частоты.

2. Основные характеристики и сравнительный анализ

Рассмотрим показатели эффективности сжатия и времени выполнения известного программного обеспечения, реализующего практическое примененение арифметического кодирования (программы 1).

Эффективность сжатия. Вообще при кодировании текста арифметическим методом количество битов в закодированной строке равно энтропии этого текста

относительно использованной для кодирования модели. Три фактора вызывают ухудшение этой характеристики:

(1) расходы на завершение текста;

(2) использование арифметики небесконечной точности;

(3) такое масштабирование счетчиков, что их сумма не превышает Max_frequency.

Как было показано, ни один из них не значителен. В порядке выделения результатов арифметического кодирования модель будет рассматриваться как строгая (в определенном выше смысле).

Арифметическое кодирование должно досылать дополнительные биты в конец каждого текста, совершая такимобразом дополнительные усилия на завершение текста. Для ликвидации неясности с последним символом процедура done_encoding() (Программа 1) посылает два бита.

Затраты при использовании арифметики конечной точности проявляются в сокращении остатков в процессе деления. Это видно при сравнении с теоретической энтропией, которая выводит частоты из счетчиков точно также масштабируемых при кодировании. Здесь затраты незначительны—порядка 10л4 битов/символ.

Дополнительные затраты на масштабирование счетчиков отчасти больше, но все равно очень малы. Для коротких текстов (меньших 2Л14 байт) их нет. Но даже с текстами в 10л5 — 10л6 байтов накладные расходы, подсчитанные экспериментально, составляют менее 0,25% от кодируемой строки.

Адаптивная модель в Программе 2, при угрозе превышения общей суммой накопленных частот значение Max_frequency, уменьшает все счетчики. Это приводит к тому, что взвешивать последние события тяжелее, чем более ранние. Таким образом, показатели имеют тенденцию прослеживать изменения во входной последовательности, которые могут быть очень полезными. (Мы сталкивались со случаями, когда ограничение счетчиков до 6-7 битов давало лучшие результаты, чем повышение точности арифметики). Это зависит от источника, к которому применяется модель.

Время выполнения. Программа 1 была написана скорее для ясности, чем для скорости. При выполнении ее вместе с адаптивной моделью из Программы 2 потребовалось около 420 мкс на байт исходного текста на ЭВМ VAX-11/780 для кодирования и почти столько же для декодирования. Однако легко устраняемые расходы, такие как вызовы некоторых процедур, создающие многие из них, и простая оптимизация увеличивают скорость в 2 раза. В приведенной версии программы на языке Си были сделаны следующие изменения:

(1) процедуры шри1_Ьк(), output_bit() и bit_p1us_fo11ow() были переведены в макросы, устранившие расходы по вызову процедур;

РИ, 2000, № 1

137

(2) часто используемые величины были помещены в регистровые переменные;

(3) умножения на 2 были заменены добавлениями (“+=”);

(4) индексный доступ к массиву в циклах Программ 1 и 2 адаптивной модели был заменен операциями с указателями.

Эта среднеоптимизированная реализация на Си имела время выполнения 214/252 мкс на входной байт, для кодирования/декодирования 100.000 байтов английского текста на VAX-11/780. Кодирование программы на Си одной и той же длины везде осуществляется несколько дольше, исключая только лишь двоичные объектные файлы. В тестовый набор были включены два искусственных файла, чтобы позволить читателям повторять результаты. 100000-байтный “алфавит” состоит из повторяемого 26-буквенного алфавита. “Асимметричные показатели” содержит 10000 копий строки “aaaabaaaac”. Эти примеры показывают, что файлы могут быть сжаты плотнее, чем 1 бит/символ (12092-байтный выход равен 93736 битам). Все приведенные результаты получены с использованием адаптивной модели из Программы 2.

Дальнейшее снижение в 2 раза временных затрат может быть достигнуто перепрограммированием приведенной программы на язык ассемблера. Тщательно оптимизированная версия Программ 1 и 2 (адаптивная модель) была реализована для VAX.

Временные характеристики ассемблерной реализации на VAX-11/780 даны в табл. 3. Они были получены при использовании возможности профиля UNIX и точны только в пределах 10%. (Этот механизм создает гистограмму значений программного счетчика прерываний часов реального времени и страдает от статистической вариантности также, как и некоторые системные ошибки). “Вычисление границ” относится к начальным частям encode_symbol() и decode_symbol() (программа 1), которые содержат операции умножения и деления. “Сдвиг битов” — это главный цикл в процедурах кодирования и декодирования. Требующее умножения и деления вычисление cum в decode_symbol(), а также последующий цикл для определения следующего символа есть “декодирование символа”. А “обновление модели” относится к адаптивной процедуре update_model() из Программы 2.

Как и предполагалось, вычисление границ и обновление модели требуют одинакового количества времени и для кодирования, и для декодирования в пределах ошибки эксперимента. Сдвиг битов осуществляется быстрее для текстовых файлов, чем для Си-программ и объектных файлов из-за лучшего его сжатия. Дополнительное время для декодирования по сравнению с кодированием возникает из-за шага “декодирование символа” — цикла в строке, выполняемого чаще (в среднем 9, 13 и 35 раз соответственно). Это также влияет на время обновления модели, так как связано с количеством накапливающих

Таблица 3

Тип последовательностей данных Время кодирования, мкс Время декодирования, мкс

Текстовые файлы 104 135

Вычисление границ 32 31

Сдвиг битов 39 30

Обновление модели 29 29

Декодирование - 45

символа

Остальное 4 0

Си-программы 109 151

Вычисление границ 30 28

Сдвиг битов 42 35

Обновление модели 33 36

Декодирование - 51

символа

Остальное 4 1

Объектный файл 158 241

VAX

Вычисление границ 34 31

Сдвиг битов 46 40

Обновление модели 75 75

Декодирование - 94

символа

Остальное 3 1

счетчиков, значения которых необходимо увеличивать в строках Программы 2. В худшем случае, когда символы распределены одинаково, эти циклы выполняются в среднем 128 раз. Такое положение можно улучшить, применяя в качестве СД для частот дерево более сложной организации, но это замедлит операции с текстовыми файлами.

Адаптивное сжатие текстов. Результаты сжатия, достигнутые Программами 1 и 2, варьируются от 4,8-5,3 битов/символ для коротких английских текстов (10л3 -10л4 байтов) до 4,5-4,7 битов/символ для длинных (10Л5-10Л6 байтов). Хотя существуют и адаптивные техники Хаффмана, они все же испытывают недостаток концептуальной простоты, свойственной арифметическому кодированию. При сравнении они оказываются более медленными. Небрежная проверка compact показывает, что внимание к оптимизации для обеих систем сравнимо при том, что арифметическое кодирование выполняется в 2 раза быстрее. Показатели сжатия в некоторой степени лучше у арифметического кодирования для всех тестовых файлов. Различие будет заметным в случае применения более сложных моделей, предсказывающих символы с вероятностями, зависящими от определенных обстоятельств (например, следования за буквой q буквы u).

Неадаптированное кодирование может быть выполнено арифметическим методом с помощью постоянной модели, подобной приведенной в Программе 2. При этом сжатие будет лучше, чем при кодировании Хаффмана. В порядке минимизации времени выполнения сумма частот cum_freq[0] будет выбираться

РИ, 2000, № 1

138

равной степени двойки, чтобы операции деления при вычислении границ (Программа 1) выполнялись через сдвиги. Для реализации на ассемблере VAX-11/ 780 время кодирования/декодирования составило 60-90 мкс. Аккуратно написанная реализация кодирования Хаффмана с использованием таблиц просмотра для кодирования и декодирования будет выполняться немного быстрее.

Кодирование черно-белых изображений. Применение для этих целей арифметического кодирования было рассмотрено Лангдоном и Риссаненом, получившим при этом блестящие результаты с помощью модели, использующей оценку вероятности цвета точки относительно окружающего ее некоторого шаблона. Он представляет собой совокупность из 10 точек, лежа-ших сверху и спереди от текущей, поэтому при сканировании растра они ей предшествуют. Это создает 1024 возможных контекста, относительно которых вероятность черного цвета у данной точки оценивается адаптивно по мере просмотра изображения. После этого каждая полярность точки кодировалась арифметическим методом в соответствии с данной вероятностью. Такой подход улучшил сжатие на 2030% по сравнению с более ранними методами. Для увеличения скорости кодирования Лангдон и Рисса-нен применили приблизительный метод арифметического кодирования, который избежал операций умножения путем представления вероятностей в виде целых степеней дроби 1/2. Кодирование Хаффмана для этого случая не может быть использовано прямо, поскольку оно никогда не выполняет сжатия двухсимвольного алфавита. Другую возможность для арифметического кодирования представляет применяемый к такому алфавиту популярный метод кодирования длин тиражей (run-length method). Модель здесь приводит данные к последовательности длин серий одинаковых символов (например, изображения представляются длинами последовательностей черных точек, идущих за белыми, следующих за черными, которым пре-дшествуют белые, и т.д.). В итоге должна быть передана последовательность длин. Стандарт факсимильных аппаратов CCITT строит код Хаффмана на основе частот, с которыми черные и белые последовательности разных длин появляются в образцахдокументов. Фиксированное арифметическое кодирование, которое будет использовать те же частоты, будет иметь лучшие характеристики, а адаптация таких частот для каждого отдельного документа будет работать еще лучше.

Кодирование произвольно распределенных целых чисел

часто рассматривается на основе применения более сложных моделей текстов, изображений или других данных. Рассмотрим, например, локально адаптивную схему сжатия Бентли, где кодирование и декодирование работает с N последними разными словами. Слово, находящееся в кэш-буфере, определяется по целочисленному индексу буфера. Слово, которое в нем не находится, передается в кэш-буфер через посылку его маркера, который следует за самими символами этого слова. Это блестящая модель для текста, в котором слова часто используются в течение некоторого короткого времени, а затем уже долго не используются. Обсуждается несколько кодирований переменной длины уже для целочисленных индексов кэш-буфера. В качестве основы для кодов переменной длины арифметический метод позволяет использовать любое распределение вероятностей, включая среди множества других и приведенные здесь. Кроме того, он допускает для индексов кэш-буфера применение адаптивной модели, что желательно в случае, когда распределение доступов к кэш- буферу труднопредсказуемо.

3. Заключение

Проблема обратимого сжатия текстовой информации и последовательностей дискретных данных будет оставаться в сфере внимания из-за возрастающих объемов хранимых и передаваемых в ЭВМ данных. В настоящее время для решения этой проблемы наиболее часто используется метод арифметического кодирования, рассмотренный в статье.

Литература: 1. Langdon G.G. An introduction to arithmetic coding. IBM Res. Беуек>р. 1984. Vol. 28, № 2. Р.135-149. 2. Rubin F. ЕхрєпшєП^ in text file compression. C^to^ia, 1997. Vol. 3.

Поступила в редколлегию 19.01.2000

Рецензент:д-р физ.-мат. наук, проф. Слесаренко А.П.

Журавка Андрей Викторович, аспирант кафедры информатики ХГТУСА ведущий инженер кафедры страхового, биржевого и банковского дела. Научные интересы: проблемы архивации текстовой информации и последовательностей дискретных данных. Увлечения и хобби: компьютерная техника, иностранные языки, туризм. Адрес: Украина, 61002, Харьков, ул. Сумская, 40, тел. 40-29-46.

Шевченко Людмила Петровна, д-р физ.-мат. наук, профессор, зав. кафедрой информатики ХГТУСА. Научные интересы: математическое моделирование, объектно-ориентированное программирование. Увлечения и хобби: компьютерная техника, иностранные языки, туризм. Адрес: Украина, 61002, Харьков, ул. Сумская, 40, тел. 40-29-25.

РИ, 2000, № 1

139