Jean's Blog

一个专注软件测试开发技术的个人博客

0%

RAG组件--向量嵌入之稀疏嵌入、密集嵌入、混合检索

稀疏嵌入与密集嵌入

image-20250815093602208

稀疏嵌入

  • 定义:早期自然语言处理中基于词频统计的嵌入方式
  • 形成方式:通过词频或n-gram统计词在文档中出现次数
  • 稀疏性原因:维度与词典大小相同(如现代汉语字典几万字对应几万维)
  • 存储方式:出现词标记为1(如”黑神话悟空”在3000、5000、8000维标记1),未出现词均为0
  • 典型示例:游戏角色特征标签(金箍棒:1,七十二变:1,筋斗云:1等)

稀疏嵌入的特定场景应用

  • 保留价值:
    • 精确匹配特定关键词(如游戏中的”金箍棒”)
    • 快速过滤未出现的关键词(得分为0)
    • 处理明确特征标签(如角色属性、技能名称)
  • 混合检索:结合稀疏嵌入的精确匹配和密集嵌入的语义理解优势

密集嵌入

  • 现代主流:大语言模型时代主要使用的嵌入形式
  • 生成方式:通过transformer架构计算,基于文档相似度特征提取
  • 特点:
    • 连续实数值(如[0.2,-0.5,0.8])
    • 维度较小(通常128-256维)
    • 特征自动学习得到
  • 优势:能捕捉语义相似度,适合处理抽象语义关系

稀疏嵌入与密集嵌入的结合使用

  • 应用场景:游戏问答系统中查询特定武器/技能时
  • 协同效应:
    • 稀疏嵌入快速定位含关键词段落
    • 密集嵌入理解语义相关但表述不同的内容
  • 示例:查询”悟空的武器在哪些关卡管用”时,稀疏嵌入能精准匹配”金箍棒”相关段落

稀疏嵌入的特征标签表示形式

image-20250815093846348

  • 标签化表示:将特征转化为二进制标记(存在=1,不存在=0)
  • 现代发展:从简单0/1标记发展为带权重的实数表示(如金箍棒:0.82)
  • 类比理解:可视为更精确的关键词检索系统

向量类型的对比

image-20250815094142413

  • 主要类型:
    • Float Vector(密集嵌入)
    • Sparse Float Vector(稀疏实数向量)
    • Binary Vector(稀疏嵌入简化形式)

Sparse Float Vector(稀疏实数向量)

  • 存储方式:键值对表示(如(1:0.5, 5:-0.3, 8:0.7))
  • 特点:
    • 只存储非零值
    • 维度可以非常大(几万维)
    • 现代稀疏向量包含权重值(非简单0/1)

Binary Vector(稀疏嵌入的简化形式)

  • 特点:
    • 仅包含0和1
    • 表示特征存在与否
    • 推荐系统中用户画像的典型表示方式
  • 示例:淘宝用户标签系统(性别、消费偏好等)

稀疏嵌入与密集嵌入的关系

  • 实现形式:
    • 密集嵌入 → Float Vector
    • 稀疏嵌入 → Binary Vector或Sparse Float Vector
  • 核心区别:
    • 密集嵌入所有位置都有值
    • 稀疏嵌入大部分位置为0

BM25:典型的稀疏嵌入实现

文本:“猢狲施展烈焰拳,击退妖怪;随后开启金刚体,抵挡神兵攻击。”如词表包含10 000个词,但作为稀疏向量只有特定维度有值,例如103维、302维等,分别对应“金刚体”“灭神纪”“猢狲”等词的权重。

  • 参数说明:
    • $k_1$:控制词频对权重的影响(取值1-2)
    • $b$:文档长度归一化参数(0-1)
  • 参数调整:
    • 文档长度差异大时增加b值
    • 强调词频重要性时增加k1值
  • 词频(TF)的解释与计算
    • 计算方式:词在当前文档中出现的次数
    • 示例:”悟空”出现3次则TF=3
    • 基础作用:单独使用TF即可生成简单稀疏向量
  • 逆文档频率(IDF)的概念与重要性
    • 定义:衡量词在所有文档中的全局重要性
    • 核心思想:
      • 在所有文档中频繁出现的词(如”的”、”是”)重要性低
      • 在少数文档中高频出现的词重要性高

BM25的应用实例与解释

  • 示例文本:”猢狲施展烈焰拳,击退妖怪;随后开启金刚体,抵挡神兵攻击”
  • 处理过程:
    • 万维词表中只有特定维度有值(如103维”金刚体”)
    • 计算各关键词的BM25得分
  • 比较示例:
    • “金箍棒”在段落出现3次,全局出现少 → 高分
    • “悟空”在段落出现3次,但全局出现万次 → 相对低分

BM25在大模型时代的应用价值

  • 快速过滤:对未出现关键词得分为0,检索效率高
  • 精确匹配:对强特征标签(如特定武器名)检索准确
  • 混合优势:与密集嵌入配合实现快速精确的混合检索

示例代码:稀疏向量的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# @Time:2025/8/15 10:01
# @Author:jinglv
import math
from collections import Counter

# 猢狲的战斗日志
battle_logs = [
"猢狲,施展,烈焰拳,击退,妖怪;随后开启,金刚体,抵挡,神兵,攻击。",
"妖怪,使用,寒冰箭,攻击,猢狲,但被,烈焰拳,反击,击溃。",
"猢狲,召唤,烈焰拳,与,毁灭咆哮,击败,妖怪,随后,收集,妖怪,精华。"
]
# 超参数
k1 = 1.5
b = 0.75
# 构建词表
vocabulary = set(word for log in battle_logs for word in log.split(","))
vocab_to_idx = {word: idx for idx, word in enumerate(vocabulary)}
# 计算IDF
N = len(battle_logs)
df = Counter(word for log in battle_logs for word in set(log.split(",")))
idf = {word: math.log((N - df[word] + 0.5) / (df[word] + 0.5) + 1) for word in vocabulary}
# 日志长度信息
avg_log_len = sum(len(log.split(",")) for log in battle_logs) / N


# BM25稀疏嵌入生成
def bm25_sparse_embedding(log):
tf = Counter(log.split(","))
log_len = len(log.split(","))
embedding = {}
for word, freq in tf.items():
if word in vocabulary:
idx = vocab_to_idx[word]
score = idf[word] * (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * log_len / avg_log_len))
embedding[idx] = score
return embedding


# 生成稀疏向量
for log in battle_logs:
sparse_embedding = bm25_sparse_embedding(log)
print(f"稀疏嵌入: {sparse_embedding}")

执行结果,内容输出

1
稀疏嵌入: {1: 0.12572760993867385, 21: 0.9235080629006516, 2: 0.12572760993867385, 11: 0.9235080629006516, 10: 0.9235080629006516, 7: 0.9235080629006516, 19: 0.6429294928361478, 12: 0.9235080629006516, 3: 0.9235080629006516, 16: 0.9235080629006516}

LangChain实现BM25的嵌入检索

安装包

1
pip install rank_bm25

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# @Time:2025/8/15 10:06
# @Author:jinglv
import os

from dotenv import load_dotenv
from langchain_community.retrievers import BM25Retriever # pip install rank_bm25
from langchain_community.vectorstores import Chroma # pip install chromadb
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

battle_logs = [
"猢狲身披锁子甲。",
"猢狲在无回谷遭遇了妖怪,妖怪开始攻击,猢狲使用铜云棒抵挡。",
"猢狲施展烈焰拳击退妖怪随后开启金刚体抵挡神兵攻击。",
"妖怪使用寒冰箭攻击猢狲但被烈焰拳反击击溃。",
"猢狲召唤烈焰拳与毁灭咆哮击败妖怪随后收集妖怪精华。"
]
request = "猢狲有什么装备和招数?"

bm25_retriever = BM25Retriever.from_texts(battle_logs)
bm25_response = bm25_retriever.invoke(request)
print(f"BM25检索结果:\n{bm25_response}")

docs = [Document(page_content=log) for log in battle_logs]
load_dotenv()

# 3. 设置嵌入模型
from langchain_huggingface import HuggingFaceEmbeddings # pip install langchain-huggingface

embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-small-zh",
model_kwargs={'device': 'cpu'},
encode_kwargs={'normalize_embeddings': True}
)

chroma_vs = Chroma.from_documents(
docs,
embedding=embeddings
# embedding=OpenAIEmbeddings(
# model="text-embedding-3-small",
# api_key=os.getenv("DEEPSEEK_API_KEY"), # 从环境变量加载API key
# base_url=os.getenv("DEEPSEEK_BASE_URL")
# )
)
chroma_retriever = chroma_vs.as_retriever()
chroma_response = chroma_retriever.invoke(request)
print(f"Chroma检索结果:\n{chroma_response}")

# hybrid_response = list({doc.page_content for doc in bm25_response}) # 缺锁子甲
# hybrid_response = list({doc.page_content for doc in chroma_response}) # 缺铜云棒
hybrid_response = list({doc.page_content for doc in bm25_response + chroma_response})
print(f"混合检索结果:\n{hybrid_response}")

prompt = ChatPromptTemplate.from_template("""
基于以下上下文,回答问题。如果上下文中没有相关信息,
请说"我无法从提供的上下文中找到相关信息"。
上下文: {context}
问题: {question}
回答:"""
)

llm = ChatOpenAI(
model=os.getenv("DEEPSEEK_MODEL_NAME"), # DeepSeek API 支持的模型名称
api_key=os.getenv("DEEPSEEK_API_KEY"), # 从环境变量加载API key
base_url=os.getenv("DEEPSEEK_BASE_URL")
)
doc_content = "\n\n".join(hybrid_response)
answer = llm.invoke(prompt.format(question=request, context=doc_content))
print(f"LLM回答:\n{answer.content}")

BGE – M3模型

为什么叫作M3?

  • 多功能性(Multi-Functionality):BGE-M3模型集成了密集检索、稀疏检索和多向量检索3种功能,能够灵活应对不同的检索需求,是目前首个实现三合一功能的嵌入模型。
  • 多语言性(Multi-Linguality):BGE-M3模型支持超过100种语言,具备强大的多语言和跨语言检索能力。
  • 多粒度性(Multi-Granularity):BGE-M3模型能够处理从短句到长达8192个token的长文档,满足不同长度文本的处理需求,上下文支持能力突出。

BGE – M3实际应用—示例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# @Time:2025/8/15 10:20
# @Author:jinglv
from FlagEmbedding import BGEM3FlagModel


def main():
model = BGEM3FlagModel("BAAI/bge-m3", use_fp16=False)
passage = ["猢狲施展烈焰拳,击退妖怪;随后开启金刚体,抵挡神兵攻击。"]

# 编码文本,获取稀疏嵌入和密集嵌入
passage_embeddings = model.encode(
passage,
return_sparse=True, # 返回稀疏嵌入
return_dense=True, # 返回密集嵌入
return_colbert_vecs=True # 返回多向量嵌入
)
# 分别提取稀疏嵌入、密集嵌入和多向量嵌入
dense_vecs = passage_embeddings["dense_vecs"]
sparse_vecs = passage_embeddings["lexical_weights"]
colbert_vecs = passage_embeddings["colbert_vecs"]
# 展示稀疏嵌入和密集嵌入的示例
print("密集嵌入维度:", dense_vecs[0].shape)
print("密集嵌入前10维:", dense_vecs[0][:10]) # 仅显示前10维

print("稀疏嵌入总长度:", len(sparse_vecs[0]))
print("稀疏嵌入前10个非零值:", list(sparse_vecs[0].items())[:10]) # 仅显示前10个非零值

print("多向量嵌入维度:", colbert_vecs[0].shape)
print("多向量嵌入前2个:", colbert_vecs[0][:2]) # 仅显示前2个多向量嵌入


if __name__ == '__main__':
main()

执行结果,输出内容

1
2
3
4
5
6
7
8
9
10
11
12
Fetching 30 files: 100%|██████████| 30/30 [03:36<00:00,  7.20s/it]
You're using a XLMRobertaTokenizerFast tokenizer. Please note that with a fast tokenizer, using the `__call__` method is faster than using a method to encode the text followed by a call to the `pad` method to get a padded encoding.
密集嵌入维度: (1024,)
密集嵌入前10维: [ 0.01156895 0.02439202 -0.02763914 -0.00984312 -0.04426081 -0.02911922
0.03953079 0.02165183 0.01074662 -0.03528604]
稀疏嵌入总长度: 23
稀疏嵌入前10个非零值: [('6', np.float32(0.07354035)), ('28323', np.float32(0.07607667)), ('8869', np.float32(0.15832472)), ('48124', np.float32(0.16404548)), ('213212', np.float32(0.20880702)), ('75133', np.float32(0.25368148)), ('4', np.float32(0.05709526)), ('31833', np.float32(0.11104022)), ('12461', np.float32(0.18773066)), ('101184', np.float32(0.18636808))]
多向量嵌入维度: (26, 1024)
多向量嵌入前2个: [[-0.05067743 0.00912711 -0.0287432 ... 0.0251329 0.05133016
-0.00171831]
[-0.03439403 -0.00773524 -0.01304412 ... 0.01616604 0.01929808
0.01006441]]
  • 典型输出维度:
    • 密集嵌入: 1024维向量(如[0.01, -0.82, 0.03,…])
    • 稀疏嵌入: 约30万维中几十个非零值(如{‘6’:0.073, ‘28323’:0.076})
    • 多向量嵌入: (token数, 1024)矩阵(如26个token生成26×1024矩阵)

核心内容总结

  • 混合检索优势:
    • 稀疏检索: 快速定位含关键词文档(如”烈焰拳”)
    • 密集检索: 按语义相似度排序(如”战斗激烈”场景)
    • 多向量检索: 精细匹配token级关系(重排序场景)
  • 典型应用场景:
    • 多语言场景: 密集检索主导跨语言匹配
    • 长文档场景: 稀疏检索捕捉关键信息
    • 高精度需求: 多向量检索提升排序质量