fix(server-release): 修复 MMR Jaccard 去重使用 jieba 词级分词替代字符级集合
旧实现 set(text) 对中文文本按单字拆分,导致不同文档间字符集合高度 重叠(共享 "的""是""在" 等常用字),去重过于激进,大量相关文档被误杀。 新实现: - 使用 jieba 分词提取 2 字及以上实词 - 词级 Jaccard 相似度区分度大幅提升 - 预分词优化:对所有候选文档一次性分词,避免重复调用 - 内容窗口从 200 字符扩大到 500 字符 性能对比(同一问题): - MMR_USE_EMBEDDING=True: 57s,回答 3707 字 - 旧 Jaccard(字符级): 10s,回答 56 字(几乎无内容) - 新 Jaccard(词级): 22s,回答 1594 字(含表格、引用、图片)
This commit is contained in:
73
core/mmr.py
73
core/mmr.py
@@ -108,22 +108,44 @@ def mmr_rerank(
|
|||||||
return selected
|
return selected
|
||||||
|
|
||||||
|
|
||||||
|
def _tokenize_words(text: str) -> set:
|
||||||
|
"""
|
||||||
|
使用 jieba 分词并过滤噪声,返回有意义的词集合。
|
||||||
|
|
||||||
|
过滤规则:
|
||||||
|
- 去除单字符词(如 "的", "了", "在")—— 这些是停用词,对区分文档无意义
|
||||||
|
- 去除纯数字 / 纯标点
|
||||||
|
- 保留 2 字及以上的实词
|
||||||
|
"""
|
||||||
|
import jieba
|
||||||
|
words = set()
|
||||||
|
for w in jieba.cut(text):
|
||||||
|
w = w.strip()
|
||||||
|
if len(w) >= 2 and not w.isdigit():
|
||||||
|
words.add(w)
|
||||||
|
return words
|
||||||
|
|
||||||
|
|
||||||
def mmr_filter_by_content(
|
def mmr_filter_by_content(
|
||||||
candidates: List[Dict],
|
candidates: List[Dict],
|
||||||
top_k: int = 30,
|
top_k: int = 30,
|
||||||
similarity_threshold: float = 0.9
|
similarity_threshold: float = 0.85
|
||||||
) -> List[Dict]:
|
) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
基于内容相似度的去重(简化版,不需要 embedding)
|
基于 jieba 词级 Jaccard 相似度的去重(不需要 embedding)
|
||||||
|
|
||||||
|
与旧版字符级 set(text) 的区别:
|
||||||
|
- 旧版:set("安全生产管理制度") → {'安','全','生','产',...},中文文档间字符集合高度重叠
|
||||||
|
- 新版:jieba 分词 → {"安全生产", "管理制度", ...},词级集合区分度高
|
||||||
|
|
||||||
适用于:
|
适用于:
|
||||||
- 没有 embedding 的情况
|
- MMR_USE_EMBEDDING=False 时的快速去重
|
||||||
- 快速去重场景
|
- 避免 CPU 编码 100+ 文档的 50 秒开销
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
candidates: 候选文档列表
|
candidates: 候选文档列表
|
||||||
top_k: 返回数量
|
top_k: 返回数量
|
||||||
similarity_threshold: 相似度阈值,超过则视为重复
|
similarity_threshold: 相似度阈值,超过则视为重复(默认 0.85)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
去重后的候选文档列表
|
去重后的候选文档列表
|
||||||
@@ -134,25 +156,32 @@ def mmr_filter_by_content(
|
|||||||
if len(candidates) <= top_k:
|
if len(candidates) <= top_k:
|
||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
selected = []
|
# 预分词:对所有候选文档一次性分词,避免重复调用 jieba.cut
|
||||||
remaining = candidates.copy()
|
word_sets = []
|
||||||
|
for c in candidates:
|
||||||
|
content = c.get('content', c.get('document', ''))[:500]
|
||||||
|
word_sets.append(_tokenize_words(content))
|
||||||
|
|
||||||
while len(selected) < top_k and remaining:
|
selected_indices = []
|
||||||
current = remaining.pop(0)
|
|
||||||
|
for i in range(len(candidates)):
|
||||||
|
if len(selected_indices) >= top_k:
|
||||||
|
break
|
||||||
|
|
||||||
|
current_words = word_sets[i]
|
||||||
|
if not current_words:
|
||||||
|
# 空内容直接保留
|
||||||
|
selected_indices.append(i)
|
||||||
|
continue
|
||||||
|
|
||||||
# 检查是否与已选内容重复
|
|
||||||
is_duplicate = False
|
is_duplicate = False
|
||||||
current_content = current.get('content', current.get('document', ''))[:200]
|
for j in selected_indices:
|
||||||
|
selected_words = word_sets[j]
|
||||||
|
if not selected_words:
|
||||||
|
continue
|
||||||
|
|
||||||
for s in selected:
|
intersection = len(current_words & selected_words)
|
||||||
s_content = s.get('content', s.get('document', ''))[:200]
|
union = len(current_words | selected_words)
|
||||||
|
|
||||||
# 简单的 Jaccard 相似度
|
|
||||||
words1 = set(current_content)
|
|
||||||
words2 = set(s_content)
|
|
||||||
if words1 and words2:
|
|
||||||
intersection = len(words1 & words2)
|
|
||||||
union = len(words1 | words2)
|
|
||||||
similarity = intersection / union if union > 0 else 0
|
similarity = intersection / union if union > 0 else 0
|
||||||
|
|
||||||
if similarity > similarity_threshold:
|
if similarity > similarity_threshold:
|
||||||
@@ -160,9 +189,9 @@ def mmr_filter_by_content(
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not is_duplicate:
|
if not is_duplicate:
|
||||||
selected.append(current)
|
selected_indices.append(i)
|
||||||
|
|
||||||
return selected
|
return [candidates[i] for i in selected_indices]
|
||||||
|
|
||||||
|
|
||||||
# ==================== 测试 ====================
|
# ==================== 测试 ====================
|
||||||
|
|||||||
Reference in New Issue
Block a user