全文约1.7w字,分为上中下篇,本文(中篇)建议阅读8分钟
本文通过通俗易懂的初中数学知识来辅助理解大语言模型的工作机制。
4. 是什么让大语言模型做得这么好?
一个字一个字地生成“矮胖子坐在墙上”,这与大语言模型所能做到的相距甚远。从我们上面讨论的简单生成AI到类人机器人,有许多不同之处和创新之处。让我们来看看:
5. 嵌入
记得我们说过我们输入字符到模型的方式并不是最好的方式。我们只是为每个字符随机选择了一个数字。如果我们可以分配更好的数字来训练更好的网络呢?我们如何找到这些更好的数字?这里有一个聪明的技巧:
当我们训练上面的模型时,我们的方法是通过移动权重,看看最终会给我们带来更小的损失。然后慢慢地递归地改变权重。在每一个转弯处,我们都会:
在这个过程中,输入是固定的。当输入是(RGB, Vol)时是有意义的。但是我们现在输入的数字是a b c等等都是我们随意挑选的。如果在每次迭代中除了将权值移动一点之外,我们还移动输入,看看我们是否可以通过使用不同的数值表示a来获得更低的损失,等等?我们确实在减少损失并使模型更好(这是我们设计的a的输入方向)。基本上,梯度下降不仅应用于权重,还应用于输入的数字表示,因为它们是任意选择的数字。这被称为“嵌入”。它是输入到数字的映射,正如你刚才看到的,它需要训练。训练嵌入的过程与训练参数的过程非常相似。这样做的一大好处是,一旦你训练了一个嵌入,如果你愿意,你可以在另一个模型中使用它。请记住,您将始终使用相同的嵌入来表示单个分词/字符/单词。
我们讨论了每个字符只有一个数字的嵌入。然而,实际上embedding 有多个数字。这是因为很难用一个数字来捕捉概念的丰富性。如果我们看一下叶子和花的例子,每个对象有四个数字(输入层的大小)。这四个数字中的每一个都传达了一个属性,模型能够使用它们来有效地猜测对象。如果我们只有一个数字,比如颜色的红色通道,那么模型可能会困难得多。我们在这里试图捕捉人类语言 — 我们需要不止一个数字。
那么,与其用单个数字来表示每个字符,不如用多个数字来表示它来捕捉它的丰富性。让我们为每个字符分配一堆数字。把一个有序的数字集合称为“向量”(每个数字都有一个位置,如果交换两个数字的位置会得到一个不同的向量。我们的叶子/花数据就是这种情况,如果我们将叶子的 R 和 G 数字交换,我们会得到不同的颜色,它将不再是相同的向量)。向量的长度就是它包含多少个数字。接下来我们将为每个字符设置一个向量,就会出现两个问题:
当然,所有嵌入向量的长度必须相同,否则我们将无法将所有字符组合输入到网络中。例如,“humpty dumpt”和下一次迭代中的“umpty dumpty”——在这两种情况下,我们在网络中都输入了12 个字符,如果这 12 个字符中的每一个不都是由长度为 10 的向量表示的,我们将无法保证将它们全部馈送到长度为120的输入层中。让我们可视化这些嵌入向量:
图片来自作者
让我们将相同大小的向量的有序集合称为矩阵。上面的这个矩阵称为嵌入矩阵。找到一个与你的字母顺序相对应的列号,然后查看矩阵对应列将得到用来表示该字母的向量。这可以更普遍地应用于嵌入任意的事物集合——只需要让矩阵对应事物列数相同即可。
6. Sub-word分词器
到目前为止,我们一直在将字符作为语言的基本构建块。这有其局限性。神经网络权重必须完成很多繁重的工作,它们必须理解特定的相邻字符序列(即单词)以及相邻的单词。如果我们直接将嵌入向量分配给单词并让网络预测下一个单词会怎么样?无论如何,网络除了数字之外什么都不理解,因此我们可以为每个单词“humpty”、“dumpty”、“sat”、“on” 等分配一个 10 长度的向量。然后我们只需给它两个单词,它就可以给我们下一个单词。“分词” 是指我们嵌入然后馈送到模型的单个单元。到目前为止,我们的模型是使用字符作为分词,现在我们建议使用整个单词作为分词(当然,如果愿意,可以使用整个句子或短语作为分词)。
使用单词分词对我们的模型有很大的影响。英语中有超过18万个单词。使用我们的输出解释方案,即每个可能的输出都有一个神经元,我们需要在输出层中有数十万个神经元而不是26个。随着现代网络实现有意义结果所需的隐藏层的存在,这个问题变得不那么紧迫。然而,值得注意的是,由于我们单独处理每个单词,并且从一个随机数嵌入开始,因此非常相似的单词(例如 “cat” 和 “cats”)也会毫无关联。但我们期望这两个单词的嵌入应该彼此接近 —模型无疑是会学习的。但是,我们能否以某种方式利用这种明显的相似性来快速启动并简化问题?
是的,我们能。当今语言模型中最常见的嵌入方案是将单词分解为子词,然后嵌入它们。在cat 示例中,我们将 cat 分解为两个分词 “cat” 和 “s”。现在,模型更容易理解 “s” 的概念,后跟其他熟悉的单词,以此类推。这也减少了我们需要的分词数量(sentencpiece 是一种常见的分词器,其词汇量选项为数万个单词,而英语中则为数十万个单词)。分词器是一种将输入文本(例如 “Humpty Dumpt”)拆分为分词,并提供嵌入矩阵中该分词的嵌入向量对应的列号。例如,在“humpty dumpty”的情况下,如果我们使用字符级分词器,并且我们按照上图的方式排列嵌入矩阵,那么分词器将首先将 humpty dumpt 拆分为字符 ['h','u',...'t'],然后返回数字 [8,21,...20] ,需要查找嵌入矩阵的第 8 列来获取 'h' 的嵌入向量(嵌入向量是将输入模型的内容,而不是数字 8,这与以前不同)。
矩阵中列的排列是完全无关紧要的,我们可以将任何列分配给'h',只要我们每次输入 'h' 时都查找相同的向量。分词器只是给我们一个任意(但固定的)数字,以便于查找。它们的主要任务实际上是将句子拆分为分词。
使用嵌入和子词分词化,模型可能看起来像这样:
图片来自作者
接下来的几节将介绍语言建模的最新进展,以及使大语言模型像今天一样强大的进展。但是,要理解这些,您需要了解一些基本的数学概念。以下是概念:
我已经在附录里增加了这些概念的总结。
7. 自注意力
到目前为止,我们只看到了一种简单的神经网络结构(称为前馈网络),它包含许多层,并且每一层都与下一层完全连接(即,有一条线连接连续层中的任意两个神经元),并且它只连接到下一层(例如,第1 层和第 3 层之间没有线等)。但是,正如您可以想象的那样,没有什么可以阻止我们删除或建立其他连接。甚至制作更复杂的结构。让我们探索一个特别重要的结构:自注意力。
如果你看一下人类语言的结构,我们要预测的下一个词,就要看之前的所有词了。但是,它们可能比其他单词更依赖于它们前面的某些单词。例如,如果我们试图预测“达米安有一个秘密的孩子,一个女孩,他在遗嘱中写道,他的所有财产,连同魔法球,都将属于____”中的下一个词。这里的这个词可以是 “her” 或 “him”,它特别取决于句子中一个更早的词:girl/boy。
好消息是,我们简单地前馈模型连接到上下文中的所有单词,因此它可以学习重要单词的合适权重,但问题是,通过前馈层连接模型中特定位置的权重是固定的(对于每个位置)。如果重要的单词总是在同一个位置,它适当地学习权重就没问题。但是,与下一个预测相关的词可能在系统中的任何位置。我们可以套用上面的那句话,当猜测 “her vs his” 时,预测的一个非常重要的词是 boy/girl,无论它出现在那句话中的哪个位置。因此,我们需要的权重不仅取决于位置,还取决于该位置的内容。我们如何实现这一目标?
自注意力的作用类似于将每个单词的嵌入向量相加,但它不是直接将它们相加,而是对每个单词应用一些权重。因此,如果humpty、dumpty、sat 的嵌入向量分别为 x1、x2、x3,那么它将把每个向量乘以一个权重(一个数字),然后再将它们相加。类似于 output = 0.5 x1 + 0.25 x2 + 0.25 x3,其中输出是自注意输出。如果我们将权重写为 u1、u2、u3,使得输出 = u1x1+u2x2+u3x3,那么我们如何找到这些权重 u1、u2、u3?
理想情况下,我们希望这些权重取决于我们添加的向量——正如我们看到的那样,有些权重可能比其他向量更重要。对于我们将要预测的单词很重要。因此,我们还希望权重取决于我们将要预测的单词。这是一个问题,我们不能提前预知将要预测的词。因此,自注意使用预测词前面的单词,即可用句子中的最后一个单词(我不知道为什么,但深度学习中的很多事情都是反复试验的,这效果很好)。
理想的情况下,我们需要这些向量的权重,我们希望每个权重都取决于我们正在聚合的单词和预测词之前的单词。我们想要一个函数u1 = F(x1, x3),其中 x1 是我们将加权的单词,x3 是我们拥有的序列中最后一个单词(假设我们只有 3 个单词)。现在,实现此目的的一种简单方法是为 x1 设置一个向量(我们称之为 k1)和一个为 x3 设置一个单独的向量(我们称之为 q3),然后简单地取它们的点积。这将给我们一个数字,它将取决于 x1 和 x3。我们如何获得这些向量 k1 和 q3?我们构建了一个很小的单层神经网络,从 x1 到 k1(或 x2 到 k2,x3 到 k3,依此类推)。我们构建了另一个从 x3 到 q3 的网络,使用我们的矩阵表示法得出权重矩阵 Wk 和 Wq,使得 k1 = Wkx1 和 q1 =Wqx1 依此类推。现在我们可以取 k1 和 q3 的点积来得到标量,所以 u1 = F(x1,x3) = Wkx1 ·Wqx3.
自注意中发生的另一件事是,我们不直接获取嵌入向量本身的加权和。相反,我们取该嵌入向量的某个“值” 的加权和,该 “值” 由另一个小型单层网络获得。这意味着类似于 k1 和 q1,我们现在也有单词 x1 的 v1,我们通过矩阵 Wv 获得它,使得 v1=Wvx1。然后聚合此 v1。所以,如果我们只有 3 个单词,并且我们试图预测第四个单词,那么一切看起来都是这样的:
自注意 图片来自作者
加号表示向量的简单加法,这意味着它们必须具有相同的长度。此处未显示的最后一个修改是标量u1、u2、u3 等。加起来不一定是 1。如果我们需要它们是权重,我们应该让它们相加。因此,我们将在此处应用一个熟悉的技巧,并使用归一化函数。
这就是自注意。还有交叉注意,你可以让q3 来自最后一个单词,但 k 和 v 可以完全来自另一个句子。例如,这在翻译任务中很有价值。现在我们知道什么是注意力了。
这整件事现在可以放在一个盒子里,被称为“自注意力块”。基本上,这个自注意块接受嵌入向量并输出用户选定的任何长度的单个输出向量。这个块有三个参数, Wk,Wq,Wv——它不需要比这更复杂。机器学习文献中有许多这样的块,它们通常由图表中带有其名称的框表示。像这样:
图片来自作者
通过自注意,你会注意到的一件事,到目前为止单词的位置似乎并不相关。我们全都使用相同的W,因此在这里切换 Humpty 和 Dumpty 不会真正产生影响——所有数字最终都会相同。这意味着虽然注意力可以弄清楚要注意什么,但这并不取决于单词的位置。但是,我们的确知道单词位置在英语中很重要,可以通过为模型提供单词位置的一些含义来提高性能。
因此,当使用自注意力时,我们通常不会将嵌入向量直接提供给自注意力块。我们稍后将看到如何在馈送到注意力块之前将“位置编码” 添加到嵌入向量中。
初学者请注意:那些不是第一次阅读自注意力的人会注意到我们没有引用任何K 和 Q 矩阵,也没有应用掩码等。这是因为这些东西是模型训练而产生的实现细节。输入一批数据,同时训练模型以从 humpty 预测 dumpty、从 humpty dumpty 预测 sat 等。这是一个提高效率的问题,不会影响解释甚至模型输出,我们选择在这里省略训练效率入侵。