
如果你最近在跟进大模型可解释性大概率会遇到一个实际问题从稀疏自编码器Sparse Autoencoder里拿到几万个“特征方向”之后怎么给它们起名字、写解释、做验证成了流水线上最费人力的环节。传统做法是把特征在语料上激活最高的文本片段抓出来再丢给大模型写一段“这个特征在描述什么”。这套流程能跑通但本质上是在做“示例归纳”不是在做“表示翻译”。特征本身是一个高维向量解释却是自然语言中间全靠文本样本这座桥来连接。于是当特征比较抽象、激活样本不够典型、或者语料覆盖不到时解释质量就会明显下降。SAEVerbalizer 这类工作试图换一个切入点既然特征方向存在于模型的残差流表示空间里那么能不能找到一种“词汇化 / 口头化”的方式把表示空间中的方向直接对应到一段可读标签或短语上本文从理解论文主题的角度出发梳理 Representation Verbalization 的核心思想、它与传统特征解释流程的差异、以及一套可供上手的工程演示。需要先说明以下内容不是对该论文官方代码的逐行复现而是基于公开标题、同类工作范式和技术推理给出的讲解与示例具体实现细节请以论文原文和开源仓库为准。1. 这篇工作真正要解决的问题先说结论SAEVerbalizer 瞄准的是“特征解释生成”这一环而不是 SAE 本身。它想解决的痛点是我们如何让模型内部一个无名的向量方向变成一个人类可以直接阅读、并且可以被自动验证的解释。在可解释性研究中SAE 把模型的残差流激活拆解成稀疏的、大致单义的组件。理论上每个组件对应一个特征比如“某个代码片段的开头”“HTML 标签a的出现”“表示否定含义的上下文”等。但问题是SAE 只输出方向和激活值不会自动告诉你“这个特征是什么意思”。解释这件事目前严重依赖外部流程。你可以把 SAE 特征理解为一座图书馆里的几十万本书每本书都有编号但没有书名。传统方法相当于翻开书找高频段落再请一位阅读速度很快的助手概括这些段落大意。而 Representation Verbalization 的思路更接近直接根据书的“检索标签”和它在书架上的位置快速生成一个候选书名然后再用少量抽样校验这个书名是否靠谱。最应该读这篇文章的读者有三类。第一类是在做 LLM 内部机制分析的研究者想把特征解释从“人工看激活样本”升级成半自动流水线。第二类是在用 SAE 分析模型安全、幻觉、越狱等问题的工程师他们需要快速获得可量化的特征标签而不是一篇篇翻词云。第三类是刚接触可解释性、想弄清楚 SAE 和 AutoInterp 之间关系的学习者。如果你只是想让模型输出更好暂时不需要深入特征解释那这篇文章可以收藏后延后阅读。2. Sparse Autoencoder Features基础概念与核心原理2.1 稀疏自编码器在做什么要理解 SAEVerbalizer先要理解 SAE 特征是怎么来的。我们可以把大模型某一层的残差流激活记为向量 a它包含的信息是混合的、稠密的。SAE 做的事情是学习一个过完备字典把 a 近似拆成a ≈ b_dec W_dec · z其中 z 是一个稀疏向量绝大多数维度为 0只有少量维度有非零激活W_dec 的每一列是一个“特征方向”b_dec 是偏置项。训练时通常要同时优化重构损失和稀疏性约束比如 L1 惩罚或者直接限制每个 token 上只有 top-k 个特征被激活。这里的“特征方向”非常关键。在 SAE 训练完成后每个特征不只是“某几个文本片段像它”它本质上是残差流空间中的一个方向向量。某些特征激活在特定的 token 上某些特征则跨 token 跨位置激活比如某个特征负责表示“这是一个 Python 函数定义的开头”另一个特征负责表示“当前上下文正在讨论与法律相关的概念”。在工程上特征的数量通常远大于模型隐藏层维度。比如 GPT-2 这类小模型公开的 SAE 权重可能包含数万甚至几十万个特征。每个特征都对应一个索引但索引本身没有可读含义这也就是“解释”环节存在的根本原因。2.2 “特征”和“解释”为什么不能画等号一个常见的误区是某个特征激活高就说明特征“代表”对应的文本含义。实际上特征激活只能说明“这个方向与当前上下文相关”不能说它一定在因果层面驱动了模型后续输出。解释是一种事后归纳SAE 特征方向是模型内部计算路径上的一个结构。两者之间有很强的相关性但并不是完全等价。这也是 SAEVerbalizer 这类工作的价值所在它不满足于“找到激活样本”而是尝试从特征方向本身出发在词汇或语义表示空间中寻找与它对齐的语言标签。用一句概括就是把“特征是什么”从检索问题变成对齐问题。下面用一个表格对比三种常见“特征解释”思路思路核心依据优点主要问题Top-k 激活样本展示文本片段直观、易实现依赖语料覆盖样本可能不典型LLM 自动总结激活样本 大模型可扩展能生成自然语言解释偏差大难以定位到精确语义Representation Verbalization特征方向与词汇/短语表示的相似性直接利用表示空间信息候选集合构建和验证仍需仔细设计3. 传统解释方法为什么还不够现在常见的特征解释流程可以拆成四步。第一步准备一段覆盖各种领域的文本语料第二步用 SAE 编码语料中每一层的激活记录大量 token 位置上的特征激活值第三步对某个目标特征取出激活值最高的若干片段第四步把这些片段交给一个文本能力较强的 LLM让它总结“这些片段里出现的模式”生成一段解释。这套流程看起来简单但实际用起来会遇到三个麻烦。第一语料偏差会直接影响解释方向。一个特征如果只在某个不常见的领域里出现而语料里恰好缺少这些内容那么取出的 top-k 激活片段就会是噪声LLM 得到的解释也会偏。即使语料覆盖很广那些激活极高的片段也往往集中在特殊格式上比如换行、引号、URL 等而不是真正有语义代表性的内容。第二解释本身很难被自动验证。假设 LLM 写出“这个特征在表示 Python 代码里的缩进块”我们怎么确认它说得对一种方法是在新的文本上用这段解释去预测特征是否激活然后计算解释的预测准确率。但这种验证仍然是间接的它验证的是“解释文本能否有效区分正负样本”而不是“解释文本与特征方向在表示空间里是否真的对齐”。第三整个流程耗时不低。要获得一个可靠的解释往往需要抽取大量 token 的激活值、清洗重复片段、多次调用 LLM 总结和评分。对于只有几千个特征的小规模实验还能接受一旦要解释几万个特征成本就会快速上升。传统方法的本质是用“行为证据”推断“内部结构”。而 Representation Verbalization 的差异在于它试图在“结构”这一层直接找到语言对应物行为证据只用来做后续验证。4. Representation VerbalizationSAEVerbalizer 的核心思路4.1 “Verbalizer”这个概念的来源“Verbalizer”这个词来自提示学习Prompt-based Learning。在少样本分类任务中模型并不会直接输出“正面”或“负面”这样的类别名而是输出某个 token 的概率。这时候需要设计一个映射表把“positive”映射到 “great”“good”等 token把“negative”映射到 “bad”“terrible”等 token再根据这些 token 的概率决定最终标签。这个映射表就是 verbalizer。SAEVerbalizer 把同一个思想迁移到了 SAE 特征解释上一个 SAE 特征可以被看作一个“没有名字的标签”。如果我们能在词汇表或短语集合中找到一组与该特征方向高度对齐的“候选词”那就等于给这个标签找到了一个 verbalizer。有了 verbalizer再让语言模型基于候选词组织成一句完整解释就会比直接看激活样本更稳。4.2 从特征方向到解释的可能流程虽然不同论文的实现细节会有差异但“Representation Verbalization”这个技术路线通常包含四个环节。第一个环节是准备特征方向。训练或加载好 SAE 后从解码器权重中取出目标特征对应的列向量并做归一化。第二个环节是构造候选 verbalizer 集合。候选可以来自模型词表里的 token也可以来自高频短语还可以来自 LLM 根据少量激活样本先生成的一批描述短语。第三个环节是语义对齐。关键步骤是把特征方向和候选短语都投影到同一个表示空间里通常是模型的残差流空间。对候选短语可以取它最后一个 token 位置的残差表示对特征就取归一化后的特征方向。然后计算两者之间的余弦相似度或点积选出最匹配的候选标签。第四个环节是生成解释与验证。得到候选标签后再用 LLM 生成更完整的解释并设计自动评测任务判断解释是否可靠。整个过程比传统“示例总结”更接近“翻译”特征方向是源语言自然语言是目标语言候选标签集合是词表。这里必须强调一点以上四环节是我根据论文标题和技术背景做的合理推断不是从论文正文中逐字抄录的实现细节。不同工作对“如何构造候选”“如何训练对齐目标”“是否用对比学习”会有明显区别。读论文时可以带着这套框架去对照作者实际做了什么。4.3 为什么对齐方式比示例归纳更值得关注传统方法把解释问题转换为“给定一组文本归纳共同模式”这本质上属于聚类和总结问题。而 Representation Verbalization 把解释问题转换为“给定一个表示方向在语言表示中发现最接近的邻居”。后者的优势在于它不依赖目标特征必须频繁出现在某个语料子集中只要模型的词表或候选短语集合中存在可以表达该语义的语言单元对齐就可能成功。当然这种方案也有局限性。词表 token 往往是碎片化的比如一个 token 只是半个词直接作为可读标签效果很差。因此更稳妥的设计可能是多级 verbalization先用对齐找出语义相近的少量 token 或短语再让 LLM 生成通顺的自然语言解释。这解释了为什么论文标题强调的是“Generating Explanations”而不是简单的“Labeling Features”真正产出的是解释不是孤立标签。5. 环境准备与前置条件如果想动手验证这套思路不需要一开始就在几十亿参数的大模型上跑。对小规模实验来说GPT-2 这类模型加公开的 SAE 权重就足够了。下面的环境说明以通用实践为主版本号请以你实际安装时为准。建议准备环境如下组件用途建议Python运行脚本3.10 或更高PyTorch模型前向计算按本机 CUDA 版本安装transformer_lens加载模型和获取缓存激活从官方仓库安装sae_lens加载公开 SAE 权重版本间 API 有差异请锁版本datasets 或自定义语料提供文本样本小规模实验可先用手写句子一台带 NVIDIA GPU 的机器加速推理小模型 CPU 也能跑但会慢在动手前先明确一点SAE 权重必须和模型匹配。例如你在 GPT-2 的某层残差流上训练 SAE加载时就要用同一层输出的 hook 名称。很多初学者在加载 SAE 后直接在某层激活上做编码结果维度对不上往往就是因为 SAE 训练的 hook 层和当前取激活的层不一致。如果是在公司内部网络环境下载模型和 SAE 权重失败时不要使用非正规代理手段应该通过内部镜像源或提前下载好权重文件再离线加载。模型权重和 SAE 权重大多有各自的开源协议使用时要注意授权边界尤其是商业化场景。6. 从“特征激活”到“口头化标签”的演示下面给出一个最小可实践流程。代码主要用于理解思路不对应任何论文的官方实现。为了让读者跑得起来我尽量选择稳定 API但如果你的库版本较新个别字段名需要按库文档调整。6.1 安装依赖pip install torch transformer-lens sae-lens datasets安装完成后可以用 Python 检查关键库能否正常导入python -c import torch, transformer_lens, sae_lens; print(ok)如果导入失败优先检查 PyTorch 是否安装成功再检查 transformer_lens 和 sae_lens 的版本兼容性。这两个库更新较快出现 API 不兼容时不要急着换版本先看官方仓库的 Release 说明。6.2 加载模型与稀疏自编码器# 文件路径load_sae.py from transformer_lens import HookedTransformer from sae_lens import SAE # 在 CPU 上跑通流程之后可以换成 cuda model HookedTransformer.from_pretrained(gpt2-small, devicecpu) # 请以你实际下载到的 release 和 sae_id 为准 sae, cfg_dict, sparsity SAE.from_pretrained( releasegpt2-small-res-jb, sae_idblocks.6.hook_resid_pre, devicecpu, ) print(hook_name:, sae.cfg.hook_name) print(d_model:, sae.cfg.d_model) print(d_sae:, sae.cfg.d_sae)这段代码的作用是同时加载模型和 SAE 权重。hook_name表示 SAE 是在模型的哪个中间层训练出来的后续取残差流激活时必须使用相同位置。d_model是模型隐藏层维度d_sae是特征数量。如果这一步报错最常见原因是release名称与本地缓存不一致。先去sae_lens的文档或模型仓库页确认可用的 release 名称不要照抄本文的字符串。6.3 在文本上提取特征激活加载完成后我们可以让模型读取一段文本然后在目标层取出残差流激活再用 SAE 编码得到稀疏特征激活向量。# 文件路径extract_feature_activations.py import torch from transformer_lens import HookedTransformer from sae_lens import SAE model HookedTransformer.from_pretrained(gpt2-small, devicecpu) sae, _, _ SAE.from_pretrained( releasegpt2-small-res-jb, sae_idblocks.6.hook_resid_pre, devicecpu, ) feature_idx 1000 # 先用一个固定索引做演示 prompts [ In Python, the best way to iterate is to use a for loop., The cat sat on the mat and looked at the window., def add(x, y):\n return x y, ] tokens model.to_tokens(prompts) _, cache model.run_with_cache(tokens) hook_name sae.cfg.hook_name resid cache[hook_name] # [batch, seq_len, d_model] with torch.no_grad(): feature_acts sae.encode(resid) # [batch, seq_len, d_sae] # 观察目标特征在第 0 条样本上的激活分布 target_acts feature_acts[0] print(max activation:, target_acts[:, feature_idx].max().item()) print(number of non-zero features in this sample:, (target_acts[0] 0).sum().item())这段代码的关键在于hook_name的使用。很多初学者把run_with_cache得到的全部层缓存里随意挑一层结果和 SAE 训练层不一致得到的特征激活分布没有意义。正确做法是直接读取sae.cfg.hook_name。feature_acts的维度会随 sae_lens 版本略有不同。有些版本返回一个包含额外维度的张量有些版本则直接返回[batch, seq_len, d_sae]。运行后打印形状如果与代码注释不一致就以实际张量形状为准。6.4 用“表示对齐”生成候选标签这一步是 Representation Verbalization 的最小原型。我们先取目标特征的解码器方向然后把它投影到模型的词汇输出空间找到最接近的若干 token。这个操作可以解释为该特征方向在“词汇语义轴”上最指向哪些词。# 文件路径verbalize_demo.py import torch from transformer_lens import HookedTransformer from sae_lens import SAE model HookedTransformer.from_pretrained(gpt2-small, devicecpu) sae, _, _ SAE.from_pretrained( releasegpt2-small-res-jb, sae_idblocks.6.hook_resid_pre, devicecpu, ) def feature_top_tokens(feature_idx: int, k: int 10): # W_dec: [d_sae, d_model]每一行对应一个特征方向 feature_direction sae.W_dec[feature_idx] # W_U: [d_model, d_vocab_out]把方向投到词表输出空间 scores feature_direction model.W_U top_indices scores.topk(k).indices tokens [] for idx in top_indices.tolist(): token_str model.tokenizer.decode([idx]).strip() tokens.append(token_str) return tokens for idx in [1000, 2333, 8888]: try: print(idx, feature_top_tokens(idx)) except IndexError: print(ffeature {idx} out of range)运行这段代码后你会看到部分索引返回了可读的 token比如def、print、http等也可能看到很多无意义的标点或空格 token。这种情况是正常的原因在于词表中的 token 本身就是碎片化的单个 token 并不等于完整概念。因此更接近论文思路的做法是引入“短语级候选”然后比较候选短语的末位表示与特征方向的余弦相似度# 文件路径phrase_alignment.py def score_candidate_feature(candidate: str, feature_idx: int): _, cache model.run_with_cache(model.to_tokens([candidate])) # 取候选短语最后一个 token 位置的残差表示 last_pos_rep cache[sae.cfg.hook_name][0, -1] feature_direction sae.W_dec[feature_idx] return torch.cosine_similarity(feature_direction, last_pos_rep, dim-1).item() candidates [ Python function definition, an opening parenthesis, HTML link, the word system, mathematical equation, ] for cand in candidates: print(cand, score_candidate_feature(cand, feature_idx))这里用到的思想是如果候选短语的语义与特征方向一致那么它们在表示空间中应该更接近。实际论文可能还会引入对比学习、候选筛选、困惑度过滤等机制但核心直觉是一样的。6.5 从候选标签生成可读解释当候选短语确定后可以把它交给 LLM 生成完整解释而不是直接输出 token 列表。这个过程不是简单拼接而是给 LLM 提供候选短语和少量样本让它生成一个可验证的描述候选语义Python function definition 激活样本片段def add(x, y): return x y 请用一句话描述这个特征可能代表的含义。到这一步“表示空间对齐”负责提供可信线索“语言模型生成”负责把线索变成人类可读文本。再把生成结果放回自动评测流程就构成了解释生成闭环。7. 运行结果验证怎么判断解释靠不靠谱解释不是写出来就完事了。一个解释哪怕读起来通顺也可能是大模型在“编故事”。因此必须有一套验证方法来判断“由 Representation Verbalization 得到的特征解释”是否真的对应特征行为。最简单的验证是正负样本分类。在一个文本语料上用 SAE 计算目标特征的激活值。把激活值高于某个阈值的 token 位置作为正样本随机选取没有激活的位置作为负样本。然后把正负样本随机混合交给一个判断模型让它根据你生成的解释判断“这段文本是否会让该特征激活”。如果判断准确率接近随机说明解释很可能只是表面通顺没有真正抓住特征行为。更进一步的验证是因果扰动实验。取出该特征方向在残差流激活上添加正向或负向的扰动观察模型输出分布是否朝解释所描述的方向变化。例如某特征被解释为“表示 Python 代码”在正向扰动后模型在下一个 token 上生成def或print的概率应该明显上升。这种验证比文本分类更有说服力因为它直接检验了解释对应的表示方向是否具备真实的因果效应。下面整理一个结果验证与排查表现象可能原因排查方式处理建议SAE 加载失败release 或 sae_id 不匹配打印 cfg_dict核对缓存目录按官方仓库的可用列表修改名称特征激活全部为 0取的 hook 层与 SAE 训练层不一致用 sae.cfg.hook_name 取缓存统一 hook 层名称token 标签都是标点单 token 粒度太碎打印 top 50 token 观察改用短语级候选集合余弦相似度普遍很低候选短语与特征语义差异大检查候选集合覆盖范围用 LLM 先生成一批候选短语判断模型预测接近随机解释没有抓住特征行为做因果扰动实验修正解释或改用更细粒度特征运行时显存不足一次编码的 token 过多观察 batch 和序列长度分批推理减小 batch对于最后一个问题还要补充一句显存不足不一定是代码问题也可能是你拿整段长文本直接做 cache。更推荐的做法是把语料切成较短的片段逐批提取特征激活并保存结果避免一次加载过多 token。8. 工程实践与注意事项以下是给准备把特征解释做成工程模块的读者的一些建议。第一把特征解释过程拆成可缓存的三个模块特征激活提取、候选标签对齐、解释生成与评测。三者之间用中间文件通信。不要每次解释都重新跑一遍模型前向否则成本非常高。特征激活值一旦算好就可以反复使用。第二建立候选短语库。不要每次都为单个特征临时生成候选短语而是维护一个经过筛选的高质量候选集合。集合可以包含代码关键词、领域术语、句法结构名称、常见危险内容类别等。当遇到新的特征时先在候选库中做对齐检索检索不到再请 LLM 扩展生成。第三记录所有可复现信息。模型名称、模型权重 commit、SAE release、语料版本、特征索引、随机种子、prompt 模板都应该作为实验配置记录下来。解释结果的可复现性在可解释性研究中非常重要同一特征在同样条件下不应该因为随机种子不同而得到完全不同的解释。第四不要在缺少因果验证的情况下过度解读。看到“某特征表示 Python 代码”不要立刻认定模型内部有一个“Python 概念神经元”。解释只是对高维方向的低维投影真实机制往往比文本标签复杂。建议在生成解释后至少做一次正负样本分类和一次扰动实验再把结论写进报告。第五涉及安全类特征时要更谨慎。如果你在用 SAE 分析模型的越狱、违规内容、幻觉等问题生成的解释无论看起来多合理都不能作为最终安全结论。需要结合多次采样、多条语料、多个层级的交叉验证并遵守所在组织的合规流程。可解释性工具只能辅助分析不能替代安全测试。第六注意 API 演进。transformer_lens 和 sae_lens 的接口变化很快直接复制网上的旧代码很容易报错。建议在项目里锁住版本并在 README 里写清楚使用的具体版本遇到新版本时先跑官方示例确认核心 API 没有变再改自己的代码。9. 总结与后续学习方向SAEVerbalizer 代表了一个值得注意的方法论转向大模型内部特征的解释正在从“示例归纳”走向“表示对齐”。传统 AutoInterp 管线靠 top-k 激活文本 LLM 总结本质上是在行为层面做归纳而 Representation Verbalization 尝试直接利用特征方向与词汇/短语表示之间的相似性把特征“翻译”成语言。两者的关系不是替代而是互补表示对齐负责提供更直接的语义线索激活样本和扰动实验负责验证解释是否真实。如果你准备继续深入下一步可以从这几个方向入手。一是把论文提出的 verbalizer 构造方法与 AutoInterp 评测结合起来量化两种方案在解释准确性上的差异。二是尝试把这类思路用于中间层特征、注意力头特征、甚至 KV 缓存特征看看表示对齐是否仍然有效。三是结合因果扰动实验把“解释文本”升级为“可干预的表示方向”这是可解释性落地到模型控制的关键一步。对于实际项目建议收藏本文并在小模型上先跑通完整流程。不要一上来就尝试解释模型所有层、所有特征先挑一层、挑几个你关心的特征把“提取-对齐-生成-验证”四步的代码骨架跑顺再逐步扩展。可解释性工作最容易犯的错误是在验证还没做好时就去追求解释数量。特征解释的价值不在于数量多而在于每一条解释都能经得起激活分布和因果扰动的检验。