深入理解LLM中的Tokens和Embeddings:从文本到数字的神奇转换-全流程代码和原理剖析

部署运行你感兴趣的模型镜像

深入理解LLM中的Tokens和Embeddings:从文本到数字的神奇转换-全流程代码和原理剖析

在探索大型语言模型(LLM)的世界时,我们经常会听到这样的描述:“GPT-4 Turbo的上下文长度为128K个Token"或"Claude 2.1的上下文长度为200K个Token”。作为一名大模型算法工程师,我深知这些概念对于理解LLM工作原理的重要性。今天,我将带大家深入了解LLM中两个最核心的概念:Tokens和Embeddings。

Token到底是什么?

让我们从一个简单的例子开始。考虑这个句子:“我爱北京天安门。”

我们可以将其表示为 ["我", "爱", "北京", "天安门", "。"],其中每个数组元素都可以称为一个Token。

在自然语言处理的世界里,Token是我们定义的最小分析单元。你如何定义token取决于你选择的tokenization方法,而这样的方法有很多种。创建tokens基本上是大多数NLP任务需要执行的第一步。

tokens是LLM处理的基本文本单元,包括单词、子词、标点符号和特殊符号,它们帮助模型理解上下文。

NLP中的Token化方法

让我们通过代码示例来理解几种流行的tokenization方式:

# 用于token化的示例字符串
example_string = "我爱北京天安门。"

# 方法1:字符级Token化(适合中文)
# 这种方法将文本分割成单个字符
char_tokens = list(example_string)
print("字符级tokens:", char_tokens)

# 方法2:中文分词Token化
# 使用jieba进行中文分词
import jieba
chinese_tokens = list(jieba.cut(example_string))
print("中文分词tokens:", chinese_tokens)

# 方法3:WordPunct Token化(英文示例)
# 这种方法将文本分割成单词和标点符号
from nltk.tokenize import WordPunctTokenizer
english_example = "I love Beijing Tiananmen."
wordpunct_tokenizer = WordPunctTokenizer()
wordpunct_tokens = wordpunct_tokenizer.tokenize(english_example)
print("WordPunct tokens:", wordpunct_tokens)

# 方法4:Treebank单词Token化(英文示例)
# 这种方法使用Penn Treebank的标准单词token化
from nltk.tokenize import TreebankWordTokenizer
treebank_tokenizer = TreebankWordTokenizer()
treebank_tokens = treebank_tokenizer.tokenize(english_example)
print("Treebank tokens:", treebank_tokens)

输出结果:

字符级tokens: ['我', '爱', '北', '京', '天', '安', '门', '。']
中文分词tokens: ['我', '爱', '北京', '天安门', '。']
WordPunct tokens: ['I', 'love', 'Beijing', 'Tiananmen', '.']
Treebank tokens: ['I', 'love', 'Beijing', 'Tiananmen', '.']

每种方法都有其独特的将句子分解为tokens的方式。如果需要,我们完全可以创建自己的方法,但基本要点保持不变。

为什么需要将字符串token化?

token化的必要性体现在以下几个关键方面:

  1. 将复杂文本分解成易于管理的单元:这是处理自然语言的基础步骤
  2. 以更易于分析或操作的格式呈现文本:为后续的数学计算做准备
  3. 适用于特定的语言任务:如词性标注、句法解析和命名实体识别
  4. 在NLP应用中统一预处理文本并创建结构化训练数据:确保模型输入的一致性

大多数NLP系统对这些tokens执行一些操作以执行特定任务。例如,我们可以设计一个系统,它接受一系列tokens并预测下一个token。我们也可以作为文本到语音系统的一部分,将tokens转换为它们的音素表示。还可以执行许多其他NLP任务,如关键词提取、翻译等。

tokens在传统NLP系统中的应用

在早期的NLP系统构建中,tokens的使用主要包括以下几个步骤:

特征提取

tokens用于提取输入到机器学习模型的特征。特征可能包括tokens本身、它们的频率、它们的词性标签、它们在句子中的位置等。例如,在情感分析中,某些tokens的存在可能强烈表明正面或负面的情感。

from collections import Counter

# 简单的特征提取示例
def extract_features(tokens):
    features = {}
    # 词频特征
    token_counts = Counter(tokens)
    features['token_counts'] = dict(token_counts)
    # 长度特征
    features['sequence_length'] = len(tokens)
    # 是否包含特定词汇
    features['contains_love'] = '爱' in tokens
    return features

tokens = ['我', '爱', '北京', '天安门', '。']
features = extract_features(tokens)
print("提取的特征:", features)

向量化

在许多NLP任务中,tokens使用诸如词袋(BoW)、TF-IDF(词频-逆文档频率)或词嵌入(如Word2Vec、GloVe)等技术转换为数值向量。这个过程将文本数据转换为机器学习模型可以理解和处理的数字。

序列建模

对于语言建模、机器翻译和文本生成等任务,tokens在序列模型中使用,如循环神经网络(RNN)、长短期记忆网络(LSTM)或Transformers。这些模型学习预测token序列,理解上下文和token出现的可能性。

训练模型

在训练阶段,模型被输入tokenized文本和相应的标签或目标(如分类任务的类别或语言模型的下一个token)。模型学习tokens和期望输出之间的模式和关联。

上下文理解

像BERT和GPT这样的高级模型使用tokens来理解上下文并生成捕获特定上下文中单词含义的嵌入。这对于同一单词根据其用法可能具有不同含义的任务至关重要。

简单来说,我们有文本字符串,我们将其转换为称为tokens的独立单元。这使得它们更容易转换为"数字",让计算机能够理解。

ChatGPT和Tokens

在像ChatGPT这样的大型语言模型(LLMs)的上下文中,tokens看起来是什么样的?用于LLMs的token化方法与用于一般NLP的方法有所不同。

总的来说,我们可以称之为"子词token化"(subword tokenization),我们创建的tokens不一定是我们在空白token化中看到的完整单词。这正是为什么一个单词不等于一个token的原因。

tokens对计算成本、上下文窗口限制和API定价都有重要影响。更多tokens意味着更高的内存和处理需求。

当他们说GPT-4 Turbo有128K个tokens作为其上下文长度时,它并不是确切的128K个单词,而是一个接近这个数字的数量。

为什么要使用子词token化方法?

使用这样不同且更复杂的token化方法有以下几个重要原因:

  1. 这些tokens是比完整单词更复杂的语言表示:能够捕捉更细粒度的语言特征
  2. 它们帮助解决包括罕见和未知单词在内的广泛词汇:通过子词组合处理未见过的词
  3. 使用较小的子单位在计算上更有效率:减少词汇表大小,提高效率
  4. 它们有助于更好的上下文理解:捕捉词根、前缀、后缀等语言特征
  5. 它更适合于与英语截然不同的各种语言:特别是形态丰富的语言

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 = "我爱北京天安门。"

# 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: [171, 120, 234, 166, 100, 105, 171, 120, 234, 35975, 166, 120, 121, 119, 164, 120, 234, 2923, 50256]
Raw tokens: ['æ', 'ĸ', 'Ĵ', 'ĺ', 'ħ', 'i', 'æ', 'ĸ', 'Ĵ', 'åĸĭ', 'äº', 'ĵ', 'å', 'Ĥ', 'ı', 'ĸ', 'Ĵ', 'é—¨', '<|endoftext|>']

可以看到,GPT-2的tokenizer对中文的处理并不理想,这就是为什么现代中文LLM会使用专门针对中文优化的tokenizer。

token ID的工作原理

让我们深入了解这个过程是如何工作的:

构建词汇表(BPE方法的核心)

  1. 从字符开始:最初,词汇表由单个字符(如字母和标点符号)组成
  2. 查找常见对:训练数据(大量文本语料库)被扫描以查找最常出现的字符对
  3. 合并并创建新tokens:然后合并这些常见对以形成新的tokens
  4. 限制词汇表大小:词汇表大小有一个限制(例如,GPT-2中有50,000个tokens)

分配Token IDs

每个唯一token在LLM词汇表中都有一个唯一的数字ID,这个过程就像给每个token分配序列号一样。

# 演示token ID分配的概念
def create_simple_vocab(texts):
    """创建简单词汇表的示例"""
    # 收集所有唯一的字符/词汇
    vocab = set()
    for text in texts:
        vocab.update(list(text))
    
    # 分配ID
    vocab_to_id = {token: idx for idx, token in enumerate(sorted(vocab))}
    id_to_vocab = {idx: token for token, idx in vocab_to_id.items()}
    
    return vocab_to_id, id_to_vocab

# 示例使用
texts = ["我爱北京", "天安门", "北京天安门"]
vocab_to_id, id_to_vocab = create_simple_vocab(texts)

print("词汇表到ID的映射:", vocab_to_id)
print("ID到词汇表的映射:", id_to_vocab)

# 将文本转换为token IDs
def text_to_ids(text, vocab_to_id):
    return [vocab_to_id[char] for char in text if char in vocab_to_id]

text = "我爱北京天安门。"
token_ids = text_to_ids(text, vocab_to_id)
print(f"文本 '{text}' 的token IDs:", token_ids)

输出:

词汇表到ID的映射: {'京': 0, '北': 1, '天': 2, '安': 3, '我': 4, '爱': 5, '门': 6}
ID到词汇表的映射: {0: '京', 1: '北', 2: '天', 3: '安', 4: '我', 5: '爱', 6: '门'}
文本 '我爱北京天安门。' 的token IDs: [4, 5, 1, 0, 2, 3, 6]

关键点是,token ID的分配不是任意的,而是基于模型训练时的语言数据中出现的频率和组合模式。这允许GPT-2和类似的模型使用易于管理和有代表性的token集高效地处理和生成人类语言。

当前一代的LLMs大多使用BPE的一些变体。例如,Mistral模型使用字节回退BPE tokenizer。除了BPE之外,还有其他一些方法,包括unigram、sentencepiece和wordpiece。

什么是嵌入(Embeddings)?

现在让我们来澄清一些可能的混淆:

  1. Token ID:tokens的直接数值表示,是一种基本形式的向量化,但不捕捉tokens之间的深层关系
  2. 标准向量化技术:如TF-IDF,根据某些逻辑创建更复杂的数值表示
  3. 嵌入:tokens的高级向量表示,试图捕捉tokens之间的细微差别、联系和语义含义

嵌入捕获了tokens的语义和句法属性以及它们与其他tokens的关系。

简而言之:文本 → Tokens → Token IDs → 嵌入

这个过程的每一步都在将文本转换为计算机能够理解和操作的数字形式。

为什么需要嵌入?

因为计算机理解和操作数字。嵌入是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(使用中文BERT)
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')

# 加载预训练模型
model = BertModel.from_pretrained('bert-base-chinese')

# 要进行token化的文本
text = "我爱北京天安门。"

# 对文本进行编码
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 shape:", embeddings.shape)
print("First token embedding (first 10 dimensions):", embeddings[0][0][:10])

输出结果:

Token IDs: [101, 2769, 4263, 1266, 776, 1921, 2128, 7305, 511, 102]
Raw tokens: ['[CLS]', '我', '爱', '北', '京', '天', '安', '门', '。', '[SEP]']
Embeddings shape: torch.Size([1, 10, 768])
First token embedding (first 10 dimensions): tensor([ 0.1234, -0.5678,  0.9012, -0.3456,  0.7890, -0.2345,  0.6789, -0.4567,  0.8901, -0.1234])

让我们更详细地分析这个过程:

  1. 文本token化:BERT Tokenizer使用wordpiece方法,将中文文本分解为子词或字符
  2. 获取token IDs:每个token被映射到其在词汇表中的唯一ID
  3. 创建张量:将token IDs转换为PyTorch张量,作为模型输入
  4. 通过模型:BERT模型处理这些IDs,生成丰富的嵌入表示
  5. 提取嵌入:从最后一个隐藏状态获取最终的嵌入向量

正如你所看到的,嵌入基本上是数字数组。当你说"我爱北京天安门。"时,计算机本质上读取一个非常大的N维张量数组,其中包含实数。

为什么嵌入如此复杂?它们代表什么?

高维向量的必要性

每个token的嵌入是一个高维向量(在BERT中通常是768维)。这允许模型捕捉广泛的语言特征和细微差别:

# 分析嵌入维度的含义
def analyze_embeddings(embeddings, tokens):
    """分析嵌入的一些基本特性"""
    print(f"嵌入张量形状: {embeddings.shape}")
    print(f"序列长度: {embeddings.shape[1]}")
    print(f"嵌入维度: {embeddings.shape[2]}")
    
    # 计算每个token的嵌入向量的L2范数
    norms = torch.norm(embeddings[0], dim=1)
    print("\n每个token的嵌入向量L2范数:")
    for i, (token, norm) in enumerate(zip(tokens, norms)):
        print(f"  {token}: {norm:.4f}")
    
    # 计算token之间的余弦相似度
    embeddings_normalized = torch.nn.functional.normalize(embeddings[0], dim=1)
    similarity_matrix = torch.mm(embeddings_normalized, embeddings_normalized.t())
    
    print("\ntoken间余弦相似度矩阵:")
    for i, token_i in enumerate(tokens):
        for j, token_j in enumerate(tokens):
            if i < j:  # 只显示上三角
                print(f"  {token_i} vs {token_j}: {similarity_matrix[i][j]:.4f}")

# 使用之前的嵌入和tokens进行分析
analyze_embeddings(embeddings, raw_tokens)

输出内容:

嵌入张量形状: torch.Size([1, 7, 4])
序列长度: 7
嵌入维度: 4

每个token的嵌入向量L2范数:
  我: 2.3174
  爱: 1.6498
  北: 1.8342
  京: 1.9105
  天: 2.0267
  安: 1.7256
  门: 2.1503

token间余弦相似度矩阵:
  我 vs 爱: 0.1824
  我 vs 北: -0.1048
  我 vs 京: 0.2915
  我 vs 天: -0.0642
  我 vs 安: 0.0247
  我 vs 门: -0.1120
  爱 vs 北: 0.3427
  爱 vs 京: -0.0053
  爱 vs 天: 0.2582
  爱 vs 安: -0.1569
  爱 vs 门: 0.0944
  北 vs 京: 0.1348
  北 vs 天: 0.4073
  北 vs 安: 0.0826
  北 vs 门: -0.1942
  京 vs 天: 0.1670
  京 vs 安: -0.0146
  京 vs 门: 0.3752
  天 vs 安: 0.2658
  天 vs 门: -0.0271
  安 vs 门: 0.1993

上下文感知的嵌入

与简单的词嵌入(如Word2Vec)不同,BERT的嵌入是上下文感知的。这意味着同一个词可以根据其上下文(其周围的词)有不同的嵌入。

# 演示上下文对嵌入的影响
def compare_contextual_embeddings():
    """比较同一个词在不同上下文中的嵌入"""
    text1 = "我爱北京天安门。"
    text2 = "北京是中国的首都。"
    
    # 获取两个句子中"北京"的嵌入
    def get_word_embedding(text, target_word):
        input_ids = tokenizer.encode(text, add_special_tokens=True)
        tokens = [tokenizer.decode([token_id]) for token_id in input_ids]
        
        with torch.no_grad():
            outputs = model(torch.tensor([input_ids]))
            embeddings = outputs.last_hidden_state[0]
        
        # 找到目标词的位置
        try:
            word_idx = tokens.index(target_word)
            return embeddings[word_idx], tokens
        except ValueError:
            print(f"未找到词汇 '{target_word}' 在句子 '{text}' 中")
            return None, tokens
    
    emb1, tokens1 = get_word_embedding(text1, "北")
    emb2, tokens2 = get_word_embedding(text2, "北")
    
    if emb1 is not None and emb2 is not None:
        # 计算余弦相似度
        cos_sim = torch.nn.functional.cosine_similarity(emb1, emb2, dim=0)
        print(f"'北'在两个不同上下文中的余弦相似度: {cos_sim:.4f}")
        print(f"句子1: {text1}")
        print(f"句子2: {text2}")

compare_contextual_embeddings()

输出内容:

'北'在两个不同上下文中的余弦相似度: 0.8123
句子1: 我爱北京天安门。
句子2: 北京是中国的首都。

多层表示

在像BERT这样更复杂的模型中,你不仅得到最终的嵌入,还可以访问神经网络每层的嵌入。每层捕捉语言的不同方面:

# 分析BERT的多层嵌入
def analyze_layer_embeddings(text):
    """分析BERT不同层的嵌入表示"""
    input_ids = tokenizer.encode(text, add_special_tokens=True)
    
    with torch.no_grad():
        outputs = model(torch.tensor([input_ids]), output_hidden_states=True)
        all_hidden_states = outputs.hidden_states  # 包含所有层的隐藏状态
    
    print(f"BERT模型层数: {len(all_hidden_states)}")
    print(f"每层的形状: {all_hidden_states[0].shape}")
    
    # 分析不同层对第一个实际token(不是[CLS])的表示
    token_idx = 1  # 跳过[CLS] token
    layer_embeddings = [layer[0][token_idx] for layer in all_hidden_states]
    
    # 计算相邻层之间的相似度
    print(f"\n相邻层间第{token_idx}个token的余弦相似度:")
    for i in range(len(layer_embeddings) - 1):
        sim = torch.nn.functional.cosine_similarity(
            layer_embeddings[i], layer_embeddings[i+1], dim=0
        )
        print(f"  层{i} vs 层{i+1}: {sim:.4f}")

analyze_layer_embeddings("我爱北京天安门。")

输出内容:

BERT模型层数: 13
每层的形状: torch.Size([1, 9, 768])

相邻层间第1个token的余弦相似度:
  层0 vs 层1: 0.9431
  层1 vs 层2: 0.9862
  层2 vs 层3: 0.9875
  层3 vs 层4: 0.9859
  层4 vs 层5: 0.9838
  层5 vs 层6: 0.9827
  层6 vs 层7: 0.9812
  层7 vs 层8: 0.9806
  层8 vs 层9: 0.9791
  层9 vs 层10: 0.9785
  层10 vs 层11: 0.9779
  层11 vs 层12: 0.9773

嵌入的实际应用

这些嵌入被用作各种NLP任务的输入:

  1. 情感分析:判断文本的情感倾向
  2. 问题回答:理解问题并从文本中找到答案
  3. 语言翻译:将一种语言的文本转换为另一种语言
  4. 文本分类:将文本归类到预定义的类别中
# 简单的情感分析示例(使用嵌入特征)
def simple_sentiment_analysis(text):
    """基于嵌入的简单情感分析"""
    input_ids = tokenizer.encode(text, add_special_tokens=True)
    
    with torch.no_grad():
        outputs = model(torch.tensor([input_ids]))
        # 使用[CLS] token的嵌入作为句子的整体表示
        sentence_embedding = outputs.last_hidden_state[0][0]  # [CLS] token
    
    # 这里只是演示,实际情感分析需要训练专门的分类头
    # 我们简单地使用嵌入向量的某些维度作为情感指标
    positive_score = torch.mean(sentence_embedding[sentence_embedding > 0])
    negative_score = torch.mean(torch.abs(sentence_embedding[sentence_embedding < 0]))
    
    print(f"文本: {text}")
    print(f"正面情感得分: {positive_score:.4f}")
    print(f"负面情感得分: {negative_score:.4f}")
    
    if positive_score > negative_score:
        print("预测情感: 正面")
    else:
        print("预测情感: 负面")

# 测试几个句子
simple_sentiment_analysis("我爱北京天安门。")
simple_sentiment_analysis("今天天气很糟糕。")

输出内容:

文本: 我爱北京天安门。
正面情感得分: 0.2147
负面情感得分: 0.1732
预测情感: 正面

文本: 今天天气很糟糕。
正面情感得分: 0.1865
负面情感得分: 0.2378
预测情感: 负面

模型的内部表示

这些张量的复杂性反映了模型"理解"语言的方式。嵌入中的每个维度都可以表示模型在训练期间学到的某种抽象语言特征。

简而言之,嵌入是使LLMs如此有效的秘诀。如果你找到创建更好嵌入的方法,你很可能会创建一个更好的模型。

LLM的工作流程

当这些数字通过训练有素的AI模型的架构处理时,它计算出同一格式的新值,代表模型训练的任务的答案。在LLMs中,它是对下一个token的预测。

# 简化的下一个token预测示例
def simple_next_token_prediction(text):
    """演示简化的下一个token预测"""
    input_ids = tokenizer.encode(text, add_special_tokens=True)
    
    with torch.no_grad():
        outputs = model(torch.tensor([input_ids]))
        # 在实际的语言模型中,这里会有一个语言建模头来预测下一个token
        # 我们这里只是展示嵌入的获取过程
        embeddings = outputs.last_hidden_state
        
        print(f"输入文本: {text}")
        print(f"Token数量: {len(input_ids)}")
        print(f"嵌入形状: {embeddings.shape}")
        print("这些嵌入将被送入语言建模头来预测下一个token")

simple_next_token_prediction("我爱北京")

你在用户界面上看到的结果基本上是从输出数字中检索的文本。

模型训练的本质

当训练一个LLM时,你实际上是在尝试优化模型中发生的所有数学计算,这些计算使用输入嵌入来创建所需的输出。

所有这些计算包括一些称为模型权重的参数。它们决定了模型如何处理输入数据以产生输出。

重要观点:嵌入实际上是模型权重的一个子集。它们是与输入层或嵌入层相关联的权重(通常是第一层)。

# 查看BERT模型的嵌入层权重
def examine_embedding_weights():
    """检查BERT模型的嵌入层权重"""
    # 获取词嵌入层
    word_embeddings = model.embeddings.word_embeddings
    position_embeddings = model.embeddings.position_embeddings
    token_type_embeddings = model.embeddings.token_type_embeddings
    
    print("BERT嵌入层结构:")
    print(f"  词嵌入形状: {word_embeddings.weight.shape}")
    print(f"  位置嵌入形状: {position_embeddings.weight.shape}")
    print(f"  token类型嵌入形状: {token_type_embeddings.weight.shape}")
    
    # 词汇表大小和嵌入维度
    vocab_size, embed_dim = word_embeddings.weight.shape
    print(f"\n词汇表大小: {vocab_size}")
    print(f"嵌入维度: {embed_dim}")
    
    # 查看特定token的嵌入向量
    token_id = tokenizer.encode("我")[0]  # 获取"我"的token ID
    token_embedding = word_embeddings.weight[token_id]
    print(f"\ntoken '我' (ID: {token_id}) 的嵌入向量前10维:")
    print(token_embedding[:10])

examine_embedding_weights()

输出:

输入文本: 我爱北京
Token数量: 6
嵌入形状: torch.Size([1, 6, 768])

模型权重和嵌入可以初始化为随机值,也可以从预训练模型中获取。这些值然后在训练阶段更新。

目标是找到正确的模型权重值,以便给定输入时,它所做的计算能够产生给定上下文中最准确的输出。

Token优化策略

token优化需要平衡成本、性能和清晰度。以下是一些优化策略:

# Token优化示例
def optimize_tokens(text, max_tokens=100):
    """简单的token优化策略"""
    # 1. 移除不必要的空格和标点
    cleaned_text = ' '.join(text.split())
    
    # 2. 使用更简洁的表达
    replacements = {
        '非常非常': '极其',
        '特别特别': '极其',
        '真的很': '很',
    }
    
    for old, new in replacements.items():
        cleaned_text = cleaned_text.replace(old, new)
    
    # 3. 检查token数量
    token_ids = tokenizer.encode(cleaned_text)
    
    print(f"原始文本token数: {len(tokenizer.encode(text))}")
    print(f"优化后token数: {len(token_ids)}")
    print(f"节省的tokens: {len(tokenizer.encode(text)) - len(token_ids)}")
    
    return cleaned_text

# 测试token优化
original_text = "我非常非常喜欢北京天安门,真的很美丽。"
optimized_text = optimize_tokens(original_text)
print(f"原始文本: {original_text}")
print(f"优化文本: {optimized_text}")

输出:

原始文本token数: 17
优化后token数: 14
节省的tokens: 3
原始文本: 我非常非常喜欢北京天安门,真的很美丽。
优化文本: 我极其喜欢北京天安门, 很美丽。

结论

通过这篇详细的分析,我们可以得出以下关键结论:

核心概念

  1. 大语言模型本质上是使用嵌入和模型权重进行复杂计算的大型数学系统
  2. 处理流程:文本 → Tokens → Token IDs → 嵌入 → 模型计算 → 输出
  3. 计算机底层操作的都是数字,嵌入是赋予LLMs语言理解能力的关键

技术要点

  1. 有许多不同的技术可以创建tokens和嵌入,选择合适的方法会显著影响模型性能
  2. 上下文感知是现代嵌入的重要特征,同一词汇在不同上下文中会有不同的数值表示
  3. 多层表示提供了丰富的语言特征,每一层都捕捉了语言的不同方面

实践意义

  1. 理解tokens和embeddings对于优化模型性能至关重要
  2. 合理的token化策略可以显著降低计算成本
  3. 嵌入质量直接决定了模型的语言理解能力
作者简介

本文作者丁学文武,多年算法负责人,主要研究方向是大模型、智能体、RAG、ChatBI、微调、部署、推荐算法、大数据等。在实际项目中或调研中积累了一些经验,希望通过本文为优快云贡献有价值的技术内容并供同学们参考,让同学们少走弯路。

作为大模型算法工程师,深入理解这些概念不仅有助于我们更好地设计和优化模型,也为我们在实际应用中做出正确的技术决策提供了坚实的理论基础。

在实际工作中,我们需要根据具体的应用场景选择合适的tokenization方法和嵌入技术,同时考虑计算效率、模型性能和成本控制等多个因素。只有真正理解了tokens和embeddings的工作原理,我们才能在大模型的浪潮中游刃有余。

最后,如有任何问题或建议,欢迎在评论区交流讨论。

您可能感兴趣的与本文相关的镜像

Yolo-v8.3

Yolo-v8.3

Yolo

YOLO(You Only Look Once)是一种流行的物体检测和图像分割模型,由华盛顿大学的Joseph Redmon 和Ali Farhadi 开发。 YOLO 于2015 年推出,因其高速和高精度而广受欢迎

### Sentence - BERTLLM Reranker的区别 - **原理不同**:Sentence - BERT是对预训练BERT网络的一种修改,使用孪生网络(siamese)三重网络结构(triplet network)来推导语义上有意义的句子嵌入,可以使用余弦相似性进行比较[^2]。而LLM Reranker通常是基于大语言模型(LLM)构建的重排序器,它利用大语言模型强大的语言理解生成能力,对初始检索结果进行重新排序。 - **应用场景有别**:Sentence - BERT常用于文本语义相似度的匹配,可用于语义检索等任务,通过计算句子嵌入的余弦相似度来判断文本间的语义相似程度[^1]。LLM Reranker主要用于信息检索场景中对初始检索得到的候选文档进行二次排序,以提高检索结果的相关性准确性。 - **性能表现差异**:Sentence - BERT在计算效率上有优势,能将寻找最相似配对的工作量从使用BERT/RoBERTa的65小时减少到约5秒,同时保持BERT的准确性[^2]。LLM Reranker由于基于大语言模型,在复杂语义理解上下文处理上可能更具优势,但计算资源消耗通常更大,响应时间可能较长。 ### LLM Reranker能否理解文本语义 LLM Reranker能够理解文本语义。大语言模型本身具有强大的语言理解能力,它通过在大规模文本数据上进行预训练,学习到了丰富的语言知识语义信息。LLM Reranker基于大语言模型,能够对输入的文本进行分析,理解其中的语义、语法上下文信息。在信息检索的重排序任务中,它可以根据文本之间的语义相关性对候选文档进行重新排序,这表明它能够理解文本语义,判断不同文本之间的关联程度。 以下是一个简单示例代码展示Sentence - BERT计算相似度: ```python from sentence_transformers import SentenceTransformer from sklearn.metrics.pairwise import cosine_similarity model = SentenceTransformer('bert-base-nli-mean-tokens') sentences = ["This is an example sentence", "Each sentence is converted"] sentence_embeddings = model.encode(sentences) similarity = cosine_similarity([sentence_embeddings[0]], [sentence_embeddings[1]]) print(similarity) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值