第一章:Embedding 基础原理
1.1 什么是 Embedding
Embedding(嵌入)是一种将离散、高维的符号数据映射到连续、低维向量空间的技术。在自然语言处理领域,Embedding 将文字、句子或文档转换为数值向量,使得语义相近的内容在向量空间中拥有相似的位置关系。
根据粒度的不同,Embedding 主要分为三类:
词向量(Word Embedding) 是最基础的嵌入形式,将每个单词映射为一个固定维度的向量。典型的词向量维度为 300 维,可以捕获单词之间的语义关系,例如"king"和"queen"在向量空间中的距离应该接近,"apple"和"orange"的距离应该小于"apple"和"car"。
句向量(Sentence Embedding) 将整个句子编码为一个向量表示。与词向量相比,句向量需要考虑词的顺序和上下文关系,能够更好地表达句子的完整语义。例如,"The cat sat on the mat"和"A feline rested upon the rug"虽然用词不同,但表达的语义相近,句向量应该相似。
文档向量(Document Embedding) 则进一步扩展到段落或文档级别,用于对长文本进行向量化表示。这在搜索引擎、文档分类等场景中尤为重要。
1.2 从 One-Hot 到 Dense Vector 的演进历程
早期的文本表示采用 One-Hot 编码(独热编码),每个词对应一个长度为词表大小的二进制向量,只有当前位置为 1,其余为 0。这种方法简单直接,但存在严重的维度灾难问题------当词表达到数十万时,向量维度将变得非常高,而且所有词向量之间两两正交,无法表达任何语义关系。
为了解决 One-Hot 的局限性,研究者们提出了**分布式表示(Distributed Representation)**的概念。其核心思想是:一个词的含义由其上下文决定,语义相似的词应该有相似的上下文分布。这一假设奠定了现代词向量技术的基础。
词袋模型(Bag of Words, BoW) 和 TF-IDF 是早期的改进方案,它们虽然仍是稀疏表示,但开始考虑词频信息。BoW 将文本表示为词频向量,TF-IDF 则进一步引入逆文档频率来调整词的重要性。然而,这些方法仍然无法捕获词的语义信息。
Word2Vec 的出现标志着词向量技术的重要突破。它通过神经网络将词映射到低维稠密向量空间,使得语义相似的词在空间中距离相近。Word2Vec 首次展示了从大规模语料中无监督学习词向量的可能性,为后续研究奠定了基础。
1.3 Embedding 空间的数学表示与几何意义
从数学角度来看,Embedding 空间是一个多维向量空间,通常维度在 100 到 1024 之间。每个词、句子或文档都被表示为该空间中的一个点向量的形式,而语义关系则通过向量之间的几何关系来表达。
Embedding 空间具有以下重要的几何性质:
距离意义:在良好的 Embedding 空间中,语义相似的对象对应点之间的距离较小。这一性质是语义搜索和聚类分析的理论基础。
向量运算:Word2Vec 著名的"king - man + woman ≈ queen"例子展示了词向量的线性运算性质。这种线性结构使得我们可以通过向量运算来发现词与词之间的语义关系。
归一化处理:在实际应用中,通常对向量进行 L2 归一化,使其位于单位球面上。这种处理有两个优点:第一,余弦相似度计算简化为向量点积;第二,便于进行相似度比较。
向量归一化的数学公式为:
v ′ = v ∣ ∣ v ∣ ∣ 2 = v ∑ i = 1 d v i 2 \mathbf{v}' = \frac{\mathbf{v}}{||\mathbf{v}||2} = \frac{\mathbf{v}}{\sqrt{\sum{i=1}^{d} v_i^2}} v′=∣∣v∣∣2v=∑i=1dvi2 v
1.4 主流 Embedding 模型架构
Word2Vec
Word2Vec 是 Google 于 2013 年发布的词嵌入模型,包含两种主要架构:
CBOW(Continuous Bag-of-Words) 根据上下文单词预测当前单词。模型接收上下文窗口内的词向量,通过隐藏层进行线性变换,输出层使用 softmax 预测目标词的概率分布。
Skip-gram 则相反,根据当前单词预测上下文单词。对于每个中心词,模型尝试预测窗口内的每个上下文词。Skip-gram 在处理低频词时表现更好,训练效率也较高。
Word2Vec 的关键参数包括向量维度(通常 100-300)、窗口大小(默认 5)、训练算法(Hierarchical Softmax 或 Negative Sampling)以及最小词频阈值。
GloVe
GloVe(Global Vectors for Word Representation)由斯坦福大学于 2014 年提出,创新性地结合了全局矩阵分解和局部上下文窗口的优点。
GloVe 首先构建词的共现矩阵,统计词对在语料中的共现频率,然后通过加权最小二乘优化学习词向量。其目标函数为:
J = ∑ i , j = 1 V f ( X i j ) ( w i T w ~ j + b i + b ~ j − log X i j ) 2 J = \sum_{i,j=1}^{V} f(X_{ij})(w_i^T \tilde{w}_j + b_i + \tilde{b}j - \log X{ij})^2 J=i,j=1∑Vf(Xij)(wiTw~j+bi+b~j−logXij)2
其中 f ( x ) f(x) f(x) 是权重函数,用于平衡高频和低频共现对。GloVe 的优势在于利用了全局统计信息,训练速度较快,且在词类比任务上表现优异。
BERT
BERT(Bidirectional Encoder Representations from Transformers)基于 Transformer 编码器架构,于 2018 年发布后在 NLP 领域引发了革命。BERT 采用双向 Transformer 编码器,能够同时考虑词的左右上下文信息。
作为 Embedding 模型,BERT 有多种使用方式:
CLS Token Embedding:使用 [CLS] 特殊标记的最后一层输出作为句子表示,适用于分类任务。需要注意的是,CLS token 在语义相似度任务中表现通常不如 Mean Pooling,因为它更偏向分类任务的学习目标。
Mean Pooling:对所有 token 的隐藏状态取平均,公式为:
E = 1 N ∑ i = 1 N h i ( L ) \mathbf{E} = \frac{1}{N} \sum_{i=1}^{N} \mathbf{h}_i^{(L)} E=N1i=1∑Nhi(L)
Last 4 Layers Concatenation:将最后四层的 [CLS] token 拼接,可获得更丰富的特征。
Sentence-Transformers
Sentence-Transformers 是基于 Transformer 的句子嵌入框架,由 UKPLab 开发,专门针对句子级语义相似度任务进行优化。
其核心架构包括:Transformer 编码器(使用 BERT、RoBERTa 等作为基础模型)、Pooling 层(MEAN、MAX、CLS 等)以及可选的归一化层。
该框架提供了丰富的预训练模型,例如 all-MiniLM-L6-v2(384 维,速度快)和 all-mpnet-base-v2(768 维,性能优异),开发者可以通过简单的 API 调用生成高质量的句子嵌入。
1.5 训练方法概览
Embedding 模型的训练方法经历了多次重要演进。
CBOW 和 Skip-gram 是早期经典的训练方法,通过预测上下文或中心词来学习词向量。目标是最大化对数似然概率:
L = ∑ w ∈ C log P ( w ∣ context ( w ) ) \mathcal{L} = \sum_{w \in \mathcal{C}} \log P(w | \text{context}(w)) L=w∈C∑logP(w∣context(w))
对比学习(Contrastive Learning) 是近年来最重要的进展之一。其核心思想是:相似的样本在嵌入空间中应该靠近,不相似的样本应该远离。典型方法包括 SimCLR、MoCo 和 InfoNCE。
InfoNCE 损失的数学形式为:
L N = − E [ log f k ( x t + k , c t ) ∑ x j ∈ X f k ( x j , c t ) ] \mathcal{L}N = - \mathbb{E} \left[\log \frac{f_k(x{t+k}, c_t)}{\sum_{x_j \in X} f_k(x_j, c_t)}\right] LN=−E[log∑xj∈Xfk(xj,ct)fk(xt+k,ct)]
其中温度参数 τ \tau τ 控制相似度分布的陡峭程度,通常设置为 0.05-0.2。
蒸馏(Distillation) 技术用于将大模型的知识迁移到小模型。知识蒸馏通过让小模型(学生网络)学习大模型(教师网络)的输出分布或中间表示,可以在保持较高性能的同时大幅减少计算开销。
第二章:文本相似度计算
2.1 余弦相似度------原理与公式推导
余弦相似度是文本相似度计算中最常用的度量方式,它衡量两个向量之间夹角的余弦值,范围在 [-1, 1] 之间。当夹角为 0° 时,余弦值为 1,表示完全相同;当夹角为 180° 时,余弦值为 -1,表示完全相反。
余弦相似度的数学公式为:
cosine_similarity ( A , B ) = A ⋅ B ∣ ∣ A ∣ ∣ ⋅ ∣ ∣ B ∣ ∣ = ∑ i = 1 n A i × B i ∑ i = 1 n A i 2 × ∑ i = 1 n B i 2 \text{cosine\similarity}(\mathbf{A}, \mathbf{B}) = \frac{\mathbf{A} \cdot \mathbf{B}}{||\mathbf{A}|| \cdot ||\mathbf{B}||} = \frac{\sum{i=1}^{n} A_i \times B_i}{\sqrt{\sum_{i=1}^{n} A_i^2} \times \sqrt{\sum_{i=1}^{n} B_i^2}} cosine_similarity(A,B)=∣∣A∣∣⋅∣∣B∣∣A⋅B=∑i=1nAi2 ×∑i=1nBi2 ∑i=1nAi×Bi
在 Python 中,余弦相似度可以这样计算:
python
import numpy as np
# 方法1:直接计算
def cosine_similarity(A, B):
dot_product = np.dot(A, B)
norm_a = np.linalg.norm(A)
norm_b = np.linalg.norm(B)
return dot_product / (norm_a * norm_b)
# 方法2:使用scipy
from scipy.spatial.distance import cosine
cos_sim = 1 - cosine(A, B)
2.2 欧氏距离与曼哈顿距离
欧氏距离(Euclidean Distance) 是最直观的距离度量,计算向量空间中两点之间的直线距离:
euclidean ( A , B ) = ∑ i = 1 n ( A i − B i ) 2 = ∣ ∣ A − B ∣ ∣ \text{euclidean}(\mathbf{A}, \mathbf{B}) = \sqrt{\sum_{i=1}^{n} (A_i - B_i)^2} = ||\mathbf{A} - \mathbf{B}|| euclidean(A,B)=i=1∑n(Ai−Bi)2 =∣∣A−B∣∣
python
from scipy.spatial.distance import euclidean
distance = euclidean(A, B)
# 或使用numpy
distance = np.linalg.norm(A - B)
曼哈顿距离(Manhattan Distance) 计算各维度差值绝对值之和,也称为城市街区距离:
manhattan ( A , B ) = ∑ i = 1 n ∣ A i − B i ∣ \text{manhattan}(\mathbf{A}, \mathbf{B}) = \sum_{i=1}^{n} |A_i - B_i| manhattan(A,B)=i=1∑n∣Ai−Bi∣
欧氏距离受向量范数影响较大,在高维空间中容易受到维度灾难的影响;曼哈顿距离则对异常值更加鲁棒。在实际的语义搜索场景中,余弦相似度通常比欧氏距离和曼哈顿距离表现更好,因为它只关注方向而不受向量长度的影响。
2.3 点积相似度与归一化处理
点积(Dot Product) 是两个向量的内积,数学定义为:
A ⋅ B = ∑ i = 1 n A i × B i = A T B \mathbf{A} \cdot \mathbf{B} = \sum_{i=1}^{n} A_i \times B_i = \mathbf{A}^T \mathbf{B} A⋅B=i=1∑nAi×Bi=ATB
点积相似度具有以下性质:
- 非标准化:受向量大小(范数)影响
- 对称性 : A ⋅ B = B ⋅ A \mathbf{A} \cdot \mathbf{B} = \mathbf{B} \cdot \mathbf{A} A⋅B=B⋅A
- 线性性 : ( c A ) ⋅ B = c ( A ⋅ B ) (c\mathbf{A}) \cdot \mathbf{B} = c(\mathbf{A} \cdot \mathbf{B}) (cA)⋅B=c(A⋅B)
当向量经过归一化处理后,点积与余弦相似度等价:
python
# 归一化后的点积等价于余弦相似度
normalized_A = A / np.linalg.norm(A)
normalized_B = B / np.linalg.norm(B)
dot_sim_normalized = np.dot(normalized_A, normalized_B) # 等于余弦相似度
这正是 Sentence-Transformers 等框架对输出向量进行 L2 归一化的原因------这样可以使用高效的点积运算代替开方运算。
2.4 语义相似度 vs 词汇匹配的本质区别
词汇匹配(Lexical Matching) 基于单词的表面形式进行相似度计算,主要方法包括:
- Jaccard 相似度 : J ( A , B ) = ∣ A ∩ B ∣ / ∣ A ∪ B ∣ J(A, B) = |A \cap B| / |A \cup B| J(A,B)=∣A∩B∣/∣A∪B∣
- Dice 系数 : D i c e ( A , B ) = 2 ∣ A ∩ B ∣ / ( ∣ A ∣ + ∣ B ∣ ) Dice(A, B) = 2|A \cap B| / (|A| + |B|) Dice(A,B)=2∣A∩B∣/(∣A∣+∣B∣)
- TF-IDF 相似度:基于词频和逆文档频率的加权匹配
语义相似度(Semantic Similarity) 则基于词语或文本的实际含义进行度量。
两种方法的本质区别如下表所示:
| 维度 | 词汇匹配 | 语义相似度 |
|---|---|---|
| 基础 | 单词表面形式 | 单词/文本含义 |
| 方法 | 字符串匹配、TF-IDF | 词向量、深度学习模型 |
| 同义词处理 | 无法识别 | 可以识别 |
| 多义词处理 | 产生歧义 | 可区分不同含义 |
| 计算复杂度 | 低 | 高 |
例如,对于查询"How to cook pasta?"和"What's the recipe for spaghetti?",词汇匹配只能识别出极低的相似度(仅"to"一个词重叠),但语义相似度可以识别出"pasta"和"spaghetti"是同义词,从而给出高相似度分数。
2.5 实际计算示例
以下是一个完整的相似度计算示例:
python
import numpy as np
from sentence_transformers import SentenceTransformer
# 加载预训练模型
model = SentenceTransformer('all-MiniLM-L6-v2')
# 待比较的句子
sentences = [
"How to cook pasta?",
"What's the recipe for spaghetti?",
"How to drive a car?",
"The weather is nice today"
]
# 生成嵌入向量
embeddings = model.encode(sentences)
# 计算余弦相似度矩阵
similarity_matrix = np.inner(embeddings, embeddings)
print("相似度矩阵:")
print(f"'How to cook pasta?' vs 'What's the recipe for spaghetti?': {similarity_matrix[0][1]:.4f}")
print(f"'How to cook pasta?' vs 'How to drive a car?': {similarity_matrix[0][2]:.4f}")
print(f"'How to cook pasta?' vs 'The weather is nice today': {similarity_matrix[0][3]:.4f}")
典型输出结果:
'How to cook pasta?' vs 'What's the recipe for spaghetti?': 0.8523
'How to cook pasta?' vs 'How to drive a car?': 0.6234
'How to cook pasta?' vs 'The weather is nice today': 0.2156
可以看到,语义相关的句子(pasta 与 spaghetti)获得了最高的相似度分数。
2.6 相似度阈值的选择与调优策略
在实际应用中,选择合适的相似度阈值至关重要。阈值设置过低会导致大量不相关结果被返回,阈值过高则可能遗漏相关内容。
阈值选择策略:
-
基于数据分布:绘制相似度分数的直方图,选择能够区分正负样本的分界点。
-
基于业务需求:高精度场景(如问答匹配)可设置较高阈值(如 0.8),高召回场景(如推荐系统)可降低阈值(如 0.5)。
-
动态阈值:根据查询类型、用户历史行为等动态调整阈值。
-
分层阈值:设置多个阈值,返回不同置信度的结果供后续处理。
python
def search_with_threshold(query_embedding, corpus_embeddings, threshold=0.7):
# 计算相似度
similarities = np.dot(corpus_embeddings, query_embedding)
# 获取高于阈值的索引
high_confidence = np.where(similarities >= threshold)[0]
medium_confidence = np.where((similarities >= 0.5) & (similarities < threshold))[0]
return {
'high': high_confidence,
'medium': medium_confidence,
'scores': similarities
}
第三章:语义搜索实战
3.1 语义搜索架构设计
语义搜索的核心流程包括四个关键阶段:索引构建 → 向量编码 → 相似度检索 → 结果排序。
图:语义搜索架构------索引构建 → 向量编码 → 相似度检索 → 结果排序
索引构建阶段将文档分割成合适的块(chunk),然后使用 Embedding 模型将每个块转换为向量,最后存入向量数据库中建立索引。
向量编码阶段将用户查询转换为查询向量。这一步需要使用与索引构建时相同的 Embedding 模型,以确保向量空间的一致性。
相似度检索阶段在向量数据库中执行最近邻搜索,找到与查询向量最相似的文档向量。
结果排序阶段可能包括重排序、多样性处理等进一步优化。
3.2 向量数据库选型对比
主流向量数据库各有特点,以下是详细的对比分析:
| 维度 | FAISS | Milvus | Chroma | Pinecone |
|---|---|---|---|---|
| 部署方式 | 单机/库 | 分布式/云原生 | 单机/轻量级 | SaaS托管 |
| 开源状态 | 开源 | 开源 | 开源 | 商业 |
| 最大维度 | 无限制 | 32,768 | 无限制 | 2,048 |
| 查询延迟 | <10ms | <50ms | 10-100ms | <50ms |
注:查询延迟和数据规模因部署环境、硬件配置和数据特征而异,上表数据为典型场景参考值。
| 数据规模 | 内存限制 | 十亿级 | 百万级 | 十亿级 |
| 多租户 | 不支持 | 支持 | 支持 | 支持 |
| 持久化 | 需外部存储 | 内置 | 内置 | 内置 |
| 学习曲线 | 中 | 高 | 低 | 低 |
| 运维成本 | 中 | 高 | 低 | 低 |
| 最佳场景 | 高性能搜索 | 大规模管理 | 快速开发 | 企业生产 |
选型建议:
- FAISS:适合有技术能力的团队,需要高性能搜索,数据量在单机内存范围内
- Milvus:适合需要分布式部署、海量数据管理的企业级应用
- Chroma:适合快速原型开发、AI 应用集成、小到中等规模数据
- Pinecone:适合希望免运维、需要高可用性的企业用户
3.3 构建一个完整的语义搜索引擎
以下示例展示如何使用 Sentence-Transformers 和 FAISS 构建端到端的语义搜索引擎:
python
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
from typing import List, Tuple
class SemanticSearchEngine:
def __init__(self, model_name: str = 'all-MiniLM-L6-v2'):
self.model = SentenceTransformer(model_name)
self.index = None
self.documents = []
def build_index(self, documents: List[str]):
"""构建向量索引"""
self.documents = documents
# 生成文档嵌入
embeddings = self.model.encode(documents, show_progress_bar=True)
# L2归一化(使余弦相似度等于点积)
faiss.normalize_L2(embeddings)
# 创建FAISS索引
dimension = embeddings.shape[1]
self.index = faiss.IndexFlatIP(dimension) # 内积索引
self.index.add(embeddings)
print(f"索引构建完成:{len(documents)} 条文档,维度 {dimension}")
def search(self, query: str, top_k: int = 5) -> List[Tuple[str, float]]:
"""语义搜索"""
# 生成查询向量
query_embedding = self.model.encode([query])
faiss.normalize_L2(query_embedding)
# 执行搜索
distances, indices = self.index.search(query_embedding, top_k)
# 返回结果
results = []
for dist, idx in zip(distances[0], indices[0]):
if idx >= 0:
results.append((self.documents[idx], float(dist)))
return results
# 使用示例(生产环境请添加异常处理)(生产环境请添加异常处理)
documents = [
"人工智能是计算机科学的一个分支。",
"机器学习是人工智能的核心技术。",
"深度学习是机器学习的一个分支。",
"自然语言处理研究如何让计算机理解人类语言。",
"计算机视觉研究如何让计算机理解图像和视频。"
]
engine = SemanticSearchEngine()
engine.build_index(documents)
# 执行搜索
results = engine.search("深度学习和神经网络的区别", top_k=3)
for doc, score in results:
print(f"相似度: {score:.4f} - {doc}")
3.4 混合搜索策略(BM25 + 语义向量融合)
BM25 是经典的基于词频的信息检索算法,公式为:
BM25 ( d , q ) = ∑ t ∈ q IDF ( t ) × T F ( t , d ) × ( k 1 + 1 ) T F ( t , d ) + k 1 × ( 1 − b + b × ∣ d ∣ a v g d l ) \text{BM25}(d, q) = \sum_{t \in q} \text{IDF}(t) \times \frac{TF(t, d) \times (k_1 + 1)}{TF(t, d) + k_1 \times (1 - b + b \times \frac{|d|}{avgdl})} BM25(d,q)=t∈q∑IDF(t)×TF(t,d)+k1×(1−b+b×avgdl∣d∣)TF(t,d)×(k1+1)
混合搜索结合 BM25 和语义向量的优势,通过线性加权融合两种分数:
python
def hybrid_search(query, documents, embeddings, embedder, alpha=0.4, beta=0.6): # alpha/beta 权重建议从 0.4/0.6 开始,根据实际效果调整
from rank_bm25 import BM25Okapi
import numpy as np
# BM25评分
tokenized_corpus = [doc.split() for doc in documents]
bm25 = BM25Okapi(tokenized_corpus)
bm25_scores = bm25.get_scores(query.split())
# 语义搜索评分
query_embedding = embedder.encode([query])
semantic_scores = np.dot(embeddings, query_embedding.T).flatten()
# 归一化
bm25_norm = (bm25_scores - bm25_scores.min()) / (bm25_scores.max() - bm25_scores.min() + 1e-8)
sem_norm = (semantic_scores - semantic_scores.min()) / (semantic_scores.max() - semantic_scores.min() + 1e-8)
# 融合
combined_scores = alpha * bm25_norm + beta * sem_norm
# 排序
sorted_indices = np.argsort(combined_scores)[::-1]
return sorted_indices
混合搜索的优势在于:BM25 擅长精确词汇匹配,语义搜索理解深层含义,两者互补提供更鲁棒的搜索体验。
3.5 RAG 中的 Embedding 应用
检索增强生成(Retrieval-Augmented Generation, RAG)是将外部知识检索与大语言模型结合的架构,Embedding 在其中扮演核心角色。
python
from langchain.text_splitter import RecursiveCharacterTextSplitter # LangChain >= 0.1.x
from langchain.vectorstores import Chroma
from sentence_transformers import SentenceTransformer
# 文档处理
document = """
人工智能(Artificial Intelligence,AI)是计算机科学的一个分支,
它试图理解智能的本质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器。
该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。
"""
# 文档分割
splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=50
)
chunks = splitter.split_text(document)
# 生成嵌入并存储
embedder = SentenceTransformer('all-MiniLM-L6-v2')
vectorstore = Chroma.from_texts(chunks, embedder)
# 检索
query = "人工智能包括哪些领域?"
docs = vectorstore.similarity_search(query, k=2)
print("检索到的文档:")
for doc in docs:
print(f"- {doc.page_content}")
RAG 系统中的 Embedding 优化策略包括:多粒度索引(句子级、段落级)、混合检索、相似度阈值过滤以及最大边际相关性(MMR)多样性优化。
3.6 性能优化:ANN 近似最近邻算法
当数据规模达到百万级以上时,精确的最近邻搜索变得不切实际,需要使用近似最近邻(Approximate Nearest Neighbor, ANN)算法。
HNSW(Hierarchical Navigable Small World) 构建多层图结构,高层包含长连接用于快速导航,低层包含短连接用于精细搜索。其参数配置:
python
import faiss
d = 384 # 向量维度
M = 32 # 每个节点的连接数
index = faiss.IndexHNSWFlat(d, M)
index.hnsw.efConstruction = 200 # 构建时探索的候选节点数
index.hnsw.efSearch = 64 # 搜索时探索的候选节点数
IVF(Inverted File Index) 首先对向量进行聚类,查询时只搜索最近的几个聚类:
python
nlist = 1000 # 聚类数量
nprobe = 10 # 搜索的聚类数量
quantizer = faiss.IndexFlatL2(d)
index = faiss.IndexIVFFlat(quantizer, d, nlist)
index.nprobe = nprobe
PQ(Product Quantization) 将高维向量分割为子向量,分别进行量化压缩:
python
m = 8 # 子向量数量
nbits = 8 # 每个子向量的编码位数
index = faiss.IndexPQ(d, m, nbits)
各算法对比:
| 特性 | HNSW | IVF | PQ | IVFPQ |
|---|---|---|---|---|
| 内存使用 | 高 | 中 | 低 | 中低 |
| 搜索速度 | 快 | 中 | 慢 | 中快 |
| 精度 | 高 | 可调 | 中低 | 高 |
| 支持动态 | 是 | 否 | 否 | 否 |
3.7 实际应用场景
智能客服:用户输入问题后,系统通过语义搜索从知识库中检索最相关的答案,再交由大语言模型生成回复。
知识检索:在企业内部文档库、学术论文库中实现语义化的内容搜索,克服关键词匹配无法处理同义词和专业术语的问题。
推荐系统:通过计算用户兴趣向量与物品向量之间的相似度,为用户提供个性化的内容推荐。
第四章:聚类分析
4.1 为什么 Embedding 适合聚类分析
聚类分析是将相似的数据点分组的技术。传统的词袋模型或 TF-IDF 向量是高维稀疏的,其中大部分维度为零,这给聚类算法带来了维数灾难的问题------在高维空间中,数据点之间的距离趋于相近,使得聚类变得无效。
Embedding 将数据映射到低维稠密空间(通常 128-1024 维),具有以下优势:
- 语义信息丰富:向量编码了数据的语义特征,语义相似的样本在空间中自然聚集
- 维度适中:避免了维数灾难,同时保留了足够的判别信息
- 距离度量有效:在语义空间中,余弦相似度等度量能够有效区分不同类别
- 非线性结构可被捕获:基于深度学习的 Embedding 能够学习复杂的语义结构
4.2 K-Means 聚类在 Embedding 空间的应用
K-Means 是最经典的聚类算法之一,通过迭代优化将数据划分为 K 个簇。
算法原理:
- 随机选择 K 个初始质心
- 将每个数据点分配到最近的质心
- 重新计算每个簇的质心
- 重复步骤 2-3 直到收敛
目标函数(簇内平方和):
J = ∑ k = 1 K ∑ x ∈ C k ∣ ∣ x − μ k ∣ ∣ 2 J = \sum_{k=1}^{K} \sum_{\mathbf{x} \in C_k} ||\mathbf{x} - \boldsymbol{\mu}_k||^2 J=k=1∑Kx∈Ck∑∣∣x−μk∣∣2
python
from sklearn.cluster import KMeans
from sentence_transformers import SentenceTransformer
import numpy as np
# 生成句子嵌入
model = SentenceTransformer('all-MiniLM-L6-v2')
sentences = [
"今天天气很好", "天气晴朗", "阳光明媚",
"我,喜欢学习编程", "他在学习Python", "编程很有趣",
"明天有会议", "会议安排在下午", "参加一个商务会议"
]
embeddings = model.encode(sentences)
# K-Means聚类
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
labels = kmeans.fit_predict(embeddings)
print("聚类结果:")
for i, (sent, label) in enumerate(zip(sentences, labels)):
print(f" {sent} -> 类别 {label}")
K 值选择:使用肘部法则或轮廓系数确定最优的聚类数量:
python
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
# 尝试不同的K值
k_range = range(2, 10)
silhouette_scores = []
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = kmeans.fit_predict(embeddings)
score = silhouette_score(embeddings, labels)
silhouette_scores.append(score)
optimal_k = k_range[np.argmax(silhouette_scores)]
print(f"最优K值: {optimal_k}")
4.3 DBSCAN 密度聚类与异常检测
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)基于密度进行聚类,能够自动发现任意形状的簇,并识别噪声点。
核心概念:
- 核心点:邻域内至少有 min_samples 个点
- 边界点:在核心点的邻域内,但不是核心点
- 噪声点:既不是核心点也不是边界点
python
from sklearn.cluster import DBSCAN
dbscan = DBSCAN(
eps=0.5, # 邻域半径
min_samples=2, # 核心点的最小样本数
metric='cosine' # 使用余弦距离
)
labels = dbscan.fit_predict(embeddings)
print("DBSCAN聚类结果:")
for sent, label in zip(sentences, labels):
if label == -1:
print(f" {sent} -> 噪声点")
else:
print(f" {sent} -> 类别 {label}")
DBSCAN 的优势在于不需要预先指定簇数量,能够自动发现异常点,适合处理带有噪声的真实数据。
4.4 层次聚类(Hierarchical Clustering)
层次聚类通过构建树状图来展示数据的层次结构,支持凝聚式和分裂式两种方法。
python
from sklearn.cluster import AgglomerativeClustering
import numpy as np
# 凝聚层次聚类
agglo = AgglomerativeClustering(
n_clusters=3, # 最终簇数
affinity='cosine', # 使用余弦距离
linkage='average' # 平均链接
)
labels = agglo.fit_predict(embeddings)
print("层次聚类结果:")
for sent, label in zip(sentences, labels):
print(f" {sent} -> 类别 {label}")
常用的链接方法包括:
- 单链接:簇间距离等于两个簇中最近点的距离,容易产生链状簇
- 全链接:簇间距离等于两个簇中最远点的距离,产生紧凑球形簇
- 平均链接:簇间距离等于所有点对距离的平均值
- Ward 方法:最小化簇内方差
4.5 聚类评估指标
轮廓系数(Silhouette Score) 衡量每个样本的聚类质量,范围 [-1, 1],值越大表示聚类越好:
s ( i ) = b ( i ) − a ( i ) max { a ( i ) , b ( i ) } s(i) = \frac{b(i) - a(i)}{\max\{a(i), b(i)\}} s(i)=max{a(i),b(i)}b(i)−a(i)
其中 a ( i ) a(i) a(i) 是样本到同簇其他点的平均距离, b ( i ) b(i) b(i) 是样本到其他簇中点的最小平均距离。
python
from sklearn.metrics import silhouette_score, davies_bouldin_score
# 轮廓系数
sil_score = silhouette_score(embeddings, labels, metric='cosine')
print(f"轮廓系数: {sil_score:.4f}")
# Davies-Bouldin指数(越小越好)
db_score = davies_bouldin_score(embeddings, labels)
print(f"Davies-Bouldin指数: {db_score:.4f}")
4.6 文本主题发现实战案例
聚类分析可以用于发现文本集合中的主题结构:
python
from sentence_transformers import SentenceTransformer
from sklearn.cluster import KMeans
from collections import Counter
# 文档集合
documents = [
"深度学习是机器学习的一个分支,使用神经网络模型。",
"卷积神经网络广泛应用于图像识别领域。",
"循环神经网络适合处理序列数据。",
"自然语言处理让计算机理解人类语言。",
"Transformer模型改变了自然语言处理的格局。",
"BERT和GPT是基于Transformer的重要模型。",
"计算机视觉研究如何让机器理解图像。",
"目标检测是计算机视觉的重要任务。",
"人脸识别技术应用广泛。",
"语音识别将声音转换为文字。"
]
# 向量化
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(documents)
# 聚类
n_topics = 3
kmeans = KMeans(n_clusters=n_topics, random_state=42, n_init=10)
labels = kmeans.fit_predict(embeddings)
# 分析每个簇的主题
print("主题发现结果:\n")
for topic_id in range(n_topics):
topic_docs = [doc for doc, label in zip(documents, labels) if label == topic_id]
print(f"主题 {topic_id + 1}:")
for doc in topic_docs:
print(f" - {doc}")
print()
4.7 可视化:t-SNE 与 UMAP 降维展示
t-SNE(t-Distributed Stochastic Neighbor Embedding) 将高维数据映射到 2D 空间,保持局部结构:
python
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
# t-SNE降维
tsne = TSNE(n_components=2, perplexity=5, random_state=42)
embeddings_2d = tsne.fit_transform(embeddings)
# 可视化
plt.figure(figsize=(10, 8))
plt.scatter(embeddings_2d[:, 0], embeddings_2d[:, 1], c=labels, cmap='viridis', s=100)
# 添加标签
for i, sent in enumerate(sentences):
plt.annotate(sent[:10], (embeddings_2d[i, 0], embeddings_2d[i, 1]), fontsize=8)
plt.title('t-SNE Visualization of Embeddings')
plt.show()
UMAP(Uniform Manifold Approximation and Projection) 相比 t-SNE 更快,能更好地保持全局结构:
python
import umap
# UMAP降维
reducer = umap.UMAP(n_components=2, n_neighbors=3, min_dist=0.1, random_state=42)
embeddings_2d = reducer.fit_transform(embeddings)
# 可视化
plt.figure(figsize=(10, 8))
plt.scatter(embeddings_2d[:, 0], embeddings_2d[:, 1], c=labels, cmap='viridis', s=100)
for i, sent in enumerate(sentences):
plt.annotate(sent[:10], (embeddings_2d[i, 0], embeddings_2d[i, 1]), fontsize=8)
plt.title('UMAP Visualization of Embeddings')
plt.show()
图:t-SNE 与 UMAP 降维效果对比
| 特性 | t-SNE | UMAP |
|---|---|---|
| 计算复杂度 | O(n²) | O(n log n) |
| 全局结构 | 较弱 | 较强 |
| 稳定性 | 低 | 较高 |
| 适用于 | 小数据集可视化 | 大数据集分析 |
第五章:多语言 Embedding
5.1 多语言 Embedding 的核心挑战
多语言 Embedding 面临三个核心挑战:
- 语言差异:不同语言的语法结构、词序、形态学特征差异巨大
- 数据不平衡:英语、中文等高资源语言有丰富的训练数据,而许多低资源语言数据匮乏
- 跨语言对齐:需要在单一向量空间中表示所有语言,使得语义相似的句子即使语言不同也能靠近
5.2 跨语言对齐技术
MUSE(Multilingual Unsupervised Sentence Embeddings) 使用对抗训练进行跨语言词向量对齐,核心思想是学习一个映射矩阵 W,将源语言向量映射到目标语言空间:
L a d v = E x ∼ X [ log D ( W x ) ] + E y ∼ Y [ log ( 1 − D ( y ) ) ] \mathcal{L}{adv} = \mathbb{E}{x \sim \mathbb{X}}[\log D(Wx)] + \mathbb{E}_{y \sim \mathbb{Y}}[\log(1 - D(y))] Ladv=Ex∼X[logD(Wx)]+Ey∼Y[log(1−D(y))]
XLM-R(XLM-RoBERTa) 基于 RoBERTa 架构,在 100 种语言的大规模语料上进行掩码语言模型预训练,具有强大的跨语言迁移能力。
LaBSE(Language-agnostic BERT Sentence Embeddings) 通过翻译语言模型预训练,在单一向量空间中表示 109 种语言的句子。
python
from sentence_transformers import SentenceTransformer
# 加载LaBSE模型
model = SentenceTransformer('sentence-transformers/LaBSE')
# 多语言句子
sentences = [
"Hello world",
"Hola mundo",
"你好世界",
"Bonjour le monde"
]
embeddings = model.encode(sentences)
# 跨语言相似度
from sklearn.metrics.pairwise import cosine_similarity
sim_matrix = cosine_similarity(embeddings)
print("跨语言相似度矩阵:")
print(f"英语-西班牙语: {sim_matrix[0][1]:.4f}")
print(f"英语-中文: {sim_matrix[0][2]:.4f}")
print(f"英语-法语: {sim_matrix[0][3]:.4f}")
5.3 多语言语义搜索实现方案
python
from FlagEmbedding import BGEM3FlagModel
# 加载bge-m3模型
model = BGEM3FlagModel('BAAI/bge-m3', use_fp16=True)
# 文档(多语言)
documents = [
"Artificial intelligence is transforming the world.",
"人工智能正在改变世界。",
"La inteligencia artificial está transformando el mundo."
]
# 查询
query = "How is AI changing the world?"
# 生成嵌入
query_embedding = model.encode(query, return_dense=True)['dense_vecs']
doc_embeddings = model.encode(documents, return_dense=True)['dense_vecs']
# 计算相似度
import numpy as np
similarities = np.inner(query_embedding, doc_embeddings)
print("多语言语义搜索结果:")
for doc, sim in zip(documents, similarities):
print(f" {sim:.4f} - {doc}")
5.4 主流多语言模型对比
| 维度 | E5-multilingual | bge-m3 | multilingual-e5 |
|---|---|---|---|
| 基础模型 | XLM-R-base | XLM-R-large | XLM-R-base/large |
| 参数量 | 270M | 560M | 110M/335M |
| 向量维度 | 768 | 1024 | 768/1024 |
| 支持语言数 | 100+ | 100+ | 90+ |
| 检索模式 | 密集 | 密集+稀疏+多向量 | 密集 |
| MTEB评分 | 62.3 | 65.8 | 67.8 |
注:MTEB(Massive Text Embedding Benchmark)多语言检索任务得分,具体分数因模型版本和测试数据集而异,数据来源于官方 Benchmark 报告。
选型建议:
- 资源受限:multilingual-e5-base
- 高精度需求:bge-m3
- 通用场景:E5-multilingual
5.5 中英文混合场景下的 Embedding 实践
在中英文混合场景中,建议使用专门优化的多语言模型:
python
from sentence_transformers import SentenceTransformer
# 加载多语言模型
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
# 混合语言句子
mixed_sentences = [
"Machine Learning 机器学习",
"深度学习 Deep Learning",
"今天天气很好",
"The weather is nice today"
]
embeddings = model.encode(mixed_sentences)
# 计算相似度
import numpy as np
sim_matrix = np.inner(embeddings, embeddings)
print("中英文混合场景相似度:")
print(f"'Machine Learning 机器学习' vs '深度学习 Deep Learning': {sim_matrix[0][1]:.4f}")
print(f"'Machine Learning 机器学习' vs '今天天气很好': {sim_matrix[0][2]:.4f}")
第六章:前沿与展望
6.1 多模态 Embedding(CLIP、图像-文本联合空间)
CLIP(Contrastive Language-Image Pre-training)由 OpenAI 发布,通过对比学习将图像和文本映射到同一向量空间,实现了令人印象深刻的零样本图像分类能力。
python
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
import torch
# 加载CLIP模型
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
# 图像
image = Image.open("example.jpg")
# 文本描述
texts = ["a photo of a cat", "a photo of a dog", "a photo of a car"]
# 编码
inputs = processor(text=texts, images=image, return_tensors="pt", padding=True)
outputs = model(**inputs)
# 计算相似度
logits_per_image = outputs.logits_per_image
probs = logits_per_image.softmax(dim=1)
print("图像-文本匹配概率:")
for text, prob in zip(texts, probs[0]):
print(f" {text}: {prob.item():.4f}")
多模态 Embedding 的发展趋势包括:更强大的视觉-语言联合表示、3D 场景理解、视频-文本对齐等。
6.2 稀疏 Embedding 与稠密 Embedding 的融合趋势
传统的稠密向量(如 BERT、Sentence-Transformers 生成)是固定维度的实数向量。稀疏向量(如 BM25、SPADE)基于词频统计,更擅长精确匹配。
最新研究表明,将稀疏和稠密 Embedding 融合可以兼顾精确匹配和语义理解:
- 稀疏检索:擅长词汇精确匹配,处理专有名词、技术术语
- 稠密检索:擅长语义理解,处理同义词、表述变化
bge-m3 模型是这一趋势的代表,它同时输出密集向量、稀疏向量和多向量表示,支持多种检索模式的融合。