第三阶段:语言的基石:分词与嵌入 (Tokenization & Embedding)

“Words are, in my not-so-humble opinion, our most inexhaustible source of magic.” — Albus Dumbledore

揭开 LLM 的第一个黑盒:理解机器如何将人类的语言转化为数学的语言。


目录


一、分词:机器阅读的第一步

在模型眼中,“我爱你” 不是情感的表达,而是一串数字。将文本转换为这串数字的过程,就是分词 (Tokenization)

1. 为什么要分词?

你可能会问:为什么不直接用字符(Character)或者单词(Word)作为最小单位?

❌ 方案 A:按字符切分 (Character-level)
  • 做法"apple"['a', 'p', 'p', 'l', 'e']
  • 优点:词表极小(26个字母+符号),不会有未知词(OOV)。
  • 缺点:序列太长。一句话变成几百个字符,模型注意力机制的计算量是序列长度的平方 ( O ( N 2 ) O(N^2) O(N2)),成本太高。而且单个字符缺乏语义。
❌ 方案 B:按单词切分 (Word-level)
  • 做法"I love apples"['I', 'love', 'apples']
  • 优点:语义完整,序列短。
  • 缺点词表爆炸。英语有几十万词,而且还要处理变形(run, running, ran)和新词(Covid-19, ChatGPT)。如果遇到词表中没有的词,只能由 <UNK> 代替,丢失信息。
✅ 方案 C:子词切分 (Subword-level) —— 现代 LLM 的选择
  • 核心思想常用词保持完整,生僻词拆解为字根
  • 例子
    • apple (常用) → ['apple']
    • unbelievable (较长) → ['un', 'believ', 'able']
  • 优势
    • 平衡性:词表大小适中(通常 30k-150k)。
    • 处理未知词:任何新词都可以拆成见过的子词。
    • 多语言能力:不同语言共享子词结构。

2. 主流分词算法详解

目前主流的大模型主要使用以下三种算法的变体:

(1) BPE (Byte Pair Encoding)

代表模型:GPT-2, GPT-3, GPT-4, Llama, Qwen, DeepSeek
原理:统计语料中相邻字符对的频率,不断合并最高频的对。

算法详细演示
假设我们有一个简单的语料库,包含以下单词和它们的频率:

hug: 10次
pug: 5次
pun: 12次
bun: 4次
hugs: 5次

步骤 1:初始化,将每个单词拆成字符

h u g: 10
p u g: 5
p u n: 12
b u n: 4
h u g s: 5

步骤 2:统计所有相邻字符对的频率

(h, u): 15次  [出现在 hug(10) + hugs(5)]
(u, g): 20次  [出现在 hug(10) + pug(5) + hugs(5)]
(p, u): 17次  [出现在 pug(5) + pun(12)]
(u, n): 16次  [出现在 pun(12) + bun(4)]
(b, u): 4次
(g, s): 5次

步骤 3:选择频率最高的对 (u, g) 合并为 ug

h ug: 10
p ug: 5
p u n: 12
b u n: 4
h ug s: 5

词表更新:[h, u, g, p, n, b, s, ug]

步骤 4:继续统计并合并,例如下一步可能合并 (h, ug)hug

hug: 10
p ug: 5
p u n: 12
b u n: 4
hug s: 5

通过这样反复迭代,最终得到一个平衡的词表。

💡 直觉理解:BPE 是一种数据压缩算法。它试图用最少的 Token 覆盖最多的文本内容。

(2) WordPiece

代表模型:BERT, DistilBERT
原理:与 BPE 类似,但合并依据不是单纯的"频率",而是似然值 (Likelihood) 的提升。

具体来说,WordPiece 在选择合并哪个字符对时,会计算:
score = freq ( x , y ) freq ( x ) × freq ( y ) \text{score} = \frac{\text{freq}(x, y)}{\text{freq}(x) \times \text{freq}(y)} score=freq(x)×freq(y)freq(x,y)

选择 score 最高的对进行合并。这样可以避免仅仅因为高频而合并不相关的字符。

实际效果:在处理生僻词和形态变化时比纯频率方法更合理。

(3) SentencePiece (Unigram 算法)

代表模型:T5, ALBERT, XLNet, Llama (部分版本)
原理:与 BPE/WordPiece 相反,它采用自顶向下的策略。

核心思想

  1. 从一个非常大的候选词表开始(包含所有可能的子词)
  2. 使用 Unigram 语言模型计算每个 Token 的重要性
  3. 逐步删除贡献最小的 Token,直到达到目标词表大小

优势

  • 语言无关:不依赖空格分词,可以直接处理中文、日文等语言
  • 可逆性:可以完美还原原始文本(包括空格)
  • 灵活性:支持多种采样策略

💡 为什么叫 SentencePiece?
因为它直接处理原始句子,不需要预分词。这对于没有明确词界的语言(如中文)特别重要。


3. OOV 问题与子词的优势

什么是 OOV (Out-of-Vocabulary) 问题?

传统词级分词的困境
假设我们训练了一个词表,包含 50,000 个常见单词。但用户输入了一个新词:

"I love ChatGPT and DeepSeek-V3!"

如果词表中没有 “ChatGPT” 和 “DeepSeek-V3”,传统方法只能将它们替换为 <UNK> (Unknown Token):

"I love <UNK> and <UNK>!"

后果

  • 信息完全丢失
  • 模型无法理解这些新词
  • 每次有新词出现都需要重新训练
子词分词如何解决 OOV?

核心思想任何词都可以拆解为已知的子词组合

示例对比

输入 词级分词 BPE 子词分词
ChatGPT <UNK> ['Chat', 'GPT']
DeepSeek-V3 <UNK> ['Deep', 'Seek', '-', 'V', '3']
unbelievable <UNK> (如果未见过) ['un', 'believ', 'able']
超级计算机 <UNK> (中文更严重) ['超级', '计算', '机']

优势

  1. 零 OOV:理论上任何词都可以拆解到字符级别,不会出现 <UNK>
  2. 语义保留:子词仍然保留部分语义(如 “Chat” 和 “GPT” 都有意义)
  3. 词表可控:不需要存储所有可能的词,只需要合理数量的子词

实际例子

import tiktoken

enc = tiktoken.encoding_for_model("gpt-4")

# 常见词:保持完整
print(enc.encode("apple"))  # [23182]  -> 1 个 Token

# 新造词:拆解为子词
print(enc.encode("ChatGPT"))  # [14326, 38, 2898]  -> 3 个 Tokens
# 对应:['Chat', 'G', 'PT']

# 超长词:自动拆解
print(enc.encode("antidisestablishmentarianism"))
# [519, 85342, 34500, 479, 8997, 2191]  -> 6 个 Tokens
子词粒度的权衡
粒度 Token 数量 语义完整性 适用场景
粗粒度(大子词) 少(序列短) 高(常用词完整) 通用文本、对话
细粒度(小子词) 多(序列长) 低(频繁拆解) 代码、专业术语
字符级 最多 最低 极端情况(密码、Base64)

现代模型的选择

  • GPT-4:50,000+ 词表,中等粒度
  • Llama 3:128,000+ 词表,较粗粒度(更高效)
  • DeepSeek-V3:100,000+ 词表,优化中文(中文粒度更粗)

4. 2025年分词技术新趋势

随着 DeepSeek-V3、Claude 3.5/4.5 等模型的发布,分词技术出现了一些明显的新趋势:

特性 早期模型 (GPT-2/BERT) 现代模型 (Llama 3 / DeepSeek-V3 / Qwen 2.5) 优势
词表大小 ~30k - 50k 100k - 150k 更大的词表意味着更多词被作为完整 Token,压缩率更高,推理更省钱。
数字处理 经常拆碎 (1998 -> 1, 9, 9, 8) 独立数字 Token 增强数学和代码处理能力。
空格处理 包含在词前 (Ġlove) 更灵活的处理 提高代码缩进的准确性。
多语言 英语为主 原生多语言 中文不再是"生僻词","你好" 是 1 个 Token 而非 2-3 个。

⚠️ 为什么 GPT-4 数不清 “Strawberry” 里的 ‘r’?
这是分词带来的典型副作用。在 GPT-4 眼中,Strawberry 是一个完整的 Token(ID: 9699),它并不"看"到里面的字母。除非你强制它按字符处理(如:“Spell the word strawberry”)。


二、嵌入:赋予词语数学灵魂

分词将文本变成了 ID 列表(如 [12, 334, 98]),但这只是索引。嵌入 (Embedding) 才是将这些索引转化为具有数学意义的向量的关键步骤。

1. 从 Token ID 到高维向量

阶段 1:Token ID(离散表示)

分词后,每个 Token 被赋予一个唯一的整数 ID:

"I love AI"[40, 3021, 15592]

这些数字本身没有任何数学意义,4041 之间的"距离"不代表语义的相近。

阶段 2:One-Hot 编码(稀疏向量)

理论上的中间步骤(实际工程中会跳过):

# 假设词表大小 V = 50000
Token "love" (ID=3021) → One-Hot 向量:
[0, 0, 0, ..., 1, ..., 0]  # 只有第 3021 位是 1,其余都是 0
     ↑
  第 3021

问题

  • 极度稀疏:50000 维的向量中只有 1 个非零值
  • 没有语义信息:“love” 和 “hate” 的向量完全正交,距离相等
  • 存储和计算浪费:矩阵乘法效率极低
阶段 3:Dense Embedding(稠密向量)

定义:嵌入是一个将离散的 Token ID 映射到连续低维向量空间的查找表 (Lookup Table)

Embedding ( x ) = W E [ x ] \text{Embedding}(x) = W_E[x] Embedding(x)=WE[x]

其中 W E ∈ R V × d W_E \in \mathbb{R}^{V \times d} WERV×d 是一个可学习的矩阵:

  • V V V:词表大小(如 150,000)
  • d d d:嵌入维度(如 4096,远小于 V)

具体过程

# 伪代码
vocab_size = 150000
embed_dim = 4096

# 初始化嵌入矩阵(训练前是随机值,训练后包含语义)
embedding_matrix = torch.randn(vocab_size, embed_dim)

# 查表操作(本质是矩阵索引)
token_id = 3021  # "love"
embedding_vector = embedding_matrix[token_id]  # 形状: [4096]

直觉
想象一个 4096 维的语义空间。

  • “猫” 的向量指向 [0.1, 0.9, -0.3, …, 0.5]
  • “狗” 的向量指向 [0.2, 0.8, -0.2, …, 0.4](接近"猫")
  • “桌子” 的向量指向 [-0.5, 0.1, 0.9, …, -0.8](远离"猫"和"狗")

模型通过训练,自动调整这些向量的位置,使得语义相近的词在空间中距离更近。

优势对比

特性 One-Hot Dense Embedding
维度 = 词表大小 (50k-150k) 可控 (通常 768-4096)
稀疏性 极度稀疏 (只有 1 个非零值) 稠密 (所有维度都有值)
语义信息 无(所有词等距) 有(相似词距离近)
存储效率

2. 嵌入空间的几何奥秘

嵌入空间最迷人的特性是其线性关系。经典的例子:

K i n g ⃗ − M a n ⃗ + W o m a n ⃗ ≈ Q u e e n ⃗ \vec{King} - \vec{Man} + \vec{Woman} \approx \vec{Queen} King Man +Woman Queen

这意味着模型不仅记住了词的含义,还理解了词与词之间的关系(如性别、首都-国家、过去式-现在式)。

3. 静态嵌入 vs 上下文嵌入

这是理解现代 LLM 的一个关键区别:

静态嵌入 (Static Embedding)

代表技术:Word2Vec, GloVe(2013-2014 年的技术)

特点:每个词有且只有一个固定的向量

问题:多义词无法区分

"Apple flavor is sweet."        # 苹果(水果)
"Apple Inc. stock rises."       # 苹果(公司)

在静态嵌入中,两个 “Apple” 的向量完全相同,无法表达不同的语义。

上下文嵌入 (Contextual Embedding)

代表技术:Transformer 模型(BERT, GPT 等)

特点:同一个词在不同上下文中会有不同的向量表示

工作流程(简化):

  1. 输入层Apple 先查表得到初始嵌入(此时还是静态的)
  2. Transformer 层:模型读取上下文,动态调整向量
  3. 输出层Apple 在 “Apple flavor” 和 “Apple Inc.” 中的最终向量已经完全不同

直觉理解

  • 静态嵌入像是"字典定义":一个词一个解释。
  • 上下文嵌入像是"根据场景理解":同一个词在不同句子里意思不同。

💡 本章重点:我们主要关注输入层的静态 Embedding(即查找表)。至于 Transformer 如何利用注意力机制生成上下文嵌入,详见 [Part 2 第1章]。


三、代码实战:从零构建与使用

1. 实战:从零训练 BPE 分词器

在使用现成的分词器之前,让我们先理解如何从零训练一个 BPE 分词器。

"""
功能:从零实现 BPE 分词器训练(教学版)
依赖:pip install tokenizers
"""
from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import Whitespace

# 1. 准备训练语料(实际应用中应该是大规模文本文件)
corpus = [
    "This is the Hugging Face Course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]

# 保存到文件(BPE 训练器需要文件路径)
with open("corpus.txt", "w") as f:
    for line in corpus:
        f.write(line + "\n")

# 2. 初始化 BPE 分词器
tokenizer = Tokenizer(BPE(unk_token="<UNK>"))

# 3. 设置预分词器(按空格切分)
tokenizer.pre_tokenizer = Whitespace()

# 4. 配置训练器
trainer = BpeTrainer(
    vocab_size=300,  # 目标词表大小
    special_tokens=["<PAD>", "<UNK>", "<BOS>", "<EOS>"],  # 特殊 Token
    min_frequency=2  # 只保留出现至少 2 次的子词
)

# 5. 训练分词器
tokenizer.train(files=["corpus.txt"], trainer=trainer)

# 6. 测试分词效果
test_sentence = "This tokenization algorithm is awesome!"
output = tokenizer.encode(test_sentence)

print(f"原始文本: {test_sentence}")
print(f"Token IDs: {output.ids}")
print(f"Tokens: {output.tokens}")
print(f"Token 数量: {len(output.tokens)}")

# 7. 保存分词器
tokenizer.save("my_bpe_tokenizer.json")
print("\n分词器已保存到 my_bpe_tokenizer.json")

# 8. 加载并使用
loaded_tokenizer = Tokenizer.from_file("my_bpe_tokenizer.json")
print("\n重新加载后的分词结果:")
print(loaded_tokenizer.encode(test_sentence).tokens)

输出示例

原始文本: This tokenization algorithm is awesome!
Token IDs: [51, 98, 156, 203, 45, 187, 12]
Tokens: ['This', 'token', 'ization', 'algorithm', 'is', 'awesome', '!']
Token 数量: 7

分词器已保存到 my_bpe_tokenizer.json

重新加载后的分词结果:
['This', 'token', 'ization', 'algorithm', 'is', 'awesome', '!']

💡 观察:训练后的分词器学会了将 “tokenization” 拆分为 “token” + “ization”,这是 BPE 从语料中学到的高频子词组合。

2. 实战:使用 TikToken 高效分词

tiktoken 是 OpenAI 开源的高性能 BPE 分词库,比 HuggingFace 的 tokenizer 快 3-6 倍。

"""
功能:演示使用 tiktoken 进行分词、ID转换及 Token 计费估算
依赖:pip install tiktoken
"""
import tiktoken

def analyze_tokens(text: str, model: str = "gpt-4o"):
    print(f"--- Model: {model} ---")

    # 1. 获取对应的编码器
    try:
        encoding = tiktoken.encoding_for_model(model)
    except KeyError:
        print(f"这 Warning: Model {model} using default cl100k_base")
        encoding = tiktoken.get_encoding("cl100k_base")

    # 2. 编码 (Text -> Token IDs)
    token_ids = encoding.encode(text)
    print(f"Text: {text}")
    print(f"Token IDs: {token_ids}")
    print(f"Count: {len(token_ids)}")

    # 3. 解码 (Token IDs -> Text)
    # 这一步可以让你看到每个 Token 到底对应什么文本
    print("Token Breakdown:")
    for tid in token_ids:
        token_bytes = encoding.decode_single_token_bytes(tid)
        try:
            print(f"  {tid}: {token_bytes.decode('utf-8')}")
        except:
            print(f"  {tid}: {token_bytes}")

# 测试案例
text_sample = "Hello, world! 我爱大模型 2025."
analyze_tokens(text_sample, "gpt-4o")

2. 实战:可视化嵌入空间

让我们看看这些高维向量在二维平面上是什么样子的。

"""
功能:使用 t-SNE/UMAP 可视化词嵌入
依赖:pip install numpy matplotlib scikit-learn openai
策略:使用 OpenAI 的 Embedding API 获取向量(也可以用本地模型)
"""
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
from openai import OpenAI

# 1. 准备测试词汇(选择有语义关系的词)
words = [
    "king", "queen", "prince", "princess",  # 王室
    "man", "woman", "boy", "girl",          # 性别
    "cat", "dog", "puppy", "kitten",        # 动物
    "car", "bus", "train", "plane"          # 交通工具
]

# 2. 获取嵌入向量(使用 OpenAI API)
client = OpenAI()  # 需要设置 OPENAI_API_KEY 环境变量

def get_embeddings(texts, model="text-embedding-3-small"):
    """批量获取文本的嵌入向量"""
    response = client.embeddings.create(
        input=texts,
        model=model
    )
    return np.array([data.embedding for data in response.data])

embeddings = get_embeddings(words)
print(f"Embedding shape: {embeddings.shape}")  # 应该是 (16, 1536)

# 3. 使用 t-SNE 降维到 2D
tsne = TSNE(n_components=2, perplexity=5, random_state=42, n_iter=1000)
vis_data = tsne.fit_transform(embeddings)

# 4. 可视化
plt.figure(figsize=(12, 10))
colors = ['red']*4 + ['blue']*4 + ['green']*4 + ['purple']*4  # 按类别着色
plt.scatter(vis_data[:, 0], vis_data[:, 1], c=colors, s=100, alpha=0.6)

for i, word in enumerate(words):
    plt.annotate(word, xy=(vis_data[i, 0], vis_data[i, 1]),
                 xytext=(5, 2), textcoords='offset points',
                 ha='right', va='bottom', fontsize=12)

plt.title("Word Embeddings Visualization (t-SNE)", fontsize=16)
plt.xlabel("Dimension 1")
plt.ylabel("Dimension 2")
plt.grid(True, alpha=0.3)
plt.tight_layout()
# plt.savefig("embedding_visualization.png", dpi=150)
# plt.show()
print("可视化完成!观察:同类词应该聚集在一起。")

如果你想使用本地模型(不依赖 API):

"""
使用 Hugging Face 的轻量级嵌入模型(无需 BERT 架构知识)
"""
from sentence_transformers import SentenceTransformer
import numpy as np

# 加载预训练的嵌入模型(这是一个封装好的工具,不需要理解内部)
model = SentenceTransformer('all-MiniLM-L6-v2')

words = ["king", "queen", "man", "woman", "cat", "dog", "car", "bus"]

# 一行代码获取嵌入
embeddings = model.encode(words)

print(f"Shape: {embeddings.shape}")  # (8, 384)
# 后续的降维和可视化代码与上面相同

使用 UMAP(通常比 t-SNE 更快更好)

# pip install umap-learn
from umap import UMAP

reducer = UMAP(n_components=2, random_state=42)
vis_data = reducer.fit_transform(embeddings)
# 绘图代码同上

3. 实战:语义相似度计算与搜索

这是 Embedding 最经典的工业级应用,也是 RAG (检索增强生成) 的核心基础。

方法 1:使用 OpenAI Embeddings API(最简单)
"""
功能:基于余弦相似度的语义搜索
依赖:pip install openai numpy
"""
import numpy as np
from openai import OpenAI

client = OpenAI()

def cosine_similarity(vec1, vec2):
    """计算两个向量的余弦相似度"""
    dot_product = np.dot(vec1, vec2)
    norm1 = np.linalg.norm(vec1)
    norm2 = np.linalg.norm(vec2)
    return dot_product / (norm1 * norm2)

# 1. 准备数据
corpus = [
    "A man is eating food.",
    "A man is eating a piece of bread.",
    "The girl is carrying a baby.",
    "A man is riding a horse.",
    "A woman is playing violin."
]
query = "A man is eating pasta."

# 2. 获取嵌入向量
def get_embedding(text, model="text-embedding-3-small"):
    response = client.embeddings.create(input=[text], model=model)
    return np.array(response.data[0].embedding)

corpus_embeddings = [get_embedding(text) for text in corpus]
query_embedding = get_embedding(query)

# 3. 计算相似度并排序
results = []
for i, doc_emb in enumerate(corpus_embeddings):
    score = cosine_similarity(query_embedding, doc_emb)
    results.append((score, corpus[i]))

results.sort(key=lambda x: x[0], reverse=True)

# 4. 输出结果
print(f"Query: {query}\n")
for score, text in results:
    print(f"Score: {score:.4f} | {text}")

输出示例

Query: A man is eating pasta.

Score: 0.8823 | A man is eating a piece of bread.
Score: 0.8654 | A man is eating food.
Score: 0.5421 | A man is riding a horse.
Score: 0.4102 | The girl is carrying a baby.
Score: 0.3876 | A woman is playing violin.

💡 观察:模型成功识别出"eating pasta"与"eating bread/food"语义最接近,即使用词不完全相同!

方法 2:使用本地模型(适合批量处理)
"""
使用 sentence-transformers 库(无需了解内部架构)
"""
from sentence_transformers import SentenceTransformer, util

# 1. 加载模型(这个库封装了所有复杂度)
model = SentenceTransformer('all-MiniLM-L6-v2')

corpus = [
    "A man is eating food.",
    "A man is eating a piece of bread.",
    "The girl is carrying a baby.",
    "A man is riding a horse.",
    "A woman is playing violin."
]
query = "A man is eating pasta."

# 2. 编码(一行搞定)
corpus_embeddings = model.encode(corpus, convert_to_tensor=True)
query_embedding = model.encode(query, convert_to_tensor=True)

# 3. 计算相似度(内置函数)
scores = util.cos_sim(query_embedding, corpus_embeddings)[0]

# 4. 排序并输出
results = [(scores[i].item(), corpus[i]) for i in range(len(corpus))]
results.sort(key=lambda x: x[0], reverse=True)

print(f"Query: {query}\n")
for score, text in results:
    print(f"Score: {score:.4f} | {text}")
核心知识点:余弦相似度

余弦相似度衡量两个向量的方向是否一致(而非距离):

cosine_similarity ( A ⃗ , B ⃗ ) = A ⃗ ⋅ B ⃗ ∣ ∣ A ⃗ ∣ ∣ × ∣ ∣ B ⃗ ∣ ∣ = cos ⁡ ( θ ) \text{cosine\_similarity}(\vec{A}, \vec{B}) = \frac{\vec{A} \cdot \vec{B}}{||\vec{A}|| \times ||\vec{B}||} = \cos(\theta) cosine_similarity(A ,B )=∣∣A ∣∣×∣∣B ∣∣A B =cos(θ)

  • 取值范围:[-1, 1]
    • 1:方向完全相同(语义完全一致)
    • 0:正交(无关)
    • -1:方向相反(语义相反,如反义词)

为什么不用欧氏距离?

  • 欧氏距离受向量长度影响,而余弦相似度只关心方向。
  • 在高维空间中,余弦相似度更稳定。

🔗 延伸阅读:关于如何将这个简单的搜索引擎扩展到百万级文档,详见 [Part 4 第2章:RAG 系统设计]。


四、工程最佳实践

1. 如何选择分词器?

  • 通用文本:直接使用模型对应的分词器(AutoTokenizer.from_pretrained(...))。
  • 代码场景:确保分词器对空格和缩进敏感(现代 LLM 分词器通常已优化)。
  • 中文场景:优先选择针对中文优化的模型(如 Qwen, DeepSeek, Yi),它们的中文压缩率远高于 Llama 原生版。

💡 成本提示:在 API 计费时,DeepSeek/Qwen 的 Tokenizer 通常比 OpenAI 的 tokenizer 在中文上节省 30%-50% 的 Token 用量。

2. 如何处理"超长文本"?

当文本超过 max_tokens(如 8192 或 128k)时:

  • 截断 (Truncation):丢弃多余部分(通常是不重要的开头或结尾)。
  • 滑动窗口 (Sliding Window):将长文本切分成重叠的块,分别处理。
  • RAG:先将长文本分块存入向量库,只检索相关的部分(详见 Part 4 第2章)。

3. 嵌入的选择

  • OpenAI text-embedding-3-small: 性价比极高,适合大多数通用场景。
  • GTE-Qwen / BGE-M3: 目前(2025)中文和多语言检索效果最好的开源嵌入模型之一。
  • Matryoshka Embeddings (俄罗斯套娃嵌入): 新技术,允许你灵活截断嵌入向量的前 k k k 维使用(例如只用前 256 维),在精度损失极小的情况下大幅减少存储空间。

五、本章小结

核心要点回顾

  1. 分词是基础

    • LLM 不读字,读 Token
    • 现代 LLM 普遍使用 BPE/SentencePiece 算法
    • 词表大小趋向于 100k-150k 以提升多语言效率
    • 子词切分完美解决了 OOV 问题
  2. 嵌入是桥梁

    • Token ID → One-Hot → Dense Embedding 的三阶段转换
    • Embedding 层本质是一个可学习的查找表
    • 将离散符号映射到连续的高维语义空间
  3. 几何即语义

    • 向量空间中的距离代表语义相似度
    • 余弦相似度是衡量语义距离的核心工具
    • 这是 RAG 和语义搜索的数学基础
  4. 静态到动态

    • 本章关注的是输入层的静态 Embedding(查找表)
    • Transformer 将静态嵌入转化为上下文相关的动态表示
    • 这部分的原理将在下一部分详细展开

实战技能清单

✅ 能够使用 tiktoken 进行高效分词和 Token 计数
✅ 理解 BPE/WordPiece/SentencePiece 三种算法的区别
✅ 能够获取和可视化嵌入向量(t-SNE/UMAP)
✅ 能够实现基于余弦相似度的语义搜索
✅ 理解 One-Hot 到 Dense Embedding 的转换过程

延伸阅读

  • Transformer 如何处理嵌入? → 详见 [Part 2 第1章:Transformer 核心揭秘]
  • 如何训练更好的嵌入模型? → 详见 [Part 3 第4章:创建更优的嵌入模型]
  • 如何构建生产级 RAG 系统? → 详见 [Part 4 第2章:检索增强生成]

下一站:[Part 2 第1章:Transformer 核心揭秘]

我们即将进入 LLM 的心脏区域,看看这些嵌入向量是如何在 Self-Attention 的编织中涌现出智能的。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐