压缩即智能:从自编码器到 GPT 的认知哲学
压缩即智能:从自编码器到 GPT 的认知哲学
为什么说压缩就是智能?GPT 的"解码器"里到底有没有"编码"?婴儿咿呀学语和神经网络训练是同一件事吗?本文试图从信息论、认知科学和代码实现三个角度,回答这些本质性问题。
目录
- 压缩即智能:一个深刻的等价关系
- 婴儿学习与自编码器:模仿、压缩、内化
- GPT 的"解码器"里藏着编码器:一个被名字误导的真相
- 没有编码就没有解码——这个理解对吗?
- CodeGPT 中的压缩与编码:代码级解剖
- 结语:理解比命名更重要
1. 压缩即智能:一个深刻的等价关系
1.1 Hutter 奖:直接用压缩率衡量智能
Marcus Hutter(AIXI 理论的提出者,DeepMind 研究员)设立了 Hutter Prize——用现金奖励能够更好地压缩维基百科的算法。这不是一个工程竞赛的噱头,背后是一个深刻的理论等价:
完美的压缩 = 完美的预测 = 完美的理解。
为什么?如果你能把一本 100MB 的书压缩到 1KB,说明你对书的内容有极深的理解——你掌握了所有的模式、规律、冗余,能用极少的信息重构全部内容。
1.2 Shannon 的奠基:预测就是压缩
Claude Shannon 在 1948 年的信息论开山之作中证明了:
数据的最优压缩率 = 数据源的熵(信息量)
如果你能完美预测下一个符号,那这个符号就不携带任何"新信息",它的熵为零,可以被完全压缩掉。
来看 CodeGPT 的训练目标——交叉熵损失:
# model.py:192 — CodeGPT 的训练损失
loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)
交叉熵(cross-entropy)本质上就是在衡量压缩效率。 具体来说:
交叉熵 H(p, q) = -Σ p(x) · log q(x)
其中:
p(x) = 真实分布(代码中下一个 token 实际是什么)
q(x) = 模型预测的分布(CodeGPT 认为下一个 token 是什么的概率)
当 q = p 时,交叉熵最小 = 信息熵 H(p)
→ 模型完美预测 → 完美压缩 → "完美理解"了数据
所以训练 CodeGPT 的过程,字面意义上就是在学习如何更好地压缩代码。 每一步梯度下降,都在让模型成为一个更好的代码压缩器。
1.3 一个直觉实验
想象你从未见过 Python,有人给你一段代码:
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
你第一次看,每个字符都是"新信息",无法压缩。
但学过 Python 之后,你的大脑建立了一套内部模型:
- 看到 def → 预测接下来是函数名和参数 → 已知模式,不需要存储
- 看到 fibonacci → 预测这是递归函数 → 预期 base case + 递归调用
- 看到 if n <= 1 → 几乎确定下一行是 return n 或 return 1
你能预测得越准,这段代码对你来说"信息量"就越小——你理解得越深,你需要记住的就越少。 这就是压缩 = 智能。
1.4 Kolmogorov 复杂性:终极理论
Kolmogorov 复杂性定义了一个对象的绝对信息量:能够生成该对象的最短程序的长度。
K("aaaaaaaaaaaa") ≈ len('print("a" * 12)') → 复杂性低,高度可压缩
K("j3kQ9xL2mP7z") ≈ len('print("j3kQ9xL2mP7z")') → 复杂性高,不可压缩
K(莎士比亚全集) ≈ 一个能"理解"英语文学的程序的长度
从这个角度看,GPT 模型本身就是一个压缩器——它是一个"程序"(权重),能够以高概率"生成"(重现)训练数据。模型越小、训练数据越多且还能高质量重现,说明压缩率越高,"智能"越高。
2. 婴儿学习与自编码器:模仿、压缩、内化
2.1 婴儿学语的三个阶段
观察一个婴儿学说话的过程:
阶段 1:感知(0-6个月)
听到 "妈妈" → 大脑存储的是原始声波
此时是纯粹的"录音机",没有压缩,没有理解
阶段 2:模仿(6-12个月)
听到 "妈妈" → 尝试发音 "ma...ma"
输出是对输入的粗糙重构
这就是自编码器!输入 → 压缩 → 重构
阶段 3:内化(1-3岁)
"妈妈" 不再是声波模式,而是一个概念:
- 关联到一个人
- 关联到温暖、安全的感觉
- 能在新的句子中使用 "妈妈来了" "找妈妈"
这就是深度表征——压缩到了语义层面
2.2 自编码器就是机器版的"模仿学习"
把婴儿学习的过程和自编码器对比:
婴儿: 自编码器:
┌──────────────┐ ┌──────────────┐
│ 耳朵听到声音 │ ← 输入 │ 输入 x │
│ (原始声波) │ │ (原始数据) │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 大脑处理 │ ← 编码 │ 编码器 E(x) │
│ (听觉皮层→ │ │ (神经网络) │
│ 语言区) │ │ │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 内部表征 │ ← 瓶颈/压缩 │ 潜在向量 z │
│ "妈妈"的概念 │ 低维表示 │ (低维向量) │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 嘴巴发音 │ ← 解码/重构 │ 解码器 D(z) │
│ "ma...ma" │ │ (神经网络) │
└──────────────┘ └──────────────┘
训练信号: "我发出的声音和听到的一样吗?" = 重构损失 ‖x - x̂‖²
2.3 为什么"不断重复"如此关键
婴儿不是听一次"妈妈"就学会的,需要数千次重复。每次重复:
第 1 次: 听到 "妈妈" → 发出 "啊啊" → 差距很大 → 大幅调整
第 10 次: 听到 "妈妈" → 发出 "嘛嘛" → 接近了 → 微调
第 100 次: 听到 "妈妈" → 发出 "妈妈" → 准确了 → 但只是模仿
第 1000 次: 看到妈妈的脸 → 主动说 "妈妈" → 内化了!概念形成
关键转变:从"模仿声音"变成"理解含义"
从"高维重构"变成"低维语义表征"
这和神经网络训练的过程惊人地相似:
# 概念示意 —— 神经网络训练也是"不断重复"
for epoch in range(1000): # ← 数千次重复
for batch in dataloader:
output = model(batch) # 尝试"发音"(前向传播)
loss = criterion(output, target) # 和"正确答案"比较
loss.backward() # 计算"哪里错了"
optimizer.step() # 调整"发音方式"(更新权重)
CodeGPT 的训练循环(train.py:306-375)和婴儿学习的对应关系:
# train.py:306-375 — CodeGPT 训练循环
while True: # 婴儿不停地听和说
X, Y = get_batch('train') # 听到一段代码
logits, loss = model(X, Y) # 尝试预测下一个 token
loss.backward() # 发现预测和真实的差距
optimizer.step() # 调整内部表征
# 每 1000 步检查一次"学得怎么样了"
if iter_num % eval_interval == 0:
losses = estimate_loss() # 类似老师的测验
2.4 更深层的共性:表征的层级结构
婴儿的认知发展是分层的:
底层: 声音模式 → "ma" 这个音节
中层: 词汇概念 → "妈妈" 是一个人
高层: 抽象关系 → "妈妈爱我" "妈妈和爸爸是一家人"
深度神经网络(包括 CodeGPT)也是分层的:
# model.py:144-150 — CodeGPT 的层级结构
self.transformer = nn.ModuleDict(dict(
wte=nn.Embedding(config.vocab_size, config.n_embd), # 第 0 层:token → 向量
wpe=nn.Embedding(config.block_size, config.n_embd), # 位置信息
drop=nn.Dropout(config.dropout),
h=nn.ModuleList([Block(config) for _ in range(config.n_layer)]), # 12 层 Block
ln_f=LayerNorm(config.n_embd, bias=config.bias),
))
12 层 Block 的每一层学到的东西不同(已被大量研究证实):
Block 0-1(底层): 词法特征 → 这是变量名、关键字还是运算符?
类似婴儿识别"这是一个声音"
Block 2-4(低层): 局部语法 → "def" 后面跟函数名,"(" 后面跟参数
类似婴儿理解"妈妈"是一个词
Block 5-8(中层): 语义关系 → 这个变量在哪里定义的?类型是什么?
类似幼儿理解"妈妈是那个人"
Block 9-11(高层): 全局逻辑 → 这个函数在做什么?递归结构?算法模式?
类似儿童理解"妈妈会照顾我"
2.5 学习的本质:有损压缩中保留什么
婴儿学语的一个关键观察:他们不是录音机。婴儿不会精确复制听到的每一个音高、语速、情绪。他们提取的是不变量——不管妈妈说"妈妈"还是爸爸说"妈妈",声音完全不同,但提取出的概念是同一个。
这就是有损压缩的精髓:丢弃不重要的细节,保留本质结构。
自编码器通过瓶颈层强制进行有损压缩:
# 自编码器 —— 概念示意
class AutoEncoder:
def __init__(self):
self.encoder = Linear(1000, 64) # 1000 维 → 64 维,必须丢弃 93.6% 的信息
self.decoder = Linear(64, 1000) # 但要能重构!所以必须保留最重要的 6.4%
# 什么被保留了?—— 数据的内在结构、模式、规律
# 什么被丢弃了?—— 噪声、偶然变化、不重要的细节
GPT 的"压缩"更隐式,但同样强大:
训练数据:数十亿 token 的代码
模型参数:1.24 亿个浮点数(约 500MB)
压缩率: 数十 TB 的训练数据 → 500MB 的权重
这 500MB 的权重里,凝结了代码的全部"规律":
- 语法规则(def 后跟函数名)
- 命名惯例(变量用 snake_case)
- 算法模式(递归需要 base case)
- API 用法(torch.tensor 的参数)
- 编程范式(函数式、面向对象)
3. GPT 的"解码器"里藏着编码器:一个被名字误导的真相
这是一个极其重要的认知澄清。很多人(包括很多从业者)被"decoder-only"这个术语误导了。
3.1 术语的历史包袱
"Decoder-only"这个说法来自于对比原始 Transformer 论文的架构。原始 Transformer 有两个明确的组件:
原始 Transformer(2017,用于机器翻译):
[Encoder] [Decoder]
┌─────────────────┐ ┌─────────────────────────┐
│ │ │ │
│ Self-Attention │ │ Masked Self-Attention │ ← GPT 保留了这个
│ (双向) │ │ (单向/因果) │
│ │ │ │
│ Feed-Forward │ │ Cross-Attention │ ← GPT 删除了这个
│ │ ─────►│ (看编码器输出) │
│ │ │ │
└─────────────────┘ │ Feed-Forward │ ← GPT 保留了这个
│ │
输入:源语言 └─────────────────────────┘
"I love code" 输出:目标语言
"我 爱 代码"
GPT 做的事情是:拿走原始 Transformer 中标记为"Decoder"的组件,删掉 Cross-Attention,然后独立使用。 所以人们叫它"decoder-only"。
但这个名字极具误导性。
3.2 GPT 实际上同时在编码和解码
让我们仔细看看 CodeGPT 的前向传播到底做了什么:
# model.py:177-198 — CodeGPT 前向传播
def forward(self, idx, targets=None):
b, t = idx.size()
pos = torch.arange(0, t, dtype=torch.long, device=device)
# ====== 编码阶段 ======
tok_emb = self.transformer.wte(idx) # token → 向量(原始表示)
pos_emb = self.transformer.wpe(pos) # 位置 → 向量
x = self.transformer.drop(tok_emb + pos_emb)
for block in self.transformer.h: # 12 层 Transformer Block
x = block(x) # 逐层构建深度表征
x = self.transformer.ln_f(x) # 最终归一化
# ====== 此时的 x 就是编码后的表征! ======
# x 的形状:(batch, seq_len, 768)
# 每个位置的 768 维向量,凝结了该位置所有前文的"理解"
# ====== 解码阶段 ======
logits = self.lm_head(x) # 表征 → 词表概率分布
loss = F.cross_entropy(...) # 与真实 token 比较
关键洞见:12 层 Transformer Block 就是编码器! lm_head(一个线性层)才是解码器!
用更清晰的方式重新理解 GPT 的结构:
GPT 的真实结构(重新命名):
┌─────────────────────────────────────┐
│ 编码过程 │
│ │
│ "def fibonacci(n):" │
│ │ │
│ ▼ │
│ Token Embedding + Position Emb │ ← 原始表示
│ │ │
│ ▼ │
│ ┌─── Block 1 ───┐ │
│ │ Self-Attention │ │ ← 词法特征
│ │ Feed-Forward │ │
│ └───────────────┘ │
│ │ │
│ ...(×12 层) │ ← 逐层深化理解
│ │ │
│ ▼ │
│ 深度表征向量 │ ← 这就是"编码"的结果!
│ (768 维,蕴含了全部理解) │
│ │
└────────────────┬────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 解码过程 │
│ │
│ lm_head: Linear(768, 50304) │ ← 就一个矩阵乘法!
│ 表征向量 → 词表概率 │
│ "下一个 token 最可能是 return" │
│ │
└─────────────────────────────────────┘
所以真相是:GPT 的"解码器"里有一个巨大的编码器(12 层 Transformer)和一个极小的解码器(1 个线性层)。
3.3 和传统编码器-解码器的本质区别
那 GPT 的"编码"和传统编码器(如 BERT)有什么不同?
BERT 的编码器(双向):
"def [MASK] (n):"
每个位置可以看到所有其他位置
[MASK] 同时看到 "def" 和 "(n):"
→ 适合理解:我知道这里应该填什么词
GPT 的编码器(单向/因果):
"def fibonacci (n) :"
每个位置只能看到自己和前面的位置
":" 能看到 "def fibonacci (n)",但看不到后面
→ 适合生成:基于已知的,预测未知的
在 CodeGPT 的代码中,这个区别体现在因果掩码:
# model.py:47-48 — 因果掩码使编码过程变成单向的
torch.tril(torch.ones(config.block_size, config.block_size))
# 这个下三角矩阵就是 GPT "编码器"和 BERT "编码器"的唯一区别!
# BERT:全 1 矩阵(每个位置看到所有位置)
# GPT:下三角矩阵(每个位置只看到前面的位置)
整个 GPT 和 BERT 在架构上的区别,就是这一个掩码矩阵。 其他组件(Self-Attention、FFN、LayerNorm、残差连接)完全一样。
3.4 一个生动的类比
把原始 Transformer 的编码器-解码器想象成翻译官: - 编码器:听完整段英语,在脑中理解全部含义 - 解码器:根据理解,一个词一个词地说出中文
GPT 的做法更像一个即兴演讲者: - 没有预先准备好的"完整理解" - 每说一个词,就即时理解已经说过的所有内容(编码) - 基于这个理解,决定下一个词(解码) - 说完下一个词后,把它也纳入理解范围,继续
演讲者说出 "def"
→ 即时编码:这是函数定义的开始
→ 解码决策:下一个词应该是函数名
演讲者说出 "def fibonacci"
→ 即时编码:这是一个叫 fibonacci 的函数,大概率是递归
→ 解码决策:下一个词应该是 "("
演讲者说出 "def fibonacci(n):"
→ 即时编码:单参数的递归函数,接下来应该有 base case
→ 解码决策:下一个词应该是换行 + 缩进 + "if"
这个"即时编码 + 即时解码"的过程,在代码中就是自回归生成:
# model.py:272-305 — 自回归生成:每步都是编码+解码
for _ in range(max_new_tokens):
# 编码:用整个模型理解目前为止的所有 token
idx_cond = idx if idx.size(1) <= self.config.block_size else idx[:, -self.config.block_size:]
logits, _ = self(idx_cond) # ← 12 层 Transformer 编码 + lm_head 解码
# 采样:从解码出的概率分布中选择下一个 token
logits = logits[:, -1, :] / temperature
probs = F.softmax(logits, dim=-1)
idx_next = torch.multinomial(probs, num_samples=1)
# 将新 token 加入序列,下一步会一起被"编码"
idx = torch.cat((idx, idx_next), dim=1)
4. 没有编码就没有解码——这个理解对吗?
4.1 这个直觉是正确的
是的。没有编码就没有解码,这在任何智能系统中都成立。 因为"解码"本质上是"从内部表征映射到外部输出"——如果没有内部表征(编码),就没有什么可以映射的。
一个完全没有编码能力的"解码器"是什么?
输入: "def fibonacci(n):"
内部: 没有任何理解,每个 token 被独立处理,不知道上下文
输出: 完全随机的下一个 token
这就是一个随机数生成器,不是智能。
4.2 GPT 的编码是隐式的但极其强大
区别在于编码的方式:
显式编码器(BERT, 传统 Seq2Seq):
step 1: 读完整个输入 → 产生编码表征(一个固定的向量或序列)
step 2: 用表征生成输出
编码和解码是分开的两个阶段
隐式编码器(GPT):
每一步同时在做编码和解码
没有明确的"编码完成"的时间点
编码是流式的、增量的——每看到一个新 token,理解就更新一次
这其实更接近人类的实际思维过程。你读代码不是"先把整个文件都读完再理解",而是"读一行理解一行,逐渐建立整体理解"。
4.3 用数学看清"编码在哪里"
让我们精确追踪 CodeGPT 中信息的流动:
# 第一步:最浅的"编码"—— 查表
tok_emb = self.transformer.wte(idx) # 每个 token 独立映射为向量,互不知道彼此
# 第二步:添加位置信息
x = tok_emb + pos_emb # 每个向量知道自己在序列中的位置,但仍不知道其他 token
# 第三步开始:真正的编码(12层,逐层加深)
for block in self.transformer.h:
x = block(x)
让我们展开一个 Block 内部发生的事情:
# model.py:93-105 — Block 内部
def forward(self, x):
# 注意力层:让每个位置"看见"其他位置
x = x + self.attn(self.ln_1(x))
# 此时 x[i] 不再只是 token i 的信息
# 它已经融合了 token 0, 1, ..., i 的信息
# 这就是"编码"!
# FFN层:对融合后的信息做非线性变换
x = x + self.mlp(self.ln_2(x))
# 进一步提炼和抽象
return x
经过 12 层之后,序列中每个位置的 768 维向量包含了极其丰富的信息:
x[位置 5] 在第 0 层:
"这个 token 是 'n'"
x[位置 5] 在第 3 层:
"这个 token 是 'n',它是 fibonacci 函数的参数"
x[位置 5] 在第 6 层:
"这个 token 是 'n',它是递归函数 fibonacci 的参数,
类型可能是整数,用于控制递归深度"
x[位置 5] 在第 11 层:
"这个 token 是 'n',在计算 fibonacci 数列的语境中,
代表第 n 个 fibonacci 数,函数即将进入递归定义,
这段代码的风格是教科书式的递归实现"
每经过一层 Block,表征的信息量就更大、更抽象、更深入。这就是编码。
4.4 为什么叫"decoder-only"而不叫"unified model"?
纯粹是历史原因和学术惯例。GPT 论文的作者从原始 Transformer 的"decoder"组件出发进行修改,所以沿用了这个名字。如果用功能来命名,更准确的说法应该是:
"Decoder-only" → 更准确的名字: "Autoregressive Transformer"
(自回归 Transformer)
或者: "Causal Language Model"
(因果语言模型)
"自回归"描述的是它的工作方式(预测下一个 token),"因果"描述的是它的注意力模式(只看过去,不看未来)。这两个名字都不暗示它"没有编码能力"。
4.5 编码器-解码器分离 vs 统一:各自的优势
分离架构(T5、翻译模型):
优势:编码器可以双向看完整输入,理解更完整
劣势:需要两套参数,编码和解码之间有信息瓶颈
适合:输入和输出是不同语言/格式(翻译、摘要)
统一架构(GPT):
优势:同一套参数同时做编码和解码,更高效
劣势:因果掩码限制了只能看过去,无法利用未来信息
适合:输入和输出是同一种语言(代码生成、对话、续写)
但 CodeGPT 通过 FIM 弥补了这个劣势!
4.6 FIM:让 GPT "看到后面"的巧妙方法
CodeGPT 的 FIM 变换是一个绝妙的设计——它在不改变架构的前提下,让"decoder-only"模型获得了"看到后文"的能力:
# tokenizer.py:187-192 — FIM 变换
# 原始代码: [A B C D E F G]
# 切分: prefix=[A B] middle=[C D E] suffix=[F G]
# PSM 重排: <prefix> A B <suffix> F G <middle> C D E
# 现在模型在生成 middle 部分(C D E)时
# suffix(F G)在它前面,因此因果注意力可以看到!
# 效果:模型在生成 C D E 时知道后面是 F G
# 这就是"decoder-only 看到后文"的秘密!
标准 GPT 生成:
def add(a, b):
████████████████ ← 只知道上面有 "def add(a, b):"
return result ← 还不存在,看不到
FIM 模式:
<|fim_prefix|> def add(a, b): <|fim_suffix|> return result <|fim_middle|>
████████████████
模型现在要生成的内容在最右边
"return result" 在它左边(因果注意力可见!)
→ 模型知道"中间应该产生一个叫 result 的变量" → "result = a + b"
这就是为什么说"没有编码就没有解码"在 GPT 中依然成立——GPT 只是把编码和解码统一成了一个流程,但编码从未缺席。
5. CodeGPT 中的压缩与编码:代码级解剖
5.1 压缩发生在哪里?
CodeGPT 中压缩发生在多个层次:
层次 1:分词(tokenizer.py)—— 符号级压缩
# tokenizer.py:73-74 — BPE 分词就是一种压缩
self.base_enc = tiktoken.get_encoding("gpt2")
# BPE 的本质:找出最频繁的字符对,合并为一个 token
# "fibonacci" 可能被编码为 ["fib", "onacci"] 而不是 10 个字母
# 高频词 "return" 可能就是 1 个 token
# → 高频模式用更少的 token 表示 = 压缩
层次 2:嵌入层 —— 离散到连续的压缩
# model.py:145 — 50304 个离散 token → 768 维连续空间
wte=nn.Embedding(config.vocab_size, config.n_embd)
# 50304 个独热向量(每个 50304 维)→ 768 维密集向量
# 压缩率:50304 / 768 ≈ 65 倍
# 但更重要的是:语义相近的 token 在 768 维空间中距离更近
# "def" 和 "class" 比 "def" 和 "42" 更近
层次 3:Transformer Blocks —— 语义级压缩
# model.py:186-188 — 12 层逐步压缩
for block in self.transformer.h:
x = block(x)
x = self.transformer.ln_f(x)
# 每一层都在做信息的"提炼":
# 丢弃不重要的局部细节,保留全局结构
# 768 维没有变(维度不变),但信息密度越来越高
层次 4:lm_head —— 表征到概率的"解压"
# model.py:191 — 从 768 维解压回 50304 维
logits = self.lm_head(x)
# 768 维的"理解" → 50304 个 token 的概率分布
# 这是唯一的"解码/解压"步骤
5.2 训练过程就是在学习最优压缩
# model.py:192 — 交叉熵 = 衡量压缩效率
loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)
训练过程中 loss 的下降轨迹,就是模型压缩能力的提升曲线:
训练初期 (loss ≈ 10):
模型对代码一无所知
预测接近均匀分布(50304 个 token 等概率)
→ log(50304) ≈ 10.8
→ 每个 token 需要 ~10.8 bits 存储
→ 无法压缩
训练中期 (loss ≈ 3):
模型学会了基本语法
"def" 后面大概率是标识符
"if" 后面大概率是条件表达式
→ 每个 token 平均只需 3 bits
→ 已经能压缩大约 3 倍
训练后期 (loss ≈ 1.5):
模型深度理解代码
能根据上下文精确预测下一个 token
→ 每个 token 平均只需 1.5 bits
→ 压缩率约 7 倍
→ "智能"显著提升
5.3 一个具体的压缩例子
假设 CodeGPT 看到了这段代码的前几个 token:
def fibonacci(n):
if n <= 1:
return
模型在 return 之后需要预测下一个 token。一个训练良好的模型会给出:
token "n" : 概率 45% ← 最可能(return n 是标准 base case)
token "1" : 概率 20% ← 也常见(return 1)
token "0" : 概率 10% ← 可能(return 0)
token "None" : 概率 3% ← 不太可能但有可能
其他 50300 个: 概率 22% ← 分散在大量不太可能的 token 上
信息量 ≈ -log₂(0.45) ≈ 1.15 bits
如果不考虑上下文(均匀分布),信息量是 log₂(50304) ≈ 15.6 bits。
从 15.6 bits 压缩到 1.15 bits——这就是"理解"的量化度量。
6. 结语:理解比命名更重要
6.1 三个核心结论
1. 压缩就是智能,这不是比喻,是数学等价。
训练 CodeGPT 的交叉熵损失直接衡量压缩效率。loss 越低 = 压缩越好 = 对代码的理解越深。Shannon 的信息论为这个等价提供了严格的数学基础。
2. 婴儿学习和神经网络训练共享相同的学习范式。
| 婴儿学语 | 自编码器/GPT 训练 |
|---|---|
| 听(输入) | 读入训练数据 |
| 大脑处理(编码) | Transformer Blocks |
| 形成概念(表征) | 768 维向量空间 |
| 发音(解码/重构) | lm_head 预测 |
| 妈妈纠正(监督信号) | 交叉熵损失 |
| 反复练习(迭代) | 训练循环 |
| 从模仿到创造(泛化) | 在未见过的代码上生成 |
核心机制相同:通过反复的输入-压缩-重构-反馈循环,逐步建立深层次的内部表征。
3. GPT 不是"没有编码器",而是"编码和解码合为一体"。
真正的结构:
12 层 Transformer Block = 编码器(99% 的参数和计算)
1 层 Linear(lm_head) = 解码器(1% 的参数和计算)
"decoder-only"只是一个历史遗留的名字
它和 BERT "encoder" 的唯一区别是一个掩码矩阵
6.2 为什么这些理解对写代码有帮助?
当你理解了"压缩即智能",你就理解了为什么: - 更多数据几乎总是有用的——更多数据提供更多待压缩的模式 - 模型越大能力越强——更大的"压缩器"能捕捉更精细的模式 - FIM 如此有效——它让模型学习压缩"有空洞的上下文",拓展了压缩的维度 - loss 是最重要的指标——它直接告诉你模型的压缩能力(= 智能水平)
当你理解了"GPT 内部有编码",你就不会奇怪: - 为什么 GPT 能"理解"代码(12 层 Block 就是编码器) - 为什么上下文窗口那么重要(编码质量取决于能看到多少上下文) - 为什么 FIM 能工作(通过重排序让因果编码器"看到"后文)
延伸阅读: 但预训练只是 ChatGPT 成功的第一步。强化学习对齐(RLHF)如何让模型从"能说"变成"会说"?多头注意力、多语言训练为什么会让不同模型收敛到同一个表征?参见 强化学习对齐与柏拉图表征。