1. 程式人生 > >文本壓縮算法的對比和選擇

文本壓縮算法的對比和選擇

進行 適應 單純 sed 做的 window fonts 其它 性能

在數據壓縮領域裏,文本壓縮的歷史最久,從Morse到Huffman和算術編碼(Arithmetic coding),再到基於字典和上下文的壓縮算法。各種算法不斷改進,從通用算法,到現在更具針對性的算法,結合應用場景的垂直化的趨勢越來越明顯。所以在選擇或者評價壓縮算法,一定要結合實際應用場景加以考慮,包括字符集、內容的大小、壓縮及解壓的性能、以及各端支持情況

數據壓縮算法

一套完整的壓縮算法,實際以下幾個部分:
技術分享

其中除編碼外的三項目的都是找到一個適於編碼的表示方法,而編碼則是以簡化的方法進行輸出。最典型的建模方法是基於字符的概率統計,而基於上下文的建模方法(Context Modeling)則是從文本內容出發,它們追求的目標都是讓字符的出現概率越不平均越好。轉換方法是最具代表性的是基於詞典的轉換,比如龐大的LZ族系。Huffman和算術編碼則是常見的編碼方法。

因為語言本身的特性,基於上下文的建模方法(Context Modeling,如PPM*系列算法)可以得到更好的壓縮比,但卻由於它的性能問題卻很普及。當前比較流行的壓縮算法中其突破的核心只有兩個:
* ANS (FSE是它的一個實現): Facebook zStd, Apple的lzfse等
* Context Modeling + LZ77 (編碼是Huffman): Brotli

下圖為六種算法的壓縮比測試的結果,分別針對一本英文小說,一本中文小說,和一份較小(4KB+)的中文混合的JSON數據。
技術分享
*其中PPM是Context Modeling的代表。
可以看到算法對字符集(中文與英文)和大小都是敏感的,表現各不相同。

算法思想的簡單概括

Huffman編碼受到了Morse編碼的影響,背後的思想就是將最高概率出現的字母以最短的編碼表示。比如英文中字母e出現概率為12%,字母z的出現概率還不到1%。

算法編碼以及區間編碼,它們是利用字符概率分布,將字符組合轉變為概率的層次劃分,最終轉換一個固定的數字(算術編碼和區間編碼最大差別就在於一個使用小數,另一個使用整數)。可以對應下圖考慮下AAAA,以及AAB的編碼輸出 (在0-1的軸上找到一個數字來表示。)。
技術分享

上面這兩類算法一直霸占著算法編碼領域,各種擁有大量的變形。從實用效果上,算術編碼的壓縮比一般要好於Huffman。但Huffman的性能要優於算術編碼,兩者都有自適應的算法,不必依賴全文進行概率統計,但畢竟算術編碼還是需要更大的計算量。

ANS是前兩類編碼算法戰爭的終結者。它在2014年被提出來,隨後很快就得到了大量應用。本質上屬於算術編碼,但它成功地找到了一個用近似概率表示的表格,將原來的概率計算轉換為查表。所以它是一個達到Huffman編碼效率的算術編碼方法。FSE(Finite State Entropy)是ANS最為著名的實現。

之前提過建模方法,要追求字符出現概率的不平均。比如動態馬爾可夫壓縮(DMC, dynamic Markov coding)。仍以英為例,全文來看,字母e出現概率最高,但是在首字母這種狀態下,字母t的出現概率可以接近17%。就是字母在是否首字母的兩種狀態下是有兩種概率分布的:
技術分享
*非首母下,各字母的出現概率有可能還有變化。

基於上下文的建模,可以想象下成語填空或者詩詞填空,其中有一個條件概率的問題。在下圖中,如果單個字符看,我們只能從整體漢字的概率分布來考慮後面的字。如果從詞的角度發出,後面確實可能會出現幾個概率較大的單字,這是一階上下文。再進一步,如果之前出現過單字,這是二階上下文(2nd order),後面文字出現的概率又會發生變化。如果再往前取一個單字又是,那麽不單是後面單字的概率極高,而且再往後有三個字的出現概率也是奇高的。在隨後編碼時,我們就可以為它找出一個最短的表示。
技術分享
實際應用不可能讓算法先要理解文字,絕大部分情況也不可能為此先建一個語料庫,即使有語料庫,其處理性能仍然會是很大的問題。其實我們也不需要得到極為精確的相關性,只要快速掌握到一定的模式就足夠了。好像學生根本不需要做大規模的訓練,就能很快註意到老師的口頭禪或者常用語,進而學得有模有樣。所以可行的算法只需要基於一部分內容的上下文進行預測,這就是PPM(部分匹配預測,prediction by partial match)以及各種演進版本。

轉換(Transform)裏詞典方法很好理解,就不介紹了,已經是應用最為廣泛技術。而文本壓縮基本目標是無損壓縮,所以去冗余這項我們最後再談。

內容字符集與大小的影響

我們思考一個問題:為什麽各種算法對內容字符集和大小的效果不同?

字符集的差異很好理解,由前面提到的信息熵決定。有很多人在連續很多年都討論過這個問題,也有非常精確的漢字信息熵的評價。單純從當前編碼的角度來看,就是作為中文基礎單元的單字數遠比英文中基本字母多得多。大的字符集很自然就使得字符間的概率差異較小,所以此時Huffman和算術編碼這類依賴於概率統計的算法對於中文壓縮都遠不及對英文壓縮的效果。

回頭再看下上面算法對比結果。當純英文時,算術編碼壓縮比優於Huffman。遇到大字符集的中文時,基本和Huffman一樣了。這兩個算法在這個測試場景沒有拉來明顯差距,可以動手換個二進數據再對比一下。

如果遇到中英文混合,且內容較小的JSON數據時,算術編碼就歇菜了。

內容大小的影響是來自於算法本身的學習成本。從統計的角度看,內容大小代表了樣本的多寡,會直接影響統計結果。以基於概率統計的算法為例,如果極少字符,有利於編碼。但同時沒有足夠的數據量,字符間沒法形成概率分布的差異,又不利於編碼。兩者共同決定最終了壓縮比。

在測試數據裏基於上下文建模的PPM表現明顯優於Huffman和算術編碼。說明雖然僅僅在局部理解字符出現的相關性,就已經能夠很好地優化效果了。即使是小數據,也遠高於前面兩類的效果。

到這裏,請思考一個問題:

  • 什麽方法能降低壓縮算法的學習成本?

那麽我們需要先定義:

  • 字符集是什麽?它的規模多大?字符的概率分布如何?
  • 內容會多大?
    *效率和費用的問題,我們不討論了。
    這個時候轉換就能發揮最大的功效了,特別預設字典類的算法。

下面是一更為具體的問題,也可以練習一下:
如果我們要設計一個極短文本(如100字以內)的壓縮算法,什麽會是最為效的算法?

Brotli與zStd的對比和選擇

在Web應用場景下,壓縮算法要追求的是更小且更快(對於流式數據,還有吞吐量的要求,另有算法應對),算法主要在壓縮比和性能之間尋求平衡。目前頁面上的各種文本資源主要還是使用gzip壓縮,自zStd和Brotli推出後,我們該如何選擇呢?

如果你只想了解一下兩者的選擇,可以跳過下面關於兩者對比的一節。

Brotli與zStd的對比

Brotli算法可以理解為LZ* + Context Modeling + Huffman編碼。這是一個極為完整的無損壓縮算法,三個部分全涉及到了。它的compression level分為11級,一般測試而言它的level 5可以達到gzip 9的效果。主要特色包括:

  • 詞典的Sliding Window擴充到16MB
  • 針對6國常用詞及HTML&JS常用關鍵詞的靜態(預置)詞典
  • 基於二階上下文建模 (2nd order context modeling)
    註意context modeling的性能很弱,如果對壓縮性能要求不高(如本地PC壓縮),可以把level調到6以上測試下。目前Chromium-based的瀏覽都默認支持Brotli。

zStd是基於FSE(ANS的一個實現),針對MB級別以上的數據,最為有效。 但是對於小數據,它特別提供一個詞典方法(回顧下前面關於小數據壓縮的問題)。 這個方法需要針對目標數據做訓練,就能生成這個詞典。使用的方法如下:

1) 詞典訓練
zstd --train FullPathToTrainingSet/* -o dictionaryName

2)  壓縮
zstd -D dictionaryName FILE

3) 解壓
zstd -D dictionaryName --decompress FILE.zst
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

這種方法的一個局限就是應用的範圍,需要既要有相對穩定的內容,又要有客戶端的支持。

Brotli和zStd的選擇

如果兩個可以同時選擇時:
較大的文本數據,選擇zStd。較小的數據,選擇Brotli或者zStd的詞典模式。
大和小的邊界需要根據內容測試來定義。500KB左右,zStd的表現一般會超過Brotli。

不要使用默認值對比。
Brotli默認的compression level是它的最大值,即11。而zstd默認是3, gzip默認為6。

以下是使用UC頭條的33條數據,大小在3~26KB,包括HTML和JSON數據,在Mac Pro上做的對比測試。
針對性能,直接使用工具對每個文件調用一次,最後加總時間的方法進行測試,並不是工具批次處理功能,所以存在一定的優化空間,特別是Brotli這種需要加載預置詞典的算法。

從完成全部文件壓縮所用時間上看,zstd/brotli對比gzip的耗時都有一定的增加。zstd-10和brotli-6基本接近:
技術分享

但從壓縮比上看,brotli-6的效果就要優於zstd-10了:
技術分享

算法選擇需要考慮的因素

所以本文寫到這裏,可以得出結論只有一個,選擇一個文本壓縮算法最有效的方式是實驗,而且不要輕人別人的測試結果,如果不知道為什麽?麻煩回到最上方,重新讀一遍。只能通過實驗,才能得出一個更為有效的算法以及參數的選擇。

  • 壓縮比 確定出目標內容,並按涉及的字符集和大小,按組合和梯度做好測試。
  • 壓縮性能 & 解壓性能 執行壓縮和解壓的設備各是什麽?性能帶來的延遲影響有多大?
  • 各端支持的情況 特別是客戶端、瀏覽器上的支持情況。
  • 內容動態性 即目標內容變化頻率。比如實時壓縮,且吞吐量較大,你就要評估下LZ4算法。其它情況要用好緩存和CDN。
  • 專利費用 比如算術編碼是用專利費用的,而區間編碼則是免費的。

文本的有損壓縮?

開篇也提到了針對場景的壓縮算法研究是目前的趨勢,特別是找到更有效的建模方法。傳統意義上理解數據壓縮,包括文本壓縮都應當是無損的。但其實也有一種場景是需要有損壓縮的。

比如早期電報,惜字如金。在現在的場景下,內容的概要處理就是有損壓縮的一個典型需求。內容的大爆發之後就有內容的進一步管理和挖掘的問題。內容的概要處理一是方便展示,二是方便檢索和歸類。

借助現在NLP領域的成果,我們可以很輕松地對句子結構和成分進行分析。可以在句子中定位出介詞短語(PP),量詞短語(QP),動詞短語(VP),又或者名詞短語(NP)等。基於這個結果,可以定位出句子的主要成分,然後把修飾成分作為冗余去掉(去冗余)。

以句子從樂視危機、易到事件,再到資金凍結、機構撤資,最近半年,樂視就像一部跌宕起伏的電視劇,作為樂視的創始人,賈躍亭一次又一次被推上風口浪尖。為例:
技術分享

我們去掉修飾短語,使用圖中標出的名詞及動詞短語,就很容易得到核心成份:
賈躍亭被推上風口浪尖。

示例使用R及Standford CoreNLP包在Mac OS下完成,源碼在GitHub上。

雖然目前這種處理的性能還是達到產品化要求,但相關的應用已經近在眼前。

文本壓縮算法的對比和選擇