Московский государственный университет имени М.В.Ломоносова
Опубликован: 10.10.2007 | Доступ: свободный | Студентов: 1478 / 158 | Оценка: 4.36 / 4.18 | Длительность: 14:22:00
Специальности: Программист
Лекция 1:

Методы сжатия без потерь

Теперь запишем алгоритм сжатия, используя целочисленные операции. Минимизация потерь по точности достигается благодаря тому, что длина целочисленного интервала всегда не менее половины всего интервала. Когда l_i или h_i одновременно находятся в верхней или нижней половине ( Half ) интервала, то мы просто записываем их одинаковые верхние биты в выходной поток, вдвое увеличивая интервал. Если l_i и h_i приближаются к середине интервала, оставаясь по разные стороны от его середины, то мы также вдвое увеличиваем интервал, записывая биты "условно". "Условно" означает, что реально эти биты выводятся в выходной файл позднее, когда становится известно их значение. Процедура изменения значений l_i и h называется нормализацией, а вывод соответствующих битов - переносом. Знаменатель дроби в приведенном ниже алгоритме будет равен 10000h = 65536, т.е. максимальное значение h_0  = 65535.

l0=0; h0=65535; i=0; delitel= b[clast]; // delitel=10
First_qtr = (h0+1)/4;    // = 16384
Half = First_qtr*2;     // = 32768
Third_qtr = First_qtr*3;// = 49152
bits_to_follow =0;      // Сколько битов сбрасывать

while(not DataFile.EOF()) {
	c = DataFile.ReadSymbol();  // Читаем символ
	j = IndexForSymbol(c); i++; // Находим его индекс 
	li = li-1 + b[j-1]*(hi-1 - li-1 + 1)/delitel;
	hi = hi-1 + b[j]*(hi-1 - li-1 + 1)/delitel - 1;
	for(;;) {         	// Обрабатываем варианты 
		if(hi < Half)   	// переполнения
			BitsPlusFollow(0);
		else if(li >= Half) {
			BitsPlusFollow(1);
			li-= Half; hi-= Half;
		}
		else if((li >= Third_qtr)&&(hi < First_qtr)){
			bits_to_follow++;
			li-= First_qtr; hi-= First_qtr;
		} else break;
		li+=li; hi+= hi+1;
	}
}    
           // Процедура переноса найденных битов в файл
void BitsPlusFollow(int bit)
{
	CompressedFile.WriteBit(bit);
	for(; bits_to_follow > 0; bits_to_follow--)
		CompressedFile.WriteBit(!bit);
}
Упражнение: Покажите, что BitsPlusFollow работает правильно и записывает в выходной файл значения, попадающие внутрь рабочего интервала.

Результаты работы алгоритма представлены в табл. 1.3

Таблица 1.3.
I Символ ( c_j ) l_i h_i Нормализованный l_i Нормализованный h_i Результат
0   0 65535      
1 К 19660 32767 13104 65535 01
2 О 13104 28832 26208 57665 010
3 В 41937 48227 7816 58143 010101
4 . 53111 58143 15836 35967 01010111
5 К 21875 25901 21964 38071 0101011101
6 О 21964 26795 22320 41647 010101110101
Упражнение: Выведите самостоятельно, какими битами нужно закончить сжатый файл, чтобы при декомпрессии были корректно получены последние 2-3 символа цепочки.

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

l0=0; h0=65535; delitel= b[clast];
First_qtr = (h0+1)/4;    	// = 16384
Half = First_qtr*2;     	// = 32768
Third_qtr = First_qtr*3;	// = 49152

value=CompressedFile.Read16Bit();
for(i=1; i< CompressedFile.DataLength(); i++){
	freq=((value-li-1+1)*delitel-1)/(hi-1 - li-1 + 1);
	for(j=1; b[j]<=freq; j++);   // Поиск символа
	
	li = li-1 + b[j-1]*(hi-1 - li-1 + 1)/delitel;
	hi = hi-1 + b[j  ]*(hi-1 - li-1 + 1)/delitel - 1;

	for(;;) {         	// Обрабатываем варианты 
		if(hi < Half)   	// переполнения
			; // Ничего
		else if(li >= Half) {
			li-= Half; hi-= Half; value-= Half;
		}
		else if((li >= Third_qtr)&&(hi < First_qtr)){
			li-= First_qtr; hi-= First_qtr;
			value-= First_qtr;
		} else break;
		li+=li; hi+= hi+1;
		value+=value+CompressedFile.ReadBit();
	}
	DataFile.WriteSymbol(c);
};
Упражнение: Предложите примеры последовательностей, сжимаемых алгоритмом с максимальным и минимальным коэффициентом.

Как видно, с неточностями арифметики мы боремся, выполняя отдельные операции над {\rm{l}}_{\rm{i}} и {\rm{h}}_{\rm{i}} синхронно в компрессоре и декомпрессоре.

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

Для того, чтобы оценить степень сжатия арифметическим алгоритмом конкретной строки, нужно найти минимальное число N, такое, что длина рабочего интервала при сжатии последнего символа цепочки была бы меньше 1/2^N . Этот критерий означает, что внутри нашего интервала заведомо найдется хотя бы одно число, в двоичном представлении которого после N -го знака будут только 0. Длину же интервала посчитать просто, поскольку она равна произведению вероятностей всех символов.

Рассмотрим приводившийся ранее пример строки из двух символов a и b с вероятностями 253/256 и 3/256. Длина последнего рабочего интервала для цепочки из 256 символов a и b с указанными вероятностями равна:

h_{256}  - l_{256}  = \left( {{{253} \over {256}}} \right)^{253}  \cdot \left( {{3 \over {256}}} \right)^3  = {{253^{253}  \cdot 9} \over {2^{2048} }} \approx 8.15501 \cdot 10^{ - 8}

Легко подсчитать, что искомое N=24 ( 1/2^{24}  \approx 5.96 \cdot 10^{ - 8} ), поскольку 23 дает слишком большой интервал (в 2 раза шире), а 25 не является минимальным числом, удовлетворяющим критерию. Выше было показано, что алгоритм Хаффмана кодирует данную цепочку в 256 битов. Т.е. для рассмотренного примера арифметический алгоритм дает десятикратное преимущество, перед алгоритмом Хаффмана и требует менее 0.1 бита на символ.

Упражнение: Подсчитайте оценку степени сжатия для строки "КОВ.КОРОВА".

Следует сказать пару слов об адаптивном алгоритме арифметического сжатия. Его идея заключается в том, чтобы перестраивать таблицу вероятностей b[j] по ходу упаковки и распаковки непосредственно при получении очередного символа. Такой алгоритм не требует сохранения значений вероятностей символов в выходной файл и, как правило, дает большую степень сжатия. Так, например, файл вида a^{1000} b^{1000} c^{1000} d^{1000} (где степень означает число повторов данного символа), адаптивный алгоритм сможет сжать эффективнее, чем потратив 2 бита на символ. Приведенный выше алгоритм достаточно просто превращается в адаптивный. Ранее мы сохраняли таблицу диапазонов в файл, а теперь мы считаем, прямо по ходу работы компрессора и декомпрессора, пересчитываем относительные частоты, корректируя в соответствии с ними таблицу диапазонов. Важно, чтобы изменения в таблице происходили в компрессоре и декомпрессоре синхронно, т.е. например, после кодирования цепочки длины 100 таблица диапазонов должна быть точно такой же, как и после декодирования цепочки длины 100. Это условие легко выполнить, если изменять таблицу после кодирования и декодирования очередного символа. Подробнее об адаптивных алгоритмах смотрите в главе "Методы контекстного моделирования".

Характеристики арифметического алгоритма:

Лучшая и худшая степень сжатия: Лучшая > 8 (возможно кодирование менее бита на символ), худшая - 1.

Плюсы алгоритма: Обеспечивает лучшую степень сжатия, чем алгоритм Хаффмана (на типичных данных на 1-10%).

Характерные особенности: Также как кодирование по Хаффману, не увеличивает размера исходных данных в худшем случае.