当前位置:   article > 正文

Elasticsearch:不用高深的数学知识来理解 LLMs 是如何工作的

Elasticsearch:不用高深的数学知识来理解 LLMs 是如何工作的

我相信您同意,我们无法忽视生成式人工智能 (GenAI),因为我们不断被有关大型语言模型 (LLMs) 的主流新闻轰炸。你很可能已经尝试过 ChatGPT,甚至可能一直将其作为助手使用。

我认为很多人对 GenAI 革命有一个基本疑问,即这些模型的明显智能来自哪里。在本文中,我将尝试用简单的术语解释生成式文本模型的工作原理,而不使用高级数学,以帮助你将它们视为计算机算法而不是魔法。

LLM 的作用是什么?

首先,我将澄清人们对大型语言模型工作原理的一个重大误解。大多数人认为这些模型可以回答问题或与你聊天,但实际上它们所能做的就是将你提供的一些文本作为输入,并猜测下一个单词(或更准确地说,下一个 token,即标记)是什么。

让我们开始从 token 中解开 LLM 的神秘面纱。

标记

标记是 LLM 理解的文本的基本单位。将标记视为单词很方便,但对于 LLM 来说,目标是尽可能高效地对文本进行编码,因此在许多情况下,标记表示比整个单词短或长的字符序列。标点符号和空格也表示为标记,可以单独表示或与其他字符分组表示。

LLM 使用的完整标记列表被称为 LLM 的词汇表,因为它可用于表达任何可能的文本。字节对编码 (byte pair encoding - BPE) 算法通常由 LLMs 用于根据输入数据集生成标记词汇表。为了让你对规模有一个大致的了解,GPT-2 语言模型是开源的,可以详细研究,它使用了 50,257 个标记的词汇表。

LLM 词汇表中的每个标记都被赋予一个唯一标识符,通常是一个数字。LLM 使用标记器(tokennizer)在以字符串形式给出的常规文本和以标记数字列表形式给出的等效标记序列之间进行转换。如果你熟悉 Python 并想使用标记,你可以安装 OpenAI 的 tiktoken 包:

pip3 install tiktoken

然后在 Python 提示符中尝试这个:

  1. $ python
  2. Python 3.11.8 (v3.11.8:db85d51d3e, Feb 6 2024, 18:02:37) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
  3. Type "help", "copyright", "credits" or "license" for more information.
  4. >>> import tiktoken
  5. >>> encoding = tiktoken.encoding_for_model("gpt-2")
  6. >>> encoding.encode("The quick brown fox jumps over the lazy dog.")
  7. [464, 2068, 7586, 21831, 18045, 625, 262, 16931, 3290, 13]
  8. >>> encoding.decode([464, 2068, 7586, 21831, 18045, 625, 262, 16931, 3290, 13])
  9. 'The quick brown fox jumps over the lazy dog.'
  10. >>> encoding.decode([464])
  11. 'The'
  12. >>> encoding.decode([2068])
  13. ' quick'
  14. >>> encoding.decode([13])
  15. '.'
  16. >>>

你可以在本实验中看到,对于 GPT-2 语言模型,标记 464 代表单词 “The”,标记 2068 代表单词 “ quick”,包括一个前导空格。此模型使用标记 13 表示句点。

由于标记是通过算法确定的,你可能会发现一些奇怪的东西,例如单词 “the” 的这三个变体,它们都被 GPT-2 编码为不同的标记:

  1. >>> encoding.decode([13])
  2. '.'
  3. >>> encoding.encode('The')
  4. [464]
  5. >>> encoding.encode('the')
  6. [1169]
  7. >>> encoding.encode(' the')
  8. [262]

BPE 算法并不总是将整个单词映射到标记。事实上,使用频率较低的单词不会成为自己的标记,而必须用多个标记进行编码。以下是此模型用两个标记编码的单词的示例:

  1. >>> encoding.encode("Payment")
  2. [19197, 434]
  3. >>> encoding.decode([19197])
  4. 'Pay'
  5. >>> encoding.decode([434])
  6. 'ment'

下一个标记预测

正如我上面所述,给定一些文本,语言模型会预测紧随其后的标记是什么。如果使用 Python 伪代码有助于理解这一点,你可以按照以下方法运行其中一个模型来获取下一个标记的预测:

predictions = get_token_predictions(['The', ' quick', ' brown', ' fox'])

该函数获取输入标记的列表,这些标记是根据用户提供的提示进行编码的。在此示例中,我假设单词都是单独的标记。为了简单起见,我使用了每个标记的文本表示,但正如你之前所见,实际上每个标记都将作为数字传递给模型。

此函数的返回值是一个数据结构,它为词汇表中的每个标记分配跟随输入文本的概率。如果这是基于 GPT-2 的,则该函数的返回值将是一个包含 50,257 个浮点数的列表,每个浮点数都预测相应标记接下来出现的概率。

在上面的例子中,你可以想象一个训练有素的语言模型将使标记 “jumps” 有很大概率跟随我用作提示的部分短语 “The quick brown fox”。再次假设模型经过适当训练, 你还可以想象,随机单词(例如 “potato”)继续这个短语的概率将低得多,接近于 0。

为了能够产生合理的预测,语言模型必须经过训练过程。在训练期间,它会被呈现大量文本以供学习。在训练结束时,该模型能够使用它在训练中看到的所有文本构建的数据结构来计算给定标记序列的下一个标记概率。

这与你的预期不同吗?我希望现在看起来不那么神奇了。

生成长文本序列

由于模型只能预测下一个标记是什么,因此让它生成完整句子的唯一方法是循环运行模型多次。每次循环迭代都会生成一个新的标记,从返回的概率中选择。然后,将此标记添加到在循环的下一次迭代中提供给模型的输入中,并持续进行,直到生成足够的文本。

让我们看一个更完整的 Python 伪代码,展示它是如何工作的:

  1. def generate_text(prompt, num_tokens, hyperparameters):
  2. tokens = tokenize(prompt)
  3. for i in range(num_tokens):
  4. predictions = get_token_predictions(tokens)
  5. next_token = select_next_token(predictions, hyperparameters)
  6. tokens.append(next_token)
  7. return ''.join(tokens)

generate_text() 函数将用户提示作为参数。例如,这可能是一个问题。

tokenize() 辅助函数使用 tiktoken 或类似的库将提示转换为等效的标记列表。在 for 循环中,get_token_predictions() 函数是调用 AI 模型来获取下一个标记的概率的地方,如上例所示。

select_next_token() 函数的工作是获取下一个标记概率(或预测)并选择最佳标记以继续输入序列。该函数可以只选择概率最高的标记,这在机器学习中称为贪婪选择(greedy selection)。更好的是,它可以使用遵循模型返回的概率的随机数生成器来选择一个标记,并以这种方式为生成的文本添加一些变化。如果多次给出相同的提示,这也会使模型产生不同的响应。

为了使标记选择过程更加灵活,可以使用超参数(hyperparameters)修改 LLM 返回的概率,这些超参数作为参数传递给文本生成函数。超参数允许你控制标记选择过程的 “贪婪性 - greediness”。如果你使用过 LLM,那么你可能熟悉温度(temperature)超参数。在更高的温度下,标记概率会趋于平坦,这会增加不太可能被选中的标记的机会,最终结果是使生成的文本看起来更有创意或更不寻常。你可能还使用了另外两个超参数,称为 top_p 和 top_k,它们控制考虑选择多少个最高概率的标记。

一旦选择了标记,循环就会迭代,现在模型会得到一个输入,该输入在末尾包含新标记,并且会生成另一个标记来跟进它。num_tokens 参数控制运行循环的迭代次数,换句话说,控制要生成多少文本。生成的文本可能(并且经常)在句子中间结束,因为 LLM 没有句子或段落的概念,因为它一次只处理一个标记。为了防止生成的文本在句子中间结束,我们可以将 num_tokens 参数视为要生成的标记的最大值而不是确切数量,在这种情况下,我们可以在生成句点标记时停止循环。

如果你已经到达这一点并理解了所有内容,那么恭喜你,你现在知道 LLM 的工作原理了。你对更多细节感兴趣吗?在下一节中,我将更详细地介绍一些技术,同时仍会尽力避免引用支持这项相当先进的技术的数学知识。

模型训练

不幸的是,如果不使用数学,讨论如何训练模型实际上很困难。我首先要向您展示一种非常简单的训练方法。

鉴于任务是预测跟随其他标记的标记,训练模型的一种简单方法是获取训练数据集中出现的所有连续标记对,并用它们构建一个概率表。

让我们用一个简短的词汇表和数据集来做到这一点。假设模型的词汇表有以下五个标记:

['I', 'you', 'like', 'apples', 'bananas']

为了使这个例子简短明了,我不会将空格或标点符号视为标记。

让我们使用由三个句子组成的训练数据集:

  • I like apples
  • I like bananas
  • you like bananas

我们可以建立一个 5x5 的表格,并在每个单元格中写下代表单元格行的标记后面跟着代表列的标记的次数。以下是根据数据集中的三个句子构建的表格:

-Iyoulikeapplesbananas
I2
you1
like12
apples
bananas

希望这很清楚。数据集有两个 “I like” 实例、一个 “you like ”实例、一个 “like apples” 实例和两个 “like bananas” 实例。

现在我们知道了每对标记在训练数据集中出现的次数,我们可以计算每个标记彼此跟随的概率。为此,我们将每行中的数字转换为概率。例如,表格中间行的标记 “like” 后面跟着一次 “apples”,两次 “banana”。这意味着 “apples” 在 33.3% 的时间里跟在 “like”后面,而 “bananas” 在剩下的 66.7% 的时间里跟在它后面。

这是计算出所有概率的完整表格。空单元格的概率为 0%。

-Iyoulikeapplesbananas
I100%
you100%
like33.3%66.7%
apples25%25%25%25%
bananas25%25%25%25%

“I”、“you” 和 “like” 的行很容易计算,但 “apples” 和 “bananas” 存在问题,因为它们根本没有数据,因为数据集中没有任何这些标记后面跟着其他标记的示例。这里我们的训练中有一个 “漏洞”,因此为了确保模型即使在缺乏训练的情况下也能产生预测,我决定将 “apples” 和 “bananas” 的后续标记的概率均匀地分布在其他四个可能的标记中,这显然会产生奇怪的结果,但至少模型在达到这两个标记之一时不会卡住。

训练数据中的漏洞问题实际上很重要。在真正的 LLMs 中,训练数据集非常大,因此你不会发现像我上面的小例子那样明显的训练漏洞。但由于训练数据覆盖率低,更小、更难检测的漏洞确实存在,而且相当常见。LLM 在这些训练不足的领域做出的标记预测质量可能很差,但通常很难察觉。这是 LLMs 有时会产生幻觉(hallucinate)的原因之一,这种情况发生在生成的文本读起来很好但包含事实错误或不一致时。

使用上面的概率表,你现在可以想象 get_token_predictions() 函数的实现将如何工作。在 Python 伪代码中它将是这样的:

  1. def get_token_predictions(input_tokens):
  2. last_token = input_tokens[-1]
  3. return probabilities_table[last_token]

比预期的要简单,对吧?该函数接受来自用户提示的一系列标记。它获取序列中的最后一个标记,并返回概率表中与该标记相对应的行。

例如,如果你使用 ['you', 'like'] 作为输入标记来调用此函数,该函数将返回 “like” 的行,这使得标记 “apples” 有 33.3% 的机会继续该句子,而标记 “bananas” 有另外 66.7% 的机会继续该句子。有了这些概率,上面显示的 select_next_token() 函数应该在三次中选择一次 “apples”。

当选择 “apples” 标记作为 “you like” 的延续时,将形成句子 “you like apples”。这是一个原始句子,在训练数据集中不存在,但它完全合理。希望你开始了解这些模型如何通过重复使用模式并将他们在训练中学习到的不同知识拼接在一起,提出看似原创的想法或概念。

上下文窗口

我在上一节中训练我的迷你语言模型的方法称为马尔可夫链(Markov chain)。

这种技术的一个问题是,只有一个标记(输入的最后一个)用于进行预测。在选择如何继续时,出现在最后一个标记之前的任何文本都不会产生任何影响,因此我们可以说此解决方案的上下文窗口等于一个标记,这个标记非常小。在如此小的上下文窗口下,模型会不断 “忘记” 其思路,并从一个单词跳到另一个单词,而没有太多一致性。

为了改进模型的预测,可以构建更大的概率表。要使用两个标记的上下文窗口,必须添加额外的表行,这些行代表两个标记的所有可能序列。对于我在示例中使用的五个标记,概率表中将有 25 个新行,每个行代表一对标记,添加到已经存在的 5 个单标记行中。必须再次训练模型,这次除了查看成对的标记外,还要查看三个标记的组。然后,在 get_token_predictions() 函数的每次循环迭代中,将使用输入中的最后两个标记(如果可用)来查找较大概率表中的相应行。

但 2 个标记的上下文窗口仍然不够。为了使生成的文本与自身一致并至少具有一些基本意义,需要更大的上下文窗口。如果没有足够大的上下文,新生成的标记就不可能与先前标记中表达的概念或想法相关。那么我们能做什么呢?将上下文窗口增加到 3 个标记将为概率表添加 125 行,而且质量仍然很差。我们需要将上下文窗口做多大?

OpenAI 的开源 GPT-2 模型使用 1024 个标记的上下文窗口。为了能够使用马尔可夫链实现这种大小的上下文窗口,概率表的每一行必须表示一个长度在 1 到 1024 个标记之间的序列。使用上面包含 5 个标记的示例词汇表,有 51024 个可能的序列,每个序列长度为 1024 个标记。需要多少个表行来表示这个?我在 Python 会话中进行了计算(向右滚动可查看完整数字):

  1. >>> pow(5, 1024)
  2. 55626846462680034577255817933310101605480399511558295763833185422180110870347954896357078975312775514101683493275895275128810854038836502721400309634442970528269449838300058261990253686064590901798039126173562593355209381270166265416453973718012279499214790991212515897719252957621869994522193843748736289511290126272884996414561770466127838448395124802899527144151299810833802858809753719892490239782222290074816037776586657834841586939662825734294051183140794537141608771803070715941051121170285190347786926570042246331102750604036185540464179153763503857127117918822547579033069472418242684328083352174724579376695971173152319349449321466491373527284227385153411689217559966957882267024615430273115634918212890625

行数可真多!而且这只是表格的一部分,因为我们还需要长度为 1023 个 token、1022 个 token 等的序列,一直到 1,因为我们希望确保当输入中没有足够的 token 时,也可以处理较短的序列。马尔可夫链很有趣,但它们确实存在很大的可扩展性问题。

1024 个 token 的上下文窗口甚至不再那么好。在 GPT-3 中,上下文窗口增加到 2048 个 token,然后在 GPT-3.5 中增加到 4096 个 token。GPT-4 最初有 8192 个 token,后来增加到 32K,然后又增加到 128K(没错,128,000 个 token!)。现在开始出现具有 1M 或更大上下文窗口的模型,当它们进行 token 预测时,可以实现更好的一致性和召回率。

总之,马尔可夫链让我们能够以正确的方式思考文本生成问题,但它们存在很大的问题,阻碍我们将其视为可行的解决方案。

从马尔可夫链到神经网络

显然,我们必须忘记拥有一个概率表的想法,因为一个合理的上下文窗口的表需要大量的 RAM。我们可以做的是用一个函数替换这个表,该函数返回一个近似的标记概率,该近似值是通过算法生成的,而不是存储为一个大表。这实际上是神经网络可以做得很好的事情。

神经网络是一种特殊类型的函数,它接受一些输入,对它们进行一些计算,并返回输出。对于语言模型,输入是代表提示的标记,输出是下一个标记的预测概率列表。

我说神经网络是 “特殊” 函数。它们之所以特殊,是因为除了函数逻辑之外,它们对输入执行的计算还由许多外部定义的参数控制。最初,网络的参数是未知的,因此,该函数产生的输出完全无用。神经网络的训练过程包括找到使函数在对训练数据集中的数据进行评估时表现最佳的参数,假设如果函数与训练数据配合良好,那么它与其他数据配合也同样出色。

在训练过程中,参数会使用一种称为反向传播(backpropagation)的算法以小增量迭代调整,该算法涉及大量数学知识,因此本文中我不会讨论它。每次调整后,神经网络的预测都会变得更好一点。更新参数后,将再次根据训练数据集对网络进行评估,结果将用于下一轮调整。此过程持续进行,直到函数在训练数据集上执行良好的下一个标记预测。

为了帮助你了解神经网络的工作规模,请考虑 GPT-2 模型有大约 15 亿个参数,而 GPT-3 将参数数量增加到 1750 亿个。据说 GPT-4 有大约 1.76 万亿个参数。使用当前一代硬件训练这种规模的神经网络需要很长时间,通常是几周或几个月。

有趣的是,由于参数太多,都是在没有人工协助的情况下通过漫长的迭代过程计算出来的,因此很难理解模型的工作原理。经过训练的 LLM 就像一个极难调试的黑匣子,因为模型的大部分 “思维” 都隐藏在参数中。即使是训练它的人也很难解释它的内部工作原理。

层、变换器和注意力机制

 你可能想知道神经网络函数内部发生了哪些神秘的计算,这些计算可以在经过精心调整的参数的帮助下,获取输入标记列表并以某种方式输出后续标记的合理概率。

神经网络配置为执行一系列操作,每个操作称为一个层。第一层接收输入,并对其进行某种类型的转换。转换后的输入进入下一层并再次进行转换。此过程持续进行,直到数据到达最后一层并最后一次转换,生成输出或预测。

机器学习专家想出了不同类型的层来对输入数据执行数学转换,他们还想出了组织和分组层的方法,以便实现所需的结果。一些层是通用的,而另一些层则设计用于处理特定类型的输入数据,例如图像或 LLMs 中的标记化文本。

当今在大型语言模型中用于文本生成的神经网络架构称为 Transformer。使用这种设计的 LLM 被称为 GPTs,即生成式预训练 Transformer

Transformer 模型的显著特征是它们执行的层计算,称为注意力机制(Attention),这使它们能够得出上下文窗口中的标记之间的关系和模式,然后将其反映在下一个标记的结果概率中。

注意力机制最初用于语言翻译器,作为一种查找输入序列中哪些标记对于提取其含义最重要方法。这种机制使现代翻译人员能够通过关注(或引起 “注意”)重要的单词或标记来从基本层面 “理解” 句子。

LLMs 有智能吗?

现在你可能开始对 LLM 是否在生成文本的方式上表现出某种形式的智能形成看法。

我个人认为 LLMs 不具备推理或提出原创想法的能力,但这并不意味着它们毫无用处。由于它们对上下文窗口中的标记执行了巧妙的计算,LLM 能够拾取用户提示中存在的模式并将其与训练期间学习到的类似模式进行匹配。它们生成的文本大部分是由训练数据的片段组成的,但它们将单词(实际上是标记)拼接在一起的方式非常复杂,在许多情况下会产生感觉新颖且有用的结果。

鉴于 LLMs 容易产生幻觉,我不会相信任何 LLM 生成输出并直接发送给最终用户的工作流程,而无需人工验证。

未来几个月或几年将出现的更大规模的 LLMs 会实现任何类似于真正智能的东西吗?我觉得这不会发生在 GPT 架构上,因为它有很多限制,但谁知道呢,也许通过未来的一些创新,我们可以实现这一点。

结束

感谢你一直陪我到最后!我希望我已经引起了您的足够兴趣,让你决定继续学习,并最终面对所有那些如果您想了解每个细节就无法避免的可怕数学。在这种情况下,我强烈推荐 Andrej Karpathy 的《神经网络:从零到英雄》视频系列。

原文:How LLMs Work, Explained Without Math - miguelgrinberg.com

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/762904
推荐阅读
相关标签
  

闽ICP备14008679号