点击下方卡片,关注「魔方AI空间」公众号
引 言
本文是LLM大模型基础入门系列的第6篇。在这篇文章中,我们重点介绍LLMs中的Tokens 和 Embeddings!
❝“GPT4 Turbo 的上下文长度为 128K 个Token。”
“Claude 2.1 的上下文长度为 200K 个Token。”
Token 到底是什么?
考虑一个句子——“It’s over 9000!”。
我们可以将其表示为 ["It's", "over", "9000!"]
,其中每个数组元素都可以称为一个Token。
在自然语言处理的世界里,Token 是我们定义的最小的分析单元。你称之为token的东西取决于你的token化方法;存在许多这样的方法。创建tokens基本上是大多数NLP任务需要执行的第一步。
NLP中的Token化方法
让我们直接跳到代码样本,以理解一些流行的将字符串token化的方式。
# 用于token化的示例字符串
example_string = "It's over 9000!"
# 方法1:空白Token化
# 这种方法基于空白字符分割文本
white_space_tokens = example_string.split()
# 方法2:WordPunct Token化
# 这种方法将文本分割成单词和标点符号
from nltk.tokenize import WordPunctTokenizer
wordpunct_tokenizer = WordPunctTokenizer()
wordpunct_tokens = wordpunct_tokenizer.tokenize(example_string)
# 方法3:Treebank单词Token化
# 这种方法使用Penn Treebank的标准单词token化
from nltk.tokenize import TreebankWordTokenizer
treebank_tokenizer = TreebankWordTokenizer()
treebank_tokens = treebank_tokenizer.tokenize(example_string)
white_space_tokens, wordpunct_tokens, treebank_tokens
(["It's", 'over', '9000!'],
['It', "'", 's', 'over', '9000', '!'],
['It', "'s", 'over', '9000', '!'])
每种方法都有其独特的将句子分解为标记的方式。 如果需要,可以创建自己的方法,但基本要点保持不变。
为什么需要将字符串token化?
将复杂文本分解成易于管理的单元。 以更易于分析或操作的格式呈现文本。 适用于特定的语言任务,如词性标注、句法解析和命名实体识别。 在NLP应用中统一预处理文本并创建结构化训练数据。
大多数NLP系统对这些 tokens 执行一些操作以执行特定任务。例如,我们可以设计一个系统,它接受一系列tokens并预测下一个token。我们也可以作为文本到语音系统的一部分,将 tokens 转换为它们的音素表示。还可以执行许多其他NLP任务,如关键词提取、翻译等。
最初是如何使用这些tokens来构建这些系统的?
特征提取:tokens用于提取输入到机器学习模型的特征。特征可能包括tokens本身、它们的频率、它们的词性标签、它们在句子中的位置等。例如,在情感分析中,某些tokens的存在可能强烈表明正面或负面的情感。 向量化:在许多NLP任务中,tokens使用诸如词袋(BoW)、TF-IDF(词频-逆文档频率)或词嵌入(如Word2Vec、GloVe)等技术转换为数值向量。这个过程将文本数据转换为机器学习模型可以理解和处理的数字。 序列建模:对于语言建模、机器翻译和文本生成等任务,tokens在序列模型中使用,如循环神经网络(RNN)、长短期记忆网络(LSTM)或Transformers。这些模型学习预测token序列,理解上下文和token出现的可能性。 训练模型:在训练阶段,模型被输入token化的文本和相应的标签或目标(如分类任务的类别或语言模型的下一个token)。模型学习tokens和期望输出之间的模式和关联。 上下文理解:像BERT和GPT这样的高级模型使用 tokens 来理解上下文并生成捕获特定上下文中单词含义的嵌入。这对于同一单词根据其用法可能具有不同含义的任务至关重要。
如果你是这方面的新手,不用担心你刚刚读到的关键词。用非常简单的话来说,我们有文本字符串,我们将其转换为称为tokens的独立单元。这使得它们更容易转换为“数字”,计算机可以理解。
ChatGPT和Tokens
在像ChatGPT这样的大型语言模型(LLMs)的上下文中,tokens看起来是什么样的?用于LLMs的token化方法与用于一般NLP的方法不同。
总的来说,我们可以称之为“子词token化”,我们创建的tokens不一定是我们在空白token化中看到的完整单词。这正是为什么一个单词不等于一个token。
当他们说GPT-4 Turbo有 128K 个tokens作为其上下文长度时,它并不是确切的128K个单词,而是一个接近这个数字的数字。
为什么要使用这样不同且更复杂的token化方法?
这些tokens是比完整单词更复杂的语言表示。 它们帮助解决包括罕见和未知单词在内的广泛词汇。 使用较小的子单位在计算上更有效率。 它们有助于更好的上下文理解。 它更适合于与英语截然不同的各种语言。
LLMs中的Token化方法
字节对编码(Byte-Pair-Encoding, BPE)
许多开源模型,如Meta的LLAMA-2和旧的GPT模型,使用这种方法的一个版本。
在现实世界中,BPE 分析大量文本以确定最常见的对。
让我们以GPT-2 Tokenizer为例。
from transformers import GPT2Tokenizer
# 初始化tokenizer
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
text = "It's over 9000!"
# Tokenize文本
token_ids = tokenizer.encode(text, add_special_tokens=True)
# 输出token IDs
print("Token IDs:", token_ids)
# 将token IDs转换回原始tokens并输出它们
raw_tokens = [tokenizer.decode([token_id]) for token_id in token_ids]
print("Raw tokens:", raw_tokens)
Token IDs: [1026, 338, 625, 50138, 0]
Raw tokens: ['It', "'s", ' over', ' 9000', '!']
什么是token ID?为什么它是数字?
让我们分解这个过程是如何工作的。
构建“词汇表”(这基本上是BPE方法的一部分)
从字符开始:最初,词汇表由单个字符(如字母和标点符号)组成。 查找常见对:训练数据(大量文本语料库)被扫描以查找最常出现的字符对。例如,如果‘th’经常出现,它就成为添加到词汇表的候选。 合并并创建新tokens:然后合并这些常见对以形成新的 tokens。这个过程是迭代进行的,每次都识别并合并下一个最频繁出现的对。词汇表从单个字符增长到常见配对,最终到像常见单词或单词部分这样的更大结构。 限制词汇表大小:词汇表大小有一个限制(例如,GPT-2中有50,000个tokens)。一旦达到这个限制,过程就停止了,结果是一个固定大小的词汇表,包括字符、常见配对和更复杂的tokens的混合。
分配Token IDs
索引词汇表:最终词汇表中的每个唯一token都被分配一个唯一的数字索引或ID。这是直接进行的,就像在列表或数组中进行索引一样。 Token ID表示:在GPT-2的背景下,每段文本(如一个单词或单词的一部分)由相应token在该词汇表中的ID表示。如果一个单词不在词汇表中,它就被分解成在词汇表中的较小tokens。 特殊tokens:特殊tokens(如代表文本开始和结束或未知单词的tokens)也被分配了唯一ID。
关键点是,token ID的分配不是任意的,而是基于模型训练时的语言数据中出现的频率和组合模式。这允许GPT-2和类似的模型使用易于管理和有代表性的token集高效地处理和生成人类语言。
在这里,“词汇表”指的是模型能够识别和使用的所有唯一tokens。它本质上是使用给定的token化方法和训练数据创建的tokens。
当前一代的LLMs大多使用BPE的一些变体。例如,Mistral模型使用字节回退BPE tokenizer。
除了BPE之外,还有其他一些方法,包括unigram、sentencepiece和wordpiece。
现在,我们不用担心所有这些。
现在,重要的是要知道,创建tokens是处理NLP或LLMs时的第一步。存在不同的token化方法来创建tokens,这些tokens也被分配了一些token ID。
什么是嵌入?
我们已经提到过这个词。在我们深入之前,让我们澄清一些混淆。
Token ID是tokens的直接数值表示。实际上,它是一种基本形式的向量化。它们不捕捉tokens之间的更深层次的关系或模式。 标准向量化技术(如TF-IDF)包括根据某些逻辑创建更复杂的数值表示。 嵌入是tokens的高级向量表示。它们试图捕捉tokens之间的细微差别、联系和语义含义。每个嵌入通常是通过神经网络计算的一系列实数在向量空间上的。
简而言之,文本被转换为tokens。Tokens被分配token ID。这些token ID可以用来创建嵌入,以便在复杂模型中进行更微妙的数值表示。
为什么这么做?
❝因为计算机理解和操作数字。
嵌入是LLMs的“真正输入”。
让我们创建一个嵌入来看看它到底是什么样子。
Token到嵌入的转换
就像不同的token化方法一样,我们有几种方法来进行token-嵌入转换。以下是一些流行的:
Word2Vec — 一个神经网络模型 GloVe(Global Vectors for Word Representation)— 一种无监督学习算法 FastText — Word2Vec的扩展 BERT(来自 Transformers 的双向编码器表示) ELMo(语言模型嵌入)——一种深度双向 LSTM 模型。
现在我们不用担心每种方法的内部工作。你所需要知道的是,你可以使用它们来创建文本的数值表示,这样计算机就可以理解。
使用 BERT 创建嵌入作为示例。
from transformers import BertTokenizer, BertModel
import torch
# 加载预训练模型的tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# 加载预训练模型
model = BertModel.from_pretrained('bert-base-uncased')
# 要进行token化的文本
text = "It's over 9000!"
# 对文本进行编码
input_ids = tokenizer.encode(text, add_special_tokens=True)
# 输出token IDs
print("Token IDs:", input_ids)
# 将token IDs转换回原始tokens并输出它们
raw_tokens = [tokenizer.decode([token_id]) for token_id in input_ids]
print("Raw tokens:", raw_tokens)
# 将ID列表转换为张量
input_ids_tensor = torch.tensor([input_ids])
# 将输入传递给模型
with torch.no_grad():
outputs = model(input_ids_tensor)
# 提取嵌入
embeddings = outputs.last_hidden_state
# 打印嵌入
print("Embeddings: ", embeddings)
Token IDs: [101, 2009, 1005, 1055, 2058, 7706, 2692, 999, 102]
Raw tokens: ['[CLS]', 'it', "'", 's', 'over', '900', '##0', '!', '[SEP]']
Embeddings: tensor([[[ 0.1116, 0.0722, 0.3173, ..., -0.0635, 0.2166, 0.3236],
[-0.4159, -0.5147, 0.5690, ..., -0.2577, 0.5710, 0.4439],
[-0.4893, -0.8719, 0.7343, ..., -0.3001, 0.6078, 0.3938],
...,
[-0.2746, -0.6479, 0.2702, ..., -0.4827, 0.1755, -0.3939],
[ 0.0846, -0.3420, 0.0216, ..., 0.6648, 0.3375, -0.2893],
[ 0.6566, 0.2011, 0.0142, ..., 0.0786, -0.5767, -0.4356]]])
就像之前使用GPT-2的例子一样,首先对文本进行token化。BERT Tokenizer使用wordpiece方法进行同样的操作。它基本上根据某些标准将单词分解成更小的部分。 获取token IDs,然后打印原始tokens。注意它与GPT-2 tokenizer输出的不同。 从token IDs创建一个张量,将其作为输入传递给预训练的BERT模型。 从最后一个隐藏状态获取最终的输出。
正如你所看到的,嵌入基本上是数字数组。
当你说,“It’s over 9000! ”计算机本质上读取一个非常大的N维张量数组,其中包含实数。
为什么嵌入如此之大和复杂?它们代表什么?
每个token的嵌入是一个高维向量。这允许模型捕捉广泛的语言特征和细微差别,比如一个词的含义、它的词性以及它与句子中其他词的关系。 上下文嵌入:与简单的词嵌入(如Word2Vec)不同,BERT的嵌入是上下文的。这意味着同一个词可以根据其上下文(其周围的词)有不同的嵌入。嵌入需要丰富和复杂,以捕捉这种上下文细微差别。 在示例中,句子“*It’s over 9000!*”被分解成多个tokens(包括BERT为处理而添加的特殊tokens)。每个token都有自己的嵌入向量。 在像BERT这样更复杂的模型中,你不仅得到最终的嵌入,还可以访问神经网络每层的嵌入。每层捕捉语言的不同方面,增加了张量的复杂性和大小。 进一步任务的输入:这些嵌入被用作各种NLP任务的输入,如情感分析、问题回答和语言翻译。嵌入的丰富性允许模型以高度复杂的方式执行这些任务。 模型的内部表示:这些张量的复杂性反映了模型“理解”语言的方式。嵌入中的每个维度都可以表示模型在训练期间学到的某种抽象语言特征。
简而言之,嵌入是使LLMs如此有效的秘诀。如果你找到创建更好嵌入的方法,你很可能会创建一个更好的模型。
当这些数字通过训练有素的AI模型的架构处理时,它计算出同一格式的新值,代表模型训练的任务的答案。在LLMs中,它是对下一个token的预测。
你在用户界面上看到的结果基本上是从输出数字中检索的文本。
当训练一个LLM时,你实际上是在尝试优化模型中发生的所有数学计算,这些计算使用输入嵌入来创建所需的输出。
所有这些计算包括一些称为模型权重的参数。它们决定了模型如何处理输入数据以产生输出。
嵌入实际上,是模型权重的一个子集。它们是与输入层(在前馈网络的情况下)或嵌入层(在像Transformers这样的模型中)相关联的权重(通常是第一层)。
模型权重和嵌入可以初始化(或计算)为随机变量,也可以从预训练模型中获取。这些值然后在训练阶段更新。
目标是找到正确的模型权重值,以便给定输入时,它所做的计算能够产生给定上下文中最准确的输出。
直观的结论
大语言模型基本上是使用嵌入和模型权重进行复杂计算的大型黑盒。 文本 -> Tokens -> Token IDs -> 嵌入。计算机在底层操作数字。嵌入是赋予LLMs其上下文语言理解的秘诀。 有许多不同的技术可以创建tokens和嵌入,这可能会显著影响模型的工作方式。
技术交流
加入「AIGCmagic社区」群聊,一起交流讨论,涉及 AI视频、AI绘画、Sora技术拆解、数字人、多模态、大模型、传统深度学习、自动驾驶等多个不同方向,可私信或添加微信号:【m_aigc2022】,备注不同方向邀请入群!!
更多精彩内容,尽在「魔方AI空间」,关注了解全栈式 AIGC内容!!