香农编码
前段时间在《信息论与编码》这门课中学习了各种编码方式,这里记录一下香农码。
设离散无记忆信源 ( X P ( X ) ) = ( a 1 a 2 … a n p ( a 1 ) p ( a 2 ) … p ( a n ) ) \begin{pmatrix} X \\ P(X) \end{pmatrix}=\begin{pmatrix} a_1&a_2&\ldots&a_n \\ p(a_1)&p(a_2)&\ldots&p(a_n) \end{pmatrix} (XP(X))=(a1p(a1)a2p(a2)……anp(an)), 0 ≤ p ( a i ) ≤ 1 0\le p(a_i) \le 1 0≤p(ai)≤1, ∑ i = 1 n p ( a i ) = 1 \sum_{i=1}^{n}p(a_i)=1 ∑i=1np(ai)=1。
编码步骤:
- 将信源符号按概率从大到小的顺序排列。不妨令 p ( a 1 ) ≥ p ( a 2 ) ≥ … ≥ p ( a n ) p(a_1)\ge p(a_2)\ge\ldots\ge p(a_n) p(a1)≥p(a2)≥…≥p(an)。
- 令 p ( a 0 ) = 0 p(a_0)=0 p(a0)=0,用 p a ( a j ) ( j = i + 1 ) p_a(a_j)(j=i+1) pa(aj)(j=i+1)表示第i个码字的累加概率,
p a ( a j ) = ∑ i = 0 j − 1 p ( a i ) , j = 1 , 2 , … , n p_a(a_j)=\sum_{i=0}^{j-1}p(a_i),j=1,2,\ldots,n pa(aj)=i=0∑j−1p(ai),j=1,2,…,n
- 确定满足下列不等式的整数 k i k_i ki,并令其为第i个码字的长度
− log 2 p ( a i ) ≤ k i < 1 − log 2 p ( a i ) -\log_2p(a_i)\le k_i\lt1-\log_2p(a_i) −log2p(ai)≤ki<1−log2p(ai)
- 将 p a ( a j ) p_a(a_j) pa(aj)用二进制表示,并取小数点后 k i k_i ki位作为符号 a i a_i ai的编码。
举例
对单符号离散无记忆信源 ( X P ( X ) ) = ( a 1 a 2 a 3 a 4 a 5 a 6 a 7 0.2 0.19 0.18 0.17 0.15 0.1 0.01 ) \begin{pmatrix} X \\ P(X) \end{pmatrix}=\begin{pmatrix} a_1&a_2&a_3&a_4&a_5&a_6&a_7 \\ 0.2&0.19&0.18&0.17 &0.15&0.1&0.01 \end{pmatrix} (XP(X))=(a10.2a20.19a30.18a40.17a50.15a60.1a70.01)编香农码,计算编码效率。
a i a_i ai | a 1 a_1 a1 | a 2 a_2 a2 | a 3 a_3 a3 | a 4 a_4 a4 | a 5 a_5 a5 | a 6 a_6 a6 | a 7 a_7 a7 |
---|---|---|---|---|---|---|---|
p ( a i ) p(a_i) p(ai) | 0.2 | 0.19 | 0.18 | 0.17 | 0.15 | 0.1 | 0.01 |
p a ( a j ) p_a(a_j) pa(aj) | 0 | 0.2 | 0.39 | 0.57 | 0.74 | 0.89 | 0.99 |
k i k_i ki | 3 | 3 | 3 | 3 | 3 | 4 | 7 |
码字 | 000 | 001 | 011 | 100 | 101 | 1110 | 1111110 |
η = H ( X ) ∑ i = 1 7 p ( a i ) k i ≈ 83.08 % \eta=\frac{H(X)}{\sum_{i=1}^7 p(a_i)k_i}\approx 83.08 \% η=∑i=17p(ai)kiH(X)≈83.08%
由此可见,香农码的编码效率其实并不高。
小数十进制转二进制
由此可见,编香农码重要的一步是要把累加概率求二进制数。而这其实也不难,分别回顾一下小数和整数的转换方法。
- 整数部分:除以2取余数,直到商为0为止。
- 小数部分:乘以2取整数,直到小数为0(或到达要求精度)为止。
Python实现
学习完香农码和小数转二进制的方法后,就要开始写作业了。看着一大堆概率在我眼前等着我去算,就瞬间没劲了。
这个时候,我想起了大师Larry Wall的名言:
优秀程序员应该有三大美德:懒惰、急躁和傲慢。
其中,懒惰,是一种品质,它会使你花很大力气去规避过度的精力消耗,敦促你写出节省体力的程序。
没错,有计算机为什么我还要手算小数二进制,而且Python不是有bin()
吗,直接用啊!
然后就报错了:
之前都没有试过,难道说bin()
只能转换整型数吗?结果一查还真是这样。
怎么办呢?既然已经知道了原理,那当然是自己写啊!
def enhanced_bin(num: float, has_prefix=True) -> str:
"""
加强版的bin()
:param num: 转换为二进制的数
:param has_prefix: 是否需要"0b"前缀
:return: 返回二进制字符串
"""
zheng = int(num)
# 整数直接转换
if zheng == num:
if has_prefix:
return bin(num)
else:
return bin(num)[2:]
xiao = num - zheng
zheng = bin(zheng)
xiao_str = ''
# 小数每次乘2取个位
while xiao:
xiao *= 2
xiao_str += str(int(xiao))
if xiao >= 1:
xiao -= 1
if not has_prefix:
zheng = zheng[2:]
return zheng + '.' + xiao_str
再来测试一下:
arr = [2, 32, 34453, 56675766567, 1.2, 0.5, 0.25, 0.24, 0.125, 3483939.22, 3483939, 0.22]
for i in arr:
print(enhanced_bin(i, has_prefix=False))
测试结果:
成功完成任务!