thedeemon: (Default)
[personal profile] thedeemon
В предыдущих частях мы без особенных усилий построили компрессор, который жмет сильнее, чем zip, rar, bz2 и 7z в их режимах по-умолчанию. Однако примененным технологиям хоть и не 30 лет, как LZ, но не меньше 15. Куда продвинулся прогресс за это время?

В классическом PPM мы исходим из того, что более длинные контексты лучше предсказывают очередной символ. Вообще говоря, это не всегда правда. Например, на границе двух слов длинный контекст будет содержать конец предыдущего слова, которое не особенно помогает в предсказании и рассеивает статистику. Вместо выбора самого длинного имеющегося контекста можно выбрать, например, тот, в котором распределение символов имеет наименьшую энтропию. Если символ будет угадан с первой попытки (без эскейпов), то в этом контексте он в среднем будет закодирован меньшим числом бит. А если в том контексте его нет, то поскольку это потенциально более короткий контекст, максимальное число уходов будет меньше. Но считать для всех контекстов символа их энтропию слишком накладно. Есть более простой метод, дающий практически тот же результат: выбирать контекст, у которого вероятность самого вероятного символа больше, чем у всех других контекстов. Поскольку скорее всего символ окажется именно тем, который имеет наибольшую вероятность (тавтология), на него потратится меньше всего бит там, где эта вероятность больше. Прием с таким осознанным выбором контекста носит название LOE - Local Order Estimation. Я попробовал реализовать его в демонстрационном компрессоре, но то ли он действительно несовместим с некоторыми другими приемами, то ли я просто не умею его готовить (самый большой вопрос - как обновлять статистику в дереве контекстов), но у меня LOE не дал улучшения сжатия, а только ухудшил.

Давайте теперь посмотрим, чему равна оценка вероятности произвольного символа в PPM. Пусть P_(k,i) - оценка вероятности i-го символа в контексте длины k, а E_k - оценка вероятности ухода из контекста длины k. Тогда если мы кодируем i-й символ в PPM 4-го порядка, и самый длинный из нашедшихся контекстов имеет длину 3, и нужный символ в нем найден, то его вероятность будет оценена как P_(3,i). Если нужного символа нет в контексте длины 3, то делается уход к более короткому. Если символ найден в контексте 2-го порядка, то его оценка там P_(2,i) будет умножена на вероятность ухода из предыдущего контекста E_4, т.к. подинтервал для P_(2,i) будет высекаться внутри подинтервала для Е_4. Если же символ нашелся лишь в контексте 1-го порядка, то произойдет два ухода, и его оценка получится равной E_4 * E_3 * P_(2,i). В общем случае если мы начали оценивать символ в самом длинном из имеющихся контекстов длины start_k, а нашли символ в контексте found_k, то его оценка его вероятности получится

Если еще больше обобщить это выражение, то можно записать так:

Т.е. оценка вероятности символа получается из ее оценок в разных контекстах с разными весами. В классическом PPM веса всех кроме одного контекстов равны нулю. Но никто же не мешает сделать их ненулевыми: брать разные оценки и смешивать их с некоторыми весами. Причем в такой схеме уже не нужны никакие уходы, мы работает только с вероятностями символов. В определенном смысле, схема с уходами - это частный случай смешивания контекстов. Вычислять такие взвешенные суммы для всех символов довольно накладно. Но все становится много проще и удобнее, если от посимвольного сжатия перейти к побитному. Будем рассматривать входные данные как последовательность бит, оценивать для каждого бита вероятность 0 и 1 разными контекстами с разными весами и кодировать бинарным арифметиком, который тоже сильно проще (в частности, там можно обойтись без делений и с минимумом умножений или даже вовсе без них). В PPM нашими контекстами были k предыдущих символов. Но опять же никто не мешает ввести дополнительные модели, например разреженные контексты (включающие некоторые не подряд идущие символы из истории), контексты, учитывающие текущую позицию (полезно для сжатия нетекстовых структурированных данных) и т.д. Можно придумать очень много разных моделей, использующих разные данные, и все они будут давать свои оценки вероятности единицы и нуля, а эти оценки мы можем как-нибудь хитро смешивать. Такой подход носит название CM - Context Mixing. Именно на нем сейчас построены самые мощные компрессоры, побеждающие в соревнованиях. В частности, семейство компрессоров PAQ работает именно по этому принципу.

На первый план в этой схеме выходит вопрос о том, как именно смешивать оценки и как обновлять веса. Самый простой способ: если каждая модель j со своим контекстом содержит счетчики нулей и единиц n0_j и n1_j (соответственно, предсказывая вероятность нуля как n0_j/(n0_j+n1_j)), то можно просто просуммировать соответствующие счетчики всех моделей
N0 = n0_1 + n0_2 + n0_3 + ...
N1 = n1_1 + n1_2 + n1_3 + ...
и получить оценку N0/(N0+N1). Это эквивалентно суммированию оценок с весами, пропорциональными количеству символов, встреченных в использованных контекстах (w_j ~ n0_j + n1_j).

Другой способ - присвоить моделям разные вещественные веса w_j, суммировать счетчики с этими весами, а потом после кодирования каждого символа (бита) увеличивать вес тем моделям, которые успешно предсказали бит, и уменьшать веса менее успешным моделям. Тут
N0 = w1 * n0_1 + w2 * n0_2 + w3 * n0_3 + ...
N1 = w1 * n1_1 + w2 * n1_2 + w3 * n1_3 + ...
P(0) = N0 / (N0 + N1)
средняя длина сжатого бита получается равна энтропии
H = -P(0) * log2 P(0) - (1-P(0)) * log2 (1-P(0))
Если поставить определение P(0) через счетчики, то получим выражение, зависящее от w1, w2, w3... от которого можно найти производную по каждому из весов w_j и будет ясно, в какую сторону вес нужно изменить, чтобы уменьшить H. Что-то вроде
w_j := w_j + (bit - P)((N0+N1)* n1_j - N1 * (n0_j + n1_j)) / (N0*N1)

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

Еще можно заметить, что если у нас есть предсказания двух моделей, и одна предсказывает 0 с вероятностью 0.52, а другая - с вероятностью 0.95, то это как бы первая говорит "а фиг его знает, что это будет за бит", а вторая - "зуб даю, будет ноль". Т.е. вторая более уверена в предсказании, а значит совместную оценку вероятности стоит сделать не посередине (0.73), а ближе к более уверенной, скажем, 0.9. Это получается естественным образом, если перейти в "logistic domain", растянуть вероятности, сложить с весами и стянуть обратно:

где

и

При таком подходе обновление весов гораздо проще:
w_j := w_j + C * (bit - p) * stretch(P_j)
где C - параметр агрессивности обучения.

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

На этом обзор энтропийных методов сжатия можно закончить. Теперь вы знаете почти все, что знаю я, и при желании сможете тоже написать лучший в мире кодек и переехать на тропический остров. :) Главное, что я хотел донести, это:
* Задача сжатия сводится к задаче предсказания, оценки вероятностей.
* Чем больше компрессор знает о данных и чем лучше он "умеет знать" (т.е. чем лучше его модель), тем лучше его предсказание очередного символа, тем меньше неопределенность символа, меньше локальная энтропия и меньше число бит на символ.
* В частности, если символ полностью предсказуем, т.е. его вероятность равна 1, неопределенность равна 0, и на него тратится 0 бит.
* Оценка вероятностей символа и ее энтропия определяются моделью и имеющимися данными, это чисто субъективная величина, которая для каждого символа характеризует не столько сам символ, сколько объем и качество знаний компрессора о характере данных. Чем лучше у кодера модель, тем меньше получается энтропия сжимаемых данных (если они вообще сжимаемы).
* Каждый новый символ добавляет сведений в модель, увеличивает накопленный объем знаний кодера и тем самым уменьшает неопределенность относительно характера данных.
* Везде выше вместо "символ" можно подставить "событие" или "сообщение". Можно рассматривать потоки сообщений или последовательности событий, предсказывать их, оценивать вероятности. Энтропия сообщения (одного из потока) также определяется качеством априорного знания о сообщении, а не самим сообщением.

Кстати, о сжимаемости. Что будет, если входные данные абсолютно случайны и равномерно распределены? Тогда во всех контекстах вероятности всех символов будут 1/A, где А - размер алфавита, тогда на каждый символ будет в среднем потрачено log2(A) бит, а значит размер сжатых данных будет равен размеру входных, никакого сжатия не будет. Это верно, если статистику посчитать заранее. Если ее составлять динамически, то кодер будет увеличивать вероятности то одному символу, то другому, они будут иметь приблизительно одинаковые вероятности, но в каждый момент времени распределение будет немного отличаться от равномерного, поэтому в среднем может на символ уйти чуть больше log2(A) и вместо сжатия получится распухание. Нетрудно заметить, что универсальных компрессоров быть не может: нельзя все последовательности длины N бит сжать до размера меньшего N, т.к. количество всех возможных файлов длины N-1 меньше, чем количество всех возможных входных данных длины N. Любой компрессор сжимает какой-то класс данных, а какой-то наоборот раздувает. И в общем-то доля более-менее сносно сжимаемых данных из всех последовательностей длины N очень невелика, просто мы обычно имеем дело с сильно избыточными форматами.

Еще можно заметить такое свойство арифметического сжатия: в первой части мы увидели, что это функция, которая из одного длинного вещественного числа между 0 и 1 делает другое вещественное число между 0 и 1, если повезет, то менее длинное (по числу значащих цифр). Исходный интервал [0,1) делится на подинтервалы S0, S1, S2... каждый из которых тоже делится на подинтервалы и так далее, но размер каждого из подинтервалов соответствует вероятности появления символа, а значит плотность этой вероятности для каждого интервала равна Pi/Si = Pi/Pi = 1. Если у 0 вероятность вдвое выше, чем у 1, его интервал будет вдвое больше, но и попадать мы в него будем вдвое чаще, т.е. итоговое число, результат сжатия, будучи рассмотрено как случайная величина, имеет равномерное распределение: сжимая множество различных файлов с одинаковой статистикой мы получим множество длинных "чисел", и они будут распределены равномерно. Равномерно распределенная на [0,1) случайная величина так же равномерна распределена на любом интервале [a,b) внути [0,1). А это значит, что результат энтропийного кодера (если он хороший и модель его правильно оценивает вероятности) есть последовательность битов с равномерным распределением. Именно поэтому хорошо сжатые данные не подлежат дальнейшему сжатию.

Еще момент: как показывают примеры со сложными узко заточенными моделями и с препроцессингом данных, мы можем все больше информации о характере данных заранее заложить в компрессор. Чем больше он будет знать о данных, тем лучше сожмет. Информация о данных как бы перетекает из данных в сам компрессор. В экстремальном случае, компрессор может уже содержать внутри себя весь сжимаемый файл и сжать его до одного маленького флага/идентификатора, что именно этот файл был сжат. Поэтому в ряде соревнований учитывают суммарный размер сжатых данных и самого компрессора. Тут мы естественным образом приходим к понятию Колмогоровской сложности. Но говорить о ней бесполезно, т.к. это величина невычислимая, потому лучше о ней молчать. :)

Продолжение...

Date: 2011-11-18 04:27 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Допустим, мы сжимаем русский текст, и в нем нам частенько попадаются слова "слово", "словарь", "ловкий", "перловка", "словно". Если мы сжимаем посимвольно, то компрессор быстро уловит межсимвольные зависимости и после "ло" будет предсказывать с большой вероятностью "в", что хорошо скажется на сжатии. Если же мы будем сжимать по словам, то эти зависимости не будут замечены и использованы. Нам придется кодировать слово целиком, исходя из частоты его встречания в тексте или в словаре. То, что слова частично похожи и их части встречаются чаще, чем сами слова по-отдельности, никак не будет использовано. Особенно это актуально в языках типа нашего, где у слов варьируются окончания и суффиксы. Если у нас 20 раз встретилось "слово", 30 раз "слова" и 10 раз "словами", при посимвольном сжатии общие части будут найдены и хорошо предсказаны, окончания - тоже (они часто повторяются с разными словами). Для пословного компрессора это совершенно разные "символы", он не может использовать их схожести.

Date: 2011-11-19 11:29 am (UTC)
From: (Anonymous)
хорошо, вот пример

в длинном тексте

...123:4:567...

4 позиция в которой происходит предсказание.

Вы утверждаете что предсказание будет точнее если использовать только часть "123"?

По моему очевидно что предсказание для "4" будет точнее по информации включающей в себя и "123", и "567".

Date: 2011-11-19 11:35 am (UTC)
From: [identity profile] p2004r.livejournal.com
анонимус это я (другой браузер использовал, а там нет пароллей и явок :)

текст ...123:4:567...

в символе 4 предсказание только по "123" не будет более точным чем предсказание по и "123", и "567"

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

Date: 2011-11-19 11:42 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Если компрессор будет заглядывать "в будущее", то декомпрессор не сможет работать - откуда он возьмет будущие символы?

Date: 2011-11-19 11:52 am (UTC)
From: [identity profile] p2004r.livejournal.com
он же блоки распаковки символов сжатого текста знает? или тут вся экономия за счет того что не надо таскать с собой словарь, достаточно алгоритм в программе?

тогда да, был невнимателен.

Date: 2011-11-19 12:08 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Да, в описанных методах никаких словарей не используется обычно. Они неявно сами образуются по мере накопления статистики контекстов.

Если бы у декодера был словарь, и он уже знал конец текущего слова ("567" из примера выше), то ему не нужно было бы предсказывать текущий символ (4), можно было бы сразу все слово вывести. Тогда все сводится к тому, чтобы как-то закодировать все слово целиком, т.е. оно становится "символом" и мы возвращаемся на несколько сообщений выше - эффективность сжатия падает за счет неиспользования схожести слов.

Date: 2011-11-19 12:20 pm (UTC)
From: [identity profile] p2004r.livejournal.com
почему целиком?

а понятно (ну я и тормоз :), словарь сжатия за счет его "аналитичности" имеет как бы максимально возможный для данного текста размер

Date: 2011-11-19 02:02 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Необязательно целиком, действительно. Моя мысль была в том, что просто если мы в середине слова используем символы постистории, то декодер в том же месте каким-то образом должен их знать, а значит их можно вывести сразу, нет смысла изображать отгадывание по буквам. Как в передаче "поле чудес": нет смысла открывать по одной букве, если можешь назвать все слово.

Ваш исходный вопрос можно превратить в такую схему: кодер и декодер имеют словарь русских слов, и видя предысторию "инфор", даже не встречая подобное слово раньше в тексте, они могут с большой вероятностью предсказать следующую букву "м", потому что так подсказывает словарь. Такой подход может в каких-то случаях улучшить сжатие, т.к. часть информации перенесена в словарь и не пишется в сжатый файл. Но в ряде случаев может наоборот навредить, т.к. в конкретном тексте используется не так много слов, и знание большого числа похожих слов, которые в данном тексте не используются, исказит распределение вероятностей символов и ухудшит сжатие. Простой пример - компрессор английского текста может быстро научиться, что после "q" всегда идет "u", и для данного текста это будет правдой, а если он будет иметь словарь и знать редкие слова, где после "q" идет "a", это уменьшит оценку вероятности "u", ухудшив сжатие.

Date: 2011-11-19 02:11 pm (UTC)
From: [identity profile] p2004r.livejournal.com
в принципе если речь о тексте на естественном языке (и доступен словарь языка или/и генератор словоформ), то достаточно первой и последней буквы + длинна слова (+ для особых случаев еще одна буква, это правило в скобках повторять рекурсивно :).

Date: 2011-11-19 03:56 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Просто номер слова в словаре получится эффективнее. :)

Date: 2011-11-19 03:59 pm (UTC)
From: [identity profile] p2004r.livejournal.com
если только не будет длиннее самого слова :)

Date: 2011-11-19 04:06 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Средняя длина английского слова - 5 букв. Русского еще длиннее. А слов в пределах миллиона.

Date: 2011-11-19 04:33 pm (UTC)
From: [identity profile] p2004r.livejournal.com
памяти все больше и она все дешевле, за таким подходом будущее :)

Date: 2011-11-19 04:50 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Каким? Отсутствием сжатия?
Да, местами так. Сейчас люди переходят на все большие битрейты и размеры в видео, беспотерьные аудиоформаты, не сжимают бинарники и т.п. Но в некоторых областях сжатие все еще актуально, особенно быстрое. Например, прочитать сжатый файл с диска и разжать в памяти бывает быстрее, чем читать с диска несжатый, ибо диск медленный.

Date: 2011-11-19 05:17 pm (UTC)
From: [identity profile] p2004r.livejournal.com
да, все каналы передачи информации место приложения.

но я писал о размере памяти в том смысле, что можно дешево иметь большие словари у отправителя и получателя информации.

Date: 2011-11-19 05:22 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
А, да, большие словари иметь не проблема. Но "во многом знании многие печали" - чем больше разных слов знаешь, тем больше сомнений какой символ будет следующий, так что для сжатия большой словарь может быть помехой.

Date: 2011-11-19 05:25 pm (UTC)
From: [identity profile] p2004r.livejournal.com
а что обсужденное сжатие по номеру словоформы проигрывает? кто то уже мерял?

Date: 2011-11-19 07:13 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Проигрывает, я мерял. Выше уже несколько раз пытался донести почему именно проигрывает и не может выигрывать.

Date: 2011-11-19 07:15 pm (UTC)
From: [identity profile] p2004r.livejournal.com
понятно, спасибо за потраченное на меня время. приятно читать Ваши посты.

Date: 2011-11-20 05:09 am (UTC)
From: [identity profile] thedeemon.livejournal.com
На всякий случай:
я среднюю длину и число слов привел не к тому, что словарь большой, а к тому, что среднее слово это 40 бит, а на номер в словаре хватит и 20.

Profile

thedeemon: (Default)
Dmitry Popov

December 2025

S M T W T F S
 12 3456
789101112 13
14151617181920
21222324252627
28293031   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 24th, 2026 11:08 pm
Powered by Dreamwidth Studios