【數據壓縮】Huffman編碼


1. 壓縮編碼概述

數據壓縮在日常生活極為常見,平常所用到jpg、mp3均采用數據壓縮(采用Huffman編碼)以減少占用空間。編碼\(C\)是指從字符空間\(A\)到碼字表\(X\)的映射。數據壓縮編碼指編碼后信息的長度較於原始信息要短。本文試圖探討Huffman編碼是如何保證唯一可譯性、如何壓縮、以及壓縮效率如何?

前綴碼

前綴碼的任意一碼字均不為其他碼字的前綴,此保證了編碼的唯一可譯性。比如碼字表{0, 01, 11, 1}001的前綴,111的前綴;當遇到字符文本011100,是應分隔為01-11-0-0還是0-11-1-0-0等?若采用前綴碼編碼,碼字表為{0, 10, 11},則字符文本011100可即時分隔為0-11-10-0可譯,所以前綴碼亦被稱為即時碼。同時,前綴碼保證了編碼的唯一可譯性,即字符空間\(A\)到碼字表\(X\)的映射為一一映射。本文探討的Huffman編碼即為前綴碼。

根據碼字長度,編碼分為等長編碼與變長編碼。等長編碼即字母表中所有碼字的長度均相等,最為常見的是字長7位的ASCII碼。變長編碼則是碼字的長度可能存在不相等。

前綴碼可表示為葉子節點為碼字的編碼二叉樹,如圖所示。

期望編碼長度

如上圖所示的兩種變長編碼,哪一種編碼壓縮效率比較好?顯然,若信息編碼之后的長度越小,則編碼的壓縮效率越好。為此,我們引出刻畫量度期望編碼長度

首先我們定義字符空間\(A = \lbrace a_1,a_2, \cdots ,a_n \rbrace\),即信息文本中有n個字符,且字符\(a_i\)的長度為\(l_i\),出現頻率(即概率)為\(p_i\);則期望編碼長度為

\[L = \sum\limits_{i = 1}^n {p_i*l_i} \]

若要期望編碼長度\(L\)越小,學過數學的都知道,則高概率的碼字字長應不長於低概率的碼字字長,即滿足

\[\forall i,j \ \ \ p_i \ge p_j \Leftrightarrow l_i \le l_j \]

最優編碼

對於二元編碼(01)的前綴碼,滿足McMillan-Kraft不等式

\[\sum\limits_{i = 1}^n {{2^{ - l_i}}} \le 1 \]

具體的證明參看[3]。McMillan-Kraft不等式從整體上限制編碼長度的下界。

如下圖所示的前綴碼即滿足McMillan-Kraft不等式。

最優編碼指期望編碼長度最小的編碼,求解最優編碼等價於數學問題:

\begin{align}
& \min \sum\limits_{i = 1}^n {{p_i}*{l_i}} \cr
& s.t. \ \sum {{2^{ - {l_i}}}} \le 1 \label{eq:kraft}
\end{align}

運用拉格朗日乘子法,構造目標函數
\begin{equation}
J = \sum {p_i*l_i + \lambda (\sum {{2^{ - l_i}}} } )
\end{equation}

\(l_i\)求偏導,

\[{{\partial J} \over {\partial l_i}} = p_i - \lambda {2^{ - l_i}}\ln 2 \]

令偏導為0,得到

\[{2^{ - l_i}} = {{p_i} \over {\lambda \ln 2}} \]

將其代入McMillan-Kraft不等式\eqref{eq:kraft}中,得到\(\lambda = {1 \over {\ln 2}}\),最優編碼的碼字長度
\begin{equation}
l_i = - \log _{2}p_i
\end{equation}

最優編碼的期望碼字長度即為字符空間的熵:
\begin{equation}
\sum\limits_{i} {p_il_i = - \sum\limits_{i} {p_i \log p_i} } = H(A)
\end{equation}

由此,定義編碼的冗余度(Redundancy of a code),表示編碼的冗余描述:
\begin{equation}
\rho = L - H(A)
\end{equation}

可以證明,前綴碼的編碼長度滿足不等式
\begin{equation}
H(A) \le L \le H(A) + 1
\end{equation}

因此,前綴碼的冗余度滿足\(0 \le \rho \le 1\)

2. Huffman編碼

Huffman編碼采用小頂堆來優化編碼二叉樹的建立過程,確保低概率的碼字字長不短於高概率的碼字,具體編碼過程如下:

  1. 將字符空間的字符以概率為關鍵值建立小頂堆;
  2. 依次取堆頂元素兩次,將該兩個字符合成一棵二叉樹,根節點的關鍵值為兩個字符的概率相加;然后將該新合成的二叉樹做為節點插入到小頂堆中;
  3. 重復步驟2直至小頂堆中只有一個節點,此節點即為編碼二叉樹。

編碼二叉樹建立過程如圖所示

此字符空間有9個字符,采用等長編碼則需要\(4\) bit;Huffman編碼的期望字長則為\(2.77\) bit;字符空間的熵為\(2.69\) bit;冗余度為\(2.77-2.69=0.08\) bit.

Python 3.6實現Huffman編碼,代碼參考了rosettacode

# -*- coding: utf-8 -*-
# @Time    : 2017/1/21
# @Author  : rain
from collections import Counter
from heapq import heapify, heappop, heappush


def huffman_coding(message):
    freq = Counter(message)  # counter for every character
    min_heap = [[cnt, [ch, '']] for ch, cnt in freq.items()]
    heapify(min_heap)
    while len(min_heap) > 1:
        low1 = heappop(min_heap)
        low2 = heappop(min_heap)
        for pair in low1[1:]:  # update children node
            pair[1] += '0'
        for pair in low2[1:]:  # update children node
            pair[1] += '1'
        # push [low1_cnt+low2_cnt, low1's children, low2's children]
        heappush(min_heap, [low1[0] + low2[0]] + low1[1:] + low2[1:])
    vocabulary = {pair[0]: pair[1] for pair in min_heap[0][1:]}  # text -> code
    return vocabulary

sentence = 'this is an example for huffman encoding'
print(huffman_coding(sentence))

上述實現中,並沒有直接構建二叉樹,而是用到了一個小技巧——在小頂堆中循環編碼。Huffman編碼存在一個缺點:解碼端要根據碼字與編碼之間的映射關系才能解碼,即解碼端與編碼端應共享一棵Huffman編碼樹。

3. 參考資料

[1] Huffman, David A. "A method for the construction of minimum-redundancy codes." Proceedings of the IRE 40.9 (1952): 1098-1101.
[2] Cover, Thomas M., and Joy A. Thomas. Elements of information theory. John Wiley & Sons, 2012.
[3] Bernd Girod, EE398A Image and Video Compression.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM