正文
实际上它们只能接收你提供的一些文本作为输入,然后猜测下一个词(更准确地说,下一个 Token)是什么
。
让我们从 Token 开始了解 LLM 的奥秘。
Token
Token 是 LLM 理解的文本基本单位。虽然将 Token 看作单词很方便,但对 LLM 来说,目标是尽可能高效地编码文本,所以在许多情况下,Token 代表的字符序列比整个单词都要短或长。标点符号和空格也被表示为 Token,可能是单独或与其他字符组合表示。
LLM 使用的所有 Token 统称为其词汇,因为它可以用来表示任何可能的文本。字节对编码(BPE)算法通常用于 LLM 生成 Token 词汇。为了让你对规模有个大致的了解,GPT-2 语言模型是开源的,可以详细研究,其词汇量为 50,257 个 Token。
LLM 词汇中的每个 Token 都有一个唯一的标识符,通常是一个数字。LLM 使用分词器在常规文本字符串和等效的 Token 数列表之间进行转换。如果你熟悉 Python 并想尝试 Token,可以安装 OpenAI 的 tiktoken 包:
$ pip install tiktoken
然后在 Python 提示符中尝试以下内容:
>>> import tiktoken
>>> encoding = tiktoken.encoding_for_model("gpt-2")
>>> encoding.encode("The quick brown fox jumps over the lazy dog.")
[464, 2068, 7586, 21831, 18045, 625, 262, 16931, 3290, 13]
>>> encoding.decode([464, 2068, 7586, 21831, 18045, 625, 262, 16931, 3290, 13])
'The quick brown fox jumps over the lazy dog.'
>>> encoding.decode([464])
'The'
>>> encoding.decode([2068])
' quick'
>>> encoding.decode([13])
'.'
在这个实验中,你可以看到对于 GPT-2 语言模型,Token 464 代表单词 “The”,而 Token 2068 代表单词 “quick”,包括一个前导空格。该模型使用 Token 13 表示句号。
由于 Token 是通过算法确定的,你可能会发现一些奇怪的现象,比如这三个变体的单词 “the”,在 GPT-2 中都编码为不同的 Token:
>>> encoding.encode('The')
[464]
>>> encoding.encode('the')
[1169]
>>> encoding.encode(' the')
[262]
BPE 算法并不总是将整个单词映射到 Token。事实上,使用频率较低的单词不能成为独立的 Token,必须使用多个 Token 进行编码。以下是一个使用两个 Token 编码的单词示例:
>>> encoding.encode("Payment")
[19197, 434]
>>> encoding.decode([19197])
'Pay'
>>> encoding.decode([434])
'ment'
下一个 Token 预测
如上所述,给定一些文本,语言模型会预测下一个紧跟其后的 Token。如果用 Python 伪代码展示可能会更清晰,下面是如何运行这些模型以获取下一个 Token 的预测:
predictions = get_token_predictions(['The', ' quick', ' brown', ' fox'])
该函数接收一个由用户提供的提示词编码而来的输入 Token 列表。在这个例子中,我假设每个单词都是一个独立的 Token。为了简化,我使用每个 Token 的文本表示,但正如你之前看到的,实际上每个 Token 会作为一个数字传递给模型。
这个函数的返回值是一个数据结构,它为词汇表中的每个 Token 分配一个紧随输入文本之后的概率。如果基于 GPT-2,这个函数的返回值将是一个包含 50,257 个浮点数的列表,每个浮点数预测相应 Token 将会出现的概率。
在上述例子中,你可以想象,一个训练良好的语言模型会给 Token “jumps” 一个较高的概率来紧跟提示词 “The quick brown fox” 后面。同样假设模型训练得当,你也可以想象,随机单词如 “potato” 继续这个短语的概率会非常低,接近于 0。
为了能够生成合理的预测,语言模型必须经过训练过程。在训练期间,它会被提供大量文本以进行学习。训练结束时,模型能够使用它在训练中见到的所有文本构建的数据结构来计算给定 Token 序列的下一个 Token 概率。
这与你的预期有何不同?我希望这现在看起来不再那么神奇了。
生成长文本序列
由于模型只能预测下一个 Token 是什么,因此生成完整句子的唯一方法是多次循环运行模型。每次循环迭代都会生成一个新的 Token,从返回的概率中选择该 Token。然后将该 Token 添加到下一次循环迭代的输入中,直到生成足够的文本为止。
让我们看一个更完整的 Python 伪代码,展示这种方法的工作原理:
def generate_text(prompt, num_tokens, hyperparameters):
tokens = tokenize(prompt)
for i in range(num_tokens):
predictions = get_token_predictions(tokens)
next_token = select_next_token(predictions, hyperparameters)
tokens.append(next_token)
return ''.join(tokens)
generate_text() 函数将用户提示作为参数。这可能是一个问题。
tokenize() 辅助函数使用 tiktoken 或类似库将提示转换为等效的 Token 列表。在 for 循环中,get_token_predictions() 函数是调用 AI 模型以获取下一个 Token 的概率,如前面的例子所示。
select_next_token() 函数的作用是获取下一个 Token 的概率(或预测)并选择最佳 Token 以继续输入序列。函数可以只选择概率最高的 Token,这在机器学习中称为 “贪心选择(greedy selection)”。更好的是,它可以使用随机数生成器来选择一个符合模型返回概率的 Token,从而为生成的文本添加一些变化。这也会使模型在多次给出相同提示时产生不同的响应。
为了使 Token 选择过程更加灵活,LLM 返回的概率可以使用超参数进行修改,这些超参数作为参数传递给文本生成函数。超参数允许你控制 Token 选择过程的 “贪婪” 程度。
如果你使用过 LLM,你可能熟悉 temperature 超参数。temperature 越高,Token 概率越平坦,这增加了选择不太可能的 Token 的机会,最终使生成的文本看起来更有创造性或更不寻常。你可能还使用了另外两个超参数,称为 top_p 和 top_k,它们控制被考虑选择的最高概率的 Token 数量。