NLP 中的标记化方法
一些常用的字符串标记方法。
# Example string for tokenization
example_string = "It's over 9000!"
# Method 1: White Space Tokenization
# This method splits the text based on white spaces
white_space_tokens = example_string.split()
# Method 2: WordPunct Tokenization
# This method splits the text into words and punctuation
from nltk.tokenize import WordPunctTokenizer
wordpunct_tokenizer = WordPunctTokenizer()
wordpunct_tokens = wordpunct_tokenizer.tokenize(example_string)
# Method 3: Treebank Word Tokenization
# This method uses the standard word tokenization of the Penn Treebank
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', '!'])
每种方法都有其独特的方式将句子分解为标记。如果需要,可以创建自己的方法
为什么需要对字符串进行标记化?
- 将复杂的文本分解为可管理的单元。
- 以更易于分析或执行操作的格式呈现文本。
- 适用于特定的语言任务,如词性标记、句法解析和命名实体识别。
- 统一预处理 NLP 应用程序中的文本并创建结构化训练数据。
大多数 NLP 系统对这些标记执行一些操作以执行特定任务。例如,可以设计一个系统来获取一系列标记并预测下一个标记。还可以将标记转换为它们的语音表示,作为文本到语音系统的一部分。可以完成许多其他 NLP 任务,例如关键字提取、翻译等。
首先,如何实际使用这些tokens来构建这些系统?
- 特征提取:tokens用于提取输入到机器学习模型中的特征。特征可能包括标记本身、它们的频率、它们的词性标签、它们在句子中的位置等。例如,在情绪分析中,某些代币的存在可能强烈表明积极或消极的情绪。
- 矢量化:在许多 NLP 任务中,使用词袋 (BoW)、TF-IDF(术语频率-逆文档频率)或词嵌入(如 Word2Vec、GloVe)等技术将标记转换为数字向量。此过程将文本数据转换为机器学习模型可以理解和处理的数字。
- 序列建模:对于语言建模、机器翻译和文本生成等任务,标记用于递归神经网络 (RNN)、长短期记忆网络 (LSTM) 或转换器等序列模型。这些模型学习预测令牌序列,了解上下文和令牌出现的可能性。
- 训练模型:在训练阶段,向模型提供标记化文本和相应的标签或目标(如分类任务的类别或语言模型的下一个标记)。这些模型学习标记和所需输出之间的模式和关联。
- 上下文理解:BERT 和 GPT 等高级模型使用标记来理解上下文并生成嵌入,以捕获特定上下文中单词的含义。这对于同一单词可能根据其用法具有不同含义的任务至关重要。
ChatGPT 和Tokens
在类似 ChatGPT 的LLMs上下文中,tokens是什么样子的?LLMs中用于的标记化方法s与一般 NLP 中使用的标记化方法不同。
从广义上讲,可以将其称为“子词标记化”,创建的tokens不一定是完整的单词,就们在空格标记中看到的那样。这就是为什么一个词不等于一个标记的原因。
当说 GPT-4 Turbo 的上下文长度为 128K 个代币时,它并不完全是 128K 个单词,而是一个接近它的数字。
为什么要使用如此不同且更复杂的标记化方法?
- 这些标记是语言的更复杂的表示,而不是完整的单词。
- 它们有助于解决广泛的词汇,包括生僻和未知的单词。
- 使用较小的亚基在计算上更有效。
- 它们有助于更好地理解上下文。
- 它对可能与英语有很大不同语言的适应性更强。
LLMs中的标记化方法
字节对编码 (BPE)
许多开源模型,如 Meta 的 LLAMA-2 和较旧的 GPT 模型,都使用这种方法。
在实际环境中,BPE 会分析大量文本以确定最常见的文本对。
让以 GPT-2 Tokenizer 为例。
from transformers import GPT2Tokenizer
# Initialize the tokenizer
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
text = "It's over 9000!"
# Tokenize the text
token_ids = tokenizer.encode(text, add_special_tokens=True)
# Output the token IDs
print("Token IDs:", token_ids)
# Convert token IDs back to raw tokens and output them
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', '!']
什么是 Tokens ID?为什么是一个数字?
分解一下这个过程是如何工作的。
建立“词汇表”(这基本上是BPE方法的一部分)
- 从字符开始:最初,词汇表由单个字符(如字母和标点符号)组成。
- 查找公共字符对:扫描训练数据(大量文本语料库)以查找最常出现的字符对。例如,如果“th”经常出现,它就会成为要添加到词汇表中的候选词。
- 合并和创建新tokens:然后合并这些常见对以形成tokens。该过程以迭代方式继续,每次识别并合并下一个最频繁的对。词汇量从单个字符增长到常见的配对,最终发展到更大的结构,如常见单词或单词的一部分。
- 限制词汇量:词汇量是有限制的(例如,GPT-2 中有 50,000 个tokens)。一旦达到此限制,该过程就会停止,从而产生一个固定大小的词汇表,其中包括字符、常见配对和更复杂的标记的混合。
分配tokens ID
- 索引词汇:最终词汇表中的每个唯一token都分配一个唯一的数字索引或 ID。这很简单,就像在列表或数组中建立索引一样。
- token ID 表示:在 GPT-2 的上下文中,每段文本(如单词或单词的一部分)都由该词汇表中相应令牌的 ID 表示。如果一个单词不在词汇表中,它就会被分解为词汇表中的较小标记。
- 特殊标记:特殊标记(如表示文本或未知单词的开头和结尾的标记)也被分配了唯一的 ID。
关键点是,tokens ID 的分配不是任意的,而是基于模型训练的语言数据中的出现频率和组合模式。这使得 GPT-2 和类似模型能够使用一组可管理且具有代表性的代币有效地处理和生成人类语言。
在这里,“词汇表”是指模型可以识别和使用的所有唯一标记。它本质上是使用给定的标记化方法在训练数据的帮助下创建的令牌。。
目前大多数LLMs使用BPE的一些变体。例如,Mistral 模型使用字节回退 BPE 分词器。
BPE 之外的其他一些方法包括 unigram、sentencepiece 和 wordpiece。
什么是嵌入?
- token ID 是token的简单数字表示形式。事实上,它是矢量化的一种基本形式。它们不会捕获令牌之间任何更深层次的关系或模式。
- 标准矢量化技术(如 TF-IDF)包括基于某些逻辑创建更复杂的数值表示。
- 嵌入是标记的高级向量表示形式。它试图捕捉标记之间最细微的差别、联系和语义含义。每个嵌入通常是由神经网络计算的向量空间上的一系列实数。
简而言之,文本被转换为标记。令牌是分配的令牌 ID。这些令牌 ID 可用于创建嵌入,以便在复杂模型中实现更细微的数值表示。
为什么需要这一切?
因为计算机理解和操作数字。
嵌入是 的LLMs“真实输入”。
创建一个嵌入,看看它到底是什么样子。
token到嵌入转换
就像不同的token化方法一样,有几种方法可以进行令牌嵌入转换。以下是一些流行的:
- Word2Vec — 神经网络模型
- GloVe(Word 表示的全局向量)——一种无监督学习算法
- FastText — Word2Vec 的扩展
- BERT(来自变压器的双向编码器表示)
- ELMo (Embeddings from Language Models) — 一个深度双向 LSTM 模型。
以 BERT 创建嵌入为例。
from transformers import BertTokenizer, BertModel
import torch
# Load pre-trained model tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# Load pre-trained model
model = BertModel.from_pretrained('bert-base-uncased')
# Text to be tokenized
text = "It's over 9000!"
# Encode text
input_ids = tokenizer.encode(text, add_special_tokens=True)
# Output the token IDs
print("Token IDs:", input_ids)
# Convert token IDs back to raw tokens and output them
raw_tokens = [tokenizer.decode([token_id]) for token_id in input_ids]
print("Raw tokens:", raw_tokens)
# Convert list of IDs to a tensor
input_ids_tensor = torch.tensor([input_ids])
# Pass the input through the model
with torch.no_grad():
outputs = model(input_ids_tensor)
# Extract the embeddings
embeddings = outputs.last_hidden_state
# Print the embeddings
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 示例一样,首先对文本进行标记化。BERT Tokenizer 使用相同的 wordpiece 方法。它基本上根据某些标准将单词分解成更小的部分。
- 获取令牌 ID,然后打印原始令牌。请注意,与 GPT-2 分词器输出相比,它有何不同。
- 我们从令牌 ID 创建一个张量,并将其作为输入传递给预训练的 BERT 模型。
- 我们从最后一个隐藏状态中获取最终输出。
为什么嵌入如此庞大和复杂?它们意味着什么?
- 每个token的嵌入都是一个高维向量。这使得模型能够捕获广泛的语言特征和细微差别,例如单词的含义、词性以及与句子中其他单词的关系。
- 上下文嵌入:与更简单的单词嵌入(如 Word2Vec)不同,BERT 的嵌入是上下文嵌入的。这意味着同一个词可以根据其上下文(其周围的词)具有不同的嵌入。嵌入需要丰富而复杂,以捕捉这种上下文的细微差别。
- 在示例中,句子“It's over 9000!”被标记化为多个标记(包括 BERT 添加用于处理的特殊标记)。每个令牌都有自己的嵌入向量。
- 在更复杂的模型(如 BERT)中,不仅可以获得最终嵌入,还可以访问神经网络每一层的嵌入。每一层都捕获了语言的不同方面,增加了张量的复杂性和大小。
- 进一步任务的输入:这些嵌入用作各种 NLP 任务的输入,如情感分析、问答和语言翻译。嵌入的丰富性使模型能够高度复杂地执行这些任务。
- 模型的内部表示:这些张量的复杂性反映了模型如何“理解”语言。嵌入中的每个维度都可以表示模型在训练过程中学到的一些抽象语言特征。
简而言之,嵌入是使LLMs工作如此出色的秘诀。如果找到了创建更好的嵌入的方法,你很可能会创建一个更好的模型。
当使用经过训练的 AI 模型的架构处理这些数字时,它会以相同的格式计算新值,表示训练模型的任务的答案。在 LLMs中,它是对下一个代币的预测。
您在用户界面上看到的结果基本上是从生成的输出编号中检索到的文本。
在训练 LLM时,实际上是在尝试使用输入嵌入来优化模型中发生的所有数学计算,以创建所需的输出。
所有这些计算都包括一些称为模型权重的参数。它们确定模型如何处理输入数据以生成输出。
实际上,嵌入是模型权重的子集。它们是与输入层(在前馈网络的情况下)或嵌入层(在 Transformer 等模型中)(通常是第一层)相关的权重。
模型权重和嵌入可以初始化(或计算)为随机变量,也可以从预训练模型中获取。然后,这些值将在训练阶段更新。
目标是为模型权重找到正确的值,以便在给定输入的情况下,它所做的计算能够为给定的上下文产生最准确的输出。
直观结论
- 大型语言模型基本上是使用嵌入和模型权重进行复杂计算的大黑匣子。
- 文本 -> token ->tokens ID ->嵌入。计算机通过数字进行操作。嵌入是赋予LLMs上下文语言理解能力的秘诀。
- 有许多不同的技术可以创建令牌和嵌入,这可能会影响模型的工作方式。