
简介这份资源面向Python中文文本处理的学习者与课程设计者围绕关键词抽取这一典型NLP任务系统整理了TF-IDF、TextRank和Word2Vec词向量聚类三种实现方法并配有原理说明、流程梳理与代码讲解适合作为课程设计、毕业设计或NLP入门实践的参考。压缩包共31个文件约1.78MB包含4个py源码脚本、14个csv数据与结果文件、8张png运行截图、2个txt词表与词性标注参考、1个docx课程论文及README说明覆盖从数据、代码到论文与截图的完整链路。已有2116人学习下载。读者可据此掌握三种关键词抽取方法的差异与适用场景直接复用源码与样例数据跑通实验并借助论文与截图理解实验设计思路文中还提示了Word2Vec语料扩充、标题权重、聚类数设置及高频词过滤等可改进细节便于进一步优化与排错。1. 中文关键词抽取三种 Python 路线到底该怎么选手上有一批中文文本——可能是用户评论、工单记录、论文摘要或者商品标题——老板让你把关键词提出来。你打开搜索引擎搜Python 中文关键词抽取跳出来一堆方案jieba、TF-IDF、TextRank、LDA、BERT……每个都有人写教程但没人告诉你什么场景该用哪个。这个标题讲的正是这件事用 Python 把中文文本里的关键词抽出来并且给出三种可落地的实现路线。它解决的不是能不能抽的问题而是抽得准不准、跑得快不快、维护成本高不高的工程取舍。适合已经会写基础 Python、手里有真实中文语料、需要快速搭一套关键词管线的从业者。下面我按从轻到重的顺序把三种方法的原理、代码、参数和踩坑点逐一拆开。2. 三种关键词抽取路线的原理与选型逻辑2.1 统计路线TF-IDF 到底在算什么TF-IDF 的核心思想很朴素一个词在当前文档里出现得越多TF 高同时在所有文档里出现得越少IDF 高它就越可能是这篇文档的关键词。公式拆开看TF词频某词在单篇文档中的出现次数常见做法是取对数平滑避免长文档里高频词权重爆炸。IDF逆文档频率log(总文档数 / 包含该词的文档数)分母加 1 防止除零。最终得分 TF × IDF。这个方法的致命前提是你需要一个文档集合而不是单篇文本。只有一篇文本时 IDF 退化成常数TF-IDF 就变成了纯粹的词频统计效果大打折扣。很多教程拿一段话跑 TF-IDF 然后说效果不好问题就出在这里——不是算法不行是输入不满足前提。TF-IDF 的优势在于速度极快、可解释性强、无需训练。缺点是它完全基于词频不理解语义。苹果和梨在它眼里毫无关系同义词无法合并而且对分词质量极度敏感。2.2 图模型路线TextRank 怎么把词排成图TextRank 借用了 PageRank 的思路把每个词看作图中的一个节点如果两个词在滑动窗口内共现就在它们之间连一条边。然后迭代计算每个节点的重要性——一个词如果被很多重要词投票共现它自己也重要。关键参数有三个参数含义常用值影响window共现窗口大小2~5越大越能捕捉远距离关联但噪声也增多alpha阻尼系数0.85控制随机跳转概率一般不改max_iter最大迭代次数100~200太小不收敛太大浪费算力TextRank 不需要外部文档集合单篇文本就能跑这是它比 TF-IDF 更实用的地方。但它对短文本效果一般——窗口内共现次数太少图太稀疏排序结果不稳定。2.3 深度学习路线BERT 词向量做语义关键词前两种方法都是词袋思路丢掉了上下文。BERT 类模型可以把每个词映射成一个包含上下文信息的稠密向量然后通过计算词向量与全文向量的相似度来打分。常见做法是用预训练模型对文本编码取最后一层或倒数第二层的 token 向量。对 token 向量做均值池化得到文档向量。计算每个 token 向量与文档向量的余弦相似度作为关键词得分。对 token 做词边界还原因为 BERT 用的是子词切分合并同一词的子词得分。这条路效果最好但代价也最大需要 GPU 或较长的 CPU 推理时间模型文件动辄几百 MB部署复杂度高。适合对精度要求高、有算力预算、且文本语义复杂的场景。选型建议如果只有单篇或少量文本、追求快速上线优先 TextRank如果有文档集合且需要批量处理TF-IDF 性价比最高如果语义歧义严重比如银行是金融机构还是河岸再上 BERT。3. 用 jieba 跑通 TF-IDF 与 TextRank 的最小命令3.1 环境准备与分词基线先把环境搭好。Python 3.8 及以上都行核心依赖只有 jiebapip install jieba如果你还需要做词性过滤比如只保留名词和动词作为候选词再装一个pip install jieba posseg分词是所有中文关键词抽取的第一步分词质量直接决定后续效果。jieba 提供三种分词模式import jieba import jieba.posseg as pseg text 自然语言处理是人工智能领域的一个重要方向中文分词是自然语言处理的基础任务。 # 精确模式适合文本分析 words jieba.lcut(text) print(精确模式:, words) # 全模式把所有可能的词都切出来适合搜索引擎召回 words_full jieba.lcut(text, cut_allTrue) print(全模式:, words_full) # 搜索引擎模式在精确模式基础上对长词再切分 words_search jieba.lcut_for_search(text) print(搜索模式:, words_search) # 词性标注过滤掉虚词 for word, flag in pseg.cut(text): if flag in (n, vn, v): # 只保留名词、动名词、动词 print(f{word}/{flag}, end )逻辑说明lcut返回列表cut返回生成器。词性标注用pseg.cutflag 是词性标签n代表名词v代表动词vn是动名词。实际做关键词抽取时我一般会过滤掉的、了、是、在这类停用词和虚词只保留实词作为候选。参数说明cut_allTrue会把自然语言切成自然、语言、自然语言召回高但噪声大lcut_for_search适合做搜索索引对关键词抽取来说精确模式通常够用。3.2 TF-IDF 抽取语料准备与参数调优TF-IDF 需要多篇文档。假设你有一个docs列表每个元素是一篇文本import jieba.analyse # 模拟语料至少 5 篇以上IDF 才有意义 docs [ 机器学习是人工智能的核心深度学习是机器学习的一个分支。, 自然语言处理让计算机理解人类语言中文分词是基础任务。, Python 是数据科学最常用的编程语言拥有丰富的机器学习库。, 关键词抽取可以从文本中提取核心信息常用于搜索和推荐。, 深度学习模型需要大量数据和算力训练成本较高。, ] # 第一步用所有文档构建 IDF 语料 # jieba 的 extract_tags 支持传入 idf 频率文件但更常见的做法是 # 先用所有文档内容喂给 jieba让它统计词频 corpus .join(docs) jieba.analyse.set_idf_path(idf.txt) # 可选自定义 IDF 文件 # 第二步对目标文档抽取关键词 target 自然语言处理中的关键词抽取方法对比包括 TF-IDF 和 TextRank。 keywords jieba.analyse.extract_tags( target, topK10, # 返回前 10 个关键词 withWeightTrue, # 同时返回权重 allowPOS(n, vn, v) # 只保留指定词性 ) for word, weight in keywords: print(f{word}\t{weight:.4f})逻辑说明extract_tags内部会先分词再计算每个词的 TF-IDF 值最后按权重排序返回 topK。withWeightTrue让你能看到每个词的得分方便设阈值过滤。allowPOS是词性白名单能有效去掉的、了这类无意义高频词。参数说明topK控制返回数量一般 5~20 之间allowPOS建议至少包含n和vn如果文本中动作描述重要再加上v。注意 jieba 自带的 IDF 语料是通用领域的如果你的文本是垂直领域比如医疗、法律最好用自己的语料重新统计 IDF否则领域专有词会被低估。3.3 TextRank 抽取窗口大小怎么定TextRank 的调用方式和 TF-IDF 几乎一样import jieba.analyse text 关键词抽取是自然语言处理的重要任务。TextRank 算法基于图模型通过词语共现关系计算重要性。与 TF-IDF 不同TextRank 不需要外部语料。 keywords jieba.analyse.textrank( text, topK8, withWeightTrue, allowPOS(n, vn, v) ) for word, weight in keywords: print(f{word}\t{weight:.4f})逻辑说明textrank内部构建词共现图默认窗口大小为 5迭代 100 次。返回结果按 PageRank 值降序排列。参数说明jieba 的textrank接口没有直接暴露 window 参数它写死在源码里是 5。如果你需要调整窗口得改 jieba 源码或者自己实现图构建。我一般不改因为 5 在多数中文文本上表现稳定。真正需要调的是allowPOS——TextRank 对词性更敏感如果不过滤虚词会在图中形成高连接节点把真正的关键词挤下去。提示TextRank 在短文本少于 50 字上容易翻车因为共现图太稀疏。如果文本很短建议直接用 TF-IDF 或者补充外部语料。4. 避坑与排查中文关键词抽取的五个血泪教训4.1 分词错误导致关键词完全跑偏现象抽取出的关键词里出现了的自、了机这种明显不该存在的词。原因jieba 默认词典不含你的领域专有词导致切分错误。比如机器学习被切成机器和学习自然语言被切成自然和语言。解决加载自定义词典。把领域词写成一个文本文件每行一个词然后jieba.load_userdict(user_dict.txt)词典格式是词语 词频 词性词频和词性可省略。加载后 jieba 会优先按你的词典切分。另外可以用jieba.suggest_freq动态调整某个词的成词概率jieba.suggest_freq((机器学习,), True) # 强制机器学习作为一个词4.2 IDF 语料不匹配导致领域词被淹没现象在医疗文本中患者、疗效这些词排不到前面反而进行、表示排得很高。原因jieba 自带 IDF 是基于通用新闻语料统计的在垂直领域里词频分布完全不同。解决用自己的语料重新计算 IDF。步骤是收集至少几百篇同领域文档用 jieba 分词后统计每个词出现在多少篇文档中然后按 IDF 公式算出每个词的 IDF 值写成 jieba 要求的格式import math import jieba from collections import Counter docs [...] # 你的领域文档列表 doc_count len(docs) word_doc_freq Counter() for doc in docs: words set(jieba.lcut(doc)) for w in words: word_doc_freq[w] 1 with open(custom_idf.txt, w, encodingutf-8) as f: for word, freq in word_doc_freq.items(): idf math.log(doc_count / (freq 1)) f.write(f{word} {idf:.6f}\n)然后jieba.analyse.set_idf_path(custom_idf.txt)加载。注意文件格式是词语 空格 IDF值每行一个。4.3 TextRank 在单句上返回空结果现象输入一句话textrank返回空列表或只有一两个词。原因共现窗口内没有足够的词对图几乎是空的PageRank 迭代无法产生有意义的排序。解决短文本场景换用 TF-IDF或者手动扩大窗口。如果坚持用 TextRank可以把多段相关文本拼在一起再跑人为增加共现机会。4.4 停用词表不全会引入噪声现象关键词里混入了我们、可以、这个这类词。原因allowPOS过滤了词性但有些代词和助词被 jieba 标注成了名词或动词。解决维护一份停用词表在抽取结果上再做一层过滤stopwords set() with open(stopwords.txt, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) keywords [(w, s) for w, s in keywords if w not in stopwords]停用词表可以从网上找通用版本再根据自己领域补充。我一般会把进行、表示、问题、方法这类在技术文档中高频但无区分度的词也加进去。4.5 权重阈值设错导致关键词过多或过少现象topK10返回的词里有一半不相关或者只返回 3 个就没了。原因不同文本的关键词分布差异很大固定 topK 不灵活。解决改用权重阈值。先跑一次看权重分布然后设一个截断值keywords jieba.analyse.extract_tags(text, topK50, withWeightTrue) # 观察权重分布后设阈值 threshold 0.1 filtered [(w, s) for w, s in keywords if s threshold]或者用权重下降拐点法把权重从高到低排列找到下降最快的那个位置作为截断点。5. 进阶技巧用 BERT 词向量做语义关键词抽取前面三种方法TF-IDF、TextRank、以及它们的变体本质上都是基于词频或共现统计的遇到同义词和多义词就会翻车。比如苹果发布了新手机和苹果是一种水果统计方法无法区分这两个苹果。如果你对精度有要求可以上 BERT 词向量。核心思路是用预训练模型把文本编码成 token 向量然后计算每个 token 与全文的语义相似度相似度高的就是关键词。这里用transformers库配合一个中文预训练模型from transformers import BertTokenizer, BertModel import torch import numpy as np # 加载中文 BERT 模型和分词器 model_name bert-base-chinese tokenizer BertTokenizer.from_pretrained(model_name) model BertModel.from_pretrained(model_name) model.eval() text 关键词抽取是自然语言处理的重要任务BERT 模型可以捕捉上下文语义信息。 # 编码 inputs tokenizer(text, return_tensorspt, truncationTrue, max_length512) with torch.no_grad(): outputs model(**inputs) # 取最后一层的 token 向量 hidden_states outputs.last_hidden_state[0] # shape: (seq_len, hidden_dim) # 计算文档向量均值池化跳过 [CLS] 和 [SEP] token_vectors hidden_states[1:-1] # 去掉特殊 token doc_vector token_vectors.mean(dim0) # 计算每个 token 与文档向量的余弦相似度 def cosine_sim(a, b): return torch.dot(a, b) / (torch.norm(a) * torch.norm(b)) scores [] tokens tokenizer.convert_ids_to_tokens(inputs[input_ids][0][1:-1]) for i, vec in enumerate(token_vectors): score cosine_sim(vec, doc_vector).item() scores.append((tokens[i], score)) # 按得分排序 scores.sort(keylambda x: x[1], reverseTrue) # 合并子词BERT 用 ## 表示子词续接 merged [] for token, score in scores: if token.startswith(##) and merged: merged[-1] (merged[-1][0] token[2:], max(merged[-1][1], score)) else: merged.append((token, score)) for word, score in merged[:10]: print(f{word}\t{score:.4f})逻辑说明last_hidden_state是 BERT 最后一层的输出每个 token 对应一个 768 维向量。均值池化得到文档向量后用余弦相似度衡量每个 token 与全文的语义相关度。子词合并是因为 BERT 的 tokenizer 会把一个中文词拆成多个子词比如关键词可能被拆成关、键、词需要还原。参数说明max_length512是 BERT 的最大输入长度超长文本需要截断或分段。model_name可以换成更大的模型如bert-large-chinese或领域预训练模型但推理时间会成倍增加。如果显存不够可以用model.half()转半精度或者用 ONNX 加速推理。这条路的效果确实比统计方法好但代价也明显单条文本推理在 CPU 上可能要几百毫秒到几秒GPU 上也要几十毫秒。如果你的场景是离线批量处理可以接受如果是在线实时抽取需要做模型蒸馏或换用更小的模型。我自己的习惯是先用 TextRank 快速跑一版看效果如果关键词质量能接受就不折腾 BERT如果发现同义词、多义词问题严重再上 BERT 做精排。不要一上来就上最重的方案很多时候统计方法加一个好的自定义词典就能解决 80% 的问题。希望帮到你。本文还有配套的精品资源点击获取