关键词冗余在搜索引擎优化、内容标签生成和文本摘要任务中非常常见。拿到一段文本后先用分词工具提取候选词,结果列表中容易混入大量同义词、近义词、不同词形以及无区分度的高频词。比如一篇讲推荐系统的文章,可能同时出现推荐系统、推荐算法、协同过滤、个性化推荐、商品推荐、内容推荐等十几个表达,而真正需要保留的核心词只有两三个。冗余词不仅让标签列表变得臃肿,还会稀释关键词权重,降低后续排序或匹配模型的准确率。要解决这个问题,需要从数量限制和重要性排序两个维度同时入手,而不是简单去重或按出现次数截断。

本文会从一个实际的候选词列表出发,分析冗余产生的原因,讨论如何设置关键词数量上限,并比较几种重要性排序算法。最终给出一个可以直接复用的筛选流程,帮助你在不丢失核心语义的前提下,把关键词列表压缩到合理规模。
关键词冗余从哪里来
关键词冗余最直接的来源是分词粒度不一致。以中文为例,jieba分词在默认模式下会把复合词拆成更小的单元,而在添加自定义词典后又会保留完整领域词。这导致同一文本在不同配置下生成不同粒度的词。例如自然语言处理可能被拆成自然语言和处理,也可能整体保留。如果把不同分词结果合并,就会产生重复语义的候选词。另一个来源是同义词与近义词,比如准确率和精度、向量和嵌入在上下文中可能指代同一概念,但字符串层面完全不重复,简单的set去重无法合并。
第三类冗余来自高频但低区分度的词。像方法、系统、数据、分析这类词几乎在所有技术文章里都会出现,它们虽然出现次数高,却不能代表文章主题。如果只按词频排序,这些词很容易挤占核心关键词的位置。第四类冗余来自大小写、单复数和词形变化,在英文场景尤其明显。例如Model和model、algorithm和algorithms如果未做归一化,会以不同字符串形式进入候选列表。
冗余关键词的危害不止是列表看起来不干净。在搜索引擎场景中,过多关键词会让页面核心主题被稀释;在推荐系统里,标签之间的语义重叠会影响相似度计算的稳定性;在自动生成摘要时,冗余词还会误导模型把注意力放在边缘信息上。因此,控制关键词数量并进行重要性排序是关键词后处理中不可省略的一步。
关键词数量限制应该如何设定
数量限制不是随便定一个固定数字。如果把所有文章都截断到前5个关键词,可能对短文本太多、对长文本太少。更合理的做法是让数量根据内容长度、主题分布和业务需求动态调整。第一种策略是固定上限,例如只保留前10个关键词。这种方式实现简单,适合对输出格式有严格要求的场景,但会牺牲长文本的覆盖度。
第二种策略是基于重要性累计占比。先对所有候选词按重要性分数从高到低排序,然后计算累计占比。当累计占比超过一个阈值,比如0.8,就停止保留后续词。这样短文本可能只输出3个词,长文本则可能输出15个词。代码实现如下:
def limit_by_cumulative_ratio(sorted_keywords, scores, threshold=0.8):
total = sum(scores)
if total == 0:
return []
cumulative = 0.0
selected = []
for kw, score in zip(sorted_keywords, scores):
cumulative += score
selected.append(kw)
if cumulative / total >= threshold:
break
return selected
第三种策略是结合业务指标,比如关键词数量不能超过接口字段长度限制,或者人工审核时不超过一屏。这个约束通常作为兜底条件,在前两种策略之后再做一次硬截断。另外,数量限制还可以和关键词覆盖的主题数量挂钩。如果一篇文章包含三个子主题,每个子主题至少保留一个代表词,那么最终数量至少为3。这样做既能控制总量,又能避免某个主题被完全忽略。
实际工程中,我一般先用累计占比自动计算一个基础数量,再取这个数量与业务上限的较小值。例如基础数量计算出12个,但接口只允许8个,就按8个输出。通过这种双层限制,关键词列表在大多数情况下都能保持稳定。
重要性排序的常用算法
关键词排序的核心是给每个候选词打一个分数,分数越高越能代表文本主题。最简单的分数是词频,但前面提到高频低区分度词会污染排序,因此需要更复杂的加权。TF-IDF是最经典的方案,它用词频和逆文档频率相乘,降低常见词的权重。具体来说,一个词在某篇文档中出现次数越多越好,但在整个语料库中出现越少越有区分度。如果语料库规模有限,可以用平滑公式避免除零。
TextRank则基于图模型,把词看作节点,词与词在窗口内的共现关系看作边。通过类似PageRank的迭代,节点得分会向中心词集中。与TF-IDF相比,TextRank不依赖外部语料库,更适合单篇文档的关键词提取。它的缺点是计算量相对较大,而且对停用词和低频词敏感。
BM25常被用在文档检索中,它改进TF-IDF中的词频饱和问题。当词频超过一定次数后,BM25的增长会放缓,避免某个词因为重复出现而得分过高。词向量加权是近年来的做法,先计算候选词与文档主题向量的余弦相似度,再结合词频做线性加权。这种方式可以捕捉语义相近的词,但需要预训练向量,部署成本较高。
下面是一个结合TF-IDF和TextRank思路的简化实现,使用jieba分词和手动计算词频、逆文档频率:
import jieba
from collections import Counter
def extract_keywords_tfidf(text, corpus_docs, top_k=8):
words = [w for w in jieba.cut(text) if len(w) > 1]
tf = Counter(words)
total = sum(tf.values())
doc_count = len(corpus_docs)
scores = {}
for word, freq in tf.items():
df = sum(1 for doc in corpus_docs if word in doc)
idf = 1.0 + (doc_count + 1) / (df + 1)
scores[word] = (freq / total) * idf
sorted_items = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return [word for word, score in sorted_items[:top_k]]
选择哪种算法取决于你的使用场景。如果有大规模语料,优先使用TF-IDF或BM25;如果只有单篇文本,TextRank更合适;如果需要语义层面的去重,可以在排序后加入词向量相似度过滤,把与已选关键词高度相似的候选词剔除。
可落地的关键词筛选流程
把前面讨论的内容串起来,一个完整的关键词筛选流程可以分成五步。第一步是候选词生成,用分词工具切分文本,过滤停用词、纯数字和长度小于2的词。第二步是候选词清洗,统一大小写、做词形还原、合并同义词。同义词合并可以基于规则词典,也可以基于词向量相似度,如果保守一些,先只做大小写和单复数归一化。第三步是重要性打分,根据语料条件选择TF-IDF或TextRank,得到每个候选词的分数。
第四步是冗余检测,在排序后的列表里从高到低遍历,如果当前词与已经选中的词相似度超过阈值,就跳过。相似度可以用编辑距离、Jaccard系数或词向量余弦值。第五步是数量限制,根据累计重要性占比自动截断,再叠加业务上限。下面是完整流程的伪代码:
def filter_keywords(candidates, scores, max_count=10, sim_threshold=0.85):
sorted_pairs = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
selected = []
for word, score in sorted_pairs:
if len(selected) >= max_count:
break
if selected and any(similarity(word, sel) > sim_threshold for sel in selected):
continue
selected.append(word)
return selected
def similarity(a, b):
# 这里可以用词向量余弦相似度或编辑距离
a_set = set(a)
b_set = set(b)
inter = len(a_set & b_set)
union = len(a_set | b_set)
return inter / union if union else 0.0
这个流程在实际项目中的调参重点是两个阈值:相似度阈值和累计占比阈值。相似度阈值设得太高,冗余词可能残留;设得太低,又可能把真正有价值的近义词误删。累计占比阈值通常从0.75到0.9之间开始尝试,观察输出关键词的可读性和主题覆盖度。如果输出结果偏少,可以降低阈值;如果偏多,则提高阈值或降低业务上限。
另外,评估关键词筛选效果不能只看列表长度,还要看核心语义是否保留。可以人工抽检一批文章,检查排序靠前的关键词是否能准确反映主题。也可以在有标注数据的场景下,用准确率、召回率和F1值来评估。关键词冗余问题的根本目标不是删词,而是让最终保留下来的少量关键词最大限度地代表原文。因此数量限制和重要性排序必须配合使用,单独做任何一步都难以获得理想效果。