轉載請標明出處:http://www.cnblogs.com/zblade/
一、概要
在項目開發中,有引入用到rANS熵編碼壓縮算法,在使用的背后,想看看其運行的基本原理,也算補一下個人的熵編碼知識。這里提到的熵編碼壓縮算法都是無損壓縮。很久沒有寫文章了,太忙了,不知道一年一篇文章算不算年更 :b
二、熵編碼
目前較為成熟的熵編碼是霍夫曼編碼,算術編碼,以及14年Duda提出的ANS(Asymmetric Numeral Systems 非對稱數系)編碼。先解釋一下霍夫曼編碼和算術編碼,然后重點說一下ANS編碼的原理。
2.1 香農熵編碼
熵在編碼中,是對信息的衡量,熵越大,表明所包含的信息越多。對於高頻出現的事件,其本身包含的信息其實是不多的,所以其對應的熵更小。而低頻出現的事件,其包含的信息更多,對應的熵更大。香農的熵編碼理論值計算公式為:
...(1)
2.2 霍夫曼編碼
霍夫曼編碼是速度最快的熵編碼,其基本原理是基於統計的頻率,構建二叉樹,最后高頻率的字符用最短的編碼表示,最低頻率的字符用最長的編碼來表示。其基本的操作就是不斷構建二叉樹的過程,借鑒示例用圖1:

基本操作就是取頻率最低的2個字符,搭建一顆二叉樹,然后根節點頻率為葉子節點之和,如此遞歸,得到最終的二叉樹,示例中的編碼結果:
a: 0
b: 10
c: 110
d: 111
用編碼替換輸入的字符,即可得到最終的編碼結果。 霍夫曼編碼總結就是2個操作:構建霍夫曼樹,執行霍夫曼編碼。霍夫曼是執行速度最快的熵編碼,但是其不能無限接近熵編碼的理論值。
2.3 算術編碼
算術編碼是一種無限接近熵編碼理論值的編碼,其本質操作就是用一個[0, 1)的小數來表示最終的編碼結果,其基本操作也是基於統計來進行的,用示例圖來表示最直觀[2]:

當前編碼的字符為ABC三種字符,如何編碼“BCCB”這個字符?
1)設定初始頻率值,三種字符均值分布,則均為1/3,划分初步的概率分布;
2)輸入B,其位於[0.333,0.667),則以此區間進行下一次划分,這是各個字符出現的頻率進行更新,分別為1/4, 2/4, 1/4,得到最新的區間划分;
3)依次遞推,最后編碼所在區間為[0.639,0.6501),輸出這個區間內的一個小數,例如0.64,轉換為對應的二進制數即為最終的編碼結果。 算術編碼是一種能夠接近理論值的熵編碼,對應的代價就是算術的過程,速度慢。
三、ANS熵編碼
項目中用到的編碼是最近幾年提出的一種新的熵編碼,本着查看原理的心理去探究了一下這種最新的編碼,很多文章都說的較為晦澀,不是我這樣的小白能夠理解的。在偶然拜讀到一位國外大佬的文章后,通過詳細的推導,總算大致了解了基本的實現原理,這里推薦有時間的可以看這篇英文原文:
Lossless Compression with Asymmetric Numeral Systems 結合這篇文章,我大致講講個人的理解:
3.1 將二進制字符串編碼成自然數
從最簡單的編碼開始,假設一個字符串是以0/1字符串組成,如果用進制轉換,我們都知道如何將其轉換為10進制數。讓我們展開來看:
假設我們已經轉換了二進制字符串
, 其對應的數值為
,如果我們得到一個新的輸入字符
,我們希望基於一個基本的編碼函數來得到輸出的數值,假設為:
....(2)
基於離散數學教程,如果還記得的話,這個公式是這樣:
...(3)
為了區別"0"、“00”等情況,我們設定初始值:
, 反過來,我們可以從一個10進制數轉換成對應的二進制字符串,其對應的函數可以表示為:
...(4)
舉例來說明:
基於公式3和4,將字符
轉換成一個十進制數,
, 那么其轉換操作為:
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzErJTNEK0MlMjh4XzAlMkMrYl8xJTI5KyUzRCsyeF8wKyUyQitiXzErJTNEKzIlMjgxJTI5KyUyQisxKyUzRCsz.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzIrJTNEK0MlMjh4XzElMkMrYl8yJTI5KyUzRCsyeF8xKyUyQitiXzIrJTNEKzIlMjgzJTI5KyUyQiswKyUzRCs2.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzMrJTNEK0MlMjh4XzIlMkMrYl8zJTI5KyUzRCsyeF8yKyUyQitiXzMrJTNEKzIlMjg2JTI5KyUyQiswKyUzRCsxMg==.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzQrJTNEK0MlMjh4XzMlMkMrYl80JTI5KyUzRCsyeF8zKyUyQitiXzQrJTNEKzIlMjgxMiUyOSslMkIrMSslM0QrMjU=.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzUrJTNEK0MlMjh4XzQlMkMrYl81JTI5KyUzRCsyeF80KyUyQitiXzUrJTNEKzIlMjgyNSUyOSslMkIrMSslM0QrNTE=.png)
其對應的解碼過程為:![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD0lMjh4XzQlMkMrYl81JTI5KyUzRCtEJTI4eF81JTI5KyUzRCslMjglNUNsZmxvb3IlNUNmcmFjJTdCeF81JTdEJTdCMiU3RCU1Q3JmbG9vciUyQyt4XzUrbW9kKzIlMjkrJTNEJTI4KyU1Q2xmbG9vciU1Q2ZyYWMlN0I1MSU3RCU3QjIlN0QlNUNyZmxvb3IlMkMrNTErbW9kKzIlMjkrJTNEKyUyODI1JTJDKzElMjk=.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD0lMjh4XzMlMkMrYl80JTI5KyUzRCtEJTI4eF80JTI5KyUzRCslMjglNUNsZmxvb3IlNUNmcmFjJTdCeF80JTdEJTdCMiU3RCU1Q3JmbG9vciUyQyt4XzQrbW9kKzIlMjkrJTNEJTI4KyU1Q2xmbG9vciU1Q2ZyYWMlN0IyNSU3RCU3QjIlN0QlNUNyZmxvb3IlMkMrMjUrbW9kKzIlMjkrJTNEKyUyODEyJTJDKzElMjk=.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD0lMjh4XzIlMkMrYl8zJTI5KyUzRCtEJTI4eF8zJTI5KyUzRCslMjglNUNsZmxvb3IlNUNmcmFjJTdCeF8zJTdEJTdCMiU3RCU1Q3JmbG9vciUyQyt4XzMrbW9kKzIlMjkrJTNEJTI4KyU1Q2xmbG9vciU1Q2ZyYWMlN0IxMiU3RCU3QjIlN0QlNUNyZmxvb3IlMkMrMTIrbW9kKzIlMjkrJTNEKyUyODYlMkMrMCUyOQ==.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD0lMjh4XzElMkMrYl8yJTI5KyUzRCtEJTI4eF8yJTI5KyUzRCslMjglNUNsZmxvb3IlNUNmcmFjJTdCeF8yJTdEJTdCMiU3RCU1Q3JmbG9vciUyQyt4XzIrbW9kKzIlMjkrJTNEJTI4KyU1Q2xmbG9vciU1Q2ZyYWMlN0I2JTdEJTdCMiU3RCU1Q3JmbG9vciUyQys2K21vZCsyJTI5KyUzRCslMjgzJTJDKzAlMjk=.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD0lMjh4XzAlMkMrYl8xJTI5KyUzRCtEJTI4eF8xJTI5KyUzRCslMjglNUNsZmxvb3IlNUNmcmFjJTdCeF8xJTdEJTdCMiU3RCU1Q3JmbG9vciUyQyt4XzErbW9kKzIlMjkrJTNEJTI4KyU1Q2xmbG9vciU1Q2ZyYWMlN0IzJTdEJTdCMiU3RCU1Q3JmbG9vciUyQyszK21vZCsyJTI5KyUzRCslMjgxJTJDKzElMjk=.png)
最終“10011”二進制字符串,轉換為十進制數為51,需要用
個bit來表示,相對於理論極限值,多了一位,注意這里的解壓結果相對輸入字符串是倒序的,一般應用的時候,會先將輸入倒序排列,這樣解壓得到結果就是正序的結果。
3.2 編碼函數推導
上面的編碼都是基於0和1字符串是均值分布的前置條件的,實際情況中,是很大可能出現不均值分布的情況的。
引用前面的公式2,
, 假設
具有
位的信息,如果我們想把
用理論值編碼
位信息,那么可以推導公式:
所以:![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD1DX29wdCUyOHhfaSUyQytiXyU3QmklMkIxJTdEJTI5KyVFMiU4OSU4OCslNUNmcmFjJTdCeF9pJTdEJTdCcF8lN0JiX2klMkIxJTdEJTdE.png)
所以,我們可以得到這樣的理論編碼函數:
...(5)
3.3 Uniform Binary Variant(uABS)
現在我們將范圍拓展,假設我們要編碼的數字范圍為[1,N], 用"1" 和"0"來分別表示奇數和偶數,對應的概率為
和
, 對應的在N個數字中,偶數出現的次數為
, 那么我們可以推導N+1 和 N之間的關系為:
...(6)
這里就不再詳細的推證了,公式6等價於:
...(7)
基於公式7,我們就可以依次編碼非均值分布的二進制數字符串了,其對應的解碼公式為:
=>
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14X2krJTNEKyU1Q2JlZ2luJTdCY2FzZXMlN0QreF8lN0JpJTJCMSU3RCstKyU1Q2xjZWlsK3hfJTdCaSUyQjElN0QucCU1Q3JjZWlsKyUyNitpZitiXyU3QmklMkIxJTdEKyUzRCswKyU1QyU1QyslNUMlM0ElNUNsY2VpbCt4XyU3QmklMkIxJTdELnAlNUNyY2VpbCslMjYrb3RoZXJ3aXNlKyU1Q2VuZCU3QmNhc2VzJTdE.png)
用實例來演示編碼和解碼的過程:
繼續上面的用例
, 設定
,其編碼過程為:![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzErJTNEK0MlMjh4XzAlMkMrYl8xJTI5KyUzRCslNUNsZmxvb3IlNUNmcmFjJTdCeF8wJTdEJTdCcCU3RCU1Q3JmbG9vcislM0QrJTVDbGZsb29yKzEuKyU1Q2ZyYWMlN0IxMCU3RCU3QjMlN0QlNUNyZmxvb3IrJTNEKzM=.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzUrJTNEK0MlMjh4XzQlMkMrYl81JTI5KyUzRCslNUNsZmxvb3IlNUNmcmFjJTdCeF8wJTdEJTdCcCU3RCU1Q3JmbG9vcislM0QrJTVDbGZsb29yKzI2LislNUNmcmFjJTdCMTAlN0QlN0IzJTdEJTVDcmZsb29yKyUzRCs4Ng==.png)
對應的解碼過程為:
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzQrJTNEKyU1Q2xjZWlsK3hfNSsuK3AlNUNyY2VpbCslM0QrJTVDbGNlaWwrODYuJTVDZnJhYyU3QjMlN0QlN0IxMCU3RCU1Q3JjZWlsKyUzRCsyNg==.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzMrJTNEKyU1Q2xjZWlsK3hfNCsuK3AlNUNyY2VpbCslM0QrJTVDbGNlaWwrMjYuJTVDZnJhYyU3QjMlN0QlN0IxMCU3RCU1Q3JjZWlsKyUzRCs4.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzIrJTNEK3hfMystJTVDbGNlaWwreF8zKy4rcCU1Q3JjZWlsKyUzRCs4Ky0lNUNsY2VpbCs4LiU1Q2ZyYWMlN0IzJTdEJTdCMTAlN0QlNUNyY2VpbCslM0QrNQ==.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzArJTNEKyU1Q2xjZWlsK3hfMSsuK3AlNUNyY2VpbCslM0QrJTVDbGNlaWwrMy4lNUNmcmFjJTdCMyU3RCU3QjEwJTdEJTVDcmNlaWwrJTNEKzE=.png)
3.4 Range Variant(rANS)
上面的uABS是針對二進制字符的熵編碼,我們也可以進一步的推廣到非二進制字符的非均值熵編碼。首先我們需要明確的是,公式5是依然生效的,只是在推廣的時候,我們將
推廣為
,也就是輸入的二進制字符變成符號
即可。這樣在新增一個字符的時候,對應的等價新增該字符的熵編碼信息,所以公式5是依然生效的。
此外理論上來說,對於字符集,我們是可以有任意的概率分布的(只要字符集任意長),但是實際的時候我們是將其限定在一個量化范圍內的,一般是
的范圍。在這個范圍內,符號
出現的次數為
,那么可以得到
, 基於這個量化,結合uABS的公式,可以用下面的公式來表示rANS的編碼:
...(8)
對應的解碼操作公式為:
....(9)
...(10)
其中
,可以理解為累計分布統計操作。
用示例來解釋一下壓縮和解壓:
對於字符集['a', 'b', 'c'],其量化的 n = 3, 其統計的分布為
, 其對應的
, 現在我們來編碼字符串"abc",對於初始值
, 我們設定為
,基於公式8可以得到編碼過程為:![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzErJTNEK0MlMjh4XzAlMkMrYSUyOSslM0QrJTVDbGZsb29yJTVDZnJhYyU3QnhfMCU3RCU3QmZfYSU3RCU1Q3JmbG9vci4yJTVFMyslMkIrQ0RGJTVCYSU1RCslMkIrJTI4eF8wKyU1QyUzQW1vZCU1QyUzQWZfYSUyOSUzRCU1Q2xmbG9vciU1Q2ZyYWMlN0I4JTdEJTdCNSU3RCU1Q3JmbG9vci44KyUyQiswKyUyQislMjg4JTVDJTNBbW9kJTVDJTNBNSUyOSslM0QrMTE=.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzIrJTNEK0MlMjh4XzElMkMrYiUyOSslM0QrJTVDbGZsb29yJTVDZnJhYyU3QnhfMSU3RCU3QmZfYiU3RCU1Q3JmbG9vci4yJTVFMyslMkIrQ0RGJTVCYiU1RCslMkIrJTI4eF8xKyU1QyUzQW1vZCU1QyUzQWZfYiUyOSUzRCU1Q2xmbG9vciU1Q2ZyYWMlN0IxMSU3RCU3QjIlN0QlNUNyZmxvb3IuOCslMkIrNSslMkIrJTI4MTElNUMlM0Ftb2QlNUMlM0EyJTI5KyUzRCs0Ng==.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzMrJTNEK0MlMjh4XzIlMkMrYyUyOSslM0QrJTVDbGZsb29yJTVDZnJhYyU3QnhfMiU3RCU3QmZfYyU3RCU1Q3JmbG9vci4yJTVFMyslMkIrQ0RGJTVCYyU1RCslMkIrJTI4eF9jKyU1QyUzQW1vZCU1QyUzQWZfYyUyOSUzRCU1Q2xmbG9vciU1Q2ZyYWMlN0I0NiU3RCU3QjElN0QlNUNyZmxvb3IuOCslMkIrNyslMkIrJTI4NDYlNUMlM0Ftb2QlNUMlM0ExJTI5KyUzRCszNzU=.png)
對應的解碼操作為:
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzIrJTNEK0QlMjh4XzMlMjkrJTNEK2ZfYy4lNUNsZmxvb3IlNUNmcmFjJTdCMyU3RCU3QjglN0QlNUNyZmxvb3ItQ0RGJTVCYyU1RCUyQiUyOHhfMyU1QyUzQW1vZCU1QyUzQTglMjkrJTNEKzEuJTVDbGZsb29yJTVDZnJhYyU3QjM3NSU3RCU3QjglN0QlNUNyZmxvb3IrLSs3KyUyQislMjgzNzUlNUMlM0Ftb2QlNUMlM0E4JTI5JTNENDY=.png)
![[公式]](/image/aHR0cHM6Ly93d3cuemhpaHUuY29tL2VxdWF0aW9uP3RleD14XzArJTNEK0QlMjh4XzElMjkrJTNEK2ZfYS4lNUNsZmxvb3IlNUNmcmFjJTdCeF8xJTdEJTdCOCU3RCU1Q3JmbG9vcistK0NERiU1QmElNUQrJTJCKyUyOHhfMSU1QyUzQW1vZCU1QyUzQTglMjkrJTNEKzUuJTVDbGZsb29yJTVDZnJhYyU3QjExJTdEJTdCOCU3RCU1Q3JmbG9vcistKzArJTJCKyUyODExJTVDJTNBbW9kJTVDJTNBOCUyOSslM0QrOA==.png)
3.5 量化處理rANS
上面的rANS編碼展示的是短字符的時候的編碼流程,如果一個文件大小有1MB或者更大,這么長的字符串如何編碼?如果直接編碼的話,肯定會超過整數的表示范圍,解決辦法就是移位分解:
當編碼
的時候,得到的結果過大時,將其右移M位來確保得到的結果處於
(例如M= 16bits)這個區間,同理在解壓的時候,如果
較小,會將其左移M位,然后在進行處理,這樣就能確保編碼結果能用整形數來表示,其大致操作流程為:
MASK = 2**M -1 BOUND = 2**(2*M) - 1 ##Encoding s = readsymbol() x_test = (x / f[s]) << n + (x % f[s]) + c[s] if(x_test > BOUND): write16bits(x & MASK) x = x >> M x = (x /f[s]) << n + (x % f[s]) + c[s] ##Decoding s = symbol[x & MASK] writeSymbol(s) x = f[s](x >> n) + (x & MASK) -c[s] if(x < 2**M): x = x << M + read16bits()
對於ANS, 還有其他的編碼,例如tANS編碼,這里就不再討論,還沒看到這部分的編碼。在實際的編碼過程中,就是脫胎於上面的編碼理論,進一步的完善編碼上下文內容即可。
引用:
[1]:熵壓縮:信息熵、Huffman編碼、算數編碼、ANS+FSE
[2]:算術編碼_小石_新浪博客
