Более 9 миллиардов гигабайт информации ежедневно путешествуют по интернету, заставляя постоянно искать все новые и новые методы упаковки данных. Самые эффективные решения используют подходы, которые позволяют достичь большей плотности за счет "потерь" информации в процессе сжатия. Google, например, недавно представили вариант сжатия с потерями, в котором отправляющий компьютер отбрасывает детали изображения, а ИИ на принимающей стороне их восстанавливает. Даже Netflix использует подход, допускающий потери, понижая качество как только становится известно, что пользователь использует устройство с низким разрешением.

В то же время очень мало внимания уделяется сжатию без потерь. Почему? Ответ прост - методы сжатия без потерь уже невероятно эффективны. С их помощью работает буквально всё, от формата PNG до утилиты PKZip. И это все благодаря студенту, что захотел пропустить экзамен.

70 лет назад в MIT профессор Роберт Фано предложил своим студентам выбор: написать обычный выпускной экзамен, или улучшить ведущий алгоритм сжатия данных. Неизвестно, говорил Фано своим студентам или нет о том, что именно он является автором этого самого алгоритма, и к тому моменту уже много лет находился в поиске улучшения. Всё что нам известно это сам факт такого предложения.

Представим сообщение, собранное из букв, цифр и знаков препинания. Очевидным способом закодировать такое сообщение будет просто приписать каждому символу уникальное двоичное число. Например, символ А компьютер может представлять как 01000001, а восклицательный знак как 00100001. Подобные кодировки очень легко парсить - каждые 8 битов соответствуют одному символу. Но у них есть существенный недостаток - они отвратительно неэффективны, так, как одно и то же количество битов используется как для редких, так и для часто встречающихся в тексте символов. Многим более эффективный подход чем-то больше напоминает "морзянку", где часто встречающаяся Е представлена лишь одной точкой, а редкая Q последовательностью тире - тире - точка - тире.

Хотя код Морзе тоже довольно таки неэффективен. Да, кодировки каких-то символов короче, каких-то длиннее. Но из-за того, что длина одного символа может различаться, сообщения могут быть понятны только если между символами присутствуют небольшие периоды тишины. И действительно, без этих пауз получатель не будет иметь возможности отличить - .-. .. - . - "trite" от - .-. ..- . - "true".

Фано удалось решить эту часть проблемы. Он понял, что можно использовать коды символов различной длины без использования съедающих место "пробелов" между ними пока вы не используете одни и те же биты и для полноценного символа и для начала другого. Например, если символ S очень часто встречается в сообщении, его можно закодировать как 01, и в таком случае ни один другой код символа не должен начинаться на 01. Например, коды 010, 011, или 0110 будут все запрещены. В этом примере получившееся сообщение можно будет однозначно читать слева направо. Приписав же символу S код 01, L - 1, M - 001, и A - 000, сообщение 0100100011 можно будет однозначно расшифровать как слово "small", несмотря на то, что L представлено одним битом, S двумя, а каждая из оставшихся букв тремя.

Чтобы определять конкретные коды, Фано решил строить бинарные деревья, так, чтобы каждый символ был листом этого дерева. Код символа определялся как путь сверху вниз. Если ветка шла влево, к коду добавлялся 0, вправо - 1. Структура дерева позволяла легко избежать "перекрытия" - так, как все символы находятся в узлах - листьях, то есть окончаниях веток, ни один код не может начинаться с битов, составляющих целиком другой код.

Дерево Фано для сообщения "encoded".
Дерево Фано для сообщения "encoded".

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

В данном примере частота ветки с символами Е и К, что встречаются 3 и 2 раза соответственно равна частоте ветки с символами BPR и O, которые встречаются в сообщении один или два раза.
В данном примере частота ветки с символами Е и К, что встречаются 3 и 2 раза соответственно равна частоте ветки с символами BPR и O, которые встречаются в сообщении один или два раза.

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

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

Теперь представим себе сообщение, в котором метод Фано проигрывает. В сообщении "Schoolroom" буква O встречается 4 раза, а S, C, H, L, R и M по одному. Подход Фано начинается с приписывания O и еще какой-нибудь буквы левой ветке, 5 использований букв из этой ветки будут соответствовать 5 использованиям букв из правой ветки. Получившееся сообщение составляет 27 бит.

Хаффман же наоборот, начинал с наименее часто встречающихся символов - например, R и M, и группировал их вместе, в дальнейшем рассматривая полученную пару как один символ.

Далее обновленный список элементов предлагает 4 варианта: O, что встречается 4 раза, пара RM, частота которой равна 2 и одиночные буквы S, H, C и L. Хаффман снова выбирает две наименее часто встречающихся элемента, допустим H и L.

Список снова обновляется: у O вес все еще 4, RM и HL имеют 2 и остались одиночные S и C. Хаффман продолжает обновлять дерево и список, на каждом шагу снова группируя вместе два самых редких элемента.

В результате "schoolroom" становится 11101111110000110110000101, что на один бит короче чем при использовании подхода Фано.

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

И все это благодаря тому, что Хаффман выбрал не идти на экзамен.

Комментарии (3)


  1. Myclass
    25.09.2023 12:29

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

    Скорости кодировки/декодировки + таблица вероятностей (статистическая или динамическая) vs. простое кол-во оригинальных байтов, нужных для сохранения? Так что лучше?

    Посмотрел в вики. Там стоит

    Кодирование Хаффмана широко применяется при сжатии данных, в том числе при сжатии фото- и видеоизображений (JPEGMPEG), в популярных архиваторах (PKZIPLZH и др.), в протоколах передачи данных HTTP (Deflate), MNP5 и MNP7 и других.

    Иду и пытаюсь узнать, как используется этот код и там в JPEG (en) стоит:

    Entropy coding is a special form of lossless data compression. It involves arranging the image components in a "zigzag" order employing run-length encoding (RLE) algorithm that groups similar frequencies together, inserting length coding zeros, and then using Huffman coding on what is left.

    The JPEG standard also allows, but does not require, decoders to support the use of arithmetic coding, which is mathematically superior to Huffman coding. However, this feature has rarely been used, as it was historically covered by patents requiring royalty-bearing licenses, and because it is slower to encode and decode compared to Huffman coding. Arithmetic coding typically makes files about 5–7% smaller

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

    Может быть на системном уровне - да, но вроде на юзерном уровне с ASCII не работают, только Unicode. На весь unicode таблиц вероятностей не наберёшся. У вас есть инфа, где Код Хаффмана используется вообще?

    Алгоритм Лемпеля — Зива — Велча - тут понятно, что есть и выгода и скорость удовлетворяемая и работает с любыми видами данных, а не только с ASCII, и что он имеет практическое применение. Хотя патентов там тоже не меньше. Но работает. Но вот Код Хаффмана? Спасибо за инфы, если такие будут.


    1. jpegqs
      25.09.2023 12:29
      +1

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

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

      Короткие коды Хаффмана моментально декодируются по таблице (lookup table). Используется повсеместно, например в .zip и .rar.


    1. sci_nov
      25.09.2023 12:29

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