1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8

# теория · шаг 1 из 8

RLE и энтропия: откуда берётся предел сжатия

Коротко

Сжатие без потерь - это запись данных короче, по которой исходные данные восстанавливаются точно. Сжать ВСЕ файлы нельзя: коротких записей меньше, чем файлов. Сжимается только то, в чём есть закономерность. Самая простая закономерность - одинаковые символы подряд; её убирает RLE, записывая «сколько раз» и «что». На строке из тысячи букв a это одна пара, на случайном тексте из четырёх букв - 756 пар, то есть запись в полтора раза ДЛИННЕЕ исходной. Вторая закономерность - неравные частоты символов; её предел называется энтропией: H = Σ p · log₂(1/p) бит на символ. Для abracadabra это 2,04 бита, 22,4 на всё слово; код Хаффмана даёт 23.

Если ты объяснишь, почему не существует архиватора, сжимающего любой файл, посчитаешь энтропию строки aaab и скажешь, почему строка abab… сжимается лучше своей энтропии, - листай до «Теперь сам».

Как думаешь, бывает ли архиватор для всего

Реклама обещает: «наш архиватор уменьшает ЛЮБОЙ файл хотя бы на один бит, и из сжатого файла исходный восстанавливается точно».

Прежде чем читать дальше, ответь себе: возможно ли это хотя бы в принципе? Не «трудно ли написать», а может ли такая программа существовать. И если нет - что тогда вообще значит «хороший архиватор»?

На что это похоже

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

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

Механизм: счёт записей, RLE и энтропия

Счёт. Файлов длиной ровно n бит - 2ⁿ штук. Записей короче n бит - 1 + 2 + 4 + … + 2ⁿ⁻¹ = 2ⁿ − 1. Одной не хватает. Больше того: если архиватор укорачивает хоть один файл, какой-то другой он обязан удлинить. Пусть не удлиняет никого. Тогда файлам длиной до k бит достаются записи длиной до k бит, а тех и других поровну - значит, заняты все, и так при каждом k. Выходит, каждый файл получил запись своей же длины, и никто не укорочен. Это ответ на вопрос из начала: архиватора для всего не бывает, и не из-за нехватки изобретательности.

Значит, сжатие - это ставка на то, какие данные встречаются на практике. Первая ставка - на повторы символа подряд. RLE (кодирование длин серий) заменяет серию одинаковых символов парой «длина, символ»:

aaaabbbcca        →   (4, a) (3, b) (2, c) (1, a)          10 символов → 4 пары
abracadabra       →   (1, a) (1, b) (1, r) (1, a) …        11 символов → 11 пар

Вторая строка показывает обратную сторону ставки: серий длиннее единицы нет, и каждая буква обросла счётчиком.

Вторая ставка - на неравные частоты: частым символам короткие коды, редким длинные. Это код Хаффмана из раздела про жадные алгоритмы. Там же было названо число, ниже которого он опуститься не может, - энтропия. Теперь - откуда оно берётся.

Пусть в тексте восемь разных символов, и каждый встречается одинаково часто - в одной восьмой позиций. Чтобы указать один из восьми, нужно три вопроса «да или нет»: каждый делит оставшихся пополам, а 8 → 4 → 2 → 1 - это три деления. Три бита, и 3 = log₂ 8. Логарифм здесь - не украшение, а число делений пополам, как в двоичном поиске.

Теперь символ встречается в доле p позиций. Он ведёт себя как «один из 1/p»: при p = 1/8 - один из восьми, при p = 1/2 - один из двух. На него нужно log₂(1/p) бит. Когда 1/p - не степень двойки, число выходит дробным. Отдельному символу дробное число бит выдать нельзя; это средняя цена, к которой можно приблизиться на длинном тексте. Энтропия - среднее этого числа по всем позициям текста: каждый символ входит с весом своей доли.

H = Σ p · log₂(1/p)        бит на символ; знак Σ - сумма по всем разным символам текста

Правило

Ни один способ сжатия без потерь не укорачивает все файлы: укорачивая одни, он удлиняет другие. RLE выгоден, только когда средняя длина серии заметно больше единицы. Если кодировать каждый символ отдельно и учитывать только его частоту, в среднем нельзя потратить меньше H = Σ p · log₂(1/p) бит на символ. Это предел для способов, которые знают о тексте только частоты символов; способ, который видит связи между соседними символами, уходит ниже.

Разбор: RLE на четырёх строках

def rle(s):
    pary = []
    i = 0
    while i < len(s):
        j = i
        while j < len(s) and s[j] == s[i]:
            j = j + 1                 # ищем конец серии
        pary.append([j - i, s[i]])
        i = j
    return pary

def obratno(pary):
    kuski = []
    for para in pary:
        kuski.append(para[1] * para[0])
    return "".join(kuski)

Замер. В третьем столбце - число пар; каждая пара занимает два места (счётчик и символ), так что запись короче исходной, только если пар меньше половины длины. Место - условная ячейка под один символ или одно число; сколько счётчик занимает на самом деле - отдельный вопрос, он в «Что ломается».

строка                                          длина     пар     запись против исходной
"a" * 1000                                       1000        1     2 места вместо 1000
строка скана: 12 w, 3 b, 20 w, 2 b, 13 w           50        5     10 вместо 50
tekst(1000, 4, 1) - случайные четыре буквы       1000      756     1512 вместо 1000
"ab" * 500                                       1000     1000     2000 вместо 1000

Строка скана - это ряд точек чёрно-белой страницы: длинные белые поля, короткие чёрные штрихи. Для таких данных RLE и придуман, и там он сжимает впятеро. На случайном тексте серии короткие - в среднем 1,3 символа, - и запись раздувается в полтора раза. На abab… серий длиннее единицы нет вовсе, и размер удваивается.

Разбор: энтропия abracadabra и где она перестаёт быть пределом

В слове 11 символов: a - пять раз, b и r - по два, c и d - по одному.

символ    доля p     log₂(1/p)     вклад p · log₂(1/p)
  a        5/11        1,138            0,517
  b        2/11        2,459            0,447
  r        2/11        2,459            0,447
  c        1/11        3,459            0,314
  d        1/11        3,459            0,314
                                  H =   2,040 бита на символ

На всё слово - 11 · 2,040 = 22,4 бита. Код Хаффмана для этих частот тратит 23 бита: он обязан выдавать целое число бит на символ и поэтому чуть не дотягивается до предела. Код фиксированной длины - три бита на символ, 33 бита.

Теперь граница. Возьмём строку "ab" * 500. Символов два, доли равны, H = 1 бит на символ, 1000 бит на строку. Но ты только что прочитал эту строку в виде короткой записи: «ab, пятьсот раз». Как запись оказалась короче предела?

Предел честный, но он посчитан для модели, в которой про символ известна только его частота. Такая модель не видит, что после a всегда идёт b. Способ, который это видит, - другая модель, и у неё другой предел. Следующие уроки ровно про это: LZ77Разбирается в разделе 17, глава «Сжатие - RLE, Хаффман, LZ77, арифметическое»: LZ77: ссылка назад вместо повтора замечает повторы кусков, контекстные модели - что символ зависит от предыдущих. Замер из следующего урока: журнал запросов из 826 символов имеет энтропию 3578 бит, а LZ77 записывает его примерно в 900.

Самого нижнего предела - длины кратчайшей программы, печатающей данный файл, - вычислить нельзя: доказано, что алгоритма для этого не существует. Поэтому сжатие остаётся ремеслом подбора моделей, а не решённой задачей.

Что ломается без этого

Первое: RLE на данных без серий. Размер удваивается. Рабочие форматы страхуются: кусок без серий записывается как есть, с пометкой «дальше столько-то символов без сжатия».

Второе: счётчик не помещается. Если на длину серии отведён один байт, серия из 300 символов в него не влезет. Её режут на 255 и 45; забытая проверка даёт счётчик 44 и потерянные 256 символов.

Третье: счётчик и данные записаны одними и теми же знаками. Запись 3a12b понятна, пока в данных нет цифр. Строка 111a запишется как 311a - и это читается и как «три единицы, одна a», и как «триста одиннадцать букв a». Счётчик хранят отдельным полем фиксированного размера, а не цифрами в общем потоке.

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

Зачем это в работе

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

Второй - RLE внутри больших форматов. В чистом виде он живёт в старых форматах картинок и в факсах. А как ступень он стоит почти везде: в архиваторе bzip2 (до и после главного преобразования), в JPEG (серии нулей), в форматах хранения таблиц по столбцам, где значение в столбце часто повторяется тысячи раз подряд.

Третий - разговор о невозможном. Когда обещают сжатие «в сто раз для любых данных», счёт записей из этого урока закрывает вопрос за минуту: не для любых, а для каких именно?

Теперь сам

Строка aaab. Запиши её в RLE. Посчитай энтропию в битах на символ (log₂(4/3) ≈ 0,415). Сколько бит потратит на эту строку код Хаффмана и сколько - идеальный кодировщик, достигающий энтропии?

Ответ. RLE: (3, a) (1, b) - две пары на четыре символа.

Доли: a - 3/4, b - 1/4.

H = 3/4 · log₂(4/3) + 1/4 · log₂ 4 = 0,75 · 0,415 + 0,25 · 2 = 0,311 + 0,5 = 0,811 бита на символ

На строку - 4 · 0,811 = 3,24 бита. Код Хаффмана при двух символах даёт каждому по одному биту: 4 бита, и меньше одного бита на символ он потратить не может, как бы ни были перекошены частоты. При долях 3/4 и 1/4 потеря невелика. При долях 95 и 5 процентов энтропия равна 0,29 бита на символ, а Хаффман по-прежнему тратит целый бит - в три с половиной раза больше нужного. Этот разрыв закрывает арифметическое кодирование, через урок.

Главное

Сжатие возможно не потому, что есть хитрый алгоритм, а потому, что настоящие данные не случайны: в них есть серии, перекошенные частоты, повторяющиеся куски. Каждый метод сжатия - это модель одной такой закономерности и ставка на то, что она в данных есть; на данных без неё метод проигрывает, и RLE на abab… показывает это буквально - вдвое длиннее. Энтропия - предел для модели, знающей только частоты символов. Это не предел сжатия вообще: умнее модель - ниже предел.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.