資料壓縮的歷史、原理及常用演算法

Sheng Gordon發表於2015-01-21

壓縮,是為了減少儲存空間而把資料轉換成比原始格式更緊湊形式的過程。資料壓縮的概念相當古老,可以追溯到發明了摩爾斯碼的19世紀中期。

摩爾斯碼的發明,是為了使電報員能夠通過電報系統,利用一系列可聽到的脈衝訊號傳遞字母資訊,從而實現文字訊息的傳輸。摩爾斯碼的發明者意識到,某些字母比其他字母使用地更頻繁(例如E比X更常見),因此決定使用短的脈衝訊號來表示常用字母,而使用較長的脈衝訊號表示非常用字母。這個基本的壓縮方案有效地改善了系統的整體效率,因為它使電報員在更短的時間內傳輸了更多的資訊。

雖然現代的壓縮流程比摩爾斯碼要複雜地多,但是它們仍然使用著相同的基本原理,也就是我們這篇文章中將要講述的內容。這些概念對我們如今的計算機世界高效執行至關重要——網際網路上從本地與雲端儲存到資料流的一切東西都嚴重依賴壓縮演算法,離開了它很可能會變得非常低效。

壓縮管道

下圖展示了壓縮方案的通用流程。原始的輸入資料包含我們需要壓縮或減小尺寸的符號序列。這些符號被壓縮器編碼,輸出結果是編碼過的資料。需要注意的是,雖然通常編碼後的資料要比原始輸入資料小,但是也有例外情況(我們後面會講到)。
1

通常在之後的某個時間,編碼後的資料會被輸入到一個解壓縮器,在這裡資料被解碼、重建,並以符號序列的形式輸出原始資料。注意,本文我們會交替地使用“序列”和“串”來指一個符號序列集。

如果輸出資料和輸入資料始終完全相同,那麼這個壓縮方案被稱為無損的,也稱無損編碼器。否則,它就是一個有損的壓縮方案。

無失真壓縮方案通常被用來壓縮文字,可執行程式,或者其他任何需要完全重建資料的地方。有失真壓縮方案在影像,音訊,視訊,或者其他為了提高壓縮效率而可以接受某些程度資訊丟失的場合很有用處。

資料模型

資訊的定義是度量一個資料片段複雜度的量。一個資料集擁有越多的資訊,它就越難被壓縮。稀有的概念和資訊的概念是相關的,因為稀有符號的出現比常見符號的出現提供了更多的資訊。

例如,“日本的一次地震”的出現比“月球的一次地震”提供的資訊號少,因為月球上的地震很不常見。我們可以預期,大多數壓縮演算法在壓縮一個符號時,能夠仔細地考慮它出現的頻率或機率。

我們把壓縮演算法降低資訊負載的有效性,稱為它的效率。一個效率更高的壓縮演算法相比效率低的壓縮演算法,能夠更多地降低特定資料集的大小。

概率模型

設計一個壓縮方案的最重要一步,是為資料建立一個概率模型。這個模型允許我們測量資料的特徵,達到有效的適應壓縮演算法的目的。為了使它更加清晰一些,讓我們瀏覽一下建模過程的部分環節。

假設我們有一個字母表G,它由資料集中所有可能出現的字元組成。在我們的例子中,G包含4個字元:從A到D。
2

我們還有一個概率統計函式P,它定義了在輸入資料串中,G中每個字元出現的概率。在輸入資料串中,概率高的符號比概率低的符號更有可能出現。
3

在這個例子中,我們假定符號是獨立同分布的。在源資料串中,一個符號的出現與其他任何符號沒有相關性。

最小編位元速率

B是最常見的符號,出現的概率是40%;而C是最不常見的符號,它的出現概率只有10%。我們的目標是設計一個壓縮方案,它對於常見符號使所需儲存空間最小化,同時它支援使用更多的必要空間來儲存不常見符號。這個折衷是壓縮的基本原理,並且已經存在於幾乎所有的壓縮演算法中。

有了字母表,我們可以小試身手,來定義一個基本的壓縮方案。如果我們簡單地把一個符號編碼為8位元的ASCII值,那麼我們的壓縮效率,即編位元速率,將是8位元/符號。假定我們對只包含4個符號的字母表改進這個方案。如果我們為每個符號分配2個位元,我們仍然能夠完全重建編碼過的資料串,而只需要1/4的空間。

這時候,我們已經顯著地提升了編位元速率(從8到2位元/符號),但是完全忽視了我們的概率模型。正如前面提到的,我們可以結合模型發明一個策略,通過對常見符號(B和D)使用更少的位元,對不常見符號(A和C)使用更多的位元,以提高編碼效率。

這提出了一個在夏農開創性論文中描述的重要觀點——我們可以簡單地基於符號(或事件)的概率,定義它的理論最小儲存空間。我們如下定義一個符號的最小編位元速率
4

例如,如果一個符號出現的概率是50%,那麼它絕對最少需要一個位元組來儲存。

熵和冗餘

更進一步,如果我們為字母表中的字元計算最小編位元速率的加權平均值,我們得到一個被稱作夏農熵的值,簡單地稱作模型的。熵被定義為給定模型的最小編位元速率。它建立在字母表和它的概率模型之上,如下描述。

5
6

正如你預料的一樣,擁有更多罕見符號的模型,比擁有較少並且常見符號的模型的熵要高。更進一步,熵值更高的模型比熵值低的模型更難壓縮。

7

在我們當前的例子中,我們模型的熵值是1.85位元/符號。編位元速率(2)和熵值(1.85)的差值被稱作壓縮方案的冗餘

8

在眾多諸如加密和人工智慧等不同的子領域,熵都是一個非常有用的話題。完整地討論熵不在本文的範圍內,但是有興趣的讀者可以在這裡獲得更多的資訊。

編碼模型

到目前為止,我們採取了一點點自由措施:自動地給出了我們符號的概率。在現實中,模型通常並不是容易得到的,我們可能通過分析源資料串(如在樣例資料彙總統計符號概率),或者在壓縮過程中自適應地學習,以得到這些概率值。不管是哪種情形,真實資料串的概率值不會完美地與模型匹配,而且我們會與這個差別正比例地損失壓縮效率。基於這個原因,推匯出(或恆定地保持)一個儘可能精確的模型是至關重要的。

常見演算法

當我們為資料集定義了概率模型之後,我們就能夠適當地利用這個模型設計出一個壓縮方案。雖然開發一個新壓縮演算法的過程超出了本文的範圍,但是我們可以利用已經存在的演算法。下面我們回顧一些最流行的演算法。

下面的每一個演算法都是一個順序處理器,這就是說如果要重建已編碼序列的第n個符號,必須先對第0..(n-1)個符號進行解碼。由於編碼後資料的不定長特性,尋找操作是不可能的——解碼器在不解碼前面的符號的情況下,無法直接跳轉到符號n的正確偏移位置。另外,一些編碼方案依賴於順序處理每個符號時保持的內部歷史狀態。

• 霍夫曼編碼

這是一個最為廣泛知曉的壓縮方案。它能夠追溯到19世紀50年代,David Huffman在他的論文“一種構建極小多餘編碼的方法”中第一次描述了這種方法。霍夫曼編碼通過得到給定字母表的最優字首碼工作。

一個字首碼代表一個數值,並使字母表中的每個符號的字首碼不會成為另一個符號字首碼的字首。例如,如果0是我們第一個符號A的字首碼,那麼字母表中的其他符號都不能以0開始。由於字首碼使位元流解碼變得清晰明確,因此很有用。

對給定字母表得到最優字首碼的過程(霍夫曼編碼的真髓)不在本文的範圍之內,但是我們可以計算一下例子中字母表G的字首碼的效率。假設我們已經對字母表中每個符號做了如下編碼。注意我們對常見符號賦予了更短的編碼,對不常見符號賦予更長的編碼。

9

使用這個系統,我們的平均編位元速率顯著地降低到了1.9位元/符號,相比之前最好的編位元速率2,而冗餘也降低到了0.05位元/符號(相比0.15)。

10

 

推薦閱讀:霍夫曼編碼壓縮演算法

• 字典方法

這種型別的編碼器使用一個字典來儲存最近發現的符號。當遇到一個符號時,首先會在字典中查詢它,檢查是否已經儲存過了。如果是,那麼輸出將只包含字典入口的引用(通常是一個偏移量),而不是整個符號。

使用字典方法的壓縮方案包括LZ77 and LZ78,它們是很多不同的無失真壓縮方案的基礎。

在一些情況下,會使用一個滑動視窗來自適應地追蹤最近發現的符號。這種情況下,一個符號只在相對較近發現時才會儲存在字典中。否則,符號被剔除(之後再出現可能會重新加入字典)。這個過程防止符號字典變得過大,並利用了一個事實,即序列中的符號會在相對短的視窗內重複出現。

• 哥倫布指數編碼

假設你有一個由0到255範圍內的整陣列成的字母表,並且一個符號的出現概率與它到0的距離有關。這樣,比較小的值是最常見的,而值越大出現的概率越小。

對於這種情形,哥倫布指數編碼器會很有用處。哥倫布編碼使用一個特定的字首碼,它優先考慮較小的值,而使大值付出高的代價。下面的表說明了最開始的幾個值:

11

對一個整數進行編碼的過程是很直接的。首先,遞增這個整數的值,並計算儲存遞增後的值所需的位元數。然後,將位元數減一,並把減一後相等數量的0輸出到結果流。最後,輸出遞增後的值(第一步中計算得到的)按位元輸出到結果流。

例如,4遞增後是5,即二進位制的101。它需要3個位元來儲存,因此我們輸出2個0到結果流,然後輸出二進位制的值101。結果就是00101。

和大多數壓縮方案一樣,哥倫布編碼的效率非常依賴於輸入序列中的特定符號。包含很多大值的序列與包含較少大值的序列相比,壓縮效果更差一些;在某些情況下,經過哥倫布編碼後的序列甚至可能比原始輸入串的尺寸更大。

• 算術編碼

算數編碼是一個比較新的壓縮演算法,在最近(過去的15年裡)得到了極大的普及,特別是媒體壓縮方面。算數編碼器是一種高效率,計算密集型,具有時序性的編碼器。

一個常見的算數編碼變種,二進位制算數編碼,使用只包含兩個符號(0和1)的字母表。這個變種特別有用處,因為它簡化了編碼器的設計,降低了執行時的計算代價,並且在編碼器和解碼器處理一個字母表和模型時,不需要任何顯式的通訊。

要獲得更多關於算數編碼的資訊,請關注我即將發表的文章,Context Adaptive Binary Arithmetic Coding。

• 行程長度編碼(RLE)

到現在為止,我們已經假設源符號是獨立同分布的。我們的概率模型和編位元速率與熵的計算方法都依賴於這個事實。但是,如果我們的符號序列不滿足這個要求呢?

假設我們序列中符號的重複度很高,並且一個特定符號的出現有力地表明,它的重複例項即將跟隨出現。這種情況下,我們可以選擇使用另一個稱作行程長度編碼的編碼方案。這種技術在符號重複度很高時表現良好,而在重複度低時表現較差。

行程長度編碼器預測資料串中連續重複符號的長度,並使用這個符號和重複次數來替代它們。

例如,序列AABBBBBBBDDD包含重複的A,B和D,使用行程長度編碼後的序列為A2B7D3,因為A被重複2次,B被重複7次,而D被重複3次。重建時,解碼器會根據資料串中的重複次數來重複每個符號,從而得到原始的輸入串。

正如我們前面提到的,這個演算法適合重複度高的資料串。但是要注意當對很少重複的序列編碼時會發生什麼:ABCDABCD會變成A1B1C1D1A1B1C1D1,與原始資料相比更差了。和其他大多數壓縮演算法相同,行程長度編碼的效率嚴重依賴與原始資料串與演算法模型的符合程度。在一些極端情形下,行程長度編碼可能產生2倍與原始輸入資料長度的壓縮結果。

推薦閱讀

有失真壓縮

雖然有失真壓縮不在本文中的討論範圍內,但是需要重點指出的是,有失真壓縮經常把無失真壓縮作為壓縮管道的一部分。有失真壓縮可能通過2個過程來完成:首先大幅度壓縮資料(拋棄不需要或者多餘的資訊),然後再通過無失真壓縮演算法進行壓縮。流行的圖形和視訊編碼,如JPEG和H.264,都是這樣做的,依賴無失真壓縮演算法如霍夫曼編碼或者算數編碼來達到高效壓縮的效果。

總結

本文聚焦於無失真壓縮技術,並對一些最流行的技術提供了一個簡明的介紹。希望它已經激起了你對於資料壓縮重要領域的興趣,併為這個主題的進一步閱讀提供方向。

相關文章