关键词提取评测:算法说重要,不代表用户觉得重要
关键词提取评测算法说重要不代表用户觉得重要一、TF-IDF 给了点击最高权重但用户搜索的是退货流程在做搜索相关性评测时我遇到一个经典的算法-用户错位问题一篇关于电商退货流程的帮助文档TF-IDF 给出的 Top-3 关键词是点击、订单、页面——三个操作步骤词汇。但用户搜索退货流程时直接跳过了这篇文章因为标题和关键词里缺了退货二字。算法认为重要的词高频但无感和用户认为重要的词核心但低频经常不是同一组词。这种错位是关键词提取在真实场景中的根本挑战。这个问题的本质是TF-IDF 的重要定义基于统计学TF × IDF而用户的重要定义基于任务需求——两者不在同一个语义空间里。二、统计显著 ≠ 语义显著为什么 TF-IDF 和 TextRank 都会犯错TF-IDF 的假设是一个词在文档中频繁出现、但在整个文档集中很少出现就是关键词。这个假设在 90% 的通用场景下是成立的。但剩下的 10%——那些核心语义词恰好被淹没在大量描述性词汇中的场景——就是算法的盲区。TextRank 试图通过图算法捕捉词与词之间的共现关系但它的基础仍然是词在文档中的出现频率。如果一个核心词只出现了两次但承载了整篇文章的语义骨架TF-IDF 和 TextRank 都会错过它。graph TD A[输入文档] -- B[预处理: 分词 去停用词] B -- C{关键词提取算法} C -- D[TF-IDF: 统计频率] C -- E[TextRank: 图排序] C -- F[KeyBERT: 嵌入相似度] C -- G[LLM: 语义理解] D -- H[高频词优先] E -- I[共现关系优先] F -- J[语义相似度优先] G -- K[全局理解优先] H -- L{对比人工标注} I -- L J -- L K -- L L -- M[F1 分数] L -- N[用户满意度] M -- O[算法得分 vs 用户评分] N -- O O -- P[错位: 算法高分 ≠ 用户满意]见证奇迹的时刻同一篇文档四种算法给出的 Top-5 关键词只有 1 个重叠——而重叠的这个词恰好不是用户认为最重要的那个。三、多算法融合与人工评价框架以下实现了一个多算法融合的关键词提取系统支持自动评测和人工评价反馈from typing import List, Dict, Tuple, Optional from dataclasses import dataclass import numpy as np from collections import Counter import jieba import jieba.analyse from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity from sentence_transformers import SentenceTransformer dataclass class KeywordResult: word: str score: float source: str # tfidf, textrank, keybert, llm class MultiStrategyKeywordExtractor: 多策略关键词提取器 —— 取各家之长 def __init__(self): # KeyBERT 需要嵌入模型 self.embedder SentenceTransformer(paraphrase-multilingual-MiniLM-L12-v2) # TF-IDF 向量化器 self.tfidf_vectorizer None def extract_tfidf( self, text: str, top_k: int 10 ) - List[KeywordResult]: TF-IDF 提取 设计原因最经典的关键词提取算法。 优点简单、快、有统计理论支撑。 缺点只统计词频完全不理解语义。 # 使用 jieba 的 TF-IDF 实现 keywords jieba.analyse.extract_tags( text, topKtop_k, withWeightTrue ) return [ KeywordResult(wordword, scoreweight, sourcetfidf) for word, weight in keywords ] def extract_textrank( self, text: str, top_k: int 10 ) - List[KeywordResult]: TextRank 提取 设计原因基于图的排序算法。 优点考虑词之间的共现关系。 缺点仍然依赖词频对低频核心词不敏感。 keywords jieba.analyse.textrank( text, topKtop_k, withWeightTrue ) return [ KeywordResult(wordword, scoreweight, sourcetextrank) for word, weight in keywords ] def extract_keybert( self, text: str, top_k: int 10 ) - List[KeywordResult]: KeyBERT 提取 —— 基于嵌入相似度 设计原因将文档和候选词都编码为嵌入向量 通过余弦相似度排序。相比 TF-IDF 能更好地捕捉语义上重要但频率不高的词。 # 分词 words list(jieba.cut(text)) # 去停用词和单字词 candidate_words [ w for w in words if len(w) 1 and not w.isspace() ] if not candidate_words: return [] # 文档嵌入 doc_embedding self.embedder.encode(text) # 候选词嵌入 相似度计算 word_scores [] for word in list(set(candidate_words))[:100]: # 限制候选词数量 word_embedding self.embedder.encode(word) # 设计原因同时考虑 文档-词相似度 和 词-词去重 sim float(cosine_similarity( [doc_embedding], [word_embedding] )[0][0]) word_scores.append((word, sim)) word_scores.sort(keylambda x: x[1], reverseTrue) return [ KeywordResult(wordword, scorescore, sourcekeybert) for word, score in word_scores[:top_k] ] def ensemble_extract( self, text: str, top_k: int 10, weights: Dict[str, float] None, ) - List[KeywordResult]: 多算法融合提取 设计原因单算法有盲区多算法融合能互补。 融合策略各算法独立提取 → 归一化分数 → 加权合并。 权重设计 - TF-IDF 0.25: 统计基础 - TextRank 0.15: 共现关系 - KeyBERT 0.60: 语义相关权重最高因为与用户感知最一致 if weights is None: weights {tfidf: 0.25, textrank: 0.15, keybert: 0.60} # 各算法独立提取 tfidf_results self.extract_tfidf(text, top_ktop_k * 2) textrank_results self.extract_textrank(text, top_ktop_k * 2) keybert_results self.extract_keybert(text, top_ktop_k * 2) # 收集所有候选词及其各算法得分 all_words: Dict[str, Dict[str, float]] {} for result in tfidf_results: all_words.setdefault(result.word, {})[tfidf] result.score for result in textrank_results: all_words.setdefault(result.word, {})[textrank] result.score for result in keybert_results: all_words.setdefault(result.word, {})[keybert] result.score # 归一化各算法得分 def normalize(scores_dict: Dict[str, float]) - Dict[str, float]: if not scores_dict: return {} max_val max(scores_dict.values()) return {k: v / max_val for k, v in scores_dict.items()} # 收集各算法的原始得分进行归一化 tfidf_raw {w: all_words[w].get(tfidf, 0) for w in all_words} textrank_raw {w: all_words[w].get(textrank, 0) for w in all_words} keybert_raw {w: all_words[w].get(keybert, 0) for w in all_words} tfidf_norm normalize(tfidf_raw) textrank_norm normalize(textrank_raw) keybert_norm normalize(keybert_raw) # 加权合并 final_scores {} for word in all_words: score ( weights[tfidf] * tfidf_norm.get(word, 0) weights[textrank] * textrank_norm.get(word, 0) weights[keybert] * keybert_norm.get(word, 0) ) final_scores[word] score # 排序取 Top-K sorted_words sorted( final_scores.items(), keylambda x: x[1], reverseTrue ) return [ KeywordResult(wordword, scorescore, sourceensemble) for word, score in sorted_words[:top_k] ] class KeywordEvaluator: 关键词提取评测器 —— 算法得分 vs 人工标注 staticmethod def precision_recall_f1( predicted: List[str], ground_truth: List[str] ) - Dict[str, float]: 计算精准率、召回率和 F1 pred_set set(predicted) gt_set set(ground_truth) tp len(pred_set gt_set) precision tp / len(pred_set) if pred_set else 0 recall tp / len(gt_set) if gt_set else 0 f1 ( 2 * precision * recall / (precision recall) if (precision recall) 0 else 0 ) return { precision: round(precision, 3), recall: round(recall, 3), f1: round(f1, 3), tp: tp, pred_count: len(pred_set), gt_count: len(gt_set), } staticmethod def rank_correlation( predicted: List[str], ground_truth: List[str] ) - float: 计算排序相关性 —— 提取顺序是否与人工排序一致 设计原因F1 只看关键词集合的重叠 但关键词的排序也很重要。 这里用简化版的 Kendalls tau 来评估排序一致性。 # 为预测和标注的关键词分配排名 pred_rank {word: i for i, word in enumerate(predicted)} gt_rank {word: i for i, word in enumerate(ground_truth)} common_words set(predicted) set(ground_truth) if len(common_words) 2: return 0.0 concordant 0 discordant 0 common_list list(common_words) for i in range(len(common_list)): for j in range(i 1, len(common_list)): w1, w2 common_list[i], common_list[j] pred_order pred_rank[w1] pred_rank[w2] gt_order gt_rank[w1] gt_rank[w2] if pred_order gt_order: concordant 1 else: discordant 1 total concordant discordant return (concordant - discordant) / total if total 0 else 0.0四、算法评分高 ≠ 用户满意度高评价方式测量什么局限性F1vs 人工标注关键词重叠率假设人工标注是真理NDCG排序质量排序相关性对低频词不敏感用户点击率实际用户行为受位置、UI 等因素影响用户满意度评分主观感受成本高、样本小最终的检验标准不是算法对你的测试集有多高的 F1而是用户能不能通过你提取的关键词快速找到他们想要的信息。这是关键词提取的终极评测指标——也是最难量化的指标。但在真实系统中用户满意度数据的获取周期通常按周甚至月计算而算法迭代需要天级别的反馈。一个折中方案是建立人工标注的金标准测试集作为日常迭代的代理指标每月再用线上用户行为数据验证一次方向是否正确。五、总结关键词提取的核心矛盾是统计显著性和语义显著性之间的鸿沟。核心结论TF-IDF 和 TextRank 依赖词频统计核心语义词可能因低频被错过KeyBERT 通过嵌入相似度部分弥补了语义盲区多算法融合TF-IDF TextRank KeyBERT比任何单一算法更稳健评测应同时包含 F1集合重叠和排序相关性肯德尔 tau最终的评测标准是用户满意度而非算法对人工标注的拟合度工程建议在项目中同时跑多个关键词提取算法对比输出结果。当不同算法给出的关键词列表差异很大时重叠率 30%说明文档的关键词信息不够明确需要考虑人工干预。