Skip to content

机制可解释性、变量与可解释基的重要性

2022 年 5 月 9 日 · 原文: https://transformer-circuits.pub/2022/mech-interp-essay/index.html


机制可解释性试图对神经网络进行逆向工程,就像人们逆向工程编译后的二进制计算机程序那样。毕竟,神经网络参数在某种意义上就是一个二进制计算机程序,运行在我们称之为神经网络架构的某个奇异虚拟机上。

这个类比其实相当深刻。随着本文的展开,我们会更多地讨论它,但下表列出了一些对应关系:

常规计算机程序 神经网络
逆向工程 机制可解释性
程序二进制 网络参数
虚拟机 / 处理器 / 解释器 网络架构
程序状态 / 内存 层表示 / 激活值
变量 / 内存位置 神经元 / 特征方向

认真对待这个类比,可以让我们探索机制可解释性中的一些宏观问题。通常,那些在神经网络逆向工程中显得思辨性强、难以把握的问题,一旦换成对普通计算机程序提出同样的问题,就会变得清晰。而这些答案中的许多似乎很可能迁移回神经网络的情形。

也许最有趣的观察是,这个类比似乎表明:发现并理解可解释的神经元——类似于理解计算机程序中的变量——不仅仅是众多有趣问题之一。可以说,它是核心任务。

攻克维度灾难

每一种可解释性方法都必须以某种方式克服维度灾难。神经网络是输入空间通常具有极高维度的函数。输入空间的 n 维体积随维度数量的增加呈指数增长,变得极其庞大。这就是维度灾难。它通常被作为学习函数的挑战提出:没有指数级的数据量,我们如何学习覆盖如此庞大输入空间的函数?但它也是可解释性的挑战:没有指数级的时间量,我们如何指望理解覆盖如此庞大空间的函数?1

一种答案是研究低维输入的玩具(toy)神经网络,通过回避问题来轻松实现全面理解。另一种答案是研究神经网络在某个感兴趣数据点邻域内的行为——这大致就是显著图(saliency maps)给出的答案。

机制可解释性如何解决维度灾难?这个问题同样值得向常规逆向工程提出!不知何故,逆向工程计算机程序的程序员能够理解程序的行为,而这些行为往往覆盖极其高维的输入空间。他们能做到这一点,是因为代码给出了程序行为的非指数级描述——这提供了另一种思路,不必把程序理解为覆盖巨大输入空间的函数。在人工神经网络的语境下,我们也可以追求同样的答案。归根结底,参数是对神经网络的有限描述——只要我们能在某种程度上理解它们。

当然,参数可能非常庞大——最大的语言模型有数千亿个参数!但像编译后的操作系统这样的二进制计算机程序同样可以非常庞大,而我们常常最终能够理解它们。

另一个推论是,我们不应指望机制可解释性是一件容易的事,或存在一套可以照搬的流水线式流程。人们往往希望可解释性能提供简单的答案、简短的说明。但我们应当预期,机制可解释性至少与逆向工程一个庞大而复杂的计算机程序一样困难。

变量与激活值

理解一个计算机程序,既需要我们理解变量,也需要理解作用于这些变量的操作。像 y = x + 5; 这样的语句,除非你理解 yx 是什么,否则毫无意义。与此同时,yx 的意义最终来自它们被程序中某处操作使用的方式。想必这正是变量名如此有用的原因!

逆向工程师通常没有变量名可资利用。他们需要弄清楚每个变量实际代表什么。事实上,这样说还低估了问题的难度。程序实际作用于一整块计算机内存——即它们的状态——而人类以离散变量的方式思考这块内存。在许多情况下,内存如何映射到变量是显而易见的,但情况并非总是如此。因此,逆向工程师必须弄清楚如何将内存切分为可以单独理解的变量,然后再弄清楚这些变量的含义。换句话说,逆向工程师必须为内存中的位置赋予意义。

神经网络的逆向工程面临着几乎完全相同的挑战。正如 Vosset al 所讨论的那样,神经网络参数可以被看作二进制指令,而神经元激活值则类似于变量或内存。每个参数描述的是先前的激活值如何影响后续的激活值。只有理解了输入和输出激活值,我们才能理解参数的意义。

但激活值是高维向量:我们如何指望理解它们?我们又撞上了维度灾难,但常规逆向工程再次指明了解决方案。理解计算机程序的内存是可能的——那同样是一个高维空间!——因为我们可以把它切分为能够分别推理和理解的变量。类似地,我们需要把神经网络的激活值分解为可独立理解的片段。

在一些非常特殊的情况下,包括纯注意力 transformer(参见 Elhageet al),我们可以利用线性性,用模型的输入和输出来描述网络的全部运算。这类似于计算机程序中的某些函数可以仅用参数和返回值来描述,而不需要中间变量。由于我们通常理解输入和输出,这让我们得以绕开问题。但在大多数情况下,我们做不到这一点——那该怎么办?

如果我们无法用特殊技巧回避这个问题,那么机制可解释性就要求我们必须以某种方式将激活值分解为可独立理解的片段。

简单的内存布局与神经元

计算机程序的内存布局通常便于理解。例如,内存中的大多数字节代表单个事物,而不是让每个比特各自代表互不相关的东西。这部分是因为这些布局更便于程序员思考,也部分是因为我们的硬件常常使"简单"的连续内存布局更加高效。拥有这些简单的内存布局,使得逆向工程计算机程序更加容易,也更容易弄清楚应当如何把内存拆分为可独立理解的变量。

神经网络中常常会发生某种与此类似的事情。

让我们暂且假设,神经网络可以理解为对一组独立的"可解释特征"进行的运算。原则上,人们可以想象"可解释特征"以激活空间中任意方向的形式嵌入。但通常情况下,带有激活函数的神经网络层似乎会把特征与其基的各个维度对齐。这是因为激活函数在某种意义上使这些方向变得自然且有用。正如 CPU 中作用于字节的运算鼓励信息按字节分组、而非随机散布在内存中一样,激活函数常常鼓励特征与某个神经元对齐,而不是对应于神经元的随机线性组合。我们称之为特权基

让特征与神经元对齐会大大降低神经网络逆向工程的难度。这并不是说,如果神经元无法被单独理解,神经网络就不可能被逆向工程。但那样做似乎要困难得多,正如逆向工程内存布局怪异的计算机程序更加困难一样。

不幸的是,许多神经元无法以这种方式理解。这些多语义的(polysemantic)神经元似乎有助于表征那些不宜从单个神经元角度理解的特征。这对神经网络的逆向工程来说是个非常棘手的问题,我们将在 SoLU 论文中进一步讨论(尤其参见第 3 节)。

目前,我们想强调的要点是:将表示分解为可独立理解的部分的能力,似乎对机制可解释性的成功至关重要。

脚注


  1. 一个可能的反对意见是:虽然输入空间是高维的,但我们只需要理解函数在实际输入的数据流形(data manifold)上的行为。(我一度曾亲身相当投入于这种方法!)然而,数据流形虽然确实比输入空间维度更低,但对于我们关心的视觉或语言等任务来说,它似乎仍然必须是非常高维的。例如,对于任何给定的自然图像,似乎都可以让任意物体从视野的任何一侧进入图像,从而沿数据流形移动——那可是很多维度!