熵是随机变量不确定性的度量:分布越均匀,熵越高;分布越集中,熵越低。

香农熵:信息量的下界

package main

import (
    "fmt"
    "math"
)

// 香农熵:H(X) = -Σ p(x) log₂ p(x)
func entropy(probs []float64) float64 {
    h := 0.0
    for _, p := range probs {
        if p > 0 {
            h -= p * math.Log2(p)
        }
    }
    return h
}

// KL 散度:D_KL(P||Q) = Σ p(x) log(p(x)/q(x))
// 衡量 P 和 Q 的差异(非对称!)
func klDivergence(p, q []float64) float64 {
    kl := 0.0
    for i := range p {
        if p[i] > 0 {
            kl += p[i] * math.Log(p[i]/q[i])
        }
    }
    return kl
}

// 交叉熵:H(P,Q) = H(P) + D_KL(P||Q) = -Σ p(x) log q(x)
// 机器学习的分类损失函数
func crossEntropy(trueLabels, predicted []float64) float64 {
    ce := 0.0
    for i := range trueLabels {
        if trueLabels[i] > 0 {
            ce -= trueLabels[i] * math.Log(predicted[i])
        }
    }
    return ce
}

func main() {
    // 均匀分布:熵最大
    uniform := []float64{0.25, 0.25, 0.25, 0.25}
    fmt.Printf("均匀分布熵: %.4f bits(最大)\n", entropy(uniform))

    // 确定性分布:熵为 0
    certain := []float64{1.0, 0, 0, 0}
    fmt.Printf("确定性分布熵: %.4f bits(最小)\n", entropy(certain))

    // 英语字母分布(近似):熵 ≈ 4.18 bits < log₂(26) = 4.7 bits
    // 这正是为什么英文可以压缩
    skewed := []float64{0.4, 0.3, 0.2, 0.1}
    fmt.Printf("偏斜分布熵: %.4f bits\n", entropy(skewed))

    // 交叉熵损失:真实标签 [1,0,0],预测 [0.7,0.2,0.1]
    truth := []float64{1, 0, 0}
    pred := []float64{0.7, 0.2, 0.1}
    fmt.Printf("交叉熵损失: %.4f\n", crossEntropy(truth, pred))
    // 预测 [0.9,0.05,0.05] 时损失更小:
    pred2 := []float64{0.9, 0.05, 0.05}
    fmt.Printf("更好预测的损失: %.4f\n", crossEntropy(truth, pred2))
}

Huffman 编码:最优前缀码

Huffman 编码是无损压缩的理论最优:出现频率高的字符用短编码,频率低的用长编码。平均码长接近香农熵下界。ZIP、gzip、JPEG 的熵编码阶段都用它(或变体)。

package main

import (
    "container/heap"
    "fmt"
)

type HNode struct {
    char        rune
    freq        int
    left, right *HNode
}

type HHeap []*HNode

func (h HHeap) Len() int            { return len(h) }
func (h HHeap) Less(i, j int) bool  { return h[i].freq < h[j].freq }
func (h HHeap) Swap(i, j int)       { h[i], h[j] = h[j], h[i] }
func (h *HHeap) Push(x interface{}) { *h = append(*h, x.(*HNode)) }
func (h *HHeap) Pop() interface{}   { old := *h; n := len(old); x := old[n-1]; *h = old[:n-1]; return x }

func buildHuffman(freq map[rune]int) *HNode {
    h := &HHeap{}
    for ch, f := range freq {
        heap.Push(h, &HNode{char: ch, freq: f})
    }
    for h.Len() > 1 {
        l := heap.Pop(h).(*HNode)
        r := heap.Pop(h).(*HNode)
        heap.Push(h, &HNode{freq: l.freq + r.freq, left: l, right: r})
    }
    return heap.Pop(h).(*HNode)
}

func buildCodes(node *HNode, prefix string, codes map[rune]string) {
    if node == nil { return }
    if node.left == nil && node.right == nil {
        codes[node.char] = prefix
        return
    }
    buildCodes(node.left, prefix+"0", codes)
    buildCodes(node.right, prefix+"1", codes)
}

func main() {
    text := "abracadabra"
    freq := map[rune]int{}
    for _, c := range text { freq[c]++ }

    root := buildHuffman(freq)
    codes := map[rune]string{}
    buildCodes(root, "", codes)

    fmt.Println("Huffman 编码:")
    totalBits := 0
    for ch, code := range codes {
        bits := len(code) * freq[ch]
        totalBits += bits
        fmt.Printf("  '%c'(freq=%d): %s (%d bits)\n", ch, freq[ch], code, bits)
    }
    fmt.Printf("总位数: %d(固定 3bit 编码需 %d 位)\n", totalBits, len(text)*3)
}

信息论的香农极限

香农信源编码定理:无损压缩的极限是信源的熵。英文文本的熵约 1.3 bits/字符(而非 ASCII 的 8 bits),这是 gzip 能压缩到 12% 的数学根基。任何无损压缩算法都不能低于这个极限——这是信息论给出的绝对下界。

推荐做法
  • 用交叉熵作为多分类损失函数(标准做法)
  • 用 KL 散度衡量两个概率分布的差异
  • 评估压缩算法的上限前先估算数据的熵
不推荐
  • 试图无损压缩随机数据——熵最大,无法压缩
  • 把 KL 散度当距离用——它不满足对称性
常见误区
  • 交叉熵损失中预测值不能为 0——需要数值稳定处理(加 ε 或用 log-softmax)

判断标准:能解释为什么机器学习用交叉熵而非 MSE 做分类损失 → 掌握本章。

信息就是惊讶。最意外的消息,承载最多信息。

— Claude Shannon, 1948