向極限挑戰:算術編碼 (轉)

向極限挑戰:算術編碼

(轉) http://blog.csdn.net/hhf383530895/archive/2009/08/24/4478605.aspx

我們在上一章中已經明白,Huffman 編碼使用整數個二進制位對符號進行編碼,這種方法在許多情況下無法得到最優的壓縮 效果。假設某個字符的出現概率為 80%,該字符事實上只需要 -log2 (0.8) = 0.322 位編碼,但 Huffman 編碼一定會為其分配一位 0 或一位 1 的編碼。可以想象,整個信息的 80% 在壓縮后都幾乎相當于理想長度的 3 倍左右,壓縮效果可想而知。

難道真的能只輸出 0.322 個 0 或 0.322 個 1 嗎?是用剪刀把計算機 存儲 器中的二進制位剪開嗎?計算機真有這樣的特異功能嗎?慢著慢著,我們不要被表面現象所迷惑,其實,在這一問題上,我們只要換一換腦筋,從另一個角度……哎呀,還是想不通,怎么能是半個呢?好了,不用費心了,數學家們也不過是在十幾年前才想到了算術編碼這種神奇的方法,還是讓我們虛心地研究一下他們究竟是從哪個角度找到突破口的吧。

輸出:一個小數

更神奇的事情發生了,算術編碼對整條信息(無論信息有多么長),其輸出僅僅是一個數,而且是一個介于 0 和 1 之間的二進制小數。例如算術編碼對某條信息的輸出為 1010001111,那么它表示小數 0.1010001111,也即十進制數 0.64。

咦?怎么一會兒是表示半個二進制位,一會兒又是輸出一個小數,算術編碼怎么這么古怪呀?不要著急,我們借助下面一個簡單的例子來闡釋算術編碼的基本原理。為了表示上的清晰,我們暫時使用十進制表示算法中出現的小數,這絲毫不會影響算法的可行性。

考慮某條信息中可能出現的字符僅有 a b c 三種,我們要壓縮保存的信息為 bccb。

在沒有開始壓縮進程之前,假設我們對 a b c 三者在信息中的出現概率一無所知(我們采用的是自適應模型),沒辦法,我們暫時認為三者的出現概率相等,也就是都為 1/3,我們將 0 - 1 區間按照概率的比例分配給三個字符,即 a 從 0.0000 到 0.3333,b 從 0.3333 到 0.6667,c 從 0.6667 到 1.0000。用圖形表示就是:

??????????????????? +-- 1.0000
??????????????????? |
?? Pc = 1/3??? |
??????????????????? |
??????????????????? +-- 0.6667
??????????????????? |
?? Pb = 1/3??? |
??????????????????? |
??????????????????? +-- 0.3333
??????????????????? |
?? Pa = 1/3??? |
??????????????????? |
??????????????????? +-- 0.0000現在我們拿到第一個字符 b,讓我們把目光投向 b 對應的區間 0.3333 - 0.6667。這時由于多了字符 b,三個字符的概率分布變成:Pa = 1/4,Pb = 2/4,Pc = 1/4。好,讓我們按照新的概率分布比例劃分 0.3333 - 0.6667 這一區間,劃分的結果可以用圖形表示為:

+-- 0.6667 Pc = 1/4 | +-- 0.5834 | | Pb = 2/4 | | | +-- 0.4167 Pa = 1/4 | +-- 0.3333

接著我們拿到字符 c,我們現在要關注上一步中得到的 c 的區間 0.5834 - 0.6667。新添了 c 以后,三個字符的概率分布變成 Pa = 1/5,Pb = 2/5,Pc = 2/5。我們用這個概率分布劃分區間 0.5834 - 0.6667:

+-- 0.6667 | Pc = 2/5 | | +-- 0.6334 | Pb = 2/5 | | +-- 0.6001 Pa = 1/5 | +-- 0.5834

現在輸入下一個字符 c,三個字符的概率分布為:Pa = 1/6,Pb = 2/6,Pc = 3/6。我們來劃分 c 的區間 0.6334 - 0.6667:

+-- 0.6667 | | Pc = 3/6 | | | +-- 0.6501 | Pb = 2/6 | | +-- 0.6390 Pa = 1/6 | +-- 0.6334

輸入最后一個字符 b,因為是最后一個字符,不用再做進一步的劃分了,上一步中得到的 b 的區間為 0.6390 - 0.6501,好,讓我們在這個區間內隨便選擇一個容易變成二進制的數,例如 0.64,將它變成二進制 0.1010001111,去掉前面沒有太多意義的 0 和小數點,我們可以輸出 1010001111,這就是信息被壓縮后的結果,我們完成了一次最簡單的算術壓縮過程。

怎么樣,不算很難吧?可如何解壓縮呢?那就更簡單了。解壓縮之前我們仍然假定三個字符的概率相等,并得出上面的第一幅分布圖。解壓縮時我們面對的是二進制流 1010001111,我們先在前面加上 0 和小數點把它變成小數 0.1010001111,也就是十進制 0.64。這時我們發現 0.64 在分布圖中落入字符 b 的區間內,我們立即輸出字符 b,并得出三個字符新的概率分布。類似壓縮時采用的方法,我們按照新的概率分布劃分字符 b 的區間。在新的劃分中,我們發現 0.64 落入了字符 c 的區間,我們可以輸出字符 c。同理,我們可以繼續輸出所有的字符,完成全部解壓縮過程(注意,為了敘述方便,我們暫時回避了如何判斷解壓縮結束的問題,實際應用中,這個問題并不難解決)。

現在把教程拋開,仔細回想一下,直到你理解了算術壓縮的基本原理,并產生了許多新的問題為止。

真的能接近極限嗎?

現在你一定明白了一些東西,也一定有了不少新問題,沒有關系,讓我們一個一個解決。

首先,我們曾反復強調,算術壓縮可以表示小數個二進制位,并由此可以接近無損壓縮的熵極限,怎么從上面的描述中沒有看出來呢?

算術編碼實際上采用了化零為整的思想來表示小數個二進制位,我們確實無法精確表示單個小數位字符,但我們可以將許多字符集中起來表示,僅僅允許在最后一位有些許的誤差。

結合上面的簡單例子考慮,我們每輸入一個符號,都對概率的分布表做一下調整,并將要輸出的小數限定在某個越來越小的區間范圍內。對輸出區間的限定是問題的關鍵所在,例如,我們輸入第一個字符 b 時,輸出區間被限定在 0.3333 - 0.6667 之間,我們無法決定輸出值得第一位是 3、4、5 還是 6,也就是說,b 的編碼長度小于一個十進制位(注意我們用十進制講解,和二進制不完全相同),那么我們暫時不決定輸出信息的任何位,繼續輸入下面的字符。直到輸入了第三個字符 c 以后,我們的輸出區間被限定在 0.6334 - 0.6667 之間,我們終于知道輸出小數的第一位(十進制)是 6,但仍然無法知道第二位是多少,也即前三個字符的編碼長度在 1 和 2 之間。等到我們輸入了所有字符之后,我們的輸出區間為 0.6390 - 0.6501,我們始終沒有得到關于第二位的確切信息,現在我們明白,輸出所有的 4 個字符,我們只需要 1 點幾個十進制位,我們唯一的選擇是輸出 2 個十進制位 0.64。這樣,我們在誤差不超過 1 個十進制位的情況下相當精確地輸出了所有信息,很好地接近了熵值(需要注明的是,為了更好地和下面的課程接軌,上面的例子采用的是 0 階自適應模型,其結果和真正的熵值還有一定的差距)。

小數有多長?

你一定已經想到,如果信息內容特別豐富,我們要輸出的小數將會很長很長,我們該如何在內存 中表示如此長的小數呢?

其實,沒有任何必要在內存中存儲要輸出的整個小數。我們從上面的例子可以知道,在編碼的進行中,我們會不斷地得到有關要輸出小數的各種信息。具體地講,當我們將區間限定在 0.6390 - 0.6501 之間時,我們已經知道要輸出的小數第一位(十進制)一定是 6,那么我們完全可以將 6 從內存中拿掉,接著在區間 0.390 - 0.501 之間繼續我們的壓縮進程。內存中始終不會有非常長的小數存在。使用二進制時也是一樣的,我們會隨著壓縮的進行不斷決定下一個要輸出的二進制位是 0 還是 1,然后輸出該位并減小內存中小數的長度。

靜態模型如何實現?

我們知道上面的簡單例子采用的是自適應模型,那么如何實現靜態模型呢?其實很簡單。對信息 bccb 我們統計出其中只有兩個字符,概率分布為 Pb = 0.5,Pc = 0.5。我們在壓縮過程中不必再更新 此概率分布,每次對區間的劃分都依照此分布即可,對上例也就是每次都平分區間。這樣,我們的壓縮過程可以簡單表示為:

輸出區間的下限 輸出區間的上限 -------------------------------------------------- 壓縮前 0.0 1.0 輸入 b 0.0 0.5 輸入 c 0.25 0.5 輸入 c 0.375 0.5 輸入 b 0.375 0.4375

我們看出,最后的輸出區間在 0.375 - 0.4375 之間,甚至連一個十進制位都沒有確定,也就是說,整個信息根本用不了一個十進制位。如果我們改用二進制來表示上述過程的話,我們會發現我們可以非常接近該信息的熵值(有的讀者可能已經算出來了,該信息的熵值為 4 個二進制位)。

為什么用自適應模型?

既然我們使用靜態模型可以很好地接近熵值,為什么還要采用自適應模型呢?

要知道,靜態模型無法適應信息的多樣性,例如,我們上面得出的概率分布沒法在所有待壓縮信息上使用,為了能正確解壓縮,我們必須再消耗一定的空間保存靜態模型統計出的概率分布,保存模型所用的空間將使我們重新遠離熵值。其次,靜態模型需要在壓縮前對信息內字符的分布進行統計,這一統計過程將消耗大量的時間,使得本來就比較慢的算術編碼壓縮更加緩慢。

另外還有最重要的一點,對較長的信息,靜態模型統計出的符號概率是該符號在整個信息中的出現概率,而自適應模型可以統計出某個符號在某一局部的出現概率或某個符號相對于某一上下文的出現概率,換句話說,自適應模型得到的概率分布將有利于對信息的壓縮(可以說結合上下文的自適應模型的信息熵建立在更高的概率層次上,其總熵值更小),好的基于上下文的自適應模型得到的壓縮結果將遠遠超過靜態模型。

自適應模型的階

我們通常用“階”(order)這一術語區分不同的自適應模型。本章開頭的例子中采用的是 0 階自適應模型,也就是說,該例子中統計的是符號在已輸入信息中的出現概率,沒有考慮任何上下文信息。

如果我們將模型變成統計符號在某個特定符號后的出現概率,那么,模型就成為了 1 階上下文自適應模型。舉例來說,我們要對一篇英文文本進行編碼,我們已經編碼了 10000 個英文字符,剛剛編碼的字符是 t,下一個要編碼的字符是 h。我們在前面的編碼過程中已經統計出前 10000 個字符中出現了 113 次字母 t,其中有 47 個 t 后面跟著字母 h。我們得出字符 h 在字符 t 后的出現頻率是 47/113,我們使用這一頻率對字符 h 進行編碼,需要 -log2 (47/113) = 1.266 位。

對比 0 階自適應模型,如果前 10000 個字符中 h 的出現次數為 82 次,則字符 h 的概率是 82/10000,我們用此概率對 h 進行編碼,需要 -log2 (82/10000) = 6.930 位。考慮上下文因素的優勢顯而易見。

我們還可以進一步擴大這一優勢,例如要編碼字符 h 的前兩個字符是 gt,而在已經編碼的文本中 gt 后面出現 h 的概率是 80%,那么我們只需要 0.322 位就可以編碼輸出字符 h。此時,我們使用的模型叫做 2 階上下文自適應模型。

最理想的情況是采用 3 階自適應模型。此時,如果結合算術編碼,對信息的壓縮效果將達到驚人的程度。采用更高階的模型需要消耗的系統 空間和時間至少在目前還無法讓人接受,使用算術壓縮的應用程序 大多數采用 2 階或 3 階的自適應模型。

轉義碼的作用

使用自適應模型的算術編碼算法必須考慮如何為從未出現過的上下文編碼。例如,在 1 階上下文模型中,需要統計出現概率的上下文可能有 256 * 256 = 65536 種,因為 0 - 255 的所有字符都有可能出現在 0 - 255 個字符中任何一個之后。當我們面對一個從未出現過的上下文時(比如剛編碼過字符 b,要編碼字符 d,而在此之前,d 從未出現在 b 的后面),該怎樣確定字符的概率呢?

比較簡單的辦法是在壓縮開始之前,為所有可能的上下文分配計數為 1 的出現次數,如果在壓縮中碰到從未出現的 bd 組合,我們認為 d 出現在 b 之后的次數為 1,并可由此得到概率進行正確的編碼。使用這種方法的問題是,在壓縮開始之前,在某上下文中的字符已經具有了一個比較小的頻率。例如對 1 階上下文模型,壓縮前,任意字符的頻率都被人為地設定為 1/65536,按照這個頻率,壓縮開始時每個字符要用 16 位編碼,只有隨著壓縮的進行,出現較頻繁的字符在頻率分布圖上占據了較大的空間后,壓縮效果才會逐漸好起來。對于 2 階或 3 階上下文模型,情況就更糟糕,我們要為幾乎從不出現的大多數上下文浪費大量的空間。

我們通過引入“轉義碼”來解決這一問題。“轉義碼”是混在壓縮數據流中的特殊的記號,用于通知解壓縮程序下一個上下文在此之前從未出現過,需要使用低階的上下文進行編碼。

舉例來講,在 3 階上下文模型中,我們剛編碼過 ght,下一個要編碼的字符是 a,而在此之前,ght 后面從未出現過字符 a,這時,壓縮程序輸出轉義碼,然后檢查 2 階的上下文表,看在此之前 ht 后面出現 a 的次數;如果 ht 后面曾經出現過 a,那么就使用 2 階上下文表中的概率為 a 編碼,否則再輸出轉義碼,檢查 1 階上下文表;如果仍未能查到,則輸出轉義碼,轉入最低的 0 階上下文表,看以前是否出現過字符 a;如果以前根本沒有出現過 a,那么我們轉到一個特殊的“轉義”上下文表,該表內包含 0 - 255 所有符號,每個符號的計數都為 1,并且永遠不會被更新,任何在高階上下文中沒有出現的符號都可以退到這里按照 1/256 的頻率進行編碼。

“轉義碼”的引入使我們擺脫了從未出現過的上下文的困擾,可以使模型根據輸入數據的變化快速 調整到最佳位置,并迅速減少對高概率符號編碼所需要的位數。

存儲空間問題

在算術編碼高階上下文模型的實現中,對內存的需求量是一個十分棘手的問題。因為我們必須保持對已出現的上下文的計數,而高階上下文模型中可能出現的上下文種類又是如此之多,數據結構的設計將直接影響到算法實現的成功與否。

在 1 階上下文模型中,使用數組來進行出現次數的統計是可行的,但對于 2 階或 3 階上下文模型,數組大小將依照指數規律增長,現有計算機的內存滿足不了我們的要求。

比較聰明的辦法是采用樹結構存儲所有出現過的上下文。利用高階上下文總是建立在低階上下文的基礎上這一規律,我們將 0 階上下文表存儲在數組中,每個數組元素包含了指向相應的 1 階上下文表的指針,1 階上下文表中又包含了指向 2 階上下文表的指針……由此構成整個上下文樹。樹中只有出現過的上下文才擁有已分配的節點,沒有出現過的上下文不必占用內存空間。在每個上下文表中,也無需保存所有 256 個字符的計數,只有在該上下文后面出現過的字符才擁有計數值。由此,我們可以最大限度地減少空間消耗。

資源

關于算術壓縮具體的設計和實現請參考下面給出的示例程序。

程序 Arith-N 由 League for Programming Freedom 的 Mark Nelson 提供,由王笨笨在 Visual C++ 5.0 環境下編譯、調試 通過。

Arith-N 包含 Visual C++ 工程 ArithN.dsp 和 ArithNExpand.dsp,分別對應了壓縮和解壓縮程序 an.exe 與 ane.exe。

Arith-N 是可以在命令行指定階數的 N 階上下文自適應算術編碼通用壓縮、解壓縮程序,由于是用作教程示例,為清晰起見,在某些地方并沒有刻意進行效率 上的優化 。

所有源程序包裝在文件 .NET /wangyg/tech/benben/src/Arith-N.zip">Arith-N.zip 中。

?

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/pyramide/archive/2010/01/12/5172334.aspx

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/456191.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/456191.shtml
英文地址,請注明出處:http://en.pswp.cn/news/456191.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

np.random.seed(0)作用

在用python時時常會看到如下代碼: import numpy as np np.random.seed(0) 其中np.random.seed(0)的作用是使得隨機數據可預測,當我們設置相同的seed,每次生成的隨機數相同。 如果不設置seed,則每次會生成不同的隨機數&#xf…

發送郵件被退回,提示: Helo command rejected: Invalid name 錯誤

我自己配置的 postfix dovecot server, 配置了outlook 后, 相同的賬號。 在有的電腦上能收發成功, 在有的電腦上發送郵件就出現退信。提示 Helo command rejected: Invalid name 錯誤。經過分析, 原來是計算機名的問題。 計算機名…

Series和DataFrame、相關性及NaN處理

pandas核心數據結構 pandas是以numpy為基礎的,還提供了一些額外的方法 Series series用來表示一維數據結構,與python內部的數組類似,但多了一些額外的功能。 series內部由兩個相互關聯的數組組成:主數組用來存放數組&#xff…

Hive謂詞解析過程分析

where col1 100 and abs(col2) > 0在Hive中的處理過程 where過濾條件稱為謂詞predicate。 以上where過濾條件在經過Hive的語法解析后,生成如下的語法樹: TOK_WHERE AND TOK_TABLE_OR_C…

算術編碼(Arithmetic Coding)源代碼

Ian H. Witten、Radford M. Neal和John G. Cleary在1987年發表了一篇啟發性的論文。論文中描述了一種基于整數運算的通用算術編碼器,而且還給出了由計算錯誤導致的效率低下的分析。以下源代碼來自于這篇論文:《基于算術編碼的數據壓縮》(Arit…

pandas讀寫各種類型數據

read_X()通常是pandas模塊下的,to_X()是dataframe的方法 CSV 讀取 使用pandas.read_csv()方法,返回的是一個dataframe csv默認是以","分割的 csv文件內容 1、read_csv()默認以第一行數據作為標題 2、調用dataframe的head()方法…

python 類裝飾器

1 裝飾器無參數 class tracer: def __init__(self,func): self.calls 0 self.func func def __call__(self,*args): self.calls 1 print(call %s to %s %(self.calls, self.func.__name__)) self.func(*args) tracer def spam(a, b, c): print(a b c) …

【數據分析】使用pandas和numpy分析美國大選獻金項目

1. 數據載入與總覽 1.1 數據加載 #繪圖工具 import matplotlib.pyplot as plt %matplotlib inline #數據處理工具 import numpy as np import pandas as pd from pandas import Series,DataFrame#數據路徑自己指定,本案例數據路徑就在當前文件夾下面子文件夾usa_e…

《容器技術系列》一1.4 Docker運行案例分析

本節書摘來華章計算機《容器技術系列》一書中的第1章 ,第1.4節,孫宏亮 著, 更多章節內容可以訪問云棲社區“華章計算機”公眾號查看。 1.4 Docker運行案例分析 1.3節著重介紹了Docker架構中各個模塊的功能,學完后我們可以對Docker的架構有一…

算術編碼的原理與分析

轉自:http://kulasuki115.blogcn.com/diary,201492702.shtml 前言 人類已進入信息時代,信息時代的重要特征是信息的數字化,人們越來越依靠計算機獲取和利用信息,這就需要對信息的表示、存儲、傳輸和處理等關鍵技術進行研究。我們…

3月22日AM

看了思維章節精講視頻課,并且總結了部分思維章節內容轉載于:https://www.cnblogs.com/bgd140206102/p/6601440.html

阿里巴巴Dubbo實現的源碼分析

Dubbo概述Dubbo是阿里巴巴開源出來的一個分布式服務框架,致力于提供高性能和透明化的RPC遠程服務調用方案,以及作為SOA服務治理的方案。它的核心功能包括: remoting:遠程通訊基礎,提供對多種NIO框架抽象封裝,包括“同步…

POJ 2106-Boolean Expressions,雙棧運用類似表達式求值!

Boolean Expressions 首先聲明此題后臺可能極水(畢竟這種數據不好造!)。昨天寫了一天卻總是找不到bug,討論區各種數據都過了,甚至懷疑輸入有問題,但看到gets也可以過,難道是思路錯了&#xff1f…

H264 CAVLC 研究

目錄 1 CAVLC概念 2 CAVLC原理 3 CAVLC編碼流程 4 CAVLC解碼流程 展開全部 1 CAVLC概念 2 CAVLC原理 3 CAVLC編碼流程 4 CAVLC解碼流程 收起 摘要糾錯編輯摘要 CAVLC即基于上下文的自適應變長編碼。H.264標準中使用CAVLC對4*4模塊的亮度和色度殘差數據進行編碼。 CAVLC-CAVLC…

【MySQL 】學習筆記千行總結

/* Windows服務 */ -- 啟動MySQLnet start mysql -- 創建Windows服務sc create mysql binPath mysqld_bin_path(注意:等號與值之間有空格)/* 連接與斷開服務器 */ mysql -h 地址 -P 端口 -u 用戶名 -p 密碼SHOW PROCESSLIST -- 顯示哪些線程正在運行 SHOW VARIABLES…

CCCC 連續因子

題意: 一個正整數N的因子中可能存在若干連續的數字。例如630可以分解為3*5*6*7,其中5、6、7就是3個連續的數字。給定任一正整數N,要求編寫程序求出最長連續因子的個數,并輸出最小的連續因子序列。 輸入格式: 輸入在一行…

Mybatis怎么能看是否執行了sql語句

項目需要學習mybatis中&#xff0c;本來mybatis也不是什么新技術&#xff0c;無奈之前沒接觸過。 驗證緩存機制時&#xff0c;需要能看到是否sql被執行了。這就需要增加日志的打印 配置如下 在pom中增加如下依賴&#xff1a; <dependency> <groupId>org.bgee.log4j…

定時備份 MySQL 并上傳到七牛

定時備份 MySQL 并上傳到七牛 多數應用場景下&#xff0c;我們需要對重要數據進行備份、并放置到一個安全的地方&#xff0c;以備不時之需。 常見的 MySQL 數據備份方式有&#xff0c;直接打包復制對應的數據庫或表文件(物理備份)、mysqldump 全量邏輯備份、xtrabackup 增量邏輯…

vue_props div賦值props定義變量 templete獲取

vue_props div賦值props定義變量 templete獲取 <div id"app"> <add v-bind:btn"h"></add> </div> <script> var vm new Vue({ el: #app, data: { h: "hello" }, components: { "add": { …

H.264句法和語法總結 句法元素的分層結構

在 H.264 定義的碼流中&#xff0c;句法元素被組織成有層次的結構&#xff0c;分別描述各個層次的信息&#xff0c;如下圖所示 在H.264 中&#xff0c;句法元素共被組織成 序列、圖像、片、宏塊、子宏塊五個層次。 在這樣的結構中&#xff0c;每一層的頭部和它的數據部分形成管…