资讯详情

中文关键词抽取:TF-IDF、TextRank与KeyBERT三种Python方案选型与实战

📅 2026/10/25 15:37:15 | 华诺云谱 👁 阅读
中文关键词抽取:TF-IDF、TextRank与KeyBERT三种Python方案选型与实战
简介这份资源面向Python初学者、NLP入门者及需要完成课程设计的学生系统讲解中文文本关键词抽取的三种经典方法TF-IDF、TextRank与Word2Vec词向量聚类。内容从原理、流程到代码实现逐层展开并配有课程论文与实验截图帮助读者理解各方法的适用场景与差异。压缩包共31个文件约1.78MB包含4个py源码脚本、14个csv数据与结果文件、8张png实验截图、2个txt词表与停用词文件以及docx论文、md说明和license结构完整便于直接运行与复现。资源还讨论了若干可改进细节如引入专业语料训练Word2Vec、为标题词语赋予初始权重、按分类数设置KMeans的n_clusters参数、过滤高频通用词等为后续优化提供思路。目前已有2116人学习下载适合作为课程设计参考或关键词抽取入门实践素材。1. 中文关键词抽取三种Python方案到底该选哪一个做中文文本处理的人迟早会撞上同一个需求给你一篇几千字的文章、一批用户评论或者一份行业报告让你把里面最核心的几个词拎出来。这件事听起来简单真动手才发现坑不少——中文没有天然空格分词同一个意思可以用完全不同的词表达而且重要本身就没有统一定义。基于Python实现中文文本关键词抽取市面上能落地的方案主要就三条路统计法TF-IDF、图模型法TextRank、以及预训练语义法KeyBERT这类。这三种方法背后是三种完全不同的重要性哲学选错了不是效果差一点而是方向性错误。这篇文章面向的是需要把关键词抽取真正跑进业务里的工程师——做舆情监控的、做内容标签的、做搜索召回的、做知识库自动归档的。我会把三种方法的原理边界、可复现的代码、参数怎么调、以及我踩过的坑一次讲清楚。你跟着走能跑通最小demo也能看到每种方法在什么数据上会翻车。免费python源码大全里这类脚本一抓一大把但大多数只给你一个能跑的壳子参数为什么这么设、换一批数据还灵不灵基本没人讲。这篇补的就是这一段。2. 三种方法的原理边界与选型判断2.1 TF-IDF统计词频和逆文档频率的加权逻辑TF-IDF的核心思想非常朴素一个词在当前文档里出现得越多TF高同时在所有文档里出现得越少IDF高它就越可能是这篇文档的关键词。TF通常是词频归一化IDF是总文档数除以包含该词的文档数再取对数。两者相乘得到权重排序取TopN。这个逻辑成立的前提是你有一批文档而不是一篇文档。IDF必须靠语料库统计出来单篇文档算不了IDF。所以TF-IDF天然适合从一批文章里各抽各的关键词这种场景比如给1000篇新闻各打5个标签。如果你只有一篇文章TF-IDF退化成纯词频排序效果会明显下降——高频的的是我们这类词会霸榜除非你有一份足够好的停用词表。另一个边界是TF-IDF完全不理解语义。苹果手机和iPhone在它眼里是两个无关的词降价和促销也毫无关联。同义词、近义词、缩写它一概不认。所以TF-IDF适合做粗筛和基线不适合做需要语义理解的精细抽取。2.2 TextRank把词当节点用共现窗口建图TextRank借用了PageRank的思路把每个词当成图里的一个节点如果两个词在同一个滑动窗口通常窗口大小5到10内共现就在它们之间连一条边。然后迭代计算每个节点的重要性——一个词如果和很多重要词共现它自己也重要。相比TF-IDFTextRank有两个明显优势。第一它不需要语料库单篇文档就能跑因为它靠的是文档内部的词共现结构。第二它考虑了词与词之间的关系能捕捉到一些TF-IDF漏掉的词——比如某个词词频不高但它总是和核心词一起出现TextRank会给它加分。但TextRank也有软肋。它对分词质量极其敏感分词错了共现窗口里全是噪声图就建歪了。窗口大小这个参数也很玄学太小比如2图太稀疏很多词孤立太大比如20图太稠密区分度下降。我一般从5开始试根据文档平均长度微调。另外TextRank在短文本上表现不稳定因为短文本共现关系本来就少图容易退化成几个孤岛。2.3 预训练语义法用句向量相似度找关键词前两种方法本质上都是词袋模型丢掉了词序和语义。预训练语义法的思路完全不同先把候选词和整篇文档都编码成向量然后算候选词向量和文档向量的余弦相似度相似度高的就是关键词。KeyBERT就是这套逻辑的典型实现——用BERT类模型做编码器候选词通过n-gram切分或分词得到。这种方法最大的好处是能理解语义。降价和促销在向量空间里距离很近即使它们从不在同一篇文档里共现语义法也能把它们关联起来。对于同义词多、表达灵活的领域比如用户评论、社交媒体文本语义法通常比前两种高一个档次。代价是计算资源。BERT类模型推理需要GPU或者至少是性能不错的CPU处理大批量文档时速度远不如TF-IDF。而且模型选择很关键——通用预训练模型在垂直领域医疗、法律、金融上可能表现平平需要领域微调或者换用领域模型。另外候选词的切分策略直接影响结果n-gram的n设多少、要不要过滤停用词都是需要调的。2.4 三种方法的选型对照表维度TF-IDFTextRank预训练语义法是否需要语料库需要算IDF不需要不需要单篇文档可用性差好好语义理解能力无弱靠共现强计算速度极快快慢依赖模型对分词质量敏感度中高低可用n-gram短文本表现中差好典型适用场景批量文档打标签单篇长文抽取语义精细抽取选型建议很直接如果你有大批量文档、追求速度和基线效果TF-IDF起步如果你处理的是单篇长文、想要比TF-IDF好一点又不想上模型TextRank如果语义准确性是硬指标、资源不是瓶颈上预训练语义法。实际项目里我经常把TF-IDF和TextRank的结果做交集或加权融合比单用一种稳。3. 用jieba把三种方法跑通的最小代码3.1 环境准备与依赖安装先把环境搭好。Python 3.8及以上都行核心依赖就几个jieba做分词scikit-learn提供TF-IDF实现jieba.analyse自带TextRankKeyBERT需要单独装。如果你用vscode python环境配置记得选对解释器不然pip装到别的环境里去了。pip install jieba scikit-learn keybert sentence-transformersjieba是中文分词的标配安装后自带一份词典开箱能用。scikit-learn的TfidfVectorizer封装好了TF-IDF计算不用自己手写。KeyBERT底层依赖sentence-transformers第一次运行会自动下载预训练模型需要网络能访问模型仓库。如果下载慢可以提前把模型缓存到本地。提示KeyBERT默认用的模型是paraphrase-MiniLM-L6-v2英文为主。中文场景建议换成shibing624/text2vec-base-chinese或者BAAI/bge-small-zh-v1.5效果差别很大。3.2 TF-IDF抽取TfidfVectorizer参数怎么设import jieba from sklearn.feature_extraction.text import TfidfVectorizer # 假设有一批文档 docs [ 苹果公司发布了新款手机搭载最新芯片拍照性能大幅提升, 国产新能源汽车销量持续增长电池技术成为竞争焦点, 苹果手机降价促销消费者购买意愿明显增强, 新能源汽车补贴政策调整行业面临重新洗牌, ] # 用jieba分词空格连接 def tokenize(text): return .join(jieba.cut(text)) corpus [tokenize(d) for d in docs] # 关键参数设置 vectorizer TfidfVectorizer( token_patternr(?u)\b\w\b, # 匹配中文词 max_df0.8, # 忽略出现在80%以上文档中的词 min_df1, # 至少出现在1篇文档中 max_features100 # 最多保留100个特征词 ) tfidf_matrix vectorizer.fit_transform(corpus) feature_names vectorizer.get_feature_names_out() # 取第一篇文档的Top5关键词 import numpy as np doc_idx 0 scores tfidf_matrix[doc_idx].toarray()[0] top_indices np.argsort(scores)[::-1][:5] for i in top_indices: if scores[i] 0: print(feature_names[i], round(scores[i], 4))这段代码的逻辑是先用jieba把每篇文档切成空格分隔的词序列然后TfidfVectorizer统计词频和逆文档频率。max_df0.8的作用是过滤掉太常见的词——如果某个词在80%以上的文档里都出现它区分度太低不该当关键词。min_df1表示至少出现一次就保留语料少的时候设1语料大可以设2或3过滤低频噪声。max_features控制最终词表大小太大计算慢太小可能漏掉关键词。注意token_pattern这个参数。scikit-learn默认的词模式是匹配两个及以上字母数字字符对中文不友好必须改成r(?u)\b\w\b才能正确匹配中文词。这是很多人第一次用TF-IDF处理中文时翻车的地方——跑出来全是空结果查半天才发现是token_pattern的问题。3.3 TextRank抽取jieba.analyse的窗口与TopNimport jieba.analyse text 苹果公司发布了新款手机搭载最新芯片拍照性能大幅提升。苹果手机降价促销消费者购买意愿明显增强。 # 提取Top10关键词带权重 keywords jieba.analyse.textrank( text, topK10, withWeightTrue, allowPOS(ns, n, vn, v) # 只保留地名、名词、动名词、动词 ) for word, weight in keywords: print(word, round(weight, 4))jieba的textrank接口很简洁但有几个参数必须理解。allowPOS控制哪些词性的词能进入候选集默认是(ns, n, vn, v)也就是地名、名词、动名词、动词。这个设置能过滤掉大部分虚词但也会漏掉一些有意义的形容词或副词。如果你的领域里形容词是关键比如情感分析需要把a加进去。窗口大小jieba没有直接暴露参数它内部用的是固定窗口默认5。如果你需要调整窗口得自己实现TextRank或者改jieba源码。我一般不建议为了调窗口去改源码而是通过调整allowPOS和分词词典来间接影响图结构。withWeightTrue返回权重值方便你做阈值过滤。实际用的时候我会设一个权重下限比如0.1以下的直接丢掉避免输出一堆低质量词。3.4 KeyBERT语义抽取中文模型替换与n-gram设置from keybert import KeyBERT # 替换为中文预训练模型 kw_model KeyBERT(modelshibing624/text2vec-base-chinese) doc 苹果公司发布了新款手机搭载最新芯片拍照性能大幅提升。苹果手机降价促销消费者购买意愿明显增强。 keywords kw_model.extract_keywords( doc, keyphrase_ngram_range(1, 2), # 候选词长度1到2 stop_wordsNone, # 中文停用词需自行传入 top_n8, use_mmrTrue, # 用MMR做多样性去重 diversity0.5 # 多样性强度 ) for kw, score in keywords: print(kw, round(score, 4))KeyBERT的核心参数是keyphrase_ngram_range它决定候选词的长度范围。(1,2)表示候选词可以是单个词或两个词的组合。中文里很多关键概念是双词组合比如新能源汽车拍照性能所以设(1,2)或(1,3)比较合理。设太大比如(1,5)会产生很多无意义的长短语。use_mmrTrue开启最大边际相关性去重避免抽出来的关键词全是同义词。diversity参数控制去重强度0到1之间越大结果越多样。我一般设0.5到0.7太低会重复太高会引入不相关的词。中文停用词KeyBERT不会自动处理需要你自己传一个停用词列表。常见做法是加载一份中文停用词表在extract_keywords之前先对文档做停用词过滤或者通过stop_words参数传入。注意KeyBERT第一次运行会下载模型如果网络不通会卡住。可以提前用sentence-transformers把模型下载到本地缓存目录再指定本地路径加载。4. 参数调优与效果验证的实操细节4.1 停用词表三种方法共用的地基停用词表是中文关键词抽取里最容易被忽视、又最影响结果的一环。不管用哪种方法如果停用词没处理好的了是在我们这个这类词会大量占据候选位置。TF-IDF里它们IDF低但TF高TextRank里它们和所有词都共现导致权重虚高语义法里它们和文档向量相似度也不低。我一般准备三份停用词表叠加使用通用停用词表网上能找到的标准表大概1200词左右、领域停用词表比如做医疗文本时把患者治疗这类太泛的词加进去、以及项目特有的高频无意义词跑一遍全量数据把Top50高频但无区分度的词手动加进去。三份合并去重后加载。def load_stopwords(*paths): stopwords set() for path in paths: with open(path, encodingutf-8) as f: for line in f: w line.strip() if w: stopwords.add(w) return stopwords stopwords load_stopwords(stopwords_general.txt, stopwords_domain.txt)加载后在分词阶段就过滤掉而不是在抽取后过滤。因为停用词进入图结构或向量计算会污染整个结果事后过滤只能去掉输出里的停用词去不掉它们对权重的干扰。4.2 分词质量对TextRank的放大效应TextRank对分词质量的敏感度是三种方法里最高的。原因在于它依赖词共现建图如果分词把新能源汽车切成了新能源和汽车这两个词各自和不同的词共现图结构就变了最终权重排序也会变。更糟的是如果切错了比如把性能提升切成性能和提升而提升又和提高增加等词共现图里会出现一堆语义相近但分散的节点稀释了真正关键词的权重。解决办法有两个。第一加载自定义词典把领域专有名词加进去确保它们不被切碎。jieba支持load_userdict词典格式是词 词频 词性。jieba.load_userdict(user_dict.txt) # user_dict.txt内容示例 # 新能源汽车 100 n # 拍照性能 50 n # 芯片制程 30 n第二对分词结果做后处理把被切碎的词合并回去。比如维护一个必须合并的词对列表扫描分词结果遇到就合并。这个方法比较笨但有效适合领域词汇量不大的场景。4.3 用人工标注集验证抽取效果三种方法跑出来的结果怎么判断好坏不能靠感觉。我一般会标一个小的验证集选20到50篇文档每篇人工标3到5个真正的关键词然后算抽取结果和标注的命中率Recall和准确率Precision。def evaluate(extracted, labeled, topk5): extracted_set set([w for w, _ in extracted[:topk]]) labeled_set set(labeled) hit extracted_set labeled_set precision len(hit) / len(extracted_set) if extracted_set else 0 recall len(hit) / len(labeled_set) if labeled_set else 0 f1 2 * precision * recall / (precision recall) if (precision recall) else 0 return precision, recall, f1这个评估很粗糙因为关键词没有唯一正确答案人工标注也有主观性。但它能给你一个相对比较的基准——同一批数据上TF-IDF的F1是0.4TextRank是0.5KeyBERT是0.6那你就知道语义法在这个场景确实更好。如果三种方法F1都在0.3以下那大概率是分词或停用词出了问题先回去查那两块。验证集不用大20篇就够看出趋势。关键是标注要一致——同一个人标标准统一。我见过有人标的时候把苹果标了另一个人觉得苹果太泛没标结果评估出来一堆假阴性白折腾。5. 避坑与排查关键词抽取最常见的五个翻车现场5.1 现象TF-IDF跑出来全是单字没有完整词原因基本只有一个分词没做或者做了但没把词用空格连接。TfidfVectorizer默认按空格和标点切分如果你直接把中文原文喂进去它会按字符切每个汉字变成一个token。另一个可能是token_pattern没改默认模式匹配不到中文词。解决确认分词步骤存在且分词结果用空格join后再传给vectorizer。同时检查token_pattern是否设为r(?u)\b\w\b。这两步做完输出应该就是完整词了。5.2 现象TextRank结果里全是表示认为进行这类动词原因是allowPOS设置太宽或者停用词表没覆盖这类高频动词。jieba默认的allowPOS包含v而表示认为进行都是动词词频高、共现广TextRank权重自然高。解决收紧allowPOS去掉v只保留n和vn。或者在停用词表里把这类泛动词加进去。更好的做法是两者结合——allowPOS保留n和vn停用词表补充领域泛动词。5.3 现象KeyBERT第一次运行卡住不动这是模型下载的问题。KeyBERT底层用sentence-transformers第一次运行会从模型仓库下载预训练权重。如果网络环境访问模型仓库慢或者不通就会卡在下载步骤没有任何报错看起来像死机。解决提前手动下载模型。可以用sentence-transformers的SentenceTransformer类指定本地路径加载或者设置环境变量把缓存目录指向一个已经下载好的位置。如果团队里有其他人已经下载过直接拷贝缓存目录最省事。5.4 现象三种方法抽出的关键词高度重叠没有互补效果这通常说明你的数据太规范了——文档长度适中、用词标准、没有太多同义词变体。在这种情况下三种方法的差异确实不大因为TF-IDF和TextRank在规范文本上表现接近语义法的优势也体现不出来。解决如果业务需要差异化结果可以调整各方法的参数让它们侧重不同。比如TF-IDF调大max_df保留更多常见词TextRank调小窗口让图更稀疏KeyBERT调大diversity让结果更多样。然后做加权融合而不是简单取交集。如果数据本身就很规范那用TF-IDF就够了不必强上语义法。5.5 现象换一批数据后效果断崖式下降最常见的原因是停用词表和自定义词典没有跟着更新。第一版停用词表是基于第一批数据做的换了一批数据后新的高频无意义词没被过滤新的领域术语没被加入词典分词切错、停用词漏网效果自然崩。解决每次换数据源先跑一遍全量数据统计Top100高频词人工过一遍把该加停用词的加进去该加自定义词典的加进去。这个步骤花不了半小时但能避免后面大量的调试时间。我一般把这个流程固化成脚本换数据时自动跑一遍高频词统计输出候选列表供人工确认。6. 把三种方法融合成一个可复用的抽取管道单用一种方法总有短板实际项目里我更倾向于把三种方法串成一个管道TF-IDF做粗筛TextRank做补充KeyBERT做精排。具体做法是先用TF-IDF和TextRank各取Top20合并去重得到候选集然后用KeyBERT对候选集做语义打分按语义分排序取最终TopN。这样既保留了统计方法的召回广度又用语义方法提升了排序精度。def hybrid_extract(text, topk10): # 第一步TF-IDF粗筛 tfidf_kws tfidf_extract(text, topk20) # 第二步TextRank补充 tr_kws textrank_extract(text, topk20) # 合并候选集 candidates list(set([w for w, _ in tfidf_kws] [w for w, _ in tr_kws])) # 第三步KeyBERT语义精排 candidate_text .join(candidates) semantic_kws kw_model.extract_keywords( text, candidatescandidates, top_ntopk ) return semantic_kws这个管道的参数需要根据数据调。候选集大小20是个经验值文档长可以设30文档短设10。KeyBERT的candidates参数直接传入候选词列表避免它自己切n-gram产生噪声。最终TopN根据业务需求定做标签一般5到8个做搜索召回可以10到15个。验证管道效果的方法和单方法一样用人工标注集算F1。我实测下来融合管道在多数中文数据集上比单用TF-IDF提升15%到25%的F1比单用KeyBERT提升5%到10%代价是计算时间增加——KeyBERT那一步是瓶颈。如果对延迟敏感可以只对TF-IDF和TextRank的交集做语义精排候选集小一半速度快不少。最后一个习惯每次上线新数据前先抽100篇跑一遍人工看Top10结果确认没有明显翻车再全量跑。这个后悔药花10分钟能省掉后面几小时的排查。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑