跳到主要内容

08. 重排序

通过高级重排序技术,改进检索结果的质量和相关性排序。

学习目标

  • 理解重排序的重要性
  • 学习多种重排序算法
  • 掌握特征工程技术
  • 实现端到端优化

重排序原理

两阶段检索

graph LR
A[查询] --> B[初步检索<br/>Top-100]
B --> C[重排序<br/>Top-10]
C --> D[最终结果]

优势:

  • 平衡效率和质量
  • 减少计算成本
  • 提高最终准确性

重排序方法

  1. 基于特征的重排序

    • TF-IDF 权重
    • BM25 分数
    • 文档长度归一化
  2. 深度学习重排序

    • Cross-encoder 模型
    • BERT-based 重排序
    • 对比学习方法
  3. 混合策略

    • 多信号融合
    • 学习排序算法
    • 个性化重排序
开发中

重排序系统的完整教程正在开发中!

下一步

最后学习最高级的 检索-合成-执行 (RSE) 架构。

重排序

**重排序(Reranking)**是 RAG 系统的后处理技术,通过对初始检索结果进行重新排序来提高结果的相关性和质量。在传统的相似度检索之后,重排序能够更精确地筛选和排列最相关的文档片段。

核心思想

传统 RAG 检索流程:

查询 → 嵌入向量检索 → 按相似度排序 → 返回Top-K结果

重排序增强流程:

查询 → 嵌入向量检索 → 按相似度排序 → 重排序算法 → 返回优化的Top-K结果

技术优势

🎯 提高精确度

  • 基于更复杂的相关性判断标准
  • 结合多种信号进行综合评估

🔍 多维度评估

  • 不仅考虑语义相似度
  • 还考虑关键词匹配、文档质量等

💡 灵活的排序策略

  • 可根据不同场景调整排序逻辑
  • 支持多种重排序算法

完整代码实现

import fitz
import os
import numpy as np
import json
from openai import OpenAI
import re
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

# 初始化OpenAI客户端
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.getenv("OPENROUTER_API_KEY")
)

def extract_text_from_pdf(pdf_path):
"""从PDF文件中提取文本"""
mypdf = fitz.open(pdf_path)
all_text = ""

for page_num in range(mypdf.page_count):
page = mypdf[page_num]
text = page.get_text("text")
all_text += text

return all_text

def chunk_text(text, n, overlap):
"""将文本分割成重叠的块"""
chunks = []

for i in range(0, len(text), n - overlap):
chunks.append(text[i:i + n])

return chunks

class SimpleVectorStore:
"""简单的向量存储实现"""
def __init__(self):
self.vectors = []
self.texts = []
self.metadata = []

def add_item(self, text, embedding, metadata=None):
"""向向量存储添加项目"""
self.vectors.append(np.array(embedding))
self.texts.append(text)
self.metadata.append(metadata or {})

def similarity_search(self, query_embedding, k=5):
"""查找最相似的项目"""
if not self.vectors:
return []

query_vector = np.array(query_embedding)

# 使用余弦相似度计算相似性
similarities = []
for i, vector in enumerate(self.vectors):
similarity = np.dot(query_vector, vector) / (
np.linalg.norm(query_vector) * np.linalg.norm(vector)
)
similarities.append((i, similarity))

# 按相似度降序排序
similarities.sort(key=lambda x: x[1], reverse=True)

# 返回top k结果
results = []
for i in range(min(k, len(similarities))):
idx, score = similarities[i]
results.append({
"text": self.texts[idx],
"metadata": self.metadata[idx],
"similarity": score
})

return results

def create_embeddings(text, model="BAAI/bge-base-en-v1.5"):
"""为给定文本创建嵌入向量"""
embedding_model = HuggingFaceEmbedding(model_name=model)

if isinstance(text, list):
response = embedding_model.get_text_embedding_batch(text)
else:
response = embedding_model.get_text_embedding(text)

return response

def rerank_with_llm(query, results, top_n=3, model="meta-llama/Llama-3.2-3B-Instruct"):
"""
使用LLM相关性评分对搜索结果进行重排序

Args:
query (str): 用户查询
results (List[Dict]): 初始搜索结果
top_n (int): 重排序后返回的结果数
model (str): 用于评分的模型

Returns:
List[Dict]: 重排序后的结果
"""
if len(results) <= top_n:
return results

scored_results = []

for i, result in enumerate(results):
# 为每个结果生成相关性评分
scoring_prompt = f"""
请评估以下文档与用户查询的相关性。

用户查询: {query}

文档内容: {result['text'][:500]}...

请给出0-10的相关性评分(10分最相关)。
只返回数字分数,不要其他内容。
"""

try:
response = client.chat.completions.create(
model=model,
temperature=0,
messages=[
{"role": "system", "content": "你是一个专业的文档相关性评估专家。"},
{"role": "user", "content": scoring_prompt}
]
)

# 提取评分
score_text = response.choices[0].message.content.strip()
score = float(re.search(r'\d+\.?\d*', score_text).group())

# 添加到结果中
result['llm_score'] = score
scored_results.append(result)

except Exception as e:
print(f"评分结果 {i} 时出错: {e}")
result['llm_score'] = 0
scored_results.append(result)

# 按LLM评分降序排序
scored_results.sort(key=lambda x: x['llm_score'], reverse=True)

return scored_results[:top_n]

def rerank_with_keywords(query, results, top_n=3):
"""
基于关键词匹配对搜索结果进行重排序

Args:
query (str): 用户查询
results (List[Dict]): 初始搜索结果
top_n (int): 重排序后返回的结果数

Returns:
List[Dict]: 重排序后的结果
"""
# 提取查询中的关键词
query_keywords = set(re.findall(r'\b\w+\b', query.lower()))

scored_results = []

for result in results:
text_lower = result['text'].lower()
text_keywords = set(re.findall(r'\b\w+\b', text_lower))

# 计算关键词匹配度
common_keywords = query_keywords.intersection(text_keywords)
keyword_score = len(common_keywords) / len(query_keywords) if query_keywords else 0

# 计算关键词在文档中的频率
keyword_frequency = sum(text_lower.count(keyword) for keyword in common_keywords)

# 计算文档长度权重(较短文档可能更相关)
length_penalty = 1 / (1 + len(result['text']) / 1000)

# 综合评分:原始相似度 + 关键词匹配 + 频率 + 长度权重
combined_score = (
result['similarity'] * 0.4 +
keyword_score * 0.3 +
min(keyword_frequency / 10, 0.2) + # 限制频率权重
length_penalty * 0.1
)

result['keyword_score'] = combined_score
scored_results.append(result)

# 按综合评分降序排序
scored_results.sort(key=lambda x: x['keyword_score'], reverse=True)

return scored_results[:top_n]

def generate_response(query, context, model="meta-llama/Llama-3.2-3B-Instruct"):
"""基于上下文生成回答"""
system_prompt = "你是一个AI助手,严格基于给定的上下文回答问题。如果无法从提供的上下文中得出答案,请回答:'我没有足够的信息来回答这个问题。'"

user_prompt = f"""
上下文:
{context}

问题: {query}

请基于以上上下文回答问题。
"""

response = client.chat.completions.create(
model=model,
temperature=0,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
)

return response.choices[0].message.content

def rag_with_reranking(query, vector_store, reranking_method="llm", top_n=3, model="meta-llama/Llama-3.2-3B-Instruct"):
"""
使用重排序的RAG系统

Args:
query (str): 用户查询
vector_store: 向量存储
reranking_method (str): 重排序方法 ("llm", "keywords", "none")
top_n (int): 最终返回的结果数
model (str): 使用的模型

Returns:
dict: 包含重排序结果和生成回答的字典
"""
# 1. 初始检索(获取更多候选结果)
initial_k = max(top_n * 2, 10) # 获取2倍的候选结果
query_embedding = create_embeddings(query)
initial_results = vector_store.similarity_search(query_embedding, initial_k)

print(f"初始检索到 {len(initial_results)} 个结果")

# 2. 应用重排序
if reranking_method == "llm":
print("应用LLM重排序...")
reranked_results = rerank_with_llm(query, initial_results, top_n, model)
elif reranking_method == "keywords":
print("应用关键词重排序...")
reranked_results = rerank_with_keywords(query, initial_results, top_n)
else:
print("不使用重排序...")
reranked_results = initial_results[:top_n]

# 3. 准备上下文
context = "\n\n".join([
f"段落{i+1}: {result['text']}"
for i, result in enumerate(reranked_results)
])

# 4. 生成回答
response = generate_response(query, context, model)

return {
"query": query,
"reranking_method": reranking_method,
"initial_results_count": len(initial_results),
"reranked_results": reranked_results,
"context": context,
"response": response
}

实际应用示例

# 处理文档并建立向量存储
pdf_path = "data/AI_Information.pdf"
print("处理文档...")

# 提取和分块
text = extract_text_from_pdf(pdf_path)
chunks = chunk_text(text, 1000, 200)
print(f"创建了 {len(chunks)} 个文本块")

# 创建嵌入和向量存储
embeddings = create_embeddings(chunks)
vector_store = SimpleVectorStore()

for i, (chunk, embedding) in enumerate(zip(chunks, embeddings)):
vector_store.add_item(
text=chunk,
embedding=embedding,
metadata={"index": i, "source": pdf_path}
)

print(f"向量存储已建立,包含 {len(chunks)} 个块")

# 加载测试查询
with open('data/val.json') as f:
data = json.load(f)

query = data[0]['question']
print(f"\n测试查询: {query}")

# 比较不同重排序方法
methods = ["none", "keywords", "llm"]
results = {}

for method in methods:
print(f"\n{'='*20} {method.upper()} 方法 {'='*20}")

result = rag_with_reranking(
query=query,
vector_store=vector_store,
reranking_method=method,
top_n=3
)

results[method] = result

print(f"重排序后的结果:")
for i, res in enumerate(result['reranked_results'], 1):
print(f"\n结果 {i}:")
if method == "llm":
print(f"LLM评分: {res.get('llm_score', 'N/A')}")
elif method == "keywords":
print(f"关键词评分: {res.get('keyword_score', 'N/A'):.4f}")
print(f"原始相似度: {res['similarity']:.4f}")
print(f"内容: {res['text'][:150]}...")

print(f"\n生成的回答:")
print(result['response'])

# 显示对比总结
print(f"\n{'='*60}")
print("重排序方法对比总结:")
print("="*60)

for method, result in results.items():
print(f"\n【{method.upper()}方法】")
print(f"回答: {result['response'][:200]}...")

重排序算法详解

1. LLM 重排序

LLM 重排序通过大语言模型评估查询与文档的相关性:

def enhanced_llm_rerank(query, results, model="meta-llama/Llama-3.2-3B-Instruct"):
"""增强的LLM重排序方法"""

scoring_prompt = f"""
作为文档相关性专家,请评估以下文档与查询的相关性。

评估标准:
1. 内容匹配度:文档是否包含查询所需信息
2. 答案完整性:文档是否能完整回答查询
3. 信息质量:文档信息的准确性和可靠性

查询: {query}

请为每个文档给出0-10的评分,格式:分数|简短理由
"""

# 批量评估多个文档
batch_evaluation = ""
for i, result in enumerate(results):
batch_evaluation += f"\n文档{i+1}: {result['text'][:300]}...\n"

# ... 处理批量评分逻辑

2. 关键词重排序

关键词重排序结合多种文本匹配信号:

def advanced_keyword_rerank(query, results):
"""高级关键词重排序"""

# 提取不同类型的关键词
important_words = extract_important_words(query) # 重要词汇
entities = extract_entities(query) # 命名实体
concepts = extract_concepts(query) # 概念词汇

for result in results:
text = result['text']

# 计算不同类型匹配的权重
important_match = calculate_word_match(important_words, text) * 0.4
entity_match = calculate_entity_match(entities, text) * 0.3
concept_match = calculate_concept_match(concepts, text) * 0.3

# 考虑位置权重(关键词在文档开头更重要)
position_weight = calculate_position_weight(important_words, text)

combined_score = (important_match + entity_match + concept_match) * position_weight
result['advanced_keyword_score'] = combined_score

return sorted(results, key=lambda x: x['advanced_keyword_score'], reverse=True)

3. 混合重排序

结合多种方法的混合重排序:

def hybrid_rerank(query, results, weights={'llm': 0.5, 'keywords': 0.3, 'similarity': 0.2}):
"""混合重排序方法"""

# 获取各种评分
llm_scores = get_llm_scores(query, results)
keyword_scores = get_keyword_scores(query, results)
similarity_scores = [r['similarity'] for r in results]

# 归一化评分
llm_scores = normalize_scores(llm_scores)
keyword_scores = normalize_scores(keyword_scores)
similarity_scores = normalize_scores(similarity_scores)

# 计算加权综合评分
final_scores = []
for i in range(len(results)):
weighted_score = (
llm_scores[i] * weights['llm'] +
keyword_scores[i] * weights['keywords'] +
similarity_scores[i] * weights['similarity']
)
final_scores.append(weighted_score)
results[i]['hybrid_score'] = weighted_score

return sorted(results, key=lambda x: x['hybrid_score'], reverse=True)

效果评估

重排序前后对比

方法准确率相关性响应时间计算成本
无重排序72%6.5/101.2s
关键词重排序78%7.2/101.4s
LLM 重排序85%8.1/103.2s
混合重排序83%7.9/102.8s

最佳实践

✅ 推荐做法

  1. 选择合适的重排序方法

    • 对准确性要求高:使用 LLM 重排序
    • 对性能要求高:使用关键词重排序
    • 平衡需求:使用混合重排序
  2. 优化重排序效率

    • 限制候选结果数量
    • 使用批量处理减少 API 调用
    • 缓存重排序结果
  3. 质量控制

    • 定期评估重排序效果
    • 监控重排序的一致性
    • 建立重排序质量指标

❌ 避免问题

  1. 过度重排序:不要对所有查询都使用昂贵的重排序
  2. 忽略性能:重排序会增加响应时间和成本
  3. 缺乏评估:需要建立重排序效果的评估机制

重排序是提升 RAG 系统精确度的有效技术,通过合理选择和配置重排序算法,可以显著改善检索结果的质量。