Ефективне стиснення простих двійкових даних


27

У мене є файл, що містить упорядковані двійкові числа від до :02n1

0000000000
0000000001
0000000010
0000000011
0000000100
...
1111111111

7z не стискав цей файл дуже ефективно (для n = 20, 22 Мб було стиснено до 300 кБ).

Чи є алгоритми, які можуть розпізнати дуже просту структуру даних і стискати файл до кількох байтів? Також я хочу знати, яка область CS або теорії інформації вивчає такі розумні алгоритми. "AI" буде занадто широким, будь ласка, запропонуйте більш конкретні ключові слова.
Поняття симетрії повинно відігравати фундаментальну роль у стисненні даних, але пошукові запити "симетрія в стисненні даних" та "теорія груп у стисненні даних" на диво не повертають майже нічого актуального.


11
Перевірте складність Колмогорова, яка в певному сенсі є оптимальною компресією (аж до постійної добавки). На жаль, складність Колмогорова не піддається обчисленню ...
Ювал Філімус

12
Чому потрібно стискати ці дані? Ви не можете просто відновити його будь-коли, коли це потрібно? (Що тісно пов'язане з підходом Колмогорова до складності, про який згадував @YuvalFilmus: складність Колмогорова - це по суті довжина найкоротшої програми, яка генерує вихід).
Девід Річербі

7
Я писав такий алгоритм у середній школі, 20 років тому. З огляду на ваш вклад, він би стислий до "декількох байт" (приблизно в 3 500 000: 1 за оптимального сценарію). Однак дані рідко коли-небудь виглядають так, тому не можна практично використовувати такий алгоритм. Загальні алгоритми стиснення повинні мати справу зі складними шаблонами, а не з простими. Будь-хто може написати алгоритм для зберігання лінійних даних, але зберігання цікавих даних є проблемою.
фірфокс

3
Як n = 20 дає 22 МБ? Я отримую 4,2 Мб, якщо використовую 4 байт цілих чисел
njzk2

3
@JiK о, добре. добре, що було б першим поняттям стиснення, не використовуючи 8 біт для представлення одного біта.
njzk2

Відповіді:


27

Це здається явним випадком використання для дельта стиснення . Якщо відоме апріорі, це тривіально: збережіть перше число дослівно, а для кожного наступного числа зберігайте лише різницю від попереднього. У вашому випадку це дастьn

0
1
1
1
1
...

Тоді це може за допомогою простого кодування довжини пробігу зберігатись у просторі, оскільки є лише O ( 1 ) групи (а саме дві) різних дельт.O(n)O(1)

Якщо невідомо, найпростішим моментом буде пошук грубої сили для розміру слова, для якого це представлення дельти / пробігу виходить найкоротшим. Можливо, лише зробіть цей пошук випадково вибраних, n великі шматки, щоб амортизувати накладні витрати знаходженняn, зберігаючи хорошу надійність.Nn

На відміну від пропозиції DW «все або нічого», дельта-компресія з кодуванням довжини пробігу може насправді дати розумні коефіцієнти стиснення для деяких простих реальних видів вмісту, наприклад, звуку низької роздільної здатності. (Таким чином, він підходить для низької якості, дуже низької затримки та низької потужності стиснення звуку.)


44

Звичайно, звичайно, є алгоритми. Ось мій алгоритм:

  1. Спочатку перевірте, чи файл містить упорядковані двійкові числа від до 2 n - 1 , для яких n . Якщо так, випишіть 0 біт, а потім n один біт, а потім 0 ​​біт.02n1nn

  2. Якщо ні, випишіть 1 біт, а потім запишіть 7z-стиснення файлу.

Це надзвичайно ефективно для файлів саме цієї структури.

Справа в тому, що у стисканні даних немає безкоштовного обіду. Можливо, ви зможете побудувати алгоритм стиснення, який добре стискає один тип файлів, за рахунок стиснення інших. Але, якщо ви апріорно знаєте щось про характер файлів, які ви будете стискати, ви можете оптимізувати свій алгоритм під конкретний тип файлу.

Область - «стиснення даних». Дивіться наш тег та читайте підручники щодо стиснення даних.


5
Завдання компресора - розпізнати загальні структури та використовувати їх. Це не так, як цей візерунок є рідкісним або незрозумілим. Тож природним є питання, чому це не експлуатується. Сказати йому, що йдеться про компроміс, або дати йому алгоритм, який не спрацьовує з усім, окрім цього шаблону, - це тотальне копіювання.
Мехрдад

17
Звичайно, для мене це виглядає нечасто. Це трапляється в реальних даних вкрай рідко, порівняно з типовими моделями, які хороші компресори шукають.
amalloy

17
@Mehrdad Це не хитре копання: це вся суть . Для будь-якого шаблону X, який просто генерується та перевіряється, існує алгоритм стиснення, який шукає цю схему та справляється з нею. Отже, це відповідь на будь-яке запитання у рядку "Чи існує алгоритм стиснення, який займається таким X?" Звичайно, завжди є алгоритм, який шукає трохи складніші схеми. І є одна, яка шукає дещо складніші візерунки, ніж ця, також безмежна . Ваше заперечення є необґрунтованим.
Девід Річербі

Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
Жил "ТАК - перестань бути злим"

Крайнє застосування цього принципу - це посилання на магнітні магнітні потоки, де будь-який файл або колекції файлів будь-якого розміру просто представлені (стиснуті) до фіксованих 160 біт даних. Звичайно, існує ризик виникнення хеш-зіткнення.
slebetman

17

Все, що використовує BWT (трансформація Burrow – Wheeler), повинно бути здатне стискати це досить добре.

Мій швидкий тест Python:

>>> import gzip
>>> import lzma
>>> import zlib
>>> import bz2
>>> import time
>>> dLen = 16
>>> inputData = '\n'.join('{:0{}b}'.format(x, dLen) for x in range(2**dLen))
>>> inputData[:100]
'0000000000000000\n0000000000000001\n0000000000000010\n0000000000000011\n0000000000000100\n000000000000010'
>>> inputData[-100:]
'111111111111010\n1111111111111011\n1111111111111100\n1111111111111101\n1111111111111110\n1111111111111111'
>>> def bwt(text):
    N = len(text)
    text2 = text * 2
    class K:
        def __init__(self, i):
            self.i = i
        def __lt__(a, b):
            i, j = a.i, b.i
            for k in range(N): # use `range()` in Python 3
                if text2[i+k] < text2[j+k]:
                    return True
                elif text2[i+k] > text2[j+k]:
                    return False
            return False # they're equal

    inorder = sorted(range(N), key=K)
    return "".join(text2[i+N-1] for i in inorder)

>>> class nothing:
    def compress(x):
        return x

>>> class bwt_c:
    def compress(x):
        return bwt(x.decode('latin_1')).encode('latin_1')

>>> for first in ('bwt_c', 'nothing', 'lzma', 'zlib', 'gzip', 'bz2'):
    fTime = -time.process_time()
    fOut = eval(first).compress(inputData)
    fTime += time.process_time()
    print(first, fTime)
    for second in ('nothing', 'lzma', 'zlib', 'gzip', 'bz2'):
        print(first, second, end=' ')
        sTime = -time.process_time()
        sOut = eval(second).compress(fOut)
        sTime += time.process_time()
        print(fTime + sTime, len(sOut))

bwt_c 53.76768319200005
bwt_c nothing 53.767727423000224 1114111
bwt_c lzma 53.83853460699993 2344
bwt_c zlib 53.7767307470001 5930
bwt_c gzip 53.782549449000044 4024
bwt_c bz2 54.15730512699997 237
nothing 6.357100005516259e-05
nothing nothing 0.0001084830000763759 1114111
nothing lzma 0.6671195740000258 27264
nothing zlib 0.05987233699988792 118206
nothing gzip 2.307255977000068 147743
nothing bz2 0.07741139000017938 187906
lzma 0.6767229399999906
lzma nothing 0.6767684639999061 27264
lzma lzma 0.6843232409999018 27324
lzma zlib 0.6774435929999072 27280
lzma gzip 0.6774431810001715 27292
lzma bz2 0.6821310499999527 27741
zlib 0.05984937799985346
zlib nothing 0.05989508399989063 118206
zlib lzma 0.09543156799986718 22800
zlib zlib 0.06264000899977873 24854
zlib gzip 0.0639041649999399 24611
zlib bz2 0.07275534999985211 21283
gzip 2.303239570000187
gzip nothing 2.303286251000145 147743
gzip lzma 2.309592880000082 1312
gzip zlib 2.3042639900002087 2088
gzip gzip 2.304663197000309 1996
gzip bz2 2.344431411000187 1670
bz2 0.07537686600016968
bz2 nothing 0.07542737000017041 187906
bz2 lzma 0.11371452700018381 22940
bz2 zlib 0.0785322910001014 24719
bz2 gzip 0.07945505000020603 24605
bz2 bz2 0.09332576600013454 27138

(Числа тут - "перший_компресор, другий_компресорний час_прийнятий байт_ут")

(BWT взято звідси )

Це все ще "не лише кілька байт", але, тим не менш, набагато краще, ніж лише gzip. Наприклад, BWT + bz2 знижується до 237 байт з 1114111 для 16-бітного вводу.

На жаль, BWT набагато надто повільні і пам'ятають багато пам'яті. Тим більше, що це наївна реалізація в Python - на моїй машині у мене закінчується оперативна пам’ять до 2 ** 20.

З Pypy я зміг запустити повний вхід 2 ** 20, і він стискає його до 2611 байт з BWT, а потім bz2. Але більше 3 хвилин і максимум 4 Гб оперативної пам’яті використовували ...

Крім того, на жаль, цей підхід все ще є O (2 ^ n) вихідним простором, здавалося б, щонайменше, з приталення кривих 1..20.


Ви могли позбутися eval, роблячи: for first in (bwt_c, nothing, lzma, zlib, gzip, bz2):і fOut = first.compress(inputData).
kasperd

@kasperd - як би я надрукував імена в такому випадку? Особисто простіше зробити (і менш схильним до помилок) зробити eval, ніж намагатися тримати синхронізацію імен + посилань.
TLW

5
Спочатку bwt, а потім bz2 добре стискає це. Це надзвичайно дивна поведінка і, ймовірно, пов’язане з цією точною схемою. Зауважте, що ви робите bwt двічі (bz2 базується на BWT), що, як правило, призводить до гіршої компресії. Також зауважте, що bwt сьогодні зазвичай працює в 4 times block sizeпам'яті (наприклад, для цього використовується ~ 4 Мб) і на швидкостях >10 MB/s(я автор такого бібліотеки / алгоритму стиснення bwt), що досить корисно для багатьох застосувань. Зауважте, що навіть gzip дає дуже хороші стисливі результати. Дякую за те, що я поділився, мені невідомо жодне дослідження щодо використання bwt двічі.
Крістоф

3
@Christoph - Я знаю, що bz2 заснований на BWT ... Я фактично почав писати відповідь про ефект "просто використовувати bz2", але виявив, що він не стискається майже так, як я очікував, пішов "так ', і вирішив побачити, чи мій власний BWT зробить краще. Тільки мені потрібен був компресор для виходу, і я пішов "може також спробувати різні компресори, щоб побачити, що відбувається".
TLW

1
@Christoph - я поглянув ще раз. 2 bwt цих даних генерують щось, що надзвичайно піддається кодуванню RLE. Як і якщо ви порахуєте кількість пар RLE, необхідних для 0, 1, 2, ... вкладених BWT на 16-бітовому вході, ви отримаєте 622591 1081343 83 ...
TLW

10

Кодування PNG робить саме те, що ви хочете. Він працює і на даних реального життя, а не тільки на надзвичайно організовані дані.

У PNG кожен рядок кодується фільтром, 4 з яких вказано. Одне з них - "кодувати цей піксель як різницю між його значенням і значенням пікселя, що знаходиться над ним". Після фільтрації дані потім zip'd за допомогою DEFLATE.

Ця фільтрація є специфічним прикладом кодування Delta, згаданого leftaroundabout у своїй відповіді, за винятком того, що замість того, щоб виконувати його за допомогою коду тривалості виконання, ви слідуєте за ним за допомогою більш потужного алгоритму DEFLATE. Це досягає тієї ж мети, лише DEFLATE буде обробляти більшу різноманітність вхідних даних, зберігаючи бажані коефіцієнти стиснення.

Іншим інструментом, який часто використовується в наукових даних, коли простий фільтр + ВІДМОВА не є настільки ефективним, це кодування RICE. У RICE ви берете блок значень і виводите спочатку всі найзначніші біти, потім усі 2-й-найбільш значущі біти, аж до найменш значущих бітів. Потім ви стискаєте результат. Для ваших даних, які не будуть настільки ефективними, як фільтрація в стилі PNG (оскільки ваші дані ідеально підходять для фільтрації PNG), але для багатьох наукових даних це, як правило, призводить до хороших результатів. За багатьма науковими даними ми бачимо, що найзначніший біт має тенденцію змінюватися повільно, а найменш значущий - майже випадковий. Це відриває дуже передбачувані дані від сильно ентропічних даних.

0000000000       00000  most significant bits
0000000001       00000
0000000010  =>   00000
0000000011       00000
0000000100       00000
                 00000
                 00000
                 00001
                 00110
                 01010 least significant bits

5

Будь-який практичний алгоритм пошуку конкретних структур обмежився б лише твердими в ньому структурами. Ви можете виправити 7z, щоб розпізнати цю конкретну послідовність, але як часто ця конкретна структура має місце в реальному житті? Не достатньо часто, щоб гарантувати час, необхідний для перевірки даних для цього входу.

Крім практичних можливостей, ідеальний компресор можна уявити як алгоритм, який намагається побудувати найкоротшу програму, яка виробляє заданий вихід. Потрібно сказати, що практичних способів зробити це не існує. Навіть якщо ви спробували перерахувати грубі сили всіх можливих програм і перевірили, чи дають вони потрібний результат ( не зовсім божевільна ідея ), ви зіткнетеся з проблемою зупинки , що означає, що вам доведеться перервати пробні запуски після певної кількості кроків виконання, перш ніж дізнатися, чи точно ця програма не може отримати бажаний результат.

Дерево пошуку такого грубого підходу зростає експоненціально з тривалістю програми і не практичне для всіх, крім найпростіших програм (щось на зразок 5-7 інструкцій).


n

1
nnn+1n1

Ну, такі інструменти, як Mathematica, знаходять функції для багатьох послідовностей ...
Рафаель

3

Коефіцієнти стиснення повністю залежать від цільового декомпресора. Якщо декомпресор не може декодувати послідовні 4-байтні числа більш компактно, ніж 4 байти на число, то ви SOL.

Існують різні речі, які дозволять кодувати послідовні числа. Наприклад диференційне кодування. Ви берете n байтів за один раз, а потім приймаєте різницю або xor біт, а потім стискаєте результат. Сюди додаються 4 варіанти для спробу кожного байта: ідентичність a'[i] = a[i]; різниця a'[i] = a[i-1]-a[i]; зворотна різниця a'[i] = a[i]-a[i-1]; і xor a'[i] = a[i]^a[i-1]. Це означає, що для вибору методів додається 2 біта і кількість байтів для 3 з 4 варіантів.

Однак не всі дані є послідовністю записів фіксованої довжини. Диференціальне кодування для цього не має сенсу (якщо компресор не може емпірично довести, що він працює для небагато даних).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.