ChatGPT能够自动生成一些表面上看起来像人类写出的文字的东西,是一件很厉害且出乎大家意料的事。那么,它是如何做到的呢?又是为何能做到呢?我在这里想大致介绍一下ChatGPT的内部机理,然后探讨一下为什么它能很好地生成我们认为是有意义的文本。我会把重点放在现在的整体情况分析上,其中虽然也会提到一些工程细节,但我不会进行深入探讨。(我下面要说的实质内容和 ChatGPT 一样也同样适用于目前其他的“大型语言模型” LLM )。
首先要解释的是,从根本上说,ChatGPT 尝试对其目前获取的任何文本进行 “合理的延续”,这里的“合理”是指“在看到数十亿个网页上人们所写的东西之后,大家可能会期望某人写出什么”。
因此,假设我们已经得到了 “人工智能最强的是它有......能力” 的文本(”The best thing about AI is its ability to”)。想象一下,(例如在网络上和数字化书籍中),扫描数十亿页人类书写的文本并找到这个文本的所有实例 —— 然后看到在接下来的时间里什么词出现的比例是多少。ChatGPT 能高效地在做类似的事情,除了(正如我将解释的)它不看字面文本;它寻找在某种意义上 “意义匹配” 的内容。但最终的结果是,它产生了一个可能接续的词的排序列表,以及 “概率”。
要注意的是,当 ChatGPT 在工作时,比如写一篇文章时,它所做的基本上只是反复询问 “鉴于到目前为止的文本,下一个词应该是什么?” —— 而且每次都增加一个词。(更准确地说,我下面也会解释,它在添加一个 “标记”,这可能只是一个词的一部分,这就是为什么它有时可以 “编造新词”)。
但是,在每一步,它得到一个带有概率的单词列表。但是,它究竟应该选择哪一个来添加到它正在写的文章(或其他内容)中呢?人们可能认为它应该是 “排名最高” 的词(即被分配到最高 “概率” 的词)。但是,这时就会有一点魔法悄悄释放。因为出于某种原因 —— 也许有一天我们会有一个更科学的理解 —— 如果我们总是挑选排名最高的词,大概率会得到一篇非常 “平淡” 的文章,这样也不会 “显示出任何创造力”(甚至有时会一字不差地重复)。但是,如果有时(随机发生)我们挑选排名较低的词,我们会得到一篇 “更有趣” 的文章。
这个随机性意味着,如果我们多次使用同一个提示,我们很可能每次都得到不同的作文。而且,为了与这个魔法的结果保持一致,还会有一个特定的所谓 “温度” 参数(temperature parameter),该参数决定了以什么样的频率使用排名较低的词。事实证明,在生成论文时,“温度”为0.8似乎是最优值。(要强调的是,这里没有使用任何“理论”;这只是一个在实践中被发现可行的问题)。例如,“温度” 的概念之所以存在,是因为恰好使用了统计物理学中熟悉的指数分布,但没有 “物理” 联系 —— 至少到目前为止我们如此认为。)
在往下分析之前,我应该解释一下,为了论述的目的,我一般不会使用 ChatGPT 中的完整系统;相反,我一般会使用更简单的 GPT-2 系统(https://resources.wolframcloud.com/NeuralNetRepository/resources/GPT2-Transformer-Trained-on-WebText-Data/),它有一个很好的特点,即它足够小,可以在标准的台式电脑上运行。因此,到目前为止我分析的内容都可以用明确的Wolfram 语言代码展示,你也可以立即在你的计算机上运行。
例如,下面展示了如何获得上述概率表。首先,我们必须检索底层的 “语言模型” 神经网:
稍后,我们来看看这个神经网的内部,并谈谈它是如何工作的。但现在我们可以把这个“网络模型” 作为一个黑匣子应用于文本,并要求按概率计算出该模型认为应该遵循的前五个词:
结果变成了一个明确的格式化的 “数据集”:
如果重复“应用模型” —— 在每一步中加入概率最高的词(在此代码中被指定为模型的“决定”),会发生什么:
如果再继续下去会发生什么?在这种情况下(“零温度”),很快就会出现相当混乱和重复的情况:
但是,如果不一直挑选“排名第一”的词,而是有时随机挑选 “排名后面” 的词(“随机性” 对应 “温度” 为 0.8)呢?则会构建起下面文本:
每次这样做,都会有不同的随机选择,文本也会不同 —— 如这 5 个例子:
要指出的是,即使在第一步,也有很多可能的 “下一个词” 可供选择(温度为 0.8),尽管它们的概率下降得很快(这个对数图上的直线对应于 n–1的 “幂律” 衰减,这是语言的一般统计的特点,https://www.wolframscience.com/nks/notes-8-8--zipfs-law/):
那么,如果继续下去会发生什么?这里有一个随机的例子。它比仅使用排名第一的词(零温度)情况要好,但还是有点奇怪:
这是用最简单的 GPT-2 模型(2019 年,https://resources.wolframcloud.com/NeuralNetRepository/resources/GPT2-Transformer-Trained-on-WebText-Data/)做的。如果用较新和较大的 GPT-3 模型(https://platform.openai.com/docs/model-index-for-researchers),结果更好。这里是用同样的 “提示” 产生的排名首位的词(零温度),但用最大的 GPT-3 模型:
下面是 “温度为 0.8” 时的一个随机例子:
概率从何而来?
ChatGPT 总是根据概率来选择下一个词。但是这些概率从何而来?让我们从一个更简单的问题开始。让我们考虑一次生成一个字母(而不是单词)的英语文本。我们怎样才能算出每个字母的概率呢?
非常简单的方法就是取一个英语文本的样本,然后计算不同字母在其中出现的频率。举例来说,这是计算维基百科上关于“cats”的文章中的字母:
然后同样再对“dogs”进行统计:
结果虽然相似,但不一样(“o” 在 “dogs” 文章中无疑更常见,因为毕竟它出现在 “dog” 这个词本身)。尽管如此,如果我们采取足够大的英语文本样本量,我们可以预期最终得到至少是相当一致的结果:
下面是我们得到的一个样本,如果用这些概率生成一个字母序列:
我们可以通过添加空格将其分解为 “单词”,就像它们是具有一定概率的字母一样:
我们可以通过强迫 “字长” 的分布与英语中的分布相一致的方法,改善生成 “单词” 的结果:
我们在这里没有碰巧得到任何 “实际存在的词”,但结果看起来稍好一些。不过,要想更进一步改善结果,需要做的不仅仅是随机分别挑选每个字母。例如,我们知道,如果我们有一个 “q”,下一个字母基本上必须是 “u”:
下面是字母本身的概率图:
这是一个显示典型英语文本中成对字母(“2-grams”)概率的图。可能的第一个字母横向显示,第二个字母纵向显示:
例如,我们在这里看到,除了 “u” 行外,“q” 列是空白的(概率为零)。现在我们不再是一次生成一个字母的 “单词”,而是使用这些 “2-gram” 概率,一次看两个字母来生成它们。下面是一个结果—— 其中包括一些 “实际存在的单词”:
有了足够多的英语文本,我们不仅可以更好地估计单个字母或成对字母(2-grams)的概率,而且还可以估计较长的字母。如果我们用逐渐变长的 n-gram 概率生成 “随机词”,我们就会发现它们是“现实存在”的单词概率更高:
但现在假设 —— 多少像 ChatGPT 那样 —— 我们处理的是整个单词,而不是字母。英语中大约有 40,000 个合理的常用词(https://reference.wolfram.com/language/ref/WordList.html)。通过查看大型英语文本语料库(比如几百万本书,总共有几千亿个单词),我们可以估计每个单词的常见程度(https://reference.wolfram.com/language/ref/WordFrequencyData.html)。利用这一点,我们可以开始生成“句子”,其中每个词都是独立随机抽取的,其出现的概率与语料库中的相同。下面是我们得到的一个样本:
意料之内,这根本就是一派胡言。所以我们如何才能做得更好呢?就像处理字母一样,我们可以开始考虑的不仅仅是单个词的概率,还有成对的或更长的词的 n-grams 的概率。在成对的情况下,以下是我们得到的 5 个例子,所有例子都是从 “cat” 这个词开始的:
结果 “看起来更合理” 了。我们可以想象,如果我们能够使用足够长的 n-grams,我们基本上会 “得到一个 ChatGPT” —— 在这个意义上,我们得到的内容是以 “正确的总体论文概率” 生成论文长度的单词序列。但问题是:没有足够的英文文本可以推导出这些概率。
在网络的抓取中,可能有几千亿个单词;在已经数字化的书籍中,可能还有几千亿个单词。但是常用词是 4 万个,即使是可能的 2-grams 的数量也已经是 16 亿了,可能的 3-grams 的数量是 60 万亿。所以我们没有办法从现有的文本中估计出所有这些的概率。而20 个字的 “文章片段” 的可能性的数量比宇宙中的粒子数量还要多,所以从这个角度说,它们永远不可能全部被写下来。
那么我们能做什么呢?最好的想法是建立一个模型,让我们估计序列出现的概率 —— 即使我们在所看的文本语料库中从未明确见过这些序列。而 ChatGPT 的核心正是一个所谓的 “大型语言模型”(LLM),建立这个模型可以很好地估计这些概率。
模型是什么?
假设你想知道(就像伽利略在 15 世纪末做过的实验,https://archive.org/details/bub_gb_49d42xp-USMC/page/404/mode/2up),从比萨塔的每一层落下的炮弹要多长时间才能落地。那么,你可以在每一种情况下测量它,并将结果制成表格。或者你可以遵循理论科学的本质:建立一个模型,给出某种计算答案的程序,而不是测量和记录每个情况。
想象一下,我们有(有点理想化的)数据,说明炮弹从不同楼层落下需要多长时间:
我们如何在一个我们没有明确数据的情况下计算出从某层楼落下需要多长时间?在这种特殊情况下,我们可以用已知的物理学定律来计算。但是,如果说我们所得到的只是数据,而我们不知道有什么基本定律在支配它。那么我们可以做一个数学上的猜测,比如说,也许我们应该用一条直线作为模型:
我们可以选择不同的直线。但这是平均来说最接近我们所给的数据的一条。而根据这条直线,我们可以估算出从任何楼层降落的时间。
我们怎么知道要在这里尝试使用一条直线呢?在某种程度上说,我们不知道。这只是数学上简单的内容,而我们已经习惯了这样的事实:我们测量的很多数据都在数学上被简单的模型很好地拟合了。我们可以尝试一些数学上更复杂的东西 —— 比如说 a + bx + cx^2,然后在这种情况下,拟合得更好:
不过,可能也会出现重大问题。比如这里是我们用 a + b/c + x sin(x) 最多也就拟合成如下:
要理解的是,从来没有一个 “无模型的模型”。你使用的任何模型都有一些特定的基础结构,然后有一组 “你可以转动的旋钮”(即你可以设置的参数)来拟合你的数据。而ChatGPT 使用了很多这样的 “旋钮” —— 实际来说,有 1750 亿个这样的按钮。
但了不起的是,ChatGPT 的底层结构 —— “仅仅” 有这么多的参数 —— 就足以使一个计算下一个单词概率的模型 “足够好”,从而为我们提供合理的文章长度的文本。
类人的任务模型
我们上面举的例子涉及到为数字数据建立模型,这些数据基本上来自于简单的物理学 —— 几个世纪以来我们都知道 “简单数学适用”。但是对于 ChatGPT 来说,我们必须为人类语言文本建立一个模型,即由人脑产生的那种模型。而对于这样的东西,我们(至少现在)还没有类似 “简单数学” 的模型。那么,它的模型可能是什么样的呢?
在我们讨论语言之前,让我们先谈谈另一项类似人任务:图像识别。举一个简单的例子,思考数字的图像(这是一个经典的机器学习例子):
我们可以为每个数字获取一堆样本图像:
然后,为了找出我们输入的图像是否对应于某个特定的数字,我们只需与我们拥有的样本进行明确的逐像素比较。但作为人类,我们可以做得更好 —— 因为即使数字是手写的,并且有各种各样的修改和扭曲,我们依然可以识别数字:
当为上面的数字数据建立模型时,取一个给定的数字值 x,然后为特定的 a 和 b 计算 a + bx。因此,如果我们把这里的每个像素的灰度值当作某个变量 xi,是否有某个所有这些变量的函数,在运算时告诉我们这个图像是什么数字?事实证明,有可能构建这样一个函数。但我们知道这并不简单。一个典型的例子可能涉及 50 万次数学运算。
但最终的结果是,如果我们把一幅图像的像素值集合输入这个函数,就会得出一个数字,指定我们的图像是哪个数字。稍后,我们将讨论如何构建这样一个函数,以及神经网络的概念。现在让我们把这个函数看做一个黑匣子,我们输入例如手写数字的图像(作为像素值的阵列),然后我们得到这些数字对应的数字:
但这里到底发生了什么?比方说,我们逐步模糊一个数字。在起初的一段时间,我们的函数仍然可以 “识别”这里是一个 “2”。但很快它就 “失去” 这个能力,并开始给出 “错误” 的结果:
但为什么我们说这是一个 “错误” 的结果呢?在这种情况下,我们知道我们通过模糊一个 “2” 得到所有的图像。但是,如果我们的目标是制作一个人类识别图像的模型,那么真正要问的问题是,如果遇到这些模糊的图像,在不知道其来源的情况下,人类会做什么。
如果从我们的函数中得到的结果与人类会说的话一致,我们就有一个 “好的模型”。而科学事实是,对于像这样的图像识别任务,我们现在基本上知道如何构建这样的函数。
我们能 “从数学上证明” 它们的作用吗?不能。因为要做到这一点,我们必须有一个关于人类正在做什么的数学理论。以 “2” 图像为例,改变几个像素。我们可以想象,如果只有几个像素 “不合适”,我们还是应该认为这个图像是 “2”。但应该到什么程度呢?这是一个关于人类视觉感知的问题。而且,对于蜜蜂或章鱼来说,答案无疑是不同的 —— 而对于假定的外星人来说,则可能完全不同。
神经网络
那么,我们用于图像识别等任务的一般模型究竟是如何工作的呢?目前最流行、最成功的方法是使用神经网络。在 20 世纪 40 年代,神经网络的发明形式与今天的使用非常接近,它可以被认为是大脑的简单理想化的工作方式。
在人类的大脑中,有大约 1000 亿个神经元(神经细胞),每个神经元都能产生电脉冲,每秒可能有一千次。这些神经元在一个复杂的网络中连接起来,每个神经元都有树状的分支,允许其将电信号传递给成千上万的其他神经元。粗略估计,任何给定的神经元是否在某一时刻产生电脉冲,取决于它从其他神经元那里收到的脉冲 —— 不同的连接有不同的 “权重” 贡献。
当我们 “看到一个图像” 时,发生的事情是,当图像的光子落在眼睛后面的(“光感受器”)细胞上时,它们在神经细胞中产生电信号。这些神经细胞与其他神经细胞相连,最终信号通过一整层的神经元。而正是在这个过程中,我们 “识别” 了图像,最终 “形成了一个想法”,即我们 “看到了一个 2”(也许最后有一些别的行为,如大声说 “2” 这个词)。
上一节中的 “黑匣子” 函数是这样一个神经网络的 “数学化” 版本。它刚好有 11 层(虽然只有 4 个 “核心层”)。
这个神经网并没有什么特别的 “理论推导”;它只是在 1998 年在一项工程项目中构建出的结果 (https://resources.wolframcloud.com/NeuralNetRepository/resources/LeNet-Trained-on-MNIST-Data/),并且被发现是有效的。(当然,这与我们描述我们的大脑是通过生物进化过程产生的没有什么不同)。
但是像这样的神经网络是如何 “识别事物” 的?关键在于吸引器的概念(https://www.wolframscience.com/nks/chap-6--starting-from-randomness#sect-6-7--the-notion-of-attractors)。假设我们有 1 和 2 的手写图像:
我们希望所有的 1 都 “被吸引到一个地方”,而所有的 2 都 “被吸引到另一个地方”。或者,换一种方式,如果一个图像在某种程度上 “更接近于 1”,而不是 2,我们希望它最终出现在 “1 的地方”,反之亦然。
作为一个直接的类比,我们假设在平面上有某些位置,用点表示(在现实生活中,它们可能是咖啡店的位置)。那么我们可以想象,从平面上的任何一点开始,我们总是想在最近的点结束(即我们总是去最近的咖啡店)。我们可以通过将平面划分为由理想化的 “分水岭” 分隔的区域(“吸引器盆地”)来表示这一点:
可以想象这是在执行一种 “识别任务”,我们不是在做类似于识别给定图像 “看起来最像” 哪个数字的事情 —— 而是很直接地看到给定点最接近哪个点。(我们在这里展示的 “Voronoi 图” 设置为在二维欧几里得空间中的分离点;数字识别任务可以看作非常类似的过程 —— 但却是在一个由每张图像中所有像素的灰度等级形成的 784 维空间中。)
那么,我们如何使一个神经网络 “完成一个识别任务”?考虑这个非常简单的情况:
我们的目标是获取一个对应于位置 {x,y}的 “输入”,然后将其 “识别” 为它最接近的三个点中的任何一个。或者换句话说,我们希望神经网络能够计算出一个{x,y} 的函数,比如:
那么,我们如何用神经网络做到这一点呢?归根结底,神经网是一个理想化 “神经元” 的连接集合 —— 通常按层排列 —— 下面是一个简单的例子:
每个 “神经元” 都被设置为运算一个简单的数字函数。为了 “使用” 这个网络,我们只需在顶部输入数字(如我们的坐标 x 和 y),然后让每一层的神经元 “运算其函数”,并通过网络向前输入结果 —— 最终在底部产生最终的结果:
在传统的(受生物启发的)设置中,每个神经元实际上都有一组来自上一层神经元的 “传入连接”,每个连接都被赋予一定的 “权重”(可以是正数或负数)。一个给定的神经元的值是通过将 “前一个神经元” 的值乘以其相应的权重来确定的,然后将这些值相加并乘以一个常数,最后应用一个 “阈值”(或 “激活”)函数。在数学术语中,如果一个神经元输入为 x = {x1, x2 …… },那么计算 f[w . x + b],其中每个神经元在网络中选择不同的权重 w 和常数 b;函数 f 通常是相同的。
计算 w . x + b只是一个矩阵乘法和加法的问题。“激活函数” f 引入了非线性(并最终导致了非线性行为)。我们可以使用各种激活函数;这里我们只使用 Ramp(http://reference.wolfram.com/language/ref/Ramp.html,或 ReLU):
说到我们希望神经网络执行的每一项任务(或者说,对于我们希望它运算的每一个整体函数),我们会赋予其不同的权重选择。(我们稍后也要讨论,这些权重通常是通过使用机器学习根据我们想要的输出实例 “训练” 神经网络来确定的)。
最终,每个神经网络都对应于一些整体的数学函数 —— 但它有可能写得很乱。对于上面的例子,它就是:
ChatGPT 的神经网络也只是对应于这样的一个数学函数 —— 但函数实际上有数十亿个项。
让我们回到单个神经元上。下面是一个有两个输入(代表坐标 x 和 y)的神经元在选择不同的权重和常数(以及 Ramp 作为激活函数)后进行计算的一些函数例子:
但上面那个更大的网络是怎么回事?以下是它的计算结果:
结果不是很 “正确”,但它接近于我们上面展示的 “最近点” 函数。
让我们看看其他一些神经网络的情况。在每一种情况下,稍后会解释,我们都在使用机器学习来寻找最佳的权重选择。然后,这里会展示带有这些权重的神经网络的计算结果:
更大的网络通常能更好地逼近目标函数。而在 “每个吸引器盆地的中间”,通常会得到我们想要的答案。但在边界(https://www.wolframscience.com/nks/notes-10-12--memory-analogs-with-numerical-data/) —— 神经网络 “很难做决定” 的地方 —— 情况可能会更加混乱。
在这个简单的数学风格的 “识别任务” 中,“正确答案” 是什么很清楚。但在识别手写数字的问题上,就不那么清楚了。如果有人把 “2” 写得很糟糕,看起来像 “7”,等等,怎么办?不过,我们还是可以问,神经网络是如何区分数字的 —— 这就给出了一个指示:
我们能 “从数学上” 说说网络是如何区分的吗?并不能。它只是在 “做神经网络所做的事” 而已。但事实证明,这通常似乎与我们人类所作的区分相当类似。
举一个更复杂的例子。比方说,我们有猫和狗的图像。训练一个神经网络来区分它们。下面是这个网络面对一些情况时可能做的事情:
现在,“正确答案” 是什么就更不清楚了。披着猫外皮的狗怎么办?等等。无论给出什么输入,神经网络都会产生一个答案。而且,这样做的方式与人类可能做的事情是合理一致的。正如我在上面所说的,这不是一个我们可以 “从第一原理推导” 的事实。它只是根据经验被发现是真的,至少在某些领域是这样。但这是神经网络有用的一个关键原因:它们以某种方式捕捉了 “类似于人类” 的做事方式。
给自己看一张猫的照片,然后问 “为什么那是一只猫?”。也许你会说 “我看到它的尖耳朵,等等”。但要解释你是如何认出这张图片是一只猫,并不是很容易。因为是你的大脑不知怎么想出来的。但是对于大脑来说,没有办法(至少现在还没有)“进入” 它的内部,看看它是如何想出来的。那么对于一个(人工)神经网络来说呢?当你向其展示一张猫的图片时,可以直接看到每个 “神经元” 的作用。但是,即使要获得一个简单的可视化,通常也是非常困难的。
在我们用于解决上述 “最近点” 问题的最终网络中,有 17 个神经元。在用于识别手写数字的网络中,有 2190 个。而在我们用来识别猫和狗的网络中,有 60,650 个神经元。通常情况下,要将相当于 60,650 个维度的空间可视化是相当困难的。但由于这是一个为处理图像而设置的网络,它的许多神经元像它所看的像素阵列一样组织成阵列。
如果用一张典型的猫图像:
那么我们就可以用一组衍生图像来表示第一层神经元的状态 —— 其中许多图像我们可以很容易地解释为 “没有背景图像的猫图片” 或 “猫的轮廓” 等:
到了第十层,就更难解释这些是什么:
但总的来说,我们可以说神经网络会 “挑选出某些特征”(也许尖尖的耳朵也在其中),并利用这些特征来确定图像是什么。但我们会命名这些特征吗,比如 “尖耳朵”?大多数情况下并没有。
我们的大脑在使用类似的特征吗?大多数情况下我们不知道。但要注意的是,像我们在这里展示的神经网络的前几层似乎可以挑出图像的某些特征(如物体的边缘),这些特征似乎与我们知道的由大脑中第一层视觉处理挑出的特征相似。
但是,假设我们想要一个神经网络的 “猫识别理论”。我们可以说 “这个网络可以做到” —— 这让我们可以感觉 “问题有多难” (例如,可能需要多少个神经元或层)。但至少到现在为止,我们还没有办法对网络的机理进行 “叙述性描述”。也许这是因为它在计算上确实是不可简化的,而且除了明确地追踪每一个步骤之外,没有一般的方法可以看出它在做什么。也可能只是因为我们还没有 “弄清科学”,还没有确定 “自然法则”,所以我们无法总结出正在发生的事情。
用 ChatGPT 生成语言时,我们会遇到同样的问题。而且同样不清楚是否有办法 “总结它在做什么”。但是语言的丰富性和细节(以及我们在这方面的经验)可能会让我们在此比对图像的研究走得更远。