什麼是熵
資料壓縮不僅起源於 40 年代由 Claude Shannon 首創的資訊理論,而且其基本原理即資訊究竟能被壓縮到多小,至今依然遵循資訊理論中的一條定理,這條定理借用了熱力學中的名詞“熵”( Entropy )來表示一條資訊中真正需要編碼的資訊量:
考慮用 0 和 1 組成的位元碼為含有 n 個符號的某條資訊編碼,假設符號 Fn 在整條資訊中重複出現的機率為 Pn,則該符號的熵也即表示該符號所需的位元位為:
En = - log2( Pn )
整條資訊的熵也即表示整條資訊所需的位元為:E = ∑En
舉個例子,對下面這條只出現了 a b c 三個字元的字串:
aabbaccbaa
字串長度為 10,字元 a b c 分別出現了 5 3 2 次,則 a b c 在資訊中出現的機率分別為 0.5 0.3 0.2,他們的熵分別為:
Ea = -log2(0.5) = 1
Eb = -log2(0.3) = 1.737
Ec = -log2(0.2) = 2.322
整條資訊的熵也即表達整個字串需要的位元為:
E = Ea * 5 + Eb * 3 + Ec * 2 = 14.855 位
回想一下如果用電腦中常用的 ASCII 編碼,表示上面的字串我們需要整整 80 位呢!現在知道資訊為什麼能被壓縮而不丟失原有的資訊內容了吧。簡單地講,用較少的位元表示較頻繁出現的符號,這就是資料壓縮的基本準則。
細心的讀者馬上會想到,我們該怎樣用 0 1 這樣的位元碼錶示零點幾個二進位位呢?確實很困難,但不是沒有辦法。一旦我們找到了準確表示零點幾個二進位位的方法,我們就有權利向無損壓縮的極限挑戰了。不要著急,看到第四章就明白了。
模型
從上面的描述,我們明白,要壓縮一條資訊,首先要分析清楚資訊中每個符號出現的機率。不同的壓縮程式通過不同的方法確定符號的出現機率,對符號
的機率計算得越準確,也就越容易得到好的壓縮效果。在壓縮程式中,用來處理輸入資訊,計算符號的機率並決定輸出哪個或哪些代碼的模組叫做模型。
難道對資訊中字元的出現機率這麼難以估計以至於有各種不同的壓縮模型嗎?對上面的字串我們不是很容易就知道每個字元的機率了嗎?是的是的,不
過上面的字串僅有 10 個字元長呀,那隻是例子而已。考慮我們現實中要壓縮的檔案,大多數可是有幾十 K 甚至幾百 K 長,幾 M
位元組的檔案不是也屢見不鮮嗎?
是的,我們可以預先掃描檔案中的所有字元,統計出每個字元出現的機率,這種方法在壓縮術語裡叫做“靜態統計模型”。但是,不同的檔案中,字元有
不同的分布機率,我們要麼先花上大量的時間統計我們要壓縮的所有檔案中的字元機率,要麼為每一個單獨的檔案儲存一份機率表以備解壓縮時需要。糟糕的是,不
但掃描檔案要消耗大量時間,而且儲存一份機率表也使壓縮後的檔案增大了不少。所以,在實際應用中,“靜態統計模型”應用的很少。
真正的壓縮程式中使用的大多是一種叫“自適應模型”的東西。自適應模型可以說是一台具有學習功能的自動機。他在資訊被輸入之前對資訊內容一無所
知並假定每個字元的出現機率均等,隨著字元不斷被輸入和編碼,他統計並紀錄已經出現過的字元的機率並將這些機率應用於對後續字元的編碼。也就是說,自適應
模型在壓縮開始時壓縮效果並不理想,但隨著壓縮的進行,他會越來越接近字元機率的準確值,並達到理想的壓縮效果。自適應模型還可以適應輸入資訊中字元分布
的突然變化,可以適應不同的檔案中的字元分布而不需要儲存機率表。
上面提到的模型可以統稱為“統計模型”,因為他們都是基於對每個字元出現次數的統計得到字元機率的。另一大類模型叫做“字典模型”。實際上,當
我們在生活中提到“工行”這個詞的時候,我們都知道其意思是指“中國工商銀行”,類似的例子還有不少,但共同的前提是我們心中都有一本約定俗成的縮寫字
典。字典模型也是如此,他並不直接計算字元出現的機率,而是使用一本字典,隨著輸入資訊的讀入,模型找出輸入資訊在字典中匹配的最長的字串,然後輸出該
字串在字典中的索引資訊。匹配越長,壓縮效果越好。事實上,字典模型本質上仍然是基於對字元機率的計算的,只不過,字典模型使用整個字串的匹配代替了
對某一字元重複次數的統計。可以證明,字典模型得到的壓縮效果仍然無法突破熵的極限。
當然,對通用的壓縮程式來說,儲存一本大字典所需的空間仍然是無法讓人忍受的,況且,任何一本預先定義的字典都無法適應不同檔案中資料的變化情
況。對了,字典模型也有相應的“自適應”方案。我們可以隨著資訊的不斷輸入,從已經輸入的資訊中建立合適的字典,並不斷更新這本字典,以適應資料的不斷變
化。
讓我們從另一個角度理解一下自適應模型。Cluade Shannon 曾試圖通過一個“聚會遊戲”(party
game)來測定英語的真實資訊容量。他每次向聽眾公布一條被他隱藏起一個字元的訊息,讓聽眾來猜下一個字元是什麼,一次猜一個,直到猜對為止。然
後,Shannon
使用猜測次數來確定整個資訊的熵。在這個實驗中,一種根據前面出現過的字元估計下一個字元機率的模型就存在於聽眾的頭腦中,比電腦中使用的自適應模型更
為進階的是,聽眾除了根據字元出現過的次數外,還可以根據他們對語言的經驗進行猜測。
編碼
通過模型,我們已經確定了對某一個符號該用多少位位元進行編碼。現在的問題是,如何設計一種編碼方案,使其盡量精確地用模型計算出來的位元表示某個符號。
最先被考慮的問題是,如果對 a 用 3 個二進位位就可以表示,而對 b 用 4
個二進位位就可以表示,那麼,在解碼時,面對一連串的二進位流,我怎麼知道哪三個位是 a,哪四個位是 b
呢?所以,必須設計出一種編碼方式,使得解碼程式可以方便地分離每個字元的編碼部分。於是有了一種叫“首碼編碼”的技術。該技術的主導思想是,任何一個字
符的編碼,都不是另一個字元編碼的首碼。反過來說就是,任何一個字元的編碼,都不是由另一個字元的編碼加上若干位 0 或 1
組成。看一下首碼編碼的一個最簡單的例子:
符號 編碼
A 0
B 10
C 110
D 1110
E 11110
有了上面的碼錶,你一定可以輕鬆地從下面這串二進位流中分辨出真正的資訊內容了:
1110010101110110111100010 - DABBDCEAAB
下一個問題是:象上面這樣的首碼編碼只能表示整數位的符號,對幾點幾位的符號只能用近似的整數位輸出,那麼怎樣輸出小數位元呢?科學家們用算術編碼解決了這個問題,我們將在第四章對算術編碼作詳細的討論。
總結一下
不同的模型使用不同的方法計算字元的出現機率,由此機率可以得出字元的熵;然後使用不同的編碼方法,盡量接近我們期望得到的熵值。所以,壓縮效
果的好壞一方面取決於模型能否準確地得到字元機率,另一方面也取決於編碼方法能否準確地用期望的位元輸出字元代碼。換句話說,壓縮 = 模型 +
編碼。如所示:
--------- 符號 ---------- 機率 ---------- 代碼 ----------
| 輸入 |-------->| 模型 |-------->| 編碼 |-------->| 輸出 |
--------- ---------- ---------- ----------
資源
我們已經知道,編寫壓縮程式往往不能對資料的整個位元組進行處理,而是要按照二進位位來讀寫和處理資料,操作二進位位的函數也就成為了壓縮程式中使用最為普遍的工具函數。我們在此提供兩組函數集,使用它們可以有效進行檔案或記憶體中的二進位位操作。它們共有六個檔案:
bitio.h - 用於檔案中二進位位操作的函數說明。
bitio.cpp - 用於檔案中二進位位操作的函數實現。
errhand.h 和 errhand.cpp - bitio.cpp 中使用的錯誤處理函數。
wm_bitio.h - 用於記憶體中二進位位操作的函數說明。
wm_bitio.cpp - 用於記憶體中二進位位操作的函數實現。
它們被共同封裝在檔案 bitio.zip 中。