RAG从入门到精通-RAG系统核心引擎Embedding模型
·
目的
理解Embedding模型的重要性以及现有Emebdding模型的优劣以及选型的指标。
Embedding是RAG的"心脏"
RAG系统工作流程与Embedding的作用
| 处理阶段 | 核心任务 | Embedding的角色 | 输入→输出转换 |
|---|---|---|---|
| 文档处理 | 知识库准备 | 将文档切片转换为向量 | 文本 → 数字向量 |
| 检索阶段 | 查找相关信息 | 计算查询与文档的语义相似度 | 问题向量 vs 文档向量 |
| 增强阶段 | 构建提示词 | 基于语义相关度筛选最佳上下文 | 相关文档 + 原始问题 |
| 生成阶段 | 生成答案 | 为LLM提供精准的上下文信息 | 增强提示 → 最终答案 |
Embedding在RAG中的核心价值
| 价值维度 | 传统关键词搜索 | 基于Embedding的语义搜索 | 优势说明 |
|---|---|---|---|
| 语义理解 | 仅匹配字面词项 | 理解同义词、相关概念 | “汽车"也能匹配"车辆”、“机动车” |
| 多语言支持 | 需要语言特定配置 | 跨语言语义映射 | 中文问题可检索英文相关内容 |
| 长尾查询 | 对生僻查询效果差 | 基于概念相似性工作 | 即使查询词不常见也能找到相关内容 |
| 上下文感知 | 忽略词语上下文 | 考虑词语在上下文中的含义 | "苹果公司"vs"水果苹果"区分明确 |
什么是Embedding?从文字到向量的魔法
Embedding技术基础概念
| 概念 | 定义 | 直观比喻 | 技术实现 |
|---|---|---|---|
| Embedding向量 | 文本的数值表示 | 文字的"数字指纹" | 浮点数数组,如[0.1, -0.5, 0.8, …] |
| 向量空间 | 所有向量存在的数学空间 | “语义宇宙” | 高维坐标系(通常256-1536维) |
| 语义相似度 | 向量间的距离关系 | “语义距离” | 余弦相似度、欧氏距离等 |
| 维度 | 向量的长度 | “指纹的复杂程度” | 模型输出固定长度的向量 |
文本到向量的转换示例
| 文本内容 | 可能的向量表示(简化) | 语义相近文本 | 相似度计算 |
|---|---|---|---|
| “我喜欢机器学习” | [0.2, 0.7, -0.1, 0.5, …] | “我热爱人工智能” | 余弦相似度: 0.89 |
| “今天天气很好” | [0.1, 0.3, 0.9, -0.2, …] | “阳光明媚的一天” | 余弦相似度: 0.85 |
| “编程很难学” | [-0.5, 0.6, 0.2, 0.4, …] | “写代码有挑战” | 余弦相似度: 0.82 |
| “苹果很好吃” | [0.3, -0.2, 0.1, 0.8, …] | “水果很美味” | 余弦相似度: 0.78 |
Embedding模型选型的关键考量因素
选择Embedding模型时,需要从多个维度进行权衡:
1. 性能指标
- 检索准确率:模型找到相关文档的能力
- 排序质量:相关文档在结果中的排名位置
- 泛化能力:在未见过的数据上的表现
2. 技术参数
- 上下文长度:单次能处理的文本长度(512 tokens? 8K? 32K?)
- 向量维度:输出向量的尺寸(384维? 768维? 1536维?)
- 推理速度:生成向量的快慢
3. 资源成本
- 计算资源:模型运行需要的GPU/CPU资源
- 存储成本:向量数据库的存储需求
- 部署复杂度:本地部署还是使用API服务
4. 业务适配
- 语言支持:是否支持你的业务语言
- 领域适配:在专业领域(医疗、法律等)的表现
- 数据隐私:是否需要本地化部署
如何选择Embedding模型
模型介绍
| 模型系列 | 代表模型 | 主要语言 | 上下文长度 | 向量维度 | 关键优势 |
|---|---|---|---|---|---|
| OpenAI | text-embedding-3-large | 多语言 | 8192 | 3072(可调整) | 多语言、通用质量高,API支持,无需本地部署 |
| QWen3 | QWen3-8B | 中英文 | 32K | 可配置 | 8B 型号在 MTEB 多语言榜单排名第一 |
| BGE系列 | bge-large-zh | 中文优化 | 512 | 1024 | 中文SOTA表现 |
| E5系列 | e5-large-v2 | 多语言 | 514 | 1024 | 平衡性能与效率 |
| M3E系列 | m3e-base | 中文 | 512 | 768 | 中文社区优化 |
| GTE系列 | gte-large | 多语言 | 8192 | 1024 | 长文本支持 |
| Sentence-BERT | all-MiniLM-L6 | 英文 | 256 | 384 | 轻量快速 |
如何选择
评估指标详细说明
| 指标 | 数学公式 | 代码实现关键点 | 适用场景 |
|---|---|---|---|
| Hit Rate @ K | H R @ K = 命中查询数 总查询数 HR@K = \frac{\text{命中查询数}}{\text{总查询数}} HR@K=总查询数命中查询数 | any(doc_id in relevant_docs for doc_id in top_k_results) |
快速评估模型是否能在前K个结果中找到相关文档 |
| MRR @ K | M R R @ K = 1 N ∑ i = 1 N 1 rank i MRR@K = \frac{1}{N}\sum_{i=1}^N \frac{1}{\text{rank}_i} MRR@K=N1i=1∑Nranki1 | 找到第一个相关文档的排名,计算其倒数 | 关注第一个相关文档的排名质量 |
| NDCG @ K | N D C G @ K = D C G @ K I D C G @ K NDCG@K = \frac{DCG@K}{IDCG@K} NDCG@K=IDCG@KDCG@K D C G @ K = ∑ i = 1 K r e l i log 2 ( i + 1 ) DCG@K = \sum_{i=1}^K \frac{rel_i}{\log_2(i+1)} DCG@K=i=1∑Klog2(i+1)reli |
计算实际排序和理想排序的折扣累积增益比 | 考虑多级相关性和排序位置的综合评估 |
- Hit Rate @ K:前K个结果中至少包含一个相关文档的概率
def hit_rate_at_k(search_results: List[List[str]],
ground_truth: List[List[str]],
k: int) -> float:
"""
计算Hit Rate @ K
Args:
search_results: 模型返回的排序结果列表,每个子列表包含文档ID
ground_truth: 真实相关文档列表,每个子列表包含相关文档ID
k: 考虑的前K个结果
Returns:
hit_rate: Hit Rate @ K 值
"""
hits = 0
total_queries = len(search_results)
for i in range(total_queries):
# 获取前K个结果
top_k_results = search_results[i][:k]
# 获取真实相关文档
relevant_docs = set(ground_truth[i])
# 检查前K个结果中是否有至少一个相关文档
if any(doc_id in relevant_docs for doc_id in top_k_results):
hits += 1
return hits / total_queries if total_queries > 0 else 0.0
- Mean Reciprocal Rank (MRR) @ K:衡量第一个相关文档的排名质量
def mean_reciprocal_rank_at_k(search_results: List[List[str]], ground_truth: List[List[str]], k: int) -> float: """ 计算Mean Reciprocal Rank @ K Args: search_results: 模型返回的排序结果列表 ground_truth: 真实相关文档列表 k: 考虑的前K个结果 Returns: mrr: Mean Reciprocal Rank @ K 值 """ reciprocal_ranks = [] for i in range(len(search_results)): top_k_results = search_results[i][:k] relevant_docs = set(ground_truth[i]) # 找到第一个相关文档的排名 first_relevant_rank = None for rank, doc_id in enumerate(top_k_results, 1): if doc_id in relevant_docs: first_relevant_rank = rank break # 计算 Reciprocal Rank if first_relevant_rank is not None: reciprocal_ranks.append(1.0 / first_relevant_rank) else: reciprocal_ranks.append(0.0) return np.mean(reciprocal_ranks) if reciprocal_ranks else 0.0 - Normalized Discounted Cumulative Gain (NDCG) @ K:考虑排序位置的综合指标
def ndcg_at_k(search_results: List[List[str]], ground_truth: List[List[str]], k: int, relevance_scores: Dict[str, float] = None) -> float: """ 计算Normalized Discounted Cumulative Gain @ K Args: search_results: 模型返回的排序结果列表 ground_truth: 真实相关文档列表 k: 考虑的前K个结果 relevance_scores: 文档相关性得分字典,如果不提供则使用二值相关性 Returns: ndcg: Normalized Discounted Cumulative Gain @ K 值 """ ndcg_scores = [] for i in range(len(search_results)): top_k_results = search_results[i][:k] relevant_docs = set(ground_truth[i]) # 计算 DCG dcg = 0.0 for rank, doc_id in enumerate(top_k_results, 1): if relevance_scores and doc_id in relevance_scores: rel = relevance_scores[doc_id] else: # 二值相关性:相关为1,不相关为0 rel = 1.0 if doc_id in relevant_docs else 0.0 dcg += rel / math.log2(rank + 1) # 使用 log2(rank + 1) 作为折扣因子 # 计算 IDCG (理想排序的DCG) # 构建理想排序:所有相关文档排在前面 ideal_relevance = [] for doc_id in relevant_docs: if relevance_scores and doc_id in relevance_scores: ideal_relevance.append(relevance_scores[doc_id]) else: ideal_relevance.append(1.0) # 按相关性得分降序排序 ideal_relevance.sort(reverse=True) ideal_relevance = ideal_relevance[:k] # 只取前K个 idcg = 0.0 for rank, rel in enumerate(ideal_relevance, 1): idcg += rel / math.log2(rank + 1) # 计算 NDCG if idcg > 0: ndcg_scores.append(dcg / idcg) else: ndcg_scores.append(0.0) return np.mean(ndcg_scores) if ndcg_scores else 0.0
总结
为RAG系统选择合适的Embedding模型至关重要,它直接决定了检索质量,进而影响生成答案的准确性。我在验证RAG的稳定性时经常使用两种以上的模型进行调试,如Qwen3、BAAI等开源模型系列。最后,希望大家在落地RAG的时候都能为RAG找到合适的"心脏"。
参考资料
更多推荐


所有评论(0)