Ajiu9

我训了个迷你 GPT,它教会我 log

Oct 10 · 10min

昨天我在 Intel Mac 上训了一个 0.8M 参数的迷你 GPT。今天我跟着 3Blue1Brown 的信息论课学熵,卡在一个所有教材都一笔带过的公式上:

−log₂p

公式我"看过"很多年。但被要求报出具体数字时,我发现自己根本不懂它为什么长这样。这篇记录我从卡住到想通的全过程——全程只用加减乘除,和一句话版的 log 定义。

log 只有一句话的含义

log₂(x) 只有一个含义:2 的几次方等于 x。

  • log₂(8) = 3,因为 2³ = 8
  • log₂(1/4) = −2,因为 2⁻² = 1/4

不做任何延伸,就这一句话。

为什么 log₂p 前面有个负号

概率 p 是个 0 到 1 之间的数。而 2 的正次方全部 ≥ 1(2¹=2,2²=4……)。想把结果压到 1 以下,指数只能取负数。

所以只要 p < 1,log₂p 就必然是负的。加个负号把它掰正。

我踩过的坑是在负号上死磕直觉——完全没必要。负号是符号卫生:先算 log,发现是负的,加负号让"信息量"这个数为正而已。直觉应该在别处找。

直觉在两条数轴上:上轴是指数(−5、−4、−3……3,均匀排开),下轴是对应的数值(1/32、1/16、1/8……8)。把上下连起来看,你会发现 p < 1 的数全部住在指数 0 的左边。"负"不是什么怪事,就是它们的住址。

把两条数轴立起来,就是一张二维曲线图——横轴放数值 x,纵轴放 log₂x:

0.512484321−1−2−3−4y = log₂x(2 的几次方)x(数值)2⁻¹ = 1/22⁰ = 12¹ = 22² = 42³ = 8概率 p0 < p < 1概率 p 永远住在这一格:曲线全部躺在 x 轴下面,y 全是负数——这就是 log₂p 必为负的样子
y = log₂x:只要 x 是 2 的几次方,纵轴上读出来的就是那个"几"。概率 p 所在的 0~1 区间(浅蓝),整段都在 x 轴下方

把"住址"和曲线对照着看:概率 p 永远住在 0 和 1 之间,对应图上浅蓝的那一小格——这段曲线全部躺在 x 轴下面,纵坐标全是负数。这就是"log₂p 必为负"在图上的样子。x = 1 是分水岭:2⁰ = 1,正好落在 y = 0 上。

bit 就是刀数

真正让我开窍的换算:把信息量想成切蛋糕的刀数。

从 1 出发对半切:

  • 切 1 刀 → 1/2
  • 切 2 刀 → 1/4
  • 切 3 刀 → 1/8
  • ……

把概率 p 切到这么小需要几刀?答案是 −log₂p。

那 bit 又是什么?想象猜一个人心里想的数,规则是你只能问"是不是大于 50"这种是非题,每次提问都把剩下的范围砍掉一半。切一刀 = 问一个问题 = 剩下的可能性减半。

所以 bit 可以理解成是非题的数量:一个事件的信息量,就是平均要问几个是非题才能锁定它。1 bit,就是一刀,就是一个问题。

用我自己的模型报数

空谈不如算数。我拿昨天训的模型实测(65 个字符的字符表):

前缀 "compr",下一个字符是 'a',模型给的 p = 0.293:

−log₂(0.293) ≈ 1.77 bits —— 模型很有把握,平均不到两个是非题就能锁定

前缀 "the ",下一个字符是 't',p = 0.108:

−log₂(0.108) ≈ 3.21 bits —— 没那么有把握,要多问几个

这里我栽过一次:第一次我报了 9.26。9.26 = 1/0.108,那是 1/p——块数(p 这么大的块,1 里面能塞多少块),不是刀数。块数回答"平均要抽多少次才能抽中它",刀数回答"几个是非题能锁定它"。熵用的是后者。

还有一个反直觉的数:p 从 0.293 掉到 0.108,掉了不到 3 倍;bits 只从 1.77 涨到 3.21,涨了 1.44。log 把倍数世界翻译成刀数世界——概率乘除,信息量加减。

最极端的情况:某个字符 p = 0.00001,比 'a' 难得 29274 倍。要多少是非题?2¹⁵ = 32768,所以只需 16.61 bits。

难得三万倍,只多问十五个问题。

"温和"不是妥协,是对半砍这条规则的物理事实——每问一句就砍掉一半,15 刀就能从三万砍到一。

六个前缀,六种"出乎意料"

拿这套公式实测自己模型的六个前缀,条件熵(每个前缀下、所有可能下一字符的平均信息量)长这样:

六个前缀的条件熵对比
同一个模型,六个前缀,六种"出乎意料"(数据来自自训模型的 dists.json)

"the " 最高(4.41)——the 后面什么都可能接,模型心里没底;"compr" 最低(2.53)——猜到这个前缀,下一个字符基本锁定。上下文越长,不确定性被收得越窄:"comp" 是 3.46,"compr" 降到 2.53,一路推到极限,就是英语的真实熵 H[English] ≈ 1 bit——3b1b 系列的终点数字。

这里我犯过一个很典型的错:我说"compr 熵低,是因为 compr 出现的概率高"。

不对。 熵看的从来不是前缀本身多常见(p(前缀)),而是它把下一个字符的不确定性收窄了多少(p(下一字符|前缀))。这个区分,恰好就是交叉熵损失的入口——训练时模型逐字符看的正是 p(下一字符|前文)。

熵就是一本账

有了 bits,熵只剩一步:按概率加权平均。

把 bits 想成价格,p 想成购买频率,熵就是这本账的日均开销:

字符价格(bits)频率(p)对账本的贡献
'a'1.77(便宜)0.293(天天买)0.519
'D'16.61(天价)0.00001(十万次一遇)0.000166

'D' 贵到离谱,但对账本几乎没影响。熵是日常开销的平均——奢侈品十年买一次,不左右账本。

前缀 "compr" 的总账:H = 2.53 bits/char。作为对照,如果 65 个字符均匀分布,上限是 log₂65 ≈ 6.02。模型靠上下文把 6.02 压到了 2.53。

最后的反例:如果不用 log 呢?

这是我今天最爽的一个"啊哈"时刻。假设有人较真:信息量凭什么用 −log₂p,用 1/p 不行吗?块数更直观啊。

算一遍就知道 1/p 会带来什么。熵的定义是"每项 = p × 信息量,再求和"。如果信息量 = 1/p:

每一项都是 p × (1/p) ≡ 1

每一项恒等于 1,求和永远等于字符表大小——H 恒等于 65。均匀分布是 65,"compr" 后面几乎是确定的也还是 65。分布的形状信息整个丢掉了。

而用 −log₂p,概率悬殊 29274 倍的两个字符,对账本的贡献是 0.519 vs 0.000166——差距被诚实地保留了下来。

log 不是众多选择中的一个,是唯一让加权平均仍然反映分布形状的选择。


今天学的这一切只需要加减乘除和"2 的几次方",但它已经通向一个每个训练大模型的人都天天打交道的东西——交叉熵:训 GPT 时屏幕上不断往下掉的那个 loss,本质就是把今天的"报数"从单个字符,变成对整个概率分布求平均。下一篇我们就去把它拆开。

一个 0.8M 的模型,把我看了很多年的公式逼成了能心算的数字。这大概就是"用自己的数据学习"最值钱的地方。

> comment on mastodon / twitter
>
@2024-2025 湘ICP备2024048835号