Managing Gigabytes--文本压缩
開門見山,文本壓縮可以歸納為兩大類, 符號方法和字典方法, 下面分別介紹下:
1)符號方法,symbolwise method
普通編碼方式是每個字符都采用相同位數編碼, 比如asc碼, 每個字符都是8位編碼。
那么現在要壓縮,就是要用更少的位數來表示字符。顯而易見, 我們只須用較小的位數來表示高概率字符, 用較長的位數來表示低概率字符,這樣平均下來就可以實現壓縮。
那么這里面就有兩個點:
a)怎么來確定每個待編碼字符的概率,這就是概率模型問題。
所謂概率模型就是為編碼器提供的概率分布函數,我們必須保證編碼器,和解碼器使用相同的模型, 不然解出來的就不對了。
那么對于一個符號的編碼位數是有個下限的, 和這個符合的信息量I(s)相關。
I(s)= -logPr[s] (Pr[s]就是s符合出現的概率)
所以對于”擲硬幣面向上“這個事實,至少要用-log(1/2)=1位來編碼。
那么對于字母表中每個符合的平均信息量稱為”概率分布的熵(entropy)“, H = sum(Pr[s]*I[s])
假定符合以假設的概率值獨立出現,H為壓縮下限。即這是個理論極值,實際上不可能達到。
這也就是Claude Shannon著名的編碼原理。
回到模型,可分為每個字符都當獨立的符號來處理的0階模型和僅考慮有限個前驅符號的m階有限上下文模型。
也可分為靜態模型, 半靜態模型, 和自適應模型。
靜態模型就是不考慮正在編碼的文本,只使用固定的模型,這個模型就適用于文本模式相對固定的任務。
半靜態模型就先為要壓縮的文件生成模型, 發送給解壓方, 這個方法需要遍歷兩遍被壓縮的文檔, 所以這個solution明顯不太好。
所以比較流行的是自適應模型,adaptive modeling,即以平緩的概率模型開始, 隨著遇到的符號變多, 不斷的調整。
自適應模型往往要解決零頻問題,這個問題解決方法很多, 最簡單的是默認每個字符在剛開始時已出現過一次。
這種模型的缺點是不能對文本進行隨機訪問, 必須從文本的開頭開始解碼,因為我們必須保證編碼和解碼時的context是相同的。
對于怎么建立符號概率模型, 也是有很多研究, 比如部分匹配模型(Prediction by Partial Matching, PPM)依據以前的字符做出預測, 動態馬爾可夫模型基于有限狀態機。
這些模型就不詳細描述了, 有興趣可以參看相關文獻。
b)知道待編碼字符的概率,怎么給它分配合適的編碼, 這就是編碼算法的問題。
哈夫曼編碼
介紹編碼算法當然先來看看哈夫曼編碼, 這個號稱是作者在研究生時為了避免參加科目考試而想出的算法,很簡單也很高效。再一次對美國的教育環境表示深深的敬意和向往。
這類算法基本想法就是給高概率字符分配較少位數的編碼。
這就有個問題, 如果每個字符的編碼位數不一樣, 那么我們怎么知道后一個字符的編碼位數,肯定不能用個len去指定。
哈夫曼的方法是解決編碼前綴的歧義性(ambiguity), 即前綴編碼,就是說每個編碼的前綴都是不同的
那么怎么去產生編碼和怎么去解碼呢?
哈夫曼樹,通過哈夫曼樹,從下到上進行編碼, 從上到下進行解碼。具體怎么構建哈夫曼樹, 就不具體說了, 很簡單。
再一次感嘆此算法構思的巧妙和簡單。
對于靜態的概率分布, 哈夫曼算法是很好的, 但對于自適應模型,哈夫曼算法會耗費較多的內存或時間。
因為自適應模型在同一時間會使用許多不同的概率分布,依賴于被編碼文本的上下文的不同。那么這樣就要同時建立多顆哈夫曼樹。
?
算術編碼
那么對于自適應模型而言算術編碼更受歡迎。
算術編碼相對比較復雜,不過顯著的優勢是算術編碼可以用低于一位的編碼來表示高概率字符, 而哈夫曼編碼對于每個字符至少要用一位來編碼。
由于算術編碼包含了各種不同的概率分布, 所以比較適合自適應模型。
但對于靜態模型,還是哈夫曼編碼要快許多。
算術編碼的基本思想就是, 在編碼過程中, 隨著各個字符的出現不斷縮小概率區間, 高概率字符不會大幅地縮短區間,而低概率字符會導致產生一個小的多的‘下一個’區間。
算術編碼只有當編碼完成時,才會一次性輸出編碼,編碼值就是在最終的概率區間內任意選取一個值,區間越小,表示這個值所用的位數越多。
不好理解,就舉個例子:
對于只有3個字符的字符集a,b,c, 對bccb進行編碼
為解決零頻問題, 開始假設a,b,c個出現過一次
編碼前概率區間為[0,1],此時abc的概率都是1/3,所以abc各占概率區間的1/3, 如b的概率區間為[0.33, 0,66]
開始編碼......
第一個字符b,所以概率區間縮短為[0.33, 0,66]
此時a:b:c的比例為1:2:1,所以各個字符的概率區間變為a = [0.333,0.416], b = [0.416, 0.583], c = [0.583, 0.666]
第二個字符c,所以概率區間縮短為[0.583, 0.666]
此時a:b:c的比例為1:2:2,所以各個字符的概率區間變為a = [0.583,0.600], b = [0.600, 0.633], c = [0.633, 0.666]
第三個字符c,所以概率區間縮短為[0.633, 0.666]
此時a:b:c的比例為1:2:3,。。。。。。
最終概率區間縮小至[0.639, 0.650]
所以0.64就可以作為bccb的編碼。
而解碼的過程就是照著編碼的過程重做一遍即可,
解碼前概率區間為[0,1],此時abc的概率都是1/3,所以abc各占概率區間的1/3, 如b的概率區間為[0.33, 0,66]
所以0.64落在了b的概率區間內, 第一個字符為b
概率區間縮短為[0.33, 0,66], 此時a:b:c的比例為1:2:1,所以各個字符的概率區間變為a = [0.333,0.416], b = [0.416, 0.583], c = [0.583, 0.666]
所以0.64落在了c的概率區間內, 第二個字符為c
。。。。。。最終完成解碼。
區間中的數需要的多少位來表示和區間長度的負對數成正比。而最終的區間長度是已編碼符合概率值的乘積(顯而易見)。
而log(AB)= log(A)+log(B), 所以最終的區間長度的對數就等于所有已編碼符號單獨概率值的對數的和。所以具有概率Pr[s]的符號s對輸出編碼的貢獻是-logPr[s], 與符號信息量相同。
所以算術編碼的輸出位數接近最優。
2)字典方法,dictionary method
字典模式很好理解, 用字典里面的碼字來替換原文,如果這個碼字比原文的位數少,那么就實現了壓縮, 如果你的字典是獨有的, 不公開的, 那么就實現了加密。
那么為了實現壓縮, 就要基于詞或短語進行編碼, 基于字符壓縮效果肯定不好, 那么如果這個字典是靜態的,因為各個領域的詞和短語都不一樣的, 所以不可能適用于所有文本。
也有半靜態的,就是每次對要壓縮的文本生成一個字典,這個方法肯定也不好。
所以就有自適應的字典方案(adaptive dictionary scheme), 這個就有技術含量了怎么產生自適應的字典了。
當然牛人是很多的,Ziv和Lempel發明了LZ77和LZ88兩種方法。
牛人想出來的方法都是很簡單,很好用的, 這個也不例外,基本原理就是文本的子串被指向其以前出現之處的指針所替換。
說白了,就是字典碼書就是文本本身, 碼字就是指針。個人感覺,在發明這個方法時, 作者可能并沒有想到什么基于字典模式的方法,應該是后面的學者把它歸到這類了。
我們就以LZ77來了解下這個方法,
LZ77的輸出碼是一系列三元組,(a,b,c), a表示往前回溯多遠, b表示短語多長, c表示要輸入的下一個字符。
為什么要有c項,是為沒有出現過的字符準備的, 這個設計照顧了新字符引進的需要。
以abaabab為例,輸出碼為:
(0,0,a) (0,0,b) (2,1,a) (3,2,b)
這個方法需要在之前的文本中找到最大匹配窗口, 這個不能線性查找, 一般采用散列表, 或二分查找樹來實現。
著名的Gzip就是這個算法的一個變體,
Gzip用hash table來定位之前出現的字符串, 用3個連續的字符作為hash鍵值,鏈表中記錄了這三個字符在窗口中出現的位置信息。
出于效率的考慮,限制了鏈表的長度, 其實也沒有必要記錄過遠的位置, 記較近的幾個位置比較合理。
Gzip比較有意思的是,對指針的偏移值也做了哈夫曼編碼,較常用的值用較少的編碼。同時對字符串匹配長度值, 也采用哈夫曼編碼。
當之前沒有發現匹配的時候, 傳遞一個原始字符(raw character),有意思的是這個原始字符和字符串匹配長度值公用同一個編碼。
也就是說第二項有可能是個字符串匹配長度值, 也有可能是個原始字符,反正前綴編碼, 也不沖突, 用心良苦都是為了壓縮。
轉載于:https://www.cnblogs.com/fxjwind/archive/2011/07/04/2097718.html
總結
以上是生活随笔為你收集整理的Managing Gigabytes--文本压缩的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle B-Tree Index
- 下一篇: js数字相加