Mímisbrunnr知恵の泉

← 情報理論 一覧

🎓 レベル:発展 | 重要度:B(重要)

📎 前提:ハフマン符号シャノンの情報源符号化定理 | 関連:結合・条件付きエントロピーと連鎖則(エントロピーレート)

要点(BLUF)

1. 算術符号:系列を1つの区間に畳む

区間 [0,1)[0,1) から始め、記号を読むたびに「現在の区間を、各記号の確率に比例して分割し、読んだ記号の小区間に縮める」を繰り返します。系列 x1xnx_1\cdots x_n を処理し終えると、幅 p(x1xn)=ip(xi)p(x_1\cdots x_n)=\prod_i p(x_i) の区間が残ります。その区間内の数を2進小数で1つ指定すれば符号化完了。区間幅 ww を一意に指すには約 log2w-\log_2 w bit 必要なので、

符号長log2p(x1xn)=i=1n(log2p(xi))\text{符号長} \approx -\log_2 p(x_1\cdots x_n) = \sum_{i=1}^n \big(-\log_2 p(x_i)\big)

1記号あたりに均すと自己情報量の平均=エントロピー。ハフマンと違い各記号を整数 bit に丸めないので、log2pi-\log_2 p_i が非整数でも無駄が系列全体で2 bit 程度に収まり、長い系列では1記号あたりほぼ消えます。条件付き確率 p(xix<i)p(x_i\mid x_{<i}) を使えば、記憶のある情報源のエントロピーレート(結合・条件付きエントロピーと連鎖則)まで圧縮できます。

graph TD
  s0["区間 0〜1 から開始"] --> s1["x1=a で 0〜0.7 に縮小"]
  s1 --> s2["x2=b で 0.49〜0.63 に縮小"]
  s2 --> s3["系列を読むほど区間は狭まる"]
  s3 --> out["最終区間内の1点を2進小数で出力(約 -log2 幅 bit)"]

2. ユニバーサル符号:確率を知らずに圧縮する

ハフマンも算術符号も確率分布 pp知っている前提でした。実データでは pp は未知・非定常です。ユニバーサル符号は分布を仮定せず、データの統計を走りながら学習します。代表が Lempel-Ziv(LZ77/LZ78):過去に出た部分列を「(位置, 長さ)」の参照に置き換える辞書式圧縮で、繰り返しが多いほど縮みます。LZ は任意の定常エルゴード情報源に対し、1記号あたり符号長がエントロピーレートに漸近することが証明されています。gzip(LZ77+ハフマン)、zip、PNG はこの系譜です。

3. コード:算術符号 vs ハフマン(底2, bit)

i.i.d. 情報源 p={a:0.7,b:0.2,c:0.1}p=\{a:0.7,b:0.2,c:0.1\} から系列を生成し、算術符号の符号長 log2p(系列)-\log_2 p(\text{系列}) が1記号ハフマンより HH に近いことを確かめます。

import numpy as np, heapq
rng=np.random.default_rng(0)
pv=np.array([0.7,0.2,0.1])     # a,b,c
H=-np.sum(pv*np.log2(pv))
n=10000
seq=rng.choice(3,size=n,p=pv)

# 算術符号の理想符号長 ~ -log2 p(系列)
arith_bits = -np.sum(np.log2(pv[seq]))

# 1記号ハフマン(整数長)
def huffman_len(prob):
    heap=[[pp,i,[i]] for i,pp in enumerate(prob)]; heapq.heapify(heap)
    lengths=[0]*len(prob); cnt=len(prob)
    while len(heap)>1:
        lo=heapq.heappop(heap); hi=heapq.heappop(heap)
        for i in lo[2]: lengths[i]+=1
        for i in hi[2]: lengths[i]+=1
        heapq.heappush(heap,[lo[0]+hi[0],cnt,lo[2]+hi[2]]); cnt+=1
    return lengths
L1=np.sum(pv*np.array(huffman_len(pv)))

print(f"エントロピー H = {H:.4f} bit/記号")
print(f"系列長 n={n}")
print(f"算術符号の符号長 ~ -log2 p(系列) = {arith_bits:.1f} bit")
print(f"  1記号あたり = {arith_bits/n:.4f} bit/記号  (H={H:.4f} に近い)")
print(f"1記号ハフマン平均長 = {L1:.4f} bit/記号 (整数丸めで H より大)")

出力:

エントロピー H = 1.1568 bit/記号
系列長 n=10000
算術符号の符号長 ~ -log2 p(系列) = 11549.6 bit
  1記号あたり = 1.1550 bit/記号  (H=1.1568 に近い)
1記号ハフマン平均長 = 1.3000 bit/記号 (整数丸めで H より大)

出力の意味:算術符号は1記号あたり 1.15501.1550 bit で、エントロピー 1.15681.1568 bit にほぼ一致(標本ゆらぎで僅かに下にも出ます)。対して1記号ハフマンは 1.30001.3000 bit と、整数長への丸めで 0.140.14 bit/記号も無駄が出ています。算術符号は系列全体を1つの数に符号化することで、ハフマンが記号ごとに払う整数丸めの税金を償却しているわけです。1万記号なら算術符号は約 1400 bit(約 175 バイト)得をします。実用の汎用圧縮(gzip 等)はユニバーサル符号でこの効率に近づきつつ、分布が未知でも動きます。

4. 数式の直観的意味

ハフマンの限界は「各記号に整数 bit を割り当てる」点にありました。算術符号は 系列を1つの実数とみなす ことで、実質的に分数 bit を扱えます——log20.70.515-\log_2 0.7\approx0.515 bit を、丸めずにそのまま積み上げられる。だから符号長は系列の自己情報量 log2p(系列)-\log_2 p(\text{系列}) にぴたり一致し、AEP(漸近等分割性とAEP)が言う「長い系列は約 2nH2^{-nH} の確率」と整合します(典型系列を nHnH bit で指す)。ユニバーサル符号はさらに「pp を知らなくても、長く見れば実効的に学習できる」という、符号化定理の実用的な完成形です。

⚠️ よくある誤解

対応シミュレーション

関連ノート