混合检索的加权策略:BM25稀疏向量与Embedding稠密向量的归一化融合
大家好,今天我们来深入探讨混合检索中的一个关键环节:加权策略,特别是针对BM25稀疏向量和Embedding稠密向量的归一化融合。混合检索旨在结合不同检索方法的优势,提升整体检索效果。而加权策略,则是将这些不同方法产生的排序结果有效融合的关键。
混合检索概述
在信息检索领域,我们通常会遇到两种主要的检索方法:
- 基于关键词的检索(Keyword-based Retrieval): 这种方法依赖于用户查询中的关键词与文档中词项的匹配程度。经典的算法包括BM25(Best Matching 25)。
- 基于语义的检索(Semantic-based Retrieval): 这种方法利用预训练语言模型(如BERT, Sentence-BERT等)将查询和文档编码成稠密向量,然后通过向量相似度(如余弦相似度)来衡量语义相关性。
这两种方法各有优缺点:
| 特性 | BM25 (稀疏向量) | Embedding (稠密向量) |
|---|---|---|
| 优点 | 速度快,可解释性强,对精确匹配敏感 | 能捕捉语义相关性,对同义词、近义词有较好的处理能力 |
| 缺点 | 无法处理语义相关性,对拼写错误、词形变化敏感 | 计算成本高,依赖高质量的Embedding模型,可解释性较差 |
| 向量表示 | 高维稀疏向量 (Term Frequency, Inverse Document Frequency) | 低维稠密向量 |
| 相似度计算 | 通常使用 BM25 公式 | 通常使用余弦相似度 |
混合检索的目标就是将这两种方法的优点结合起来,扬长避短,从而获得更好的检索效果。一个典型的混合检索流程如下:
- 查询处理: 接收用户查询。
- 稀疏向量检索: 使用 BM25 等算法,基于关键词进行检索,得到一个排序列表。
- 稠密向量检索: 使用 Embedding 模型将查询和文档编码成向量,计算相似度,得到另一个排序列表。
- 加权融合: 将两个排序列表根据一定的权重进行融合,生成最终的排序结果。
今天的重点就在于第4步:加权融合。
加权融合的挑战
直接将 BM25 的得分和 Embedding 的相似度进行加权平均通常效果不佳。原因在于:
- 得分范围不一致: BM25 得分和 Embedding 相似度的范围可能差异很大。例如,BM25 得分可能在 0 到 100 之间,而余弦相似度则在 -1 到 1 之间。
- 得分分布不一致: BM25 得分和 Embedding 相似度的分布也可能不同。例如,BM25 得分可能呈现长尾分布,而余弦相似度可能更集中在某个区间。
因此,我们需要对 BM25 得分和 Embedding 相似度进行归一化,使其具有可比性,然后再进行加权融合。
归一化方法
常用的归一化方法有以下几种:
-
Min-Max Scaling: 将得分线性缩放到 [0, 1] 区间。
def min_max_scaling(scores): min_score = min(scores) max_score = max(scores) if max_score == min_score: return [0.0] * len(scores) # 避免除以0 scaled_scores = [(score - min_score) / (max_score - min_score) for score in scores] return scaled_scores -
Z-Score Normalization (Standardization): 将得分转换为标准正态分布,均值为 0,标准差为 1。
import numpy as np def z_score_normalization(scores): mean = np.mean(scores) std = np.std(scores) if std == 0: return [0.0] * len(scores) # 避免除以0 normalized_scores = [(score - mean) / std for score in scores] return normalized_scores -
Sigmoid Scaling: 将得分映射到 [0, 1] 区间,并具有 S 形曲线的特征。
import numpy as np def sigmoid_scaling(scores): scaled_scores = [1 / (1 + np.exp(-score)) for score in scores] return scaled_scores -
Rank-Based Normalization: 基于得分的排名进行归一化。例如,可以将得分排名归一化到 [0, 1] 区间。
def rank_based_normalization(scores): ranked_scores = sorted(enumerate(scores), key=lambda x: x[1], reverse=True) normalized_scores = [0.0] * len(scores) for i, (index, score) in enumerate(ranked_scores): normalized_scores[index] = (len(scores) - i) / len(scores) # 将排名归一化到 [0, 1] return normalized_scores
选择哪种归一化方法取决于具体的应用场景和数据分布。Min-Max Scaling 简单易用,但对异常值敏感。Z-Score Normalization 对异常值不敏感,但可能将得分映射到负数。Sigmoid Scaling 可以突出高分,但对低分不敏感。Rank-Based Normalization 不受得分具体数值的影响,只关注排名,但可能会损失一些信息。
加权融合策略
在对 BM25 得分和 Embedding 相似度进行归一化之后,就可以进行加权融合了。最简单的加权融合方法是线性加权:
def linear_weighted_fusion(bm25_scores, embedding_scores, weight_bm25, weight_embedding):
"""
线性加权融合 BM25 得分和 Embedding 相似度。
Args:
bm25_scores: 归一化后的 BM25 得分列表。
embedding_scores: 归一化后的 Embedding 相似度列表。
weight_bm25: BM25 得分的权重。
weight_embedding: Embedding 相似度的权重。
Returns:
融合后的得分列表。
"""
if len(bm25_scores) != len(embedding_scores):
raise ValueError("BM25 scores and embedding scores must have the same length.")
fused_scores = [weight_bm25 * bm25_score + weight_embedding * embedding_score
for bm25_score, embedding_score in zip(bm25_scores, embedding_scores)]
return fused_scores
其中,weight_bm25 和 weight_embedding 分别是 BM25 得分和 Embedding 相似度的权重,且 weight_bm25 + weight_embedding = 1。
确定合适的权重是一个重要的任务。常用的方法包括:
- 手动调整: 根据经验或通过实验,手动调整权重。
- 学习权重: 使用机器学习算法(如 LambdaMART, RankNet 等)来学习最优的权重。
代码示例:一个完整的混合检索流程
下面是一个完整的混合检索流程的示例代码,包括 BM25 检索、Embedding 检索、归一化和加权融合:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
from sentence_transformers import SentenceTransformer
# 模拟文档数据
documents = [
"This is the first document about cats.",
"This document is about dogs.",
"The third document is about animals.",
"Another document about pets."
]
# 模拟用户查询
query = "What are good pets?"
# 1. BM25 检索
def bm25_retrieval(query, documents):
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(documents)
query_vector = vectorizer.transform([query])
bm25_scores = cosine_similarity(query_vector, tfidf_matrix).flatten() # 使用 TF-IDF 相似度作为 BM25 的近似
return bm25_scores
# 2. Embedding 检索
def embedding_retrieval(query, documents):
model = SentenceTransformer('all-MiniLM-L6-v2') # 选择一个合适的 Sentence Embedding 模型
query_embedding = model.encode(query)
document_embeddings = model.encode(documents)
embedding_scores = cosine_similarity(query_embedding.reshape(1, -1), document_embeddings).flatten()
return embedding_scores
# 3. 归一化
def normalize_scores(scores, method="min_max"):
if method == "min_max":
return min_max_scaling(scores)
elif method == "z_score":
return z_score_normalization(scores)
elif method == "sigmoid":
return sigmoid_scaling(scores)
elif method == "rank":
return rank_based_normalization(scores)
else:
raise ValueError("Invalid normalization method.")
# 4. 加权融合
def weighted_fusion(bm25_scores, embedding_scores, weight_bm25=0.5):
weight_embedding = 1 - weight_bm25
fused_scores = linear_weighted_fusion(bm25_scores, embedding_scores, weight_bm25, weight_embedding)
return fused_scores
# 主程序
if __name__ == "__main__":
bm25_scores = bm25_retrieval(query, documents)
embedding_scores = embedding_retrieval(query, documents)
# 归一化
bm25_scores_normalized = normalize_scores(bm25_scores, method="min_max")
embedding_scores_normalized = normalize_scores(embedding_scores, method="min_max")
# 加权融合
fused_scores = weighted_fusion(bm25_scores_normalized, embedding_scores_normalized, weight_bm25=0.6)
# 排序并输出结果
ranked_results = sorted(enumerate(fused_scores), key=lambda x: x[1], reverse=True)
print("Query:", query)
print("Results:")
for i, (index, score) in enumerate(ranked_results):
print(f"Rank {i+1}: Document {index+1}, Score: {score:.4f}, Content: {documents[index]}")
代码说明:
- BM25 检索: 使用
TfidfVectorizer来计算 TF-IDF 向量,并使用余弦相似度作为 BM25 的近似得分。 注意:这只是一个近似,真正的BM25实现涉及更复杂的公式。 在实际应用中,应该使用专门的 BM25 实现库,例如rank_bm25。 - Embedding 检索: 使用
SentenceTransformer库加载一个预训练的 Sentence Embedding 模型,将查询和文档编码成向量,并计算余弦相似度。 - 归一化: 使用
normalize_scores函数对 BM25 得分和 Embedding 相似度进行归一化。这里使用了 Min-Max Scaling 作为示例,可以根据需要选择其他归一化方法。 - 加权融合: 使用
weighted_fusion函数对归一化后的 BM25 得分和 Embedding 相似度进行加权融合。 - 排序和输出: 将融合后的得分进行排序,并输出结果。
实验和评估
为了评估混合检索的效果,我们需要进行实验。常用的评估指标包括:
- Precision@K: 返回结果中前 K 个文档的准确率。
- Recall@K: 返回结果中前 K 个文档的召回率。
- Mean Average Precision (MAP): 所有查询的平均准确率的均值。
- Normalized Discounted Cumulative Gain (NDCG): 考虑排序位置的评估指标。
在实验中,我们需要比较不同加权策略的效果,例如:
- 不同的归一化方法。
- 不同的权重。
- 不同的 Embedding 模型。
通过实验,我们可以找到最优的加权策略,从而提升混合检索的效果。
总结
混合检索通过结合基于关键词和基于语义的检索方法,能够有效地提升检索效果。加权融合是混合检索中的一个关键环节,需要对 BM25 得分和 Embedding 相似度进行归一化,并选择合适的权重。常用的归一化方法包括 Min-Max Scaling、Z-Score Normalization、Sigmoid Scaling 和 Rank-Based Normalization。常用的加权融合方法是线性加权,可以通过手动调整或机器学习算法来学习最优的权重。 通过实验和评估,我们可以找到最优的加权策略,从而获得更好的检索效果。
一些思考与未来方向
- 动态权重调整: 可以考虑根据查询的特性动态调整权重。 例如,对于包含明确关键词的查询,可以增加 BM25 的权重;对于语义模糊的查询,可以增加 Embedding 的权重。
- 更复杂的融合模型: 可以使用更复杂的模型,例如神经网络,来学习 BM25 和 Embedding 的非线性融合方式。
- Query Expansion: 在混合检索中,可以结合 Query Expansion 技术,例如使用同义词或相关词来扩展查询,从而提升检索效果。
- 负样本挖掘: 在学习权重时,可以使用负样本挖掘技术,找到更难区分的负样本,从而提升模型的泛化能力。
- 冷启动问题: 当新文档没有embedding时,如何进行有效的混合检索,也是一个值得研究的问题。可以考虑使用知识蒸馏等技术,从现有embedding模型中迁移知识。