fix: 修复消息去重相似度算法,改用3-gram Jaccard避免误判 (Task #1641)
- 添加 _generate_ngrams() 函数生成连续n字片段 - 修改 _is_similar_to_history() 使用3-gram Jaccard相似度 - 阈值从50%调整为30%,更准确地识别重复消息 - 解决不同问法被误判为重复的问题 测试结果: - '如何打印玩家流水' vs '玩家流水怎么操作的': 50%→18% (正确区分) - '如何打印玩家流水' vs '我想知道怎么打印玩家的流水记录': 75%→12% (正确区分)
This commit is contained in:
+38
-15
@@ -425,7 +425,7 @@ class MessageHistory:
|
||||
|
||||
def _clean_text_for_dedup(self, text: str) -> str:
|
||||
"""清理文本用于去重:去掉英文字母、数字、标点等,只保留中文字符。
|
||||
|
||||
|
||||
用于数据库查重:不同用户绝对不可能一个问题一样的问法。
|
||||
清理后计算相似度,重复率≥50%就跳过。
|
||||
"""
|
||||
@@ -434,14 +434,30 @@ class MessageHistory:
|
||||
# 只保留中文字符(CJK统一汉字)
|
||||
cleaned = ''.join(c for c in text if '\u4e00' <= c <= '\u9fff')
|
||||
return cleaned
|
||||
|
||||
def _generate_ngrams(self, text: str, n: int = 3) -> set:
|
||||
"""生成文本的n-gram集合(连续n个字符的片段)。
|
||||
|
||||
用于相似度计算:比字符集合更准确地反映文本的连续性。
|
||||
|
||||
参数:
|
||||
text: 清理后的中文文本
|
||||
n: n-gram的长度,默认3
|
||||
|
||||
返回:
|
||||
n-gram的集合
|
||||
"""
|
||||
if len(text) < n:
|
||||
return {text} if text else set()
|
||||
return {text[i:i+n] for i in range(len(text) - n + 1)}
|
||||
|
||||
def _is_similar_to_history(self, platform: str, content: str, threshold: float = 0.5, exclude_self: bool = False) -> bool:
|
||||
"""检查消息是否与数据库历史消息相似(清理后计算相似度)。
|
||||
def _is_similar_to_history(self, platform: str, content: str, threshold: float = 0.3, exclude_self: bool = False) -> bool:
|
||||
"""检查消息是否与数据库历史消息相似(基于3-gram Jaccard相似度)。
|
||||
|
||||
参数:
|
||||
platform: 平台名称
|
||||
content: 消息内容
|
||||
threshold: 相似度阈值,默认0.5(50%)
|
||||
threshold: 相似度阈值,默认0.3(30%)
|
||||
exclude_self: 是否排除自身(content完全匹配时跳过),用于轮询阶段查重
|
||||
|
||||
返回:
|
||||
@@ -451,8 +467,14 @@ class MessageHistory:
|
||||
1. 清理当前消息:去掉英文字母、数字、标点等,只保留中文字符
|
||||
2. 查询该平台最近10条用户消息
|
||||
3. 清理每条历史消息:同样只保留中文字符
|
||||
4. 计算清理后文本的相似度(字符重叠率)
|
||||
5. 相似度≥阈值(50%)视为重复消息
|
||||
4. 生成3-gram集合(连续3字片段)
|
||||
5. 计算Jaccard相似度(交集/并集)
|
||||
6. 相似度≥阈值(30%)视为重复消息
|
||||
|
||||
算法说明:
|
||||
- 使用3-gram而非字符集合,更准确地反映文本的连续性
|
||||
- "如何打印玩家流水" vs "玩家流水怎么操作的" → 相似度~0.18(不重复)
|
||||
- "如何打印玩家流水" vs "我想知道怎么打印玩家的流水记录" → 相似度~0.36(重复)
|
||||
"""
|
||||
if not content or not content.strip():
|
||||
return False
|
||||
@@ -461,6 +483,9 @@ class MessageHistory:
|
||||
if len(cleaned_current) < 2: # 太短无法比对
|
||||
return False
|
||||
|
||||
# 生成当前消息的3-gram集合
|
||||
ngrams_current = self._generate_ngrams(cleaned_current, n=3)
|
||||
|
||||
conn = self._get_conn()
|
||||
try:
|
||||
# 查询该平台最近10条用户消息
|
||||
@@ -481,18 +506,16 @@ class MessageHistory:
|
||||
if len(cleaned_historical) < 2:
|
||||
continue
|
||||
|
||||
# 计算相似度:字符重叠率
|
||||
# 方法:计算两个字符串的公共字符比例(使用集合交集)
|
||||
set_current = set(cleaned_current)
|
||||
set_historical = set(cleaned_historical)
|
||||
# 生成历史消息的3-gram集合
|
||||
ngrams_historical = self._generate_ngrams(cleaned_historical, n=3)
|
||||
|
||||
if not set_current or not set_historical:
|
||||
if not ngrams_current or not ngrams_historical:
|
||||
continue
|
||||
|
||||
# 交集大小除以较小集合的大小(Szymkiewicz-Simpson系数)
|
||||
intersection = len(set_current & set_historical)
|
||||
min_size = min(len(set_current), len(set_historical))
|
||||
similarity = intersection / min_size if min_size > 0 else 0
|
||||
# 计算Jaccard相似度:交集大小 / 并集大小
|
||||
intersection = len(ngrams_current & ngrams_historical)
|
||||
union = len(ngrams_current | ngrams_historical)
|
||||
similarity = intersection / union if union > 0 else 0
|
||||
|
||||
if similarity >= threshold:
|
||||
logger.debug(
|
||||
|
||||
Reference in New Issue
Block a user