AI/ML 详解:可解释性

Day 27 · 2026-06-13 · 难度 ★★★★☆
面向:有编程经验的非 AI 方向工程师
工程对应 → super-individual D10: Hallucination 治理(把可解释性洞察落到防幻觉工程)

机制可解释性Mechanistic Interpretability

逆向工程残差流
一句话类比

训练好的神经网络就是一个没有源码的二进制——权重是「编译产物」,前向推理是「运行」,但没人写过它内部的逻辑。机制可解释性就是反汇编 + 单步调试:在不改权重的前提下,给这个黑盒挂上「断点」和「distributed tracing」,逐层看每个组件读了什么、写了什么、把哪一跳的信号传给了下游。目标不是「模型预测得准不准」,而是「它内部到底跑了什么算法」。

它解决什么问题 + 工作机制

传统 ML 可解释性(如 SHAP、特征重要度)只告诉你「哪个输入特征影响了输出」——这是黑盒外部的归因。机制可解释性更激进:它要还原模型内部的计算电路,像读懂一段汇编一样读懂权重在做什么。核心抓手是 Transformer 的一个结构事实——残差流(residual stream)

残差流 = 一条共享消息总线

token embedding 残差流 →→→→→→→→→→→→ unembed → logits
↑读 ↓写     ↑读 ↓写
Attn 头MLP ... 每层都在总线上累加自己的贡献

每个组件独立读总线、算结果、把结果回总线——线性可加,所以能拆解

关键洞察来自 Anthropic 的 A Mathematical Framework for Transformer Circuits(2021):因为每个 attention 头和 MLP 都是把结果回残差流(而非覆盖),整条总线就是各组件贡献的线性叠加——你可以把任意一跳单独「拆」出来看。最著名的发现是 induction head(归纳头):一种只在≥2 层模型里才出现的注意力回路,做的事是「在上文找到当前 token 上次出现的位置,把它后面那个 token 复制过来」——这正是 in-context learning(看几个例子就会照做)的核心机制之一。

代码示例
# pip install transformer_lens —— Neel Nanda 的机制可解释性标准库
from transformer_lens import HookedTransformer
model = HookedTransformer.from_pretrained("gpt2-small")

# run_with_cache:跑一次前向,同时把每一层的中间激活都"录"下来
tokens = model.to_tokens("The cat sat on the mat. The cat sat on the")
logits, cache = model.run_with_cache(tokens)

# 取第 5 层、第 1 个 attention 头的注意力权重矩阵
attn = cache["pattern", 5][0, 1]   # shape: [seq, seq]
# 看最后一个 token 把注意力放在哪——induction head 会指向上一次 "cat" 之后
print(attn[-1].argmax().item())  # 预期:指向第一句 "cat" 后面的位置
常见误区 + 实践场景
"可解释性 = 给输出配一段自然语言解释"——错。让模型自己解释自己(chain-of-thought、"为什么这么答")是 self-report,它可能是事后编的合理化,未必反映真实计算路径(Anthropic 的研究反复显示二者会背离)。机制可解释性的价值恰恰在于:它不信模型的自述,直接验尸内部电路。
📌 妈妈场景:你做跨学科思考时常问「这个系统为什么这样运作」。机制可解释性给了一个范式——对任何黑盒(包括组织、市场、甚至自己的认知习惯),先问「内部有没有一条可拆解的因果电路」,而不是停在「输入→输出」的相关性描述。
Takeaway + 思考题
💡 机制可解释性把神经网络当作「待反汇编的程序」,靠残差流的线性可加性把黑盒拆成可读电路。
🤔 如果模型的自我解释和它的真实电路会背离,你还敢把「让 AI 解释一下」当作可信审计手段吗?

稀疏自编码器Sparse Autoencoders (SAE)

字典学习叠加
一句话类比

模型只有 ~3000 个神经元,却要表示几万个概念——它的办法是把多个概念打包进同一个维度,像 bit-packing 把几个布尔标志塞进一个 int,或像哈希把无限的 key 映射到有限的桶。代价是单个神经元变得多义(polysemantic):一个神经元同时对「DNA 序列」「阿拉伯文」「法语」亮起,你读不懂它。SAE 就是那个解包器:把压在一起的表示「解压」回一组稀疏、单义的命名字段——每个字段只代表一个概念。

它解决什么问题 + 工作机制

这个「打包」现象有个名字叫 superposition(叠加):Anthropic 的 Toy Models of Superposition(2022)证明,当特征「又多又稀疏」时,网络会故意把 n 个特征压进远小于 n 维的空间里,靠特征极少同时出现来容忍冲突——这是一种有损压缩,也正是神经元读不懂的根因。

SAE 的机制是 dictionary learning(字典学习):训练一个超宽的自编码器,把 d 维激活向量 x 编码成一个远比 d 宽、但几乎全是 0 的特征向量,再解码重建回 x。数学上最小化:

L = ‖x − x̂‖²  +  λ · ‖f‖₁

· ‖x − x̂‖²(重建误差):解压后必须能还原原激活——保证没丢信息
· λ‖f‖₁(L1 稀疏惩罚):逼迫特征向量 f 里绝大多数维度=0,每次只有几个亮
· λ 调大 → 更稀疏更单义,但重建更差;这是核心 trade-off

为什么 L1 惩罚能逼出单义?因为「只许少数维度激活」等价于强迫每个维度去抢占一个独立、可复用的概念,而不是和别人合用。Anthropic 的 Towards Monosemanticity(2023)在一个小模型上用 16× 宽的 SAE 跑出近 15000 个特征,人工评估约 70% 干净对应单一概念;2024 的 Scaling Monosemanticity 把它放大到生产级的 Claude 3 Sonnet,找到了著名的「金门大桥特征」——把它的激活强行钳到 10× 最大值,模型会满嘴都是金门大桥、甚至自认为就是那座桥。这证明特征不只是相关,而是因果可操控的。

代码示例
# pip install sae-lens —— 加载社区预训练好的 SAE,不必自己训
from sae_lens import SAE
from transformer_lens import HookedTransformer

model = HookedTransformer.from_pretrained("gpt2-small")
sae, cfg, _ = SAE.from_pretrained("gpt2-small-res-jb", "blocks.7.hook_resid_pre")

_, cache = model.run_with_cache(model.to_tokens("The Golden Gate Bridge is"))
acts = cache["blocks.7.hook_resid_pre"]      # 第 7 层残差流激活

features = sae.encode(acts)                    # 解包成超宽稀疏特征向量
top = features[0, -1].topk(5)               # 最后一个 token 上最亮的 5 个特征
print(top.indices, top.values)             # 每个 index 对应一个可解释概念
# 用 Neuronpedia 查这些 index 的含义:neuronpedia.org
常见误区 + 实践场景
"SAE 找到的每个特征都是干净的人类概念"——别太乐观。约 30% 特征仍然模糊或难解释;SAE 还会受 重建-稀疏 trade-off 影响,λ 不同跑出的字典不一样,存在「特征劈裂」(一个概念被拆成几个近义特征)等已知问题。SAE 是当前最好的解包工具,但不是完美字典
📌 妈妈场景:superposition 是绝佳的跨学科隐喻——容量有限的系统如何承载远超容量的信息?大脑、语言、甚至你一天的注意力都在「叠加」。它和你熟悉的哈希冲突、有损压缩、复杂性科学里的「维度坍缩」是同一族思想,可以互相照亮。
Takeaway + 思考题
💡 神经元读不懂,是因为概念被「叠加」压进了少数维度;SAE 用字典学习 + L1 稀疏把它解包回单义特征。
🤔 如果一个特征能被强行钳住来改变模型行为(金门大桥),那「编辑模型的信念」离「编辑人的信念」在原理上还有多远?

特征回路Feature Circuits

调用链因果
一句话类比

单个特征像一个微服务,特征回路就是它们组成的调用链 / DAG:早层特征「检测到这是个人名」→ 中层特征「这是法国地名」→ 后层特征「该输出法语」。特征是节点,权重是边,整张图就是一份 attribution graph(归因图)——和你画过的微服务依赖图、Spark DAG 是同一种东西。机制可解释性的终极目标,就是把模型的某个行为还原成一条可读、可验证的回路

它解决什么问题 + 工作机制

知道「有哪些特征」(SAE 干的事)只是元件清单,不等于懂电路。要懂行为,得知道特征怎么连、谁触发谁。验证连接的核心方法是 activation patching(激活打补丁,又叫 causal tracing)——一种干净的因果实验:

Activation Patching:A/B 灌流做因果消融

1. 跑「干净」输入 clean,缓存某组件激活
2. 跑「损坏」输入 corrupt(答案被破坏)
3. 把 clean 的某一个激活,移植进 corrupt 的前向里
4. 看输出恢复了多少 恢复越多 = 这个组件越因果关键

= 给分布式系统逐个换组件做 A/B,定位「哪一跳真正决定了结果」

这比单纯看「哪个神经元激活高」强得多:高激活只是相关,而 patching 是主动干预——它直接回答「拿掉/替换这一跳,结果会不会变」。把每条边都这样验一遍,就能拼出一张 attribution graph。2025 年 Anthropic 的「circuit tracing / attribution graphs」工作把这套方法用到生产级 Claude 上,画出了多步推理、诗歌押韵、心算等行为背后的真实回路——发现模型有时会提前规划(写诗时先想好韵脚再倒推句子),这是 self-report 完全看不到的。

代码示例
# 用 activation patching 定位"哪个层对正确答案最关键"
import torch
clean = model.to_tokens("The Eiffel Tower is in the city of")
corrupt = model.to_tokens("The Colosseum is in the city of")
ans = model.to_single_token(" Paris")

_, clean_cache = model.run_with_cache(clean)

def patch_layer(corrupt_act, hook, layer):
    # 把 clean 的残差流移植进 corrupt 的前向
    corrupt_act[:] = clean_cache["resid_post", layer]
    return corrupt_act

for L in range(model.cfg.n_layers):
    logits = model.run_with_hooks(corrupt, fwd_hooks=[
        (f"blocks.{L}.hook_resid_post", lambda a,h: patch_layer(a,h,L))])
    print(L, logits[0,-1,ans].item())   # 哪层一patch就让 "Paris" 概率飙升 = 关键层
常见误区 + 实践场景
"画出一张回路图就等于完全理解了模型"——别过度自信。现有 circuit 多是在窄任务(间接宾语识别、特定事实回忆)上局部还原,离「读懂整个模型」差着数量级;回路之间还会叠加、干扰。它是真实的进步,但远非「全反编译」。诚实地说:我们能读懂的,还只是这台机器的几个零件。
📌 妈妈场景:activation patching 的思维——「想知道某一跳是否真因果,就移植/替换它,看结果变不变」——可直接搬到你做决策复盘:与其争论「是不是 X 导致了结果」,不如设计一个能反事实替换 X 的小实验。这是分布式系统的 chaos engineering 用在认知上。
Takeaway + 思考题
💡 特征是元件,回路才是程序;用 activation patching 做因果消融,才能把「相关」升级成「电路」。
🤔 如果模型写诗时被证实会「提前规划韵脚」,那它的「逐 token 生成」表象下,藏着多少我们以为不存在的隐式计划?

探针Probing

只读探针诊断
一句话类比

探针就是给运行中的系统挂一个只读 APM probe:在模型某一层的激活上接一个 tap,训一个极轻量的线性分类器,问「这一层的内部状态里,有没有编码某个信息(词性?情感?真假?)」。探针不改模型、不参与训练,纯粹是事后抽头读总线——和你在内部消息总线上接 packet sniffer 看「这条数据流里有没有携带字段 X」一模一样。

它解决什么问题 + 工作机制

SAE 和 circuit 都偏重、偏研究级;探针是最轻量、最早、最实用的可解释性工具,源头是 Alain & Bengio 的 Understanding intermediate layers using linear classifier probes(2016)。机制极简:冻结模型,取第 ℓ 层激活当特征,训一个线性分类器去预测你关心的属性。关键约束是「线性」

  • 如果一个简单的线性探针就能从某层读出「情感」,说明该层已经把情感线性可分地编码好了——信息「现成可用」;
  • 如果必须上深层非线性探针才读得出,那多半是探针自己学会的,不能算模型「表示」了它。

Alain & Bengio 用线性探针扫 ResNet,发现线性可分度随层数单调上升——底层是边缘纹理,越往上越抽象。这给了「深度网络逐层提炼表示」一个可量化的证据。

代码示例
from sklearn.linear_model import LogisticRegression
import numpy as np

# 收集每条句子在第 6 层、最后一个 token 的激活,配情感标签
X, y = [], []
for text, label in dataset:          # label: 1=正面 0=负面
    _, cache = model.run_with_cache(model.to_tokens(text))
    X.append(cache["resid_post", 6][0, -1].numpy())
    y.append(label)

# 线性探针:能线性读出 = 该层已"现成"编码了情感
probe = LogisticRegression(max_iter=1000).fit(np.array(X), y)
print("线性可分度(探针准确率):", probe.score(np.array(X), y))
# 在不同层重复 → 看情感信息在第几层"成型"
常见误区 + 实践场景
"探针读得出 X,就证明模型了 X 来决策"——最经典的陷阱。探针只证明信息存在于激活里(相关),不证明模型在前向中真的用了它(因果)。某层「能读出语法」不代表预测时依赖语法——要验因果,得回到第 3 张卡的 activation patching。探针回答「有没有」,patching 回答「用没用」,两者分工别混。
📌 妈妈场景:探针是性价比最高的诊断起点。想知道某个开源模型「内部到底懂不懂法律实体 / 医学概念」,不用拆电路,先训几个线性探针扫各层,10 行代码就能给出「这信息几乎不存在 / 第 N 层才成型」的快速体检——再决定要不要微调或换模型。
Takeaway + 思考题
💡 探针是只读抽头:用「线性可分」当门槛,量化「某层有没有现成编码某信息」——轻、快,但只测相关、不测因果。
🤔 「信息存在」和「信息被使用」的鸿沟,在你熟悉的系统(日志里有字段 ≠ 业务逻辑读了它)里是不是也天天发生?
工程对应 → super-individual D10:Hallucination 治理

深入资源Further Reading

深入思考Deep Questions

1. 探针、SAE、activation patching 三者都想「理解模型」,它们在「相关 vs 因果」这条轴上各处什么位置?该怎么组合用?
这是本期最核心的串联。三者是一条从相关到因果、从粗到细的流水线。探针最便宜,回答「某层有没有编码 X」——纯相关,证明信息存在,但不证明被使用(探针读得出语法 ≠ 模型预测时用语法)。SAE解决「神经元读不懂」的叠加问题,把激活解包成单义特征——它给你一份可命名的元件清单,比探针的「有没有」更细,但单看特征激活仍是相关。activation patching 才上升到因果:通过主动移植/消融某一跳,回答「拿掉它结果变不变」。实战组合:先用探针快速扫层定位「信息大概在第几层成型」(10 行代码体检)→ 在关键层用 SAE 解包出候选特征(拿到可解释元件)→ 用 patching 验证哪些特征/组件真因果(拼成回路)。记成:探针问「在哪」,SAE 问「是什么」,patching 问「是不是它干的」——正是分布式的 tracing → 拆服务 → chaos 注入在认知上的迁移。
2. Anthropic 反复发现「模型的自我解释(CoT / self-report)会和它的真实电路背离」。这对「让 AI 解释自己」这种治理手段意味着什么?
意味着把 self-report 当审计证据是危险的。模型生成「我之所以这么答,是因为…」时,它在做的是生成一段看起来合理的解释文本,而不是读取并报告自己的真实计算——这两件事在架构上根本不是一回事。circuit tracing 的研究里有直接案例:模型嘴上说按某步骤推理,电路里走的却是另一条路(比如心算时嘴上说「进位」,电路里用的是一组并行的近似查表特征)。三层含义:(1) 对齐与安全不能只靠「让模型坦白」——会骗人的模型,「坦白」也能是编的;机制可解释性是少数能绕过自述、直接验尸的手段,是 AI 安全的关键基础设施;(2) CoT 仍有用——它能真实改善计算(多写中间步=更多 compute),但「CoT 文本」≠「忠实的内部日志」,二者差距本身要被监控;(3) 对防幻觉工程(见 D10)的启示:与其让模型「自评有没有幻觉」,不如用外部可验证的手段(检索佐证、一致性采样、特征级监控)——别把裁判权交给被告。
3. superposition 说「容量有限的系统被迫把超量信息叠加进少数维度」。这个原理在大脑、语言、组织里是否同样成立?它是 bug 还是 feature?
大概率是普遍原理,而且是 feature 不是 bug。superposition 的成立条件很一般:要表示的概念远多于可用维度,且这些概念很少同时出现(稀疏)——满足这两条,任何有限系统都会「理性地」选择叠加,用「极少冲突」换「极高容量」。大脑:神经元数量远不足以「一概念一神经元」,分布式、叠加式编码几乎是必然,这也解释了为什么单电极很难读懂单个神经元(和多义神经元同构)。语言:词汇有限却要表达无限意义,靠一词多义 + 上下文消歧——这就是语言层的 superposition。组织:一人身兼数职、一个流程承载多个目标,也是容量受限下的叠加,代价同样是「读不懂某个角色在干嘛」。把它当 feature 的理由:叠加是容量与可分性之间的最优折中——冲突够稀疏时收益远大于偶发干扰。它变成 bug,是当「稀疏假设被打破」(概念开始频繁共现)冲突爆发——对应大脑的认知过载、组织的角色冲突、模型的特征干扰。对「AI 超级个体」的提醒:你给自己叠加的角色越多,越要守住「它们很少同时被触发」这条前提,否则就从高容量滑向高干扰。
4. 如果有一天能完整反编译一个前沿模型的所有电路,会发生什么好事和坏事?「完全可解释」是纯粹的福音吗?
好处显而易见:可审计(提前发现欺骗、后门、危险能力的电路)、可精准编辑(像金门大桥那样定点改信念,去偏见、纠错而不全量重训)、可验证对齐(直接看模型「想不想」做某事,而非只看它说什么)。但「完全可解释」绝非纯福音,至少三重张力:(1) 能力即风险对称——能定点编辑信念的工具也能定点植入信念;能读出危险能力的方法也能帮人提取它。可解释性是 dual-use 的。(2) 反编译者会被对抗——一旦「电路可读」成为审计标准,就会出现「训练出对抗可解释性的模型」(把危险电路藏进更深的叠加里),是军备竞赛而非终局。(3) 哲学层面的不适——若「信念=可被外部钳位的特征」对模型成立,它对人类心智的隐喻让很多人不安:自由意志、说服、洗脑的边界都被重新逼问。我的判断:可解释性净值为正且是必需品(不理解就无法安全部署强模型),但它不是「装上就安全」的开关,而是把安全从「黑盒信任」转成「谁有权读写这些电路」的治理问题。技术解决「能不能看」,看完之后「谁能改、改什么」是更难的社会选择。
5. 把机制可解释性的方法论(不信自述、找可拆解的因果电路、用反事实干预验证)搬到「理解自己的认知」上,会得到什么?
会得到一套相当反直觉但强大的自我认知纪律。逐条映射:(1) 不信 self-report → 你对「我为什么这么决定」的事后解释,很可能和真实驱动你的「电路」背离——就像模型的 CoT。承认这点就是去自欺的第一步:理由是合理化,不一定是原因。(2) 找可拆解的因果电路 → 与其用「我就是焦虑」描述自己,不如追问「有没有一条可命名的触发链」(情境→念头→行为)。这和把「模型行为」拆成「特征回路」同构,也呼应佛学「缘起」——一切是条件聚合的链,非单一本质。(3) 反事实干预验证 → 别停在内省(相关),做 activation patching 式的小实验:移除/替换某条件看行为变不变,才知道哪个是真因——把 chaos engineering 用在心智上。深层启示:机制可解释性和内观/正念惊人地相通——都拒绝相信表层叙事,都直接观察「过程本身」,都把「自我」看成可拆解的过程的聚合而非不可分的黑盒。区别只在工具:一个用 patching 和 SAE,一个用持续的觉察。这两条路指向同一件事——把任何黑盒(模型或自己)变成可读、可验证、可干预的系统